@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.js CHANGED
@@ -86,12 +86,15 @@ var Clock = class {
86
86
 
87
87
  // src/core/scheduler.ts
88
88
  var Scheduler = class {
89
- constructor(options = {}) {
89
+ constructor(tempoMap, options = {}) {
90
90
  this._rightEdge = 0;
91
+ // integer ticks
91
92
  this._listeners = /* @__PURE__ */ new Set();
92
93
  this._loopEnabled = false;
93
94
  this._loopStart = 0;
95
+ // integer ticks
94
96
  this._loopEnd = 0;
97
+ this._tempoMap = tempoMap;
95
98
  this._lookahead = options.lookahead ?? 0.2;
96
99
  this._onLoop = options.onLoop;
97
100
  }
@@ -101,25 +104,40 @@ var Scheduler = class {
101
104
  removeListener(listener) {
102
105
  this._listeners.delete(listener);
103
106
  }
104
- setLoop(enabled, start, end) {
105
- if (enabled && start >= end) {
107
+ /** Primary API — ticks as source of truth */
108
+ setLoop(enabled, startTick, endTick) {
109
+ if (enabled && (!Number.isFinite(startTick) || !Number.isFinite(endTick))) {
106
110
  console.warn(
107
- "[waveform-playlist] Scheduler.setLoop: start (" + start + ") must be less than end (" + end + ")"
111
+ "[waveform-playlist] Scheduler.setLoop: non-finite tick values (" + startTick + ", " + endTick + ")"
112
+ );
113
+ return;
114
+ }
115
+ if (enabled && startTick >= endTick) {
116
+ console.warn(
117
+ "[waveform-playlist] Scheduler.setLoop: startTick (" + startTick + ") must be less than endTick (" + endTick + ")"
108
118
  );
109
119
  return;
110
120
  }
111
121
  this._loopEnabled = enabled;
112
- this._loopStart = start;
113
- this._loopEnd = end;
114
- }
115
- reset(time) {
116
- this._rightEdge = time;
117
- }
118
- advance(currentTime) {
119
- const targetEdge = currentTime + this._lookahead;
122
+ this._loopStart = Math.round(startTick);
123
+ this._loopEnd = Math.round(endTick);
124
+ }
125
+ /** Convenience — converts seconds to ticks via TempoMap */
126
+ setLoopSeconds(enabled, startSec, endSec) {
127
+ const startTick = this._tempoMap.secondsToTicks(startSec);
128
+ const endTick = this._tempoMap.secondsToTicks(endSec);
129
+ this.setLoop(enabled, startTick, endTick);
130
+ }
131
+ /** Reset scheduling cursor. Takes seconds (from Clock), converts to ticks. */
132
+ reset(timeSeconds) {
133
+ this._rightEdge = this._tempoMap.secondsToTicks(timeSeconds);
134
+ }
135
+ /** Advance the scheduling window. Takes seconds (from Clock), converts to ticks. */
136
+ advance(currentTimeSeconds) {
137
+ const targetTick = this._tempoMap.secondsToTicks(currentTimeSeconds + this._lookahead);
120
138
  if (this._loopEnabled && this._loopEnd > this._loopStart) {
121
139
  const loopDuration = this._loopEnd - this._loopStart;
122
- let remaining = targetEdge - this._rightEdge;
140
+ let remaining = targetTick - this._rightEdge;
123
141
  while (remaining > 0) {
124
142
  const distToEnd = this._loopEnd - this._rightEdge;
125
143
  if (distToEnd <= 0 || distToEnd > remaining) {
@@ -132,21 +150,25 @@ var Scheduler = class {
132
150
  for (const listener of this._listeners) {
133
151
  listener.onPositionJump(this._loopStart);
134
152
  }
135
- this._onLoop?.(this._loopStart);
153
+ this._onLoop?.(
154
+ this._tempoMap.ticksToSeconds(this._loopStart),
155
+ this._tempoMap.ticksToSeconds(this._loopEnd),
156
+ currentTimeSeconds
157
+ );
136
158
  this._rightEdge = this._loopStart;
137
159
  if (loopDuration <= 0) break;
138
160
  }
139
161
  return;
140
162
  }
141
- if (targetEdge > this._rightEdge) {
142
- this._generateAndConsume(this._rightEdge, targetEdge);
143
- this._rightEdge = targetEdge;
163
+ if (targetTick > this._rightEdge) {
164
+ this._generateAndConsume(this._rightEdge, targetTick);
165
+ this._rightEdge = targetTick;
144
166
  }
145
167
  }
146
- _generateAndConsume(from, to) {
168
+ _generateAndConsume(fromTick, toTick) {
147
169
  for (const listener of this._listeners) {
148
170
  try {
149
- const events = listener.generate(from, to);
171
+ const events = listener.generate(fromTick, toTick);
150
172
  for (const event of events) {
151
173
  try {
152
174
  listener.consume(event);
@@ -196,17 +218,37 @@ var Timer = class {
196
218
  // src/timeline/sample-timeline.ts
197
219
  var SampleTimeline = class {
198
220
  constructor(sampleRate) {
221
+ this._tempoMap = null;
199
222
  this._sampleRate = sampleRate;
200
223
  }
201
224
  get sampleRate() {
202
225
  return this._sampleRate;
203
226
  }
227
+ setTempoMap(tempoMap) {
228
+ this._tempoMap = tempoMap;
229
+ }
204
230
  samplesToSeconds(samples) {
205
231
  return samples / this._sampleRate;
206
232
  }
207
233
  secondsToSamples(seconds) {
208
234
  return Math.round(seconds * this._sampleRate);
209
235
  }
236
+ ticksToSamples(ticks) {
237
+ if (!this._tempoMap) {
238
+ throw new Error(
239
+ "[waveform-playlist] SampleTimeline: tempoMap not set \u2014 call setTempoMap() first"
240
+ );
241
+ }
242
+ return Math.round(this._tempoMap.ticksToSeconds(ticks) * this._sampleRate);
243
+ }
244
+ samplesToTicks(samples) {
245
+ if (!this._tempoMap) {
246
+ throw new Error(
247
+ "[waveform-playlist] SampleTimeline: tempoMap not set \u2014 call setTempoMap() first"
248
+ );
249
+ }
250
+ return this._tempoMap.secondsToTicks(samples / this._sampleRate);
251
+ }
210
252
  };
211
253
 
212
254
  // src/timeline/tempo-map.ts
@@ -253,7 +295,7 @@ var TempoMap = class {
253
295
  const entry = this._entries[lo];
254
296
  const secondsIntoSegment = seconds - entry.secondsAtTick;
255
297
  const ticksPerSecond = entry.bpm / 60 * this._ppqn;
256
- return entry.tick + secondsIntoSegment * ticksPerSecond;
298
+ return Math.round(entry.tick + secondsIntoSegment * ticksPerSecond);
257
299
  }
258
300
  beatsToSeconds(beats) {
259
301
  return this.ticksToSeconds(beats * this._ppqn);
@@ -555,14 +597,15 @@ var TrackNode = class {
555
597
 
556
598
  // src/audio/clip-player.ts
557
599
  var ClipPlayer = class {
558
- constructor(audioContext, sampleTimeline, toAudioTime) {
600
+ constructor(audioContext, sampleTimeline, tempoMap, toAudioTime) {
559
601
  this._tracks = /* @__PURE__ */ new Map();
560
602
  this._trackNodes = /* @__PURE__ */ new Map();
561
603
  this._activeSources = /* @__PURE__ */ new Map();
562
604
  this._loopEnabled = false;
563
- this._loopEnd = 0;
605
+ this._loopEndSamples = 0;
564
606
  this._audioContext = audioContext;
565
607
  this._sampleTimeline = sampleTimeline;
608
+ this._tempoMap = tempoMap;
566
609
  this._toAudioTime = toAudioTime;
567
610
  }
568
611
  setTracks(tracks, trackNodes) {
@@ -572,41 +615,50 @@ var ClipPlayer = class {
572
615
  this._tracks.set(track.id, { track, clips: track.clips });
573
616
  }
574
617
  }
575
- setLoop(enabled, _start, end) {
618
+ /** Set loop region using ticks. startTick is unused — loop clamping only needs
619
+ * the end boundary; mid-clip restart at loopStart is handled by onPositionJump. */
620
+ setLoop(enabled, _startTick, endTick) {
576
621
  this._loopEnabled = enabled;
577
- this._loopEnd = end;
622
+ this._loopEndSamples = this._sampleTimeline.ticksToSamples(endTick);
623
+ }
624
+ /** Set loop region using samples directly */
625
+ setLoopSamples(enabled, _startSample, endSample) {
626
+ this._loopEnabled = enabled;
627
+ this._loopEndSamples = endSample;
578
628
  }
579
629
  updateTrack(trackId, track) {
580
630
  this._tracks.set(trackId, { track, clips: track.clips });
581
631
  this._silenceTrack(trackId);
582
632
  }
583
- generate(fromTime, toTime) {
633
+ generate(fromTick, toTick) {
584
634
  const events = [];
635
+ const fromSample = this._sampleTimeline.ticksToSamples(fromTick);
636
+ const toSample = this._sampleTimeline.ticksToSamples(toTick);
585
637
  for (const [trackId, state] of this._tracks) {
586
638
  for (const clip of state.clips) {
587
639
  if (clip.durationSamples === 0) continue;
588
640
  if (!clip.audioBuffer) continue;
589
- const clipStartTime = this._sampleTimeline.samplesToSeconds(clip.startSample);
590
- const clipDuration = this._sampleTimeline.samplesToSeconds(clip.durationSamples);
591
- const clipOffsetTime = this._sampleTimeline.samplesToSeconds(clip.offsetSamples);
592
- if (clipStartTime < fromTime) continue;
593
- if (clipStartTime >= toTime) continue;
594
- const fadeInDuration = clip.fadeIn ? this._sampleTimeline.samplesToSeconds(clip.fadeIn.duration ?? 0) : 0;
595
- const fadeOutDuration = clip.fadeOut ? this._sampleTimeline.samplesToSeconds(clip.fadeOut.duration ?? 0) : 0;
596
- let duration = clipDuration;
597
- if (this._loopEnabled && clipStartTime + duration > this._loopEnd) {
598
- duration = this._loopEnd - clipStartTime;
641
+ const clipStartSample = clip.startSample;
642
+ if (clipStartSample < fromSample) continue;
643
+ if (clipStartSample >= toSample) continue;
644
+ const fadeInDurationSamples = clip.fadeIn ? clip.fadeIn.duration ?? 0 : 0;
645
+ const fadeOutDurationSamples = clip.fadeOut ? clip.fadeOut.duration ?? 0 : 0;
646
+ let durationSamples = clip.durationSamples;
647
+ if (this._loopEnabled && clipStartSample + durationSamples > this._loopEndSamples) {
648
+ durationSamples = this._loopEndSamples - clipStartSample;
599
649
  }
650
+ const clipTick = this._sampleTimeline.samplesToTicks(clipStartSample);
600
651
  events.push({
601
652
  trackId,
602
653
  clipId: clip.id,
603
654
  audioBuffer: clip.audioBuffer,
604
- transportTime: clipStartTime,
605
- offset: clipOffsetTime,
606
- duration,
655
+ tick: clipTick,
656
+ startSample: clipStartSample,
657
+ offsetSamples: clip.offsetSamples,
658
+ durationSamples,
607
659
  gain: clip.gain,
608
- fadeInDuration,
609
- fadeOutDuration
660
+ fadeInDurationSamples,
661
+ fadeOutDurationSamples
610
662
  });
611
663
  }
612
664
  }
@@ -620,18 +672,25 @@ var ClipPlayer = class {
620
672
  );
621
673
  return;
622
674
  }
623
- if (event.offset >= event.audioBuffer.duration) {
675
+ const sampleRate = this._sampleTimeline.sampleRate;
676
+ const offsetSeconds = event.offsetSamples / sampleRate;
677
+ const durationSeconds = event.durationSamples / sampleRate;
678
+ if (offsetSeconds >= event.audioBuffer.duration) {
679
+ console.warn(
680
+ "[waveform-playlist] ClipPlayer.consume: offset (" + offsetSeconds + "s) exceeds audioBuffer.duration (" + event.audioBuffer.duration + 's) for clipId "' + event.clipId + '" \u2014 clip will not play'
681
+ );
624
682
  return;
625
683
  }
626
684
  const source = this._audioContext.createBufferSource();
627
685
  source.buffer = event.audioBuffer;
628
- const when = this._toAudioTime(event.transportTime);
686
+ const transportSeconds = this._tempoMap.ticksToSeconds(event.tick);
687
+ const when = this._toAudioTime(transportSeconds);
629
688
  const gainNode = this._audioContext.createGain();
630
689
  gainNode.gain.value = event.gain;
631
- let fadeIn = event.fadeInDuration;
632
- let fadeOut = event.fadeOutDuration;
633
- if (fadeIn + fadeOut > event.duration) {
634
- const ratio = event.duration / (fadeIn + fadeOut);
690
+ let fadeIn = event.fadeInDurationSamples / sampleRate;
691
+ let fadeOut = event.fadeOutDurationSamples / sampleRate;
692
+ if (fadeIn + fadeOut > durationSeconds) {
693
+ const ratio = durationSeconds / (fadeIn + fadeOut);
635
694
  fadeIn *= ratio;
636
695
  fadeOut *= ratio;
637
696
  }
@@ -640,9 +699,9 @@ var ClipPlayer = class {
640
699
  gainNode.gain.linearRampToValueAtTime(event.gain, when + fadeIn);
641
700
  }
642
701
  if (fadeOut > 0) {
643
- const fadeOutStart = when + event.duration - fadeOut;
702
+ const fadeOutStart = when + durationSeconds - fadeOut;
644
703
  gainNode.gain.setValueAtTime(event.gain, fadeOutStart);
645
- gainNode.gain.linearRampToValueAtTime(0, when + event.duration);
704
+ gainNode.gain.linearRampToValueAtTime(0, when + durationSeconds);
646
705
  }
647
706
  source.connect(gainNode);
648
707
  gainNode.connect(trackNode.input);
@@ -658,33 +717,37 @@ var ClipPlayer = class {
658
717
  console.warn("[waveform-playlist] ClipPlayer: error disconnecting gain node:", String(err));
659
718
  }
660
719
  });
661
- source.start(when, event.offset, event.duration);
720
+ source.start(when, offsetSeconds, durationSeconds);
662
721
  }
663
- onPositionJump(newTime) {
722
+ onPositionJump(newTick) {
664
723
  this.silence();
724
+ const newSample = this._sampleTimeline.ticksToSamples(newTick);
665
725
  for (const [trackId, state] of this._tracks) {
666
726
  for (const clip of state.clips) {
667
727
  if (clip.durationSamples === 0) continue;
668
728
  if (!clip.audioBuffer) continue;
669
- const clipStartTime = this._sampleTimeline.samplesToSeconds(clip.startSample);
670
- const clipDuration = this._sampleTimeline.samplesToSeconds(clip.durationSamples);
671
- const clipEndTime = clipStartTime + clipDuration;
672
- const clipOffsetTime = this._sampleTimeline.samplesToSeconds(clip.offsetSamples);
673
- if (clipStartTime <= newTime && clipEndTime > newTime) {
674
- const offsetIntoClip = newTime - clipStartTime;
675
- const offset = clipOffsetTime + offsetIntoClip;
676
- const duration = clipEndTime - newTime;
677
- const fadeOutDuration = clip.fadeOut ? this._sampleTimeline.samplesToSeconds(clip.fadeOut.duration ?? 0) : 0;
729
+ const clipStartSample = clip.startSample;
730
+ const clipEndSample = clipStartSample + clip.durationSamples;
731
+ if (clipStartSample <= newSample && clipEndSample > newSample) {
732
+ const offsetIntoClipSamples = newSample - clipStartSample;
733
+ const offsetSamples = clip.offsetSamples + offsetIntoClipSamples;
734
+ let durationSamples = clipEndSample - newSample;
735
+ if (this._loopEnabled && newSample + durationSamples > this._loopEndSamples) {
736
+ durationSamples = this._loopEndSamples - newSample;
737
+ }
738
+ if (durationSamples <= 0) continue;
739
+ const fadeOutDurationSamples = clip.fadeOut ? clip.fadeOut.duration ?? 0 : 0;
678
740
  this.consume({
679
741
  trackId,
680
742
  clipId: clip.id,
681
743
  audioBuffer: clip.audioBuffer,
682
- transportTime: newTime,
683
- offset,
684
- duration,
744
+ tick: newTick,
745
+ startSample: newSample,
746
+ offsetSamples,
747
+ durationSamples,
685
748
  gain: clip.gain,
686
- fadeInDuration: 0,
687
- fadeOutDuration
749
+ fadeInDurationSamples: 0,
750
+ fadeOutDurationSamples
688
751
  });
689
752
  }
690
753
  }
@@ -757,31 +820,29 @@ var MetronomePlayer = class {
757
820
  this._accentBuffer = accent;
758
821
  this._normalBuffer = normal;
759
822
  }
760
- generate(fromTime, toTime) {
823
+ generate(fromTick, toTick) {
761
824
  if (!this._enabled || !this._accentBuffer || !this._normalBuffer) {
762
825
  return [];
763
826
  }
764
827
  const events = [];
765
- const fromTicks = this._tempoMap.secondsToTicks(fromTime);
766
- const toTicks = this._tempoMap.secondsToTicks(toTime);
767
- let entry = this._meterMap.getEntryAt(fromTicks);
768
- let beatSize = this._meterMap.ticksPerBeat(fromTicks);
769
- const tickIntoSection = fromTicks - entry.tick;
828
+ let entry = this._meterMap.getEntryAt(fromTick);
829
+ let beatSize = this._meterMap.ticksPerBeat(fromTick);
830
+ const tickIntoSection = fromTick - entry.tick;
770
831
  let tick = entry.tick + Math.ceil(tickIntoSection / beatSize) * beatSize;
771
- while (tick < toTicks) {
772
- const currentEntry = this._meterMap.getEntryAt(tick);
832
+ while (tick < toTick) {
833
+ const tickPos = tick;
834
+ const currentEntry = this._meterMap.getEntryAt(tickPos);
773
835
  if (currentEntry.tick !== entry.tick) {
774
836
  entry = currentEntry;
775
- beatSize = this._meterMap.ticksPerBeat(tick);
837
+ beatSize = this._meterMap.ticksPerBeat(tickPos);
776
838
  }
777
- const isAccent = this._meterMap.isBarBoundary(tick);
778
- const transportTime = this._tempoMap.ticksToSeconds(tick);
839
+ const isAccent = this._meterMap.isBarBoundary(tickPos);
779
840
  events.push({
780
- transportTime,
841
+ tick: tickPos,
781
842
  isAccent,
782
843
  buffer: isAccent ? this._accentBuffer : this._normalBuffer
783
844
  });
784
- beatSize = this._meterMap.ticksPerBeat(tick);
845
+ beatSize = this._meterMap.ticksPerBeat(tickPos);
785
846
  tick += beatSize;
786
847
  }
787
848
  return events;
@@ -802,10 +863,10 @@ var MetronomePlayer = class {
802
863
  );
803
864
  }
804
865
  });
805
- source.start(this._toAudioTime(event.transportTime));
866
+ const transportTime = this._tempoMap.ticksToSeconds(event.tick);
867
+ source.start(this._toAudioTime(transportTime));
806
868
  }
807
- onPositionJump(_newTime) {
808
- this.silence();
869
+ onPositionJump(_newTick) {
809
870
  }
810
871
  silence() {
811
872
  for (const source of this._activeSources) {
@@ -838,6 +899,8 @@ var Transport = class _Transport {
838
899
  this._soloedTrackIds = /* @__PURE__ */ new Set();
839
900
  this._mutedTrackIds = /* @__PURE__ */ new Set();
840
901
  this._playing = false;
902
+ this._loopEnabled = false;
903
+ this._loopStartSeconds = 0;
841
904
  this._listeners = /* @__PURE__ */ new Map();
842
905
  this._audioContext = audioContext;
843
906
  const sampleRate = options.sampleRate ?? audioContext.sampleRate;
@@ -848,15 +911,17 @@ var Transport = class _Transport {
848
911
  const lookahead = options.schedulerLookahead ?? 0.2;
849
912
  _Transport._validateOptions(sampleRate, ppqn, tempo, numerator, denominator, lookahead);
850
913
  this._clock = new Clock(audioContext);
851
- this._scheduler = new Scheduler({
852
- lookahead,
853
- onLoop: (loopStartTime) => {
854
- this._clock.seekTo(loopStartTime);
855
- }
856
- });
857
914
  this._sampleTimeline = new SampleTimeline(sampleRate);
858
915
  this._meterMap = new MeterMap(ppqn, numerator, denominator);
859
916
  this._tempoMap = new TempoMap(ppqn, tempo);
917
+ this._scheduler = new Scheduler(this._tempoMap, {
918
+ lookahead,
919
+ onLoop: (loopStartSeconds, loopEndSeconds, currentTimeSeconds) => {
920
+ const timeToBoundary = loopEndSeconds - currentTimeSeconds;
921
+ this._clock.seekTo(loopStartSeconds - timeToBoundary);
922
+ }
923
+ });
924
+ this._sampleTimeline.setTempoMap(this._tempoMap);
860
925
  this._initAudioGraph(audioContext);
861
926
  this._timer = new Timer(() => {
862
927
  const time = this._clock.getTime();
@@ -880,7 +945,8 @@ var Transport = class _Transport {
880
945
  this._scheduler.reset(currentTime);
881
946
  this._endTime = endTime;
882
947
  this._clock.start();
883
- this._clipPlayer.onPositionJump(currentTime);
948
+ const currentTick = this._tempoMap.secondsToTicks(currentTime);
949
+ this._clipPlayer.onPositionJump(currentTick);
884
950
  this._timer.start();
885
951
  this._playing = true;
886
952
  this._emit("play");
@@ -916,12 +982,17 @@ var Transport = class _Transport {
916
982
  this._endTime = void 0;
917
983
  if (wasPlaying) {
918
984
  this._clock.start();
919
- this._clipPlayer.onPositionJump(time);
985
+ const seekTick = this._tempoMap.secondsToTicks(time);
986
+ this._clipPlayer.onPositionJump(seekTick);
920
987
  this._timer.start();
921
988
  }
922
989
  }
923
990
  getCurrentTime() {
924
- return this._clock.getTime();
991
+ const t = this._clock.getTime();
992
+ if (this._loopEnabled && t < this._loopStartSeconds) {
993
+ return this._loopStartSeconds;
994
+ }
995
+ return t;
925
996
  }
926
997
  isPlaying() {
927
998
  return this._playing;
@@ -1037,15 +1108,46 @@ var Transport = class _Transport {
1037
1108
  this._masterNode.setVolume(volume);
1038
1109
  }
1039
1110
  // --- Loop ---
1040
- setLoop(enabled, start, end) {
1041
- if (enabled && start >= end) {
1111
+ /** Primary loop API — ticks as source of truth */
1112
+ setLoop(enabled, startTick, endTick) {
1113
+ if (enabled && startTick >= endTick) {
1114
+ console.warn(
1115
+ "[waveform-playlist] Transport.setLoop: startTick (" + startTick + ") must be less than endTick (" + endTick + ")"
1116
+ );
1117
+ return;
1118
+ }
1119
+ this._loopEnabled = enabled;
1120
+ this._loopStartSeconds = this._tempoMap.ticksToSeconds(startTick);
1121
+ this._scheduler.setLoop(enabled, startTick, endTick);
1122
+ this._clipPlayer.setLoop(enabled, startTick, endTick);
1123
+ this._emit("loop");
1124
+ }
1125
+ /** Convenience — converts seconds to ticks */
1126
+ setLoopSeconds(enabled, startSec, endSec) {
1127
+ const startTick = this._tempoMap.secondsToTicks(startSec);
1128
+ const endTick = this._tempoMap.secondsToTicks(endSec);
1129
+ this.setLoop(enabled, startTick, endTick);
1130
+ }
1131
+ /** Convenience — sets loop in samples */
1132
+ setLoopSamples(enabled, startSample, endSample) {
1133
+ if (enabled && (!Number.isFinite(startSample) || !Number.isFinite(endSample))) {
1042
1134
  console.warn(
1043
- "[waveform-playlist] Transport.setLoop: start (" + start + ") must be less than end (" + end + ")"
1135
+ "[waveform-playlist] Transport.setLoopSamples: non-finite sample values (" + startSample + ", " + endSample + ")"
1044
1136
  );
1045
1137
  return;
1046
1138
  }
1047
- this._scheduler.setLoop(enabled, start, end);
1048
- this._clipPlayer.setLoop(enabled, start, end);
1139
+ if (enabled && startSample >= endSample) {
1140
+ console.warn(
1141
+ "[waveform-playlist] Transport.setLoopSamples: startSample (" + startSample + ") must be less than endSample (" + endSample + ")"
1142
+ );
1143
+ return;
1144
+ }
1145
+ const startTick = this._sampleTimeline.samplesToTicks(startSample);
1146
+ const endTick = this._sampleTimeline.samplesToTicks(endSample);
1147
+ this._loopEnabled = enabled;
1148
+ this._loopStartSeconds = this._tempoMap.ticksToSeconds(startTick);
1149
+ this._clipPlayer.setLoopSamples(enabled, startSample, endSample);
1150
+ this._scheduler.setLoop(enabled, startTick, endTick);
1049
1151
  this._emit("loop");
1050
1152
  }
1051
1153
  // --- Tempo ---
@@ -1169,7 +1271,12 @@ var Transport = class _Transport {
1169
1271
  this._masterNode = new MasterNode(audioContext);
1170
1272
  this._masterNode.output.connect(audioContext.destination);
1171
1273
  const toAudioTime = (transportTime) => this._clock.toAudioTime(transportTime);
1172
- this._clipPlayer = new ClipPlayer(audioContext, this._sampleTimeline, toAudioTime);
1274
+ this._clipPlayer = new ClipPlayer(
1275
+ audioContext,
1276
+ this._sampleTimeline,
1277
+ this._tempoMap,
1278
+ toAudioTime
1279
+ );
1173
1280
  this._metronomePlayer = new MetronomePlayer(
1174
1281
  audioContext,
1175
1282
  this._tempoMap,
@@ -1272,7 +1379,7 @@ var NativePlayoutAdapter = class {
1272
1379
  this._transport.setTrackPan(trackId, pan);
1273
1380
  }
1274
1381
  setLoop(enabled, start, end) {
1275
- this._transport.setLoop(enabled, start, end);
1382
+ this._transport.setLoopSeconds(enabled, start, end);
1276
1383
  }
1277
1384
  dispose() {
1278
1385
  this._transport.dispose();