diff --git a/packages/core/clockworker.mjs b/packages/core/clockworker.mjs index 97940d3e..0515c0d0 100644 --- a/packages/core/clockworker.mjs +++ b/packages/core/clockworker.mjs @@ -12,7 +12,7 @@ const createWorker = (func) => new Worker(urlifyFunction(func)); // this is just a setInterval with a counter, running in a worker export class ClockWorker { worker; - interval = 1 / 10; // query span, use powers of 2 to get better float accuracy + interval = 1 / 20; // query span, use powers of 2 to get better float accuracy tick = 0; constructor(callback, interval = this.interval) { this.interval = interval; diff --git a/packages/core/scheduler.mjs b/packages/core/scheduler.mjs index c2c7448a..aa2c5a37 100644 --- a/packages/core/scheduler.mjs +++ b/packages/core/scheduler.mjs @@ -6,45 +6,52 @@ This program is free software: you can redistribute it and/or modify it under th import { ClockWorker } from './clockworker.mjs'; +// TODO: make pause work with origin. +// find out why setPattern takes so long +// reimplement setCps + export class Scheduler { worker; pattern; started = false; + origin; phase = 0; cps = 1; - lastTime; - error = 0; + getTime; constructor({ interval, onTrigger, onError, latency = 0.1, getTime }) { this.worker = new ClockWorker((_, interval) => { try { - const begin = this.phase; - const end = this.phase + interval * this.cps; - this.phase = end; - const haps = this.pattern.queryArc(begin, end); - // this.log(begin, end, haps); - // measure time between last and current callback and calculate deviation from extected interval - const time = getTime?.(); - if (time && this.lastTime) { - const diff = time - this.lastTime; - this.error = diff - interval; - } - this.lastTime = time; + // first, calculate queryArc, where + // - first query should start from zero + // - next query must start where the other left off + // - queries must be synced to the interval clock => no drifting + const begin = this.phase; // begin where we left off last time || 0 + const time = getTime(); + this.origin = this.origin ?? time; // capture timestamp of first tick + const runTime = time - this.origin; // how long the scheduler is running since start + const cps = this.cps; // remember cps used to calculate the current slice + // TODO: find a way to implement cps without jumps.. + const end = (runTime + interval) * cps; + // console.log('runTime', runTime); + this.phase = end; // remember where we left off fro next query + const haps = this.pattern.queryArc(begin, end); // get haps + const t = getTime(); // need new timestamp after query (can take a few ms) + // schedule each hap haps.forEach((hap) => { if (typeof hap.value?.cps === 'number') { this.setCps(hap.value?.cps); } + // skip haps without onset if (!hap.part.begin.equals(hap.whole.begin)) { return; } - // console.log('error', this.error); - const deadline = (hap.whole.begin - begin) / this.cps + latency - this.error; - // const deadline = hap.whole.begin - begin + latency; // - error; + // calculate absolute time for this hap, .whole.begin is relative to 0, so we need to add the origin + const scheduledTime = hap.whole.begin / cps + latency + this.origin; + // deadline = time in s until the event should be scheduled + const deadline = scheduledTime - t; if (deadline < 0) { - console.warn( - `deadline ${deadline.toFixed( - 2, - )} is below zero! latency ${latency}s, interval ${interval}s, error ${this.error.toFixed(2)}s`, - ); + console.warn(`deadline ${deadline.toFixed(2)} is below zero! latency ${latency}s, interval ${interval}s`); + return; } // TODO: use legato / duration of objectified value const duration = hap.duration / this.cps; @@ -57,6 +64,7 @@ export class Scheduler { }, interval); } start() { + console.log('start'); if (!this.pattern) { throw new Error('Scheduler: no pattern set! call .setPattern first.'); } @@ -64,15 +72,21 @@ export class Scheduler { this.started = true; } pause() { + console.log('pause'); this.worker.stop(); + this.phase = 0; + delete this.origin; this.started = false; } stop() { + console.log('stop'); this.phase = 0; + delete this.origin; this.worker.stop(); this.started = false; } setPattern(pat) { + console.log('set pattern!'); this.pattern = pat; } setCps(cps = 1) {