First try
This commit is contained in:
@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
44
src/Clock.ts
44
src/Clock.ts
@ -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 {
|
||||
|
||||
@ -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};
|
||||
}
|
||||
|
||||
}
|
||||
@ -9,6 +9,9 @@ class TransportProcessor extends AudioWorkletProcessor {
|
||||
this.lastPausedTime = 0;
|
||||
this.startedAgainTime = 0;
|
||||
this.wasStopped = false;
|
||||
this.bpm = 120;
|
||||
this.ppqn = 48;
|
||||
this.currentPulsePosition = 0;
|
||||
}
|
||||
|
||||
handleMessage = (message) => {
|
||||
@ -25,8 +28,12 @@ class TransportProcessor extends AudioWorkletProcessor {
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
|
||||
@ -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();
|
||||
});
|
||||
});
|
||||
|
||||
@ -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"
|
||||
|
||||
Reference in New Issue
Block a user