@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 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. Just another scheduler listener.
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
- private _entryAt;
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
- private _entryAt;
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 };