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

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

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.
*/
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);
};

View File

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

View File

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

View File

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

View File

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