@dawcore/transport 0.0.1 → 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/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
  }
@@ -22,14 +34,32 @@ interface TransportOptions {
22
34
  ppqn?: number;
23
35
  /** Initial tempo in BPM. Default: 120 */
24
36
  tempo?: number;
25
- /** Beats per bar. Default: 4 */
26
- beatsPerBar?: number;
37
+ /** Time signature numerator. Default: 4 */
38
+ numerator?: number;
39
+ /** Time signature denominator. Default: 4 */
40
+ denominator?: number;
27
41
  /** How far ahead to schedule audio, in seconds. Default: 0.2 */
28
42
  schedulerLookahead?: number;
29
43
  }
44
+ /** Public return type for getMeter() */
45
+ interface MeterSignature {
46
+ numerator: number;
47
+ denominator: number;
48
+ }
49
+ /** Storage entry for MeterMap */
50
+ interface MeterEntry {
51
+ /** Tick position where this meter starts */
52
+ tick: Tick;
53
+ /** Time signature numerator (e.g., 6 in 6/8) */
54
+ numerator: number;
55
+ /** Time signature denominator (e.g., 8 in 6/8) */
56
+ denominator: number;
57
+ /** Cached cumulative bar count from tick 0 to this entry. Derived — do not set manually. */
58
+ readonly barAtTick: number;
59
+ }
30
60
  interface TempoEntry {
31
61
  /** Tick position where this tempo starts */
32
- tick: number;
62
+ tick: Tick;
33
63
  /** Beats per minute */
34
64
  bpm: number;
35
65
  /** Cached cumulative seconds up to this tick (for O(log n) lookup). Derived — do not set manually. */
@@ -40,8 +70,10 @@ interface TransportPosition {
40
70
  bar: number;
41
71
  /** 1-indexed beat within bar */
42
72
  beat: number;
43
- /** Sub-beat tick (0 to ppqn-1) */
44
- 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;
45
77
  }
46
78
 
47
79
  declare class Clock {
@@ -63,10 +95,29 @@ declare class Clock {
63
95
  isRunning(): boolean;
64
96
  }
65
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
+
66
114
  interface SchedulerOptions {
67
115
  lookahead?: number;
68
- /** Called when the scheduler wraps at loopEnd — Transport uses this to seek the clock */
69
- 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;
70
121
  }
71
122
  declare class Scheduler<T extends SchedulerEvent> {
72
123
  private _lookahead;
@@ -76,12 +127,18 @@ declare class Scheduler<T extends SchedulerEvent> {
76
127
  private _loopStart;
77
128
  private _loopEnd;
78
129
  private _onLoop;
79
- constructor(options?: SchedulerOptions);
130
+ private _tempoMap;
131
+ constructor(tempoMap: TempoMap, options?: SchedulerOptions);
80
132
  addListener(listener: SchedulerListener<T>): void;
81
133
  removeListener(listener: SchedulerListener<T>): void;
82
- setLoop(enabled: boolean, start: number, end: number): void;
83
- reset(time: number): void;
84
- 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;
85
142
  private _generateAndConsume;
86
143
  }
87
144
 
@@ -97,35 +154,43 @@ declare class Timer {
97
154
 
98
155
  declare class SampleTimeline {
99
156
  private _sampleRate;
157
+ private _tempoMap;
100
158
  constructor(sampleRate: number);
101
159
  get sampleRate(): number;
102
- samplesToSeconds(samples: number): number;
103
- secondsToSamples(seconds: number): number;
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;
104
165
  }
105
166
 
106
- declare class TickTimeline {
107
- private _ppqn;
108
- constructor(ppqn?: number);
109
- get ppqn(): number;
110
- ticksPerBeat(): number;
111
- ticksPerBar(beatsPerBar: number): number;
112
- toPosition(ticks: number, beatsPerBar: number): TransportPosition;
113
- fromPosition(bar: number, beat: number, tick: number, beatsPerBar: number): number;
114
- }
115
-
116
- declare class TempoMap {
167
+ declare class MeterMap {
117
168
  private _ppqn;
118
169
  private _entries;
119
- constructor(ppqn?: number, initialBpm?: number);
120
- getTempo(atTick?: number): number;
121
- setTempo(bpm: number, atTick?: number): void;
122
- ticksToSeconds(ticks: number): number;
123
- secondsToTicks(seconds: number): number;
124
- beatsToSeconds(beats: number): number;
125
- secondsToBeats(seconds: number): number;
126
- private _ticksToSecondsInternal;
170
+ constructor(ppqn: number, numerator?: number, denominator?: number);
171
+ get ppqn(): number;
172
+ getMeter(atTick?: Tick): MeterSignature;
173
+ setMeter(numerator: number, denominator: number, atTick?: Tick): void;
174
+ removeMeter(atTick: Tick): void;
175
+ clearMeters(): void;
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;
181
+ /** Internal: get the full entry at a tick (for MetronomePlayer beat grid anchoring) */
182
+ getEntryAt(tick: Tick): MeterEntry;
127
183
  private _entryAt;
184
+ private _ticksPerBarForEntry;
185
+ private _snapToBarBoundary;
186
+ private _computeBarAtTick;
128
187
  private _recomputeCache;
188
+ /**
189
+ * After changing a meter entry, re-snap downstream entries to bar boundaries
190
+ * of their preceding meter so barAtTick stays integer.
191
+ */
192
+ private _resnapDownstreamEntries;
193
+ private _validateMeter;
129
194
  }
130
195
 
131
196
  declare class MasterNode {
@@ -161,33 +226,40 @@ interface ClipEvent extends SchedulerEvent {
161
226
  trackId: string;
162
227
  clipId: string;
163
228
  audioBuffer: AudioBuffer;
164
- /** Offset into the audioBuffer (seconds) */
165
- offset: number;
166
- /** Duration to play (seconds) */
167
- 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;
168
235
  /** Clip gain multiplier */
169
236
  gain: number;
170
- /** Fade in duration in seconds */
171
- fadeInDuration: number;
172
- /** Fade out duration in seconds */
173
- fadeOutDuration: number;
237
+ /** Fade in duration (integer samples) */
238
+ fadeInDurationSamples: Sample;
239
+ /** Fade out duration (integer samples) */
240
+ fadeOutDurationSamples: Sample;
174
241
  }
175
242
  declare class ClipPlayer implements SchedulerListener<ClipEvent> {
176
243
  private _audioContext;
177
244
  private _sampleTimeline;
245
+ private _tempoMap;
178
246
  private _toAudioTime;
179
247
  private _tracks;
180
248
  private _trackNodes;
181
249
  private _activeSources;
182
250
  private _loopEnabled;
183
- private _loopEnd;
184
- constructor(audioContext: AudioContext, sampleTimeline: SampleTimeline, toAudioTime: (transportTime: number) => number);
251
+ private _loopEndSamples;
252
+ constructor(audioContext: AudioContext, sampleTimeline: SampleTimeline, tempoMap: TempoMap, toAudioTime: (transportTime: number) => number);
185
253
  setTracks(tracks: ClipTrack[], trackNodes: Map<string, TrackNode>): void;
186
- 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;
187
259
  updateTrack(trackId: string, track: ClipTrack): void;
188
- generate(fromTime: number, toTime: number): ClipEvent[];
260
+ generate(fromTick: Tick, toTick: Tick): ClipEvent[];
189
261
  consume(event: ClipEvent): void;
190
- onPositionJump(newTime: number): void;
262
+ onPositionJump(newTick: Tick): void;
191
263
  silence(): void;
192
264
  private _silenceTrack;
193
265
  }
@@ -199,21 +271,19 @@ interface MetronomeEvent extends SchedulerEvent {
199
271
  declare class MetronomePlayer implements SchedulerListener<MetronomeEvent> {
200
272
  private _audioContext;
201
273
  private _tempoMap;
202
- private _tickTimeline;
274
+ private _meterMap;
203
275
  private _destination;
204
276
  private _toAudioTime;
205
277
  private _enabled;
206
- private _beatsPerBar;
207
278
  private _accentBuffer;
208
279
  private _normalBuffer;
209
280
  private _activeSources;
210
- constructor(audioContext: AudioContext, tempoMap: TempoMap, tickTimeline: TickTimeline, destination: AudioNode, toAudioTime: (transportTime: number) => number);
281
+ constructor(audioContext: AudioContext, tempoMap: TempoMap, meterMap: MeterMap, destination: AudioNode, toAudioTime: (transportTime: number) => number);
211
282
  setEnabled(enabled: boolean): void;
212
- setBeatsPerBar(beats: number): void;
213
283
  setClickSounds(accent: AudioBuffer, normal: AudioBuffer): void;
214
- generate(fromTime: number, toTime: number): MetronomeEvent[];
284
+ generate(fromTick: Tick, toTick: Tick): MetronomeEvent[];
215
285
  consume(event: MetronomeEvent): void;
216
- onPositionJump(_newTime: number): void;
286
+ onPositionJump(_newTick: Tick): void;
217
287
  silence(): void;
218
288
  }
219
289
 
@@ -223,6 +293,7 @@ interface TransportEvents {
223
293
  stop: () => void;
224
294
  loop: () => void;
225
295
  tempochange: () => void;
296
+ meterchange: () => void;
226
297
  }
227
298
  type TransportEventType = keyof TransportEvents;
228
299
  declare class Transport {
@@ -231,7 +302,7 @@ declare class Transport {
231
302
  private _scheduler;
232
303
  private _timer;
233
304
  private _sampleTimeline;
234
- private _tickTimeline;
305
+ private _meterMap;
235
306
  private _tempoMap;
236
307
  private _clipPlayer;
237
308
  private _metronomePlayer;
@@ -242,6 +313,8 @@ declare class Transport {
242
313
  private _mutedTrackIds;
243
314
  private _playing;
244
315
  private _endTime;
316
+ private _loopEnabled;
317
+ private _loopStartSeconds;
245
318
  private _listeners;
246
319
  constructor(audioContext: AudioContext, options?: TransportOptions);
247
320
  get audioContext(): AudioContext;
@@ -260,10 +333,25 @@ declare class Transport {
260
333
  setTrackMute(trackId: string, muted: boolean): void;
261
334
  setTrackSolo(trackId: string, soloed: boolean): void;
262
335
  setMasterVolume(volume: number): void;
263
- setLoop(enabled: boolean, start: number, end: number): void;
264
- setTempo(bpm: number): void;
265
- getTempo(): number;
266
- setBeatsPerBar(beats: 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;
347
+ clearMeters(): void;
348
+ clearTempos(): void;
349
+ barToTick(bar: number): Tick;
350
+ tickToBar(tick: Tick): number;
351
+ /** Convert transport time (seconds) to tick position, using the tempo map. */
352
+ timeToTick(seconds: number): Tick;
353
+ /** Convert tick position to transport time (seconds), using the tempo map. */
354
+ tickToTime(tick: Tick): number;
267
355
  setMetronomeEnabled(enabled: boolean): void;
268
356
  setMetronomeClickSounds(accent: AudioBuffer, normal: AudioBuffer): void;
269
357
  connectTrackOutput(trackId: string, node: AudioNode): void;
@@ -303,4 +391,4 @@ declare class NativePlayoutAdapter implements PlayoutAdapter {
303
391
  dispose(): void;
304
392
  }
305
393
 
306
- export { type ClipEvent, ClipPlayer, Clock, MasterNode, type MetronomeEvent, MetronomePlayer, NativePlayoutAdapter, SampleTimeline, Scheduler, type SchedulerEvent, type SchedulerListener, type SchedulerOptions, type TempoEntry, TempoMap, TickTimeline, 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 };