tone 15.2.6 → 15.2.8
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/instrument/Sampler.test.ts +93 -0
- package/Tone/instrument/Sampler.ts +131 -3
- package/Tone/version.ts +1 -1
- package/build/Tone.js +1 -1
- package/build/Tone.js.map +1 -1
- package/build/esm/instrument/Sampler.d.ts +56 -0
- package/build/esm/instrument/Sampler.js +107 -3
- package/build/esm/instrument/Sampler.js.map +1 -1
- package/build/esm/version.js +1 -1
- package/package.json +11 -13
|
@@ -5,6 +5,7 @@ import { CompareToFile } from "../../test/helper/CompareToFile.js";
|
|
|
5
5
|
import { InstrumentTest } from "../../test/helper/InstrumentTests.js";
|
|
6
6
|
import { atTime, Offline } from "../../test/helper/Offline.js";
|
|
7
7
|
import { ToneAudioBuffer } from "../core/context/ToneAudioBuffer.js";
|
|
8
|
+
import { getContext } from "../core/Global.js";
|
|
8
9
|
import { Sampler } from "./Sampler.js";
|
|
9
10
|
|
|
10
11
|
describe("Sampler", () => {
|
|
@@ -56,6 +57,7 @@ describe("Sampler", () => {
|
|
|
56
57
|
{
|
|
57
58
|
attack: 0.2,
|
|
58
59
|
release: 0.3,
|
|
60
|
+
loop: true
|
|
59
61
|
}
|
|
60
62
|
);
|
|
61
63
|
expect(sampler.attack).to.equal(0.2);
|
|
@@ -267,6 +269,97 @@ describe("Sampler", () => {
|
|
|
267
269
|
});
|
|
268
270
|
});
|
|
269
271
|
|
|
272
|
+
context("Looping", () => {
|
|
273
|
+
|
|
274
|
+
it("can be set to loop", () => {
|
|
275
|
+
const sampler = new Sampler();
|
|
276
|
+
sampler.loop = true;
|
|
277
|
+
expect(sampler.loop).to.be.true;
|
|
278
|
+
sampler.dispose();
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
it("can set the loop points", () => {
|
|
282
|
+
const sampler = new Sampler();
|
|
283
|
+
sampler.loopStart = 0.2;
|
|
284
|
+
expect(sampler.loopStart).to.equal(0.2);
|
|
285
|
+
sampler.loopEnd = 0.7;
|
|
286
|
+
expect(sampler.loopEnd).to.equal(0.7);
|
|
287
|
+
sampler.setLoopPoints(0, 0.5);
|
|
288
|
+
expect(sampler.loopStart).to.equal(0);
|
|
289
|
+
expect(sampler.loopEnd).to.equal(0.5);
|
|
290
|
+
sampler.dispose();
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
it("loops the audio", async () => {
|
|
294
|
+
const buff = await Offline(() => {
|
|
295
|
+
const sampler = new Sampler({
|
|
296
|
+
urls: {
|
|
297
|
+
A4: A4_buffer
|
|
298
|
+
}
|
|
299
|
+
});
|
|
300
|
+
sampler.loop = true;
|
|
301
|
+
sampler.toDestination();
|
|
302
|
+
sampler.triggerAttack("A4");
|
|
303
|
+
}, A4_buffer.duration * 1.5);
|
|
304
|
+
expect(buff.getRmsAtTime(0)).to.be.above(0);
|
|
305
|
+
expect(buff.getRmsAtTime(A4_buffer.duration * 0.5)).to.be.above(0);
|
|
306
|
+
expect(buff.getRmsAtTime(A4_buffer.duration)).to.be.above(0);
|
|
307
|
+
expect(buff.getRmsAtTime(A4_buffer.duration * 1.2)).to.be.above(0);
|
|
308
|
+
});
|
|
309
|
+
|
|
310
|
+
it("setting the loop multiple times has no affect", async () => {
|
|
311
|
+
const buff = await Offline(() => {
|
|
312
|
+
const sampler = new Sampler({
|
|
313
|
+
urls: {
|
|
314
|
+
A4: A4_buffer
|
|
315
|
+
}
|
|
316
|
+
});
|
|
317
|
+
sampler.loop = true;
|
|
318
|
+
sampler.loop = true;
|
|
319
|
+
sampler.toDestination();
|
|
320
|
+
sampler.triggerAttack("A4");
|
|
321
|
+
}, A4_buffer.duration * 1.5);
|
|
322
|
+
expect(buff.getRmsAtTime(0)).to.be.above(0);
|
|
323
|
+
expect(buff.getRmsAtTime(A4_buffer.duration * 0.5)).to.be.above(0);
|
|
324
|
+
expect(buff.getRmsAtTime(A4_buffer.duration)).to.be.above(0);
|
|
325
|
+
expect(buff.getRmsAtTime(A4_buffer.duration * 1.2)).to.be.above(0);
|
|
326
|
+
});
|
|
327
|
+
|
|
328
|
+
it("loops the audio when loop is set after start", async () => {
|
|
329
|
+
const buff = await Offline(() => {
|
|
330
|
+
const sampler = new Sampler({
|
|
331
|
+
urls: {
|
|
332
|
+
A4: A4_buffer
|
|
333
|
+
}
|
|
334
|
+
});
|
|
335
|
+
sampler.toDestination();
|
|
336
|
+
sampler.triggerAttack("A4");
|
|
337
|
+
sampler.loop = true;
|
|
338
|
+
}, A4_buffer.duration * 1.5);
|
|
339
|
+
expect(buff.getRmsAtTime(0)).to.be.above(0);
|
|
340
|
+
expect(buff.getRmsAtTime(A4_buffer.duration * 0.5)).to.be.above(0);
|
|
341
|
+
expect(buff.getRmsAtTime(A4_buffer.duration)).to.be.above(0);
|
|
342
|
+
expect(buff.getRmsAtTime(A4_buffer.duration * 1.2)).to.be.above(0);
|
|
343
|
+
});
|
|
344
|
+
|
|
345
|
+
it("starts buffers at loopStart when set to loop", async () => {
|
|
346
|
+
const testSample =
|
|
347
|
+
A4_buffer.toArray(0)[Math.floor(0.1 * getContext().sampleRate)];
|
|
348
|
+
const buff = await Offline(() => {
|
|
349
|
+
const sampler = new Sampler({
|
|
350
|
+
urls: {
|
|
351
|
+
A4: A4_buffer
|
|
352
|
+
}
|
|
353
|
+
});
|
|
354
|
+
sampler.loopStart = 0.1;
|
|
355
|
+
sampler.loop = true;
|
|
356
|
+
sampler.toDestination();
|
|
357
|
+
sampler.triggerAttack("A4");
|
|
358
|
+
}, 0.05);
|
|
359
|
+
expect(buff.toArray()[0][0]).to.equal(testSample);
|
|
360
|
+
});
|
|
361
|
+
});
|
|
362
|
+
|
|
270
363
|
context("add samples", () => {
|
|
271
364
|
it("can add a note with its midi value", async () => {
|
|
272
365
|
const buffer = await Offline(() => {
|
|
@@ -10,9 +10,9 @@ import {
|
|
|
10
10
|
Note,
|
|
11
11
|
Time,
|
|
12
12
|
} from "../core/type/Units.js";
|
|
13
|
-
import { assert } from "../core/util/Debug.js";
|
|
13
|
+
import { assert, assertRange } from "../core/util/Debug.js";
|
|
14
14
|
import { timeRange } from "../core/util/Decorator.js";
|
|
15
|
-
import { optionsFromArguments } from "../core/util/Defaults.js";
|
|
15
|
+
import { defaultArg, optionsFromArguments } from "../core/util/Defaults.js";
|
|
16
16
|
import { noOp } from "../core/util/Interface.js";
|
|
17
17
|
import { isArray, isNote, isNumber } from "../core/util/TypeCheck.js";
|
|
18
18
|
import { Instrument, InstrumentOptions } from "../instrument/Instrument.js";
|
|
@@ -34,6 +34,9 @@ export interface SamplerOptions extends InstrumentOptions {
|
|
|
34
34
|
baseUrl: string;
|
|
35
35
|
curve: ToneBufferSourceCurve;
|
|
36
36
|
urls: SamplesMap;
|
|
37
|
+
loop: boolean;
|
|
38
|
+
loopEnd: number;
|
|
39
|
+
loopStart: number;
|
|
37
40
|
}
|
|
38
41
|
|
|
39
42
|
/**
|
|
@@ -70,6 +73,26 @@ export class Sampler extends Instrument<SamplerOptions> {
|
|
|
70
73
|
*/
|
|
71
74
|
private _activeSources: Map<MidiNote, ToneBufferSource[]> = new Map();
|
|
72
75
|
|
|
76
|
+
/**
|
|
77
|
+
* The list of all provided midi notes
|
|
78
|
+
*/
|
|
79
|
+
private _providedMidiNotes: MidiNote[] = [];
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* if the buffer should loop once its over
|
|
83
|
+
*/
|
|
84
|
+
private _loop: boolean;
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* if 'loop' is true, the loop will start at this position
|
|
88
|
+
*/
|
|
89
|
+
private _loopStart: Time;
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* if 'loop' is true, the loop will end at this position
|
|
93
|
+
*/
|
|
94
|
+
private _loopEnd: Time;
|
|
95
|
+
|
|
73
96
|
/**
|
|
74
97
|
* The envelope applied to the beginning of the sample.
|
|
75
98
|
* @min 0
|
|
@@ -144,6 +167,9 @@ export class Sampler extends Instrument<SamplerOptions> {
|
|
|
144
167
|
this.attack = options.attack;
|
|
145
168
|
this.release = options.release;
|
|
146
169
|
this.curve = options.curve;
|
|
170
|
+
this._loop = options.loop;
|
|
171
|
+
this._loopStart = options.loopStart;
|
|
172
|
+
this._loopEnd = options.loopEnd;
|
|
147
173
|
|
|
148
174
|
// invoke the callback if it's already loaded
|
|
149
175
|
if (this._buffers.loaded) {
|
|
@@ -161,6 +187,9 @@ export class Sampler extends Instrument<SamplerOptions> {
|
|
|
161
187
|
onerror: noOp,
|
|
162
188
|
release: 0.1,
|
|
163
189
|
urls: {},
|
|
190
|
+
loop: false,
|
|
191
|
+
loopEnd: 0,
|
|
192
|
+
loopStart: 0,
|
|
164
193
|
});
|
|
165
194
|
}
|
|
166
195
|
|
|
@@ -197,6 +226,7 @@ export class Sampler extends Instrument<SamplerOptions> {
|
|
|
197
226
|
if (!Array.isArray(notes)) {
|
|
198
227
|
notes = [notes];
|
|
199
228
|
}
|
|
229
|
+
const offset = defaultArg(this._loopStart, 0);
|
|
200
230
|
notes.forEach((note) => {
|
|
201
231
|
const midiFloat = ftomf(
|
|
202
232
|
new FrequencyClass(this.context, note).toFrequency()
|
|
@@ -210,6 +240,9 @@ export class Sampler extends Instrument<SamplerOptions> {
|
|
|
210
240
|
const playbackRate = intervalToFrequencyRatio(
|
|
211
241
|
difference + remainder
|
|
212
242
|
);
|
|
243
|
+
const duration = this._loop
|
|
244
|
+
? undefined
|
|
245
|
+
: buffer.duration / playbackRate;
|
|
213
246
|
// play that note
|
|
214
247
|
const source = new ToneBufferSource({
|
|
215
248
|
url: buffer,
|
|
@@ -217,9 +250,12 @@ export class Sampler extends Instrument<SamplerOptions> {
|
|
|
217
250
|
curve: this.curve,
|
|
218
251
|
fadeIn: this.attack,
|
|
219
252
|
fadeOut: this.release,
|
|
253
|
+
loop: this._loop,
|
|
254
|
+
loopStart: this._loopStart,
|
|
255
|
+
loopEnd: this._loopEnd,
|
|
220
256
|
playbackRate,
|
|
221
257
|
}).connect(this.output);
|
|
222
|
-
source.start(time,
|
|
258
|
+
source.start(time, offset, duration, velocity);
|
|
223
259
|
// add it to the active sources
|
|
224
260
|
if (!isArray(this._activeSources.get(midi))) {
|
|
225
261
|
this._activeSources.set(midi, []);
|
|
@@ -357,6 +393,98 @@ export class Sampler extends Instrument<SamplerOptions> {
|
|
|
357
393
|
return this._buffers.loaded;
|
|
358
394
|
}
|
|
359
395
|
|
|
396
|
+
/**
|
|
397
|
+
* Set the loop start and end. Will only loop if loop is set to true.
|
|
398
|
+
* @param loopStart The loop start time
|
|
399
|
+
* @param loopEnd The loop end time
|
|
400
|
+
* @example
|
|
401
|
+
* const sampler = new Tone.Sampler({
|
|
402
|
+
* urls: {
|
|
403
|
+
* A1: "https://tonejs.github.io/audio/berklee/guitar_chord4.mp3",
|
|
404
|
+
* },
|
|
405
|
+
* }).toDestination();
|
|
406
|
+
* // loop between the given points
|
|
407
|
+
* sampler.setLoopPoints(0.2, 0.3);
|
|
408
|
+
* sampler.loop = true;
|
|
409
|
+
*/
|
|
410
|
+
setLoopPoints(loopStart: Time, loopEnd: Time): this {
|
|
411
|
+
this.loopStart = loopStart;
|
|
412
|
+
this.loopEnd = loopEnd;
|
|
413
|
+
return this;
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
/**
|
|
417
|
+
* If loop is true, the loop will start at this position.
|
|
418
|
+
*/
|
|
419
|
+
get loopStart(): Time {
|
|
420
|
+
return this._loopStart;
|
|
421
|
+
}
|
|
422
|
+
set loopStart(loopStart) {
|
|
423
|
+
this._loopStart = loopStart;
|
|
424
|
+
this._providedMidiNotes.forEach((midiNote) => {
|
|
425
|
+
const buffer = this._buffers.get(midiNote);
|
|
426
|
+
if (buffer.loaded) {
|
|
427
|
+
assertRange(this.toSeconds(loopStart), 0, buffer.duration);
|
|
428
|
+
}
|
|
429
|
+
});
|
|
430
|
+
// get the current sources
|
|
431
|
+
this._activeSources.forEach((sourceList) => {
|
|
432
|
+
sourceList.forEach((source) => {
|
|
433
|
+
source.loopStart = loopStart;
|
|
434
|
+
});
|
|
435
|
+
});
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
/**
|
|
439
|
+
* If loop is true, the loop will end at this position.
|
|
440
|
+
*/
|
|
441
|
+
get loopEnd(): Time {
|
|
442
|
+
return this._loopEnd;
|
|
443
|
+
}
|
|
444
|
+
set loopEnd(loopEnd) {
|
|
445
|
+
this._loopEnd = loopEnd;
|
|
446
|
+
this._providedMidiNotes.forEach((midiNote) => {
|
|
447
|
+
const buffer = this._buffers.get(midiNote);
|
|
448
|
+
if (buffer.loaded) {
|
|
449
|
+
assertRange(this.toSeconds(loopEnd), 0, buffer.duration);
|
|
450
|
+
}
|
|
451
|
+
});
|
|
452
|
+
// get the current sources
|
|
453
|
+
this._activeSources.forEach((sourceList) => {
|
|
454
|
+
sourceList.forEach((source) => {
|
|
455
|
+
source.loopEnd = loopEnd;
|
|
456
|
+
});
|
|
457
|
+
});
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
|
|
461
|
+
/**
|
|
462
|
+
* If the buffers should loop once they are over.
|
|
463
|
+
* @example
|
|
464
|
+
* const sampler = new Tone.Sampler({
|
|
465
|
+
* urls: {
|
|
466
|
+
* A4: "https://tonejs.github.io/audio/berklee/femalevoice_aa_A4.mp3",
|
|
467
|
+
* },
|
|
468
|
+
* }).toDestination();
|
|
469
|
+
* sampler.loop = true;
|
|
470
|
+
*/
|
|
471
|
+
get loop(): boolean {
|
|
472
|
+
return this._loop;
|
|
473
|
+
}
|
|
474
|
+
set loop(loop) {
|
|
475
|
+
// if no change, do nothing
|
|
476
|
+
if (this._loop === loop) {
|
|
477
|
+
return;
|
|
478
|
+
}
|
|
479
|
+
this._loop = loop;
|
|
480
|
+
// set the loop of all of the sources
|
|
481
|
+
this._activeSources.forEach((sourceList) => {
|
|
482
|
+
sourceList.forEach((source) => {
|
|
483
|
+
source.loop = loop;
|
|
484
|
+
});
|
|
485
|
+
});
|
|
486
|
+
}
|
|
487
|
+
|
|
360
488
|
/**
|
|
361
489
|
* Clean up
|
|
362
490
|
*/
|
package/Tone/version.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const version: string = "15.2.
|
|
1
|
+
export const version: string = "15.2.8";
|