Merge pull request #987 from marcelblum/context-options-bugfixes

fixes for Context options handling & micro timing bugs
This commit is contained in:
Yotam Mann 2022-01-15 09:51:35 -05:00 committed by GitHub
commit fb17cf564f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 42 additions and 43 deletions

View file

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

View file

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

View file

@ -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,22 @@ 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.
* Adjusting this value also affects the [[updateInterval]].
*/
get lookAhead(): Seconds {
return this._lookAhead;
}
set lookAhead(time: Seconds) {
this._lookAhead = time;
// if lookAhead is 0, default to .01 updateInterval
this.updateInterval = time ? (time / 2) : .01;
}
private _lookAhead!: Seconds;
/**
* The type of playback, which affects tradeoffs between audio
* output latency and responsiveness.
@ -431,29 +445,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 +460,7 @@ export class Context extends BaseContext {
* }, 100);
*/
now(): Seconds {
return this._context.currentTime + this.lookAhead;
return this._context.currentTime + this._lookAhead;
}
/**