tone 14.8.36 → 14.8.39
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/component/filter/FeedbackCombFilter.test.ts +16 -0
- package/Tone/core/clock/Ticker.ts +11 -5
- package/Tone/core/context/BaseContext.ts +1 -2
- package/Tone/core/context/Context.test.ts +2 -0
- package/Tone/core/context/Context.ts +35 -50
- package/Tone/core/context/DummyContext.test.ts +1 -1
- package/Tone/core/context/DummyContext.ts +1 -1
- package/Tone/core/context/ToneAudioBuffer.ts +52 -27
- package/Tone/core/worklet/ToneAudioWorklet.ts +1 -1
- package/Tone/effect/BitCrusher.test.ts +16 -0
- 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/event/Sequence.ts +1 -1
- 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/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/BaseContext.d.ts +1 -1
- package/build/esm/core/context/BaseContext.js.map +1 -1
- package/build/esm/core/context/Context.d.ts +14 -15
- package/build/esm/core/context/Context.js +32 -38
- package/build/esm/core/context/Context.js.map +1 -1
- package/build/esm/core/context/DummyContext.d.ts +1 -1
- package/build/esm/core/context/DummyContext.js +1 -1
- package/build/esm/core/context/DummyContext.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/core/worklet/ToneAudioWorklet.js +1 -1
- package/build/esm/core/worklet/ToneAudioWorklet.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/event/Sequence.d.ts +1 -1
- package/build/esm/event/Sequence.js +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];
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { expect } from "chai";
|
|
2
2
|
import { FeedbackCombFilter } from "./FeedbackCombFilter";
|
|
3
|
+
import { BitCrusher } from "Tone/effect/BitCrusher";
|
|
3
4
|
import { BasicTests } from "test/helper/Basic";
|
|
4
5
|
import { PassAudio } from "test/helper/PassAudio";
|
|
5
6
|
import { Offline } from "test/helper/Offline";
|
|
@@ -76,5 +77,20 @@ describe("FeedbackCombFilter", () => {
|
|
|
76
77
|
});
|
|
77
78
|
});
|
|
78
79
|
});
|
|
80
|
+
|
|
81
|
+
it("should be usable with the BitCrusher", (done) => {
|
|
82
|
+
new FeedbackCombFilter();
|
|
83
|
+
new BitCrusher(4);
|
|
84
|
+
|
|
85
|
+
const handle = setTimeout(() => {
|
|
86
|
+
window.onunhandledrejection = null;
|
|
87
|
+
done();
|
|
88
|
+
}, 100);
|
|
89
|
+
|
|
90
|
+
window.onunhandledrejection = (event) => {
|
|
91
|
+
done(event.reason);
|
|
92
|
+
clearTimeout(handle);
|
|
93
|
+
};
|
|
94
|
+
});
|
|
79
95
|
});
|
|
80
96
|
|
|
@@ -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 {
|
|
@@ -343,7 +340,7 @@ export class Context extends BaseContext {
|
|
|
343
340
|
/**
|
|
344
341
|
* Maps a module name to promise of the addModule method
|
|
345
342
|
*/
|
|
346
|
-
private
|
|
343
|
+
private _workletPromise: null | Promise<void> = null;
|
|
347
344
|
|
|
348
345
|
/**
|
|
349
346
|
* Create an audio worklet node from a name and options. The module
|
|
@@ -359,29 +356,23 @@ export class Context extends BaseContext {
|
|
|
359
356
|
/**
|
|
360
357
|
* Add an AudioWorkletProcessor module
|
|
361
358
|
* @param url The url of the module
|
|
362
|
-
* @param name The name of the module
|
|
363
359
|
*/
|
|
364
|
-
async addAudioWorkletModule(url: string
|
|
360
|
+
async addAudioWorkletModule(url: string): Promise<void> {
|
|
365
361
|
assert(
|
|
366
362
|
isDefined(this.rawContext.audioWorklet),
|
|
367
363
|
"AudioWorkletNode is only available in a secure context (https or localhost)"
|
|
368
364
|
);
|
|
369
|
-
if (!this.
|
|
370
|
-
this.
|
|
371
|
-
name,
|
|
372
|
-
this.rawContext.audioWorklet.addModule(url)
|
|
373
|
-
);
|
|
365
|
+
if (!this._workletPromise) {
|
|
366
|
+
this._workletPromise = this.rawContext.audioWorklet.addModule(url);
|
|
374
367
|
}
|
|
375
|
-
await this.
|
|
368
|
+
await this._workletPromise;
|
|
376
369
|
}
|
|
377
370
|
|
|
378
371
|
/**
|
|
379
372
|
* Returns a promise which resolves when all of the worklets have been loaded on this context
|
|
380
373
|
*/
|
|
381
374
|
protected async workletsAreReady(): Promise<void> {
|
|
382
|
-
|
|
383
|
-
this._workletModules.forEach((promise) => promises.push(promise));
|
|
384
|
-
await Promise.all(promises);
|
|
375
|
+
await this._workletPromise ? this._workletPromise : Promise.resolve();
|
|
385
376
|
}
|
|
386
377
|
|
|
387
378
|
//---------------------------
|
|
@@ -391,8 +382,9 @@ export class Context extends BaseContext {
|
|
|
391
382
|
/**
|
|
392
383
|
* How often the interval callback is invoked.
|
|
393
384
|
* This number corresponds to how responsive the scheduling
|
|
394
|
-
* can be.
|
|
395
|
-
*
|
|
385
|
+
* can be. Setting to 0 will result in the lowest practial interval
|
|
386
|
+
* based on context properties. context.updateInterval + context.lookAhead
|
|
387
|
+
* gives you the total latency between scheduling an event and hearing it.
|
|
396
388
|
*/
|
|
397
389
|
get updateInterval(): Seconds {
|
|
398
390
|
return this._ticker.updateInterval;
|
|
@@ -412,6 +404,22 @@ export class Context extends BaseContext {
|
|
|
412
404
|
this._ticker.type = type;
|
|
413
405
|
}
|
|
414
406
|
|
|
407
|
+
/**
|
|
408
|
+
* The amount of time into the future events are scheduled. Giving Web Audio
|
|
409
|
+
* a short amount of time into the future to schedule events can reduce clicks and
|
|
410
|
+
* improve performance. This value can be set to 0 to get the lowest latency.
|
|
411
|
+
* Adjusting this value also affects the [[updateInterval]].
|
|
412
|
+
*/
|
|
413
|
+
get lookAhead(): Seconds {
|
|
414
|
+
return this._lookAhead;
|
|
415
|
+
}
|
|
416
|
+
set lookAhead(time: Seconds) {
|
|
417
|
+
this._lookAhead = time;
|
|
418
|
+
// if lookAhead is 0, default to .01 updateInterval
|
|
419
|
+
this.updateInterval = time ? (time / 2) : .01;
|
|
420
|
+
}
|
|
421
|
+
private _lookAhead!: Seconds;
|
|
422
|
+
|
|
415
423
|
/**
|
|
416
424
|
* The type of playback, which affects tradeoffs between audio
|
|
417
425
|
* output latency and responsiveness.
|
|
@@ -431,29 +439,6 @@ export class Context extends BaseContext {
|
|
|
431
439
|
return this._latencyHint;
|
|
432
440
|
}
|
|
433
441
|
|
|
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
442
|
/**
|
|
458
443
|
* The unwrapped AudioContext or OfflineAudioContext
|
|
459
444
|
*/
|
|
@@ -469,7 +454,7 @@ export class Context extends BaseContext {
|
|
|
469
454
|
* }, 100);
|
|
470
455
|
*/
|
|
471
456
|
now(): Seconds {
|
|
472
|
-
return this._context.currentTime + this.
|
|
457
|
+
return this._context.currentTime + this._lookAhead;
|
|
473
458
|
}
|
|
474
459
|
|
|
475
460
|
/**
|
|
@@ -25,7 +25,7 @@ describe("DummyContext", () => {
|
|
|
25
25
|
context.decodeAudioData(new Float32Array(100));
|
|
26
26
|
context.createAudioWorkletNode("test.js");
|
|
27
27
|
context.rawContext;
|
|
28
|
-
context.addAudioWorkletModule("test.js"
|
|
28
|
+
context.addAudioWorkletModule("test.js");
|
|
29
29
|
context.resume();
|
|
30
30
|
context.setTimeout(() => {}, 1);
|
|
31
31
|
context.clearTimeout(1);
|
|
@@ -127,7 +127,7 @@ export class DummyContext extends BaseContext {
|
|
|
127
127
|
return {} as AnyAudioContext;
|
|
128
128
|
}
|
|
129
129
|
|
|
130
|
-
async addAudioWorkletModule(_url: string
|
|
130
|
+
async addAudioWorkletModule(_url: string): Promise<void> {
|
|
131
131
|
return Promise.resolve();
|
|
132
132
|
}
|
|
133
133
|
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { getContext } from "../Global";
|
|
2
2
|
import { Tone } from "../Tone";
|
|
3
3
|
import { Samples, Seconds } from "../type/Units";
|
|
4
|
-
import { isAudioBuffer } from "../util/AdvancedTypeCheck";
|
|
5
4
|
import { optionsFromArguments } from "../util/Defaults";
|
|
6
5
|
import { noOp } from "../util/Interface";
|
|
7
6
|
import { isArray, isNumber, isString } from "../util/TypeCheck";
|
|
@@ -25,7 +24,6 @@ interface ToneAudioBufferOptions {
|
|
|
25
24
|
* @category Core
|
|
26
25
|
*/
|
|
27
26
|
export class ToneAudioBuffer extends Tone {
|
|
28
|
-
|
|
29
27
|
readonly name: string = "ToneAudioBuffer";
|
|
30
28
|
|
|
31
29
|
/**
|
|
@@ -54,23 +52,26 @@ export class ToneAudioBuffer extends Tone {
|
|
|
54
52
|
constructor(
|
|
55
53
|
url?: string | ToneAudioBuffer | AudioBuffer,
|
|
56
54
|
onload?: (buffer: ToneAudioBuffer) => void,
|
|
57
|
-
onerror?: (error: Error) => void
|
|
55
|
+
onerror?: (error: Error) => void
|
|
58
56
|
);
|
|
59
57
|
constructor(options?: Partial<ToneAudioBufferOptions>);
|
|
60
58
|
constructor() {
|
|
61
|
-
|
|
62
59
|
super();
|
|
63
60
|
|
|
64
|
-
const options = optionsFromArguments(
|
|
61
|
+
const options = optionsFromArguments(
|
|
62
|
+
ToneAudioBuffer.getDefaults(),
|
|
63
|
+
arguments,
|
|
64
|
+
["url", "onload", "onerror"]
|
|
65
|
+
);
|
|
65
66
|
|
|
66
67
|
this.reverse = options.reverse;
|
|
67
68
|
this.onload = options.onload;
|
|
68
69
|
|
|
69
|
-
if (
|
|
70
|
-
this.set(options.url);
|
|
71
|
-
} else if (isString(options.url)) {
|
|
70
|
+
if (isString(options.url)) {
|
|
72
71
|
// initiate the download
|
|
73
72
|
this.load(options.url).catch(options.onerror);
|
|
73
|
+
} else if (options.url) {
|
|
74
|
+
this.set(options.url);
|
|
74
75
|
}
|
|
75
76
|
}
|
|
76
77
|
|
|
@@ -132,11 +133,13 @@ export class ToneAudioBuffer extends Tone {
|
|
|
132
133
|
* @returns A Promise which resolves with this ToneAudioBuffer
|
|
133
134
|
*/
|
|
134
135
|
async load(url: string): Promise<this> {
|
|
135
|
-
const doneLoading: Promise<void> = ToneAudioBuffer.load(url).then(
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
136
|
+
const doneLoading: Promise<void> = ToneAudioBuffer.load(url).then(
|
|
137
|
+
(audioBuffer) => {
|
|
138
|
+
this.set(audioBuffer);
|
|
139
|
+
// invoke the onload method
|
|
140
|
+
this.onload(this);
|
|
141
|
+
}
|
|
142
|
+
);
|
|
140
143
|
ToneAudioBuffer.downloads.push(doneLoading);
|
|
141
144
|
try {
|
|
142
145
|
await doneLoading;
|
|
@@ -165,11 +168,15 @@ export class ToneAudioBuffer extends Tone {
|
|
|
165
168
|
fromArray(array: Float32Array | Float32Array[]): this {
|
|
166
169
|
const isMultidimensional = isArray(array) && array[0].length > 0;
|
|
167
170
|
const channels = isMultidimensional ? array.length : 1;
|
|
168
|
-
const len = isMultidimensional
|
|
171
|
+
const len = isMultidimensional
|
|
172
|
+
? (array[0] as Float32Array).length
|
|
173
|
+
: array.length;
|
|
169
174
|
const context = getContext();
|
|
170
175
|
const buffer = context.createBuffer(channels, len, context.sampleRate);
|
|
171
|
-
const multiChannelArray: Float32Array[] =
|
|
172
|
-
|
|
176
|
+
const multiChannelArray: Float32Array[] =
|
|
177
|
+
!isMultidimensional && channels === 1
|
|
178
|
+
? [array as Float32Array]
|
|
179
|
+
: (array as Float32Array[]);
|
|
173
180
|
|
|
174
181
|
for (let c = 0; c < channels; c++) {
|
|
175
182
|
buffer.copyToChannel(multiChannelArray[c], c);
|
|
@@ -195,7 +202,7 @@ export class ToneAudioBuffer extends Tone {
|
|
|
195
202
|
}
|
|
196
203
|
}
|
|
197
204
|
// divide by the number of channels
|
|
198
|
-
outputArray = outputArray.map(sample => sample / numChannels);
|
|
205
|
+
outputArray = outputArray.map((sample) => sample / numChannels);
|
|
199
206
|
this.fromArray(outputArray);
|
|
200
207
|
}
|
|
201
208
|
return this;
|
|
@@ -243,11 +250,21 @@ export class ToneAudioBuffer extends Tone {
|
|
|
243
250
|
assert(this.loaded, "Buffer is not loaded");
|
|
244
251
|
const startSamples = Math.floor(start * this.sampleRate);
|
|
245
252
|
const endSamples = Math.floor(end * this.sampleRate);
|
|
246
|
-
assert(
|
|
253
|
+
assert(
|
|
254
|
+
startSamples < endSamples,
|
|
255
|
+
"The start time must be less than the end time"
|
|
256
|
+
);
|
|
247
257
|
const length = endSamples - startSamples;
|
|
248
|
-
const retBuffer = getContext().createBuffer(
|
|
258
|
+
const retBuffer = getContext().createBuffer(
|
|
259
|
+
this.numberOfChannels,
|
|
260
|
+
length,
|
|
261
|
+
this.sampleRate
|
|
262
|
+
);
|
|
249
263
|
for (let channel = 0; channel < this.numberOfChannels; channel++) {
|
|
250
|
-
retBuffer.copyToChannel(
|
|
264
|
+
retBuffer.copyToChannel(
|
|
265
|
+
this.getChannelData(channel).subarray(startSamples, endSamples),
|
|
266
|
+
channel
|
|
267
|
+
);
|
|
251
268
|
}
|
|
252
269
|
return new ToneAudioBuffer(retBuffer);
|
|
253
270
|
}
|
|
@@ -333,7 +350,7 @@ export class ToneAudioBuffer extends Tone {
|
|
|
333
350
|
* @return A ToneAudioBuffer created from the array
|
|
334
351
|
*/
|
|
335
352
|
static fromArray(array: Float32Array | Float32Array[]): ToneAudioBuffer {
|
|
336
|
-
return
|
|
353
|
+
return new ToneAudioBuffer().fromArray(array);
|
|
337
354
|
}
|
|
338
355
|
|
|
339
356
|
/**
|
|
@@ -355,7 +372,6 @@ export class ToneAudioBuffer extends Tone {
|
|
|
355
372
|
* Loads a url using fetch and returns the AudioBuffer.
|
|
356
373
|
*/
|
|
357
374
|
static async load(url: string): Promise<AudioBuffer> {
|
|
358
|
-
|
|
359
375
|
// test if the url contains multiple extensions
|
|
360
376
|
const matches = url.match(/\[([^\]\[]+\|.+)\]$/);
|
|
361
377
|
if (matches) {
|
|
@@ -371,12 +387,19 @@ export class ToneAudioBuffer extends Tone {
|
|
|
371
387
|
}
|
|
372
388
|
|
|
373
389
|
// make sure there is a slash between the baseUrl and the url
|
|
374
|
-
const baseUrl =
|
|
375
|
-
|
|
390
|
+
const baseUrl =
|
|
391
|
+
ToneAudioBuffer.baseUrl === "" ||
|
|
392
|
+
ToneAudioBuffer.baseUrl.endsWith("/")
|
|
393
|
+
? ToneAudioBuffer.baseUrl
|
|
394
|
+
: ToneAudioBuffer.baseUrl + "/";
|
|
395
|
+
|
|
376
396
|
// encode special characters in file path
|
|
377
397
|
const location = document.createElement("a");
|
|
378
|
-
location.href =
|
|
379
|
-
location.pathname = (location.pathname + location.hash)
|
|
398
|
+
location.href = baseUrl + url;
|
|
399
|
+
location.pathname = (location.pathname + location.hash)
|
|
400
|
+
.split("/")
|
|
401
|
+
.map(encodeURIComponent)
|
|
402
|
+
.join("/");
|
|
380
403
|
|
|
381
404
|
const response = await fetch(location.href);
|
|
382
405
|
if (!response.ok) {
|
|
@@ -401,7 +424,9 @@ export class ToneAudioBuffer extends Tone {
|
|
|
401
424
|
static supportsType(url: string): boolean {
|
|
402
425
|
const extensions = url.split(".");
|
|
403
426
|
const extension = extensions[extensions.length - 1];
|
|
404
|
-
const response = document
|
|
427
|
+
const response = document
|
|
428
|
+
.createElement("audio")
|
|
429
|
+
.canPlayType("audio/" + extension);
|
|
405
430
|
return response !== "";
|
|
406
431
|
}
|
|
407
432
|
|
|
@@ -53,7 +53,7 @@ export abstract class ToneAudioWorklet<Options extends ToneAudioWorkletOptions>
|
|
|
53
53
|
this._dummyParam = this._dummyGain.gain;
|
|
54
54
|
|
|
55
55
|
// Register the processor
|
|
56
|
-
this.context.addAudioWorkletModule(blobUrl
|
|
56
|
+
this.context.addAudioWorkletModule(blobUrl).then(() => {
|
|
57
57
|
// create the worklet when it's read
|
|
58
58
|
if (!this.disposed) {
|
|
59
59
|
this._worklet = this.context.createAudioWorkletNode(name, this.workletOptions);
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { BitCrusher } from "./BitCrusher";
|
|
2
|
+
import { FeedbackCombFilter } from "Tone/component/filter/FeedbackCombFilter";
|
|
2
3
|
import { Oscillator } from "Tone/source/oscillator/Oscillator";
|
|
3
4
|
import { BasicTests } from "test/helper/Basic";
|
|
4
5
|
import { EffectTests } from "test/helper/EffectTests";
|
|
@@ -38,5 +39,20 @@ describe("BitCrusher", () => {
|
|
|
38
39
|
crusher.dispose();
|
|
39
40
|
});
|
|
40
41
|
});
|
|
42
|
+
|
|
43
|
+
it("should be usable with the FeedbackCombFilter", (done) => {
|
|
44
|
+
new BitCrusher(4);
|
|
45
|
+
new FeedbackCombFilter();
|
|
46
|
+
|
|
47
|
+
const handle = setTimeout(() => {
|
|
48
|
+
window.onunhandledrejection = null;
|
|
49
|
+
done();
|
|
50
|
+
}, 100);
|
|
51
|
+
|
|
52
|
+
window.onunhandledrejection = (event) => {
|
|
53
|
+
done(event.reason);
|
|
54
|
+
clearTimeout(handle);
|
|
55
|
+
};
|
|
56
|
+
});
|
|
41
57
|
});
|
|
42
58
|
|
|
@@ -89,19 +89,21 @@ describe("Pattern", () => {
|
|
|
89
89
|
it("is invoked after it's started", () => {
|
|
90
90
|
let invoked = false;
|
|
91
91
|
return Offline(({ transport }) => {
|
|
92
|
+
const values = ["a", "b", "c"];
|
|
92
93
|
let index = 0;
|
|
93
94
|
const pattern = new Pattern((() => {
|
|
94
95
|
invoked = true;
|
|
95
|
-
expect(pattern.value).to.equal(index);
|
|
96
|
+
expect(pattern.value).to.equal(values[index]);
|
|
97
|
+
expect(pattern.index).to.equal(index);
|
|
96
98
|
index++;
|
|
97
|
-
}),
|
|
99
|
+
}), values).start(0);
|
|
98
100
|
transport.start();
|
|
99
101
|
}, 0.2).then(() => {
|
|
100
102
|
expect(invoked).to.be.true;
|
|
101
103
|
});
|
|
102
104
|
});
|
|
103
105
|
|
|
104
|
-
it("passes in the scheduled time and pattern
|
|
106
|
+
it("passes in the scheduled time and pattern note to the callback", () => {
|
|
105
107
|
let invoked = false;
|
|
106
108
|
return Offline(({ transport }) => {
|
|
107
109
|
const startTime = 0.05;
|
|
@@ -109,6 +111,8 @@ describe("Pattern", () => {
|
|
|
109
111
|
expect(time).to.be.a("number");
|
|
110
112
|
expect(time - startTime).to.be.closeTo(0.3, 0.01);
|
|
111
113
|
expect(note).to.be.equal("a");
|
|
114
|
+
expect(pattern.value).to.equal("a");
|
|
115
|
+
expect(pattern.index).to.be.equal(0);
|
|
112
116
|
invoked = true;
|
|
113
117
|
}), ["a"], "up");
|
|
114
118
|
transport.start(startTime);
|
|
@@ -121,16 +125,38 @@ describe("Pattern", () => {
|
|
|
121
125
|
it("passes in the next note of the pattern", () => {
|
|
122
126
|
let counter = 0;
|
|
123
127
|
return Offline(({ transport }) => {
|
|
128
|
+
const values = ["a", "b", "c"];
|
|
124
129
|
const pattern = new Pattern(((time, note) => {
|
|
125
|
-
expect(note).to.equal(counter % 3);
|
|
130
|
+
expect(note).to.equal(values[counter % 3]);
|
|
131
|
+
expect(pattern.value).to.equal(values[counter % 3]);
|
|
132
|
+
expect(pattern.index).to.be.equal(counter % 3);
|
|
126
133
|
counter++;
|
|
127
|
-
}),
|
|
134
|
+
}), values, "up").start(0);
|
|
128
135
|
pattern.interval = "16n";
|
|
129
136
|
transport.start(0);
|
|
130
137
|
}, 0.7).then(() => {
|
|
131
138
|
expect(counter).to.equal(6);
|
|
132
139
|
});
|
|
133
140
|
});
|
|
141
|
+
|
|
142
|
+
it("can modify the pattern type and values", () => {
|
|
143
|
+
let counter = 0;
|
|
144
|
+
return Offline(({ transport }) => {
|
|
145
|
+
const values = ["a", "b", "c"];
|
|
146
|
+
const pattern = new Pattern(((time, note) => {
|
|
147
|
+
expect(note).to.equal(values[counter % 3]);
|
|
148
|
+
expect(pattern.value).to.equal(values[counter % 3]);
|
|
149
|
+
expect(pattern.index).to.be.equal(counter % 3);
|
|
150
|
+
counter++;
|
|
151
|
+
}), ["a"], "down").start(0);
|
|
152
|
+
pattern.interval = "16n";
|
|
153
|
+
pattern.pattern = "up";
|
|
154
|
+
pattern.values = values;
|
|
155
|
+
transport.start(0);
|
|
156
|
+
}, 0.7).then(() => {
|
|
157
|
+
expect(counter).to.equal(6);
|
|
158
|
+
});
|
|
159
|
+
});
|
|
134
160
|
});
|
|
135
161
|
|
|
136
162
|
});
|