begin clock rewrite
This commit is contained in:
@ -32,7 +32,7 @@
|
|||||||
"fflate": "^0.8.0",
|
"fflate": "^0.8.0",
|
||||||
"highlight.js": "^11.9.0",
|
"highlight.js": "^11.9.0",
|
||||||
"jisg": "^0.9.7",
|
"jisg": "^0.9.7",
|
||||||
"lru-cache": "^10.0.1",
|
"lru-cache": "^10.2.0",
|
||||||
"marked": "^7.0.3",
|
"marked": "^7.0.3",
|
||||||
"osc": "^2.4.4",
|
"osc": "^2.4.4",
|
||||||
"postcss": "^8.4.27",
|
"postcss": "^8.4.27",
|
||||||
|
|||||||
@ -53,8 +53,8 @@ export const beat = (app: Editor) => (n: number | number[] = 1, nudge: number =
|
|||||||
const nArray = Array.isArray(n) ? n : [n];
|
const nArray = Array.isArray(n) ? n : [n];
|
||||||
const results: boolean[] = nArray.map(
|
const results: boolean[] = nArray.map(
|
||||||
(value) =>
|
(value) =>
|
||||||
(app.clock.pulses_since_origin - Math.floor(nudge * app.clock.ppqn)) %
|
(app.clock.pulses_since_origin - Math.round(nudge * app.clock.ppqn)) %
|
||||||
Math.floor(value * app.clock.ppqn) === 0,
|
Math.round(value * app.clock.ppqn) === 0,
|
||||||
);
|
);
|
||||||
return results.some((value) => value === true);
|
return results.some((value) => value === true);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -49,24 +49,23 @@ export async function tryEvaluate(application: Editor, code: File, timeout = 500
|
|||||||
* @returns A Promise that resolves when the evaluation is complete.
|
* @returns A Promise that resolves when the evaluation is complete.
|
||||||
*/
|
*/
|
||||||
code.evaluations!++;
|
code.evaluations!++;
|
||||||
const candidateCode = code.candidate;
|
|
||||||
|
|
||||||
const cachedFunction = cache.get(candidateCode);
|
const cachedFunction = cache.get(code.candidate);
|
||||||
if (cachedFunction) {
|
if (cachedFunction) {
|
||||||
cachedFunction.call(application.api);
|
cachedFunction.call(application.api);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const wrappedCode = `let i = ${code.evaluations}; ${candidateCode}`;
|
const wrappedCode = `let i = ${code.evaluations}; ${code.candidate}`;
|
||||||
const isCodeValid = await Promise.race([
|
const isCodeValid = await Promise.race([
|
||||||
tryCatchWrapper(application, wrappedCode),
|
tryCatchWrapper(application, wrappedCode),
|
||||||
delay(timeout)
|
delay(timeout)
|
||||||
]);
|
]);
|
||||||
|
|
||||||
if (isCodeValid) {
|
if (isCodeValid) {
|
||||||
code.committed = candidateCode;
|
code.committed = code.candidate;
|
||||||
const newFunction = new Function(`"use strict"; ${codeReplace(wrappedCode)}`);
|
const newFunction = new Function(`"use strict"; ${codeReplace(wrappedCode)}`);
|
||||||
addFunctionToCache(candidateCode, newFunction);
|
addFunctionToCache(code.candidate, newFunction);
|
||||||
} else {
|
} else {
|
||||||
application.api.logOnce("Compilation error!");
|
application.api.logOnce("Compilation error!");
|
||||||
await delay(100);
|
await delay(100);
|
||||||
@ -107,3 +106,7 @@ export const evaluateOnce = async (
|
|||||||
*/
|
*/
|
||||||
await tryCatchWrapper(application, code);
|
await tryCatchWrapper(application, code);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -12,28 +12,10 @@ export interface TimePosition {
|
|||||||
*/
|
*/
|
||||||
bar: number;
|
bar: number;
|
||||||
beat: number;
|
beat: number;
|
||||||
pulse: number;
|
tick: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class Clock {
|
export class Clock {
|
||||||
/**
|
|
||||||
* The Clock Class is responsible for keeping track of the current time.
|
|
||||||
* It is also responsible for starting and stopping the Clock TransportNode.
|
|
||||||
*
|
|
||||||
* @param app - The main application instance
|
|
||||||
* @param ctx - The current AudioContext used by app
|
|
||||||
* @param transportNode - The TransportNode helper
|
|
||||||
* @param bpm - The current beats per minute value
|
|
||||||
* @param time_signature - The time signature
|
|
||||||
* @param time_position - The current time position
|
|
||||||
* @param ppqn - The pulses per quarter note
|
|
||||||
* @param tick - The current tick since origin
|
|
||||||
* @param running - Is the clock running?
|
|
||||||
* @param lastPauseTime - The last time the clock was paused
|
|
||||||
* @param lastPlayPressTime - The last time the clock was started
|
|
||||||
* @param totalPauseTime - The total time the clock has been paused / stopped
|
|
||||||
*/
|
|
||||||
|
|
||||||
ctx: AudioContext;
|
ctx: AudioContext;
|
||||||
logicalTime: number;
|
logicalTime: number;
|
||||||
transportNode: TransportNode | null;
|
transportNode: TransportNode | null;
|
||||||
@ -51,7 +33,7 @@ export class Clock {
|
|||||||
public app: Editor,
|
public app: Editor,
|
||||||
ctx: AudioContext,
|
ctx: AudioContext,
|
||||||
) {
|
) {
|
||||||
this.time_position = { bar: 0, beat: 0, pulse: 0 };
|
this.time_position = { bar: 0, beat: 0, tick: 0 };
|
||||||
this.time_signature = [4, 4];
|
this.time_signature = [4, 4];
|
||||||
this.logicalTime = 0;
|
this.logicalTime = 0;
|
||||||
this.tick = 0;
|
this.tick = 0;
|
||||||
@ -75,19 +57,15 @@ export class Clock {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
convertTicksToTimeposition(ticks: number): TimePosition {
|
|
||||||
/**
|
|
||||||
* Converts ticks to a TimePosition object.
|
|
||||||
* @param ticks The number of ticks to convert.
|
|
||||||
* @returns The TimePosition object representing the converted ticks.
|
|
||||||
*/
|
|
||||||
|
|
||||||
|
|
||||||
|
convertTicksToTimeposition(ticks: number): TimePosition {
|
||||||
const beatsPerBar = this.app.clock.time_signature[0]!;
|
const beatsPerBar = this.app.clock.time_signature[0]!;
|
||||||
const ppqnPosition = ticks % this.app.clock.ppqn;
|
const tickPosition = ticks % this.app.clock.ppqn;
|
||||||
const beatNumber = Math.floor(ticks / this.app.clock.ppqn);
|
const beatNumber = Math.floor(ticks / this.app.clock.ppqn);
|
||||||
const barNumber = Math.floor(beatNumber / beatsPerBar);
|
const barNumber = Math.floor(beatNumber / beatsPerBar);
|
||||||
const beatWithinBar = Math.floor(beatNumber % beatsPerBar);
|
const beatWithinBar = Math.floor(beatNumber % beatsPerBar);
|
||||||
return { bar: barNumber, beat: beatWithinBar, pulse: ppqnPosition };
|
return { bar: barNumber, beat: beatWithinBar, tick: tickPosition };
|
||||||
}
|
}
|
||||||
|
|
||||||
get ticks_before_new_bar(): number {
|
get ticks_before_new_bar(): number {
|
||||||
@ -97,7 +75,7 @@ export class Clock {
|
|||||||
*
|
*
|
||||||
* @returns number of ticks until next bar
|
* @returns number of ticks until next bar
|
||||||
*/
|
*/
|
||||||
const ticskMissingFromBeat = this.ppqn - this.time_position.pulse;
|
const ticskMissingFromBeat = this.ppqn - this.time_position.tick;
|
||||||
const beatsMissingFromBar = this.beats_per_bar - this.time_position.beat;
|
const beatsMissingFromBar = this.beats_per_bar - this.time_position.beat;
|
||||||
return beatsMissingFromBar * this.ppqn + ticskMissingFromBeat;
|
return beatsMissingFromBar * this.ppqn + ticskMissingFromBeat;
|
||||||
}
|
}
|
||||||
@ -109,7 +87,7 @@ export class Clock {
|
|||||||
*
|
*
|
||||||
* @returns number of ticks until next beat
|
* @returns number of ticks until next beat
|
||||||
*/
|
*/
|
||||||
return this.app.clock.pulses_since_origin + this.time_position.pulse;
|
return this.app.clock.pulses_since_origin + this.time_position.tick;
|
||||||
}
|
}
|
||||||
|
|
||||||
get beats_per_bar(): number {
|
get beats_per_bar(): number {
|
||||||
@ -183,7 +161,7 @@ export class Clock {
|
|||||||
if (ppqn > 0 && this._ppqn !== ppqn) {
|
if (ppqn > 0 && this._ppqn !== ppqn) {
|
||||||
this._ppqn = ppqn;
|
this._ppqn = ppqn;
|
||||||
this.transportNode?.setPPQN(ppqn);
|
this.transportNode?.setPPQN(ppqn);
|
||||||
this.logicalTime = this.realTime;
|
this.logicalTime = this.tick * this.pulse_duration_at_bpm(this.bpm);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -256,7 +234,7 @@ export class Clock {
|
|||||||
this.tick = 0;
|
this.tick = 0;
|
||||||
this.lastPauseTime = this.app.audioContext.currentTime;
|
this.lastPauseTime = this.app.audioContext.currentTime;
|
||||||
this.logicalTime = this.realTime;
|
this.logicalTime = this.realTime;
|
||||||
this.time_position = { bar: 0, beat: 0, pulse: 0 };
|
this.time_position = { bar: 0, beat: 0, tick: 0 };
|
||||||
this.app.api.MidiConnection.sendStopMessage();
|
this.app.api.MidiConnection.sendStopMessage();
|
||||||
this.transportNode?.stop();
|
this.transportNode?.stop();
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,5 +1,4 @@
|
|||||||
import { tryEvaluate } from "../Evaluator";
|
import { tryEvaluate } from "../Evaluator";
|
||||||
const zeroPad = (num, places) => String(num).padStart(places, "0");
|
|
||||||
|
|
||||||
export class TransportNode extends AudioWorkletNode {
|
export class TransportNode extends AudioWorkletNode {
|
||||||
|
|
||||||
@ -13,28 +12,41 @@ export class TransportNode extends AudioWorkletNode {
|
|||||||
|
|
||||||
/** @type {(this: MessagePort, ev: MessageEvent<any>) => any} */
|
/** @type {(this: MessagePort, ev: MessageEvent<any>) => any} */
|
||||||
handleMessage = (message) => {
|
handleMessage = (message) => {
|
||||||
|
let clock = this.app.clock;
|
||||||
|
const startTime = performance.now();
|
||||||
|
|
||||||
|
|
||||||
|
if (message.data.type === "time") {
|
||||||
|
console.log(message.data)
|
||||||
|
clock.time_position = {
|
||||||
|
tick: message.data.tick,
|
||||||
|
beat: message.data.beat,
|
||||||
|
bar: message.data.bar,
|
||||||
|
time: message.data.time,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
if (message.data.type === "bang") {
|
if (message.data.type === "bang") {
|
||||||
if (this.app.clock.running) {
|
if (this.app.clock.running) {
|
||||||
|
clock.time_position = clock.convertTicksToTimeposition(clock.tick);
|
||||||
if (this.app.settings.send_clock) {
|
this.app.settings.send_clock ?? this.app.api.MidiConnection.sendMidiClock();
|
||||||
this.app.api.MidiConnection.sendMidiClock();
|
|
||||||
}
|
tryEvaluate(
|
||||||
|
this.app,
|
||||||
const futureTimeStamp = this.app.clock.convertTicksToTimeposition(
|
this.app.exampleIsPlaying
|
||||||
this.app.clock.tick
|
? this.app.example_buffer
|
||||||
|
: this.app.global_buffer
|
||||||
);
|
);
|
||||||
this.app.clock.time_position = futureTimeStamp;
|
|
||||||
|
clock.incrementTick(message.data.bpm);
|
||||||
if (this.app.exampleIsPlaying) {
|
|
||||||
tryEvaluate(this.app, this.app.example_buffer);
|
|
||||||
} else {
|
|
||||||
tryEvaluate(this.app, this.app.global_buffer);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.app.clock.incrementTick(message.data.bpm);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
|
||||||
|
const endTime = performance.now();
|
||||||
|
const executionTime = endTime - startTime;
|
||||||
|
console.log(`Execution time: ${executionTime}ms`);
|
||||||
|
};
|
||||||
|
|
||||||
start() {
|
start() {
|
||||||
this.port.postMessage({ type: "start" });
|
this.port.postMessage({ type: "start" });
|
||||||
|
|||||||
@ -2,12 +2,16 @@ class TransportProcessor extends AudioWorkletProcessor {
|
|||||||
constructor(options) {
|
constructor(options) {
|
||||||
super(options);
|
super(options);
|
||||||
this.port.addEventListener("message", this.handleMessage);
|
this.port.addEventListener("message", this.handleMessage);
|
||||||
this.port.start();
|
this.bpm = 120;
|
||||||
this.nudge = 0;
|
this.nudge = 0;
|
||||||
this.started = false;
|
this.started = false;
|
||||||
this.bpm = 120;
|
|
||||||
this.ppqn = 48 * 2;
|
this.ppqn = 48 * 2;
|
||||||
|
this.timeSignature = [4, 4];
|
||||||
|
this.port.start();
|
||||||
this.currentPulsePosition = 0;
|
this.currentPulsePosition = 0;
|
||||||
|
this.startTime = 0;
|
||||||
|
this.pauseTime = 0;
|
||||||
|
this.totalPauseTime = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
handleMessage = (message) => {
|
handleMessage = (message) => {
|
||||||
@ -15,34 +19,62 @@ class TransportProcessor extends AudioWorkletProcessor {
|
|||||||
this.port.postMessage(message.data);
|
this.port.postMessage(message.data);
|
||||||
} else if (message.data.type === "start") {
|
} else if (message.data.type === "start") {
|
||||||
this.started = true;
|
this.started = true;
|
||||||
|
if (this.pauseTime) {
|
||||||
|
this.totalPauseTime += currentTime - this.pauseTime;
|
||||||
|
this.pauseTime = null;
|
||||||
|
} else {
|
||||||
|
this.startTime = currentTime
|
||||||
|
}
|
||||||
} else if (message.data.type === "pause") {
|
} else if (message.data.type === "pause") {
|
||||||
this.started = false;
|
this.started = false;
|
||||||
|
this.pauseTime = currentTime;
|
||||||
} else if (message.data.type === "stop") {
|
} else if (message.data.type === "stop") {
|
||||||
this.started = false;
|
this.started = false;
|
||||||
|
this.startTime = 0;
|
||||||
|
this.pauseTime = 0;
|
||||||
|
this.totalPauseTime = 0;
|
||||||
|
this.currentPulsePosition = 0;
|
||||||
} else if (message.data.type === "bpm") {
|
} else if (message.data.type === "bpm") {
|
||||||
this.bpm = message.data.value;
|
this.bpm = message.data.value;
|
||||||
this.currentPulsePosition = currentTime;
|
|
||||||
} else if (message.data.type === "ppqn") {
|
} else if (message.data.type === "ppqn") {
|
||||||
this.ppqn = message.data.value;
|
this.ppqn = message.data.value;
|
||||||
this.currentPulsePosition = currentTime;
|
|
||||||
} else if (message.data.type === "nudge") {
|
} else if (message.data.type === "nudge") {
|
||||||
this.nudge = message.data.value;
|
this.nudge = message.data.value;
|
||||||
|
} else if (message.data.type === "timeSignature") {
|
||||||
|
this.timeSignature = message.data.value;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
process() {
|
process() {
|
||||||
if (this.started) {
|
if (this.started) {
|
||||||
const adjustedCurrentTime = currentTime + this.nudge / 100;
|
const adjustedCurrentTime = (currentTime - this.startTime) + this.nudge / 100;
|
||||||
const beatNumber = adjustedCurrentTime / (60 / this.bpm);
|
const beatNumber = adjustedCurrentTime / (60 / this.bpm);
|
||||||
const currentPulsePosition = Math.ceil(beatNumber * this.ppqn);
|
const currentPulsePosition = Math.round(beatNumber * this.ppqn);
|
||||||
|
|
||||||
if (currentPulsePosition > this.currentPulsePosition) {
|
if (currentPulsePosition > this.currentPulsePosition) {
|
||||||
this.currentPulsePosition = currentPulsePosition;
|
this.currentPulsePosition = currentPulsePosition;
|
||||||
this.port.postMessage({ type: "bang", bpm: this.bpm });
|
|
||||||
|
// Calculate current tick, beat, and bar
|
||||||
|
const ticksPerBeat = this.ppqn;
|
||||||
|
const beatsPerBar = this.timeSignature[0];
|
||||||
|
const ticksPerBar = ticksPerBeat * beatsPerBar;
|
||||||
|
|
||||||
|
const currentTick = this.currentPulsePosition % ticksPerBeat;
|
||||||
|
const currentBeat = Math.floor(this.currentPulsePosition / ticksPerBeat) % beatsPerBar;
|
||||||
|
const currentBar = Math.floor(this.currentPulsePosition / ticksPerBar);
|
||||||
|
|
||||||
|
this.port.postMessage({
|
||||||
|
type: 'time',
|
||||||
|
time: currentTime,
|
||||||
|
tick: currentTick,
|
||||||
|
beat: currentBeat,
|
||||||
|
bar: currentBar
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
registerProcessor("transport", TransportProcessor);
|
registerProcessor("transport", TransportProcessor);
|
||||||
@ -2807,10 +2807,10 @@ long@4.0.0:
|
|||||||
resolved "https://registry.yarnpkg.com/long/-/long-4.0.0.tgz#9a7b71cfb7d361a194ea555241c92f7468d5bf28"
|
resolved "https://registry.yarnpkg.com/long/-/long-4.0.0.tgz#9a7b71cfb7d361a194ea555241c92f7468d5bf28"
|
||||||
integrity sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==
|
integrity sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==
|
||||||
|
|
||||||
lru-cache@^10.0.1:
|
lru-cache@^10.2.0:
|
||||||
version "10.0.1"
|
version "10.2.0"
|
||||||
resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-10.0.1.tgz#0a3be479df549cca0e5d693ac402ff19537a6b7a"
|
resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-10.2.0.tgz#0bd445ca57363465900f4d1f9bd8db343a4d95c3"
|
||||||
integrity sha512-IJ4uwUTi2qCccrioU6g9g/5rvvVl13bsdczUUcqbciD9iLr095yj8DQKdObriEvuNSx325N1rV1O0sJFszx75g==
|
integrity sha512-2bIM8x+VAf6JT4bKAljS1qUWgMsqZRPGJS6FSahIMPVvctcNhyVp7AJu7quxOW9jwkryBReKZY5tY5JYv2n/7Q==
|
||||||
|
|
||||||
lru-cache@^5.1.1:
|
lru-cache@^5.1.1:
|
||||||
version "5.1.1"
|
version "5.1.1"
|
||||||
|
|||||||
Reference in New Issue
Block a user