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",
"unique-names-generator": "^4.7.1",
"vite-plugin-markdown": "^2.1.0",
"zifferjs": "^0.0.16",
"zifferjs": "link:../zifferjs",
"zzfx": "^1.2.0"
}
}

View File

@ -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 {
console.log("STARTING?");
this.app.audioContext.resume()
this.transportNode?.start();
}
}
public pause(): void {

View File

@ -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>) => any} */
handleMessage = (message) => {
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);
// Evaluate the global buffer only once per ppqn value
if (this.nextPulsePosition !== nextPulsePosition) {
this.nextPulsePosition = nextPulsePosition;
setTimeout(() => {
console.log("EVALUATING");
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.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};
}
}

View File

@ -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,9 +49,17 @@ 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);
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;
}
}

View File

@ -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) {
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;
}

View File

@ -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();
});
});

View File

@ -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"