@dawcore/transport 0.0.11 → 0.0.13
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 +41 -10
- package/dist/index.d.mts +45 -2
- package/dist/index.d.ts +45 -2
- package/dist/index.js +96 -3
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +96 -3
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -2
package/dist/index.mjs
CHANGED
|
@@ -212,7 +212,7 @@ var SampleTimeline = class {
|
|
|
212
212
|
"[waveform-playlist] SampleTimeline: tempoMap not set \u2014 call setTempoMap() first"
|
|
213
213
|
);
|
|
214
214
|
}
|
|
215
|
-
return this._tempoMap.secondsToTicks(samples / this._sampleRate);
|
|
215
|
+
return Math.round(this._tempoMap.secondsToTicks(samples / this._sampleRate));
|
|
216
216
|
}
|
|
217
217
|
};
|
|
218
218
|
|
|
@@ -243,6 +243,11 @@ var TempoMap = class _TempoMap {
|
|
|
243
243
|
getTempo(atTick = 0) {
|
|
244
244
|
return this._getTempoAt(atTick);
|
|
245
245
|
}
|
|
246
|
+
/** Number of tempo entries in the map. Always >= 1 — the tick-0 entry is
|
|
247
|
+
* permanent. Used by Transport.setTempo to detect multi-entry maps. */
|
|
248
|
+
get entryCount() {
|
|
249
|
+
return this._entries.length;
|
|
250
|
+
}
|
|
246
251
|
setTempo(bpm, atTick = 0, options) {
|
|
247
252
|
_TempoMap._validateBpm(bpm);
|
|
248
253
|
const interpolation = options?.interpolation ?? "step";
|
|
@@ -321,6 +326,18 @@ var TempoMap = class _TempoMap {
|
|
|
321
326
|
const first = this._entries[0];
|
|
322
327
|
this._entries = [{ tick: 0, bpm: first.bpm, interpolation: "step", secondsAtTick: 0 }];
|
|
323
328
|
}
|
|
329
|
+
/** Remove the tempo entry at exactly `atTick`, if one exists. The tick-0
|
|
330
|
+
* entry is permanent (a map must always have a tempo) — removing it is a
|
|
331
|
+
* no-op, matching removeMeter's treatment of the initial meter. The
|
|
332
|
+
* seconds cache is recomputed from the removal point, the same partial
|
|
333
|
+
* update setTempo uses. */
|
|
334
|
+
removeTempo(atTick) {
|
|
335
|
+
if (atTick === 0) return;
|
|
336
|
+
const i = this._entries.findIndex((e) => e.tick === atTick);
|
|
337
|
+
if (i === -1) return;
|
|
338
|
+
this._entries = [...this._entries.slice(0, i), ...this._entries.slice(i + 1)];
|
|
339
|
+
this._recomputeCache(i);
|
|
340
|
+
}
|
|
324
341
|
/** Get the interpolated BPM at a tick position */
|
|
325
342
|
_getTempoAt(atTick) {
|
|
326
343
|
const entryIndex = this._entryIndexAt(atTick);
|
|
@@ -646,12 +663,19 @@ var MeterMap = class {
|
|
|
646
663
|
"[waveform-playlist] MeterMap: denominator must be a power of 2 (1-32), got " + denominator
|
|
647
664
|
);
|
|
648
665
|
}
|
|
666
|
+
if (!Number.isInteger(this._ppqn * 4 / denominator)) {
|
|
667
|
+
throw new Error(
|
|
668
|
+
"[waveform-playlist] MeterMap: ppqn (" + this._ppqn + ") * 4 is not divisible by denominator (" + denominator + ") \u2014 bar boundaries would fall on fractional ticks"
|
|
669
|
+
);
|
|
670
|
+
}
|
|
649
671
|
}
|
|
650
672
|
};
|
|
651
673
|
|
|
652
674
|
// src/audio/master-node.ts
|
|
653
675
|
var MasterNode = class {
|
|
654
676
|
constructor(audioContext) {
|
|
677
|
+
this._destination = null;
|
|
678
|
+
this._effectsInput = null;
|
|
655
679
|
this._gainNode = audioContext.createGain();
|
|
656
680
|
}
|
|
657
681
|
get input() {
|
|
@@ -660,6 +684,37 @@ var MasterNode = class {
|
|
|
660
684
|
get output() {
|
|
661
685
|
return this._gainNode;
|
|
662
686
|
}
|
|
687
|
+
/** Connect the master output to its final destination (audioContext.destination) */
|
|
688
|
+
connectOutput(destination) {
|
|
689
|
+
this._destination = destination;
|
|
690
|
+
this._gainNode.connect(destination);
|
|
691
|
+
}
|
|
692
|
+
/**
|
|
693
|
+
* Insert an effects chain between the master gain and the destination.
|
|
694
|
+
* Only the destination edge is severed (targeted disconnect), so parallel
|
|
695
|
+
* taps on the master output (analyzers, recorders) keep working.
|
|
696
|
+
* The caller is responsible for routing the chain's output onward.
|
|
697
|
+
*/
|
|
698
|
+
connectEffects(effectsInput) {
|
|
699
|
+
if (this._effectsInput) {
|
|
700
|
+
this._gainNode.disconnect(this._effectsInput);
|
|
701
|
+
} else if (this._destination) {
|
|
702
|
+
this._gainNode.disconnect(this._destination);
|
|
703
|
+
}
|
|
704
|
+
this._gainNode.connect(effectsInput);
|
|
705
|
+
this._effectsInput = effectsInput;
|
|
706
|
+
}
|
|
707
|
+
/** Remove the effects chain and restore direct routing to the destination */
|
|
708
|
+
disconnectEffects() {
|
|
709
|
+
if (!this._effectsInput) {
|
|
710
|
+
return;
|
|
711
|
+
}
|
|
712
|
+
this._gainNode.disconnect(this._effectsInput);
|
|
713
|
+
if (this._destination) {
|
|
714
|
+
this._gainNode.connect(this._destination);
|
|
715
|
+
}
|
|
716
|
+
this._effectsInput = null;
|
|
717
|
+
}
|
|
663
718
|
setVolume(value) {
|
|
664
719
|
this._gainNode.gain.value = value;
|
|
665
720
|
}
|
|
@@ -669,6 +724,8 @@ var MasterNode = class {
|
|
|
669
724
|
} catch (err) {
|
|
670
725
|
console.warn("[waveform-playlist] MasterNode.dispose: error disconnecting: " + String(err));
|
|
671
726
|
}
|
|
727
|
+
this._destination = null;
|
|
728
|
+
this._effectsInput = null;
|
|
672
729
|
}
|
|
673
730
|
};
|
|
674
731
|
|
|
@@ -1319,6 +1376,7 @@ var _Transport = class _Transport {
|
|
|
1319
1376
|
this._clipPlayer.onPositionJump(seekTick);
|
|
1320
1377
|
this._timer.start();
|
|
1321
1378
|
}
|
|
1379
|
+
this._emit("seek", { seconds: time });
|
|
1322
1380
|
}
|
|
1323
1381
|
getCurrentTime() {
|
|
1324
1382
|
if (this._countingIn) {
|
|
@@ -1489,12 +1547,22 @@ var _Transport = class _Transport {
|
|
|
1489
1547
|
this._emit("loop");
|
|
1490
1548
|
}
|
|
1491
1549
|
// --- Tempo ---
|
|
1550
|
+
/** Returns true when the tempo was applied, false when a defaulted (no
|
|
1551
|
+
* atTick) write was refused because the tempo map has multiple entries —
|
|
1552
|
+
* pass an explicit atTick to modify a multi-entry map (#407). */
|
|
1492
1553
|
setTempo(bpm, atTick, options) {
|
|
1554
|
+
if (atTick === void 0 && this._tempoMap.entryCount > 1) {
|
|
1555
|
+
console.warn(
|
|
1556
|
+
"[waveform-playlist] Transport.setTempo: refusing defaulted tick-0 write of " + bpm + " BPM \u2014 the tempo map has " + this._tempoMap.entryCount + " entries. Pass an explicit atTick to modify a multi-entry tempo map."
|
|
1557
|
+
);
|
|
1558
|
+
return false;
|
|
1559
|
+
}
|
|
1493
1560
|
this._tempoMap.setTempo(bpm, atTick, options);
|
|
1494
1561
|
if (this._loopEnabled) {
|
|
1495
1562
|
this._loopStartSeconds = this._tempoMap.ticksToSeconds(this._loopStartTick);
|
|
1496
1563
|
}
|
|
1497
1564
|
this._emit("tempochange", { bpm, atTick: atTick ?? 0 });
|
|
1565
|
+
return true;
|
|
1498
1566
|
}
|
|
1499
1567
|
getTempo(atTick) {
|
|
1500
1568
|
return this._tempoMap.getTempo(atTick);
|
|
@@ -1532,6 +1600,17 @@ var _Transport = class _Transport {
|
|
|
1532
1600
|
}
|
|
1533
1601
|
this._emit("tempochange", { bpm: this._tempoMap.getTempo(), atTick: 0 });
|
|
1534
1602
|
}
|
|
1603
|
+
/** Remove the tempo entry at exactly `atTick` (tick 0 is permanent — a
|
|
1604
|
+
* no-op, like removeMeter's initial entry). Mirrors setTempo's loop-cache
|
|
1605
|
+
* invalidation and event; the emitted bpm is the tempo now in force at
|
|
1606
|
+
* the removed position. */
|
|
1607
|
+
removeTempo(atTick) {
|
|
1608
|
+
this._tempoMap.removeTempo(atTick);
|
|
1609
|
+
if (this._loopEnabled) {
|
|
1610
|
+
this._loopStartSeconds = this._tempoMap.ticksToSeconds(this._loopStartTick);
|
|
1611
|
+
}
|
|
1612
|
+
this._emit("tempochange", { bpm: this._tempoMap.getTempo(atTick), atTick });
|
|
1613
|
+
}
|
|
1535
1614
|
barToTick(bar) {
|
|
1536
1615
|
return this._meterMap.barToTick(bar);
|
|
1537
1616
|
}
|
|
@@ -1607,6 +1686,20 @@ var _Transport = class _Transport {
|
|
|
1607
1686
|
get masterOutputNode() {
|
|
1608
1687
|
return this._masterNode.output;
|
|
1609
1688
|
}
|
|
1689
|
+
/**
|
|
1690
|
+
* Insert an effects chain on the master bus, between the master gain and
|
|
1691
|
+
* audioContext.destination. Accepts any AudioNode chain (Tone.js effects,
|
|
1692
|
+
* WAM plugins, native nodes). The caller is responsible for connecting the
|
|
1693
|
+
* chain's output to audioContext.destination. Calling again replaces the
|
|
1694
|
+
* previous chain. Parallel taps on `masterOutputNode` are unaffected.
|
|
1695
|
+
*/
|
|
1696
|
+
connectMasterOutput(node) {
|
|
1697
|
+
this._masterNode.connectEffects(node);
|
|
1698
|
+
}
|
|
1699
|
+
/** Remove the master effects chain and restore direct routing to audioContext.destination. */
|
|
1700
|
+
disconnectMasterOutput() {
|
|
1701
|
+
this._masterNode.disconnectEffects();
|
|
1702
|
+
}
|
|
1610
1703
|
// --- Events ---
|
|
1611
1704
|
on(event, cb) {
|
|
1612
1705
|
if (!this._listeners.has(event)) {
|
|
@@ -1660,7 +1753,7 @@ var _Transport = class _Transport {
|
|
|
1660
1753
|
}
|
|
1661
1754
|
_initAudioGraph(audioContext) {
|
|
1662
1755
|
this._masterNode = new MasterNode(audioContext);
|
|
1663
|
-
this._masterNode.
|
|
1756
|
+
this._masterNode.connectOutput(audioContext.destination);
|
|
1664
1757
|
const toAudioTime = (transportTime) => this._clock.toAudioTime(transportTime);
|
|
1665
1758
|
this._clipPlayer = new ClipPlayer(
|
|
1666
1759
|
audioContext,
|
|
@@ -1904,7 +1997,7 @@ var NativePlayoutAdapter = class {
|
|
|
1904
1997
|
return this._transport.isCountingIn();
|
|
1905
1998
|
}
|
|
1906
1999
|
setTempo(bpm, atTick) {
|
|
1907
|
-
this._transport.setTempo(bpm, atTick !== void 0 ? atTick : void 0);
|
|
2000
|
+
return this._transport.setTempo(bpm, atTick !== void 0 ? atTick : void 0);
|
|
1908
2001
|
}
|
|
1909
2002
|
setMeter(numerator, denominator, atTick) {
|
|
1910
2003
|
this._transport.setMeter(
|