From 4a03a726452c442f26a224f1621a6a2b81de3991 Mon Sep 17 00:00:00 2001 From: Miika Alonen Date: Thu, 24 Aug 2023 23:15:36 +0300 Subject: [PATCH 1/7] Add logical time to transportprocessor --- src/TransportNode.js | 12 ++---------- src/TransportProcessor.js | 29 ++++++++++++++++++++++++++--- src/classes/ZPlayer.ts | 14 +++++++++----- 3 files changed, 37 insertions(+), 18 deletions(-) diff --git a/src/TransportNode.js b/src/TransportNode.js index 3ffd396..b9df84b 100644 --- a/src/TransportNode.js +++ b/src/TransportNode.js @@ -21,20 +21,14 @@ 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; // setInterval(() => this.ping(), 1000); - this.startTime = null; - this.elapsedTime = 0; } /** @type {(this: MessagePort, ev: MessageEvent) => any} */ handleMessage = (message) => { if (message.data && message.data.type === "bang") { - if (this.startTime === null) { - this.startTime = message.data.currentTime; - } - this.elapsedTime = message.data.currentTime - this.startTime; - this.prevCurrentTime = message.data.currentTime; - let { futureTimeStamp, timeToNextPulse, nextPulsePosition } = this.convertTimeToNextBarsBeats(this.elapsedTime); + let { futureTimeStamp, timeToNextPulse, nextPulsePosition } = this.convertTimeToNextBarsBeats(message.data.logicalTime); // Evaluate the global buffer only once per ppqn value if (this.nextPulsePosition !== nextPulsePosition) { @@ -65,8 +59,6 @@ export class TransportNode extends AudioWorkletNode { } stop() { - this.startTime = null; - this.elapsedTime = null; this.app.clock.tick = 0; // this.$clock.innerHTML = `[${1} | ${1} | ${zeroPad(1, '2')}]`; this.port.postMessage("stop"); diff --git a/src/TransportProcessor.js b/src/TransportProcessor.js index da91ba9..aab0fdf 100644 --- a/src/TransportProcessor.js +++ b/src/TransportProcessor.js @@ -4,7 +4,11 @@ class TransportProcessor extends AudioWorkletProcessor { super(options); this.port.addEventListener("message", this.handleMessage); this.port.start(); - this.stated = false; + this.started = false; + this.totalPausedTime = 0; + this.lastPausedTime = 0; + this.startedAgainTime = 0; + this.wasStopped = false; } handleMessage = (message) => { @@ -14,14 +18,33 @@ class TransportProcessor extends AudioWorkletProcessor { this.started = true; } else if (message.data === "pause") { this.started = false; + if(this.lastPausedTime === 0) { + this.lastPausedTime = currentTime; + } } else if (message.data === "stop") { this.started = false; - this.currentTime = 0; + this.totalPausedTime = 0; + this.lastPausedTime = 0; + this.startedAgainTime = 0; + this.wasStopped = true; } }; process(inputs, outputs, parameters) { - if (this.started) this.port.postMessage({ type: "bang", currentTime }); + if (this.started) { + if(this.lastPausedTime>0) { + const pausedTime = currentTime-this.lastPausedTime; + this.totalPausedTime += pausedTime; + this.lastPausedTime = 0; + } + if(this.wasStopped) { + this.startedAgainTime = currentTime; + this.wasStopped = false; + } + const logicalTime = currentTime-this.totalPausedTime-this.startedAgainTime; + //console.log("Logical/Current:", logicalTime, currentTime); + this.port.postMessage({ type: "bang", logicalTime }); + } return true; } } diff --git a/src/classes/ZPlayer.ts b/src/classes/ZPlayer.ts index 2486e08..4526bc1 100644 --- a/src/classes/ZPlayer.ts +++ b/src/classes/ZPlayer.ts @@ -37,6 +37,12 @@ export class Player extends Event { } areWeThereYet = (): boolean => { + // If clock has stopped + if(this.app.clock.pulses_since_origin Date: Fri, 25 Aug 2023 07:58:48 +0200 Subject: [PATCH 2/7] better error catching --- src/Evaluator.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/Evaluator.ts b/src/Evaluator.ts index c9130c7..d5659ed 100644 --- a/src/Evaluator.ts +++ b/src/Evaluator.ts @@ -18,7 +18,9 @@ const tryCatchWrapper = ( return new Promise((resolve, _) => { try { Function( - `"use strict";try{${codeReplace(code)}} catch (e) {console.log(e); _reportError(e);};` + `"use strict";try{${codeReplace( + code + )}} catch (e) {console.log(e); _reportError(e);};` ).call(application.api); resolve(true); } catch (error) { @@ -64,7 +66,7 @@ export const tryEvaluate = async ( const newFunction = new Function( `"use strict";try{${codeReplace( wrappedCode - )}} catch (e) {console.log(e)};` + )}} catch (e) {console.log(e); _reportError(e);};` ); addFunctionToCache(candidateCode, newFunction); } else { From 814ea0c66042309b18ae9011b82d7461fca2a178 Mon Sep 17 00:00:00 2001 From: Miika Alonen Date: Fri, 25 Aug 2023 13:36:23 +0300 Subject: [PATCH 3/7] Change from audiocontext currenttime to logicaltime --- src/TransportNode.js | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/TransportNode.js b/src/TransportNode.js index b9df84b..8ef0c3a 100644 --- a/src/TransportNode.js +++ b/src/TransportNode.js @@ -28,19 +28,20 @@ export class TransportNode extends AudioWorkletNode { /** @type {(this: MessagePort, ev: MessageEvent) => any} */ handleMessage = (message) => { if (message.data && message.data.type === "bang") { - let { futureTimeStamp, timeToNextPulse, nextPulsePosition } = this.convertTimeToNextBarsBeats(message.data.logicalTime); + this.logicalTime = message.data.logicalTime; + let { futureTimeStamp, timeToNextPulse, nextPulsePosition } = this.convertTimeToNextBarsBeats(this.logicalTime); // Evaluate the global buffer only once per ppqn value if (this.nextPulsePosition !== nextPulsePosition) { this.nextPulsePosition = nextPulsePosition; setTimeout(() => { - const now = this.app.audioContext.currentTime; + const now = this.logicalTime; this.app.clock.time_position = futureTimeStamp; - // this.$clock.innerHTML = `[${futureTimeStamp.bar}:${futureTimeStamp.beat}:${zeroPad(futureTimeStamp.pulse, '2')}]`; + //this.$clock.innerHTML = `[${futureTimeStamp.bar}:${futureTimeStamp.beat}:${zeroPad(futureTimeStamp.pulse, '2')}]`; tryEvaluate(this.app, this.app.global_buffer); this.hasBeenEvaluated = true; this.currentPulsePosition = nextPulsePosition; - const then = this.app.audioContext.currentTime; + const then = this.logicalTime; 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; From 090669fddadeeb835c694ae959ccaca6c73f378a Mon Sep 17 00:00:00 2001 From: Miika Alonen Date: Fri, 25 Aug 2023 15:43:42 +0300 Subject: [PATCH 4/7] Go back to currentTime for latency calculations --- src/TransportNode.js | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/TransportNode.js b/src/TransportNode.js index 8ef0c3a..0d33e63 100644 --- a/src/TransportNode.js +++ b/src/TransportNode.js @@ -35,13 +35,12 @@ export class TransportNode extends AudioWorkletNode { if (this.nextPulsePosition !== nextPulsePosition) { this.nextPulsePosition = nextPulsePosition; setTimeout(() => { - const now = this.logicalTime; + const now = this.app.audioContext.currentTime; this.app.clock.time_position = futureTimeStamp; - //this.$clock.innerHTML = `[${futureTimeStamp.bar}:${futureTimeStamp.beat}:${zeroPad(futureTimeStamp.pulse, '2')}]`; tryEvaluate(this.app, this.app.global_buffer); this.hasBeenEvaluated = true; this.currentPulsePosition = nextPulsePosition; - const then = this.logicalTime; + 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; From db33b3eda82870e5ed7d8d2d5b3b182daf216b6f Mon Sep 17 00:00:00 2001 From: Miika Alonen Date: Fri, 25 Aug 2023 15:48:06 +0300 Subject: [PATCH 5/7] Remove unnecessary variables --- src/TransportNode.js | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/TransportNode.js b/src/TransportNode.js index 0d33e63..e961cac 100644 --- a/src/TransportNode.js +++ b/src/TransportNode.js @@ -21,15 +21,12 @@ 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; - // setInterval(() => this.ping(), 1000); } /** @type {(this: MessagePort, ev: MessageEvent) => any} */ handleMessage = (message) => { if (message.data && message.data.type === "bang") { - this.logicalTime = message.data.logicalTime; - let { futureTimeStamp, timeToNextPulse, nextPulsePosition } = this.convertTimeToNextBarsBeats(this.logicalTime); + let { futureTimeStamp, timeToNextPulse, nextPulsePosition } = this.convertTimeToNextBarsBeats(message.data.logicalTime); // Evaluate the global buffer only once per ppqn value if (this.nextPulsePosition !== nextPulsePosition) { From 0dcf7237369fe7c406e24fa0bd072e43bf62a8ec Mon Sep 17 00:00:00 2001 From: Miika Alonen Date: Sat, 26 Aug 2023 15:00:52 +0300 Subject: [PATCH 6/7] Added syncing to next beat in ZPlayer --- src/Clock.ts | 17 ++++++++++++++--- src/classes/ZPlayer.ts | 17 ++++++++++++----- 2 files changed, 26 insertions(+), 8 deletions(-) diff --git a/src/Clock.ts b/src/Clock.ts index bbd23c7..a1d5ea7 100644 --- a/src/Clock.ts +++ b/src/Clock.ts @@ -65,12 +65,23 @@ export class Clock { * * @returns number of ticks until next bar */ - const currentBeatInTicks = ((this.app.clock.beats_since_origin - 1) * 48) + this.time_position.pulse + 1 + 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 - return nextBarinTicks - currentBeatInTicks + return nextBarinTicks - currentBeatInTicks; } - get beats_per_bar(): number { + get next_beat_in_ticks(): number { + /** + * This function returns the number of ticks separating the current moment + * from the beginning of the next beat. + * + * @returns number of ticks until next beat + */ + const ticksMissingToNextBeat = (this.time_position.pulse + 1) % this.ppqn; + return this.app.clock.pulses_since_origin + ticksMissingToNextBeat; + } + + get beats_per_bar(): number { /** * Returns the number of beats per bar. */ diff --git a/src/classes/ZPlayer.ts b/src/classes/ZPlayer.ts index 4526bc1..f5a0294 100644 --- a/src/classes/ZPlayer.ts +++ b/src/classes/ZPlayer.ts @@ -36,23 +36,30 @@ export class Player extends Event { return this.app.clock.convertPulseToSecond(pulse); } + // Check if it's time to play the event areWeThereYet = (): boolean => { // If clock has stopped if(this.app.clock.pulses_since_origin= this.app.clock.next_beat_in_ticks) + ) + || + ( // If pattern is already playing this.current && - this.pulseToSecond(this.app.api.epulse()+1) >= + this.pulseToSecond(this.app.clock.pulses_since_origin+1) >= this.pulseToSecond(this.callTime) + (this.current.duration*4) * this.pulseToSecond(this.app.api.ppqn()) ) ); - + + // Increment local tick (how many times sound/midi has been called) this.tick = howAboutNow ? 0 : this.tick+1; return howAboutNow; From 97b3f89a20afbc25379c43b0cfbb016bbc84be7d Mon Sep 17 00:00:00 2001 From: Miika Alonen Date: Sat, 26 Aug 2023 18:43:39 +0300 Subject: [PATCH 7/7] Add wait method to ziffers --- package.json | 2 +- src/API.ts | 5 +--- src/Clock.ts | 1 - src/classes/ZPlayer.ts | 65 +++++++++++++++++++++++++++++++++++------- yarn.lock | 8 +++--- 5 files changed, 61 insertions(+), 20 deletions(-) diff --git a/package.json b/package.json index 49f9613..f0e1838 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.13", + "zifferjs": "^0.0.14", "zzfx": "^1.2.0" } } diff --git a/src/API.ts b/src/API.ts index fb1fde3..4407bc8 100644 --- a/src/API.ts +++ b/src/API.ts @@ -554,10 +554,7 @@ export class UserAPI { player = new Player(input, options, this.app); this.app.api.patternCache.set(key, player); } - if ((player && player.notStarted()) || player.played) { - player.callTime = this.epulse(); - player.played = false; - } + if(player) player.updateLastCallTime(); return player; }; diff --git a/src/Clock.ts b/src/Clock.ts index a1d5ea7..ed6c987 100644 --- a/src/Clock.ts +++ b/src/Clock.ts @@ -121,7 +121,6 @@ export class Clock { return n * this.pulse_duration } - public start(): void { /** * Starts the TransportNode (starts the clock). diff --git a/src/classes/ZPlayer.ts b/src/classes/ZPlayer.ts index f5a0294..ee4b9f9 100644 --- a/src/classes/ZPlayer.ts +++ b/src/classes/ZPlayer.ts @@ -9,12 +9,14 @@ import { RestEvent } from "./RestEvent"; export class Player extends Event { input: string; ziffers: Ziffers; - callTime: number = 0; + firstCallTime: number = 0; + lastCallTime: number = 0; + waitTime = 0; startBeat: number = 0; played: boolean = false; current!: Pitch|Chord|ZRest; retro: boolean = false; - tick: number = 0; + index: number = -1; constructor(input: string, options: object, public app: Editor) { super(app); @@ -22,6 +24,22 @@ export class Player extends Event { this.ziffers = new Ziffers(input, options); } + get ticks(): number { + const dur = this.ziffers.duration; + return dur * 4 * this.app.clock.ppqn; + } + + nextEndTime(): number { + return this.firstCallTime + this.ticks; + } + + updateLastCallTime(): void { + if (this.notStarted() || this.played) { + this.lastCallTime = this.app.clock.pulses_since_origin; + this.played = false; + } + } + notStarted(): boolean { return this.ziffers.notStarted(); } @@ -39,28 +57,34 @@ export class Player extends Event { // Check if it's time to play the event areWeThereYet = (): boolean => { // If clock has stopped - if(this.app.clock.pulses_since_origin= this.app.clock.next_beat_in_ticks) + this.app.clock.pulses_since_origin+1 >= this.app.clock.next_beat_in_ticks) && + (this.app.clock.pulses_since_origin+1 >= this.firstCallTime+this.waitTime) ) || ( // If pattern is already playing this.current && this.pulseToSecond(this.app.clock.pulses_since_origin+1) >= - this.pulseToSecond(this.callTime) + + this.pulseToSecond(this.lastCallTime) + (this.current.duration*4) * this.pulseToSecond(this.app.api.ppqn()) ) ); - // Increment local tick (how many times sound/midi has been called) - this.tick = howAboutNow ? 0 : this.tick+1; + // Increment index of how many times sound/midi have been called + this.index = howAboutNow ? this.index+1 : this.index; + + if(howAboutNow && this.notStarted()) { + this.firstCallTime = this.app.clock.pulses_since_origin+1; + } return howAboutNow; } @@ -110,12 +134,33 @@ export class Player extends Event { } retrograde() { - if(this.tick === 0 && this.ziffers.index === 0) { + if(this.index === -1 && this.ziffers.index === -1) { this.ziffers.retrograde(); } return this; } + wait(value: number) { + if(this.index === -1 && this.ziffers.index === -1) { + + // TODO: THIS LATER! + + /* if(typeof value === "string") { + const cueKey = this.app.api.patternCues.get(value); + if(cueKey) { + const waitedPatter = this.app.api.patternCache.get(cueKey) as Player; + if(waitedPatter) { + this.waitTime = waitedPatter.nextEndTime(); + } + } + } */ + + this.waitTime = Math.ceil(value*4*this.app.clock.ppqn); + + } + return this; + } + out = (): void => { // TODO? } diff --git a/yarn.lock b/yarn.lock index 799b986..347cbc0 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1446,10 +1446,10 @@ yaml@^2.1.1: resolved "https://registry.yarnpkg.com/yaml/-/yaml-2.3.1.tgz#02fe0975d23cd441242aa7204e09fc28ac2ac33b" integrity sha512-2eHWfjaoXgTBC2jNM1LRef62VQa0umtvRiDSk6HSzW7RvS5YtkabJrwYLLEKWBc8a5U2PTSCs+dJjUTJdlHsWQ== -zifferjs@^0.0.13: - version "0.0.13" - resolved "https://registry.yarnpkg.com/zifferjs/-/zifferjs-0.0.13.tgz#af155633357c95da6a4e5aaa84f23013b5574236" - integrity sha512-eNOQOn+NM4L3v2FqQEf0RSiJOKiZMaotGLGj1VBCPHi5WhHp3N61R7k9ZrnQKhPnfSI80NBoplhQ1Q1sdEjFlQ== +zifferjs@^0.0.14: + version "0.0.14" + resolved "https://registry.yarnpkg.com/zifferjs/-/zifferjs-0.0.14.tgz#7876c799a08e799be7af22b65f4cb6f0b44f79ca" + integrity sha512-CpS3zTm8Btm8aTxd7sSUgVCF/S/jJ3hqwgp7uRzbZI8k6yJWhzo/rjMlEZoOmeBhs7Qy4XsVk7pfrLdS8AAIVA== zzfx@^1.2.0: version "1.2.0"