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.
- package/Tone/core/clock/TickSource.test.ts +52 -0
- package/Tone/core/clock/TickSource.ts +70 -9
- package/Tone/core/type/Frequency.ts +8 -8
- package/Tone/effect/Chorus.test.ts +11 -8
- package/Tone/instrument/NoiseSynth.ts +56 -17
- package/Tone/source/Source.ts +84 -27
- package/Tone/source/buffer/Player.test.ts +98 -49
- package/Tone/source/buffer/Player.ts +45 -21
- package/Tone/version.ts +1 -1
- package/build/Tone.js +1 -1
- package/build/Tone.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/type/Frequency.js +8 -8
- package/build/esm/core/type/Frequency.js.map +1 -1
- package/build/esm/instrument/NoiseSynth.d.ts +10 -0
- package/build/esm/instrument/NoiseSynth.js +14 -2
- package/build/esm/instrument/NoiseSynth.js.map +1 -1
- package/build/esm/source/Source.js +27 -14
- package/build/esm/source/Source.js.map +1 -1
- package/build/esm/source/buffer/Player.d.ts +2 -1
- package/build/esm/source/buffer/Player.js +19 -11
- 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 +26 -26
|
@@ -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
|
}
|
|
@@ -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
|
|
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:
|
|
223
|
-
dbb:
|
|
224
|
-
ebb:
|
|
225
|
-
fbb:
|
|
226
|
-
gbb:
|
|
227
|
-
abb:
|
|
228
|
-
bbb:
|
|
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
|
-
|
|
17
|
-
|
|
18
|
-
|
|
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 {
|
|
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(
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
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(
|
|
51
|
-
|
|
52
|
-
|
|
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(
|
|
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(
|
|
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(
|
|
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
|
-
|
|
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);
|
package/Tone/source/Source.ts
CHANGED
|
@@ -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 {
|
|
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 {
|
|
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<
|
|
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(
|
|
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(
|
|
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 =
|
|
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 (
|
|
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(
|
|
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
|
|
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 (
|
|
209
|
-
this.context.transport.
|
|
210
|
-
this.
|
|
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 =
|
|
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 (
|
|
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(
|
|
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
|
|
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 (
|
|
325
|
+
if (
|
|
326
|
+
stateEvent &&
|
|
327
|
+
stateEvent.state === "started" &&
|
|
328
|
+
stateEvent.time !== offset
|
|
329
|
+
) {
|
|
282
330
|
// get the offset
|
|
283
|
-
const startOffset =
|
|
331
|
+
const startOffset =
|
|
332
|
+
offset - this.toSeconds(stateEvent.time);
|
|
284
333
|
let duration: number | undefined;
|
|
285
334
|
if (stateEvent.duration) {
|
|
286
|
-
duration =
|
|
335
|
+
duration =
|
|
336
|
+
this.toSeconds(stateEvent.duration) -
|
|
337
|
+
startOffset;
|
|
287
338
|
}
|
|
288
|
-
this._start(
|
|
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(
|
|
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
|