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.
Files changed (62) hide show
  1. package/Tone/component/analysis/Meter.test.ts +25 -0
  2. package/Tone/component/analysis/Meter.ts +7 -5
  3. package/Tone/component/filter/FeedbackCombFilter.test.ts +16 -0
  4. package/Tone/core/clock/Ticker.ts +11 -5
  5. package/Tone/core/context/BaseContext.ts +1 -2
  6. package/Tone/core/context/Context.test.ts +2 -0
  7. package/Tone/core/context/Context.ts +35 -50
  8. package/Tone/core/context/DummyContext.test.ts +1 -1
  9. package/Tone/core/context/DummyContext.ts +1 -1
  10. package/Tone/core/context/ToneAudioBuffer.ts +52 -27
  11. package/Tone/core/worklet/ToneAudioWorklet.ts +1 -1
  12. package/Tone/effect/BitCrusher.test.ts +16 -0
  13. package/Tone/event/Pattern.test.ts +31 -5
  14. package/Tone/event/Pattern.ts +19 -6
  15. package/Tone/event/PatternGenerator.test.ts +18 -26
  16. package/Tone/event/PatternGenerator.ts +52 -59
  17. package/Tone/event/Sequence.ts +1 -1
  18. package/Tone/instrument/MetalSynth.ts +48 -21
  19. package/Tone/signal/ToneConstantSource.test.ts +1 -1
  20. package/Tone/source/OneShotSource.ts +1 -1
  21. package/Tone/source/buffer/Player.test.ts +25 -0
  22. package/Tone/source/buffer/Player.ts +1 -1
  23. package/Tone/source/oscillator/ToneOscillatorNode.test.ts +1 -1
  24. package/Tone/version.ts +1 -1
  25. package/build/Tone.js +1 -1
  26. package/build/Tone.js.map +1 -1
  27. package/build/esm/component/analysis/Meter.d.ts +1 -1
  28. package/build/esm/component/analysis/Meter.js +5 -7
  29. package/build/esm/component/analysis/Meter.js.map +1 -1
  30. package/build/esm/core/clock/Ticker.d.ts +5 -1
  31. package/build/esm/core/clock/Ticker.js +6 -4
  32. package/build/esm/core/clock/Ticker.js.map +1 -1
  33. package/build/esm/core/context/BaseContext.d.ts +1 -1
  34. package/build/esm/core/context/BaseContext.js.map +1 -1
  35. package/build/esm/core/context/Context.d.ts +14 -15
  36. package/build/esm/core/context/Context.js +32 -38
  37. package/build/esm/core/context/Context.js.map +1 -1
  38. package/build/esm/core/context/DummyContext.d.ts +1 -1
  39. package/build/esm/core/context/DummyContext.js +1 -1
  40. package/build/esm/core/context/DummyContext.js.map +1 -1
  41. package/build/esm/core/context/ToneAudioBuffer.js +25 -15
  42. package/build/esm/core/context/ToneAudioBuffer.js.map +1 -1
  43. package/build/esm/core/worklet/ToneAudioWorklet.js +1 -1
  44. package/build/esm/core/worklet/ToneAudioWorklet.js.map +1 -1
  45. package/build/esm/event/Pattern.d.ts +8 -0
  46. package/build/esm/event/Pattern.js +11 -4
  47. package/build/esm/event/Pattern.js.map +1 -1
  48. package/build/esm/event/PatternGenerator.d.ts +4 -4
  49. package/build/esm/event/PatternGenerator.js +52 -58
  50. package/build/esm/event/PatternGenerator.js.map +1 -1
  51. package/build/esm/event/Sequence.d.ts +1 -1
  52. package/build/esm/event/Sequence.js +1 -1
  53. package/build/esm/instrument/MetalSynth.d.ts +0 -1
  54. package/build/esm/instrument/MetalSynth.js +15 -13
  55. package/build/esm/instrument/MetalSynth.js.map +1 -1
  56. package/build/esm/source/OneShotSource.js +1 -1
  57. package/build/esm/source/OneShotSource.js.map +1 -1
  58. package/build/esm/source/buffer/Player.js +2 -1
  59. package/build/esm/source/buffer/Player.js.map +1 -1
  60. package/build/esm/version.js +1 -1
  61. package/docs/tone.json +1 -1
  62. 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 = 0;
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 * this.smoothing);
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: Seconds;
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._updateInterval = updateInterval;
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, 128 / 44100);
130
+ this._updateInterval = Math.max(interval, this._minimumUpdateInterval);
125
131
  if (this._type === "worker") {
126
- this._worker.postMessage(Math.max(interval * 1000, 1));
132
+ this._worker?.postMessage(this._updateInterval * 1000);
127
133
  }
128
134
  }
129
135
 
@@ -105,8 +105,7 @@ export abstract class BaseContext
105
105
  abstract get rawContext(): AnyAudioContext;
106
106
 
107
107
  abstract addAudioWorkletModule(
108
- _url: string,
109
- _name: string
108
+ _url: string
110
109
  ): Promise<void>;
111
110
 
112
111
  abstract lookAhead: number;
@@ -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, isString } from "../util/TypeCheck";
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
- this._setLatencyHint(options.latencyHint);
138
- this.lookAhead = options.lookAhead;
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 _workletModules: Map<string, Promise<void>> = new Map();
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, name: string): Promise<void> {
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._workletModules.has(name)) {
370
- this._workletModules.set(
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._workletModules.get(name);
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
- const promises: Promise<void>[] = [];
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. context.updateInterval + context.lookAhead gives you the
395
- * total latency between scheduling an event and hearing it.
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.lookAhead;
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", "test");
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, _name: string): Promise<void> {
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(ToneAudioBuffer.getDefaults(), arguments, ["url", "onload", "onerror"]);
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 (options.url && isAudioBuffer(options.url) || options.url instanceof ToneAudioBuffer) {
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(audioBuffer => {
136
- this.set(audioBuffer);
137
- // invoke the onload method
138
- this.onload(this);
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 ? (array[0] as Float32Array).length : array.length;
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[] = !isMultidimensional && channels === 1 ?
172
- [array as Float32Array] : array as Float32Array[];
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(startSamples < endSamples, "The start time must be less than the end time");
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(this.numberOfChannels, length, this.sampleRate);
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(this.getChannelData(channel).subarray(startSamples, endSamples), channel);
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 (new ToneAudioBuffer()).fromArray(array);
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 = ToneAudioBuffer.baseUrl === "" || ToneAudioBuffer.baseUrl.endsWith("/") ? ToneAudioBuffer.baseUrl : ToneAudioBuffer.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 = (baseUrl + url);
379
- location.pathname = (location.pathname + location.hash).split("/").map(encodeURIComponent).join("/");
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.createElement("audio").canPlayType("audio/" + extension);
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, name).then(() => {
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
- }), [0, 1, 2]).start(0);
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 index to the callback", () => {
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
- }), [0, 1, 2], "up").start(0);
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
  });