First try
This commit is contained in:
@ -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"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
48
src/Clock.ts
48
src/Clock.ts
@ -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 {
|
||||||
|
|||||||
@ -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};
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -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();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@ -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"
|
||||||
|
|||||||
Reference in New Issue
Block a user