From 796fea2d6c6898da49ed42289396a5232e259683 Mon Sep 17 00:00:00 2001 From: Marcel Blum Date: Mon, 29 Nov 2021 11:14:14 -0500 Subject: [PATCH 1/6] prevent Context options overriding, remove unreachable/overridden code, fix Ticker precision add _minimumUpdateInterval to Ticker, derive from actual context sampleRate instead of assuming 44100; add lookAhead setter to Context so that updateInterval is also adjusted; prevent latencyHint from getting overridden when rolling own context --- Tone/core/clock/Ticker.ts | 16 ++++++--- Tone/core/context/Context.ts | 66 +++++++++++++++--------------------- 2 files changed, 39 insertions(+), 43 deletions(-) diff --git a/Tone/core/clock/Ticker.ts b/Tone/core/clock/Ticker.ts index 91a07415..108abd52 100644 --- a/Tone/core/clock/Ticker.ts +++ b/Tone/core/clock/Ticker.ts @@ -16,7 +16,12 @@ export class Ticker { /** * The update interval of the worker */ - private _updateInterval: Seconds; + private _updateInterval!: Seconds; + + /** + * The lowest allowable interval, preferably calculated from context sampleRate + */ + private _minimumUpdateInterval: Seconds; /** * The callback to invoke at regular intervals @@ -33,11 +38,12 @@ export class Ticker { */ private _worker!: Worker; - constructor(callback: () => void, type: TickerClockSource, updateInterval: Seconds) { + constructor(callback: () => void, type: TickerClockSource, updateInterval: Seconds, contextSampleRate?: number) { this._callback = callback; this._type = type; - this._updateInterval = updateInterval; + this._minimumUpdateInterval = Math.max( 128/(contextSampleRate || 44100), .001 ); + this.updateInterval = updateInterval; // create the clock source for the first time this._createClock(); @@ -121,9 +127,9 @@ export class Ticker { return this._updateInterval; } set updateInterval(interval: Seconds) { - this._updateInterval = Math.max(interval, 128 / 44100); + this._updateInterval = Math.max(interval, this._minimumUpdateInterval); if (this._type === "worker") { - this._worker.postMessage(Math.max(interval * 1000, 1)); + this._worker?.postMessage(this._updateInterval * 1000); } } diff --git a/Tone/core/context/Context.ts b/Tone/core/context/Context.ts index 7042268b..e2803b47 100644 --- a/Tone/core/context/Context.ts +++ b/Tone/core/context/Context.ts @@ -3,7 +3,7 @@ import { Seconds } from "../type/Units"; import { isAudioContext } from "../util/AdvancedTypeCheck"; import { optionsFromArguments } from "../util/Defaults"; import { Timeline } from "../util/Timeline"; -import { isDefined, isString } from "../util/TypeCheck"; +import { isDefined } from "../util/TypeCheck"; import { AnyAudioContext, createAudioContext, @@ -39,13 +39,6 @@ export interface ContextTimeoutEvent { export class Context extends BaseContext { readonly name: string = "Context"; - /** - * The amount of time into the future events are scheduled. Giving Web Audio - * a short amount of time into the future to schedule events can reduce clicks and - * improve performance. This value can be set to 0 to get the lowest latency. - */ - lookAhead: Seconds; - /** * private reference to the BaseAudioContext */ @@ -116,16 +109,20 @@ export class Context extends BaseContext { if (options.context) { this._context = options.context; + // custom context provided, latencyHint unknown (unless explicitly provided in options) + this._latencyHint = arguments[0]?.latencyHint || ""; } else { this._context = createAudioContext({ latencyHint: options.latencyHint, }); + this._latencyHint = options.latencyHint; } this._ticker = new Ticker( this.emit.bind(this, "tick"), options.clockSource, - options.updateInterval + options.updateInterval, + this._context.sampleRate ); this.on("tick", this._timeoutLoop.bind(this)); @@ -133,9 +130,9 @@ export class Context extends BaseContext { this._context.onstatechange = () => { this.emit("statechange", this.state); }; - - this._setLatencyHint(options.latencyHint); - this.lookAhead = options.lookAhead; + + // if no custom updateInterval provided, updateInterval will be derived by lookAhead setter + this[arguments[0]?.hasOwnProperty("updateInterval") ? "_lookAhead" : "lookAhead"] = options.lookAhead; } static getDefaults(): ContextOptions { @@ -391,8 +388,9 @@ export class Context extends BaseContext { /** * How often the interval callback is invoked. * This number corresponds to how responsive the scheduling - * can be. context.updateInterval + context.lookAhead gives you the - * total latency between scheduling an event and hearing it. + * can be. Setting to 0 will result in the lowest practial interval + * based on context properties. context.updateInterval + context.lookAhead + * gives you the total latency between scheduling an event and hearing it. */ get updateInterval(): Seconds { return this._ticker.updateInterval; @@ -412,6 +410,21 @@ export class Context extends BaseContext { this._ticker.type = type; } + /** + * The amount of time into the future events are scheduled. Giving Web Audio + * a short amount of time into the future to schedule events can reduce clicks and + * improve performance. This value can be set to 0 to get the lowest latency. + */ + get lookAhead(): Seconds { + return this._lookAhead; + } + set lookAhead(time: Seconds) { + this._lookAhead = time; + // if lookAhead is 0, default to .025 updateInterval + this.updateInterval = time ? (time / 2) : .025; + } + private _lookAhead!: Seconds; + /** * The type of playback, which affects tradeoffs between audio * output latency and responsiveness. @@ -431,29 +444,6 @@ export class Context extends BaseContext { return this._latencyHint; } - /** - * Update the lookAhead and updateInterval based on the latencyHint - */ - private _setLatencyHint(hint: ContextLatencyHint | Seconds): void { - let lookAheadValue = 0; - this._latencyHint = hint; - if (isString(hint)) { - switch (hint) { - case "interactive": - lookAheadValue = 0.1; - break; - case "playback": - lookAheadValue = 0.5; - break; - case "balanced": - lookAheadValue = 0.25; - break; - } - } - this.lookAhead = lookAheadValue; - this.updateInterval = lookAheadValue / 2; - } - /** * The unwrapped AudioContext or OfflineAudioContext */ @@ -469,7 +459,7 @@ export class Context extends BaseContext { * }, 100); */ now(): Seconds { - return this._context.currentTime + this.lookAhead; + return this._context.currentTime + this._lookAhead; } /** From 2fa6c66b9046ee04de661d54efc1b61ab8b8b271 Mon Sep 17 00:00:00 2001 From: Marcel Blum Date: Mon, 29 Nov 2021 11:33:59 -0500 Subject: [PATCH 2/6] tweak default updateInterval for 0 lookAhead --- Tone/core/context/Context.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Tone/core/context/Context.ts b/Tone/core/context/Context.ts index e2803b47..b9d52a51 100644 --- a/Tone/core/context/Context.ts +++ b/Tone/core/context/Context.ts @@ -420,8 +420,8 @@ export class Context extends BaseContext { } set lookAhead(time: Seconds) { this._lookAhead = time; - // if lookAhead is 0, default to .025 updateInterval - this.updateInterval = time ? (time / 2) : .025; + // if lookAhead is 0, default to .0125 updateInterval + this.updateInterval = time ? (time / 2) : .0125; } private _lookAhead!: Seconds; From 63c958774b7b53aaa23ceb796d719ae0106c58c7 Mon Sep 17 00:00:00 2001 From: Marcel Blum Date: Mon, 29 Nov 2021 20:23:15 -0500 Subject: [PATCH 3/6] another updateInterval tweak --- Tone/core/context/Context.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tone/core/context/Context.ts b/Tone/core/context/Context.ts index b9d52a51..0545a20e 100644 --- a/Tone/core/context/Context.ts +++ b/Tone/core/context/Context.ts @@ -421,7 +421,7 @@ export class Context extends BaseContext { set lookAhead(time: Seconds) { this._lookAhead = time; // if lookAhead is 0, default to .0125 updateInterval - this.updateInterval = time ? (time / 2) : .0125; + this.updateInterval = time ? (time / 2) : .01; } private _lookAhead!: Seconds; From fba875c809b4b77b26dbd283da269570906c0137 Mon Sep 17 00:00:00 2001 From: Marcel Blum Date: Wed, 1 Dec 2021 12:19:03 -0500 Subject: [PATCH 4/6] include updateInterval check in Context constructor test --- Tone/core/context/Context.test.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Tone/core/context/Context.test.ts b/Tone/core/context/Context.test.ts index 83e1dffd..9568a905 100644 --- a/Tone/core/context/Context.test.ts +++ b/Tone/core/context/Context.test.ts @@ -75,8 +75,10 @@ describe("Context", () => { clockSource: "timeout", latencyHint: "playback", lookAhead: 0.2, + updateInterval: 0.1 }); expect(ctx.lookAhead).to.equal(0.2); + expect(ctx.updateInterval).to.equal(0.1); expect(ctx.latencyHint).to.equal("playback"); expect(ctx.clockSource).to.equal("timeout"); ctx.dispose(); From 1295bd46037c3ffcb73afff173e6099d0ac4336e Mon Sep 17 00:00:00 2001 From: Marcel Blum Date: Wed, 1 Dec 2021 13:55:28 -0500 Subject: [PATCH 5/6] docs update --- Tone/core/context/Context.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/Tone/core/context/Context.ts b/Tone/core/context/Context.ts index 0545a20e..aca22af0 100644 --- a/Tone/core/context/Context.ts +++ b/Tone/core/context/Context.ts @@ -414,6 +414,7 @@ export class Context extends BaseContext { * The amount of time into the future events are scheduled. Giving Web Audio * a short amount of time into the future to schedule events can reduce clicks and * improve performance. This value can be set to 0 to get the lowest latency. + * Adjusting this value also affects the [[updateInterval]]. */ get lookAhead(): Seconds { return this._lookAhead; From c68903380e28324c3e1c1c800bbe3c9281e55d24 Mon Sep 17 00:00:00 2001 From: Marcel Blum Date: Wed, 1 Dec 2021 17:44:53 -0500 Subject: [PATCH 6/6] comment correction --- Tone/core/context/Context.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tone/core/context/Context.ts b/Tone/core/context/Context.ts index aca22af0..0f417a8b 100644 --- a/Tone/core/context/Context.ts +++ b/Tone/core/context/Context.ts @@ -421,7 +421,7 @@ export class Context extends BaseContext { } set lookAhead(time: Seconds) { this._lookAhead = time; - // if lookAhead is 0, default to .0125 updateInterval + // if lookAhead is 0, default to .01 updateInterval this.updateInterval = time ? (time / 2) : .01; } private _lookAhead!: Seconds;