begin clock rewrite
This commit is contained in:
@ -32,7 +32,7 @@
|
||||
"fflate": "^0.8.0",
|
||||
"highlight.js": "^11.9.0",
|
||||
"jisg": "^0.9.7",
|
||||
"lru-cache": "^10.0.1",
|
||||
"lru-cache": "^10.2.0",
|
||||
"marked": "^7.0.3",
|
||||
"osc": "^2.4.4",
|
||||
"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 results: boolean[] = nArray.map(
|
||||
(value) =>
|
||||
(app.clock.pulses_since_origin - Math.floor(nudge * app.clock.ppqn)) %
|
||||
Math.floor(value * app.clock.ppqn) === 0,
|
||||
(app.clock.pulses_since_origin - Math.round(nudge * app.clock.ppqn)) %
|
||||
Math.round(value * app.clock.ppqn) === 0,
|
||||
);
|
||||
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.
|
||||
*/
|
||||
code.evaluations!++;
|
||||
const candidateCode = code.candidate;
|
||||
|
||||
const cachedFunction = cache.get(candidateCode);
|
||||
const cachedFunction = cache.get(code.candidate);
|
||||
if (cachedFunction) {
|
||||
cachedFunction.call(application.api);
|
||||
return;
|
||||
}
|
||||
|
||||
const wrappedCode = `let i = ${code.evaluations}; ${candidateCode}`;
|
||||
const wrappedCode = `let i = ${code.evaluations}; ${code.candidate}`;
|
||||
const isCodeValid = await Promise.race([
|
||||
tryCatchWrapper(application, wrappedCode),
|
||||
delay(timeout)
|
||||
]);
|
||||
|
||||
if (isCodeValid) {
|
||||
code.committed = candidateCode;
|
||||
code.committed = code.candidate;
|
||||
const newFunction = new Function(`"use strict"; ${codeReplace(wrappedCode)}`);
|
||||
addFunctionToCache(candidateCode, newFunction);
|
||||
addFunctionToCache(code.candidate, newFunction);
|
||||
} else {
|
||||
application.api.logOnce("Compilation error!");
|
||||
await delay(100);
|
||||
@ -107,3 +106,7 @@ export const evaluateOnce = async (
|
||||
*/
|
||||
await tryCatchWrapper(application, code);
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@ -12,28 +12,10 @@ export interface TimePosition {
|
||||
*/
|
||||
bar: number;
|
||||
beat: number;
|
||||
pulse: number;
|
||||
tick: number;
|
||||
}
|
||||
|
||||
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;
|
||||
logicalTime: number;
|
||||
transportNode: TransportNode | null;
|
||||
@ -51,7 +33,7 @@ export class Clock {
|
||||
public app: Editor,
|
||||
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.logicalTime = 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 ppqnPosition = ticks % this.app.clock.ppqn;
|
||||
const tickPosition = ticks % this.app.clock.ppqn;
|
||||
const beatNumber = Math.floor(ticks / this.app.clock.ppqn);
|
||||
const barNumber = 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 {
|
||||
@ -97,7 +75,7 @@ export class Clock {
|
||||
*
|
||||
* @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;
|
||||
return beatsMissingFromBar * this.ppqn + ticskMissingFromBeat;
|
||||
}
|
||||
@ -109,7 +87,7 @@ export class Clock {
|
||||
*
|
||||
* @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 {
|
||||
@ -183,7 +161,7 @@ export class Clock {
|
||||
if (ppqn > 0 && this._ppqn !== ppqn) {
|
||||
this._ppqn = 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.lastPauseTime = this.app.audioContext.currentTime;
|
||||
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.transportNode?.stop();
|
||||
}
|
||||
|
||||
@ -1,5 +1,4 @@
|
||||
import { tryEvaluate } from "../Evaluator";
|
||||
const zeroPad = (num, places) => String(num).padStart(places, "0");
|
||||
|
||||
export class TransportNode extends AudioWorkletNode {
|
||||
|
||||
@ -13,28 +12,41 @@ export class TransportNode extends AudioWorkletNode {
|
||||
|
||||
/** @type {(this: MessagePort, ev: MessageEvent<any>) => any} */
|
||||
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 (this.app.clock.running) {
|
||||
|
||||
if (this.app.settings.send_clock) {
|
||||
this.app.api.MidiConnection.sendMidiClock();
|
||||
}
|
||||
|
||||
const futureTimeStamp = this.app.clock.convertTicksToTimeposition(
|
||||
this.app.clock.tick
|
||||
clock.time_position = clock.convertTicksToTimeposition(clock.tick);
|
||||
this.app.settings.send_clock ?? this.app.api.MidiConnection.sendMidiClock();
|
||||
|
||||
tryEvaluate(
|
||||
this.app,
|
||||
this.app.exampleIsPlaying
|
||||
? this.app.example_buffer
|
||||
: this.app.global_buffer
|
||||
);
|
||||
this.app.clock.time_position = futureTimeStamp;
|
||||
|
||||
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);
|
||||
|
||||
clock.incrementTick(message.data.bpm);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const endTime = performance.now();
|
||||
const executionTime = endTime - startTime;
|
||||
console.log(`Execution time: ${executionTime}ms`);
|
||||
};
|
||||
|
||||
start() {
|
||||
this.port.postMessage({ type: "start" });
|
||||
|
||||
@ -2,12 +2,16 @@ class TransportProcessor extends AudioWorkletProcessor {
|
||||
constructor(options) {
|
||||
super(options);
|
||||
this.port.addEventListener("message", this.handleMessage);
|
||||
this.port.start();
|
||||
this.bpm = 120;
|
||||
this.nudge = 0;
|
||||
this.started = false;
|
||||
this.bpm = 120;
|
||||
this.ppqn = 48 * 2;
|
||||
this.timeSignature = [4, 4];
|
||||
this.port.start();
|
||||
this.currentPulsePosition = 0;
|
||||
this.startTime = 0;
|
||||
this.pauseTime = 0;
|
||||
this.totalPauseTime = 0;
|
||||
}
|
||||
|
||||
handleMessage = (message) => {
|
||||
@ -15,34 +19,62 @@ class TransportProcessor extends AudioWorkletProcessor {
|
||||
this.port.postMessage(message.data);
|
||||
} else if (message.data.type === "start") {
|
||||
this.started = true;
|
||||
if (this.pauseTime) {
|
||||
this.totalPauseTime += currentTime - this.pauseTime;
|
||||
this.pauseTime = null;
|
||||
} else {
|
||||
this.startTime = currentTime
|
||||
}
|
||||
} else if (message.data.type === "pause") {
|
||||
this.started = false;
|
||||
this.pauseTime = currentTime;
|
||||
} else if (message.data.type === "stop") {
|
||||
this.started = false;
|
||||
this.startTime = 0;
|
||||
this.pauseTime = 0;
|
||||
this.totalPauseTime = 0;
|
||||
this.currentPulsePosition = 0;
|
||||
} else if (message.data.type === "bpm") {
|
||||
this.bpm = message.data.value;
|
||||
this.currentPulsePosition = currentTime;
|
||||
} else if (message.data.type === "ppqn") {
|
||||
this.ppqn = message.data.value;
|
||||
this.currentPulsePosition = currentTime;
|
||||
} else if (message.data.type === "nudge") {
|
||||
this.nudge = message.data.value;
|
||||
} else if (message.data.type === "timeSignature") {
|
||||
this.timeSignature = message.data.value;
|
||||
}
|
||||
};
|
||||
|
||||
process() {
|
||||
if (this.started) {
|
||||
const adjustedCurrentTime = currentTime + this.nudge / 100;
|
||||
const adjustedCurrentTime = (currentTime - this.startTime) + this.nudge / 100;
|
||||
const beatNumber = adjustedCurrentTime / (60 / this.bpm);
|
||||
const currentPulsePosition = Math.ceil(beatNumber * this.ppqn);
|
||||
const currentPulsePosition = Math.round(beatNumber * this.ppqn);
|
||||
|
||||
if (currentPulsePosition > this.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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
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"
|
||||
integrity sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==
|
||||
|
||||
lru-cache@^10.0.1:
|
||||
version "10.0.1"
|
||||
resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-10.0.1.tgz#0a3be479df549cca0e5d693ac402ff19537a6b7a"
|
||||
integrity sha512-IJ4uwUTi2qCccrioU6g9g/5rvvVl13bsdczUUcqbciD9iLr095yj8DQKdObriEvuNSx325N1rV1O0sJFszx75g==
|
||||
lru-cache@^10.2.0:
|
||||
version "10.2.0"
|
||||
resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-10.2.0.tgz#0bd445ca57363465900f4d1f9bd8db343a4d95c3"
|
||||
integrity sha512-2bIM8x+VAf6JT4bKAljS1qUWgMsqZRPGJS6FSahIMPVvctcNhyVp7AJu7quxOW9jwkryBReKZY5tY5JYv2n/7Q==
|
||||
|
||||
lru-cache@^5.1.1:
|
||||
version "5.1.1"
|
||||
|
||||
Reference in New Issue
Block a user