@dawcore/transport 0.0.12 → 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 +26 -0
- package/dist/index.d.ts +26 -0
- package/dist/index.js +51 -1
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +51 -1
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -10,7 +10,7 @@ Native Web Audio transport for multi-track audio scheduling, looping, tempo, and
|
|
|
10
10
|
- **Built-in metronome** — Beat-grid click scheduling with accent on beat 1. Default synthesized click sounds out of the box.
|
|
11
11
|
- **Count-in (pre-roll)** — Configurable bars of click sounds before playback begins. Beat-by-beat events for UI countdown.
|
|
12
12
|
- **Per-track signal chain** — Native GainNode (volume) → StereoPannerNode → GainNode (mute) → effects hook → master output.
|
|
13
|
-
- **Effects plugin hook** — `connectTrackOutput(trackId, node)`
|
|
13
|
+
- **Effects plugin hook** — `connectTrackOutput(trackId, node)` and `connectMasterOutput(node)` insert any `AudioNode` chain (Tone.js effects, WAM plugins, native nodes) per-track or on the master bus.
|
|
14
14
|
- **Type-safe coordinates** — Branded `Tick` and `Sample` types prevent accidentally passing seconds where ticks or samples are expected. Zero runtime cost.
|
|
15
15
|
- **PlayoutAdapter bridge** — `NativePlayoutAdapter` implements the `PlayoutAdapter` interface from `@waveform-playlist/engine`.
|
|
16
16
|
|
|
@@ -55,9 +55,12 @@ import { NativePlayoutAdapter } from '@dawcore/transport';
|
|
|
55
55
|
const audioContext = new AudioContext({ sampleRate: 48000 });
|
|
56
56
|
const adapter = new NativePlayoutAdapter(audioContext);
|
|
57
57
|
|
|
58
|
-
// Use as daw-editor's adapter
|
|
58
|
+
// Use as daw-editor's playout adapter
|
|
59
59
|
const editor = document.querySelector('daw-editor');
|
|
60
|
-
editor.
|
|
60
|
+
editor.adapter = adapter;
|
|
61
|
+
|
|
62
|
+
// Transport-specific APIs stay available on your adapter reference
|
|
63
|
+
adapter.transport.setMetronomeEnabled(true);
|
|
61
64
|
```
|
|
62
65
|
|
|
63
66
|
### Metronome
|
|
@@ -156,6 +159,17 @@ transport.connectTrackOutput('vocals', reverb);
|
|
|
156
159
|
|
|
157
160
|
// Remove effects — restores direct routing to master
|
|
158
161
|
transport.disconnectTrackOutput('vocals');
|
|
162
|
+
|
|
163
|
+
// Master bus effects — inserted between master gain and destination
|
|
164
|
+
const compressor = audioContext.createDynamicsCompressor();
|
|
165
|
+
compressor.connect(audioContext.destination);
|
|
166
|
+
|
|
167
|
+
transport.connectMasterOutput(compressor);
|
|
168
|
+
|
|
169
|
+
// Remove master effects — restores direct routing to destination.
|
|
170
|
+
// Parallel taps on transport.masterOutputNode (analyzers, recorders)
|
|
171
|
+
// are unaffected by connect/disconnect.
|
|
172
|
+
transport.disconnectMasterOutput();
|
|
159
173
|
```
|
|
160
174
|
|
|
161
175
|
## API
|
|
@@ -205,7 +219,8 @@ new Transport(audioContext: AudioContext, options?: TransportOptions)
|
|
|
205
219
|
- `setLoopSamples(enabled, startSample: Sample, endSample: Sample)` — Set loop region in samples (convenience)
|
|
206
220
|
|
|
207
221
|
**Tempo & Meter:**
|
|
208
|
-
- `setTempo(bpm, atTick?, options?)` / `getTempo(atTick?: Tick)` — options: `{ interpolation: 'step' | 'linear' | { type: 'curve', slope } }`
|
|
222
|
+
- `setTempo(bpm, atTick?, options?)` / `getTempo(atTick?: Tick)` — options: `{ interpolation: 'step' | 'linear' | { type: 'curve', slope } }`. Returns `boolean`: a defaulted `atTick` is the single-BPM convenience path and is refused (with a warning) when the tempo map has more than one entry — pass an explicit `atTick` to modify a tempo curve.
|
|
223
|
+
- `removeTempo(atTick: Tick)` — remove the tempo entry at a tick (the tick-0 entry cannot be removed)
|
|
209
224
|
- `clearTempos()` — remove all tempo entries
|
|
210
225
|
- `setMeter(numerator, denominator, atTick?: Tick)` / `getMeter(atTick?: Tick)`
|
|
211
226
|
- `removeMeter(atTick: Tick)` / `clearMeters()`
|
|
@@ -224,12 +239,16 @@ new Transport(audioContext: AudioContext, options?: TransportOptions)
|
|
|
224
239
|
- `isCountingIn()` — whether count-in is active
|
|
225
240
|
|
|
226
241
|
**Effects:**
|
|
227
|
-
- `connectTrackOutput(trackId, node)` — Insert effects chain
|
|
228
|
-
- `disconnectTrackOutput(trackId)` — Remove effects chain
|
|
242
|
+
- `connectTrackOutput(trackId, node)` — Insert per-track effects chain
|
|
243
|
+
- `disconnectTrackOutput(trackId)` — Remove per-track effects chain
|
|
244
|
+
- `connectMasterOutput(node)` — Insert master bus effects chain
|
|
245
|
+
- `disconnectMasterOutput()` — Remove master bus effects chain
|
|
246
|
+
- `masterOutputNode` (getter) — Master gain node, for parallel taps (analyzers, recorders) that should survive chain connect/disconnect
|
|
229
247
|
|
|
230
248
|
**Events:**
|
|
231
249
|
- `on(event, callback)` / `off(event, callback)`
|
|
232
|
-
- Events: `play`, `pause`, `stop`, `loop`, `tempochange`, `meterchange`, `countIn`, `countInEnd`
|
|
250
|
+
- Events: `play`, `pause`, `stop`, `seek`, `loop`, `tempochange`, `meterchange`, `countIn`, `countInEnd`
|
|
251
|
+
- `seek` payload: `{ seconds: number }`
|
|
233
252
|
- `tempochange` payload: `{ bpm: number, atTick: Tick }`
|
|
234
253
|
- `meterchange` payload: `{ numerator: number, denominator: number, atTick: Tick }`
|
|
235
254
|
- `countIn` payload: `{ beat: number, totalBeats: number }`
|
|
@@ -245,15 +264,27 @@ new NativePlayoutAdapter(audioContext: AudioContext, options?: TransportOptions)
|
|
|
245
264
|
|
|
246
265
|
Implements `PlayoutAdapter` from `@waveform-playlist/engine`. All methods delegate to the internal `Transport` instance.
|
|
247
266
|
|
|
248
|
-
- `adapter.transport` — Direct access to the `Transport` for tempo, metronome, and effects APIs
|
|
267
|
+
- `adapter.transport` — Direct access to the `Transport` for tempo, metronome, count-in, and effects APIs
|
|
268
|
+
- `adapter.ppqn` — Tick resolution, read by the engine on construction
|
|
269
|
+
- `adapter.masterOutputNode` — Master gain node for parallel taps (analyzers, recorders)
|
|
270
|
+
- `adapter.init()` — Resumes a suspended AudioContext and waits for the hardware pipeline to warm up (Safari needs this before clips scheduled at time 0 play on time)
|
|
271
|
+
- `setTempo(bpm, atTick?)`, `setMeter(numerator, denominator, atTick?)`, `ticksToSeconds(tick)`, `secondsToTicks(seconds)` — tempo/meter surface the engine uses for tick-based timeline math
|
|
272
|
+
|
|
273
|
+
## Examples
|
|
274
|
+
|
|
275
|
+
[`examples/dawcore-native/`](https://github.com/naomiaro/waveform-playlist/tree/main/examples/dawcore-native) pairs this transport with the `@dawcore/components` editor: metronome, tempo automation, mixed meter, beat-map grids, effects, and recording pages (`pnpm example:dawcore-native`).
|
|
249
276
|
|
|
250
277
|
## Architecture
|
|
251
278
|
|
|
252
|
-
See [TRANSPORT.md](
|
|
279
|
+
See [TRANSPORT.md](https://github.com/naomiaro/waveform-playlist/blob/main/packages/transport/TRANSPORT.md) for the full architecture guide.
|
|
253
280
|
|
|
254
281
|
## How It Works
|
|
255
282
|
|
|
256
|
-
See [EDUCATIONAL.md](
|
|
283
|
+
See [EDUCATIONAL.md](https://github.com/naomiaro/waveform-playlist/blob/main/packages/transport/EDUCATIONAL.md) for an in-depth explanation of the math and timing models behind audio transport systems.
|
|
284
|
+
|
|
285
|
+
## Documentation
|
|
286
|
+
|
|
287
|
+
Full guides at [naomiaro.github.io/waveform-playlist](https://naomiaro.github.io/waveform-playlist/).
|
|
257
288
|
|
|
258
289
|
## License
|
|
259
290
|
|
package/dist/index.d.mts
CHANGED
|
@@ -281,9 +281,22 @@ declare class MeterMap {
|
|
|
281
281
|
|
|
282
282
|
declare class MasterNode {
|
|
283
283
|
private _gainNode;
|
|
284
|
+
private _destination;
|
|
285
|
+
private _effectsInput;
|
|
284
286
|
constructor(audioContext: AudioContext);
|
|
285
287
|
get input(): AudioNode;
|
|
286
288
|
get output(): AudioNode;
|
|
289
|
+
/** Connect the master output to its final destination (audioContext.destination) */
|
|
290
|
+
connectOutput(destination: AudioNode): void;
|
|
291
|
+
/**
|
|
292
|
+
* Insert an effects chain between the master gain and the destination.
|
|
293
|
+
* Only the destination edge is severed (targeted disconnect), so parallel
|
|
294
|
+
* taps on the master output (analyzers, recorders) keep working.
|
|
295
|
+
* The caller is responsible for routing the chain's output onward.
|
|
296
|
+
*/
|
|
297
|
+
connectEffects(effectsInput: AudioNode): void;
|
|
298
|
+
/** Remove the effects chain and restore direct routing to the destination */
|
|
299
|
+
disconnectEffects(): void;
|
|
287
300
|
setVolume(value: number): void;
|
|
288
301
|
dispose(): void;
|
|
289
302
|
}
|
|
@@ -393,6 +406,9 @@ interface TransportEvents {
|
|
|
393
406
|
pause: () => void;
|
|
394
407
|
stop: () => void;
|
|
395
408
|
loop: () => void;
|
|
409
|
+
seek: (event: {
|
|
410
|
+
seconds: number;
|
|
411
|
+
}) => void;
|
|
396
412
|
tempochange: (event: TempoChangeEventData) => void;
|
|
397
413
|
meterchange: (event: MeterChangeEventData) => void;
|
|
398
414
|
countIn: (event: CountInEventData) => void;
|
|
@@ -492,6 +508,16 @@ declare class Transport {
|
|
|
492
508
|
/** The master output AudioNode. Connect your own nodes (analyzers, recorders, etc.)
|
|
493
509
|
* in parallel. The transport already routes this to audioContext.destination. */
|
|
494
510
|
get masterOutputNode(): AudioNode;
|
|
511
|
+
/**
|
|
512
|
+
* Insert an effects chain on the master bus, between the master gain and
|
|
513
|
+
* audioContext.destination. Accepts any AudioNode chain (Tone.js effects,
|
|
514
|
+
* WAM plugins, native nodes). The caller is responsible for connecting the
|
|
515
|
+
* chain's output to audioContext.destination. Calling again replaces the
|
|
516
|
+
* previous chain. Parallel taps on `masterOutputNode` are unaffected.
|
|
517
|
+
*/
|
|
518
|
+
connectMasterOutput(node: AudioNode): void;
|
|
519
|
+
/** Remove the master effects chain and restore direct routing to audioContext.destination. */
|
|
520
|
+
disconnectMasterOutput(): void;
|
|
495
521
|
on<K extends TransportEventType>(event: K, cb: TransportEvents[K]): void;
|
|
496
522
|
off<K extends TransportEventType>(event: K, cb: TransportEvents[K]): void;
|
|
497
523
|
dispose(): void;
|
package/dist/index.d.ts
CHANGED
|
@@ -281,9 +281,22 @@ declare class MeterMap {
|
|
|
281
281
|
|
|
282
282
|
declare class MasterNode {
|
|
283
283
|
private _gainNode;
|
|
284
|
+
private _destination;
|
|
285
|
+
private _effectsInput;
|
|
284
286
|
constructor(audioContext: AudioContext);
|
|
285
287
|
get input(): AudioNode;
|
|
286
288
|
get output(): AudioNode;
|
|
289
|
+
/** Connect the master output to its final destination (audioContext.destination) */
|
|
290
|
+
connectOutput(destination: AudioNode): void;
|
|
291
|
+
/**
|
|
292
|
+
* Insert an effects chain between the master gain and the destination.
|
|
293
|
+
* Only the destination edge is severed (targeted disconnect), so parallel
|
|
294
|
+
* taps on the master output (analyzers, recorders) keep working.
|
|
295
|
+
* The caller is responsible for routing the chain's output onward.
|
|
296
|
+
*/
|
|
297
|
+
connectEffects(effectsInput: AudioNode): void;
|
|
298
|
+
/** Remove the effects chain and restore direct routing to the destination */
|
|
299
|
+
disconnectEffects(): void;
|
|
287
300
|
setVolume(value: number): void;
|
|
288
301
|
dispose(): void;
|
|
289
302
|
}
|
|
@@ -393,6 +406,9 @@ interface TransportEvents {
|
|
|
393
406
|
pause: () => void;
|
|
394
407
|
stop: () => void;
|
|
395
408
|
loop: () => void;
|
|
409
|
+
seek: (event: {
|
|
410
|
+
seconds: number;
|
|
411
|
+
}) => void;
|
|
396
412
|
tempochange: (event: TempoChangeEventData) => void;
|
|
397
413
|
meterchange: (event: MeterChangeEventData) => void;
|
|
398
414
|
countIn: (event: CountInEventData) => void;
|
|
@@ -492,6 +508,16 @@ declare class Transport {
|
|
|
492
508
|
/** The master output AudioNode. Connect your own nodes (analyzers, recorders, etc.)
|
|
493
509
|
* in parallel. The transport already routes this to audioContext.destination. */
|
|
494
510
|
get masterOutputNode(): AudioNode;
|
|
511
|
+
/**
|
|
512
|
+
* Insert an effects chain on the master bus, between the master gain and
|
|
513
|
+
* audioContext.destination. Accepts any AudioNode chain (Tone.js effects,
|
|
514
|
+
* WAM plugins, native nodes). The caller is responsible for connecting the
|
|
515
|
+
* chain's output to audioContext.destination. Calling again replaces the
|
|
516
|
+
* previous chain. Parallel taps on `masterOutputNode` are unaffected.
|
|
517
|
+
*/
|
|
518
|
+
connectMasterOutput(node: AudioNode): void;
|
|
519
|
+
/** Remove the master effects chain and restore direct routing to audioContext.destination. */
|
|
520
|
+
disconnectMasterOutput(): void;
|
|
495
521
|
on<K extends TransportEventType>(event: K, cb: TransportEvents[K]): void;
|
|
496
522
|
off<K extends TransportEventType>(event: K, cb: TransportEvents[K]): void;
|
|
497
523
|
dispose(): void;
|
package/dist/index.js
CHANGED
|
@@ -711,6 +711,8 @@ var MeterMap = class {
|
|
|
711
711
|
// src/audio/master-node.ts
|
|
712
712
|
var MasterNode = class {
|
|
713
713
|
constructor(audioContext) {
|
|
714
|
+
this._destination = null;
|
|
715
|
+
this._effectsInput = null;
|
|
714
716
|
this._gainNode = audioContext.createGain();
|
|
715
717
|
}
|
|
716
718
|
get input() {
|
|
@@ -719,6 +721,37 @@ var MasterNode = class {
|
|
|
719
721
|
get output() {
|
|
720
722
|
return this._gainNode;
|
|
721
723
|
}
|
|
724
|
+
/** Connect the master output to its final destination (audioContext.destination) */
|
|
725
|
+
connectOutput(destination) {
|
|
726
|
+
this._destination = destination;
|
|
727
|
+
this._gainNode.connect(destination);
|
|
728
|
+
}
|
|
729
|
+
/**
|
|
730
|
+
* Insert an effects chain between the master gain and the destination.
|
|
731
|
+
* Only the destination edge is severed (targeted disconnect), so parallel
|
|
732
|
+
* taps on the master output (analyzers, recorders) keep working.
|
|
733
|
+
* The caller is responsible for routing the chain's output onward.
|
|
734
|
+
*/
|
|
735
|
+
connectEffects(effectsInput) {
|
|
736
|
+
if (this._effectsInput) {
|
|
737
|
+
this._gainNode.disconnect(this._effectsInput);
|
|
738
|
+
} else if (this._destination) {
|
|
739
|
+
this._gainNode.disconnect(this._destination);
|
|
740
|
+
}
|
|
741
|
+
this._gainNode.connect(effectsInput);
|
|
742
|
+
this._effectsInput = effectsInput;
|
|
743
|
+
}
|
|
744
|
+
/** Remove the effects chain and restore direct routing to the destination */
|
|
745
|
+
disconnectEffects() {
|
|
746
|
+
if (!this._effectsInput) {
|
|
747
|
+
return;
|
|
748
|
+
}
|
|
749
|
+
this._gainNode.disconnect(this._effectsInput);
|
|
750
|
+
if (this._destination) {
|
|
751
|
+
this._gainNode.connect(this._destination);
|
|
752
|
+
}
|
|
753
|
+
this._effectsInput = null;
|
|
754
|
+
}
|
|
722
755
|
setVolume(value) {
|
|
723
756
|
this._gainNode.gain.value = value;
|
|
724
757
|
}
|
|
@@ -728,6 +761,8 @@ var MasterNode = class {
|
|
|
728
761
|
} catch (err) {
|
|
729
762
|
console.warn("[waveform-playlist] MasterNode.dispose: error disconnecting: " + String(err));
|
|
730
763
|
}
|
|
764
|
+
this._destination = null;
|
|
765
|
+
this._effectsInput = null;
|
|
731
766
|
}
|
|
732
767
|
};
|
|
733
768
|
|
|
@@ -1378,6 +1413,7 @@ var _Transport = class _Transport {
|
|
|
1378
1413
|
this._clipPlayer.onPositionJump(seekTick);
|
|
1379
1414
|
this._timer.start();
|
|
1380
1415
|
}
|
|
1416
|
+
this._emit("seek", { seconds: time });
|
|
1381
1417
|
}
|
|
1382
1418
|
getCurrentTime() {
|
|
1383
1419
|
if (this._countingIn) {
|
|
@@ -1687,6 +1723,20 @@ var _Transport = class _Transport {
|
|
|
1687
1723
|
get masterOutputNode() {
|
|
1688
1724
|
return this._masterNode.output;
|
|
1689
1725
|
}
|
|
1726
|
+
/**
|
|
1727
|
+
* Insert an effects chain on the master bus, between the master gain and
|
|
1728
|
+
* audioContext.destination. Accepts any AudioNode chain (Tone.js effects,
|
|
1729
|
+
* WAM plugins, native nodes). The caller is responsible for connecting the
|
|
1730
|
+
* chain's output to audioContext.destination. Calling again replaces the
|
|
1731
|
+
* previous chain. Parallel taps on `masterOutputNode` are unaffected.
|
|
1732
|
+
*/
|
|
1733
|
+
connectMasterOutput(node) {
|
|
1734
|
+
this._masterNode.connectEffects(node);
|
|
1735
|
+
}
|
|
1736
|
+
/** Remove the master effects chain and restore direct routing to audioContext.destination. */
|
|
1737
|
+
disconnectMasterOutput() {
|
|
1738
|
+
this._masterNode.disconnectEffects();
|
|
1739
|
+
}
|
|
1690
1740
|
// --- Events ---
|
|
1691
1741
|
on(event, cb) {
|
|
1692
1742
|
if (!this._listeners.has(event)) {
|
|
@@ -1740,7 +1790,7 @@ var _Transport = class _Transport {
|
|
|
1740
1790
|
}
|
|
1741
1791
|
_initAudioGraph(audioContext) {
|
|
1742
1792
|
this._masterNode = new MasterNode(audioContext);
|
|
1743
|
-
this._masterNode.
|
|
1793
|
+
this._masterNode.connectOutput(audioContext.destination);
|
|
1744
1794
|
const toAudioTime = (transportTime) => this._clock.toAudioTime(transportTime);
|
|
1745
1795
|
this._clipPlayer = new ClipPlayer(
|
|
1746
1796
|
audioContext,
|