tone 14.8.32 → 14.8.36

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.
@@ -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(stopEvent.time, computedTime + this.sampleTime, e => {
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(stopEvent.time, time + this.sampleTime, e => {
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
- // return the ticks
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
  }
@@ -50,7 +50,7 @@ export class FrequencyClass<Type extends number = Hertz> extends TimeClass<Type,
50
50
  },
51
51
  },
52
52
  note: {
53
- regexp: /^([a-g]{1}(?:b|#|x|bb)?)(-?[0-9]+)/i,
53
+ regexp: /^([a-g]{1}(?:b|#|##|x|bb|###|#x|x#|bbb)?)(-?[0-9]+)/i,
54
54
  method(pitch, octave): number {
55
55
  const index = noteToScaleIndex[pitch.toLowerCase()];
56
56
  const noteNumber = index + (parseInt(octave, 10) + 1) * 12;
@@ -219,13 +219,13 @@ export class FrequencyClass<Type extends number = Hertz> extends TimeClass<Type,
219
219
  * @hidden
220
220
  */
221
221
  const noteToScaleIndex = {
222
- cbb: -2, cb: -1, c: 0, "c#": 1, cx: 2,
223
- dbb: 0, db: 1, d: 2, "d#": 3, dx: 4,
224
- ebb: 2, eb: 3, e: 4, "e#": 5, ex: 6,
225
- fbb: 3, fb: 4, f: 5, "f#": 6, fx: 7,
226
- gbb: 5, gb: 6, g: 7, "g#": 8, gx: 9,
227
- abb: 7, ab: 8, a: 9, "a#": 10, ax: 11,
228
- bbb: 9, bb: 10, b: 11, "b#": 12, bx: 13,
222
+ cbbb: -3, cbb: -2, cb: -1, c: 0, "c#": 1, cx: 2, "c##": 2, "c###": 3, "cx#": 3, "c#x": 3,
223
+ dbbb: -1, dbb: 0, db: 1, d: 2, "d#": 3, dx: 4, "d##": 4, "d###": 5, "dx#": 5, "d#x": 5,
224
+ ebbb: 1, ebb: 2, eb: 3, e: 4, "e#": 5, ex: 6, "e##": 6, "e###": 7, "ex#": 7, "e#x": 7,
225
+ fbbb: 2, fbb: 3, fb: 4, f: 5, "f#": 6, fx: 7, "f##": 7, "f###": 8, "fx#": 8, "f#x": 8,
226
+ gbbb: 4, gbb: 5, gb: 6, g: 7, "g#": 8, gx: 9, "g##": 9, "g###": 10, "gx#": 10, "g#x": 10,
227
+ abbb: 6, abb: 7, ab: 8, a: 9, "a#": 10, ax: 11, "a##": 11, "a###": 12, "ax#": 12, "a#x": 12,
228
+ bbbb: 8, bbb: 9, bb: 10, b: 11, "b#": 12, bx: 13, "b##": 13, "b###": 14, "bx#": 14, "b#x": 14,
229
229
  };
230
230
 
231
231
  /**
@@ -7,25 +7,29 @@ import { Oscillator } from "Tone/source";
7
7
  import { Offline } from "test/helper/Offline";
8
8
 
9
9
  describe("Chorus", () => {
10
-
11
10
  BasicTests(Chorus);
12
11
  EffectTests(Chorus);
13
12
 
14
13
  it("matches a file", () => {
15
- return CompareToFile(() => {
16
- const chorus = new Chorus().toDestination().start();
17
- const osc = new Oscillator(220, "sawtooth").connect(chorus).start();
18
- }, "chorus.wav", 0.1);
14
+ return CompareToFile(
15
+ () => {
16
+ const chorus = new Chorus().toDestination().start();
17
+ const osc = new Oscillator(220, "sawtooth")
18
+ .connect(chorus)
19
+ .start();
20
+ },
21
+ "chorus.wav",
22
+ 0.25
23
+ );
19
24
  });
20
25
 
21
26
  context("API", () => {
22
-
23
27
  it("can pass in options in the constructor", () => {
24
28
  const chorus = new Chorus({
25
29
  frequency: 2,
26
30
  delayTime: 1,
27
31
  depth: 0.4,
28
- spread: 90
32
+ spread: 90,
29
33
  });
30
34
  expect(chorus.frequency.value).to.be.closeTo(2, 0.01);
31
35
  expect(chorus.delayTime).to.be.closeTo(1, 0.01);
@@ -84,4 +88,3 @@ describe("Chorus", () => {
84
88
  });
85
89
  });
86
90
  });
87
-
@@ -4,7 +4,10 @@ import { omitFromObject, optionsFromArguments } from "../core/util/Defaults";
4
4
  import { RecursivePartial } from "../core/util/Interface";
5
5
  import { Noise, NoiseOptions } from "../source/Noise";
6
6
  import { Instrument, InstrumentOptions } from "./Instrument";
7
- import { ToneAudioNode, ToneAudioNodeOptions } from "../core/context/ToneAudioNode";
7
+ import {
8
+ ToneAudioNode,
9
+ ToneAudioNodeOptions,
10
+ } from "../core/context/ToneAudioNode";
8
11
  import { Envelope, EnvelopeOptions } from "../component/envelope/Envelope";
9
12
  import { Source } from "../source/Source";
10
13
 
@@ -14,7 +17,7 @@ export interface NoiseSynthOptions extends InstrumentOptions {
14
17
  }
15
18
 
16
19
  /**
17
- * Tone.NoiseSynth is composed of [[Noise]] through an [[AmplitudeEnvelope]].
20
+ * Tone.NoiseSynth is composed of [[Noise]] through an [[AmplitudeEnvelope]].
18
21
  * ```
19
22
  * +-------+ +-------------------+
20
23
  * | Noise +>--> AmplitudeEnvelope +>--> Output
@@ -26,7 +29,6 @@ export interface NoiseSynthOptions extends InstrumentOptions {
26
29
  * @category Instrument
27
30
  */
28
31
  export class NoiseSynth extends Instrument<NoiseSynthOptions> {
29
-
30
32
  readonly name = "NoiseSynth";
31
33
 
32
34
  /**
@@ -39,17 +41,30 @@ export class NoiseSynth extends Instrument<NoiseSynthOptions> {
39
41
  */
40
42
  readonly envelope: AmplitudeEnvelope;
41
43
 
42
- constructor(options?: RecursivePartial<NoiseSynthOptions>)
44
+ constructor(options?: RecursivePartial<NoiseSynthOptions>);
43
45
  constructor() {
44
46
  super(optionsFromArguments(NoiseSynth.getDefaults(), arguments));
45
- const options = optionsFromArguments(NoiseSynth.getDefaults(), arguments);
46
- this.noise = new Noise(Object.assign({
47
- context: this.context,
48
- }, options.noise));
47
+ const options = optionsFromArguments(
48
+ NoiseSynth.getDefaults(),
49
+ arguments
50
+ );
51
+ this.noise = new Noise(
52
+ Object.assign(
53
+ {
54
+ context: this.context,
55
+ },
56
+ options.noise
57
+ )
58
+ );
49
59
 
50
- this.envelope = new AmplitudeEnvelope(Object.assign({
51
- context: this.context,
52
- }, options.envelope));
60
+ this.envelope = new AmplitudeEnvelope(
61
+ Object.assign(
62
+ {
63
+ context: this.context,
64
+ },
65
+ options.envelope
66
+ )
67
+ );
53
68
 
54
69
  // connect the noise to the output
55
70
  this.noise.chain(this.envelope, this.output);
@@ -58,17 +73,23 @@ export class NoiseSynth extends Instrument<NoiseSynthOptions> {
58
73
  static getDefaults(): NoiseSynthOptions {
59
74
  return Object.assign(Instrument.getDefaults(), {
60
75
  envelope: Object.assign(
61
- omitFromObject(Envelope.getDefaults(), Object.keys(ToneAudioNode.getDefaults())),
76
+ omitFromObject(
77
+ Envelope.getDefaults(),
78
+ Object.keys(ToneAudioNode.getDefaults())
79
+ ),
62
80
  {
63
81
  decay: 0.1,
64
82
  sustain: 0.0,
65
- },
83
+ }
66
84
  ),
67
85
  noise: Object.assign(
68
- omitFromObject(Noise.getDefaults(), Object.keys(Source.getDefaults())),
86
+ omitFromObject(
87
+ Noise.getDefaults(),
88
+ Object.keys(Source.getDefaults())
89
+ ),
69
90
  {
70
91
  type: "white",
71
- },
92
+ }
72
93
  ),
73
94
  });
74
95
  }
@@ -87,7 +108,11 @@ export class NoiseSynth extends Instrument<NoiseSynthOptions> {
87
108
  // start the noise
88
109
  this.noise.start(time);
89
110
  if (this.envelope.sustain === 0) {
90
- this.noise.stop(time + this.toSeconds(this.envelope.attack) + this.toSeconds(this.envelope.decay));
111
+ this.noise.stop(
112
+ time +
113
+ this.toSeconds(this.envelope.attack) +
114
+ this.toSeconds(this.envelope.decay)
115
+ );
91
116
  }
92
117
  return this;
93
118
  }
@@ -110,7 +135,21 @@ export class NoiseSynth extends Instrument<NoiseSynthOptions> {
110
135
  return this;
111
136
  }
112
137
 
113
- triggerAttackRelease(duration: Time, time?: Time, velocity: NormalRange = 1): this {
138
+ /**
139
+ * Trigger the attack and then the release after the duration.
140
+ * @param duration The amount of time to hold the note for
141
+ * @param time The time the note should start
142
+ * @param velocity The volume of the note (0-1)
143
+ * @example
144
+ * const noiseSynth = new Tone.NoiseSynth().toDestination();
145
+ * // hold the note for 0.5 seconds
146
+ * noiseSynth.triggerAttackRelease(0.5);
147
+ */
148
+ triggerAttackRelease(
149
+ duration: Time,
150
+ time?: Time,
151
+ velocity: NormalRange = 1
152
+ ): this {
114
153
  time = this.toSeconds(time);
115
154
  duration = this.toSeconds(duration);
116
155
  this.triggerAttack(time, velocity);
@@ -2,11 +2,19 @@ import { Volume } from "../component/channel/Volume";
2
2
  import "../core/context/Destination";
3
3
  import "../core/clock/Transport";
4
4
  import { Param } from "../core/context/Param";
5
- import { OutputNode, ToneAudioNode, ToneAudioNodeOptions } from "../core/context/ToneAudioNode";
5
+ import {
6
+ OutputNode,
7
+ ToneAudioNode,
8
+ ToneAudioNodeOptions,
9
+ } from "../core/context/ToneAudioNode";
6
10
  import { Decibels, Seconds, Time } from "../core/type/Units";
7
11
  import { defaultArg } from "../core/util/Defaults";
8
12
  import { noOp, readOnly } from "../core/util/Interface";
9
- import { BasicPlaybackState, StateTimeline, StateTimelineEvent } from "../core/util/StateTimeline";
13
+ import {
14
+ BasicPlaybackState,
15
+ StateTimeline,
16
+ StateTimelineEvent,
17
+ } from "../core/util/StateTimeline";
10
18
  import { isDefined, isUndef } from "../core/util/TypeCheck";
11
19
  import { assert, assertContextRunning } from "../core/util/Debug";
12
20
  import { GT } from "../core/util/Math";
@@ -20,9 +28,9 @@ export interface SourceOptions extends ToneAudioNodeOptions {
20
28
  }
21
29
 
22
30
  /**
23
- * Base class for sources.
31
+ * Base class for sources.
24
32
  * start/stop of this.context.transport.
25
- *
33
+ *
26
34
  * ```
27
35
  * // Multiple state change events can be chained together,
28
36
  * // but must be set in the correct order and with ascending times
@@ -36,8 +44,9 @@ export interface SourceOptions extends ToneAudioNodeOptions {
36
44
  * state.start("+0.3").stop("+0.2");
37
45
  * ```
38
46
  */
39
- export abstract class Source<Options extends SourceOptions> extends ToneAudioNode<Options> {
40
-
47
+ export abstract class Source<
48
+ Options extends SourceOptions
49
+ > extends ToneAudioNode<Options> {
41
50
  /**
42
51
  * The output volume node
43
52
  */
@@ -129,7 +138,9 @@ export abstract class Source<Options extends SourceOptions> extends ToneAudioNod
129
138
  get state(): BasicPlaybackState {
130
139
  if (this._synced) {
131
140
  if (this.context.transport.state === "started") {
132
- return this._state.getValueAtTime(this.context.transport.seconds) as BasicPlaybackState;
141
+ return this._state.getValueAtTime(
142
+ this.context.transport.seconds
143
+ ) as BasicPlaybackState;
133
144
  } else {
134
145
  return "stopped";
135
146
  }
@@ -155,7 +166,11 @@ export abstract class Source<Options extends SourceOptions> extends ToneAudioNod
155
166
  // overwrite these functions
156
167
  protected abstract _start(time: Time, offset?: Time, duration?: Time): void;
157
168
  protected abstract _stop(time: Time): void;
158
- protected abstract _restart(time: Seconds, offset?: Time, duration?: Time): void;
169
+ protected abstract _restart(
170
+ time: Seconds,
171
+ offset?: Time,
172
+ duration?: Time
173
+ ): void;
159
174
 
160
175
  /**
161
176
  * Ensure that the scheduled time is not before the current time.
@@ -178,12 +193,24 @@ export abstract class Source<Options extends SourceOptions> extends ToneAudioNod
178
193
  * source.start("+0.5"); // starts the source 0.5 seconds from now
179
194
  */
180
195
  start(time?: Time, offset?: Time, duration?: Time): this {
181
- let computedTime = isUndef(time) && this._synced ? this.context.transport.seconds : this.toSeconds(time);
196
+ let computedTime =
197
+ isUndef(time) && this._synced
198
+ ? this.context.transport.seconds
199
+ : this.toSeconds(time);
182
200
  computedTime = this._clampToCurrentTime(computedTime);
183
201
  // if it's started, stop it and restart it
184
- if (!this._synced && this._state.getValueAtTime(computedTime) === "started") {
202
+ if (
203
+ !this._synced &&
204
+ this._state.getValueAtTime(computedTime) === "started"
205
+ ) {
185
206
  // time should be strictly greater than the previous start time
186
- assert(GT(computedTime, (this._state.get(computedTime) as StateTimelineEvent).time), "Start time must be strictly greater than previous start time");
207
+ assert(
208
+ GT(
209
+ computedTime,
210
+ (this._state.get(computedTime) as StateTimelineEvent).time
211
+ ),
212
+ "Start time must be strictly greater than previous start time"
213
+ );
187
214
  this._state.cancel(computedTime);
188
215
  this._state.setStateAtTime("started", computedTime);
189
216
  this.log("restart", computedTime);
@@ -196,18 +223,26 @@ export abstract class Source<Options extends SourceOptions> extends ToneAudioNod
196
223
  const event = this._state.get(computedTime);
197
224
  if (event) {
198
225
  event.offset = this.toSeconds(defaultArg(offset, 0));
199
- event.duration = duration ? this.toSeconds(duration) : undefined;
226
+ event.duration = duration
227
+ ? this.toSeconds(duration)
228
+ : undefined;
200
229
  }
201
- const sched = this.context.transport.schedule(t => {
230
+ const sched = this.context.transport.schedule((t) => {
202
231
  this._start(t, offset, duration);
203
232
  }, computedTime);
204
233
  this._scheduled.push(sched);
205
234
 
206
235
  // if the transport is already started
207
236
  // and the time is greater than where the transport is
208
- if (this.context.transport.state === "started" &&
209
- this.context.transport.getSecondsAtTime(this.immediate()) > computedTime) {
210
- this._syncedStart(this.now(), this.context.transport.seconds);
237
+ if (
238
+ this.context.transport.state === "started" &&
239
+ this.context.transport.getSecondsAtTime(this.immediate()) >
240
+ computedTime
241
+ ) {
242
+ this._syncedStart(
243
+ this.now(),
244
+ this.context.transport.seconds
245
+ );
211
246
  }
212
247
  } else {
213
248
  assertContextRunning(this.context);
@@ -227,14 +262,23 @@ export abstract class Source<Options extends SourceOptions> extends ToneAudioNod
227
262
  * source.stop("+0.5"); // stops the source 0.5 seconds from now
228
263
  */
229
264
  stop(time?: Time): this {
230
- let computedTime = isUndef(time) && this._synced ? this.context.transport.seconds : this.toSeconds(time);
265
+ let computedTime =
266
+ isUndef(time) && this._synced
267
+ ? this.context.transport.seconds
268
+ : this.toSeconds(time);
231
269
  computedTime = this._clampToCurrentTime(computedTime);
232
- if (this._state.getValueAtTime(computedTime) === "started" || isDefined(this._state.getNextState("started", computedTime))) {
270
+ if (
271
+ this._state.getValueAtTime(computedTime) === "started" ||
272
+ isDefined(this._state.getNextState("started", computedTime))
273
+ ) {
233
274
  this.log("stop", computedTime);
234
275
  if (!this._synced) {
235
276
  this._stop(computedTime);
236
277
  } else {
237
- const sched = this.context.transport.schedule(this._stop.bind(this), computedTime);
278
+ const sched = this.context.transport.schedule(
279
+ this._stop.bind(this),
280
+ computedTime
281
+ );
238
282
  this._scheduled.push(sched);
239
283
  }
240
284
  this._state.cancel(computedTime);
@@ -274,23 +318,36 @@ export abstract class Source<Options extends SourceOptions> extends ToneAudioNod
274
318
  if (!this._synced) {
275
319
  this._synced = true;
276
320
  this._syncedStart = (time, offset) => {
277
- if (offset > 0) {
321
+ if (GT(offset, 0)) {
278
322
  // get the playback state at that time
279
323
  const stateEvent = this._state.get(offset);
280
324
  // listen for start events which may occur in the middle of the sync'ed time
281
- if (stateEvent && stateEvent.state === "started" && stateEvent.time !== offset) {
325
+ if (
326
+ stateEvent &&
327
+ stateEvent.state === "started" &&
328
+ stateEvent.time !== offset
329
+ ) {
282
330
  // get the offset
283
- const startOffset = offset - this.toSeconds(stateEvent.time);
331
+ const startOffset =
332
+ offset - this.toSeconds(stateEvent.time);
284
333
  let duration: number | undefined;
285
334
  if (stateEvent.duration) {
286
- duration = this.toSeconds(stateEvent.duration) - startOffset;
335
+ duration =
336
+ this.toSeconds(stateEvent.duration) -
337
+ startOffset;
287
338
  }
288
- this._start(time, this.toSeconds(stateEvent.offset) + startOffset, duration);
339
+ this._start(
340
+ time,
341
+ this.toSeconds(stateEvent.offset) + startOffset,
342
+ duration
343
+ );
289
344
  }
290
345
  }
291
346
  };
292
- this._syncedStop = time => {
293
- const seconds = this.context.transport.getSecondsAtTime(Math.max(time - this.sampleTime, 0));
347
+ this._syncedStop = (time) => {
348
+ const seconds = this.context.transport.getSecondsAtTime(
349
+ Math.max(time - this.sampleTime, 0)
350
+ );
294
351
  if (this._state.getValueAtTime(seconds) === "started") {
295
352
  this._stop(time);
296
353
  }
@@ -317,7 +374,7 @@ export abstract class Source<Options extends SourceOptions> extends ToneAudioNod
317
374
  }
318
375
  this._synced = false;
319
376
  // clear all of the scheduled ids
320
- this._scheduled.forEach(id => this.context.transport.clear(id));
377
+ this._scheduled.forEach((id) => this.context.transport.clear(id));
321
378
  this._scheduled = [];
322
379
  this._state.cancel(0);
323
380
  // stop it also