@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/README.md +8 -4
- package/dist/index.d.mts +110 -69
- package/dist/index.d.ts +110 -69
- package/dist/index.js +203 -96
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +203 -96
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
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
|
-
|
|
105
|
-
|
|
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:
|
|
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 =
|
|
113
|
-
this._loopEnd =
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
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 =
|
|
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?.(
|
|
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 (
|
|
142
|
-
this._generateAndConsume(this._rightEdge,
|
|
143
|
-
this._rightEdge =
|
|
163
|
+
if (targetTick > this._rightEdge) {
|
|
164
|
+
this._generateAndConsume(this._rightEdge, targetTick);
|
|
165
|
+
this._rightEdge = targetTick;
|
|
144
166
|
}
|
|
145
167
|
}
|
|
146
|
-
_generateAndConsume(
|
|
168
|
+
_generateAndConsume(fromTick, toTick) {
|
|
147
169
|
for (const listener of this._listeners) {
|
|
148
170
|
try {
|
|
149
|
-
const events = listener.generate(
|
|
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.
|
|
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
|
-
|
|
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.
|
|
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(
|
|
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
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
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
|
-
|
|
605
|
-
|
|
606
|
-
|
|
655
|
+
tick: clipTick,
|
|
656
|
+
startSample: clipStartSample,
|
|
657
|
+
offsetSamples: clip.offsetSamples,
|
|
658
|
+
durationSamples,
|
|
607
659
|
gain: clip.gain,
|
|
608
|
-
|
|
609
|
-
|
|
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
|
-
|
|
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
|
|
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.
|
|
632
|
-
let fadeOut = event.
|
|
633
|
-
if (fadeIn + fadeOut >
|
|
634
|
-
const ratio =
|
|
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 +
|
|
702
|
+
const fadeOutStart = when + durationSeconds - fadeOut;
|
|
644
703
|
gainNode.gain.setValueAtTime(event.gain, fadeOutStart);
|
|
645
|
-
gainNode.gain.linearRampToValueAtTime(0, when +
|
|
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,
|
|
720
|
+
source.start(when, offsetSeconds, durationSeconds);
|
|
662
721
|
}
|
|
663
|
-
onPositionJump(
|
|
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
|
|
670
|
-
const
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
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
|
-
|
|
683
|
-
|
|
684
|
-
|
|
744
|
+
tick: newTick,
|
|
745
|
+
startSample: newSample,
|
|
746
|
+
offsetSamples,
|
|
747
|
+
durationSamples,
|
|
685
748
|
gain: clip.gain,
|
|
686
|
-
|
|
687
|
-
|
|
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(
|
|
823
|
+
generate(fromTick, toTick) {
|
|
761
824
|
if (!this._enabled || !this._accentBuffer || !this._normalBuffer) {
|
|
762
825
|
return [];
|
|
763
826
|
}
|
|
764
827
|
const events = [];
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
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 <
|
|
772
|
-
const
|
|
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(
|
|
837
|
+
beatSize = this._meterMap.ticksPerBeat(tickPos);
|
|
776
838
|
}
|
|
777
|
-
const isAccent = this._meterMap.isBarBoundary(
|
|
778
|
-
const transportTime = this._tempoMap.ticksToSeconds(tick);
|
|
839
|
+
const isAccent = this._meterMap.isBarBoundary(tickPos);
|
|
779
840
|
events.push({
|
|
780
|
-
|
|
841
|
+
tick: tickPos,
|
|
781
842
|
isAccent,
|
|
782
843
|
buffer: isAccent ? this._accentBuffer : this._normalBuffer
|
|
783
844
|
});
|
|
784
|
-
beatSize = this._meterMap.ticksPerBeat(
|
|
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
|
-
|
|
866
|
+
const transportTime = this._tempoMap.ticksToSeconds(event.tick);
|
|
867
|
+
source.start(this._toAudioTime(transportTime));
|
|
806
868
|
}
|
|
807
|
-
onPositionJump(
|
|
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.
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
1041
|
-
|
|
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.
|
|
1135
|
+
"[waveform-playlist] Transport.setLoopSamples: non-finite sample values (" + startSample + ", " + endSample + ")"
|
|
1044
1136
|
);
|
|
1045
1137
|
return;
|
|
1046
1138
|
}
|
|
1047
|
-
|
|
1048
|
-
|
|
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(
|
|
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.
|
|
1382
|
+
this._transport.setLoopSeconds(enabled, start, end);
|
|
1276
1383
|
}
|
|
1277
1384
|
dispose() {
|
|
1278
1385
|
this._transport.dispose();
|