@dawcore/transport 0.0.3 → 0.0.5
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 +76 -5
- package/dist/index.d.mts +126 -6
- package/dist/index.d.ts +126 -6
- package/dist/index.js +528 -28
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +528 -28
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -7,7 +7,8 @@ Native Web Audio transport for multi-track audio scheduling, looping, tempo, and
|
|
|
7
7
|
- **Native Web Audio** — No Tone.js, no `standardized-audio-context`. Direct `AudioContext` with full `sampleRate` and `latencyHint` control.
|
|
8
8
|
- **Sliding window scheduler** — Schedules audio 200ms ahead via `requestAnimationFrame` for glitch-free playback.
|
|
9
9
|
- **Dual timeline** — Sample-absolute positions for audio clips, PPQN tick positions for metronome/MIDI.
|
|
10
|
-
- **Built-in metronome** — Beat-grid click scheduling with accent on beat 1.
|
|
10
|
+
- **Built-in metronome** — Beat-grid click scheduling with accent on beat 1. Default synthesized click sounds out of the box.
|
|
11
|
+
- **Count-in (pre-roll)** — Configurable bars of click sounds before playback begins. Beat-by-beat events for UI countdown.
|
|
11
12
|
- **Per-track signal chain** — Native GainNode (volume) → StereoPannerNode → GainNode (mute) → effects hook → master output.
|
|
12
13
|
- **Effects plugin hook** — `connectTrackOutput(trackId, node)` inserts any `AudioNode` chain (Tone.js effects, WAM plugins, native nodes).
|
|
13
14
|
- **Type-safe coordinates** — Branded `Tick` and `Sample` types prevent accidentally passing seconds where ticks or samples are expected. Zero runtime cost.
|
|
@@ -71,9 +72,31 @@ const transport = new Transport(audioContext, {
|
|
|
71
72
|
denominator: 4,
|
|
72
73
|
});
|
|
73
74
|
|
|
75
|
+
// Default click sounds are built in — just enable and play
|
|
74
76
|
transport.setMetronomeEnabled(true);
|
|
75
|
-
transport.setMetronomeClickSounds(accentBuffer, normalBuffer);
|
|
76
77
|
transport.play();
|
|
78
|
+
|
|
79
|
+
// Override with custom click sounds
|
|
80
|
+
transport.setMetronomeClickSounds(accentBuffer, normalBuffer);
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
### Count-In
|
|
84
|
+
|
|
85
|
+
```typescript
|
|
86
|
+
transport.setCountIn(true);
|
|
87
|
+
transport.setCountInBars(1); // 1–8 bars, default 1
|
|
88
|
+
transport.setCountInMode('always'); // 'always' | 'recording-only' (default)
|
|
89
|
+
|
|
90
|
+
// Beat-by-beat events for UI countdown
|
|
91
|
+
transport.on('countIn', ({ beat, totalBeats }) => {
|
|
92
|
+
console.log(beat + ' / ' + totalBeats); // "1 / 4", "2 / 4", ...
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
transport.on('countInEnd', () => {
|
|
96
|
+
console.log('Playback starting');
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
transport.play(); // Plays count-in clicks, then starts playback
|
|
77
100
|
```
|
|
78
101
|
|
|
79
102
|
### Mixed Meter
|
|
@@ -92,6 +115,38 @@ transport.setMetronomeEnabled(true);
|
|
|
92
115
|
transport.play();
|
|
93
116
|
```
|
|
94
117
|
|
|
118
|
+
### Tempo Automation
|
|
119
|
+
|
|
120
|
+
```typescript
|
|
121
|
+
const transport = new Transport(audioContext, { tempo: 100 });
|
|
122
|
+
|
|
123
|
+
// Linear ramp from 100 to 160 BPM over 8 bars
|
|
124
|
+
transport.setTempo(160, transport.barToTick(9), { interpolation: 'linear' });
|
|
125
|
+
|
|
126
|
+
// Query interpolated BPM at any position
|
|
127
|
+
transport.getTempo(transport.barToTick(5)); // 130 BPM (midway through ramp)
|
|
128
|
+
|
|
129
|
+
// Curved ramp: ease-in (slow start, fast end)
|
|
130
|
+
transport.clearTempos();
|
|
131
|
+
transport.setTempo(80);
|
|
132
|
+
transport.setTempo(160, transport.barToTick(9), {
|
|
133
|
+
interpolation: { type: 'curve', slope: 0.2 }, // concave
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
// Curved ramp: ease-out (fast start, slow end)
|
|
137
|
+
transport.clearTempos();
|
|
138
|
+
transport.setTempo(80);
|
|
139
|
+
transport.setTempo(160, transport.barToTick(9), {
|
|
140
|
+
interpolation: { type: 'curve', slope: 0.8 }, // convex
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
// Mix step and linear: jump to 80 BPM at bar 4, ramp to 140 at bar 8
|
|
144
|
+
transport.clearTempos();
|
|
145
|
+
transport.setTempo(120);
|
|
146
|
+
transport.setTempo(80, transport.barToTick(5)); // step (instant jump)
|
|
147
|
+
transport.setTempo(140, transport.barToTick(9), { interpolation: 'linear' }); // ramp
|
|
148
|
+
```
|
|
149
|
+
|
|
95
150
|
### Effects
|
|
96
151
|
|
|
97
152
|
```typescript
|
|
@@ -123,6 +178,8 @@ new Transport(audioContext: AudioContext, options?: TransportOptions)
|
|
|
123
178
|
| `numerator` | `4` | Beats per bar (time signature numerator) |
|
|
124
179
|
| `denominator` | `4` | Beat unit (time signature denominator) |
|
|
125
180
|
| `schedulerLookahead` | `0.2` | How far ahead to schedule (seconds) |
|
|
181
|
+
| `accentFrequency` | `1000` | Default accent click frequency (Hz) |
|
|
182
|
+
| `normalFrequency` | `800` | Default normal click frequency (Hz) |
|
|
126
183
|
|
|
127
184
|
**Playback:**
|
|
128
185
|
- `play(startTime?, endTime?)` — Start or resume playback
|
|
@@ -151,7 +208,7 @@ new Transport(audioContext: AudioContext, options?: TransportOptions)
|
|
|
151
208
|
- `setLoopSamples(enabled, startSample: Sample, endSample: Sample)` — Set loop region in samples (convenience)
|
|
152
209
|
|
|
153
210
|
**Tempo & Meter:**
|
|
154
|
-
- `setTempo(bpm, atTick?)` / `getTempo(atTick
|
|
211
|
+
- `setTempo(bpm, atTick?, options?)` / `getTempo(atTick?: Tick)` — options: `{ interpolation: 'step' | 'linear' | { type: 'curve', slope } }`
|
|
155
212
|
- `clearTempos()` — remove all tempo entries
|
|
156
213
|
- `setMeter(numerator, denominator, atTick?: Tick)` / `getMeter(atTick?: Tick)`
|
|
157
214
|
- `removeMeter(atTick: Tick)` / `clearMeters()`
|
|
@@ -160,7 +217,14 @@ new Transport(audioContext: AudioContext, options?: TransportOptions)
|
|
|
160
217
|
|
|
161
218
|
**Metronome:**
|
|
162
219
|
- `setMetronomeEnabled(enabled)`
|
|
163
|
-
- `setMetronomeClickSounds(accent, normal)`
|
|
220
|
+
- `setMetronomeClickSounds(accent, normal)` — overrides default synthesized sounds
|
|
221
|
+
|
|
222
|
+
**Count-In:**
|
|
223
|
+
- `setCountIn(enabled)` — enable/disable count-in
|
|
224
|
+
- `setCountInBars(bars)` — number of bars (1–8, default 1)
|
|
225
|
+
- `setCountInMode(mode)` — `'recording-only'` (default) or `'always'`
|
|
226
|
+
- `setRecording(recording)` — consumer signals recording state (for `'recording-only'` mode)
|
|
227
|
+
- `isCountingIn()` — whether count-in is active
|
|
164
228
|
|
|
165
229
|
**Effects:**
|
|
166
230
|
- `connectTrackOutput(trackId, node)` — Insert effects chain
|
|
@@ -168,7 +232,10 @@ new Transport(audioContext: AudioContext, options?: TransportOptions)
|
|
|
168
232
|
|
|
169
233
|
**Events:**
|
|
170
234
|
- `on(event, callback)` / `off(event, callback)`
|
|
171
|
-
- Events: `play`, `pause`, `stop`, `loop`, `tempochange`
|
|
235
|
+
- Events: `play`, `pause`, `stop`, `loop`, `tempochange`, `meterchange`, `countIn`, `countInEnd`
|
|
236
|
+
- `tempochange` payload: `{ bpm: number, atTick: Tick }`
|
|
237
|
+
- `meterchange` payload: `{ numerator: number, denominator: number, atTick: Tick }`
|
|
238
|
+
- `countIn` payload: `{ beat: number, totalBeats: number }`
|
|
172
239
|
|
|
173
240
|
**Cleanup:**
|
|
174
241
|
- `dispose()` — Stop playback, disconnect all nodes, remove listeners
|
|
@@ -187,6 +254,10 @@ Implements `PlayoutAdapter` from `@waveform-playlist/engine`. All methods delega
|
|
|
187
254
|
|
|
188
255
|
See [TRANSPORT.md](./TRANSPORT.md) for the full architecture guide.
|
|
189
256
|
|
|
257
|
+
## How It Works
|
|
258
|
+
|
|
259
|
+
See [EDUCATIONAL.md](./EDUCATIONAL.md) for an in-depth explanation of the math and timing models behind audio transport systems.
|
|
260
|
+
|
|
190
261
|
## License
|
|
191
262
|
|
|
192
263
|
MIT
|
package/dist/index.d.mts
CHANGED
|
@@ -40,6 +40,10 @@ interface TransportOptions {
|
|
|
40
40
|
denominator?: number;
|
|
41
41
|
/** How far ahead to schedule audio, in seconds. Default: 0.2 */
|
|
42
42
|
schedulerLookahead?: number;
|
|
43
|
+
/** Accent click frequency in Hz. Default: 1000 */
|
|
44
|
+
accentFrequency?: number;
|
|
45
|
+
/** Normal click frequency in Hz. Default: 800 */
|
|
46
|
+
normalFrequency?: number;
|
|
43
47
|
}
|
|
44
48
|
/** Public return type for getMeter() */
|
|
45
49
|
interface MeterSignature {
|
|
@@ -57,11 +61,20 @@ interface MeterEntry {
|
|
|
57
61
|
/** Cached cumulative bar count from tick 0 to this entry. Derived — do not set manually. */
|
|
58
62
|
readonly barAtTick: number;
|
|
59
63
|
}
|
|
64
|
+
/** How to interpolate tempo from the previous entry to this one.
|
|
65
|
+
* 'step' = instant jump (default). 'linear' = linear ramp.
|
|
66
|
+
* { type: 'curve', slope } = Möbius-Ease curve (slope 0-1 exclusive). */
|
|
67
|
+
type TempoInterpolation = 'step' | 'linear' | {
|
|
68
|
+
type: 'curve';
|
|
69
|
+
slope: number;
|
|
70
|
+
};
|
|
60
71
|
interface TempoEntry {
|
|
61
72
|
/** Tick position where this tempo starts */
|
|
62
73
|
tick: Tick;
|
|
63
74
|
/** Beats per minute */
|
|
64
75
|
bpm: number;
|
|
76
|
+
/** How to arrive at this BPM from the previous entry */
|
|
77
|
+
readonly interpolation: TempoInterpolation;
|
|
65
78
|
/** Cached cumulative seconds up to this tick (for O(log n) lookup). Derived — do not set manually. */
|
|
66
79
|
readonly secondsAtTick: number;
|
|
67
80
|
}
|
|
@@ -75,6 +88,33 @@ interface TransportPosition {
|
|
|
75
88
|
* Not branded Tick — a remainder within a beat, not an absolute position. */
|
|
76
89
|
subTick: number;
|
|
77
90
|
}
|
|
91
|
+
/** Count-in mode: 'always' plays count-in on every play(), 'recording-only' only when recording. */
|
|
92
|
+
type CountInMode = 'always' | 'recording-only';
|
|
93
|
+
/** Payload emitted with the 'countIn' transport event.
|
|
94
|
+
* Example: beat=1, totalBeats=4 means "first of four beats". */
|
|
95
|
+
interface CountInEventData {
|
|
96
|
+
/** Current beat number (1-indexed) */
|
|
97
|
+
beat: number;
|
|
98
|
+
/** Total number of beats in the count-in */
|
|
99
|
+
totalBeats: number;
|
|
100
|
+
}
|
|
101
|
+
/** Payload emitted with the 'tempochange' transport event. */
|
|
102
|
+
interface TempoChangeEventData {
|
|
103
|
+
/** The new BPM value */
|
|
104
|
+
bpm: number;
|
|
105
|
+
/** Tick position where the tempo was set */
|
|
106
|
+
atTick: Tick;
|
|
107
|
+
}
|
|
108
|
+
/** Payload emitted with the 'meterchange' transport event.
|
|
109
|
+
* After removeMeter/clearMeters, reflects the resulting meter at that position. */
|
|
110
|
+
interface MeterChangeEventData {
|
|
111
|
+
/** Time signature numerator */
|
|
112
|
+
numerator: number;
|
|
113
|
+
/** Time signature denominator */
|
|
114
|
+
denominator: number;
|
|
115
|
+
/** Tick position where the meter was set or removed */
|
|
116
|
+
atTick: Tick;
|
|
117
|
+
}
|
|
78
118
|
|
|
79
119
|
declare class Clock {
|
|
80
120
|
private _audioContext;
|
|
@@ -95,19 +135,52 @@ declare class Clock {
|
|
|
95
135
|
isRunning(): boolean;
|
|
96
136
|
}
|
|
97
137
|
|
|
138
|
+
interface SetTempoOptions {
|
|
139
|
+
interpolation?: TempoInterpolation;
|
|
140
|
+
}
|
|
98
141
|
declare class TempoMap {
|
|
99
142
|
private _ppqn;
|
|
100
143
|
private _entries;
|
|
101
144
|
constructor(ppqn?: number, initialBpm?: number);
|
|
102
145
|
getTempo(atTick?: Tick): number;
|
|
103
|
-
setTempo(bpm: number, atTick?: Tick): void;
|
|
146
|
+
setTempo(bpm: number, atTick?: Tick, options?: SetTempoOptions): void;
|
|
104
147
|
ticksToSeconds(ticks: Tick): number;
|
|
105
148
|
secondsToTicks(seconds: number): Tick;
|
|
106
149
|
beatsToSeconds(beats: number): number;
|
|
107
150
|
secondsToBeats(seconds: number): number;
|
|
108
151
|
clearTempos(): void;
|
|
152
|
+
/** Get the interpolated BPM at a tick position */
|
|
153
|
+
private _getTempoAt;
|
|
109
154
|
private _ticksToSecondsInternal;
|
|
110
|
-
|
|
155
|
+
/**
|
|
156
|
+
* Exact integration for a linear BPM ramp using the logarithmic formula.
|
|
157
|
+
* For bpm(t) = bpm0 + r*t where r = (bpm1-bpm0)/T:
|
|
158
|
+
* seconds = (T * 60) / (ppqn * (bpm1-bpm0)) * ln(bpmAtTick / bpm0)
|
|
159
|
+
*/
|
|
160
|
+
private _ticksToSecondsLinear;
|
|
161
|
+
/**
|
|
162
|
+
* Inverse of _ticksToSecondsLinear: given seconds, return ticks.
|
|
163
|
+
* Closed-form via exponential: bpmAtTick = bpm0 * exp(seconds * deltaBpm * ppqn / (60 * T))
|
|
164
|
+
* then ticks = (bpmAtTick - bpm0) * T / deltaBpm
|
|
165
|
+
*
|
|
166
|
+
* Note: exp(log(x)) has ~1 ULP floating-point error, so round-trips depend on
|
|
167
|
+
* Math.round() in the caller (secondsToTicks). This is sufficient for all tested
|
|
168
|
+
* BPM ranges (10–300 BPM) but is not algebraically exact like the previous
|
|
169
|
+
* trapezoidal/quadratic approach was.
|
|
170
|
+
*/
|
|
171
|
+
private _secondsToTicksLinear;
|
|
172
|
+
/**
|
|
173
|
+
* Subdivided trapezoidal integration for a Möbius-Ease tempo curve.
|
|
174
|
+
* The BPM at progress p is: bpm0 + curveNormalizedAt(p, slope) * (bpm1 - bpm0).
|
|
175
|
+
* We subdivide into CURVE_SUBDIVISIONS intervals and apply trapezoidal rule.
|
|
176
|
+
*/
|
|
177
|
+
private _ticksToSecondsCurve;
|
|
178
|
+
/**
|
|
179
|
+
* Inverse of _ticksToSecondsCurve: given seconds into a curved segment,
|
|
180
|
+
* return ticks. Uses binary search since there's no closed-form inverse.
|
|
181
|
+
*/
|
|
182
|
+
private _secondsToTicksCurve;
|
|
183
|
+
private _entryIndexAt;
|
|
111
184
|
private _recomputeCache;
|
|
112
185
|
}
|
|
113
186
|
|
|
@@ -287,13 +360,29 @@ declare class MetronomePlayer implements SchedulerListener<MetronomeEvent> {
|
|
|
287
360
|
silence(): void;
|
|
288
361
|
}
|
|
289
362
|
|
|
363
|
+
interface CountInEvent extends SchedulerEvent {
|
|
364
|
+
isAccent: boolean;
|
|
365
|
+
buffer: AudioBuffer;
|
|
366
|
+
beat: number;
|
|
367
|
+
totalBeats: number;
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
interface ClickSoundOptions {
|
|
371
|
+
/** Frequency for accent click (beat 1). Default: 1000 Hz */
|
|
372
|
+
accentFrequency?: number;
|
|
373
|
+
/** Frequency for normal click (other beats). Default: 800 Hz */
|
|
374
|
+
normalFrequency?: number;
|
|
375
|
+
}
|
|
376
|
+
|
|
290
377
|
interface TransportEvents {
|
|
291
378
|
play: () => void;
|
|
292
379
|
pause: () => void;
|
|
293
380
|
stop: () => void;
|
|
294
381
|
loop: () => void;
|
|
295
|
-
tempochange: () => void;
|
|
296
|
-
meterchange: () => void;
|
|
382
|
+
tempochange: (event: TempoChangeEventData) => void;
|
|
383
|
+
meterchange: (event: MeterChangeEventData) => void;
|
|
384
|
+
countIn: (event: CountInEventData) => void;
|
|
385
|
+
countInEnd: () => void;
|
|
297
386
|
}
|
|
298
387
|
type TransportEventType = keyof TransportEvents;
|
|
299
388
|
declare class Transport {
|
|
@@ -314,8 +403,22 @@ declare class Transport {
|
|
|
314
403
|
private _playing;
|
|
315
404
|
private _endTime;
|
|
316
405
|
private _loopEnabled;
|
|
406
|
+
private _loopStartTick;
|
|
317
407
|
private _loopStartSeconds;
|
|
318
408
|
private _listeners;
|
|
409
|
+
private _countInEnabled;
|
|
410
|
+
private _countInBars;
|
|
411
|
+
private _countInMode;
|
|
412
|
+
private _recording;
|
|
413
|
+
private _countingIn;
|
|
414
|
+
private _countInStartPosition;
|
|
415
|
+
private _countInDuration;
|
|
416
|
+
private _countInPlayer;
|
|
417
|
+
private _countInScheduler;
|
|
418
|
+
private _accentBuffer;
|
|
419
|
+
private _normalBuffer;
|
|
420
|
+
private _schedulerLookahead;
|
|
421
|
+
private _ppqn;
|
|
319
422
|
constructor(audioContext: AudioContext, options?: TransportOptions);
|
|
320
423
|
get audioContext(): AudioContext;
|
|
321
424
|
play(startTime?: number, endTime?: number): void;
|
|
@@ -339,7 +442,7 @@ declare class Transport {
|
|
|
339
442
|
setLoopSeconds(enabled: boolean, startSec: number, endSec: number): void;
|
|
340
443
|
/** Convenience — sets loop in samples */
|
|
341
444
|
setLoopSamples(enabled: boolean, startSample: Sample, endSample: Sample): void;
|
|
342
|
-
setTempo(bpm: number, atTick?: Tick): void;
|
|
445
|
+
setTempo(bpm: number, atTick?: Tick, options?: SetTempoOptions): void;
|
|
343
446
|
getTempo(atTick?: Tick): number;
|
|
344
447
|
setMeter(numerator: number, denominator: number, atTick?: Tick): void;
|
|
345
448
|
getMeter(atTick?: Tick): MeterSignature;
|
|
@@ -354,6 +457,13 @@ declare class Transport {
|
|
|
354
457
|
tickToTime(tick: Tick): number;
|
|
355
458
|
setMetronomeEnabled(enabled: boolean): void;
|
|
356
459
|
setMetronomeClickSounds(accent: AudioBuffer, normal: AudioBuffer): void;
|
|
460
|
+
static readonly MIN_COUNT_IN_BARS = 1;
|
|
461
|
+
static readonly MAX_COUNT_IN_BARS = 8;
|
|
462
|
+
setCountIn(enabled: boolean): void;
|
|
463
|
+
setCountInBars(bars: number): void;
|
|
464
|
+
setCountInMode(mode: CountInMode): void;
|
|
465
|
+
setRecording(recording: boolean): void;
|
|
466
|
+
isCountingIn(): boolean;
|
|
357
467
|
connectTrackOutput(trackId: string, node: AudioNode): void;
|
|
358
468
|
disconnectTrackOutput(trackId: string): void;
|
|
359
469
|
on<K extends TransportEventType>(event: K, cb: TransportEvents[K]): void;
|
|
@@ -361,6 +471,11 @@ declare class Transport {
|
|
|
361
471
|
dispose(): void;
|
|
362
472
|
private static _validateOptions;
|
|
363
473
|
private _initAudioGraph;
|
|
474
|
+
private _initCountIn;
|
|
475
|
+
private _shouldCountIn;
|
|
476
|
+
private _startCountIn;
|
|
477
|
+
private _finishCountIn;
|
|
478
|
+
private _cancelCountIn;
|
|
364
479
|
private _silenceAll;
|
|
365
480
|
private _applyMuteState;
|
|
366
481
|
private _emit;
|
|
@@ -388,7 +503,12 @@ declare class NativePlayoutAdapter implements PlayoutAdapter {
|
|
|
388
503
|
setTrackSolo(trackId: string, soloed: boolean): void;
|
|
389
504
|
setTrackPan(trackId: string, pan: number): void;
|
|
390
505
|
setLoop(enabled: boolean, start: number, end: number): void;
|
|
506
|
+
setCountIn(enabled: boolean): void;
|
|
507
|
+
setCountInBars(bars: number): void;
|
|
508
|
+
setCountInMode(mode: CountInMode): void;
|
|
509
|
+
setRecording(recording: boolean): void;
|
|
510
|
+
isCountingIn(): boolean;
|
|
391
511
|
dispose(): void;
|
|
392
512
|
}
|
|
393
513
|
|
|
394
|
-
export { type ClipEvent, ClipPlayer, Clock, MasterNode, type MeterEntry, MeterMap, type MeterSignature, type MetronomeEvent, MetronomePlayer, NativePlayoutAdapter, type Sample, SampleTimeline, Scheduler, type SchedulerEvent, type SchedulerListener, type SchedulerOptions, type TempoEntry, TempoMap, type Tick, Timer, TrackNode, Transport, type TransportEvents, type TransportOptions, type TransportPosition };
|
|
514
|
+
export { type ClickSoundOptions, type ClipEvent, ClipPlayer, Clock, type CountInEvent, type CountInEventData, type CountInMode, MasterNode, type MeterChangeEventData, type MeterEntry, MeterMap, type MeterSignature, type MetronomeEvent, MetronomePlayer, NativePlayoutAdapter, type Sample, SampleTimeline, Scheduler, type SchedulerEvent, type SchedulerListener, type SchedulerOptions, type SetTempoOptions, type TempoChangeEventData, type TempoEntry, type TempoInterpolation, TempoMap, type Tick, Timer, TrackNode, Transport, type TransportEvents, type TransportOptions, type TransportPosition };
|
package/dist/index.d.ts
CHANGED
|
@@ -40,6 +40,10 @@ interface TransportOptions {
|
|
|
40
40
|
denominator?: number;
|
|
41
41
|
/** How far ahead to schedule audio, in seconds. Default: 0.2 */
|
|
42
42
|
schedulerLookahead?: number;
|
|
43
|
+
/** Accent click frequency in Hz. Default: 1000 */
|
|
44
|
+
accentFrequency?: number;
|
|
45
|
+
/** Normal click frequency in Hz. Default: 800 */
|
|
46
|
+
normalFrequency?: number;
|
|
43
47
|
}
|
|
44
48
|
/** Public return type for getMeter() */
|
|
45
49
|
interface MeterSignature {
|
|
@@ -57,11 +61,20 @@ interface MeterEntry {
|
|
|
57
61
|
/** Cached cumulative bar count from tick 0 to this entry. Derived — do not set manually. */
|
|
58
62
|
readonly barAtTick: number;
|
|
59
63
|
}
|
|
64
|
+
/** How to interpolate tempo from the previous entry to this one.
|
|
65
|
+
* 'step' = instant jump (default). 'linear' = linear ramp.
|
|
66
|
+
* { type: 'curve', slope } = Möbius-Ease curve (slope 0-1 exclusive). */
|
|
67
|
+
type TempoInterpolation = 'step' | 'linear' | {
|
|
68
|
+
type: 'curve';
|
|
69
|
+
slope: number;
|
|
70
|
+
};
|
|
60
71
|
interface TempoEntry {
|
|
61
72
|
/** Tick position where this tempo starts */
|
|
62
73
|
tick: Tick;
|
|
63
74
|
/** Beats per minute */
|
|
64
75
|
bpm: number;
|
|
76
|
+
/** How to arrive at this BPM from the previous entry */
|
|
77
|
+
readonly interpolation: TempoInterpolation;
|
|
65
78
|
/** Cached cumulative seconds up to this tick (for O(log n) lookup). Derived — do not set manually. */
|
|
66
79
|
readonly secondsAtTick: number;
|
|
67
80
|
}
|
|
@@ -75,6 +88,33 @@ interface TransportPosition {
|
|
|
75
88
|
* Not branded Tick — a remainder within a beat, not an absolute position. */
|
|
76
89
|
subTick: number;
|
|
77
90
|
}
|
|
91
|
+
/** Count-in mode: 'always' plays count-in on every play(), 'recording-only' only when recording. */
|
|
92
|
+
type CountInMode = 'always' | 'recording-only';
|
|
93
|
+
/** Payload emitted with the 'countIn' transport event.
|
|
94
|
+
* Example: beat=1, totalBeats=4 means "first of four beats". */
|
|
95
|
+
interface CountInEventData {
|
|
96
|
+
/** Current beat number (1-indexed) */
|
|
97
|
+
beat: number;
|
|
98
|
+
/** Total number of beats in the count-in */
|
|
99
|
+
totalBeats: number;
|
|
100
|
+
}
|
|
101
|
+
/** Payload emitted with the 'tempochange' transport event. */
|
|
102
|
+
interface TempoChangeEventData {
|
|
103
|
+
/** The new BPM value */
|
|
104
|
+
bpm: number;
|
|
105
|
+
/** Tick position where the tempo was set */
|
|
106
|
+
atTick: Tick;
|
|
107
|
+
}
|
|
108
|
+
/** Payload emitted with the 'meterchange' transport event.
|
|
109
|
+
* After removeMeter/clearMeters, reflects the resulting meter at that position. */
|
|
110
|
+
interface MeterChangeEventData {
|
|
111
|
+
/** Time signature numerator */
|
|
112
|
+
numerator: number;
|
|
113
|
+
/** Time signature denominator */
|
|
114
|
+
denominator: number;
|
|
115
|
+
/** Tick position where the meter was set or removed */
|
|
116
|
+
atTick: Tick;
|
|
117
|
+
}
|
|
78
118
|
|
|
79
119
|
declare class Clock {
|
|
80
120
|
private _audioContext;
|
|
@@ -95,19 +135,52 @@ declare class Clock {
|
|
|
95
135
|
isRunning(): boolean;
|
|
96
136
|
}
|
|
97
137
|
|
|
138
|
+
interface SetTempoOptions {
|
|
139
|
+
interpolation?: TempoInterpolation;
|
|
140
|
+
}
|
|
98
141
|
declare class TempoMap {
|
|
99
142
|
private _ppqn;
|
|
100
143
|
private _entries;
|
|
101
144
|
constructor(ppqn?: number, initialBpm?: number);
|
|
102
145
|
getTempo(atTick?: Tick): number;
|
|
103
|
-
setTempo(bpm: number, atTick?: Tick): void;
|
|
146
|
+
setTempo(bpm: number, atTick?: Tick, options?: SetTempoOptions): void;
|
|
104
147
|
ticksToSeconds(ticks: Tick): number;
|
|
105
148
|
secondsToTicks(seconds: number): Tick;
|
|
106
149
|
beatsToSeconds(beats: number): number;
|
|
107
150
|
secondsToBeats(seconds: number): number;
|
|
108
151
|
clearTempos(): void;
|
|
152
|
+
/** Get the interpolated BPM at a tick position */
|
|
153
|
+
private _getTempoAt;
|
|
109
154
|
private _ticksToSecondsInternal;
|
|
110
|
-
|
|
155
|
+
/**
|
|
156
|
+
* Exact integration for a linear BPM ramp using the logarithmic formula.
|
|
157
|
+
* For bpm(t) = bpm0 + r*t where r = (bpm1-bpm0)/T:
|
|
158
|
+
* seconds = (T * 60) / (ppqn * (bpm1-bpm0)) * ln(bpmAtTick / bpm0)
|
|
159
|
+
*/
|
|
160
|
+
private _ticksToSecondsLinear;
|
|
161
|
+
/**
|
|
162
|
+
* Inverse of _ticksToSecondsLinear: given seconds, return ticks.
|
|
163
|
+
* Closed-form via exponential: bpmAtTick = bpm0 * exp(seconds * deltaBpm * ppqn / (60 * T))
|
|
164
|
+
* then ticks = (bpmAtTick - bpm0) * T / deltaBpm
|
|
165
|
+
*
|
|
166
|
+
* Note: exp(log(x)) has ~1 ULP floating-point error, so round-trips depend on
|
|
167
|
+
* Math.round() in the caller (secondsToTicks). This is sufficient for all tested
|
|
168
|
+
* BPM ranges (10–300 BPM) but is not algebraically exact like the previous
|
|
169
|
+
* trapezoidal/quadratic approach was.
|
|
170
|
+
*/
|
|
171
|
+
private _secondsToTicksLinear;
|
|
172
|
+
/**
|
|
173
|
+
* Subdivided trapezoidal integration for a Möbius-Ease tempo curve.
|
|
174
|
+
* The BPM at progress p is: bpm0 + curveNormalizedAt(p, slope) * (bpm1 - bpm0).
|
|
175
|
+
* We subdivide into CURVE_SUBDIVISIONS intervals and apply trapezoidal rule.
|
|
176
|
+
*/
|
|
177
|
+
private _ticksToSecondsCurve;
|
|
178
|
+
/**
|
|
179
|
+
* Inverse of _ticksToSecondsCurve: given seconds into a curved segment,
|
|
180
|
+
* return ticks. Uses binary search since there's no closed-form inverse.
|
|
181
|
+
*/
|
|
182
|
+
private _secondsToTicksCurve;
|
|
183
|
+
private _entryIndexAt;
|
|
111
184
|
private _recomputeCache;
|
|
112
185
|
}
|
|
113
186
|
|
|
@@ -287,13 +360,29 @@ declare class MetronomePlayer implements SchedulerListener<MetronomeEvent> {
|
|
|
287
360
|
silence(): void;
|
|
288
361
|
}
|
|
289
362
|
|
|
363
|
+
interface CountInEvent extends SchedulerEvent {
|
|
364
|
+
isAccent: boolean;
|
|
365
|
+
buffer: AudioBuffer;
|
|
366
|
+
beat: number;
|
|
367
|
+
totalBeats: number;
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
interface ClickSoundOptions {
|
|
371
|
+
/** Frequency for accent click (beat 1). Default: 1000 Hz */
|
|
372
|
+
accentFrequency?: number;
|
|
373
|
+
/** Frequency for normal click (other beats). Default: 800 Hz */
|
|
374
|
+
normalFrequency?: number;
|
|
375
|
+
}
|
|
376
|
+
|
|
290
377
|
interface TransportEvents {
|
|
291
378
|
play: () => void;
|
|
292
379
|
pause: () => void;
|
|
293
380
|
stop: () => void;
|
|
294
381
|
loop: () => void;
|
|
295
|
-
tempochange: () => void;
|
|
296
|
-
meterchange: () => void;
|
|
382
|
+
tempochange: (event: TempoChangeEventData) => void;
|
|
383
|
+
meterchange: (event: MeterChangeEventData) => void;
|
|
384
|
+
countIn: (event: CountInEventData) => void;
|
|
385
|
+
countInEnd: () => void;
|
|
297
386
|
}
|
|
298
387
|
type TransportEventType = keyof TransportEvents;
|
|
299
388
|
declare class Transport {
|
|
@@ -314,8 +403,22 @@ declare class Transport {
|
|
|
314
403
|
private _playing;
|
|
315
404
|
private _endTime;
|
|
316
405
|
private _loopEnabled;
|
|
406
|
+
private _loopStartTick;
|
|
317
407
|
private _loopStartSeconds;
|
|
318
408
|
private _listeners;
|
|
409
|
+
private _countInEnabled;
|
|
410
|
+
private _countInBars;
|
|
411
|
+
private _countInMode;
|
|
412
|
+
private _recording;
|
|
413
|
+
private _countingIn;
|
|
414
|
+
private _countInStartPosition;
|
|
415
|
+
private _countInDuration;
|
|
416
|
+
private _countInPlayer;
|
|
417
|
+
private _countInScheduler;
|
|
418
|
+
private _accentBuffer;
|
|
419
|
+
private _normalBuffer;
|
|
420
|
+
private _schedulerLookahead;
|
|
421
|
+
private _ppqn;
|
|
319
422
|
constructor(audioContext: AudioContext, options?: TransportOptions);
|
|
320
423
|
get audioContext(): AudioContext;
|
|
321
424
|
play(startTime?: number, endTime?: number): void;
|
|
@@ -339,7 +442,7 @@ declare class Transport {
|
|
|
339
442
|
setLoopSeconds(enabled: boolean, startSec: number, endSec: number): void;
|
|
340
443
|
/** Convenience — sets loop in samples */
|
|
341
444
|
setLoopSamples(enabled: boolean, startSample: Sample, endSample: Sample): void;
|
|
342
|
-
setTempo(bpm: number, atTick?: Tick): void;
|
|
445
|
+
setTempo(bpm: number, atTick?: Tick, options?: SetTempoOptions): void;
|
|
343
446
|
getTempo(atTick?: Tick): number;
|
|
344
447
|
setMeter(numerator: number, denominator: number, atTick?: Tick): void;
|
|
345
448
|
getMeter(atTick?: Tick): MeterSignature;
|
|
@@ -354,6 +457,13 @@ declare class Transport {
|
|
|
354
457
|
tickToTime(tick: Tick): number;
|
|
355
458
|
setMetronomeEnabled(enabled: boolean): void;
|
|
356
459
|
setMetronomeClickSounds(accent: AudioBuffer, normal: AudioBuffer): void;
|
|
460
|
+
static readonly MIN_COUNT_IN_BARS = 1;
|
|
461
|
+
static readonly MAX_COUNT_IN_BARS = 8;
|
|
462
|
+
setCountIn(enabled: boolean): void;
|
|
463
|
+
setCountInBars(bars: number): void;
|
|
464
|
+
setCountInMode(mode: CountInMode): void;
|
|
465
|
+
setRecording(recording: boolean): void;
|
|
466
|
+
isCountingIn(): boolean;
|
|
357
467
|
connectTrackOutput(trackId: string, node: AudioNode): void;
|
|
358
468
|
disconnectTrackOutput(trackId: string): void;
|
|
359
469
|
on<K extends TransportEventType>(event: K, cb: TransportEvents[K]): void;
|
|
@@ -361,6 +471,11 @@ declare class Transport {
|
|
|
361
471
|
dispose(): void;
|
|
362
472
|
private static _validateOptions;
|
|
363
473
|
private _initAudioGraph;
|
|
474
|
+
private _initCountIn;
|
|
475
|
+
private _shouldCountIn;
|
|
476
|
+
private _startCountIn;
|
|
477
|
+
private _finishCountIn;
|
|
478
|
+
private _cancelCountIn;
|
|
364
479
|
private _silenceAll;
|
|
365
480
|
private _applyMuteState;
|
|
366
481
|
private _emit;
|
|
@@ -388,7 +503,12 @@ declare class NativePlayoutAdapter implements PlayoutAdapter {
|
|
|
388
503
|
setTrackSolo(trackId: string, soloed: boolean): void;
|
|
389
504
|
setTrackPan(trackId: string, pan: number): void;
|
|
390
505
|
setLoop(enabled: boolean, start: number, end: number): void;
|
|
506
|
+
setCountIn(enabled: boolean): void;
|
|
507
|
+
setCountInBars(bars: number): void;
|
|
508
|
+
setCountInMode(mode: CountInMode): void;
|
|
509
|
+
setRecording(recording: boolean): void;
|
|
510
|
+
isCountingIn(): boolean;
|
|
391
511
|
dispose(): void;
|
|
392
512
|
}
|
|
393
513
|
|
|
394
|
-
export { type ClipEvent, ClipPlayer, Clock, MasterNode, type MeterEntry, MeterMap, type MeterSignature, type MetronomeEvent, MetronomePlayer, NativePlayoutAdapter, type Sample, SampleTimeline, Scheduler, type SchedulerEvent, type SchedulerListener, type SchedulerOptions, type TempoEntry, TempoMap, type Tick, Timer, TrackNode, Transport, type TransportEvents, type TransportOptions, type TransportPosition };
|
|
514
|
+
export { type ClickSoundOptions, type ClipEvent, ClipPlayer, Clock, type CountInEvent, type CountInEventData, type CountInMode, MasterNode, type MeterChangeEventData, type MeterEntry, MeterMap, type MeterSignature, type MetronomeEvent, MetronomePlayer, NativePlayoutAdapter, type Sample, SampleTimeline, Scheduler, type SchedulerEvent, type SchedulerListener, type SchedulerOptions, type SetTempoOptions, type TempoChangeEventData, type TempoEntry, type TempoInterpolation, TempoMap, type Tick, Timer, TrackNode, Transport, type TransportEvents, type TransportOptions, type TransportPosition };
|