begin clock rewrite

This commit is contained in:
2024-04-20 01:58:06 +02:00
parent 9dfac1141f
commit 2304015975
7 changed files with 96 additions and 71 deletions

View File

@ -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",

View File

@ -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);
}; };

View File

@ -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);
}; };

View File

@ -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();
} }

View File

@ -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);
this.app.settings.send_clock ?? this.app.api.MidiConnection.sendMidiClock();
if (this.app.settings.send_clock) { tryEvaluate(
this.app.api.MidiConnection.sendMidiClock(); this.app,
} this.app.exampleIsPlaying
? this.app.example_buffer
const futureTimeStamp = this.app.clock.convertTicksToTimeposition( : this.app.global_buffer
this.app.clock.tick
); );
this.app.clock.time_position = futureTimeStamp;
if (this.app.exampleIsPlaying) { clock.incrementTick(message.data.bpm);
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" });

View File

@ -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);

View File

@ -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"