@dawcore/transport 0.0.9 → 0.0.11

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.d.mts CHANGED
@@ -142,6 +142,10 @@ declare class TempoMap {
142
142
  private _ppqn;
143
143
  private _entries;
144
144
  constructor(ppqn?: number, initialBpm?: number);
145
+ /** A non-finite or non-positive BPM silently corrupts the secondsAtTick
146
+ * cache (Infinity, or non-monotonic values that break the binary search
147
+ * in secondsToTicks) — reject it at the boundary instead. */
148
+ private static _validateBpm;
145
149
  getTempo(atTick?: Tick): number;
146
150
  setTempo(bpm: number, atTick?: Tick, options?: SetTempoOptions): void;
147
151
  ticksToSeconds(ticks: Tick): number;
@@ -299,17 +303,18 @@ interface ClipEvent extends SchedulerEvent {
299
303
  trackId: string;
300
304
  clipId: string;
301
305
  audioBuffer: AudioBuffer;
302
- /** Clip position on timeline (integer samples) */
306
+ /** Clip position on timeline (integer samples, at the TIMELINE sample rate) */
303
307
  startSample: Sample;
304
- /** Offset into audioBuffer (integer samples) */
308
+ /** Offset into audioBuffer (integer samples, at the BUFFER's own sample
309
+ * rate — they index the buffer, not the timeline) */
305
310
  offsetSamples: Sample;
306
- /** Duration to play (integer samples) */
311
+ /** Duration to play (integer samples, at the BUFFER's own sample rate) */
307
312
  durationSamples: Sample;
308
313
  /** Clip gain multiplier */
309
314
  gain: number;
310
- /** Fade in duration (integer samples) */
315
+ /** Fade in duration (integer samples, at the TIMELINE sample rate) */
311
316
  fadeInDurationSamples: Sample;
312
- /** Fade out duration (integer samples) */
317
+ /** Fade out duration (integer samples, at the TIMELINE sample rate) */
313
318
  fadeOutDurationSamples: Sample;
314
319
  }
315
320
  declare class ClipPlayer implements SchedulerListener<ClipEvent> {
@@ -467,6 +472,9 @@ declare class Transport {
467
472
  isCountingIn(): boolean;
468
473
  connectTrackOutput(trackId: string, node: AudioNode): void;
469
474
  disconnectTrackOutput(trackId: string): void;
475
+ /** The master output AudioNode. Connect your own nodes (analyzers, recorders, etc.)
476
+ * in parallel. The transport already routes this to audioContext.destination. */
477
+ get masterOutputNode(): AudioNode;
470
478
  on<K extends TransportEventType>(event: K, cb: TransportEvents[K]): void;
471
479
  off<K extends TransportEventType>(event: K, cb: TransportEvents[K]): void;
472
480
  dispose(): void;
@@ -515,6 +523,7 @@ declare class NativePlayoutAdapter implements PlayoutAdapter {
515
523
  setMeter(numerator: number, denominator: number, atTick?: number): void;
516
524
  ticksToSeconds(tick: number): number;
517
525
  secondsToTicks(seconds: number): number;
526
+ get masterOutputNode(): AudioNode;
518
527
  dispose(): void;
519
528
  }
520
529
 
package/dist/index.d.ts CHANGED
@@ -142,6 +142,10 @@ declare class TempoMap {
142
142
  private _ppqn;
143
143
  private _entries;
144
144
  constructor(ppqn?: number, initialBpm?: number);
145
+ /** A non-finite or non-positive BPM silently corrupts the secondsAtTick
146
+ * cache (Infinity, or non-monotonic values that break the binary search
147
+ * in secondsToTicks) — reject it at the boundary instead. */
148
+ private static _validateBpm;
145
149
  getTempo(atTick?: Tick): number;
146
150
  setTempo(bpm: number, atTick?: Tick, options?: SetTempoOptions): void;
147
151
  ticksToSeconds(ticks: Tick): number;
@@ -299,17 +303,18 @@ interface ClipEvent extends SchedulerEvent {
299
303
  trackId: string;
300
304
  clipId: string;
301
305
  audioBuffer: AudioBuffer;
302
- /** Clip position on timeline (integer samples) */
306
+ /** Clip position on timeline (integer samples, at the TIMELINE sample rate) */
303
307
  startSample: Sample;
304
- /** Offset into audioBuffer (integer samples) */
308
+ /** Offset into audioBuffer (integer samples, at the BUFFER's own sample
309
+ * rate — they index the buffer, not the timeline) */
305
310
  offsetSamples: Sample;
306
- /** Duration to play (integer samples) */
311
+ /** Duration to play (integer samples, at the BUFFER's own sample rate) */
307
312
  durationSamples: Sample;
308
313
  /** Clip gain multiplier */
309
314
  gain: number;
310
- /** Fade in duration (integer samples) */
315
+ /** Fade in duration (integer samples, at the TIMELINE sample rate) */
311
316
  fadeInDurationSamples: Sample;
312
- /** Fade out duration (integer samples) */
317
+ /** Fade out duration (integer samples, at the TIMELINE sample rate) */
313
318
  fadeOutDurationSamples: Sample;
314
319
  }
315
320
  declare class ClipPlayer implements SchedulerListener<ClipEvent> {
@@ -467,6 +472,9 @@ declare class Transport {
467
472
  isCountingIn(): boolean;
468
473
  connectTrackOutput(trackId: string, node: AudioNode): void;
469
474
  disconnectTrackOutput(trackId: string): void;
475
+ /** The master output AudioNode. Connect your own nodes (analyzers, recorders, etc.)
476
+ * in parallel. The transport already routes this to audioContext.destination. */
477
+ get masterOutputNode(): AudioNode;
470
478
  on<K extends TransportEventType>(event: K, cb: TransportEvents[K]): void;
471
479
  off<K extends TransportEventType>(event: K, cb: TransportEvents[K]): void;
472
480
  dispose(): void;
@@ -515,6 +523,7 @@ declare class NativePlayoutAdapter implements PlayoutAdapter {
515
523
  setMeter(numerator: number, denominator: number, atTick?: number): void;
516
524
  ticksToSeconds(tick: number): number;
517
525
  secondsToTicks(seconds: number): number;
526
+ get masterOutputNode(): AudioNode;
518
527
  dispose(): void;
519
528
  }
520
529
 
package/dist/index.js CHANGED
@@ -261,15 +261,27 @@ function curveNormalizedAt(x, slope) {
261
261
  const p = Math.max(CURVE_EPSILON, Math.min(1 - CURVE_EPSILON, slope));
262
262
  return p * p / (1 - p * 2) * (Math.pow((1 - p) / p, 2 * x) - 1);
263
263
  }
264
- var TempoMap = class {
264
+ var TempoMap = class _TempoMap {
265
265
  constructor(ppqn = 960, initialBpm = 120) {
266
+ _TempoMap._validateBpm(initialBpm);
266
267
  this._ppqn = ppqn;
267
268
  this._entries = [{ tick: 0, bpm: initialBpm, interpolation: "step", secondsAtTick: 0 }];
268
269
  }
270
+ /** A non-finite or non-positive BPM silently corrupts the secondsAtTick
271
+ * cache (Infinity, or non-monotonic values that break the binary search
272
+ * in secondsToTicks) — reject it at the boundary instead. */
273
+ static _validateBpm(bpm) {
274
+ if (!Number.isFinite(bpm) || bpm <= 0) {
275
+ throw new Error(
276
+ "[waveform-playlist] TempoMap: bpm must be a finite positive number, got " + bpm
277
+ );
278
+ }
279
+ }
269
280
  getTempo(atTick = 0) {
270
281
  return this._getTempoAt(atTick);
271
282
  }
272
283
  setTempo(bpm, atTick = 0, options) {
284
+ _TempoMap._validateBpm(bpm);
273
285
  const interpolation = options?.interpolation ?? "step";
274
286
  if (typeof interpolation === "object" && interpolation.type === "curve") {
275
287
  const s = interpolation.slope;
@@ -507,6 +519,7 @@ function isPowerOf2(n) {
507
519
  var MeterMap = class {
508
520
  constructor(ppqn, numerator = 4, denominator = 4) {
509
521
  this._ppqn = ppqn;
522
+ this._validateMeter(numerator, denominator);
510
523
  this._entries = [{ tick: 0, numerator, denominator, barAtTick: 0 }];
511
524
  }
512
525
  get ppqn() {
@@ -691,7 +704,7 @@ var MasterNode = class {
691
704
  try {
692
705
  this._gainNode.disconnect();
693
706
  } catch (err) {
694
- console.warn("[waveform-playlist] MasterNode.dispose: error disconnecting:", String(err));
707
+ console.warn("[waveform-playlist] MasterNode.dispose: error disconnecting: " + String(err));
695
708
  }
696
709
  }
697
710
  };
@@ -800,11 +813,19 @@ var ClipPlayer = class {
800
813
  const clipTick = clip.startTick !== void 0 ? clip.startTick : this._sampleTimeline.samplesToTicks(clip.startSample);
801
814
  if (clipTick < fromTick) continue;
802
815
  if (clipTick >= toTick) continue;
803
- const fadeInDurationSamples = clip.fadeIn ? clip.fadeIn.duration ?? 0 : 0;
804
- const fadeOutDurationSamples = clip.fadeOut ? clip.fadeOut.duration ?? 0 : 0;
816
+ const timelineRate = this._sampleTimeline.sampleRate;
817
+ const bufferRate = clip.audioBuffer.sampleRate;
818
+ const fadeInDurationSamples = clip.fadeIn ? Math.round((clip.fadeIn.duration ?? 0) * timelineRate) : 0;
819
+ const fadeOutDurationSamples = clip.fadeOut ? Math.round((clip.fadeOut.duration ?? 0) * timelineRate) : 0;
805
820
  let durationSamples = clip.durationSamples;
806
- if (this._loopEnabled && clip.startSample + durationSamples > this._loopEndSamples) {
807
- durationSamples = this._loopEndSamples - clip.startSample;
821
+ if (this._loopEnabled) {
822
+ const durationTimelineSamples = Math.round(
823
+ clip.durationSamples / bufferRate * timelineRate
824
+ );
825
+ const allowedTimelineSamples = this._loopEndSamples - clip.startSample;
826
+ if (durationTimelineSamples > allowedTimelineSamples) {
827
+ durationSamples = Math.round(allowedTimelineSamples / timelineRate * bufferRate);
828
+ }
808
829
  }
809
830
  events.push({
810
831
  trackId,
@@ -830,9 +851,9 @@ var ClipPlayer = class {
830
851
  );
831
852
  return;
832
853
  }
833
- const sampleRate = this._sampleTimeline.sampleRate;
834
- const offsetSeconds = event.offsetSamples / sampleRate;
835
- const durationSeconds = event.durationSamples / sampleRate;
854
+ const bufferRate = event.audioBuffer.sampleRate;
855
+ const offsetSeconds = event.offsetSamples / bufferRate;
856
+ const durationSeconds = event.durationSamples / bufferRate;
836
857
  if (offsetSeconds >= event.audioBuffer.duration) {
837
858
  console.warn(
838
859
  "[waveform-playlist] ClipPlayer.consume: offset (" + offsetSeconds + "s) exceeds audioBuffer.duration (" + event.audioBuffer.duration + 's) for clipId "' + event.clipId + '" \u2014 clip will not play'
@@ -845,8 +866,9 @@ var ClipPlayer = class {
845
866
  const when = this._toAudioTime(transportSeconds);
846
867
  const gainNode = this._audioContext.createGain();
847
868
  gainNode.gain.value = event.gain;
848
- let fadeIn = event.fadeInDurationSamples / sampleRate;
849
- let fadeOut = event.fadeOutDurationSamples / sampleRate;
869
+ const timelineRate = this._sampleTimeline.sampleRate;
870
+ let fadeIn = event.fadeInDurationSamples / timelineRate;
871
+ let fadeOut = event.fadeOutDurationSamples / timelineRate;
850
872
  if (fadeIn + fadeOut > durationSeconds) {
851
873
  const ratio = durationSeconds / (fadeIn + fadeOut);
852
874
  fadeIn *= ratio;
@@ -886,16 +908,21 @@ var ClipPlayer = class {
886
908
  if (!clip.audioBuffer) continue;
887
909
  const clipTick = clip.startTick !== void 0 ? clip.startTick : this._sampleTimeline.samplesToTicks(clip.startSample);
888
910
  if (clipTick >= newTick) continue;
889
- const clipEndSample = clip.startSample + clip.durationSamples;
911
+ const timelineRate = this._sampleTimeline.sampleRate;
912
+ const bufferRate = clip.audioBuffer.sampleRate;
913
+ const bufToTimeline = (n) => Math.round(n / bufferRate * timelineRate);
914
+ const timelineToBuf = (n) => Math.round(n / timelineRate * bufferRate);
915
+ const clipEndSample = clip.startSample + bufToTimeline(clip.durationSamples);
890
916
  if (clipEndSample <= newSample) continue;
891
917
  const offsetIntoClipSamples = newSample - clip.startSample;
892
- const offsetSamples = clip.offsetSamples + offsetIntoClipSamples;
893
- let durationSamples = clipEndSample - newSample;
894
- if (this._loopEnabled && newSample + durationSamples > this._loopEndSamples) {
895
- durationSamples = this._loopEndSamples - newSample;
918
+ const offsetSamples = clip.offsetSamples + timelineToBuf(offsetIntoClipSamples);
919
+ let remainingTimelineSamples = clipEndSample - newSample;
920
+ if (this._loopEnabled && newSample + remainingTimelineSamples > this._loopEndSamples) {
921
+ remainingTimelineSamples = this._loopEndSamples - newSample;
896
922
  }
897
- if (durationSamples <= 0) continue;
898
- const fadeOutDurationSamples = clip.fadeOut ? clip.fadeOut.duration ?? 0 : 0;
923
+ if (remainingTimelineSamples <= 0) continue;
924
+ const durationSamples = timelineToBuf(remainingTimelineSamples);
925
+ const fadeOutDurationSamples = clip.fadeOut ? Math.round((clip.fadeOut.duration ?? 0) * timelineRate) : 0;
899
926
  this.consume({
900
927
  trackId,
901
928
  clipId: clip.id,
@@ -1612,6 +1639,11 @@ var _Transport = class _Transport {
1612
1639
  }
1613
1640
  trackNode.disconnectEffects();
1614
1641
  }
1642
+ /** The master output AudioNode. Connect your own nodes (analyzers, recorders, etc.)
1643
+ * in parallel. The transport already routes this to audioContext.destination. */
1644
+ get masterOutputNode() {
1645
+ return this._masterNode.output;
1646
+ }
1615
1647
  // --- Events ---
1616
1648
  on(event, cb) {
1617
1649
  if (!this._listeners.has(event)) {
@@ -1924,6 +1956,9 @@ var NativePlayoutAdapter = class {
1924
1956
  secondsToTicks(seconds) {
1925
1957
  return this._transport.timeToTick(seconds);
1926
1958
  }
1959
+ get masterOutputNode() {
1960
+ return this._transport.masterOutputNode;
1961
+ }
1927
1962
  dispose() {
1928
1963
  this._transport.dispose();
1929
1964
  }