First try

This commit is contained in:
2023-08-29 16:08:11 +03:00
parent 9e993e8579
commit 6b7517ea4d
7 changed files with 105 additions and 64 deletions

View File

@ -38,7 +38,7 @@
"tone": "^14.8.49", "tone": "^14.8.49",
"unique-names-generator": "^4.7.1", "unique-names-generator": "^4.7.1",
"vite-plugin-markdown": "^2.1.0", "vite-plugin-markdown": "^2.1.0",
"zifferjs": "^0.0.16", "zifferjs": "link:../zifferjs",
"zzfx": "^1.2.0" "zzfx": "^1.2.0"
} }
} }

View File

@ -34,18 +34,18 @@ export class Clock {
ctx: AudioContext ctx: AudioContext
transportNode: TransportNode | null transportNode: TransportNode | null
bpm: number private _bpm: number
time_signature: number[] time_signature: number[]
time_position: TimePosition time_position: TimePosition
ppqn: number private _ppqn: number
tick: number tick: number
constructor(public app: Editor, ctx: AudioContext) { constructor(public app: Editor, ctx: AudioContext) {
this.time_position = { bar: 0, beat: 0, pulse: 0 } this.time_position = { bar: 0, beat: 0, pulse: 0 }
this.time_signature = [4, 4]; this.time_signature = [4, 4];
this.tick = 0; this.tick = -1;
this.bpm = 120; this._bpm = 120;
this.ppqn = 48; this._ppqn = 48;
this.transportNode = null; this.transportNode = null;
this.ctx = ctx; this.ctx = ctx;
ctx.audioWorklet.addModule(TransportProcessor).then((e) => { ctx.audioWorklet.addModule(TransportProcessor).then((e) => {
@ -65,8 +65,8 @@ export class Clock {
* *
* @returns number of ticks until next bar * @returns number of ticks until next bar
*/ */
const currentBeatInTicks = ((this.app.clock.beats_since_origin - 1) * this.ppqn) + this.time_position.pulse + 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 + 1 const nextBarinTicks = (this.beats_per_bar * this.ppqn) * this.time_position.bar;
return nextBarinTicks - currentBeatInTicks; return nextBarinTicks - currentBeatInTicks;
} }
@ -77,7 +77,7 @@ export class Clock {
* *
* @returns number of ticks until next beat * @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; return this.app.clock.pulses_since_origin + ticksMissingToNextBeat;
} }
@ -94,7 +94,7 @@ export class Clock {
* *
* @returns number of beats since origin * @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 { get pulses_since_origin(): number {
@ -103,7 +103,7 @@ export class Clock {
* *
* @returns number of pulses since origin * @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; 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 { public convertPulseToSecond(n: number): number {
/** /**
* Converts a pulse to a second. * Converts a pulse to a second.
@ -126,12 +144,10 @@ export class Clock {
* Starts the TransportNode (starts the clock). * Starts the TransportNode (starts the clock).
*/ */
// @ts-ignore // @ts-ignore
if (this.transportNode?.state === 'running') { console.log("STARTING?");
console.log('Already started') this.app.audioContext.resume()
} else { this.transportNode?.start();
this.app.audioContext.resume()
this.transportNode?.start();
}
} }
public pause(): void { public pause(): void {

View File

@ -10,9 +10,7 @@ export class TransportNode extends AudioWorkletNode {
this.port.start(); this.port.start();
/** @type {HTMLSpanElement} */ /** @type {HTMLSpanElement} */
this.$clock = document.getElementById("clockviewer"); this.$clock = document.getElementById("clockviewer");
this.hasBeenEvaluated = false;
this.currentPulsePosition = 0; this.currentPulsePosition = 0;
this.nextPulsePosition = -1;
this.executionLatency = 0; this.executionLatency = 0;
this.lastLatencies = [ this.lastLatencies = [
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 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, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
]; ];
this.indexOfLastLatencies = 0; this.indexOfLastLatencies = 0;
this.logicalTime = 0;
} }
/** @type {(this: MessagePort, ev: MessageEvent<any>) => any} */ /** @type {(this: MessagePort, ev: MessageEvent<any>) => any} */
handleMessage = (message) => { handleMessage = (message) => {
if (message.data && message.data.type === "bang") { if (message.data && message.data.type === "bang") {
let { futureTimeStamp, timeToNextPulse, nextPulsePosition } = this.convertTimeToNextBarsBeats(message.data.logicalTime); 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);
// 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);
}
} }
}; };
@ -55,6 +53,14 @@ export class TransportNode extends AudioWorkletNode {
this.port.postMessage("pause"); this.port.postMessage("pause");
} }
setBPM(bpm) {
this.port.postMessage({ type: "bpm", value: bpm });
}
setPPQN(ppqn) {
this.port.postMessage({ type: "ppqn", value: ppqn });
}
stop() { stop() {
this.app.clock.tick = 0; this.app.clock.tick = 0;
// this.$clock.innerHTML = `[${1} | ${1} | ${zeroPad(1, '2')}]`; // 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 futureBeatNumber = this.nextPulsePosition / this.app.clock.ppqn;
const futureBarNumber = futureBeatNumber / beatsPerBar; 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};
}
} }

View File

@ -9,24 +9,31 @@ class TransportProcessor extends AudioWorkletProcessor {
this.lastPausedTime = 0; this.lastPausedTime = 0;
this.startedAgainTime = 0; this.startedAgainTime = 0;
this.wasStopped = false; this.wasStopped = false;
this.bpm = 120;
this.ppqn = 48;
this.currentPulsePosition = 0;
} }
handleMessage = (message) => { handleMessage = (message) => {
if (message.data && message.data.type === "ping") { if(message.data && message.data.type === "ping") {
this.port.postMessage(message.data); this.port.postMessage(message.data);
} else if (message.data === "start") { } else if (message.data === "start") {
this.started = true; this.started = true;
} else if (message.data === "pause") { } else if(message.data === "pause") {
this.started = false; this.started = false;
if(this.lastPausedTime === 0) { if(this.lastPausedTime === 0) {
this.lastPausedTime = currentTime; this.lastPausedTime = currentTime;
} }
} else if (message.data === "stop") { } else if(message.data === "stop") {
this.started = false; this.started = false;
this.totalPausedTime = 0; this.totalPausedTime = 0;
this.lastPausedTime = 0; this.lastPausedTime = 0;
this.startedAgainTime = 0;
this.wasStopped = true; 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; this.wasStopped = false;
} }
const logicalTime = currentTime-this.totalPausedTime-this.startedAgainTime; const logicalTime = currentTime-this.totalPausedTime-this.startedAgainTime;
//console.log(currentTime, this.totalPausedTime, this.startedAgainTime);
//console.log("Logical/Current:", logicalTime, currentTime); //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; return true;
} }

View File

@ -65,7 +65,7 @@ export class Player extends Event {
} }
atTheBeginning = (): boolean => { atTheBeginning = (): boolean => {
return this.pulse()===0 && this.ziffers.index===0; return this.ziffers.index===0;
} }
origin = (): number => { origin = (): number => {
@ -76,6 +76,10 @@ export class Player extends Event {
return this.app.clock.time_position.pulse; return this.app.clock.time_position.pulse;
} }
beat = (): number => {
return this.app.clock.time_position.beat;
}
nextBeat = (): number => { nextBeat = (): number => {
return this.app.clock.next_beat_in_ticks; return this.app.clock.next_beat_in_ticks;
} }
@ -150,24 +154,25 @@ export class Player extends Event {
} }
scale(name: string) { scale(name: string) {
this.ziffers.scale(name); if(this.atTheBeginning()) this.ziffers.scale(name);
return this; return this;
} }
key(name: string) { 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; return this;
} }
octave(value: number) { octave(value: number) {
this.ziffers.octave(value); if(this.atTheBeginning()) this.ziffers.octave(value);
return this; return this;
} }
retrograde() { retrograde() {
if(this.index === -1 && this.ziffers.index === -1) { if(this.atTheBeginning()) this.ziffers.retrograde();
this.ziffers.retrograde();
}
return this; return this;
} }

View File

@ -463,6 +463,7 @@ export class Editor {
this.stop_buttons.forEach((button) => { this.stop_buttons.forEach((button) => {
button.addEventListener("click", () => { button.addEventListener("click", () => {
this.setButtonHighlighting("stop", true); this.setButtonHighlighting("stop", true);
this.isPlaying = false;
this.clock.stop(); this.clock.stop();
}); });
}); });

View File

@ -1446,10 +1446,9 @@ yaml@^2.1.1:
resolved "https://registry.yarnpkg.com/yaml/-/yaml-2.3.1.tgz#02fe0975d23cd441242aa7204e09fc28ac2ac33b" resolved "https://registry.yarnpkg.com/yaml/-/yaml-2.3.1.tgz#02fe0975d23cd441242aa7204e09fc28ac2ac33b"
integrity sha512-2eHWfjaoXgTBC2jNM1LRef62VQa0umtvRiDSk6HSzW7RvS5YtkabJrwYLLEKWBc8a5U2PTSCs+dJjUTJdlHsWQ== integrity sha512-2eHWfjaoXgTBC2jNM1LRef62VQa0umtvRiDSk6HSzW7RvS5YtkabJrwYLLEKWBc8a5U2PTSCs+dJjUTJdlHsWQ==
zifferjs@^0.0.16: "zifferjs@link:../zifferjs":
version "0.0.16" version "0.0.0"
resolved "https://registry.yarnpkg.com/zifferjs/-/zifferjs-0.0.16.tgz#befa11eb923a04a3ee97eb7844bd70b5bb2752bf" uid ""
integrity sha512-pxcQKqdW9sMRj8d2GGUGsPnkSg4bgi9+5pp3dicURqwUwsgCUfY2vXLWRF9LKM8K3mjzO37V0nHZIqQ3LyYLKg==
zzfx@^1.2.0: zzfx@^1.2.0:
version "1.2.0" version "1.2.0"