@dawcore/transport 0.0.2 → 0.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +41 -5
- package/dist/index.d.mts +153 -69
- package/dist/index.d.ts +153 -69
- package/dist/index.js +388 -113
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +388 -113
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
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
|
-
/**
|
|
6
|
-
|
|
15
|
+
/** Tick position (integer) on the timeline */
|
|
16
|
+
tick: Tick;
|
|
7
17
|
}
|
|
8
18
|
interface SchedulerListener<T extends SchedulerEvent> {
|
|
9
|
-
/** Generate events in the
|
|
10
|
-
generate(
|
|
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
|
|
14
|
-
|
|
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:
|
|
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) */
|
|
@@ -45,11 +57,20 @@ interface MeterEntry {
|
|
|
45
57
|
/** Cached cumulative bar count from tick 0 to this entry. Derived — do not set manually. */
|
|
46
58
|
readonly barAtTick: number;
|
|
47
59
|
}
|
|
60
|
+
/** How to interpolate tempo from the previous entry to this one.
|
|
61
|
+
* 'step' = instant jump (default). 'linear' = linear ramp.
|
|
62
|
+
* { type: 'curve', slope } = Möbius-Ease curve (slope 0-1 exclusive). */
|
|
63
|
+
type TempoInterpolation = 'step' | 'linear' | {
|
|
64
|
+
type: 'curve';
|
|
65
|
+
slope: number;
|
|
66
|
+
};
|
|
48
67
|
interface TempoEntry {
|
|
49
68
|
/** Tick position where this tempo starts */
|
|
50
|
-
tick:
|
|
69
|
+
tick: Tick;
|
|
51
70
|
/** Beats per minute */
|
|
52
71
|
bpm: number;
|
|
72
|
+
/** How to arrive at this BPM from the previous entry */
|
|
73
|
+
readonly interpolation: TempoInterpolation;
|
|
53
74
|
/** Cached cumulative seconds up to this tick (for O(log n) lookup). Derived — do not set manually. */
|
|
54
75
|
readonly secondsAtTick: number;
|
|
55
76
|
}
|
|
@@ -58,8 +79,10 @@ interface TransportPosition {
|
|
|
58
79
|
bar: number;
|
|
59
80
|
/** 1-indexed beat within bar */
|
|
60
81
|
beat: number;
|
|
61
|
-
/** Sub-beat tick (0 to ppqn-1)
|
|
62
|
-
|
|
82
|
+
/** Sub-beat tick remainder (0 to ppqn-1). Named subTick to avoid
|
|
83
|
+
* collision with SchedulerEvent.tick (absolute timeline position).
|
|
84
|
+
* Not branded Tick — a remainder within a beat, not an absolute position. */
|
|
85
|
+
subTick: number;
|
|
63
86
|
}
|
|
64
87
|
|
|
65
88
|
declare class Clock {
|
|
@@ -81,10 +104,62 @@ declare class Clock {
|
|
|
81
104
|
isRunning(): boolean;
|
|
82
105
|
}
|
|
83
106
|
|
|
107
|
+
interface SetTempoOptions {
|
|
108
|
+
interpolation?: TempoInterpolation;
|
|
109
|
+
}
|
|
110
|
+
declare class TempoMap {
|
|
111
|
+
private _ppqn;
|
|
112
|
+
private _entries;
|
|
113
|
+
constructor(ppqn?: number, initialBpm?: number);
|
|
114
|
+
getTempo(atTick?: Tick): number;
|
|
115
|
+
setTempo(bpm: number, atTick?: Tick, options?: SetTempoOptions): void;
|
|
116
|
+
ticksToSeconds(ticks: Tick): number;
|
|
117
|
+
secondsToTicks(seconds: number): Tick;
|
|
118
|
+
beatsToSeconds(beats: number): number;
|
|
119
|
+
secondsToBeats(seconds: number): number;
|
|
120
|
+
clearTempos(): void;
|
|
121
|
+
/** Get the interpolated BPM at a tick position */
|
|
122
|
+
private _getTempoAt;
|
|
123
|
+
private _ticksToSecondsInternal;
|
|
124
|
+
/**
|
|
125
|
+
* Exact integration for a linear BPM ramp using the logarithmic formula.
|
|
126
|
+
* For bpm(t) = bpm0 + r*t where r = (bpm1-bpm0)/T:
|
|
127
|
+
* seconds = (T * 60) / (ppqn * (bpm1-bpm0)) * ln(bpmAtTick / bpm0)
|
|
128
|
+
*/
|
|
129
|
+
private _ticksToSecondsLinear;
|
|
130
|
+
/**
|
|
131
|
+
* Inverse of _ticksToSecondsLinear: given seconds, return ticks.
|
|
132
|
+
* Closed-form via exponential: bpmAtTick = bpm0 * exp(seconds * deltaBpm * ppqn / (60 * T))
|
|
133
|
+
* then ticks = (bpmAtTick - bpm0) * T / deltaBpm
|
|
134
|
+
*
|
|
135
|
+
* Note: exp(log(x)) has ~1 ULP floating-point error, so round-trips depend on
|
|
136
|
+
* Math.round() in the caller (secondsToTicks). This is sufficient for all tested
|
|
137
|
+
* BPM ranges (10–300 BPM) but is not algebraically exact like the previous
|
|
138
|
+
* trapezoidal/quadratic approach was.
|
|
139
|
+
*/
|
|
140
|
+
private _secondsToTicksLinear;
|
|
141
|
+
/**
|
|
142
|
+
* Subdivided trapezoidal integration for a Möbius-Ease tempo curve.
|
|
143
|
+
* The BPM at progress p is: bpm0 + curveNormalizedAt(p, slope) * (bpm1 - bpm0).
|
|
144
|
+
* We subdivide into CURVE_SUBDIVISIONS intervals and apply trapezoidal rule.
|
|
145
|
+
*/
|
|
146
|
+
private _ticksToSecondsCurve;
|
|
147
|
+
/**
|
|
148
|
+
* Inverse of _ticksToSecondsCurve: given seconds into a curved segment,
|
|
149
|
+
* return ticks. Uses binary search since there's no closed-form inverse.
|
|
150
|
+
*/
|
|
151
|
+
private _secondsToTicksCurve;
|
|
152
|
+
private _entryIndexAt;
|
|
153
|
+
private _recomputeCache;
|
|
154
|
+
}
|
|
155
|
+
|
|
84
156
|
interface SchedulerOptions {
|
|
85
157
|
lookahead?: number;
|
|
86
|
-
/** Called when the scheduler wraps at loopEnd
|
|
87
|
-
|
|
158
|
+
/** Called when the scheduler wraps at loopEnd.
|
|
159
|
+
* Receives loopStart, loopEnd, and the currentTimeSeconds snapshot from
|
|
160
|
+
* advance() so the Transport can compute the correct clock seek target
|
|
161
|
+
* without re-reading the live AudioContext.currentTime. */
|
|
162
|
+
onLoop?: (loopStartSeconds: number, loopEndSeconds: number, currentTimeSeconds: number) => void;
|
|
88
163
|
}
|
|
89
164
|
declare class Scheduler<T extends SchedulerEvent> {
|
|
90
165
|
private _lookahead;
|
|
@@ -94,12 +169,18 @@ declare class Scheduler<T extends SchedulerEvent> {
|
|
|
94
169
|
private _loopStart;
|
|
95
170
|
private _loopEnd;
|
|
96
171
|
private _onLoop;
|
|
97
|
-
|
|
172
|
+
private _tempoMap;
|
|
173
|
+
constructor(tempoMap: TempoMap, options?: SchedulerOptions);
|
|
98
174
|
addListener(listener: SchedulerListener<T>): void;
|
|
99
175
|
removeListener(listener: SchedulerListener<T>): void;
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
176
|
+
/** Primary API — ticks as source of truth */
|
|
177
|
+
setLoop(enabled: boolean, startTick: Tick, endTick: Tick): void;
|
|
178
|
+
/** Convenience — converts seconds to ticks via TempoMap */
|
|
179
|
+
setLoopSeconds(enabled: boolean, startSec: number, endSec: number): void;
|
|
180
|
+
/** Reset scheduling cursor. Takes seconds (from Clock), converts to ticks. */
|
|
181
|
+
reset(timeSeconds: number): void;
|
|
182
|
+
/** Advance the scheduling window. Takes seconds (from Clock), converts to ticks. */
|
|
183
|
+
advance(currentTimeSeconds: number): void;
|
|
103
184
|
private _generateAndConsume;
|
|
104
185
|
}
|
|
105
186
|
|
|
@@ -115,26 +196,14 @@ declare class Timer {
|
|
|
115
196
|
|
|
116
197
|
declare class SampleTimeline {
|
|
117
198
|
private _sampleRate;
|
|
199
|
+
private _tempoMap;
|
|
118
200
|
constructor(sampleRate: number);
|
|
119
201
|
get sampleRate(): number;
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
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;
|
|
202
|
+
setTempoMap(tempoMap: TempoMap): void;
|
|
203
|
+
samplesToSeconds(samples: Sample): number;
|
|
204
|
+
secondsToSamples(seconds: number): Sample;
|
|
205
|
+
ticksToSamples(ticks: Tick): Sample;
|
|
206
|
+
samplesToTicks(samples: Sample): Tick;
|
|
138
207
|
}
|
|
139
208
|
|
|
140
209
|
declare class MeterMap {
|
|
@@ -142,17 +211,17 @@ declare class MeterMap {
|
|
|
142
211
|
private _entries;
|
|
143
212
|
constructor(ppqn: number, numerator?: number, denominator?: number);
|
|
144
213
|
get ppqn(): number;
|
|
145
|
-
getMeter(atTick?:
|
|
146
|
-
setMeter(numerator: number, denominator: number, atTick?:
|
|
147
|
-
removeMeter(atTick:
|
|
214
|
+
getMeter(atTick?: Tick): MeterSignature;
|
|
215
|
+
setMeter(numerator: number, denominator: number, atTick?: Tick): void;
|
|
216
|
+
removeMeter(atTick: Tick): void;
|
|
148
217
|
clearMeters(): void;
|
|
149
|
-
ticksPerBeat(atTick?:
|
|
150
|
-
ticksPerBar(atTick?:
|
|
151
|
-
barToTick(bar: number):
|
|
152
|
-
tickToBar(tick:
|
|
153
|
-
isBarBoundary(tick:
|
|
218
|
+
ticksPerBeat(atTick?: Tick): number;
|
|
219
|
+
ticksPerBar(atTick?: Tick): number;
|
|
220
|
+
barToTick(bar: number): Tick;
|
|
221
|
+
tickToBar(tick: Tick): number;
|
|
222
|
+
isBarBoundary(tick: Tick): boolean;
|
|
154
223
|
/** Internal: get the full entry at a tick (for MetronomePlayer beat grid anchoring) */
|
|
155
|
-
getEntryAt(tick:
|
|
224
|
+
getEntryAt(tick: Tick): MeterEntry;
|
|
156
225
|
private _entryAt;
|
|
157
226
|
private _ticksPerBarForEntry;
|
|
158
227
|
private _snapToBarBoundary;
|
|
@@ -199,33 +268,40 @@ interface ClipEvent extends SchedulerEvent {
|
|
|
199
268
|
trackId: string;
|
|
200
269
|
clipId: string;
|
|
201
270
|
audioBuffer: AudioBuffer;
|
|
202
|
-
/**
|
|
203
|
-
|
|
204
|
-
/**
|
|
205
|
-
|
|
271
|
+
/** Clip position on timeline (integer samples) */
|
|
272
|
+
startSample: Sample;
|
|
273
|
+
/** Offset into audioBuffer (integer samples) */
|
|
274
|
+
offsetSamples: Sample;
|
|
275
|
+
/** Duration to play (integer samples) */
|
|
276
|
+
durationSamples: Sample;
|
|
206
277
|
/** Clip gain multiplier */
|
|
207
278
|
gain: number;
|
|
208
|
-
/** Fade in duration
|
|
209
|
-
|
|
210
|
-
/** Fade out duration
|
|
211
|
-
|
|
279
|
+
/** Fade in duration (integer samples) */
|
|
280
|
+
fadeInDurationSamples: Sample;
|
|
281
|
+
/** Fade out duration (integer samples) */
|
|
282
|
+
fadeOutDurationSamples: Sample;
|
|
212
283
|
}
|
|
213
284
|
declare class ClipPlayer implements SchedulerListener<ClipEvent> {
|
|
214
285
|
private _audioContext;
|
|
215
286
|
private _sampleTimeline;
|
|
287
|
+
private _tempoMap;
|
|
216
288
|
private _toAudioTime;
|
|
217
289
|
private _tracks;
|
|
218
290
|
private _trackNodes;
|
|
219
291
|
private _activeSources;
|
|
220
292
|
private _loopEnabled;
|
|
221
|
-
private
|
|
222
|
-
constructor(audioContext: AudioContext, sampleTimeline: SampleTimeline, toAudioTime: (transportTime: number) => number);
|
|
293
|
+
private _loopEndSamples;
|
|
294
|
+
constructor(audioContext: AudioContext, sampleTimeline: SampleTimeline, tempoMap: TempoMap, toAudioTime: (transportTime: number) => number);
|
|
223
295
|
setTracks(tracks: ClipTrack[], trackNodes: Map<string, TrackNode>): void;
|
|
224
|
-
|
|
296
|
+
/** Set loop region using ticks. startTick is unused — loop clamping only needs
|
|
297
|
+
* the end boundary; mid-clip restart at loopStart is handled by onPositionJump. */
|
|
298
|
+
setLoop(enabled: boolean, _startTick: Tick, endTick: Tick): void;
|
|
299
|
+
/** Set loop region using samples directly */
|
|
300
|
+
setLoopSamples(enabled: boolean, _startSample: Sample, endSample: Sample): void;
|
|
225
301
|
updateTrack(trackId: string, track: ClipTrack): void;
|
|
226
|
-
generate(
|
|
302
|
+
generate(fromTick: Tick, toTick: Tick): ClipEvent[];
|
|
227
303
|
consume(event: ClipEvent): void;
|
|
228
|
-
onPositionJump(
|
|
304
|
+
onPositionJump(newTick: Tick): void;
|
|
229
305
|
silence(): void;
|
|
230
306
|
private _silenceTrack;
|
|
231
307
|
}
|
|
@@ -247,9 +323,9 @@ declare class MetronomePlayer implements SchedulerListener<MetronomeEvent> {
|
|
|
247
323
|
constructor(audioContext: AudioContext, tempoMap: TempoMap, meterMap: MeterMap, destination: AudioNode, toAudioTime: (transportTime: number) => number);
|
|
248
324
|
setEnabled(enabled: boolean): void;
|
|
249
325
|
setClickSounds(accent: AudioBuffer, normal: AudioBuffer): void;
|
|
250
|
-
generate(
|
|
326
|
+
generate(fromTick: Tick, toTick: Tick): MetronomeEvent[];
|
|
251
327
|
consume(event: MetronomeEvent): void;
|
|
252
|
-
onPositionJump(
|
|
328
|
+
onPositionJump(_newTick: Tick): void;
|
|
253
329
|
silence(): void;
|
|
254
330
|
}
|
|
255
331
|
|
|
@@ -279,6 +355,9 @@ declare class Transport {
|
|
|
279
355
|
private _mutedTrackIds;
|
|
280
356
|
private _playing;
|
|
281
357
|
private _endTime;
|
|
358
|
+
private _loopEnabled;
|
|
359
|
+
private _loopStartTick;
|
|
360
|
+
private _loopStartSeconds;
|
|
282
361
|
private _listeners;
|
|
283
362
|
constructor(audioContext: AudioContext, options?: TransportOptions);
|
|
284
363
|
get audioContext(): AudioContext;
|
|
@@ -297,20 +376,25 @@ declare class Transport {
|
|
|
297
376
|
setTrackMute(trackId: string, muted: boolean): void;
|
|
298
377
|
setTrackSolo(trackId: string, soloed: boolean): void;
|
|
299
378
|
setMasterVolume(volume: number): void;
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
379
|
+
/** Primary loop API — ticks as source of truth */
|
|
380
|
+
setLoop(enabled: boolean, startTick: Tick, endTick: Tick): void;
|
|
381
|
+
/** Convenience — converts seconds to ticks */
|
|
382
|
+
setLoopSeconds(enabled: boolean, startSec: number, endSec: number): void;
|
|
383
|
+
/** Convenience — sets loop in samples */
|
|
384
|
+
setLoopSamples(enabled: boolean, startSample: Sample, endSample: Sample): void;
|
|
385
|
+
setTempo(bpm: number, atTick?: Tick, options?: SetTempoOptions): void;
|
|
386
|
+
getTempo(atTick?: Tick): number;
|
|
387
|
+
setMeter(numerator: number, denominator: number, atTick?: Tick): void;
|
|
388
|
+
getMeter(atTick?: Tick): MeterSignature;
|
|
389
|
+
removeMeter(atTick: Tick): void;
|
|
306
390
|
clearMeters(): void;
|
|
307
391
|
clearTempos(): void;
|
|
308
|
-
barToTick(bar: number):
|
|
309
|
-
tickToBar(tick:
|
|
392
|
+
barToTick(bar: number): Tick;
|
|
393
|
+
tickToBar(tick: Tick): number;
|
|
310
394
|
/** Convert transport time (seconds) to tick position, using the tempo map. */
|
|
311
|
-
timeToTick(seconds: number):
|
|
395
|
+
timeToTick(seconds: number): Tick;
|
|
312
396
|
/** Convert tick position to transport time (seconds), using the tempo map. */
|
|
313
|
-
tickToTime(tick:
|
|
397
|
+
tickToTime(tick: Tick): number;
|
|
314
398
|
setMetronomeEnabled(enabled: boolean): void;
|
|
315
399
|
setMetronomeClickSounds(accent: AudioBuffer, normal: AudioBuffer): void;
|
|
316
400
|
connectTrackOutput(trackId: string, node: AudioNode): void;
|
|
@@ -350,4 +434,4 @@ declare class NativePlayoutAdapter implements PlayoutAdapter {
|
|
|
350
434
|
dispose(): void;
|
|
351
435
|
}
|
|
352
436
|
|
|
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 };
|
|
437
|
+
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 SetTempoOptions, type TempoEntry, type TempoInterpolation, TempoMap, type Tick, Timer, TrackNode, Transport, type TransportEvents, type TransportOptions, type TransportPosition };
|