tone 15.2.13 → 15.3.0
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/source/buffer/Player.test.ts +236 -0
- package/Tone/source/buffer/Player.ts +100 -1
- package/Tone/version.ts +1 -1
- package/build/Tone.js +1 -1
- package/build/Tone.js.map +1 -1
- package/build/esm/source/buffer/Player.d.ts +28 -0
- package/build/esm/source/buffer/Player.js +86 -1
- package/build/esm/source/buffer/Player.js.map +1 -1
- package/build/esm/version.js +1 -1
- package/build/esm/version.js.map +1 -1
- package/package.json +1 -7
|
@@ -427,6 +427,31 @@ describe("Player", () => {
|
|
|
427
427
|
expect(buff.isSilent()).to.be.true;
|
|
428
428
|
});
|
|
429
429
|
|
|
430
|
+
it("seeking updates stopped state", async () => {
|
|
431
|
+
await Offline(() => {
|
|
432
|
+
const player = new Player(buffer).toDestination();
|
|
433
|
+
player.start(0);
|
|
434
|
+
player.seek(buffer.duration * 0.75, 0.5);
|
|
435
|
+
|
|
436
|
+
return (time) => {
|
|
437
|
+
whenBetween(time, 0, 0.5, () => {
|
|
438
|
+
expect(player.state).to.equal("started");
|
|
439
|
+
});
|
|
440
|
+
whenBetween(time, 0.5, 0.5 + buffer.duration * 0.25, () => {
|
|
441
|
+
expect(player.state).to.equal("started");
|
|
442
|
+
});
|
|
443
|
+
whenBetween(
|
|
444
|
+
time,
|
|
445
|
+
0.5 + buffer.duration * 0.25,
|
|
446
|
+
Infinity,
|
|
447
|
+
() => {
|
|
448
|
+
expect(player.state).to.equal("stopped");
|
|
449
|
+
}
|
|
450
|
+
);
|
|
451
|
+
};
|
|
452
|
+
}, buffer.duration);
|
|
453
|
+
});
|
|
454
|
+
|
|
430
455
|
it("can seek to a position at the given time", async () => {
|
|
431
456
|
const buff = await Offline(() => {
|
|
432
457
|
const ramp = new Float32Array(
|
|
@@ -708,4 +733,215 @@ describe("Player", () => {
|
|
|
708
733
|
});
|
|
709
734
|
});
|
|
710
735
|
});
|
|
736
|
+
|
|
737
|
+
context("progress", () => {
|
|
738
|
+
it("can get the progress of the player", async () => {
|
|
739
|
+
await Offline(() => {
|
|
740
|
+
const player = new Player(buffer);
|
|
741
|
+
player.start(0);
|
|
742
|
+
return (time) => {
|
|
743
|
+
whenBetween(time, 0, buffer.duration, () => {
|
|
744
|
+
expect(player.progress).to.be.closeTo(time, 0.01);
|
|
745
|
+
});
|
|
746
|
+
whenBetween(time, buffer.duration, Infinity, () => {
|
|
747
|
+
expect(player.progress).to.equal(0);
|
|
748
|
+
});
|
|
749
|
+
};
|
|
750
|
+
}, buffer.duration * 1.1);
|
|
751
|
+
});
|
|
752
|
+
|
|
753
|
+
it("progress goes back to 0 when the player loops", async () => {
|
|
754
|
+
await Offline(() => {
|
|
755
|
+
const player = new Player(buffer);
|
|
756
|
+
player.loop = true;
|
|
757
|
+
player.start(0);
|
|
758
|
+
return (time) => {
|
|
759
|
+
whenBetween(time, 0, buffer.duration, () => {
|
|
760
|
+
expect(player.progress).to.be.closeTo(time, 0.01);
|
|
761
|
+
});
|
|
762
|
+
whenBetween(
|
|
763
|
+
time,
|
|
764
|
+
buffer.duration,
|
|
765
|
+
buffer.duration * 2,
|
|
766
|
+
() => {
|
|
767
|
+
expect(player.progress).to.be.closeTo(
|
|
768
|
+
time - buffer.duration,
|
|
769
|
+
0.01
|
|
770
|
+
);
|
|
771
|
+
}
|
|
772
|
+
);
|
|
773
|
+
};
|
|
774
|
+
}, buffer.duration * 2);
|
|
775
|
+
});
|
|
776
|
+
|
|
777
|
+
it("loops between loopStart and loopEnd", async () => {
|
|
778
|
+
await Offline(() => {
|
|
779
|
+
const player = new Player(buffer);
|
|
780
|
+
player.loop = true;
|
|
781
|
+
player.loopStart = 0.1;
|
|
782
|
+
player.loopEnd = 0.9;
|
|
783
|
+
player.start(0);
|
|
784
|
+
return (time) => {
|
|
785
|
+
whenBetween(time, 0, 0.8, () => {
|
|
786
|
+
expect(player.progress).to.be.closeTo(time + 0.1, 0.01);
|
|
787
|
+
});
|
|
788
|
+
whenBetween(time, 0.8, 1.6, () => {
|
|
789
|
+
expect(player.progress).to.be.closeTo(time - 0.7, 0.01);
|
|
790
|
+
});
|
|
791
|
+
whenBetween(time, 1.6, 2.4, () => {
|
|
792
|
+
expect(player.progress).to.be.closeTo(time - 1.5, 0.01);
|
|
793
|
+
});
|
|
794
|
+
};
|
|
795
|
+
}, 2.4);
|
|
796
|
+
});
|
|
797
|
+
|
|
798
|
+
it("progress updates at the rate of the playbackRate", async () => {
|
|
799
|
+
await Offline(() => {
|
|
800
|
+
const player = new Player(buffer);
|
|
801
|
+
player.playbackRate = 2;
|
|
802
|
+
player.start(0);
|
|
803
|
+
return (time) => {
|
|
804
|
+
whenBetween(time, 0, buffer.duration / 2, () => {
|
|
805
|
+
expect(player.progress).to.be.closeTo(time * 2, 0.01);
|
|
806
|
+
});
|
|
807
|
+
whenBetween(time, buffer.duration / 2, Infinity, () => {
|
|
808
|
+
expect(player.progress).to.be.equal(0);
|
|
809
|
+
});
|
|
810
|
+
};
|
|
811
|
+
}, buffer.duration);
|
|
812
|
+
});
|
|
813
|
+
|
|
814
|
+
it("playbackRate can be changed after start", async () => {
|
|
815
|
+
let playbackRateChanged = false;
|
|
816
|
+
await Offline(() => {
|
|
817
|
+
const player = new Player(buffer);
|
|
818
|
+
player.start(0);
|
|
819
|
+
return (time) => {
|
|
820
|
+
whenBetween(time, 0, buffer.duration * 0.5, () => {
|
|
821
|
+
expect(player.progress).to.be.closeTo(time, 0.01);
|
|
822
|
+
});
|
|
823
|
+
if (!playbackRateChanged && time > buffer.duration * 0.5) {
|
|
824
|
+
playbackRateChanged = true;
|
|
825
|
+
player.playbackRate = 2;
|
|
826
|
+
}
|
|
827
|
+
// after the playbackRate is changed, the progress should move half as fast
|
|
828
|
+
whenBetween(
|
|
829
|
+
time,
|
|
830
|
+
buffer.duration * 0.5,
|
|
831
|
+
buffer.duration * 0.75,
|
|
832
|
+
() => {
|
|
833
|
+
const timeAfterHalf = time - buffer.duration * 0.5;
|
|
834
|
+
expect(player.progress).to.be.closeTo(
|
|
835
|
+
buffer.duration * 0.5 + timeAfterHalf * 2,
|
|
836
|
+
0.01
|
|
837
|
+
);
|
|
838
|
+
}
|
|
839
|
+
);
|
|
840
|
+
|
|
841
|
+
whenBetween(time, buffer.duration * 0.75, Infinity, () => {
|
|
842
|
+
expect(player.progress).to.be.equal(0);
|
|
843
|
+
});
|
|
844
|
+
};
|
|
845
|
+
}, buffer.duration);
|
|
846
|
+
});
|
|
847
|
+
|
|
848
|
+
it("can start at an offset", async () => {
|
|
849
|
+
await Offline(() => {
|
|
850
|
+
const player = new Player(buffer);
|
|
851
|
+
player.start(0, buffer.duration / 2);
|
|
852
|
+
return (time) => {
|
|
853
|
+
whenBetween(time, 0.01, buffer.duration / 2, () => {
|
|
854
|
+
expect(player.progress).to.be.closeTo(
|
|
855
|
+
time + buffer.duration / 2,
|
|
856
|
+
0.01
|
|
857
|
+
);
|
|
858
|
+
});
|
|
859
|
+
whenBetween(time, buffer.duration / 2, Infinity, () => {
|
|
860
|
+
expect(player.progress).to.be.equal(0);
|
|
861
|
+
});
|
|
862
|
+
};
|
|
863
|
+
}, buffer.duration);
|
|
864
|
+
});
|
|
865
|
+
|
|
866
|
+
it("can seek to a new position", async () => {
|
|
867
|
+
await Offline(() => {
|
|
868
|
+
const player = new Player(buffer);
|
|
869
|
+
player.start(0);
|
|
870
|
+
player.seek(0, buffer.duration / 2);
|
|
871
|
+
return (time) => {
|
|
872
|
+
whenBetween(time, 0, buffer.duration * 0.5, () => {
|
|
873
|
+
expect(player.progress).to.be.closeTo(time, 0.01);
|
|
874
|
+
});
|
|
875
|
+
whenBetween(
|
|
876
|
+
time,
|
|
877
|
+
buffer.duration * 0.5,
|
|
878
|
+
buffer.duration * 1.5,
|
|
879
|
+
() => {
|
|
880
|
+
expect(player.progress).to.be.closeTo(
|
|
881
|
+
time - buffer.duration * 0.5,
|
|
882
|
+
0.01
|
|
883
|
+
);
|
|
884
|
+
}
|
|
885
|
+
);
|
|
886
|
+
whenBetween(time, buffer.duration * 1.5, Infinity, () => {
|
|
887
|
+
expect(player.progress).to.be.equal(0);
|
|
888
|
+
});
|
|
889
|
+
};
|
|
890
|
+
}, buffer.duration * 2);
|
|
891
|
+
});
|
|
892
|
+
|
|
893
|
+
it("can start and stop multiple times", async () => {
|
|
894
|
+
await Offline(() => {
|
|
895
|
+
const player = new Player(buffer);
|
|
896
|
+
player.start(0);
|
|
897
|
+
player.stop(0.1);
|
|
898
|
+
player.start(0.2, buffer.duration / 2);
|
|
899
|
+
player.stop(0.3);
|
|
900
|
+
return (time) => {
|
|
901
|
+
whenBetween(time, 0, 0.1, () => {
|
|
902
|
+
expect(player.progress).to.be.closeTo(time, 0.01);
|
|
903
|
+
});
|
|
904
|
+
whenBetween(time, 0.1, 0.2, () => {
|
|
905
|
+
expect(player.progress).to.equal(0);
|
|
906
|
+
});
|
|
907
|
+
whenBetween(time, 0.2, 0.3, () => {
|
|
908
|
+
expect(player.progress).to.be.closeTo(
|
|
909
|
+
time - 0.2 + buffer.duration / 2,
|
|
910
|
+
0.01
|
|
911
|
+
);
|
|
912
|
+
});
|
|
913
|
+
whenBetween(time, 0.3, 0.4, () => {
|
|
914
|
+
expect(player.progress).to.equal(0);
|
|
915
|
+
});
|
|
916
|
+
};
|
|
917
|
+
}, 0.4);
|
|
918
|
+
});
|
|
919
|
+
|
|
920
|
+
it("can seek multiple times", async () => {
|
|
921
|
+
await Offline(() => {
|
|
922
|
+
const player = new Player(buffer);
|
|
923
|
+
player.start(0);
|
|
924
|
+
player.seek(1, 0.5);
|
|
925
|
+
player.seek(0, 1);
|
|
926
|
+
player.seek(1.5, 1.5);
|
|
927
|
+
return (time) => {
|
|
928
|
+
whenBetween(time, 0, 0.5, () => {
|
|
929
|
+
expect(player.progress).to.be.closeTo(time, 0.01);
|
|
930
|
+
});
|
|
931
|
+
whenBetween(time, 0.5, 1, () => {
|
|
932
|
+
expect(player.progress).to.be.closeTo(time + 0.5, 0.01);
|
|
933
|
+
});
|
|
934
|
+
whenBetween(time, 1, 1.5, () => {
|
|
935
|
+
expect(player.progress).to.be.closeTo(time - 1, 0.01);
|
|
936
|
+
});
|
|
937
|
+
whenBetween(time, 1.5, buffer.duration, () => {
|
|
938
|
+
expect(player.progress).to.be.closeTo(time, 0.01);
|
|
939
|
+
});
|
|
940
|
+
whenBetween(time, buffer.duration, Infinity, () => {
|
|
941
|
+
expect(player.progress).to.equal(0);
|
|
942
|
+
});
|
|
943
|
+
};
|
|
944
|
+
}, 3);
|
|
945
|
+
});
|
|
946
|
+
});
|
|
711
947
|
});
|
|
@@ -1,10 +1,13 @@
|
|
|
1
|
+
import { TickParam } from "../../core/clock/TickParam.js";
|
|
1
2
|
import { ToneAudioBuffer } from "../../core/context/ToneAudioBuffer.js";
|
|
2
3
|
import { Positive, Seconds, Time } from "../../core/type/Units.js";
|
|
3
4
|
import { assertRange } from "../../core/util/Debug.js";
|
|
4
5
|
import { timeRange } from "../../core/util/Decorator.js";
|
|
5
6
|
import { defaultArg, optionsFromArguments } from "../../core/util/Defaults.js";
|
|
6
7
|
import { noOp } from "../../core/util/Interface.js";
|
|
8
|
+
import { Timeline } from "../../core/util/Timeline.js";
|
|
7
9
|
import { isUndef } from "../../core/util/TypeCheck.js";
|
|
10
|
+
import { ToneConstantSource } from "../../signal/ToneConstantSource.js";
|
|
8
11
|
import { Source, SourceOptions } from "../Source.js";
|
|
9
12
|
import { ToneBufferSource } from "./ToneBufferSource.js";
|
|
10
13
|
|
|
@@ -69,6 +72,33 @@ export class Player extends Source<PlayerOptions> {
|
|
|
69
72
|
*/
|
|
70
73
|
private _activeSources: Set<ToneBufferSource> = new Set();
|
|
71
74
|
|
|
75
|
+
/**
|
|
76
|
+
* Used as the source of the TickParam, but not started or used for anything else.
|
|
77
|
+
*/
|
|
78
|
+
private _constantSource = new ToneConstantSource({
|
|
79
|
+
context: this.context,
|
|
80
|
+
units: "hertz",
|
|
81
|
+
offset: 0,
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Used to track the progress of the player.
|
|
86
|
+
*/
|
|
87
|
+
private _progressTracker = new TickParam({
|
|
88
|
+
context: this.context,
|
|
89
|
+
units: "hertz",
|
|
90
|
+
value: 0,
|
|
91
|
+
param: this._constantSource.offset,
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Combined with the _progressTracker param to track the progress of the player in seconds.
|
|
96
|
+
*/
|
|
97
|
+
private _progressOffset = new Timeline<{
|
|
98
|
+
time: Seconds;
|
|
99
|
+
seek: Seconds;
|
|
100
|
+
}>(Infinity);
|
|
101
|
+
|
|
72
102
|
/**
|
|
73
103
|
* The fadeIn time of the amplitude envelope.
|
|
74
104
|
*/
|
|
@@ -142,6 +172,52 @@ export class Player extends Source<PlayerOptions> {
|
|
|
142
172
|
return this;
|
|
143
173
|
}
|
|
144
174
|
|
|
175
|
+
/**
|
|
176
|
+
* Internal method to get the progress at a specific time.
|
|
177
|
+
* @param time The time to evaluate the progress at.
|
|
178
|
+
*/
|
|
179
|
+
private _getProgressAtTime(time: Seconds): Seconds {
|
|
180
|
+
const state = this._state.getValueAtTime(time);
|
|
181
|
+
if (state === "stopped") {
|
|
182
|
+
return 0;
|
|
183
|
+
}
|
|
184
|
+
const startTime = this._state.getLastState("started", time)!;
|
|
185
|
+
|
|
186
|
+
// sum all of the offsets between the start time and the time
|
|
187
|
+
let seeksSinceStart = 0;
|
|
188
|
+
this._progressOffset.forEachBetween(startTime.time, time, (event) => {
|
|
189
|
+
seeksSinceStart += event.seek;
|
|
190
|
+
});
|
|
191
|
+
const progress =
|
|
192
|
+
this._progressTracker.getTicksAtTime(time) + seeksSinceStart;
|
|
193
|
+
if (this._loop) {
|
|
194
|
+
const loopEnd =
|
|
195
|
+
this.loopEnd === 0
|
|
196
|
+
? this.buffer.duration
|
|
197
|
+
: this.toSeconds(this.loopEnd);
|
|
198
|
+
const loopStart = this.toSeconds(this.loopStart);
|
|
199
|
+
const duration = loopEnd - loopStart;
|
|
200
|
+
return (progress % duration) + loopStart;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
return progress;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* Displays the elapsed seconds since the player was started, taking into account playbackRate changes.
|
|
208
|
+
* @example
|
|
209
|
+
* const player = new Tone.Player("https://tonejs.github.io/audio/berklee/gong_1.mp3", () => {
|
|
210
|
+
* player.start();
|
|
211
|
+
* setInterval(() => {
|
|
212
|
+
* console.log(player.progress);
|
|
213
|
+
* }, 100);
|
|
214
|
+
* }).toDestination();
|
|
215
|
+
*/
|
|
216
|
+
get progress(): Seconds {
|
|
217
|
+
const now = this.now();
|
|
218
|
+
return this._getProgressAtTime(now);
|
|
219
|
+
}
|
|
220
|
+
|
|
145
221
|
/**
|
|
146
222
|
* Internal callback when the buffer is loaded.
|
|
147
223
|
*/
|
|
@@ -228,7 +304,7 @@ export class Player extends Source<PlayerOptions> {
|
|
|
228
304
|
playbackRate: this._playbackRate,
|
|
229
305
|
}).connect(this.output);
|
|
230
306
|
|
|
231
|
-
//
|
|
307
|
+
// schedule the "stopped" state
|
|
232
308
|
if (!this._loop && !this._synced) {
|
|
233
309
|
// cancel the previous stop
|
|
234
310
|
this._state.cancel(startTime + computedDuration);
|
|
@@ -245,6 +321,14 @@ export class Player extends Source<PlayerOptions> {
|
|
|
245
321
|
// add it to the array of active sources
|
|
246
322
|
this._activeSources.add(source);
|
|
247
323
|
|
|
324
|
+
// used to track the progress of the player
|
|
325
|
+
const seekDelta = computedOffset - this._getProgressAtTime(startTime);
|
|
326
|
+
this._progressOffset.add({
|
|
327
|
+
time: startTime,
|
|
328
|
+
seek: seekDelta,
|
|
329
|
+
});
|
|
330
|
+
this._progressTracker.setValueAtTime(this._playbackRate, startTime);
|
|
331
|
+
|
|
248
332
|
// start it
|
|
249
333
|
if (this._loop && isUndef(origDuration)) {
|
|
250
334
|
source.start(startTime, computedOffset);
|
|
@@ -264,6 +348,7 @@ export class Player extends Source<PlayerOptions> {
|
|
|
264
348
|
protected _stop(time?: Time): void {
|
|
265
349
|
const computedTime = this.toSeconds(time);
|
|
266
350
|
this._activeSources.forEach((source) => source.stop(computedTime));
|
|
351
|
+
this._progressTracker.setValueAtTime(0, computedTime);
|
|
267
352
|
}
|
|
268
353
|
|
|
269
354
|
/**
|
|
@@ -301,6 +386,8 @@ export class Player extends Source<PlayerOptions> {
|
|
|
301
386
|
const computedOffset = this.toSeconds(offset);
|
|
302
387
|
// if it's currently playing, stop it
|
|
303
388
|
this._stop(computedTime);
|
|
389
|
+
// remove the stop event
|
|
390
|
+
this._state.cancel(computedTime);
|
|
304
391
|
// restart it at the given time
|
|
305
392
|
this._start(computedTime, computedOffset);
|
|
306
393
|
}
|
|
@@ -412,12 +499,21 @@ export class Player extends Source<PlayerOptions> {
|
|
|
412
499
|
set playbackRate(rate) {
|
|
413
500
|
this._playbackRate = rate;
|
|
414
501
|
const now = this.now();
|
|
502
|
+
this._progressTracker.setValueAtTime(rate, now);
|
|
415
503
|
|
|
416
504
|
// cancel the stop event since it's at a different time now
|
|
417
505
|
const stopEvent = this._state.getNextState("stopped", now);
|
|
418
506
|
if (stopEvent && stopEvent.implicitEnd) {
|
|
419
507
|
this._state.cancel(stopEvent.time);
|
|
420
508
|
this._activeSources.forEach((source) => source.cancelStop());
|
|
509
|
+
|
|
510
|
+
const progress = this._getProgressAtTime(now);
|
|
511
|
+
const remainingTime = this._buffer.duration - progress;
|
|
512
|
+
const newStopTime = now + remainingTime / rate;
|
|
513
|
+
// reschedule the implicit stop event
|
|
514
|
+
this._state.setStateAtTime("stopped", newStopTime, {
|
|
515
|
+
implicitEnd: true,
|
|
516
|
+
});
|
|
421
517
|
}
|
|
422
518
|
|
|
423
519
|
// set all the sources
|
|
@@ -454,6 +550,9 @@ export class Player extends Source<PlayerOptions> {
|
|
|
454
550
|
this._activeSources.forEach((source) => source.dispose());
|
|
455
551
|
this._activeSources.clear();
|
|
456
552
|
this._buffer.dispose();
|
|
553
|
+
this._constantSource.dispose();
|
|
554
|
+
this._progressTracker.dispose();
|
|
555
|
+
this._progressOffset.dispose();
|
|
457
556
|
return this;
|
|
458
557
|
}
|
|
459
558
|
}
|
package/Tone/version.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const version: string = "15.
|
|
1
|
+
export const version: string = "15.3.0";
|