From 6b7517ea4db9e0ba0a2cc1e70f6ffa90119b30c4 Mon Sep 17 00:00:00 2001 From: Miika Alonen Date: Tue, 29 Aug 2023 16:08:11 +0300 Subject: [PATCH] First try --- package.json | 2 +- src/Clock.ts | 48 ++++++++++++++++++---------- src/TransportNode.js | 67 +++++++++++++++++++++------------------ src/TransportProcessor.js | 25 ++++++++++++--- src/classes/ZPlayer.ts | 19 +++++++---- src/main.ts | 1 + yarn.lock | 7 ++-- 7 files changed, 105 insertions(+), 64 deletions(-) diff --git a/package.json b/package.json index d4a378e..0828600 100644 --- a/package.json +++ b/package.json @@ -38,7 +38,7 @@ "tone": "^14.8.49", "unique-names-generator": "^4.7.1", "vite-plugin-markdown": "^2.1.0", - "zifferjs": "^0.0.16", + "zifferjs": "link:../zifferjs", "zzfx": "^1.2.0" } } diff --git a/src/Clock.ts b/src/Clock.ts index ed6c987..a0d91ae 100644 --- a/src/Clock.ts +++ b/src/Clock.ts @@ -34,18 +34,18 @@ export class Clock { ctx: AudioContext transportNode: TransportNode | null - bpm: number + private _bpm: number time_signature: number[] time_position: TimePosition - ppqn: number + private _ppqn: number tick: number constructor(public app: Editor, ctx: AudioContext) { this.time_position = { bar: 0, beat: 0, pulse: 0 } this.time_signature = [4, 4]; - this.tick = 0; - this.bpm = 120; - this.ppqn = 48; + this.tick = -1; + this._bpm = 120; + this._ppqn = 48; this.transportNode = null; this.ctx = ctx; ctx.audioWorklet.addModule(TransportProcessor).then((e) => { @@ -65,8 +65,8 @@ export class Clock { * * @returns number of ticks until next bar */ - const currentBeatInTicks = ((this.app.clock.beats_since_origin - 1) * this.ppqn) + this.time_position.pulse + 1 - const nextBarinTicks = (this.beats_per_bar * this.ppqn) * this.time_position.bar + 1 + const currentBeatInTicks = ((this.app.clock.beats_since_origin * this.ppqn) + this.time_position.pulse); + const nextBarinTicks = (this.beats_per_bar * this.ppqn) * this.time_position.bar; return nextBarinTicks - currentBeatInTicks; } @@ -77,7 +77,7 @@ export class Clock { * * @returns number of ticks until next beat */ - const ticksMissingToNextBeat = (this.time_position.pulse + 1) % this.ppqn; + const ticksMissingToNextBeat = (this.time_position.pulse) % this.ppqn; return this.app.clock.pulses_since_origin + ticksMissingToNextBeat; } @@ -94,7 +94,7 @@ export class Clock { * * @returns number of beats since origin */ - return (this.time_position.bar - 1) * this.beats_per_bar + this.time_position.beat; + return Math.floor(this.tick / this.ppqn); } get pulses_since_origin(): number { @@ -103,7 +103,7 @@ export class Clock { * * @returns number of pulses since origin */ - return (this.beats_since_origin * this.ppqn) + this.time_position.pulse + return this.tick; } @@ -114,6 +114,24 @@ export class Clock { return 60 / this.bpm / this.ppqn; } + get bpm(): number { + return this._bpm; + } + + set bpm(bpm: number) { + this._bpm = bpm; + this.transportNode?.setBPM(bpm); + } + + get ppqn(): number { + return this._ppqn; + } + + set ppqn(ppqn: number) { + this._ppqn = ppqn; + this.transportNode?.setPPQN(ppqn); + } + public convertPulseToSecond(n: number): number { /** * Converts a pulse to a second. @@ -126,12 +144,10 @@ export class Clock { * Starts the TransportNode (starts the clock). */ // @ts-ignore - if (this.transportNode?.state === 'running') { - console.log('Already started') - } else { - this.app.audioContext.resume() - this.transportNode?.start(); - } + console.log("STARTING?"); + this.app.audioContext.resume() + this.transportNode?.start(); + } public pause(): void { diff --git a/src/TransportNode.js b/src/TransportNode.js index e961cac..02669ca 100644 --- a/src/TransportNode.js +++ b/src/TransportNode.js @@ -10,9 +10,7 @@ export class TransportNode extends AudioWorkletNode { this.port.start(); /** @type {HTMLSpanElement} */ this.$clock = document.getElementById("clockviewer"); - this.hasBeenEvaluated = false; this.currentPulsePosition = 0; - this.nextPulsePosition = -1; this.executionLatency = 0; this.lastLatencies = [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, @@ -21,29 +19,29 @@ export class TransportNode extends AudioWorkletNode { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ]; this.indexOfLastLatencies = 0; + this.logicalTime = 0; } /** @type {(this: MessagePort, ev: MessageEvent) => any} */ handleMessage = (message) => { if (message.data && message.data.type === "bang") { - let { futureTimeStamp, timeToNextPulse, nextPulsePosition } = this.convertTimeToNextBarsBeats(message.data.logicalTime); - - // Evaluate the global buffer only once per ppqn value - if (this.nextPulsePosition !== nextPulsePosition) { - this.nextPulsePosition = nextPulsePosition; - setTimeout(() => { - const now = this.app.audioContext.currentTime; - this.app.clock.time_position = futureTimeStamp; - tryEvaluate(this.app, this.app.global_buffer); - this.hasBeenEvaluated = true; - this.currentPulsePosition = nextPulsePosition; - const then = this.app.audioContext.currentTime; - this.lastLatencies[this.indexOfLastLatencies] = then - now; - this.indexOfLastLatencies = (this.indexOfLastLatencies + 1) % this.lastLatencies.length; - const averageLatency = this.lastLatencies.reduce((a, b) => a + b) / this.lastLatencies.length; - this.executionLatency = averageLatency / 1000; - }, (timeToNextPulse + this.executionLatency) * 1000); - } + this.logicalTime = message.data.logicalTime; + this.app.clock.tick++ + let futureTimeStamp = this.convertTicksToTimeposition(this.app.clock.tick); + console.log("BANG", this.logicalTime, futureTimeStamp); + + setTimeout(() => { + console.log("EVALUATING"); + const now = this.app.audioContext.currentTime; + this.app.clock.time_position = futureTimeStamp; + tryEvaluate(this.app, this.app.global_buffer); + const then = this.app.audioContext.currentTime; + this.lastLatencies[this.indexOfLastLatencies] = then - now; + this.indexOfLastLatencies = (this.indexOfLastLatencies + 1) % this.lastLatencies.length; + const averageLatency = this.lastLatencies.reduce((a, b) => a + b) / this.lastLatencies.length; + this.executionLatency = averageLatency / 1000; + }, (this.app.clock.pulse_duration + this.executionLatency) * 1000); + } }; @@ -55,6 +53,14 @@ export class TransportNode extends AudioWorkletNode { this.port.postMessage("pause"); } + setBPM(bpm) { + this.port.postMessage({ type: "bpm", value: bpm }); + } + + setPPQN(ppqn) { + this.port.postMessage({ type: "ppqn", value: ppqn }); + } + stop() { this.app.clock.tick = 0; // this.$clock.innerHTML = `[${1} | ${1} | ${zeroPad(1, '2')}]`; @@ -86,17 +92,16 @@ export class TransportNode extends AudioWorkletNode { const futureBeatNumber = this.nextPulsePosition / this.app.clock.ppqn; const futureBarNumber = futureBeatNumber / beatsPerBar; - const futureTimeStamp = { - bar: Math.floor(futureBarNumber) + 1, - beat: Math.floor(futureBeatNumber) % beatsPerBar + 1, - pulse: Math.floor(this.nextPulsePosition) % this.app.clock.ppqn - }; - this.app.clock.tick++ - return { - futureTimeStamp, - timeToNextPulse, - nextPulsePosition - }; } + + convertTicksToTimeposition(ticks) { + const beatsPerBar = this.app.clock.time_signature[0]; + const ppqnPosition = (ticks % this.app.clock.ppqn)+1; + const beatNumber = Math.floor(ticks / this.app.clock.ppqn); + const barNumber = Math.floor(beatNumber / beatsPerBar)+1; + const beatWithinBar = Math.floor(beatNumber % beatsPerBar)+1; + return {bar: barNumber, beat: beatWithinBar, ppqn: ppqnPosition}; + } + } \ No newline at end of file diff --git a/src/TransportProcessor.js b/src/TransportProcessor.js index aab0fdf..69e3cea 100644 --- a/src/TransportProcessor.js +++ b/src/TransportProcessor.js @@ -9,24 +9,31 @@ class TransportProcessor extends AudioWorkletProcessor { this.lastPausedTime = 0; this.startedAgainTime = 0; this.wasStopped = false; + this.bpm = 120; + this.ppqn = 48; + this.currentPulsePosition = 0; } handleMessage = (message) => { - if (message.data && message.data.type === "ping") { + if(message.data && message.data.type === "ping") { this.port.postMessage(message.data); } else if (message.data === "start") { this.started = true; - } else if (message.data === "pause") { + } else if(message.data === "pause") { this.started = false; if(this.lastPausedTime === 0) { this.lastPausedTime = currentTime; } - } else if (message.data === "stop") { + } else if(message.data === "stop") { this.started = false; this.totalPausedTime = 0; this.lastPausedTime = 0; - this.startedAgainTime = 0; this.wasStopped = true; + this.currentPulsePosition = 0; + } else if(message.data === 'bpm') { + this.bpm = message.data.value; + } else if(message.data === 'ppqn') { + this.ppqn = message.data.value; } }; @@ -42,8 +49,16 @@ class TransportProcessor extends AudioWorkletProcessor { this.wasStopped = false; } const logicalTime = currentTime-this.totalPausedTime-this.startedAgainTime; + //console.log(currentTime, this.totalPausedTime, this.startedAgainTime); //console.log("Logical/Current:", logicalTime, currentTime); - this.port.postMessage({ type: "bang", logicalTime }); + + const beatNumber = logicalTime / (60 / this.bpm); + const nextPulsePosition = Math.ceil(beatNumber * this.ppqn); + + if(nextPulsePosition > this.currentPulsePosition) { + this.currentPulsePosition = nextPulsePosition; + this.port.postMessage({ type: "bang", logicalTime }); + } } return true; } diff --git a/src/classes/ZPlayer.ts b/src/classes/ZPlayer.ts index c7d0506..ee7b7fb 100644 --- a/src/classes/ZPlayer.ts +++ b/src/classes/ZPlayer.ts @@ -65,7 +65,7 @@ export class Player extends Event { } atTheBeginning = (): boolean => { - return this.pulse()===0 && this.ziffers.index===0; + return this.ziffers.index===0; } origin = (): number => { @@ -76,6 +76,10 @@ export class Player extends Event { return this.app.clock.time_position.pulse; } + beat = (): number => { + return this.app.clock.time_position.beat; + } + nextBeat = (): number => { return this.app.clock.next_beat_in_ticks; } @@ -150,24 +154,25 @@ export class Player extends Event { } scale(name: string) { - this.ziffers.scale(name); + if(this.atTheBeginning()) this.ziffers.scale(name); return this; } key(name: string) { - this.ziffers.key(name); + if(this.firstRun() || this.atTheBeginning()) { + console.log("At", this.app.clock.time_position); + this.ziffers.key(name); + } return this; } octave(value: number) { - this.ziffers.octave(value); + if(this.atTheBeginning()) this.ziffers.octave(value); return this; } retrograde() { - if(this.index === -1 && this.ziffers.index === -1) { - this.ziffers.retrograde(); - } + if(this.atTheBeginning()) this.ziffers.retrograde(); return this; } diff --git a/src/main.ts b/src/main.ts index f71fe60..0ca6db0 100644 --- a/src/main.ts +++ b/src/main.ts @@ -463,6 +463,7 @@ export class Editor { this.stop_buttons.forEach((button) => { button.addEventListener("click", () => { this.setButtonHighlighting("stop", true); + this.isPlaying = false; this.clock.stop(); }); }); diff --git a/yarn.lock b/yarn.lock index f2ec551..50ca12a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1446,10 +1446,9 @@ yaml@^2.1.1: resolved "https://registry.yarnpkg.com/yaml/-/yaml-2.3.1.tgz#02fe0975d23cd441242aa7204e09fc28ac2ac33b" integrity sha512-2eHWfjaoXgTBC2jNM1LRef62VQa0umtvRiDSk6HSzW7RvS5YtkabJrwYLLEKWBc8a5U2PTSCs+dJjUTJdlHsWQ== -zifferjs@^0.0.16: - version "0.0.16" - resolved "https://registry.yarnpkg.com/zifferjs/-/zifferjs-0.0.16.tgz#befa11eb923a04a3ee97eb7844bd70b5bb2752bf" - integrity sha512-pxcQKqdW9sMRj8d2GGUGsPnkSg4bgi9+5pp3dicURqwUwsgCUfY2vXLWRF9LKM8K3mjzO37V0nHZIqQ3LyYLKg== +"zifferjs@link:../zifferjs": + version "0.0.0" + uid "" zzfx@^1.2.0: version "1.2.0"