tone 14.8.35 → 14.8.38
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Tone/component/analysis/Meter.test.ts +25 -0
- package/Tone/component/analysis/Meter.ts +7 -5
- package/Tone/core/clock/TickSource.test.ts +52 -0
- package/Tone/core/clock/TickSource.ts +70 -9
- package/Tone/core/clock/Ticker.ts +11 -5
- package/Tone/core/context/Context.test.ts +2 -0
- package/Tone/core/context/Context.ts +29 -38
- package/Tone/core/context/ToneAudioBuffer.ts +52 -27
- package/Tone/event/Pattern.test.ts +31 -5
- package/Tone/event/Pattern.ts +19 -6
- package/Tone/event/PatternGenerator.test.ts +18 -26
- package/Tone/event/PatternGenerator.ts +52 -59
- package/Tone/instrument/MetalSynth.ts +48 -21
- package/Tone/signal/ToneConstantSource.test.ts +1 -1
- package/Tone/source/OneShotSource.ts +1 -1
- package/Tone/source/buffer/Player.test.ts +25 -0
- package/Tone/source/buffer/Player.ts +1 -1
- package/Tone/source/oscillator/ToneOscillatorNode.test.ts +1 -1
- package/Tone/version.ts +1 -1
- package/build/Tone.js +1 -1
- package/build/Tone.js.map +1 -1
- package/build/esm/component/analysis/Meter.d.ts +1 -1
- package/build/esm/component/analysis/Meter.js +5 -7
- package/build/esm/component/analysis/Meter.js.map +1 -1
- package/build/esm/core/clock/TickSource.d.ts +8 -0
- package/build/esm/core/clock/TickSource.js +49 -7
- package/build/esm/core/clock/TickSource.js.map +1 -1
- package/build/esm/core/clock/Ticker.d.ts +5 -1
- package/build/esm/core/clock/Ticker.js +6 -4
- package/build/esm/core/clock/Ticker.js.map +1 -1
- package/build/esm/core/context/Context.d.ts +12 -12
- package/build/esm/core/context/Context.js +26 -29
- package/build/esm/core/context/Context.js.map +1 -1
- package/build/esm/core/context/ToneAudioBuffer.js +25 -15
- package/build/esm/core/context/ToneAudioBuffer.js.map +1 -1
- package/build/esm/event/Pattern.d.ts +8 -0
- package/build/esm/event/Pattern.js +11 -4
- package/build/esm/event/Pattern.js.map +1 -1
- package/build/esm/event/PatternGenerator.d.ts +4 -4
- package/build/esm/event/PatternGenerator.js +52 -58
- package/build/esm/event/PatternGenerator.js.map +1 -1
- package/build/esm/instrument/MetalSynth.d.ts +0 -1
- package/build/esm/instrument/MetalSynth.js +15 -13
- package/build/esm/instrument/MetalSynth.js.map +1 -1
- package/build/esm/source/OneShotSource.js +1 -1
- package/build/esm/source/OneShotSource.js.map +1 -1
- package/build/esm/source/buffer/Player.js +2 -1
- package/build/esm/source/buffer/Player.js.map +1 -1
- package/build/esm/version.js +1 -1
- package/docs/tone.json +1 -1
- package/package.json +2 -2
|
@@ -5,6 +5,8 @@ import { ONLINE_TESTING } from "test/helper/Supports";
|
|
|
5
5
|
import { Signal } from "Tone/signal/Signal";
|
|
6
6
|
import { Oscillator } from "Tone/source/oscillator/Oscillator";
|
|
7
7
|
import { Meter } from "./Meter";
|
|
8
|
+
import { Panner } from "Tone/component/channel/Panner";
|
|
9
|
+
import { Merge } from "Tone/component/channel/Merge";
|
|
8
10
|
|
|
9
11
|
describe("Meter", () => {
|
|
10
12
|
|
|
@@ -86,6 +88,29 @@ describe("Meter", () => {
|
|
|
86
88
|
done();
|
|
87
89
|
}, 400);
|
|
88
90
|
});
|
|
91
|
+
|
|
92
|
+
it("can get the rms levels for multiple channels", (done) => {
|
|
93
|
+
const meter = new Meter({
|
|
94
|
+
channelCount: 2,
|
|
95
|
+
smoothing: 0.5,
|
|
96
|
+
});
|
|
97
|
+
const merge = new Merge().connect(meter);
|
|
98
|
+
const osc0 = new Oscillator().connect(merge, 0, 0).start();
|
|
99
|
+
const osc1 = new Oscillator().connect(merge, 0, 1).start();
|
|
100
|
+
osc0.volume.value = -6;
|
|
101
|
+
osc1.volume.value = -18;
|
|
102
|
+
setTimeout(() => {
|
|
103
|
+
const values = meter.getValue();
|
|
104
|
+
expect(values).to.have.lengthOf(2);
|
|
105
|
+
expect(values[0]).to.be.closeTo(-9, 1);
|
|
106
|
+
expect(values[1]).to.be.closeTo(-21, 1);
|
|
107
|
+
meter.dispose();
|
|
108
|
+
merge.dispose();
|
|
109
|
+
osc0.dispose();
|
|
110
|
+
osc1.dispose();
|
|
111
|
+
done();
|
|
112
|
+
}, 400);
|
|
113
|
+
});
|
|
89
114
|
}
|
|
90
115
|
});
|
|
91
116
|
});
|
|
@@ -42,9 +42,9 @@ export class Meter extends MeterBase<MeterOptions> {
|
|
|
42
42
|
smoothing: number;
|
|
43
43
|
|
|
44
44
|
/**
|
|
45
|
-
* The previous frame's value
|
|
45
|
+
* The previous frame's value for each channel.
|
|
46
46
|
*/
|
|
47
|
-
private _rms
|
|
47
|
+
private _rms: number[];
|
|
48
48
|
|
|
49
49
|
/**
|
|
50
50
|
* @param smoothing The amount of smoothing applied between frames.
|
|
@@ -64,6 +64,8 @@ export class Meter extends MeterBase<MeterOptions> {
|
|
|
64
64
|
|
|
65
65
|
this.smoothing = options.smoothing,
|
|
66
66
|
this.normalRange = options.normalRange;
|
|
67
|
+
this._rms = new Array(options.channelCount);
|
|
68
|
+
this._rms.fill(0);
|
|
67
69
|
}
|
|
68
70
|
|
|
69
71
|
static getDefaults(): MeterOptions {
|
|
@@ -93,13 +95,13 @@ export class Meter extends MeterBase<MeterOptions> {
|
|
|
93
95
|
getValue(): number | number[] {
|
|
94
96
|
const aValues = this._analyser.getValue();
|
|
95
97
|
const channelValues = this.channels === 1 ? [aValues as Float32Array] : aValues as Float32Array[];
|
|
96
|
-
const vals = channelValues.map(values => {
|
|
98
|
+
const vals = channelValues.map((values, index) => {
|
|
97
99
|
const totalSquared = values.reduce((total, current) => total + current * current, 0);
|
|
98
100
|
const rms = Math.sqrt(totalSquared / values.length);
|
|
99
101
|
// the rms can only fall at the rate of the smoothing
|
|
100
102
|
// but can jump up instantly
|
|
101
|
-
this._rms = Math.max(rms, this._rms
|
|
102
|
-
return this.normalRange ? this._rms : gainToDb(this._rms);
|
|
103
|
+
this._rms[index] = Math.max(rms, this._rms[index] * this.smoothing);
|
|
104
|
+
return this.normalRange ? this._rms[index] : gainToDb(this._rms[index]);
|
|
103
105
|
});
|
|
104
106
|
if (this.channels === 1) {
|
|
105
107
|
return vals[0];
|
|
@@ -237,6 +237,35 @@ describe("TickSource", () => {
|
|
|
237
237
|
expect(source.getTicksAtTime(6)).to.be.closeTo(6, 0.01);
|
|
238
238
|
source.dispose();
|
|
239
239
|
});
|
|
240
|
+
|
|
241
|
+
it("will recompute memoized values when events are modified", () => {
|
|
242
|
+
const source = new TickSource(1);
|
|
243
|
+
source.start(3).pause(4);
|
|
244
|
+
expect(source.getTicksAtTime(1)).to.be.closeTo(0, 0.01);
|
|
245
|
+
expect(source.getTicksAtTime(2)).to.be.closeTo(0, 0.01);
|
|
246
|
+
expect(source.getTicksAtTime(3)).to.be.closeTo(0, 0.01);
|
|
247
|
+
expect(source.getTicksAtTime(4)).to.be.closeTo(1, 0.01);
|
|
248
|
+
expect(source.getTicksAtTime(5)).to.be.closeTo(1, 0.01);
|
|
249
|
+
source.start(1).pause(2);
|
|
250
|
+
expect(source.getTicksAtTime(1)).to.be.closeTo(0, 0.01);
|
|
251
|
+
expect(source.getTicksAtTime(2)).to.be.closeTo(1, 0.01);
|
|
252
|
+
expect(source.getTicksAtTime(3)).to.be.closeTo(1, 0.01);
|
|
253
|
+
expect(source.getTicksAtTime(4)).to.be.closeTo(2, 0.01);
|
|
254
|
+
expect(source.getTicksAtTime(5)).to.be.closeTo(2, 0.01);
|
|
255
|
+
source.setTicksAtTime(3, 1);
|
|
256
|
+
expect(source.getTicksAtTime(1)).to.be.closeTo(3, 0.01);
|
|
257
|
+
expect(source.getTicksAtTime(2)).to.be.closeTo(4, 0.01);
|
|
258
|
+
expect(source.getTicksAtTime(3)).to.be.closeTo(4, 0.01);
|
|
259
|
+
expect(source.getTicksAtTime(4)).to.be.closeTo(5, 0.01);
|
|
260
|
+
expect(source.getTicksAtTime(5)).to.be.closeTo(5, 0.01);
|
|
261
|
+
source.cancel(4);
|
|
262
|
+
expect(source.getTicksAtTime(1)).to.be.closeTo(3, 0.01);
|
|
263
|
+
expect(source.getTicksAtTime(2)).to.be.closeTo(4, 0.01);
|
|
264
|
+
expect(source.getTicksAtTime(3)).to.be.closeTo(4, 0.01);
|
|
265
|
+
expect(source.getTicksAtTime(4)).to.be.closeTo(5, 0.01);
|
|
266
|
+
expect(source.getTicksAtTime(5)).to.be.closeTo(6, 0.01);
|
|
267
|
+
source.dispose();
|
|
268
|
+
});
|
|
240
269
|
});
|
|
241
270
|
|
|
242
271
|
context("forEachTickBetween", () => {
|
|
@@ -591,6 +620,29 @@ describe("TickSource", () => {
|
|
|
591
620
|
expect(source.getSecondsAtTime(1.5)).to.be.closeTo(0, 0.01);
|
|
592
621
|
source.dispose();
|
|
593
622
|
});
|
|
623
|
+
|
|
624
|
+
it("will recompute memoized values when events are modified", () => {
|
|
625
|
+
const source = new TickSource(1);
|
|
626
|
+
source.start(3).pause(4);
|
|
627
|
+
expect(source.getSecondsAtTime(1)).to.be.closeTo(0, 0.01);
|
|
628
|
+
expect(source.getSecondsAtTime(2)).to.be.closeTo(0, 0.01);
|
|
629
|
+
expect(source.getSecondsAtTime(3)).to.be.closeTo(0, 0.01);
|
|
630
|
+
expect(source.getSecondsAtTime(4)).to.be.closeTo(1, 0.01);
|
|
631
|
+
expect(source.getSecondsAtTime(5)).to.be.closeTo(1, 0.01);
|
|
632
|
+
source.start(1).pause(2);
|
|
633
|
+
expect(source.getSecondsAtTime(1)).to.be.closeTo(0, 0.01);
|
|
634
|
+
expect(source.getSecondsAtTime(2)).to.be.closeTo(1, 0.01);
|
|
635
|
+
expect(source.getSecondsAtTime(3)).to.be.closeTo(1, 0.01);
|
|
636
|
+
expect(source.getSecondsAtTime(4)).to.be.closeTo(2, 0.01);
|
|
637
|
+
expect(source.getSecondsAtTime(5)).to.be.closeTo(2, 0.01);
|
|
638
|
+
source.cancel(4);
|
|
639
|
+
expect(source.getSecondsAtTime(1)).to.be.closeTo(0, 0.01);
|
|
640
|
+
expect(source.getSecondsAtTime(2)).to.be.closeTo(1, 0.01);
|
|
641
|
+
expect(source.getSecondsAtTime(3)).to.be.closeTo(1, 0.01);
|
|
642
|
+
expect(source.getSecondsAtTime(4)).to.be.closeTo(2, 0.01);
|
|
643
|
+
expect(source.getSecondsAtTime(5)).to.be.closeTo(3, 0.01);
|
|
644
|
+
source.dispose();
|
|
645
|
+
});
|
|
594
646
|
});
|
|
595
647
|
|
|
596
648
|
context("Frequency", () => {
|
|
@@ -3,7 +3,7 @@ import { Seconds, Ticks, Time } from "../type/Units";
|
|
|
3
3
|
import { optionsFromArguments } from "../util/Defaults";
|
|
4
4
|
import { readOnly } from "../util/Interface";
|
|
5
5
|
import { PlaybackState, StateTimeline, StateTimelineEvent } from "../util/StateTimeline";
|
|
6
|
-
import { Timeline } from "../util/Timeline";
|
|
6
|
+
import { Timeline, TimelineEvent } from "../util/Timeline";
|
|
7
7
|
import { isDefined } from "../util/TypeCheck";
|
|
8
8
|
import { TickSignal } from "./TickSignal";
|
|
9
9
|
import { EQ } from "../util/Math";
|
|
@@ -13,12 +13,24 @@ interface TickSourceOptions extends ToneWithContextOptions {
|
|
|
13
13
|
units: "bpm" | "hertz";
|
|
14
14
|
}
|
|
15
15
|
|
|
16
|
-
interface TickSourceOffsetEvent {
|
|
16
|
+
interface TickSourceOffsetEvent extends TimelineEvent {
|
|
17
17
|
ticks: number;
|
|
18
18
|
time: number;
|
|
19
19
|
seconds: number;
|
|
20
20
|
}
|
|
21
21
|
|
|
22
|
+
interface TickSourceTicksAtTimeEvent extends TimelineEvent {
|
|
23
|
+
state: PlaybackState;
|
|
24
|
+
time: number;
|
|
25
|
+
ticks: number;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
interface TickSourceSecondsAtTimeEvent extends TimelineEvent {
|
|
29
|
+
state: PlaybackState;
|
|
30
|
+
time: number;
|
|
31
|
+
seconds: number;
|
|
32
|
+
}
|
|
33
|
+
|
|
22
34
|
/**
|
|
23
35
|
* Uses [TickSignal](TickSignal) to track elapsed ticks with complex automation curves.
|
|
24
36
|
*/
|
|
@@ -41,6 +53,16 @@ export class TickSource<TypeName extends "bpm" | "hertz"> extends ToneWithContex
|
|
|
41
53
|
*/
|
|
42
54
|
private _tickOffset: Timeline<TickSourceOffsetEvent> = new Timeline();
|
|
43
55
|
|
|
56
|
+
/**
|
|
57
|
+
* Memoized values of getTicksAtTime at events with state other than "started"
|
|
58
|
+
*/
|
|
59
|
+
private _ticksAtTime: Timeline<TickSourceTicksAtTimeEvent> = new Timeline<TickSourceTicksAtTimeEvent>();
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Memoized values of getSecondsAtTime at events with state other than "started"
|
|
63
|
+
*/
|
|
64
|
+
private _secondsAtTime: Timeline<TickSourceSecondsAtTimeEvent> = new Timeline<TickSourceSecondsAtTimeEvent>();
|
|
65
|
+
|
|
44
66
|
/**
|
|
45
67
|
* @param frequency The initial frequency that the signal ticks at
|
|
46
68
|
*/
|
|
@@ -90,6 +112,8 @@ export class TickSource<TypeName extends "bpm" | "hertz"> extends ToneWithContex
|
|
|
90
112
|
if (isDefined(offset)) {
|
|
91
113
|
this.setTicksAtTime(offset, computedTime);
|
|
92
114
|
}
|
|
115
|
+
this._ticksAtTime.cancel(computedTime);
|
|
116
|
+
this._secondsAtTime.cancel(computedTime);
|
|
93
117
|
}
|
|
94
118
|
return this;
|
|
95
119
|
}
|
|
@@ -111,6 +135,8 @@ export class TickSource<TypeName extends "bpm" | "hertz"> extends ToneWithContex
|
|
|
111
135
|
this._state.cancel(computedTime);
|
|
112
136
|
this._state.setStateAtTime("stopped", computedTime);
|
|
113
137
|
this.setTicksAtTime(0, computedTime);
|
|
138
|
+
this._ticksAtTime.cancel(computedTime);
|
|
139
|
+
this._secondsAtTime.cancel(computedTime);
|
|
114
140
|
return this;
|
|
115
141
|
}
|
|
116
142
|
|
|
@@ -122,6 +148,8 @@ export class TickSource<TypeName extends "bpm" | "hertz"> extends ToneWithContex
|
|
|
122
148
|
const computedTime = this.toSeconds(time);
|
|
123
149
|
if (this._state.getValueAtTime(computedTime) === "started") {
|
|
124
150
|
this._state.setStateAtTime("paused", computedTime);
|
|
151
|
+
this._ticksAtTime.cancel(computedTime);
|
|
152
|
+
this._secondsAtTime.cancel(computedTime);
|
|
125
153
|
}
|
|
126
154
|
return this;
|
|
127
155
|
}
|
|
@@ -134,6 +162,8 @@ export class TickSource<TypeName extends "bpm" | "hertz"> extends ToneWithContex
|
|
|
134
162
|
time = this.toSeconds(time);
|
|
135
163
|
this._state.cancel(time);
|
|
136
164
|
this._tickOffset.cancel(time);
|
|
165
|
+
this._ticksAtTime.cancel(time);
|
|
166
|
+
this._secondsAtTime.cancel(time);
|
|
137
167
|
return this;
|
|
138
168
|
}
|
|
139
169
|
|
|
@@ -145,16 +175,21 @@ export class TickSource<TypeName extends "bpm" | "hertz"> extends ToneWithContex
|
|
|
145
175
|
getTicksAtTime(time?: Time): Ticks {
|
|
146
176
|
const computedTime = this.toSeconds(time);
|
|
147
177
|
const stopEvent = this._state.getLastState("stopped", computedTime) as StateTimelineEvent;
|
|
178
|
+
|
|
179
|
+
// get previously memoized ticks if available
|
|
180
|
+
const memoizedEvent = this._ticksAtTime.get(computedTime);
|
|
181
|
+
|
|
148
182
|
// this event allows forEachBetween to iterate until the current time
|
|
149
183
|
const tmpEvent: StateTimelineEvent = { state: "paused", time: computedTime };
|
|
150
184
|
this._state.add(tmpEvent);
|
|
151
185
|
|
|
152
186
|
// keep track of the previous offset event
|
|
153
|
-
let lastState = stopEvent;
|
|
154
|
-
let elapsedTicks = 0;
|
|
187
|
+
let lastState = memoizedEvent ? memoizedEvent : stopEvent;
|
|
188
|
+
let elapsedTicks = memoizedEvent ? memoizedEvent.ticks : 0;
|
|
189
|
+
let eventToMemoize : TickSourceTicksAtTimeEvent | null = null;
|
|
155
190
|
|
|
156
191
|
// iterate through all the events since the last stop
|
|
157
|
-
this._state.forEachBetween(
|
|
192
|
+
this._state.forEachBetween(lastState.time, computedTime + this.sampleTime, e => {
|
|
158
193
|
let periodStartTime = lastState.time;
|
|
159
194
|
// if there is an offset event in this period use that
|
|
160
195
|
const offsetEvent = this._tickOffset.get(e.time);
|
|
@@ -164,6 +199,10 @@ export class TickSource<TypeName extends "bpm" | "hertz"> extends ToneWithContex
|
|
|
164
199
|
}
|
|
165
200
|
if (lastState.state === "started" && e.state !== "started") {
|
|
166
201
|
elapsedTicks += this.frequency.getTicksAtTime(e.time) - this.frequency.getTicksAtTime(periodStartTime);
|
|
202
|
+
// do not memoize the temporary event
|
|
203
|
+
if (e.time != tmpEvent.time) {
|
|
204
|
+
eventToMemoize = { state: e.state, time: e.time, ticks: elapsedTicks };
|
|
205
|
+
}
|
|
167
206
|
}
|
|
168
207
|
lastState = e;
|
|
169
208
|
});
|
|
@@ -171,6 +210,11 @@ export class TickSource<TypeName extends "bpm" | "hertz"> extends ToneWithContex
|
|
|
171
210
|
// remove the temporary event
|
|
172
211
|
this._state.remove(tmpEvent);
|
|
173
212
|
|
|
213
|
+
// memoize the ticks at the most recent event with state other than "started"
|
|
214
|
+
if (eventToMemoize) {
|
|
215
|
+
this._ticksAtTime.add(eventToMemoize);
|
|
216
|
+
}
|
|
217
|
+
|
|
174
218
|
// return the ticks
|
|
175
219
|
return elapsedTicks;
|
|
176
220
|
}
|
|
@@ -211,12 +255,16 @@ export class TickSource<TypeName extends "bpm" | "hertz"> extends ToneWithContex
|
|
|
211
255
|
const tmpEvent: StateTimelineEvent = { state: "paused", time };
|
|
212
256
|
this._state.add(tmpEvent);
|
|
213
257
|
|
|
258
|
+
// get previously memoized seconds if available
|
|
259
|
+
const memoizedEvent = this._secondsAtTime.get(time);
|
|
260
|
+
|
|
214
261
|
// keep track of the previous offset event
|
|
215
|
-
let lastState = stopEvent;
|
|
216
|
-
let elapsedSeconds = 0;
|
|
262
|
+
let lastState = memoizedEvent ? memoizedEvent : stopEvent;
|
|
263
|
+
let elapsedSeconds = memoizedEvent ? memoizedEvent.seconds : 0;
|
|
264
|
+
let eventToMemoize : TickSourceSecondsAtTimeEvent | null = null;
|
|
217
265
|
|
|
218
266
|
// iterate through all the events since the last stop
|
|
219
|
-
this._state.forEachBetween(
|
|
267
|
+
this._state.forEachBetween(lastState.time, time + this.sampleTime, e => {
|
|
220
268
|
let periodStartTime = lastState.time;
|
|
221
269
|
// if there is an offset event in this period use that
|
|
222
270
|
const offsetEvent = this._tickOffset.get(e.time);
|
|
@@ -226,6 +274,10 @@ export class TickSource<TypeName extends "bpm" | "hertz"> extends ToneWithContex
|
|
|
226
274
|
}
|
|
227
275
|
if (lastState.state === "started" && e.state !== "started") {
|
|
228
276
|
elapsedSeconds += e.time - periodStartTime;
|
|
277
|
+
// do not memoize the temporary event
|
|
278
|
+
if (e.time != tmpEvent.time) {
|
|
279
|
+
eventToMemoize = { state: e.state, time: e.time, seconds: elapsedSeconds };
|
|
280
|
+
}
|
|
229
281
|
}
|
|
230
282
|
lastState = e;
|
|
231
283
|
});
|
|
@@ -233,7 +285,12 @@ export class TickSource<TypeName extends "bpm" | "hertz"> extends ToneWithContex
|
|
|
233
285
|
// remove the temporary event
|
|
234
286
|
this._state.remove(tmpEvent);
|
|
235
287
|
|
|
236
|
-
//
|
|
288
|
+
// memoize the seconds at the most recent event with state other than "started"
|
|
289
|
+
if (eventToMemoize) {
|
|
290
|
+
this._secondsAtTime.add(eventToMemoize);
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
// return the seconds
|
|
237
294
|
return elapsedSeconds;
|
|
238
295
|
}
|
|
239
296
|
|
|
@@ -250,6 +307,8 @@ export class TickSource<TypeName extends "bpm" | "hertz"> extends ToneWithContex
|
|
|
250
307
|
ticks,
|
|
251
308
|
time,
|
|
252
309
|
});
|
|
310
|
+
this._ticksAtTime.cancel(time);
|
|
311
|
+
this._secondsAtTime.cancel(time);
|
|
253
312
|
return this;
|
|
254
313
|
}
|
|
255
314
|
|
|
@@ -332,6 +391,8 @@ export class TickSource<TypeName extends "bpm" | "hertz"> extends ToneWithContex
|
|
|
332
391
|
super.dispose();
|
|
333
392
|
this._state.dispose();
|
|
334
393
|
this._tickOffset.dispose();
|
|
394
|
+
this._ticksAtTime.dispose();
|
|
395
|
+
this._secondsAtTime.dispose();
|
|
335
396
|
this.frequency.dispose();
|
|
336
397
|
return this;
|
|
337
398
|
}
|
|
@@ -16,7 +16,12 @@ export class Ticker {
|
|
|
16
16
|
/**
|
|
17
17
|
* The update interval of the worker
|
|
18
18
|
*/
|
|
19
|
-
private _updateInterval
|
|
19
|
+
private _updateInterval!: Seconds;
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* The lowest allowable interval, preferably calculated from context sampleRate
|
|
23
|
+
*/
|
|
24
|
+
private _minimumUpdateInterval: Seconds;
|
|
20
25
|
|
|
21
26
|
/**
|
|
22
27
|
* The callback to invoke at regular intervals
|
|
@@ -33,11 +38,12 @@ export class Ticker {
|
|
|
33
38
|
*/
|
|
34
39
|
private _worker!: Worker;
|
|
35
40
|
|
|
36
|
-
constructor(callback: () => void, type: TickerClockSource, updateInterval: Seconds) {
|
|
41
|
+
constructor(callback: () => void, type: TickerClockSource, updateInterval: Seconds, contextSampleRate?: number) {
|
|
37
42
|
|
|
38
43
|
this._callback = callback;
|
|
39
44
|
this._type = type;
|
|
40
|
-
this.
|
|
45
|
+
this._minimumUpdateInterval = Math.max( 128/(contextSampleRate || 44100), .001 );
|
|
46
|
+
this.updateInterval = updateInterval;
|
|
41
47
|
|
|
42
48
|
// create the clock source for the first time
|
|
43
49
|
this._createClock();
|
|
@@ -121,9 +127,9 @@ export class Ticker {
|
|
|
121
127
|
return this._updateInterval;
|
|
122
128
|
}
|
|
123
129
|
set updateInterval(interval: Seconds) {
|
|
124
|
-
this._updateInterval = Math.max(interval,
|
|
130
|
+
this._updateInterval = Math.max(interval, this._minimumUpdateInterval);
|
|
125
131
|
if (this._type === "worker") {
|
|
126
|
-
this._worker
|
|
132
|
+
this._worker?.postMessage(this._updateInterval * 1000);
|
|
127
133
|
}
|
|
128
134
|
}
|
|
129
135
|
|
|
@@ -75,8 +75,10 @@ describe("Context", () => {
|
|
|
75
75
|
clockSource: "timeout",
|
|
76
76
|
latencyHint: "playback",
|
|
77
77
|
lookAhead: 0.2,
|
|
78
|
+
updateInterval: 0.1
|
|
78
79
|
});
|
|
79
80
|
expect(ctx.lookAhead).to.equal(0.2);
|
|
81
|
+
expect(ctx.updateInterval).to.equal(0.1);
|
|
80
82
|
expect(ctx.latencyHint).to.equal("playback");
|
|
81
83
|
expect(ctx.clockSource).to.equal("timeout");
|
|
82
84
|
ctx.dispose();
|
|
@@ -3,7 +3,7 @@ import { Seconds } from "../type/Units";
|
|
|
3
3
|
import { isAudioContext } from "../util/AdvancedTypeCheck";
|
|
4
4
|
import { optionsFromArguments } from "../util/Defaults";
|
|
5
5
|
import { Timeline } from "../util/Timeline";
|
|
6
|
-
import { isDefined
|
|
6
|
+
import { isDefined } from "../util/TypeCheck";
|
|
7
7
|
import {
|
|
8
8
|
AnyAudioContext,
|
|
9
9
|
createAudioContext,
|
|
@@ -39,13 +39,6 @@ export interface ContextTimeoutEvent {
|
|
|
39
39
|
export class Context extends BaseContext {
|
|
40
40
|
readonly name: string = "Context";
|
|
41
41
|
|
|
42
|
-
/**
|
|
43
|
-
* The amount of time into the future events are scheduled. Giving Web Audio
|
|
44
|
-
* a short amount of time into the future to schedule events can reduce clicks and
|
|
45
|
-
* improve performance. This value can be set to 0 to get the lowest latency.
|
|
46
|
-
*/
|
|
47
|
-
lookAhead: Seconds;
|
|
48
|
-
|
|
49
42
|
/**
|
|
50
43
|
* private reference to the BaseAudioContext
|
|
51
44
|
*/
|
|
@@ -116,16 +109,20 @@ export class Context extends BaseContext {
|
|
|
116
109
|
|
|
117
110
|
if (options.context) {
|
|
118
111
|
this._context = options.context;
|
|
112
|
+
// custom context provided, latencyHint unknown (unless explicitly provided in options)
|
|
113
|
+
this._latencyHint = arguments[0]?.latencyHint || "";
|
|
119
114
|
} else {
|
|
120
115
|
this._context = createAudioContext({
|
|
121
116
|
latencyHint: options.latencyHint,
|
|
122
117
|
});
|
|
118
|
+
this._latencyHint = options.latencyHint;
|
|
123
119
|
}
|
|
124
120
|
|
|
125
121
|
this._ticker = new Ticker(
|
|
126
122
|
this.emit.bind(this, "tick"),
|
|
127
123
|
options.clockSource,
|
|
128
|
-
options.updateInterval
|
|
124
|
+
options.updateInterval,
|
|
125
|
+
this._context.sampleRate
|
|
129
126
|
);
|
|
130
127
|
this.on("tick", this._timeoutLoop.bind(this));
|
|
131
128
|
|
|
@@ -133,9 +130,9 @@ export class Context extends BaseContext {
|
|
|
133
130
|
this._context.onstatechange = () => {
|
|
134
131
|
this.emit("statechange", this.state);
|
|
135
132
|
};
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
this
|
|
133
|
+
|
|
134
|
+
// if no custom updateInterval provided, updateInterval will be derived by lookAhead setter
|
|
135
|
+
this[arguments[0]?.hasOwnProperty("updateInterval") ? "_lookAhead" : "lookAhead"] = options.lookAhead;
|
|
139
136
|
}
|
|
140
137
|
|
|
141
138
|
static getDefaults(): ContextOptions {
|
|
@@ -391,8 +388,9 @@ export class Context extends BaseContext {
|
|
|
391
388
|
/**
|
|
392
389
|
* How often the interval callback is invoked.
|
|
393
390
|
* This number corresponds to how responsive the scheduling
|
|
394
|
-
* can be.
|
|
395
|
-
*
|
|
391
|
+
* can be. Setting to 0 will result in the lowest practial interval
|
|
392
|
+
* based on context properties. context.updateInterval + context.lookAhead
|
|
393
|
+
* gives you the total latency between scheduling an event and hearing it.
|
|
396
394
|
*/
|
|
397
395
|
get updateInterval(): Seconds {
|
|
398
396
|
return this._ticker.updateInterval;
|
|
@@ -412,6 +410,22 @@ export class Context extends BaseContext {
|
|
|
412
410
|
this._ticker.type = type;
|
|
413
411
|
}
|
|
414
412
|
|
|
413
|
+
/**
|
|
414
|
+
* The amount of time into the future events are scheduled. Giving Web Audio
|
|
415
|
+
* a short amount of time into the future to schedule events can reduce clicks and
|
|
416
|
+
* improve performance. This value can be set to 0 to get the lowest latency.
|
|
417
|
+
* Adjusting this value also affects the [[updateInterval]].
|
|
418
|
+
*/
|
|
419
|
+
get lookAhead(): Seconds {
|
|
420
|
+
return this._lookAhead;
|
|
421
|
+
}
|
|
422
|
+
set lookAhead(time: Seconds) {
|
|
423
|
+
this._lookAhead = time;
|
|
424
|
+
// if lookAhead is 0, default to .01 updateInterval
|
|
425
|
+
this.updateInterval = time ? (time / 2) : .01;
|
|
426
|
+
}
|
|
427
|
+
private _lookAhead!: Seconds;
|
|
428
|
+
|
|
415
429
|
/**
|
|
416
430
|
* The type of playback, which affects tradeoffs between audio
|
|
417
431
|
* output latency and responsiveness.
|
|
@@ -431,29 +445,6 @@ export class Context extends BaseContext {
|
|
|
431
445
|
return this._latencyHint;
|
|
432
446
|
}
|
|
433
447
|
|
|
434
|
-
/**
|
|
435
|
-
* Update the lookAhead and updateInterval based on the latencyHint
|
|
436
|
-
*/
|
|
437
|
-
private _setLatencyHint(hint: ContextLatencyHint | Seconds): void {
|
|
438
|
-
let lookAheadValue = 0;
|
|
439
|
-
this._latencyHint = hint;
|
|
440
|
-
if (isString(hint)) {
|
|
441
|
-
switch (hint) {
|
|
442
|
-
case "interactive":
|
|
443
|
-
lookAheadValue = 0.1;
|
|
444
|
-
break;
|
|
445
|
-
case "playback":
|
|
446
|
-
lookAheadValue = 0.5;
|
|
447
|
-
break;
|
|
448
|
-
case "balanced":
|
|
449
|
-
lookAheadValue = 0.25;
|
|
450
|
-
break;
|
|
451
|
-
}
|
|
452
|
-
}
|
|
453
|
-
this.lookAhead = lookAheadValue;
|
|
454
|
-
this.updateInterval = lookAheadValue / 2;
|
|
455
|
-
}
|
|
456
|
-
|
|
457
448
|
/**
|
|
458
449
|
* The unwrapped AudioContext or OfflineAudioContext
|
|
459
450
|
*/
|
|
@@ -469,7 +460,7 @@ export class Context extends BaseContext {
|
|
|
469
460
|
* }, 100);
|
|
470
461
|
*/
|
|
471
462
|
now(): Seconds {
|
|
472
|
-
return this._context.currentTime + this.
|
|
463
|
+
return this._context.currentTime + this._lookAhead;
|
|
473
464
|
}
|
|
474
465
|
|
|
475
466
|
/**
|