113 lines
4.6 KiB
JavaScript
113 lines
4.6 KiB
JavaScript
import { tryEvaluate } from "./Evaluator";
|
|
const zeroPad = (num, places) => String(num).padStart(places, '0')
|
|
|
|
export class TransportNode extends AudioWorkletNode {
|
|
|
|
constructor(context, options, application) {
|
|
super(context, "transport", options);
|
|
this.app = application
|
|
this.port.addEventListener("message", this.handleMessage);
|
|
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,
|
|
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,
|
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
];
|
|
this.indexOfLastLatencies = 0;
|
|
// setInterval(() => this.ping(), 1000);
|
|
this.startTime = null;
|
|
this.elapsedTime = 0;
|
|
}
|
|
|
|
/** @type {(this: MessagePort, ev: MessageEvent<any>) => 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);
|
|
|
|
// 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;
|
|
// 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;
|
|
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);
|
|
}
|
|
}
|
|
};
|
|
|
|
start() {
|
|
this.port.postMessage("start");
|
|
}
|
|
|
|
pause() {
|
|
this.port.postMessage("pause");
|
|
}
|
|
|
|
stop() {
|
|
this.startTime = null;
|
|
this.elapsedTime = null;
|
|
this.app.clock.tick = 0;
|
|
// this.$clock.innerHTML = `[${1} | ${1} | ${zeroPad(1, '2')}]`;
|
|
this.port.postMessage("stop");
|
|
}
|
|
|
|
convertTimeToBarsBeats(currentTime) {
|
|
const beatDuration = 60 / this.app.clock.bpm;
|
|
const beatNumber = (currentTime) / beatDuration;
|
|
|
|
const beatsPerBar = this.app.clock.time_signature[0];
|
|
const barNumber = Math.floor(beatNumber / beatsPerBar) + 1;
|
|
const beatWithinBar = Math.floor(beatNumber % beatsPerBar) + 1;
|
|
|
|
const ppqnPosition = Math.floor((beatNumber % 1) * this.app.clock.ppqn);
|
|
this.app.clock.tick++
|
|
return { bar: barNumber, beat: beatWithinBar, ppqn: ppqnPosition };
|
|
}
|
|
|
|
convertTimeToNextBarsBeats(currentTime) {
|
|
|
|
const beatDuration = 60 / this.app.clock.bpm;
|
|
const beatNumber = (currentTime) / beatDuration;
|
|
const beatsPerBar = this.app.clock.time_signature[0];
|
|
|
|
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(futureBeatNumber) % beatsPerBar + 1,
|
|
pulse: Math.floor(this.nextPulsePosition) % this.app.clock.ppqn
|
|
};
|
|
|
|
this.app.clock.tick++
|
|
return {
|
|
futureTimeStamp,
|
|
timeToNextPulse,
|
|
nextPulsePosition
|
|
};
|
|
}
|
|
} |