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.
@@ -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
- // set the looping properties
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.2.13";
1
+ export const version: string = "15.3.0";