@dawcore/transport 0.0.2 → 0.0.3

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/dist/index.mjs CHANGED
@@ -49,12 +49,15 @@ var Clock = class {
49
49
 
50
50
  // src/core/scheduler.ts
51
51
  var Scheduler = class {
52
- constructor(options = {}) {
52
+ constructor(tempoMap, options = {}) {
53
53
  this._rightEdge = 0;
54
+ // integer ticks
54
55
  this._listeners = /* @__PURE__ */ new Set();
55
56
  this._loopEnabled = false;
56
57
  this._loopStart = 0;
58
+ // integer ticks
57
59
  this._loopEnd = 0;
60
+ this._tempoMap = tempoMap;
58
61
  this._lookahead = options.lookahead ?? 0.2;
59
62
  this._onLoop = options.onLoop;
60
63
  }
@@ -64,25 +67,40 @@ var Scheduler = class {
64
67
  removeListener(listener) {
65
68
  this._listeners.delete(listener);
66
69
  }
67
- setLoop(enabled, start, end) {
68
- if (enabled && start >= end) {
70
+ /** Primary API — ticks as source of truth */
71
+ setLoop(enabled, startTick, endTick) {
72
+ if (enabled && (!Number.isFinite(startTick) || !Number.isFinite(endTick))) {
69
73
  console.warn(
70
- "[waveform-playlist] Scheduler.setLoop: start (" + start + ") must be less than end (" + end + ")"
74
+ "[waveform-playlist] Scheduler.setLoop: non-finite tick values (" + startTick + ", " + endTick + ")"
75
+ );
76
+ return;
77
+ }
78
+ if (enabled && startTick >= endTick) {
79
+ console.warn(
80
+ "[waveform-playlist] Scheduler.setLoop: startTick (" + startTick + ") must be less than endTick (" + endTick + ")"
71
81
  );
72
82
  return;
73
83
  }
74
84
  this._loopEnabled = enabled;
75
- this._loopStart = start;
76
- this._loopEnd = end;
77
- }
78
- reset(time) {
79
- this._rightEdge = time;
80
- }
81
- advance(currentTime) {
82
- const targetEdge = currentTime + this._lookahead;
85
+ this._loopStart = Math.round(startTick);
86
+ this._loopEnd = Math.round(endTick);
87
+ }
88
+ /** Convenience — converts seconds to ticks via TempoMap */
89
+ setLoopSeconds(enabled, startSec, endSec) {
90
+ const startTick = this._tempoMap.secondsToTicks(startSec);
91
+ const endTick = this._tempoMap.secondsToTicks(endSec);
92
+ this.setLoop(enabled, startTick, endTick);
93
+ }
94
+ /** Reset scheduling cursor. Takes seconds (from Clock), converts to ticks. */
95
+ reset(timeSeconds) {
96
+ this._rightEdge = this._tempoMap.secondsToTicks(timeSeconds);
97
+ }
98
+ /** Advance the scheduling window. Takes seconds (from Clock), converts to ticks. */
99
+ advance(currentTimeSeconds) {
100
+ const targetTick = this._tempoMap.secondsToTicks(currentTimeSeconds + this._lookahead);
83
101
  if (this._loopEnabled && this._loopEnd > this._loopStart) {
84
102
  const loopDuration = this._loopEnd - this._loopStart;
85
- let remaining = targetEdge - this._rightEdge;
103
+ let remaining = targetTick - this._rightEdge;
86
104
  while (remaining > 0) {
87
105
  const distToEnd = this._loopEnd - this._rightEdge;
88
106
  if (distToEnd <= 0 || distToEnd > remaining) {
@@ -95,21 +113,25 @@ var Scheduler = class {
95
113
  for (const listener of this._listeners) {
96
114
  listener.onPositionJump(this._loopStart);
97
115
  }
98
- this._onLoop?.(this._loopStart);
116
+ this._onLoop?.(
117
+ this._tempoMap.ticksToSeconds(this._loopStart),
118
+ this._tempoMap.ticksToSeconds(this._loopEnd),
119
+ currentTimeSeconds
120
+ );
99
121
  this._rightEdge = this._loopStart;
100
122
  if (loopDuration <= 0) break;
101
123
  }
102
124
  return;
103
125
  }
104
- if (targetEdge > this._rightEdge) {
105
- this._generateAndConsume(this._rightEdge, targetEdge);
106
- this._rightEdge = targetEdge;
126
+ if (targetTick > this._rightEdge) {
127
+ this._generateAndConsume(this._rightEdge, targetTick);
128
+ this._rightEdge = targetTick;
107
129
  }
108
130
  }
109
- _generateAndConsume(from, to) {
131
+ _generateAndConsume(fromTick, toTick) {
110
132
  for (const listener of this._listeners) {
111
133
  try {
112
- const events = listener.generate(from, to);
134
+ const events = listener.generate(fromTick, toTick);
113
135
  for (const event of events) {
114
136
  try {
115
137
  listener.consume(event);
@@ -159,17 +181,37 @@ var Timer = class {
159
181
  // src/timeline/sample-timeline.ts
160
182
  var SampleTimeline = class {
161
183
  constructor(sampleRate) {
184
+ this._tempoMap = null;
162
185
  this._sampleRate = sampleRate;
163
186
  }
164
187
  get sampleRate() {
165
188
  return this._sampleRate;
166
189
  }
190
+ setTempoMap(tempoMap) {
191
+ this._tempoMap = tempoMap;
192
+ }
167
193
  samplesToSeconds(samples) {
168
194
  return samples / this._sampleRate;
169
195
  }
170
196
  secondsToSamples(seconds) {
171
197
  return Math.round(seconds * this._sampleRate);
172
198
  }
199
+ ticksToSamples(ticks) {
200
+ if (!this._tempoMap) {
201
+ throw new Error(
202
+ "[waveform-playlist] SampleTimeline: tempoMap not set \u2014 call setTempoMap() first"
203
+ );
204
+ }
205
+ return Math.round(this._tempoMap.ticksToSeconds(ticks) * this._sampleRate);
206
+ }
207
+ samplesToTicks(samples) {
208
+ if (!this._tempoMap) {
209
+ throw new Error(
210
+ "[waveform-playlist] SampleTimeline: tempoMap not set \u2014 call setTempoMap() first"
211
+ );
212
+ }
213
+ return this._tempoMap.secondsToTicks(samples / this._sampleRate);
214
+ }
173
215
  };
174
216
 
175
217
  // src/timeline/tempo-map.ts
@@ -216,7 +258,7 @@ var TempoMap = class {
216
258
  const entry = this._entries[lo];
217
259
  const secondsIntoSegment = seconds - entry.secondsAtTick;
218
260
  const ticksPerSecond = entry.bpm / 60 * this._ppqn;
219
- return entry.tick + secondsIntoSegment * ticksPerSecond;
261
+ return Math.round(entry.tick + secondsIntoSegment * ticksPerSecond);
220
262
  }
221
263
  beatsToSeconds(beats) {
222
264
  return this.ticksToSeconds(beats * this._ppqn);
@@ -518,14 +560,15 @@ var TrackNode = class {
518
560
 
519
561
  // src/audio/clip-player.ts
520
562
  var ClipPlayer = class {
521
- constructor(audioContext, sampleTimeline, toAudioTime) {
563
+ constructor(audioContext, sampleTimeline, tempoMap, toAudioTime) {
522
564
  this._tracks = /* @__PURE__ */ new Map();
523
565
  this._trackNodes = /* @__PURE__ */ new Map();
524
566
  this._activeSources = /* @__PURE__ */ new Map();
525
567
  this._loopEnabled = false;
526
- this._loopEnd = 0;
568
+ this._loopEndSamples = 0;
527
569
  this._audioContext = audioContext;
528
570
  this._sampleTimeline = sampleTimeline;
571
+ this._tempoMap = tempoMap;
529
572
  this._toAudioTime = toAudioTime;
530
573
  }
531
574
  setTracks(tracks, trackNodes) {
@@ -535,41 +578,50 @@ var ClipPlayer = class {
535
578
  this._tracks.set(track.id, { track, clips: track.clips });
536
579
  }
537
580
  }
538
- setLoop(enabled, _start, end) {
581
+ /** Set loop region using ticks. startTick is unused — loop clamping only needs
582
+ * the end boundary; mid-clip restart at loopStart is handled by onPositionJump. */
583
+ setLoop(enabled, _startTick, endTick) {
539
584
  this._loopEnabled = enabled;
540
- this._loopEnd = end;
585
+ this._loopEndSamples = this._sampleTimeline.ticksToSamples(endTick);
586
+ }
587
+ /** Set loop region using samples directly */
588
+ setLoopSamples(enabled, _startSample, endSample) {
589
+ this._loopEnabled = enabled;
590
+ this._loopEndSamples = endSample;
541
591
  }
542
592
  updateTrack(trackId, track) {
543
593
  this._tracks.set(trackId, { track, clips: track.clips });
544
594
  this._silenceTrack(trackId);
545
595
  }
546
- generate(fromTime, toTime) {
596
+ generate(fromTick, toTick) {
547
597
  const events = [];
598
+ const fromSample = this._sampleTimeline.ticksToSamples(fromTick);
599
+ const toSample = this._sampleTimeline.ticksToSamples(toTick);
548
600
  for (const [trackId, state] of this._tracks) {
549
601
  for (const clip of state.clips) {
550
602
  if (clip.durationSamples === 0) continue;
551
603
  if (!clip.audioBuffer) continue;
552
- const clipStartTime = this._sampleTimeline.samplesToSeconds(clip.startSample);
553
- const clipDuration = this._sampleTimeline.samplesToSeconds(clip.durationSamples);
554
- const clipOffsetTime = this._sampleTimeline.samplesToSeconds(clip.offsetSamples);
555
- if (clipStartTime < fromTime) continue;
556
- if (clipStartTime >= toTime) continue;
557
- const fadeInDuration = clip.fadeIn ? this._sampleTimeline.samplesToSeconds(clip.fadeIn.duration ?? 0) : 0;
558
- const fadeOutDuration = clip.fadeOut ? this._sampleTimeline.samplesToSeconds(clip.fadeOut.duration ?? 0) : 0;
559
- let duration = clipDuration;
560
- if (this._loopEnabled && clipStartTime + duration > this._loopEnd) {
561
- duration = this._loopEnd - clipStartTime;
604
+ const clipStartSample = clip.startSample;
605
+ if (clipStartSample < fromSample) continue;
606
+ if (clipStartSample >= toSample) continue;
607
+ const fadeInDurationSamples = clip.fadeIn ? clip.fadeIn.duration ?? 0 : 0;
608
+ const fadeOutDurationSamples = clip.fadeOut ? clip.fadeOut.duration ?? 0 : 0;
609
+ let durationSamples = clip.durationSamples;
610
+ if (this._loopEnabled && clipStartSample + durationSamples > this._loopEndSamples) {
611
+ durationSamples = this._loopEndSamples - clipStartSample;
562
612
  }
613
+ const clipTick = this._sampleTimeline.samplesToTicks(clipStartSample);
563
614
  events.push({
564
615
  trackId,
565
616
  clipId: clip.id,
566
617
  audioBuffer: clip.audioBuffer,
567
- transportTime: clipStartTime,
568
- offset: clipOffsetTime,
569
- duration,
618
+ tick: clipTick,
619
+ startSample: clipStartSample,
620
+ offsetSamples: clip.offsetSamples,
621
+ durationSamples,
570
622
  gain: clip.gain,
571
- fadeInDuration,
572
- fadeOutDuration
623
+ fadeInDurationSamples,
624
+ fadeOutDurationSamples
573
625
  });
574
626
  }
575
627
  }
@@ -583,18 +635,25 @@ var ClipPlayer = class {
583
635
  );
584
636
  return;
585
637
  }
586
- if (event.offset >= event.audioBuffer.duration) {
638
+ const sampleRate = this._sampleTimeline.sampleRate;
639
+ const offsetSeconds = event.offsetSamples / sampleRate;
640
+ const durationSeconds = event.durationSamples / sampleRate;
641
+ if (offsetSeconds >= event.audioBuffer.duration) {
642
+ console.warn(
643
+ "[waveform-playlist] ClipPlayer.consume: offset (" + offsetSeconds + "s) exceeds audioBuffer.duration (" + event.audioBuffer.duration + 's) for clipId "' + event.clipId + '" \u2014 clip will not play'
644
+ );
587
645
  return;
588
646
  }
589
647
  const source = this._audioContext.createBufferSource();
590
648
  source.buffer = event.audioBuffer;
591
- const when = this._toAudioTime(event.transportTime);
649
+ const transportSeconds = this._tempoMap.ticksToSeconds(event.tick);
650
+ const when = this._toAudioTime(transportSeconds);
592
651
  const gainNode = this._audioContext.createGain();
593
652
  gainNode.gain.value = event.gain;
594
- let fadeIn = event.fadeInDuration;
595
- let fadeOut = event.fadeOutDuration;
596
- if (fadeIn + fadeOut > event.duration) {
597
- const ratio = event.duration / (fadeIn + fadeOut);
653
+ let fadeIn = event.fadeInDurationSamples / sampleRate;
654
+ let fadeOut = event.fadeOutDurationSamples / sampleRate;
655
+ if (fadeIn + fadeOut > durationSeconds) {
656
+ const ratio = durationSeconds / (fadeIn + fadeOut);
598
657
  fadeIn *= ratio;
599
658
  fadeOut *= ratio;
600
659
  }
@@ -603,9 +662,9 @@ var ClipPlayer = class {
603
662
  gainNode.gain.linearRampToValueAtTime(event.gain, when + fadeIn);
604
663
  }
605
664
  if (fadeOut > 0) {
606
- const fadeOutStart = when + event.duration - fadeOut;
665
+ const fadeOutStart = when + durationSeconds - fadeOut;
607
666
  gainNode.gain.setValueAtTime(event.gain, fadeOutStart);
608
- gainNode.gain.linearRampToValueAtTime(0, when + event.duration);
667
+ gainNode.gain.linearRampToValueAtTime(0, when + durationSeconds);
609
668
  }
610
669
  source.connect(gainNode);
611
670
  gainNode.connect(trackNode.input);
@@ -621,33 +680,37 @@ var ClipPlayer = class {
621
680
  console.warn("[waveform-playlist] ClipPlayer: error disconnecting gain node:", String(err));
622
681
  }
623
682
  });
624
- source.start(when, event.offset, event.duration);
683
+ source.start(when, offsetSeconds, durationSeconds);
625
684
  }
626
- onPositionJump(newTime) {
685
+ onPositionJump(newTick) {
627
686
  this.silence();
687
+ const newSample = this._sampleTimeline.ticksToSamples(newTick);
628
688
  for (const [trackId, state] of this._tracks) {
629
689
  for (const clip of state.clips) {
630
690
  if (clip.durationSamples === 0) continue;
631
691
  if (!clip.audioBuffer) continue;
632
- const clipStartTime = this._sampleTimeline.samplesToSeconds(clip.startSample);
633
- const clipDuration = this._sampleTimeline.samplesToSeconds(clip.durationSamples);
634
- const clipEndTime = clipStartTime + clipDuration;
635
- const clipOffsetTime = this._sampleTimeline.samplesToSeconds(clip.offsetSamples);
636
- if (clipStartTime <= newTime && clipEndTime > newTime) {
637
- const offsetIntoClip = newTime - clipStartTime;
638
- const offset = clipOffsetTime + offsetIntoClip;
639
- const duration = clipEndTime - newTime;
640
- const fadeOutDuration = clip.fadeOut ? this._sampleTimeline.samplesToSeconds(clip.fadeOut.duration ?? 0) : 0;
692
+ const clipStartSample = clip.startSample;
693
+ const clipEndSample = clipStartSample + clip.durationSamples;
694
+ if (clipStartSample <= newSample && clipEndSample > newSample) {
695
+ const offsetIntoClipSamples = newSample - clipStartSample;
696
+ const offsetSamples = clip.offsetSamples + offsetIntoClipSamples;
697
+ let durationSamples = clipEndSample - newSample;
698
+ if (this._loopEnabled && newSample + durationSamples > this._loopEndSamples) {
699
+ durationSamples = this._loopEndSamples - newSample;
700
+ }
701
+ if (durationSamples <= 0) continue;
702
+ const fadeOutDurationSamples = clip.fadeOut ? clip.fadeOut.duration ?? 0 : 0;
641
703
  this.consume({
642
704
  trackId,
643
705
  clipId: clip.id,
644
706
  audioBuffer: clip.audioBuffer,
645
- transportTime: newTime,
646
- offset,
647
- duration,
707
+ tick: newTick,
708
+ startSample: newSample,
709
+ offsetSamples,
710
+ durationSamples,
648
711
  gain: clip.gain,
649
- fadeInDuration: 0,
650
- fadeOutDuration
712
+ fadeInDurationSamples: 0,
713
+ fadeOutDurationSamples
651
714
  });
652
715
  }
653
716
  }
@@ -720,31 +783,29 @@ var MetronomePlayer = class {
720
783
  this._accentBuffer = accent;
721
784
  this._normalBuffer = normal;
722
785
  }
723
- generate(fromTime, toTime) {
786
+ generate(fromTick, toTick) {
724
787
  if (!this._enabled || !this._accentBuffer || !this._normalBuffer) {
725
788
  return [];
726
789
  }
727
790
  const events = [];
728
- const fromTicks = this._tempoMap.secondsToTicks(fromTime);
729
- const toTicks = this._tempoMap.secondsToTicks(toTime);
730
- let entry = this._meterMap.getEntryAt(fromTicks);
731
- let beatSize = this._meterMap.ticksPerBeat(fromTicks);
732
- const tickIntoSection = fromTicks - entry.tick;
791
+ let entry = this._meterMap.getEntryAt(fromTick);
792
+ let beatSize = this._meterMap.ticksPerBeat(fromTick);
793
+ const tickIntoSection = fromTick - entry.tick;
733
794
  let tick = entry.tick + Math.ceil(tickIntoSection / beatSize) * beatSize;
734
- while (tick < toTicks) {
735
- const currentEntry = this._meterMap.getEntryAt(tick);
795
+ while (tick < toTick) {
796
+ const tickPos = tick;
797
+ const currentEntry = this._meterMap.getEntryAt(tickPos);
736
798
  if (currentEntry.tick !== entry.tick) {
737
799
  entry = currentEntry;
738
- beatSize = this._meterMap.ticksPerBeat(tick);
800
+ beatSize = this._meterMap.ticksPerBeat(tickPos);
739
801
  }
740
- const isAccent = this._meterMap.isBarBoundary(tick);
741
- const transportTime = this._tempoMap.ticksToSeconds(tick);
802
+ const isAccent = this._meterMap.isBarBoundary(tickPos);
742
803
  events.push({
743
- transportTime,
804
+ tick: tickPos,
744
805
  isAccent,
745
806
  buffer: isAccent ? this._accentBuffer : this._normalBuffer
746
807
  });
747
- beatSize = this._meterMap.ticksPerBeat(tick);
808
+ beatSize = this._meterMap.ticksPerBeat(tickPos);
748
809
  tick += beatSize;
749
810
  }
750
811
  return events;
@@ -765,10 +826,10 @@ var MetronomePlayer = class {
765
826
  );
766
827
  }
767
828
  });
768
- source.start(this._toAudioTime(event.transportTime));
829
+ const transportTime = this._tempoMap.ticksToSeconds(event.tick);
830
+ source.start(this._toAudioTime(transportTime));
769
831
  }
770
- onPositionJump(_newTime) {
771
- this.silence();
832
+ onPositionJump(_newTick) {
772
833
  }
773
834
  silence() {
774
835
  for (const source of this._activeSources) {
@@ -801,6 +862,8 @@ var Transport = class _Transport {
801
862
  this._soloedTrackIds = /* @__PURE__ */ new Set();
802
863
  this._mutedTrackIds = /* @__PURE__ */ new Set();
803
864
  this._playing = false;
865
+ this._loopEnabled = false;
866
+ this._loopStartSeconds = 0;
804
867
  this._listeners = /* @__PURE__ */ new Map();
805
868
  this._audioContext = audioContext;
806
869
  const sampleRate = options.sampleRate ?? audioContext.sampleRate;
@@ -811,15 +874,17 @@ var Transport = class _Transport {
811
874
  const lookahead = options.schedulerLookahead ?? 0.2;
812
875
  _Transport._validateOptions(sampleRate, ppqn, tempo, numerator, denominator, lookahead);
813
876
  this._clock = new Clock(audioContext);
814
- this._scheduler = new Scheduler({
815
- lookahead,
816
- onLoop: (loopStartTime) => {
817
- this._clock.seekTo(loopStartTime);
818
- }
819
- });
820
877
  this._sampleTimeline = new SampleTimeline(sampleRate);
821
878
  this._meterMap = new MeterMap(ppqn, numerator, denominator);
822
879
  this._tempoMap = new TempoMap(ppqn, tempo);
880
+ this._scheduler = new Scheduler(this._tempoMap, {
881
+ lookahead,
882
+ onLoop: (loopStartSeconds, loopEndSeconds, currentTimeSeconds) => {
883
+ const timeToBoundary = loopEndSeconds - currentTimeSeconds;
884
+ this._clock.seekTo(loopStartSeconds - timeToBoundary);
885
+ }
886
+ });
887
+ this._sampleTimeline.setTempoMap(this._tempoMap);
823
888
  this._initAudioGraph(audioContext);
824
889
  this._timer = new Timer(() => {
825
890
  const time = this._clock.getTime();
@@ -843,7 +908,8 @@ var Transport = class _Transport {
843
908
  this._scheduler.reset(currentTime);
844
909
  this._endTime = endTime;
845
910
  this._clock.start();
846
- this._clipPlayer.onPositionJump(currentTime);
911
+ const currentTick = this._tempoMap.secondsToTicks(currentTime);
912
+ this._clipPlayer.onPositionJump(currentTick);
847
913
  this._timer.start();
848
914
  this._playing = true;
849
915
  this._emit("play");
@@ -879,12 +945,17 @@ var Transport = class _Transport {
879
945
  this._endTime = void 0;
880
946
  if (wasPlaying) {
881
947
  this._clock.start();
882
- this._clipPlayer.onPositionJump(time);
948
+ const seekTick = this._tempoMap.secondsToTicks(time);
949
+ this._clipPlayer.onPositionJump(seekTick);
883
950
  this._timer.start();
884
951
  }
885
952
  }
886
953
  getCurrentTime() {
887
- return this._clock.getTime();
954
+ const t = this._clock.getTime();
955
+ if (this._loopEnabled && t < this._loopStartSeconds) {
956
+ return this._loopStartSeconds;
957
+ }
958
+ return t;
888
959
  }
889
960
  isPlaying() {
890
961
  return this._playing;
@@ -1000,15 +1071,46 @@ var Transport = class _Transport {
1000
1071
  this._masterNode.setVolume(volume);
1001
1072
  }
1002
1073
  // --- Loop ---
1003
- setLoop(enabled, start, end) {
1004
- if (enabled && start >= end) {
1074
+ /** Primary loop API — ticks as source of truth */
1075
+ setLoop(enabled, startTick, endTick) {
1076
+ if (enabled && startTick >= endTick) {
1077
+ console.warn(
1078
+ "[waveform-playlist] Transport.setLoop: startTick (" + startTick + ") must be less than endTick (" + endTick + ")"
1079
+ );
1080
+ return;
1081
+ }
1082
+ this._loopEnabled = enabled;
1083
+ this._loopStartSeconds = this._tempoMap.ticksToSeconds(startTick);
1084
+ this._scheduler.setLoop(enabled, startTick, endTick);
1085
+ this._clipPlayer.setLoop(enabled, startTick, endTick);
1086
+ this._emit("loop");
1087
+ }
1088
+ /** Convenience — converts seconds to ticks */
1089
+ setLoopSeconds(enabled, startSec, endSec) {
1090
+ const startTick = this._tempoMap.secondsToTicks(startSec);
1091
+ const endTick = this._tempoMap.secondsToTicks(endSec);
1092
+ this.setLoop(enabled, startTick, endTick);
1093
+ }
1094
+ /** Convenience — sets loop in samples */
1095
+ setLoopSamples(enabled, startSample, endSample) {
1096
+ if (enabled && (!Number.isFinite(startSample) || !Number.isFinite(endSample))) {
1005
1097
  console.warn(
1006
- "[waveform-playlist] Transport.setLoop: start (" + start + ") must be less than end (" + end + ")"
1098
+ "[waveform-playlist] Transport.setLoopSamples: non-finite sample values (" + startSample + ", " + endSample + ")"
1007
1099
  );
1008
1100
  return;
1009
1101
  }
1010
- this._scheduler.setLoop(enabled, start, end);
1011
- this._clipPlayer.setLoop(enabled, start, end);
1102
+ if (enabled && startSample >= endSample) {
1103
+ console.warn(
1104
+ "[waveform-playlist] Transport.setLoopSamples: startSample (" + startSample + ") must be less than endSample (" + endSample + ")"
1105
+ );
1106
+ return;
1107
+ }
1108
+ const startTick = this._sampleTimeline.samplesToTicks(startSample);
1109
+ const endTick = this._sampleTimeline.samplesToTicks(endSample);
1110
+ this._loopEnabled = enabled;
1111
+ this._loopStartSeconds = this._tempoMap.ticksToSeconds(startTick);
1112
+ this._clipPlayer.setLoopSamples(enabled, startSample, endSample);
1113
+ this._scheduler.setLoop(enabled, startTick, endTick);
1012
1114
  this._emit("loop");
1013
1115
  }
1014
1116
  // --- Tempo ---
@@ -1132,7 +1234,12 @@ var Transport = class _Transport {
1132
1234
  this._masterNode = new MasterNode(audioContext);
1133
1235
  this._masterNode.output.connect(audioContext.destination);
1134
1236
  const toAudioTime = (transportTime) => this._clock.toAudioTime(transportTime);
1135
- this._clipPlayer = new ClipPlayer(audioContext, this._sampleTimeline, toAudioTime);
1237
+ this._clipPlayer = new ClipPlayer(
1238
+ audioContext,
1239
+ this._sampleTimeline,
1240
+ this._tempoMap,
1241
+ toAudioTime
1242
+ );
1136
1243
  this._metronomePlayer = new MetronomePlayer(
1137
1244
  audioContext,
1138
1245
  this._tempoMap,
@@ -1235,7 +1342,7 @@ var NativePlayoutAdapter = class {
1235
1342
  this._transport.setTrackPan(trackId, pan);
1236
1343
  }
1237
1344
  setLoop(enabled, start, end) {
1238
- this._transport.setLoop(enabled, start, end);
1345
+ this._transport.setLoopSeconds(enabled, start, end);
1239
1346
  }
1240
1347
  dispose() {
1241
1348
  this._transport.dispose();