diff --git a/package.json b/package.json index 5e1ff8c..0da5610 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/src/API/Time/Filters.ts b/src/API/Time/Filters.ts index 33f9f08..de83047 100644 --- a/src/API/Time/Filters.ts +++ b/src/API/Time/Filters.ts @@ -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); }; diff --git a/src/Evaluator.ts b/src/Evaluator.ts index e56fd56..988b656 100644 --- a/src/Evaluator.ts +++ b/src/Evaluator.ts @@ -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); }; + + + + diff --git a/src/clock/Clock.ts b/src/clock/Clock.ts index 3bf7647..c45f1e1 100644 --- a/src/clock/Clock.ts +++ b/src/clock/Clock.ts @@ -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(); } diff --git a/src/clock/ClockNode.js b/src/clock/ClockNode.js index ee91cea..373b4e6 100644 --- a/src/clock/ClockNode.js +++ b/src/clock/ClockNode.js @@ -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} */ 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" }); diff --git a/src/clock/ClockProcessor.js b/src/clock/ClockProcessor.js index e4ea681..252e00e 100644 --- a/src/clock/ClockProcessor.js +++ b/src/clock/ClockProcessor.js @@ -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); \ No newline at end of file diff --git a/yarn.lock b/yarn.lock index c2c2327..227a2f0 100644 --- a/yarn.lock +++ b/yarn.lock @@ -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"