mirror of
https://github.com/Tonejs/Tone.js
synced 2024-12-27 03:53:07 +00:00
Merge pull request #987 from marcelblum/context-options-bugfixes
fixes for Context options handling & micro timing bugs
This commit is contained in:
commit
fb17cf564f
3 changed files with 42 additions and 43 deletions
|
@ -16,7 +16,12 @@ export class Ticker {
|
||||||
/**
|
/**
|
||||||
* The update interval of the worker
|
* 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
|
* The callback to invoke at regular intervals
|
||||||
|
@ -33,11 +38,12 @@ export class Ticker {
|
||||||
*/
|
*/
|
||||||
private _worker!: Worker;
|
private _worker!: Worker;
|
||||||
|
|
||||||
constructor(callback: () => void, type: TickerClockSource, updateInterval: Seconds) {
|
constructor(callback: () => void, type: TickerClockSource, updateInterval: Seconds, contextSampleRate?: number) {
|
||||||
|
|
||||||
this._callback = callback;
|
this._callback = callback;
|
||||||
this._type = type;
|
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
|
// create the clock source for the first time
|
||||||
this._createClock();
|
this._createClock();
|
||||||
|
@ -121,9 +127,9 @@ export class Ticker {
|
||||||
return this._updateInterval;
|
return this._updateInterval;
|
||||||
}
|
}
|
||||||
set updateInterval(interval: Seconds) {
|
set updateInterval(interval: Seconds) {
|
||||||
this._updateInterval = Math.max(interval, 128 / 44100);
|
this._updateInterval = Math.max(interval, this._minimumUpdateInterval);
|
||||||
if (this._type === "worker") {
|
if (this._type === "worker") {
|
||||||
this._worker.postMessage(Math.max(interval * 1000, 1));
|
this._worker?.postMessage(this._updateInterval * 1000);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -75,8 +75,10 @@ describe("Context", () => {
|
||||||
clockSource: "timeout",
|
clockSource: "timeout",
|
||||||
latencyHint: "playback",
|
latencyHint: "playback",
|
||||||
lookAhead: 0.2,
|
lookAhead: 0.2,
|
||||||
|
updateInterval: 0.1
|
||||||
});
|
});
|
||||||
expect(ctx.lookAhead).to.equal(0.2);
|
expect(ctx.lookAhead).to.equal(0.2);
|
||||||
|
expect(ctx.updateInterval).to.equal(0.1);
|
||||||
expect(ctx.latencyHint).to.equal("playback");
|
expect(ctx.latencyHint).to.equal("playback");
|
||||||
expect(ctx.clockSource).to.equal("timeout");
|
expect(ctx.clockSource).to.equal("timeout");
|
||||||
ctx.dispose();
|
ctx.dispose();
|
||||||
|
|
|
@ -3,7 +3,7 @@ import { Seconds } from "../type/Units";
|
||||||
import { isAudioContext } from "../util/AdvancedTypeCheck";
|
import { isAudioContext } from "../util/AdvancedTypeCheck";
|
||||||
import { optionsFromArguments } from "../util/Defaults";
|
import { optionsFromArguments } from "../util/Defaults";
|
||||||
import { Timeline } from "../util/Timeline";
|
import { Timeline } from "../util/Timeline";
|
||||||
import { isDefined, isString } from "../util/TypeCheck";
|
import { isDefined } from "../util/TypeCheck";
|
||||||
import {
|
import {
|
||||||
AnyAudioContext,
|
AnyAudioContext,
|
||||||
createAudioContext,
|
createAudioContext,
|
||||||
|
@ -39,13 +39,6 @@ export interface ContextTimeoutEvent {
|
||||||
export class Context extends BaseContext {
|
export class Context extends BaseContext {
|
||||||
readonly name: string = "Context";
|
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
|
* private reference to the BaseAudioContext
|
||||||
*/
|
*/
|
||||||
|
@ -116,16 +109,20 @@ export class Context extends BaseContext {
|
||||||
|
|
||||||
if (options.context) {
|
if (options.context) {
|
||||||
this._context = options.context;
|
this._context = options.context;
|
||||||
|
// custom context provided, latencyHint unknown (unless explicitly provided in options)
|
||||||
|
this._latencyHint = arguments[0]?.latencyHint || "";
|
||||||
} else {
|
} else {
|
||||||
this._context = createAudioContext({
|
this._context = createAudioContext({
|
||||||
latencyHint: options.latencyHint,
|
latencyHint: options.latencyHint,
|
||||||
});
|
});
|
||||||
|
this._latencyHint = options.latencyHint;
|
||||||
}
|
}
|
||||||
|
|
||||||
this._ticker = new Ticker(
|
this._ticker = new Ticker(
|
||||||
this.emit.bind(this, "tick"),
|
this.emit.bind(this, "tick"),
|
||||||
options.clockSource,
|
options.clockSource,
|
||||||
options.updateInterval
|
options.updateInterval,
|
||||||
|
this._context.sampleRate
|
||||||
);
|
);
|
||||||
this.on("tick", this._timeoutLoop.bind(this));
|
this.on("tick", this._timeoutLoop.bind(this));
|
||||||
|
|
||||||
|
@ -133,9 +130,9 @@ export class Context extends BaseContext {
|
||||||
this._context.onstatechange = () => {
|
this._context.onstatechange = () => {
|
||||||
this.emit("statechange", this.state);
|
this.emit("statechange", this.state);
|
||||||
};
|
};
|
||||||
|
|
||||||
this._setLatencyHint(options.latencyHint);
|
// if no custom updateInterval provided, updateInterval will be derived by lookAhead setter
|
||||||
this.lookAhead = options.lookAhead;
|
this[arguments[0]?.hasOwnProperty("updateInterval") ? "_lookAhead" : "lookAhead"] = options.lookAhead;
|
||||||
}
|
}
|
||||||
|
|
||||||
static getDefaults(): ContextOptions {
|
static getDefaults(): ContextOptions {
|
||||||
|
@ -391,8 +388,9 @@ export class Context extends BaseContext {
|
||||||
/**
|
/**
|
||||||
* How often the interval callback is invoked.
|
* How often the interval callback is invoked.
|
||||||
* This number corresponds to how responsive the scheduling
|
* This number corresponds to how responsive the scheduling
|
||||||
* can be. context.updateInterval + context.lookAhead gives you the
|
* can be. Setting to 0 will result in the lowest practial interval
|
||||||
* total latency between scheduling an event and hearing it.
|
* based on context properties. context.updateInterval + context.lookAhead
|
||||||
|
* gives you the total latency between scheduling an event and hearing it.
|
||||||
*/
|
*/
|
||||||
get updateInterval(): Seconds {
|
get updateInterval(): Seconds {
|
||||||
return this._ticker.updateInterval;
|
return this._ticker.updateInterval;
|
||||||
|
@ -412,6 +410,22 @@ export class Context extends BaseContext {
|
||||||
this._ticker.type = type;
|
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
|
* The type of playback, which affects tradeoffs between audio
|
||||||
* output latency and responsiveness.
|
* output latency and responsiveness.
|
||||||
|
@ -431,29 +445,6 @@ export class Context extends BaseContext {
|
||||||
return this._latencyHint;
|
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
|
* The unwrapped AudioContext or OfflineAudioContext
|
||||||
*/
|
*/
|
||||||
|
@ -469,7 +460,7 @@ export class Context extends BaseContext {
|
||||||
* }, 100);
|
* }, 100);
|
||||||
*/
|
*/
|
||||||
now(): Seconds {
|
now(): Seconds {
|
||||||
return this._context.currentTime + this.lookAhead;
|
return this._context.currentTime + this._lookAhead;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
Loading…
Reference in a new issue