@dawcore/transport 0.0.2 → 0.0.3

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
@@ -10,6 +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. Just another scheduler listener.
11
11
  - **Per-track signal chain** — Native GainNode (volume) → StereoPannerNode → GainNode (mute) → effects hook → master output.
12
12
  - **Effects plugin hook** — `connectTrackOutput(trackId, node)` inserts any `AudioNode` chain (Tone.js effects, WAM plugins, native nodes).
13
+ - **Type-safe coordinates** — Branded `Tick` and `Sample` types prevent accidentally passing seconds where ticks or samples are expected. Zero runtime cost.
13
14
  - **PlayoutAdapter bridge** — `NativePlayoutAdapter` implements the `PlayoutAdapter` interface from `@waveform-playlist/engine`.
14
15
 
15
16
  ## Installation
@@ -145,14 +146,17 @@ new Transport(audioContext: AudioContext, options?: TransportOptions)
145
146
  - `setMasterVolume(volume)`
146
147
 
147
148
  **Loop:**
148
- - `setLoop(enabled, start, end)` — Set loop region in seconds
149
+ - `setLoop(enabled, startTick: Tick, endTick: Tick)` — Set loop region in ticks (primary API)
150
+ - `setLoopSeconds(enabled, start, end)` — Set loop region in seconds (convenience)
151
+ - `setLoopSamples(enabled, startSample: Sample, endSample: Sample)` — Set loop region in samples (convenience)
149
152
 
150
153
  **Tempo & Meter:**
151
154
  - `setTempo(bpm, atTick?)` / `getTempo(atTick?)`
152
155
  - `clearTempos()` — remove all tempo entries
153
- - `setMeter(numerator, denominator, atTick?)` / `getMeter(atTick?)`
154
- - `removeMeter(atTick)` / `clearMeters()`
155
- - `barToTick(bar)` / `tickToBar(tick)`
156
+ - `setMeter(numerator, denominator, atTick?: Tick)` / `getMeter(atTick?: Tick)`
157
+ - `removeMeter(atTick: Tick)` / `clearMeters()`
158
+ - `barToTick(bar): Tick` / `tickToBar(tick: Tick)`
159
+ - `timeToTick(seconds): Tick` / `tickToTime(tick: Tick)`
156
160
 
157
161
  **Metronome:**
158
162
  - `setMetronomeEnabled(enabled)`
package/dist/index.d.mts CHANGED
@@ -1,17 +1,29 @@
1
1
  import { ClipTrack } from '@waveform-playlist/core';
2
2
  import { PlayoutAdapter } from '@waveform-playlist/engine';
3
3
 
4
+ /** Branded type for tick positions — prevents accidentally passing seconds where ticks are expected. */
5
+ declare const __tick: unique symbol;
6
+ type Tick = number & {
7
+ readonly [__tick]: never;
8
+ };
9
+ /** Branded type for sample counts — prevents accidentally passing seconds where samples are expected. */
10
+ declare const __sample: unique symbol;
11
+ type Sample = number & {
12
+ readonly [__sample]: never;
13
+ };
4
14
  interface SchedulerEvent {
5
- /** Transport time (elapsed seconds from timeline start) when this event should be realized */
6
- transportTime: number;
15
+ /** Tick position (integer) on the timeline */
16
+ tick: Tick;
7
17
  }
8
18
  interface SchedulerListener<T extends SchedulerEvent> {
9
- /** Generate events in the time window [fromTime, toTime) */
10
- generate(fromTime: number, toTime: number): T[];
19
+ /** Generate events in the tick window [fromTick, toTick) */
20
+ generate(fromTick: Tick, toTick: Tick): T[];
11
21
  /** Realize an event (create audio nodes, start sources) */
12
22
  consume(event: T): void;
13
- /** Position jumped (loop/seek) — stop active sources, re-schedule */
14
- onPositionJump(newTime: number): void;
23
+ /** Position jumped (loop/seek) — listeners may stop and re-schedule as appropriate
24
+ * (ClipPlayer stops sources and creates mid-clip restarts; MetronomePlayer is a no-op
25
+ * since clicks are short one-shots that finish naturally) */
26
+ onPositionJump(newTick: Tick): void;
15
27
  /** Stop all active audio immediately */
16
28
  silence(): void;
17
29
  }
@@ -37,7 +49,7 @@ interface MeterSignature {
37
49
  /** Storage entry for MeterMap */
38
50
  interface MeterEntry {
39
51
  /** Tick position where this meter starts */
40
- tick: number;
52
+ tick: Tick;
41
53
  /** Time signature numerator (e.g., 6 in 6/8) */
42
54
  numerator: number;
43
55
  /** Time signature denominator (e.g., 8 in 6/8) */
@@ -47,7 +59,7 @@ interface MeterEntry {
47
59
  }
48
60
  interface TempoEntry {
49
61
  /** Tick position where this tempo starts */
50
- tick: number;
62
+ tick: Tick;
51
63
  /** Beats per minute */
52
64
  bpm: number;
53
65
  /** Cached cumulative seconds up to this tick (for O(log n) lookup). Derived — do not set manually. */
@@ -58,8 +70,10 @@ interface TransportPosition {
58
70
  bar: number;
59
71
  /** 1-indexed beat within bar */
60
72
  beat: number;
61
- /** Sub-beat tick (0 to ppqn-1) */
62
- tick: number;
73
+ /** Sub-beat tick remainder (0 to ppqn-1). Named subTick to avoid
74
+ * collision with SchedulerEvent.tick (absolute timeline position).
75
+ * Not branded Tick — a remainder within a beat, not an absolute position. */
76
+ subTick: number;
63
77
  }
64
78
 
65
79
  declare class Clock {
@@ -81,10 +95,29 @@ declare class Clock {
81
95
  isRunning(): boolean;
82
96
  }
83
97
 
98
+ declare class TempoMap {
99
+ private _ppqn;
100
+ private _entries;
101
+ constructor(ppqn?: number, initialBpm?: number);
102
+ getTempo(atTick?: Tick): number;
103
+ setTempo(bpm: number, atTick?: Tick): void;
104
+ ticksToSeconds(ticks: Tick): number;
105
+ secondsToTicks(seconds: number): Tick;
106
+ beatsToSeconds(beats: number): number;
107
+ secondsToBeats(seconds: number): number;
108
+ clearTempos(): void;
109
+ private _ticksToSecondsInternal;
110
+ private _entryAt;
111
+ private _recomputeCache;
112
+ }
113
+
84
114
  interface SchedulerOptions {
85
115
  lookahead?: number;
86
- /** Called when the scheduler wraps at loopEnd — Transport uses this to seek the clock */
87
- onLoop?: (loopStartTime: number) => void;
116
+ /** Called when the scheduler wraps at loopEnd.
117
+ * Receives loopStart, loopEnd, and the currentTimeSeconds snapshot from
118
+ * advance() so the Transport can compute the correct clock seek target
119
+ * without re-reading the live AudioContext.currentTime. */
120
+ onLoop?: (loopStartSeconds: number, loopEndSeconds: number, currentTimeSeconds: number) => void;
88
121
  }
89
122
  declare class Scheduler<T extends SchedulerEvent> {
90
123
  private _lookahead;
@@ -94,12 +127,18 @@ declare class Scheduler<T extends SchedulerEvent> {
94
127
  private _loopStart;
95
128
  private _loopEnd;
96
129
  private _onLoop;
97
- constructor(options?: SchedulerOptions);
130
+ private _tempoMap;
131
+ constructor(tempoMap: TempoMap, options?: SchedulerOptions);
98
132
  addListener(listener: SchedulerListener<T>): void;
99
133
  removeListener(listener: SchedulerListener<T>): void;
100
- setLoop(enabled: boolean, start: number, end: number): void;
101
- reset(time: number): void;
102
- advance(currentTime: number): void;
134
+ /** Primary API ticks as source of truth */
135
+ setLoop(enabled: boolean, startTick: Tick, endTick: Tick): void;
136
+ /** Convenience — converts seconds to ticks via TempoMap */
137
+ setLoopSeconds(enabled: boolean, startSec: number, endSec: number): void;
138
+ /** Reset scheduling cursor. Takes seconds (from Clock), converts to ticks. */
139
+ reset(timeSeconds: number): void;
140
+ /** Advance the scheduling window. Takes seconds (from Clock), converts to ticks. */
141
+ advance(currentTimeSeconds: number): void;
103
142
  private _generateAndConsume;
104
143
  }
105
144
 
@@ -115,26 +154,14 @@ declare class Timer {
115
154
 
116
155
  declare class SampleTimeline {
117
156
  private _sampleRate;
157
+ private _tempoMap;
118
158
  constructor(sampleRate: number);
119
159
  get sampleRate(): number;
120
- samplesToSeconds(samples: number): number;
121
- secondsToSamples(seconds: number): number;
122
- }
123
-
124
- declare class TempoMap {
125
- private _ppqn;
126
- private _entries;
127
- constructor(ppqn?: number, initialBpm?: number);
128
- getTempo(atTick?: number): number;
129
- setTempo(bpm: number, atTick?: number): void;
130
- ticksToSeconds(ticks: number): number;
131
- secondsToTicks(seconds: number): number;
132
- beatsToSeconds(beats: number): number;
133
- secondsToBeats(seconds: number): number;
134
- clearTempos(): void;
135
- private _ticksToSecondsInternal;
136
- private _entryAt;
137
- private _recomputeCache;
160
+ setTempoMap(tempoMap: TempoMap): void;
161
+ samplesToSeconds(samples: Sample): number;
162
+ secondsToSamples(seconds: number): Sample;
163
+ ticksToSamples(ticks: Tick): Sample;
164
+ samplesToTicks(samples: Sample): Tick;
138
165
  }
139
166
 
140
167
  declare class MeterMap {
@@ -142,17 +169,17 @@ declare class MeterMap {
142
169
  private _entries;
143
170
  constructor(ppqn: number, numerator?: number, denominator?: number);
144
171
  get ppqn(): number;
145
- getMeter(atTick?: number): MeterSignature;
146
- setMeter(numerator: number, denominator: number, atTick?: number): void;
147
- removeMeter(atTick: number): void;
172
+ getMeter(atTick?: Tick): MeterSignature;
173
+ setMeter(numerator: number, denominator: number, atTick?: Tick): void;
174
+ removeMeter(atTick: Tick): void;
148
175
  clearMeters(): void;
149
- ticksPerBeat(atTick?: number): number;
150
- ticksPerBar(atTick?: number): number;
151
- barToTick(bar: number): number;
152
- tickToBar(tick: number): number;
153
- isBarBoundary(tick: number): boolean;
176
+ ticksPerBeat(atTick?: Tick): number;
177
+ ticksPerBar(atTick?: Tick): number;
178
+ barToTick(bar: number): Tick;
179
+ tickToBar(tick: Tick): number;
180
+ isBarBoundary(tick: Tick): boolean;
154
181
  /** Internal: get the full entry at a tick (for MetronomePlayer beat grid anchoring) */
155
- getEntryAt(tick: number): MeterEntry;
182
+ getEntryAt(tick: Tick): MeterEntry;
156
183
  private _entryAt;
157
184
  private _ticksPerBarForEntry;
158
185
  private _snapToBarBoundary;
@@ -199,33 +226,40 @@ interface ClipEvent extends SchedulerEvent {
199
226
  trackId: string;
200
227
  clipId: string;
201
228
  audioBuffer: AudioBuffer;
202
- /** Offset into the audioBuffer (seconds) */
203
- offset: number;
204
- /** Duration to play (seconds) */
205
- duration: number;
229
+ /** Clip position on timeline (integer samples) */
230
+ startSample: Sample;
231
+ /** Offset into audioBuffer (integer samples) */
232
+ offsetSamples: Sample;
233
+ /** Duration to play (integer samples) */
234
+ durationSamples: Sample;
206
235
  /** Clip gain multiplier */
207
236
  gain: number;
208
- /** Fade in duration in seconds */
209
- fadeInDuration: number;
210
- /** Fade out duration in seconds */
211
- fadeOutDuration: number;
237
+ /** Fade in duration (integer samples) */
238
+ fadeInDurationSamples: Sample;
239
+ /** Fade out duration (integer samples) */
240
+ fadeOutDurationSamples: Sample;
212
241
  }
213
242
  declare class ClipPlayer implements SchedulerListener<ClipEvent> {
214
243
  private _audioContext;
215
244
  private _sampleTimeline;
245
+ private _tempoMap;
216
246
  private _toAudioTime;
217
247
  private _tracks;
218
248
  private _trackNodes;
219
249
  private _activeSources;
220
250
  private _loopEnabled;
221
- private _loopEnd;
222
- constructor(audioContext: AudioContext, sampleTimeline: SampleTimeline, toAudioTime: (transportTime: number) => number);
251
+ private _loopEndSamples;
252
+ constructor(audioContext: AudioContext, sampleTimeline: SampleTimeline, tempoMap: TempoMap, toAudioTime: (transportTime: number) => number);
223
253
  setTracks(tracks: ClipTrack[], trackNodes: Map<string, TrackNode>): void;
224
- setLoop(enabled: boolean, _start: number, end: number): void;
254
+ /** Set loop region using ticks. startTick is unused — loop clamping only needs
255
+ * the end boundary; mid-clip restart at loopStart is handled by onPositionJump. */
256
+ setLoop(enabled: boolean, _startTick: Tick, endTick: Tick): void;
257
+ /** Set loop region using samples directly */
258
+ setLoopSamples(enabled: boolean, _startSample: Sample, endSample: Sample): void;
225
259
  updateTrack(trackId: string, track: ClipTrack): void;
226
- generate(fromTime: number, toTime: number): ClipEvent[];
260
+ generate(fromTick: Tick, toTick: Tick): ClipEvent[];
227
261
  consume(event: ClipEvent): void;
228
- onPositionJump(newTime: number): void;
262
+ onPositionJump(newTick: Tick): void;
229
263
  silence(): void;
230
264
  private _silenceTrack;
231
265
  }
@@ -247,9 +281,9 @@ declare class MetronomePlayer implements SchedulerListener<MetronomeEvent> {
247
281
  constructor(audioContext: AudioContext, tempoMap: TempoMap, meterMap: MeterMap, destination: AudioNode, toAudioTime: (transportTime: number) => number);
248
282
  setEnabled(enabled: boolean): void;
249
283
  setClickSounds(accent: AudioBuffer, normal: AudioBuffer): void;
250
- generate(fromTime: number, toTime: number): MetronomeEvent[];
284
+ generate(fromTick: Tick, toTick: Tick): MetronomeEvent[];
251
285
  consume(event: MetronomeEvent): void;
252
- onPositionJump(_newTime: number): void;
286
+ onPositionJump(_newTick: Tick): void;
253
287
  silence(): void;
254
288
  }
255
289
 
@@ -279,6 +313,8 @@ declare class Transport {
279
313
  private _mutedTrackIds;
280
314
  private _playing;
281
315
  private _endTime;
316
+ private _loopEnabled;
317
+ private _loopStartSeconds;
282
318
  private _listeners;
283
319
  constructor(audioContext: AudioContext, options?: TransportOptions);
284
320
  get audioContext(): AudioContext;
@@ -297,20 +333,25 @@ declare class Transport {
297
333
  setTrackMute(trackId: string, muted: boolean): void;
298
334
  setTrackSolo(trackId: string, soloed: boolean): void;
299
335
  setMasterVolume(volume: number): void;
300
- setLoop(enabled: boolean, start: number, end: number): void;
301
- setTempo(bpm: number, atTick?: number): void;
302
- getTempo(atTick?: number): number;
303
- setMeter(numerator: number, denominator: number, atTick?: number): void;
304
- getMeter(atTick?: number): MeterSignature;
305
- removeMeter(atTick: number): void;
336
+ /** Primary loop API ticks as source of truth */
337
+ setLoop(enabled: boolean, startTick: Tick, endTick: Tick): void;
338
+ /** Convenience — converts seconds to ticks */
339
+ setLoopSeconds(enabled: boolean, startSec: number, endSec: number): void;
340
+ /** Convenience — sets loop in samples */
341
+ setLoopSamples(enabled: boolean, startSample: Sample, endSample: Sample): void;
342
+ setTempo(bpm: number, atTick?: Tick): void;
343
+ getTempo(atTick?: Tick): number;
344
+ setMeter(numerator: number, denominator: number, atTick?: Tick): void;
345
+ getMeter(atTick?: Tick): MeterSignature;
346
+ removeMeter(atTick: Tick): void;
306
347
  clearMeters(): void;
307
348
  clearTempos(): void;
308
- barToTick(bar: number): number;
309
- tickToBar(tick: number): number;
349
+ barToTick(bar: number): Tick;
350
+ tickToBar(tick: Tick): number;
310
351
  /** Convert transport time (seconds) to tick position, using the tempo map. */
311
- timeToTick(seconds: number): number;
352
+ timeToTick(seconds: number): Tick;
312
353
  /** Convert tick position to transport time (seconds), using the tempo map. */
313
- tickToTime(tick: number): number;
354
+ tickToTime(tick: Tick): number;
314
355
  setMetronomeEnabled(enabled: boolean): void;
315
356
  setMetronomeClickSounds(accent: AudioBuffer, normal: AudioBuffer): void;
316
357
  connectTrackOutput(trackId: string, node: AudioNode): void;
@@ -350,4 +391,4 @@ declare class NativePlayoutAdapter implements PlayoutAdapter {
350
391
  dispose(): void;
351
392
  }
352
393
 
353
- export { type ClipEvent, ClipPlayer, Clock, MasterNode, type MeterEntry, MeterMap, type MeterSignature, type MetronomeEvent, MetronomePlayer, NativePlayoutAdapter, SampleTimeline, Scheduler, type SchedulerEvent, type SchedulerListener, type SchedulerOptions, type TempoEntry, TempoMap, Timer, TrackNode, Transport, type TransportEvents, type TransportOptions, type TransportPosition };
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 };
package/dist/index.d.ts CHANGED
@@ -1,17 +1,29 @@
1
1
  import { ClipTrack } from '@waveform-playlist/core';
2
2
  import { PlayoutAdapter } from '@waveform-playlist/engine';
3
3
 
4
+ /** Branded type for tick positions — prevents accidentally passing seconds where ticks are expected. */
5
+ declare const __tick: unique symbol;
6
+ type Tick = number & {
7
+ readonly [__tick]: never;
8
+ };
9
+ /** Branded type for sample counts — prevents accidentally passing seconds where samples are expected. */
10
+ declare const __sample: unique symbol;
11
+ type Sample = number & {
12
+ readonly [__sample]: never;
13
+ };
4
14
  interface SchedulerEvent {
5
- /** Transport time (elapsed seconds from timeline start) when this event should be realized */
6
- transportTime: number;
15
+ /** Tick position (integer) on the timeline */
16
+ tick: Tick;
7
17
  }
8
18
  interface SchedulerListener<T extends SchedulerEvent> {
9
- /** Generate events in the time window [fromTime, toTime) */
10
- generate(fromTime: number, toTime: number): T[];
19
+ /** Generate events in the tick window [fromTick, toTick) */
20
+ generate(fromTick: Tick, toTick: Tick): T[];
11
21
  /** Realize an event (create audio nodes, start sources) */
12
22
  consume(event: T): void;
13
- /** Position jumped (loop/seek) — stop active sources, re-schedule */
14
- onPositionJump(newTime: number): void;
23
+ /** Position jumped (loop/seek) — listeners may stop and re-schedule as appropriate
24
+ * (ClipPlayer stops sources and creates mid-clip restarts; MetronomePlayer is a no-op
25
+ * since clicks are short one-shots that finish naturally) */
26
+ onPositionJump(newTick: Tick): void;
15
27
  /** Stop all active audio immediately */
16
28
  silence(): void;
17
29
  }
@@ -37,7 +49,7 @@ interface MeterSignature {
37
49
  /** Storage entry for MeterMap */
38
50
  interface MeterEntry {
39
51
  /** Tick position where this meter starts */
40
- tick: number;
52
+ tick: Tick;
41
53
  /** Time signature numerator (e.g., 6 in 6/8) */
42
54
  numerator: number;
43
55
  /** Time signature denominator (e.g., 8 in 6/8) */
@@ -47,7 +59,7 @@ interface MeterEntry {
47
59
  }
48
60
  interface TempoEntry {
49
61
  /** Tick position where this tempo starts */
50
- tick: number;
62
+ tick: Tick;
51
63
  /** Beats per minute */
52
64
  bpm: number;
53
65
  /** Cached cumulative seconds up to this tick (for O(log n) lookup). Derived — do not set manually. */
@@ -58,8 +70,10 @@ interface TransportPosition {
58
70
  bar: number;
59
71
  /** 1-indexed beat within bar */
60
72
  beat: number;
61
- /** Sub-beat tick (0 to ppqn-1) */
62
- tick: number;
73
+ /** Sub-beat tick remainder (0 to ppqn-1). Named subTick to avoid
74
+ * collision with SchedulerEvent.tick (absolute timeline position).
75
+ * Not branded Tick — a remainder within a beat, not an absolute position. */
76
+ subTick: number;
63
77
  }
64
78
 
65
79
  declare class Clock {
@@ -81,10 +95,29 @@ declare class Clock {
81
95
  isRunning(): boolean;
82
96
  }
83
97
 
98
+ declare class TempoMap {
99
+ private _ppqn;
100
+ private _entries;
101
+ constructor(ppqn?: number, initialBpm?: number);
102
+ getTempo(atTick?: Tick): number;
103
+ setTempo(bpm: number, atTick?: Tick): void;
104
+ ticksToSeconds(ticks: Tick): number;
105
+ secondsToTicks(seconds: number): Tick;
106
+ beatsToSeconds(beats: number): number;
107
+ secondsToBeats(seconds: number): number;
108
+ clearTempos(): void;
109
+ private _ticksToSecondsInternal;
110
+ private _entryAt;
111
+ private _recomputeCache;
112
+ }
113
+
84
114
  interface SchedulerOptions {
85
115
  lookahead?: number;
86
- /** Called when the scheduler wraps at loopEnd — Transport uses this to seek the clock */
87
- onLoop?: (loopStartTime: number) => void;
116
+ /** Called when the scheduler wraps at loopEnd.
117
+ * Receives loopStart, loopEnd, and the currentTimeSeconds snapshot from
118
+ * advance() so the Transport can compute the correct clock seek target
119
+ * without re-reading the live AudioContext.currentTime. */
120
+ onLoop?: (loopStartSeconds: number, loopEndSeconds: number, currentTimeSeconds: number) => void;
88
121
  }
89
122
  declare class Scheduler<T extends SchedulerEvent> {
90
123
  private _lookahead;
@@ -94,12 +127,18 @@ declare class Scheduler<T extends SchedulerEvent> {
94
127
  private _loopStart;
95
128
  private _loopEnd;
96
129
  private _onLoop;
97
- constructor(options?: SchedulerOptions);
130
+ private _tempoMap;
131
+ constructor(tempoMap: TempoMap, options?: SchedulerOptions);
98
132
  addListener(listener: SchedulerListener<T>): void;
99
133
  removeListener(listener: SchedulerListener<T>): void;
100
- setLoop(enabled: boolean, start: number, end: number): void;
101
- reset(time: number): void;
102
- advance(currentTime: number): void;
134
+ /** Primary API ticks as source of truth */
135
+ setLoop(enabled: boolean, startTick: Tick, endTick: Tick): void;
136
+ /** Convenience — converts seconds to ticks via TempoMap */
137
+ setLoopSeconds(enabled: boolean, startSec: number, endSec: number): void;
138
+ /** Reset scheduling cursor. Takes seconds (from Clock), converts to ticks. */
139
+ reset(timeSeconds: number): void;
140
+ /** Advance the scheduling window. Takes seconds (from Clock), converts to ticks. */
141
+ advance(currentTimeSeconds: number): void;
103
142
  private _generateAndConsume;
104
143
  }
105
144
 
@@ -115,26 +154,14 @@ declare class Timer {
115
154
 
116
155
  declare class SampleTimeline {
117
156
  private _sampleRate;
157
+ private _tempoMap;
118
158
  constructor(sampleRate: number);
119
159
  get sampleRate(): number;
120
- samplesToSeconds(samples: number): number;
121
- secondsToSamples(seconds: number): number;
122
- }
123
-
124
- declare class TempoMap {
125
- private _ppqn;
126
- private _entries;
127
- constructor(ppqn?: number, initialBpm?: number);
128
- getTempo(atTick?: number): number;
129
- setTempo(bpm: number, atTick?: number): void;
130
- ticksToSeconds(ticks: number): number;
131
- secondsToTicks(seconds: number): number;
132
- beatsToSeconds(beats: number): number;
133
- secondsToBeats(seconds: number): number;
134
- clearTempos(): void;
135
- private _ticksToSecondsInternal;
136
- private _entryAt;
137
- private _recomputeCache;
160
+ setTempoMap(tempoMap: TempoMap): void;
161
+ samplesToSeconds(samples: Sample): number;
162
+ secondsToSamples(seconds: number): Sample;
163
+ ticksToSamples(ticks: Tick): Sample;
164
+ samplesToTicks(samples: Sample): Tick;
138
165
  }
139
166
 
140
167
  declare class MeterMap {
@@ -142,17 +169,17 @@ declare class MeterMap {
142
169
  private _entries;
143
170
  constructor(ppqn: number, numerator?: number, denominator?: number);
144
171
  get ppqn(): number;
145
- getMeter(atTick?: number): MeterSignature;
146
- setMeter(numerator: number, denominator: number, atTick?: number): void;
147
- removeMeter(atTick: number): void;
172
+ getMeter(atTick?: Tick): MeterSignature;
173
+ setMeter(numerator: number, denominator: number, atTick?: Tick): void;
174
+ removeMeter(atTick: Tick): void;
148
175
  clearMeters(): void;
149
- ticksPerBeat(atTick?: number): number;
150
- ticksPerBar(atTick?: number): number;
151
- barToTick(bar: number): number;
152
- tickToBar(tick: number): number;
153
- isBarBoundary(tick: number): boolean;
176
+ ticksPerBeat(atTick?: Tick): number;
177
+ ticksPerBar(atTick?: Tick): number;
178
+ barToTick(bar: number): Tick;
179
+ tickToBar(tick: Tick): number;
180
+ isBarBoundary(tick: Tick): boolean;
154
181
  /** Internal: get the full entry at a tick (for MetronomePlayer beat grid anchoring) */
155
- getEntryAt(tick: number): MeterEntry;
182
+ getEntryAt(tick: Tick): MeterEntry;
156
183
  private _entryAt;
157
184
  private _ticksPerBarForEntry;
158
185
  private _snapToBarBoundary;
@@ -199,33 +226,40 @@ interface ClipEvent extends SchedulerEvent {
199
226
  trackId: string;
200
227
  clipId: string;
201
228
  audioBuffer: AudioBuffer;
202
- /** Offset into the audioBuffer (seconds) */
203
- offset: number;
204
- /** Duration to play (seconds) */
205
- duration: number;
229
+ /** Clip position on timeline (integer samples) */
230
+ startSample: Sample;
231
+ /** Offset into audioBuffer (integer samples) */
232
+ offsetSamples: Sample;
233
+ /** Duration to play (integer samples) */
234
+ durationSamples: Sample;
206
235
  /** Clip gain multiplier */
207
236
  gain: number;
208
- /** Fade in duration in seconds */
209
- fadeInDuration: number;
210
- /** Fade out duration in seconds */
211
- fadeOutDuration: number;
237
+ /** Fade in duration (integer samples) */
238
+ fadeInDurationSamples: Sample;
239
+ /** Fade out duration (integer samples) */
240
+ fadeOutDurationSamples: Sample;
212
241
  }
213
242
  declare class ClipPlayer implements SchedulerListener<ClipEvent> {
214
243
  private _audioContext;
215
244
  private _sampleTimeline;
245
+ private _tempoMap;
216
246
  private _toAudioTime;
217
247
  private _tracks;
218
248
  private _trackNodes;
219
249
  private _activeSources;
220
250
  private _loopEnabled;
221
- private _loopEnd;
222
- constructor(audioContext: AudioContext, sampleTimeline: SampleTimeline, toAudioTime: (transportTime: number) => number);
251
+ private _loopEndSamples;
252
+ constructor(audioContext: AudioContext, sampleTimeline: SampleTimeline, tempoMap: TempoMap, toAudioTime: (transportTime: number) => number);
223
253
  setTracks(tracks: ClipTrack[], trackNodes: Map<string, TrackNode>): void;
224
- setLoop(enabled: boolean, _start: number, end: number): void;
254
+ /** Set loop region using ticks. startTick is unused — loop clamping only needs
255
+ * the end boundary; mid-clip restart at loopStart is handled by onPositionJump. */
256
+ setLoop(enabled: boolean, _startTick: Tick, endTick: Tick): void;
257
+ /** Set loop region using samples directly */
258
+ setLoopSamples(enabled: boolean, _startSample: Sample, endSample: Sample): void;
225
259
  updateTrack(trackId: string, track: ClipTrack): void;
226
- generate(fromTime: number, toTime: number): ClipEvent[];
260
+ generate(fromTick: Tick, toTick: Tick): ClipEvent[];
227
261
  consume(event: ClipEvent): void;
228
- onPositionJump(newTime: number): void;
262
+ onPositionJump(newTick: Tick): void;
229
263
  silence(): void;
230
264
  private _silenceTrack;
231
265
  }
@@ -247,9 +281,9 @@ declare class MetronomePlayer implements SchedulerListener<MetronomeEvent> {
247
281
  constructor(audioContext: AudioContext, tempoMap: TempoMap, meterMap: MeterMap, destination: AudioNode, toAudioTime: (transportTime: number) => number);
248
282
  setEnabled(enabled: boolean): void;
249
283
  setClickSounds(accent: AudioBuffer, normal: AudioBuffer): void;
250
- generate(fromTime: number, toTime: number): MetronomeEvent[];
284
+ generate(fromTick: Tick, toTick: Tick): MetronomeEvent[];
251
285
  consume(event: MetronomeEvent): void;
252
- onPositionJump(_newTime: number): void;
286
+ onPositionJump(_newTick: Tick): void;
253
287
  silence(): void;
254
288
  }
255
289
 
@@ -279,6 +313,8 @@ declare class Transport {
279
313
  private _mutedTrackIds;
280
314
  private _playing;
281
315
  private _endTime;
316
+ private _loopEnabled;
317
+ private _loopStartSeconds;
282
318
  private _listeners;
283
319
  constructor(audioContext: AudioContext, options?: TransportOptions);
284
320
  get audioContext(): AudioContext;
@@ -297,20 +333,25 @@ declare class Transport {
297
333
  setTrackMute(trackId: string, muted: boolean): void;
298
334
  setTrackSolo(trackId: string, soloed: boolean): void;
299
335
  setMasterVolume(volume: number): void;
300
- setLoop(enabled: boolean, start: number, end: number): void;
301
- setTempo(bpm: number, atTick?: number): void;
302
- getTempo(atTick?: number): number;
303
- setMeter(numerator: number, denominator: number, atTick?: number): void;
304
- getMeter(atTick?: number): MeterSignature;
305
- removeMeter(atTick: number): void;
336
+ /** Primary loop API ticks as source of truth */
337
+ setLoop(enabled: boolean, startTick: Tick, endTick: Tick): void;
338
+ /** Convenience — converts seconds to ticks */
339
+ setLoopSeconds(enabled: boolean, startSec: number, endSec: number): void;
340
+ /** Convenience — sets loop in samples */
341
+ setLoopSamples(enabled: boolean, startSample: Sample, endSample: Sample): void;
342
+ setTempo(bpm: number, atTick?: Tick): void;
343
+ getTempo(atTick?: Tick): number;
344
+ setMeter(numerator: number, denominator: number, atTick?: Tick): void;
345
+ getMeter(atTick?: Tick): MeterSignature;
346
+ removeMeter(atTick: Tick): void;
306
347
  clearMeters(): void;
307
348
  clearTempos(): void;
308
- barToTick(bar: number): number;
309
- tickToBar(tick: number): number;
349
+ barToTick(bar: number): Tick;
350
+ tickToBar(tick: Tick): number;
310
351
  /** Convert transport time (seconds) to tick position, using the tempo map. */
311
- timeToTick(seconds: number): number;
352
+ timeToTick(seconds: number): Tick;
312
353
  /** Convert tick position to transport time (seconds), using the tempo map. */
313
- tickToTime(tick: number): number;
354
+ tickToTime(tick: Tick): number;
314
355
  setMetronomeEnabled(enabled: boolean): void;
315
356
  setMetronomeClickSounds(accent: AudioBuffer, normal: AudioBuffer): void;
316
357
  connectTrackOutput(trackId: string, node: AudioNode): void;
@@ -350,4 +391,4 @@ declare class NativePlayoutAdapter implements PlayoutAdapter {
350
391
  dispose(): void;
351
392
  }
352
393
 
353
- export { type ClipEvent, ClipPlayer, Clock, MasterNode, type MeterEntry, MeterMap, type MeterSignature, type MetronomeEvent, MetronomePlayer, NativePlayoutAdapter, SampleTimeline, Scheduler, type SchedulerEvent, type SchedulerListener, type SchedulerOptions, type TempoEntry, TempoMap, Timer, TrackNode, Transport, type TransportEvents, type TransportOptions, type TransportPosition };
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 };