Add Two Clocks

Co-authored-by: Raphaël Forment <Bubobubobubobubo@users.noreply.github.com>
This commit is contained in:
Fr0stbyteR
2023-08-02 20:20:36 +08:00
parent 3b65cfd5c7
commit 0d2373c026
4 changed files with 80 additions and 18 deletions

View File

@ -210,7 +210,6 @@ export class UserAPI {
get bar(): number { return this.app.clock.time_position.bar } get bar(): number { return this.app.clock.time_position.bar }
get pulse(): number { return this.app.clock.time_position.pulse } get pulse(): number { return this.app.clock.time_position.pulse }
get beat(): number { return this.app.clock.time_position.beat } get beat(): number { return this.app.clock.time_position.beat }
get beats_since_origin(): number { return this.app.clock.beats_since_origin }
onbar(...bar: number[]): boolean { onbar(...bar: number[]): boolean {
return bar.some(b => b === this.app.clock.time_position.bar) return bar.some(b => b === this.app.clock.time_position.bar)
@ -247,7 +246,7 @@ export class UserAPI {
playSound = async (values: object) => { playSound = async (values: object) => {
await this.load; await this.load;
webaudioOutput(sound(values), 0.01) webaudioOutput(sound(values), 0.01) // TODO: timestamp précis du temps d'exécution
} }
} }

View File

@ -38,6 +38,15 @@ export class Clock {
get beats_per_bar(): number { return this.time_signature[0]; } get beats_per_bar(): number { return this.time_signature[0]; }
get pulse_duration(): number {
return 60 / this.bpm / this.ppqn;
}
public convertPulseToSecond(n: number): number {
return n * this.pulse_duration
}
start(): void { start(): void {
// Check if the clock is already running // Check if the clock is already running
if (this.transportNode?.state === 'running') { if (this.transportNode?.state === 'running') {

View File

@ -11,28 +11,43 @@ export class TransportNode extends AudioWorkletNode {
/** @type {HTMLSpanElement} */ /** @type {HTMLSpanElement} */
this.$clock = document.getElementById("clockviewer"); this.$clock = document.getElementById("clockviewer");
this.hasBeenEvaluated = false; this.hasBeenEvaluated = false;
this.currentPulse = 0; this.currentPulsePosition = 0;
this.nextPulsePosition = -1;
this.executionLatency = 0;
this.lastLatencies = [0, 0, 0, 0, 0];
this.indexOfLastLatencies = 0;
setInterval(() => this.ping(), 1000);
} }
ping() {
this.port.postMessage({ type: "ping", t: performance.now() })
}
/** @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 === "ping") {
let info = this.convertTimeToBarsBeats(message.data.currentTime); const delay = performance.now() - message.data.t;
this.app.clock.time_position = { bar: info.bar, beat: info.beat, pulse: info.ppqn } console.log(delay);
this.$clock.innerHTML = `[${info.bar} | ${info.beat} | ${zeroPad(info.ppqn, '2')}]` } else if (message.data && message.data.type === "bang") {
let { futureTimeStamp, timeToNextPulse, nextPulsePosition } = this.convertTimeToNextBarsBeats(message.data.currentTime);
// Evaluate the global buffer only once per ppqn value // Evaluate the global buffer only once per ppqn value
if (this.currentPulse !== info.ppqn) { if (this.nextPulsePosition !== nextPulsePosition) {
this.hasBeenEvaluated = false; this.nextPulsePosition = nextPulsePosition;
} setTimeout(() => {
const now = performance.now();
if (!this.hasBeenEvaluated) { this.app.clock.time_position = futureTimeStamp;
tryEvaluate( this.app, this.app.global_buffer ); this.$clock.innerHTML = `[${futureTimeStamp.bar} | ${futureTimeStamp.beat} | ${zeroPad(futureTimeStamp.pulse, '2')}]`;
this.hasBeenEvaluated = true; tryEvaluate( this.app, this.app.global_buffer );
this.currentPulse = info.ppqn; this.hasBeenEvaluated = true;
this.app.api.midi_clock(); this.currentPulsePosition = nextPulsePosition;
this.app.api.midi_clock();
const then = performance.now();
this.lastLatencies[this.indexOfLastLatencies] = then - now;
this.indexOfLastLatencies = (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);
} }
} }
}; };
@ -57,4 +72,41 @@ export class TransportNode extends AudioWorkletNode {
this.app.clock.tick++ this.app.clock.tick++
return { bar: barNumber, beat: beatWithinBar, ppqn: ppqnPosition }; return { bar: barNumber, beat: beatWithinBar, ppqn: ppqnPosition };
} }
convertTimeToNextBarsBeats(currentTime) {
const beatDuration = 60 / this.app.clock.bpm;
const beatNumber = (currentTime) / beatDuration;
this.currentPulsePosition = beatNumber * this.app.clock.ppqn;
const nextPulsePosition = Math.ceil(this.currentPulsePosition);
const timeToNextPulse = this.app.clock.convertPulseToSecond(this.nextPulsePosition - this.currentPulsePosition);
const futureBeatNumber = this.nextPulsePosition / this.app.clock.ppqn;
const futureBarNumber = futureBeatNumber / beatsPerBar;
const futureTimeStamp = {
bar: Math.floor(futureBarNumber) + 1,
beat: Math.floor(futureBarNumber) % beatsPerBar + 1,
pulse: this.nextPulsePosition
};
this.app.clock.tick++
return {
futureTimeStamp,
timeToNextPulse,
nextPulsePosition
};
// TODO: correction
// const barNumber = Math.floor(beatNumber / beatsPerBar) + 1;
// const beatsPerBar = this.app.clock.time_signature[0];
// const beatWithinBar = Math.floor(beatNumber % beatsPerBar) + 1;
// const ppqnPosition = Math.floor((beatNumber % 1) * this.app.clock.ppqn);
// return {
// bar: barNumber,
// beat: beatWithinBar,
// ppqn: ppqnPosition,
// delta: delta
// };
}
} }

View File

@ -8,7 +8,9 @@ class TransportProcessor extends AudioWorkletProcessor {
} }
handleMessage = (message) => { handleMessage = (message) => {
if (message.data === "start") { if (message.data && message.data.type === "ping") {
this.port.postMessage(message.data);
} 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;