@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/README.md +34 -7
- package/dist/index.d.mts +151 -63
- package/dist/index.d.ts +151 -63
- package/dist/index.js +438 -142
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +437 -141
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -23,12 +23,12 @@ __export(index_exports, {
|
|
|
23
23
|
ClipPlayer: () => ClipPlayer,
|
|
24
24
|
Clock: () => Clock,
|
|
25
25
|
MasterNode: () => MasterNode,
|
|
26
|
+
MeterMap: () => MeterMap,
|
|
26
27
|
MetronomePlayer: () => MetronomePlayer,
|
|
27
28
|
NativePlayoutAdapter: () => NativePlayoutAdapter,
|
|
28
29
|
SampleTimeline: () => SampleTimeline,
|
|
29
30
|
Scheduler: () => Scheduler,
|
|
30
31
|
TempoMap: () => TempoMap,
|
|
31
|
-
TickTimeline: () => TickTimeline,
|
|
32
32
|
Timer: () => Timer,
|
|
33
33
|
TrackNode: () => TrackNode,
|
|
34
34
|
Transport: () => Transport
|
|
@@ -86,12 +86,15 @@ var Clock = class {
|
|
|
86
86
|
|
|
87
87
|
// src/core/scheduler.ts
|
|
88
88
|
var Scheduler = class {
|
|
89
|
-
constructor(options = {}) {
|
|
89
|
+
constructor(tempoMap, options = {}) {
|
|
90
90
|
this._rightEdge = 0;
|
|
91
|
+
// integer ticks
|
|
91
92
|
this._listeners = /* @__PURE__ */ new Set();
|
|
92
93
|
this._loopEnabled = false;
|
|
93
94
|
this._loopStart = 0;
|
|
95
|
+
// integer ticks
|
|
94
96
|
this._loopEnd = 0;
|
|
97
|
+
this._tempoMap = tempoMap;
|
|
95
98
|
this._lookahead = options.lookahead ?? 0.2;
|
|
96
99
|
this._onLoop = options.onLoop;
|
|
97
100
|
}
|
|
@@ -101,25 +104,40 @@ var Scheduler = class {
|
|
|
101
104
|
removeListener(listener) {
|
|
102
105
|
this._listeners.delete(listener);
|
|
103
106
|
}
|
|
104
|
-
|
|
105
|
-
|
|
107
|
+
/** Primary API — ticks as source of truth */
|
|
108
|
+
setLoop(enabled, startTick, endTick) {
|
|
109
|
+
if (enabled && (!Number.isFinite(startTick) || !Number.isFinite(endTick))) {
|
|
110
|
+
console.warn(
|
|
111
|
+
"[waveform-playlist] Scheduler.setLoop: non-finite tick values (" + startTick + ", " + endTick + ")"
|
|
112
|
+
);
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
if (enabled && startTick >= endTick) {
|
|
106
116
|
console.warn(
|
|
107
|
-
"[waveform-playlist] Scheduler.setLoop:
|
|
117
|
+
"[waveform-playlist] Scheduler.setLoop: startTick (" + startTick + ") must be less than endTick (" + endTick + ")"
|
|
108
118
|
);
|
|
109
119
|
return;
|
|
110
120
|
}
|
|
111
121
|
this._loopEnabled = enabled;
|
|
112
|
-
this._loopStart =
|
|
113
|
-
this._loopEnd =
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
122
|
+
this._loopStart = Math.round(startTick);
|
|
123
|
+
this._loopEnd = Math.round(endTick);
|
|
124
|
+
}
|
|
125
|
+
/** Convenience — converts seconds to ticks via TempoMap */
|
|
126
|
+
setLoopSeconds(enabled, startSec, endSec) {
|
|
127
|
+
const startTick = this._tempoMap.secondsToTicks(startSec);
|
|
128
|
+
const endTick = this._tempoMap.secondsToTicks(endSec);
|
|
129
|
+
this.setLoop(enabled, startTick, endTick);
|
|
130
|
+
}
|
|
131
|
+
/** Reset scheduling cursor. Takes seconds (from Clock), converts to ticks. */
|
|
132
|
+
reset(timeSeconds) {
|
|
133
|
+
this._rightEdge = this._tempoMap.secondsToTicks(timeSeconds);
|
|
134
|
+
}
|
|
135
|
+
/** Advance the scheduling window. Takes seconds (from Clock), converts to ticks. */
|
|
136
|
+
advance(currentTimeSeconds) {
|
|
137
|
+
const targetTick = this._tempoMap.secondsToTicks(currentTimeSeconds + this._lookahead);
|
|
120
138
|
if (this._loopEnabled && this._loopEnd > this._loopStart) {
|
|
121
139
|
const loopDuration = this._loopEnd - this._loopStart;
|
|
122
|
-
let remaining =
|
|
140
|
+
let remaining = targetTick - this._rightEdge;
|
|
123
141
|
while (remaining > 0) {
|
|
124
142
|
const distToEnd = this._loopEnd - this._rightEdge;
|
|
125
143
|
if (distToEnd <= 0 || distToEnd > remaining) {
|
|
@@ -132,21 +150,25 @@ var Scheduler = class {
|
|
|
132
150
|
for (const listener of this._listeners) {
|
|
133
151
|
listener.onPositionJump(this._loopStart);
|
|
134
152
|
}
|
|
135
|
-
this._onLoop?.(
|
|
153
|
+
this._onLoop?.(
|
|
154
|
+
this._tempoMap.ticksToSeconds(this._loopStart),
|
|
155
|
+
this._tempoMap.ticksToSeconds(this._loopEnd),
|
|
156
|
+
currentTimeSeconds
|
|
157
|
+
);
|
|
136
158
|
this._rightEdge = this._loopStart;
|
|
137
159
|
if (loopDuration <= 0) break;
|
|
138
160
|
}
|
|
139
161
|
return;
|
|
140
162
|
}
|
|
141
|
-
if (
|
|
142
|
-
this._generateAndConsume(this._rightEdge,
|
|
143
|
-
this._rightEdge =
|
|
163
|
+
if (targetTick > this._rightEdge) {
|
|
164
|
+
this._generateAndConsume(this._rightEdge, targetTick);
|
|
165
|
+
this._rightEdge = targetTick;
|
|
144
166
|
}
|
|
145
167
|
}
|
|
146
|
-
_generateAndConsume(
|
|
168
|
+
_generateAndConsume(fromTick, toTick) {
|
|
147
169
|
for (const listener of this._listeners) {
|
|
148
170
|
try {
|
|
149
|
-
const events = listener.generate(
|
|
171
|
+
const events = listener.generate(fromTick, toTick);
|
|
150
172
|
for (const event of events) {
|
|
151
173
|
try {
|
|
152
174
|
listener.consume(event);
|
|
@@ -196,44 +218,36 @@ var Timer = class {
|
|
|
196
218
|
// src/timeline/sample-timeline.ts
|
|
197
219
|
var SampleTimeline = class {
|
|
198
220
|
constructor(sampleRate) {
|
|
221
|
+
this._tempoMap = null;
|
|
199
222
|
this._sampleRate = sampleRate;
|
|
200
223
|
}
|
|
201
224
|
get sampleRate() {
|
|
202
225
|
return this._sampleRate;
|
|
203
226
|
}
|
|
227
|
+
setTempoMap(tempoMap) {
|
|
228
|
+
this._tempoMap = tempoMap;
|
|
229
|
+
}
|
|
204
230
|
samplesToSeconds(samples) {
|
|
205
231
|
return samples / this._sampleRate;
|
|
206
232
|
}
|
|
207
233
|
secondsToSamples(seconds) {
|
|
208
234
|
return Math.round(seconds * this._sampleRate);
|
|
209
235
|
}
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
get ppqn() {
|
|
218
|
-
return this._ppqn;
|
|
219
|
-
}
|
|
220
|
-
ticksPerBeat() {
|
|
221
|
-
return this._ppqn;
|
|
222
|
-
}
|
|
223
|
-
ticksPerBar(beatsPerBar) {
|
|
224
|
-
return this._ppqn * beatsPerBar;
|
|
225
|
-
}
|
|
226
|
-
toPosition(ticks, beatsPerBar) {
|
|
227
|
-
const ticksPerBar = this.ticksPerBar(beatsPerBar);
|
|
228
|
-
const bar = Math.floor(ticks / ticksPerBar) + 1;
|
|
229
|
-
const remaining = ticks % ticksPerBar;
|
|
230
|
-
const beat = Math.floor(remaining / this._ppqn) + 1;
|
|
231
|
-
const tick = remaining % this._ppqn;
|
|
232
|
-
return { bar, beat, tick };
|
|
236
|
+
ticksToSamples(ticks) {
|
|
237
|
+
if (!this._tempoMap) {
|
|
238
|
+
throw new Error(
|
|
239
|
+
"[waveform-playlist] SampleTimeline: tempoMap not set \u2014 call setTempoMap() first"
|
|
240
|
+
);
|
|
241
|
+
}
|
|
242
|
+
return Math.round(this._tempoMap.ticksToSeconds(ticks) * this._sampleRate);
|
|
233
243
|
}
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
244
|
+
samplesToTicks(samples) {
|
|
245
|
+
if (!this._tempoMap) {
|
|
246
|
+
throw new Error(
|
|
247
|
+
"[waveform-playlist] SampleTimeline: tempoMap not set \u2014 call setTempoMap() first"
|
|
248
|
+
);
|
|
249
|
+
}
|
|
250
|
+
return this._tempoMap.secondsToTicks(samples / this._sampleRate);
|
|
237
251
|
}
|
|
238
252
|
};
|
|
239
253
|
|
|
@@ -281,7 +295,7 @@ var TempoMap = class {
|
|
|
281
295
|
const entry = this._entries[lo];
|
|
282
296
|
const secondsIntoSegment = seconds - entry.secondsAtTick;
|
|
283
297
|
const ticksPerSecond = entry.bpm / 60 * this._ppqn;
|
|
284
|
-
return entry.tick + secondsIntoSegment * ticksPerSecond;
|
|
298
|
+
return Math.round(entry.tick + secondsIntoSegment * ticksPerSecond);
|
|
285
299
|
}
|
|
286
300
|
beatsToSeconds(beats) {
|
|
287
301
|
return this.ticksToSeconds(beats * this._ppqn);
|
|
@@ -289,6 +303,10 @@ var TempoMap = class {
|
|
|
289
303
|
secondsToBeats(seconds) {
|
|
290
304
|
return this.secondsToTicks(seconds) / this._ppqn;
|
|
291
305
|
}
|
|
306
|
+
clearTempos() {
|
|
307
|
+
const first = this._entries[0];
|
|
308
|
+
this._entries = [{ tick: 0, bpm: first.bpm, secondsAtTick: 0 }];
|
|
309
|
+
}
|
|
292
310
|
_ticksToSecondsInternal(ticks) {
|
|
293
311
|
const entry = this._entryAt(ticks);
|
|
294
312
|
const ticksIntoSegment = ticks - entry.tick;
|
|
@@ -321,6 +339,179 @@ var TempoMap = class {
|
|
|
321
339
|
}
|
|
322
340
|
};
|
|
323
341
|
|
|
342
|
+
// src/timeline/meter-map.ts
|
|
343
|
+
function isPowerOf2(n) {
|
|
344
|
+
return n > 0 && (n & n - 1) === 0;
|
|
345
|
+
}
|
|
346
|
+
var MeterMap = class {
|
|
347
|
+
constructor(ppqn, numerator = 4, denominator = 4) {
|
|
348
|
+
this._ppqn = ppqn;
|
|
349
|
+
this._entries = [{ tick: 0, numerator, denominator, barAtTick: 0 }];
|
|
350
|
+
}
|
|
351
|
+
get ppqn() {
|
|
352
|
+
return this._ppqn;
|
|
353
|
+
}
|
|
354
|
+
getMeter(atTick = 0) {
|
|
355
|
+
const entry = this._entryAt(atTick);
|
|
356
|
+
return { numerator: entry.numerator, denominator: entry.denominator };
|
|
357
|
+
}
|
|
358
|
+
setMeter(numerator, denominator, atTick = 0) {
|
|
359
|
+
this._validateMeter(numerator, denominator);
|
|
360
|
+
if (atTick < 0) {
|
|
361
|
+
throw new Error("[waveform-playlist] MeterMap: atTick must be non-negative, got " + atTick);
|
|
362
|
+
}
|
|
363
|
+
if (atTick === 0) {
|
|
364
|
+
this._entries[0] = { ...this._entries[0], numerator, denominator };
|
|
365
|
+
this._resnapDownstreamEntries(0);
|
|
366
|
+
this._recomputeCache(0);
|
|
367
|
+
return;
|
|
368
|
+
}
|
|
369
|
+
const snapped = this._snapToBarBoundary(atTick);
|
|
370
|
+
if (snapped !== atTick) {
|
|
371
|
+
console.warn(
|
|
372
|
+
"[waveform-playlist] MeterMap.setMeter: tick " + atTick + " is not on a bar boundary, snapped to " + snapped
|
|
373
|
+
);
|
|
374
|
+
}
|
|
375
|
+
let i = this._entries.length - 1;
|
|
376
|
+
while (i > 0 && this._entries[i].tick > snapped) i--;
|
|
377
|
+
if (this._entries[i].tick === snapped) {
|
|
378
|
+
this._entries[i] = { ...this._entries[i], numerator, denominator };
|
|
379
|
+
} else {
|
|
380
|
+
const barAtTick = this._computeBarAtTick(snapped);
|
|
381
|
+
this._entries.splice(i + 1, 0, { tick: snapped, numerator, denominator, barAtTick });
|
|
382
|
+
i = i + 1;
|
|
383
|
+
}
|
|
384
|
+
this._resnapDownstreamEntries(i);
|
|
385
|
+
this._recomputeCache(i);
|
|
386
|
+
}
|
|
387
|
+
removeMeter(atTick) {
|
|
388
|
+
if (atTick === 0) {
|
|
389
|
+
throw new Error("[waveform-playlist] MeterMap: cannot remove meter at tick 0");
|
|
390
|
+
}
|
|
391
|
+
const idx = this._entries.findIndex((e) => e.tick === atTick);
|
|
392
|
+
if (idx > 0) {
|
|
393
|
+
this._entries.splice(idx, 1);
|
|
394
|
+
this._recomputeCache(idx);
|
|
395
|
+
} else if (idx === -1) {
|
|
396
|
+
console.warn("[waveform-playlist] MeterMap.removeMeter: no entry at tick " + atTick);
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
clearMeters() {
|
|
400
|
+
const first = this._entries[0];
|
|
401
|
+
this._entries = [{ ...first, barAtTick: 0 }];
|
|
402
|
+
}
|
|
403
|
+
ticksPerBeat(atTick = 0) {
|
|
404
|
+
const entry = this._entryAt(atTick);
|
|
405
|
+
return this._ppqn * (4 / entry.denominator);
|
|
406
|
+
}
|
|
407
|
+
ticksPerBar(atTick = 0) {
|
|
408
|
+
const entry = this._entryAt(atTick);
|
|
409
|
+
return entry.numerator * this._ppqn * (4 / entry.denominator);
|
|
410
|
+
}
|
|
411
|
+
barToTick(bar) {
|
|
412
|
+
if (bar < 1) {
|
|
413
|
+
throw new Error("[waveform-playlist] MeterMap: bar must be >= 1, got " + bar);
|
|
414
|
+
}
|
|
415
|
+
const targetBar = bar - 1;
|
|
416
|
+
for (let i = 0; i < this._entries.length; i++) {
|
|
417
|
+
const nextBar = i < this._entries.length - 1 ? this._entries[i + 1].barAtTick : Infinity;
|
|
418
|
+
if (targetBar < nextBar) {
|
|
419
|
+
const barsInto = targetBar - this._entries[i].barAtTick;
|
|
420
|
+
const tpb = this._ticksPerBarForEntry(this._entries[i]);
|
|
421
|
+
return this._entries[i].tick + barsInto * tpb;
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
return 0;
|
|
425
|
+
}
|
|
426
|
+
tickToBar(tick) {
|
|
427
|
+
const entry = this._entryAt(tick);
|
|
428
|
+
const ticksInto = tick - entry.tick;
|
|
429
|
+
const tpb = this._ticksPerBarForEntry(entry);
|
|
430
|
+
return entry.barAtTick + Math.floor(ticksInto / tpb) + 1;
|
|
431
|
+
}
|
|
432
|
+
isBarBoundary(tick) {
|
|
433
|
+
const entry = this._entryAt(tick);
|
|
434
|
+
const ticksInto = tick - entry.tick;
|
|
435
|
+
const tpb = this._ticksPerBarForEntry(entry);
|
|
436
|
+
return ticksInto % tpb === 0;
|
|
437
|
+
}
|
|
438
|
+
/** Internal: get the full entry at a tick (for MetronomePlayer beat grid anchoring) */
|
|
439
|
+
getEntryAt(tick) {
|
|
440
|
+
return this._entryAt(tick);
|
|
441
|
+
}
|
|
442
|
+
_entryAt(tick) {
|
|
443
|
+
let lo = 0;
|
|
444
|
+
let hi = this._entries.length - 1;
|
|
445
|
+
while (lo < hi) {
|
|
446
|
+
const mid = lo + hi + 1 >> 1;
|
|
447
|
+
if (this._entries[mid].tick <= tick) {
|
|
448
|
+
lo = mid;
|
|
449
|
+
} else {
|
|
450
|
+
hi = mid - 1;
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
return this._entries[lo];
|
|
454
|
+
}
|
|
455
|
+
_ticksPerBarForEntry(entry) {
|
|
456
|
+
return entry.numerator * this._ppqn * (4 / entry.denominator);
|
|
457
|
+
}
|
|
458
|
+
_snapToBarBoundary(atTick) {
|
|
459
|
+
const entry = this._entryAt(atTick);
|
|
460
|
+
const tpb = this._ticksPerBarForEntry(entry);
|
|
461
|
+
const ticksInto = atTick - entry.tick;
|
|
462
|
+
if (ticksInto % tpb === 0) return atTick;
|
|
463
|
+
return entry.tick + Math.ceil(ticksInto / tpb) * tpb;
|
|
464
|
+
}
|
|
465
|
+
_computeBarAtTick(tick) {
|
|
466
|
+
const entry = this._entryAt(tick);
|
|
467
|
+
const ticksInto = tick - entry.tick;
|
|
468
|
+
const tpb = this._ticksPerBarForEntry(entry);
|
|
469
|
+
return entry.barAtTick + ticksInto / tpb;
|
|
470
|
+
}
|
|
471
|
+
_recomputeCache(fromIndex) {
|
|
472
|
+
for (let i = Math.max(1, fromIndex); i < this._entries.length; i++) {
|
|
473
|
+
const prev = this._entries[i - 1];
|
|
474
|
+
const tickDelta = this._entries[i].tick - prev.tick;
|
|
475
|
+
const tpb = this._ticksPerBarForEntry(prev);
|
|
476
|
+
this._entries[i] = {
|
|
477
|
+
...this._entries[i],
|
|
478
|
+
barAtTick: prev.barAtTick + tickDelta / tpb
|
|
479
|
+
};
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
/**
|
|
483
|
+
* After changing a meter entry, re-snap downstream entries to bar boundaries
|
|
484
|
+
* of their preceding meter so barAtTick stays integer.
|
|
485
|
+
*/
|
|
486
|
+
_resnapDownstreamEntries(fromIndex) {
|
|
487
|
+
for (let i = Math.max(1, fromIndex + 1); i < this._entries.length; i++) {
|
|
488
|
+
const prev = this._entries[i - 1];
|
|
489
|
+
const tpb = this._ticksPerBarForEntry(prev);
|
|
490
|
+
const tick = this._entries[i].tick;
|
|
491
|
+
const ticksIntoPrev = tick - prev.tick;
|
|
492
|
+
if (ticksIntoPrev % tpb !== 0) {
|
|
493
|
+
const snapped = prev.tick + Math.ceil(ticksIntoPrev / tpb) * tpb;
|
|
494
|
+
console.warn(
|
|
495
|
+
"[waveform-playlist] MeterMap: meter change moved entry from tick " + tick + " to " + snapped + " (bar boundary alignment)"
|
|
496
|
+
);
|
|
497
|
+
this._entries[i] = { ...this._entries[i], tick: snapped };
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
_validateMeter(numerator, denominator) {
|
|
502
|
+
if (!Number.isInteger(numerator) || numerator < 1 || numerator > 32) {
|
|
503
|
+
throw new Error(
|
|
504
|
+
"[waveform-playlist] MeterMap: numerator must be an integer 1-32, got " + numerator
|
|
505
|
+
);
|
|
506
|
+
}
|
|
507
|
+
if (!isPowerOf2(denominator) || denominator > 32) {
|
|
508
|
+
throw new Error(
|
|
509
|
+
"[waveform-playlist] MeterMap: denominator must be a power of 2 (1-32), got " + denominator
|
|
510
|
+
);
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
};
|
|
514
|
+
|
|
324
515
|
// src/audio/master-node.ts
|
|
325
516
|
var MasterNode = class {
|
|
326
517
|
constructor(audioContext) {
|
|
@@ -406,14 +597,15 @@ var TrackNode = class {
|
|
|
406
597
|
|
|
407
598
|
// src/audio/clip-player.ts
|
|
408
599
|
var ClipPlayer = class {
|
|
409
|
-
constructor(audioContext, sampleTimeline, toAudioTime) {
|
|
600
|
+
constructor(audioContext, sampleTimeline, tempoMap, toAudioTime) {
|
|
410
601
|
this._tracks = /* @__PURE__ */ new Map();
|
|
411
602
|
this._trackNodes = /* @__PURE__ */ new Map();
|
|
412
603
|
this._activeSources = /* @__PURE__ */ new Map();
|
|
413
604
|
this._loopEnabled = false;
|
|
414
|
-
this.
|
|
605
|
+
this._loopEndSamples = 0;
|
|
415
606
|
this._audioContext = audioContext;
|
|
416
607
|
this._sampleTimeline = sampleTimeline;
|
|
608
|
+
this._tempoMap = tempoMap;
|
|
417
609
|
this._toAudioTime = toAudioTime;
|
|
418
610
|
}
|
|
419
611
|
setTracks(tracks, trackNodes) {
|
|
@@ -423,41 +615,50 @@ var ClipPlayer = class {
|
|
|
423
615
|
this._tracks.set(track.id, { track, clips: track.clips });
|
|
424
616
|
}
|
|
425
617
|
}
|
|
426
|
-
|
|
618
|
+
/** Set loop region using ticks. startTick is unused — loop clamping only needs
|
|
619
|
+
* the end boundary; mid-clip restart at loopStart is handled by onPositionJump. */
|
|
620
|
+
setLoop(enabled, _startTick, endTick) {
|
|
621
|
+
this._loopEnabled = enabled;
|
|
622
|
+
this._loopEndSamples = this._sampleTimeline.ticksToSamples(endTick);
|
|
623
|
+
}
|
|
624
|
+
/** Set loop region using samples directly */
|
|
625
|
+
setLoopSamples(enabled, _startSample, endSample) {
|
|
427
626
|
this._loopEnabled = enabled;
|
|
428
|
-
this.
|
|
627
|
+
this._loopEndSamples = endSample;
|
|
429
628
|
}
|
|
430
629
|
updateTrack(trackId, track) {
|
|
431
630
|
this._tracks.set(trackId, { track, clips: track.clips });
|
|
432
631
|
this._silenceTrack(trackId);
|
|
433
632
|
}
|
|
434
|
-
generate(
|
|
633
|
+
generate(fromTick, toTick) {
|
|
435
634
|
const events = [];
|
|
635
|
+
const fromSample = this._sampleTimeline.ticksToSamples(fromTick);
|
|
636
|
+
const toSample = this._sampleTimeline.ticksToSamples(toTick);
|
|
436
637
|
for (const [trackId, state] of this._tracks) {
|
|
437
638
|
for (const clip of state.clips) {
|
|
438
639
|
if (clip.durationSamples === 0) continue;
|
|
439
640
|
if (!clip.audioBuffer) continue;
|
|
440
|
-
const
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
if (this._loopEnabled && clipStartTime + duration > this._loopEnd) {
|
|
449
|
-
duration = this._loopEnd - clipStartTime;
|
|
641
|
+
const clipStartSample = clip.startSample;
|
|
642
|
+
if (clipStartSample < fromSample) continue;
|
|
643
|
+
if (clipStartSample >= toSample) continue;
|
|
644
|
+
const fadeInDurationSamples = clip.fadeIn ? clip.fadeIn.duration ?? 0 : 0;
|
|
645
|
+
const fadeOutDurationSamples = clip.fadeOut ? clip.fadeOut.duration ?? 0 : 0;
|
|
646
|
+
let durationSamples = clip.durationSamples;
|
|
647
|
+
if (this._loopEnabled && clipStartSample + durationSamples > this._loopEndSamples) {
|
|
648
|
+
durationSamples = this._loopEndSamples - clipStartSample;
|
|
450
649
|
}
|
|
650
|
+
const clipTick = this._sampleTimeline.samplesToTicks(clipStartSample);
|
|
451
651
|
events.push({
|
|
452
652
|
trackId,
|
|
453
653
|
clipId: clip.id,
|
|
454
654
|
audioBuffer: clip.audioBuffer,
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
655
|
+
tick: clipTick,
|
|
656
|
+
startSample: clipStartSample,
|
|
657
|
+
offsetSamples: clip.offsetSamples,
|
|
658
|
+
durationSamples,
|
|
458
659
|
gain: clip.gain,
|
|
459
|
-
|
|
460
|
-
|
|
660
|
+
fadeInDurationSamples,
|
|
661
|
+
fadeOutDurationSamples
|
|
461
662
|
});
|
|
462
663
|
}
|
|
463
664
|
}
|
|
@@ -471,18 +672,25 @@ var ClipPlayer = class {
|
|
|
471
672
|
);
|
|
472
673
|
return;
|
|
473
674
|
}
|
|
474
|
-
|
|
675
|
+
const sampleRate = this._sampleTimeline.sampleRate;
|
|
676
|
+
const offsetSeconds = event.offsetSamples / sampleRate;
|
|
677
|
+
const durationSeconds = event.durationSamples / sampleRate;
|
|
678
|
+
if (offsetSeconds >= event.audioBuffer.duration) {
|
|
679
|
+
console.warn(
|
|
680
|
+
"[waveform-playlist] ClipPlayer.consume: offset (" + offsetSeconds + "s) exceeds audioBuffer.duration (" + event.audioBuffer.duration + 's) for clipId "' + event.clipId + '" \u2014 clip will not play'
|
|
681
|
+
);
|
|
475
682
|
return;
|
|
476
683
|
}
|
|
477
684
|
const source = this._audioContext.createBufferSource();
|
|
478
685
|
source.buffer = event.audioBuffer;
|
|
479
|
-
const
|
|
686
|
+
const transportSeconds = this._tempoMap.ticksToSeconds(event.tick);
|
|
687
|
+
const when = this._toAudioTime(transportSeconds);
|
|
480
688
|
const gainNode = this._audioContext.createGain();
|
|
481
689
|
gainNode.gain.value = event.gain;
|
|
482
|
-
let fadeIn = event.
|
|
483
|
-
let fadeOut = event.
|
|
484
|
-
if (fadeIn + fadeOut >
|
|
485
|
-
const ratio =
|
|
690
|
+
let fadeIn = event.fadeInDurationSamples / sampleRate;
|
|
691
|
+
let fadeOut = event.fadeOutDurationSamples / sampleRate;
|
|
692
|
+
if (fadeIn + fadeOut > durationSeconds) {
|
|
693
|
+
const ratio = durationSeconds / (fadeIn + fadeOut);
|
|
486
694
|
fadeIn *= ratio;
|
|
487
695
|
fadeOut *= ratio;
|
|
488
696
|
}
|
|
@@ -491,9 +699,9 @@ var ClipPlayer = class {
|
|
|
491
699
|
gainNode.gain.linearRampToValueAtTime(event.gain, when + fadeIn);
|
|
492
700
|
}
|
|
493
701
|
if (fadeOut > 0) {
|
|
494
|
-
const fadeOutStart = when +
|
|
702
|
+
const fadeOutStart = when + durationSeconds - fadeOut;
|
|
495
703
|
gainNode.gain.setValueAtTime(event.gain, fadeOutStart);
|
|
496
|
-
gainNode.gain.linearRampToValueAtTime(0, when +
|
|
704
|
+
gainNode.gain.linearRampToValueAtTime(0, when + durationSeconds);
|
|
497
705
|
}
|
|
498
706
|
source.connect(gainNode);
|
|
499
707
|
gainNode.connect(trackNode.input);
|
|
@@ -509,33 +717,37 @@ var ClipPlayer = class {
|
|
|
509
717
|
console.warn("[waveform-playlist] ClipPlayer: error disconnecting gain node:", String(err));
|
|
510
718
|
}
|
|
511
719
|
});
|
|
512
|
-
source.start(when,
|
|
720
|
+
source.start(when, offsetSeconds, durationSeconds);
|
|
513
721
|
}
|
|
514
|
-
onPositionJump(
|
|
722
|
+
onPositionJump(newTick) {
|
|
515
723
|
this.silence();
|
|
724
|
+
const newSample = this._sampleTimeline.ticksToSamples(newTick);
|
|
516
725
|
for (const [trackId, state] of this._tracks) {
|
|
517
726
|
for (const clip of state.clips) {
|
|
518
727
|
if (clip.durationSamples === 0) continue;
|
|
519
728
|
if (!clip.audioBuffer) continue;
|
|
520
|
-
const
|
|
521
|
-
const
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
729
|
+
const clipStartSample = clip.startSample;
|
|
730
|
+
const clipEndSample = clipStartSample + clip.durationSamples;
|
|
731
|
+
if (clipStartSample <= newSample && clipEndSample > newSample) {
|
|
732
|
+
const offsetIntoClipSamples = newSample - clipStartSample;
|
|
733
|
+
const offsetSamples = clip.offsetSamples + offsetIntoClipSamples;
|
|
734
|
+
let durationSamples = clipEndSample - newSample;
|
|
735
|
+
if (this._loopEnabled && newSample + durationSamples > this._loopEndSamples) {
|
|
736
|
+
durationSamples = this._loopEndSamples - newSample;
|
|
737
|
+
}
|
|
738
|
+
if (durationSamples <= 0) continue;
|
|
739
|
+
const fadeOutDurationSamples = clip.fadeOut ? clip.fadeOut.duration ?? 0 : 0;
|
|
529
740
|
this.consume({
|
|
530
741
|
trackId,
|
|
531
742
|
clipId: clip.id,
|
|
532
743
|
audioBuffer: clip.audioBuffer,
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
744
|
+
tick: newTick,
|
|
745
|
+
startSample: newSample,
|
|
746
|
+
offsetSamples,
|
|
747
|
+
durationSamples,
|
|
536
748
|
gain: clip.gain,
|
|
537
|
-
|
|
538
|
-
|
|
749
|
+
fadeInDurationSamples: 0,
|
|
750
|
+
fadeOutDurationSamples
|
|
539
751
|
});
|
|
540
752
|
}
|
|
541
753
|
}
|
|
@@ -587,15 +799,14 @@ var ClipPlayer = class {
|
|
|
587
799
|
|
|
588
800
|
// src/audio/metronome-player.ts
|
|
589
801
|
var MetronomePlayer = class {
|
|
590
|
-
constructor(audioContext, tempoMap,
|
|
802
|
+
constructor(audioContext, tempoMap, meterMap, destination, toAudioTime) {
|
|
591
803
|
this._enabled = false;
|
|
592
|
-
this._beatsPerBar = 4;
|
|
593
804
|
this._accentBuffer = null;
|
|
594
805
|
this._normalBuffer = null;
|
|
595
806
|
this._activeSources = /* @__PURE__ */ new Set();
|
|
596
807
|
this._audioContext = audioContext;
|
|
597
808
|
this._tempoMap = tempoMap;
|
|
598
|
-
this.
|
|
809
|
+
this._meterMap = meterMap;
|
|
599
810
|
this._destination = destination;
|
|
600
811
|
this._toAudioTime = toAudioTime;
|
|
601
812
|
}
|
|
@@ -605,31 +816,34 @@ var MetronomePlayer = class {
|
|
|
605
816
|
this.silence();
|
|
606
817
|
}
|
|
607
818
|
}
|
|
608
|
-
setBeatsPerBar(beats) {
|
|
609
|
-
this._beatsPerBar = beats;
|
|
610
|
-
}
|
|
611
819
|
setClickSounds(accent, normal) {
|
|
612
820
|
this._accentBuffer = accent;
|
|
613
821
|
this._normalBuffer = normal;
|
|
614
822
|
}
|
|
615
|
-
generate(
|
|
823
|
+
generate(fromTick, toTick) {
|
|
616
824
|
if (!this._enabled || !this._accentBuffer || !this._normalBuffer) {
|
|
617
825
|
return [];
|
|
618
826
|
}
|
|
619
827
|
const events = [];
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
const
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
const
|
|
626
|
-
const
|
|
627
|
-
|
|
828
|
+
let entry = this._meterMap.getEntryAt(fromTick);
|
|
829
|
+
let beatSize = this._meterMap.ticksPerBeat(fromTick);
|
|
830
|
+
const tickIntoSection = fromTick - entry.tick;
|
|
831
|
+
let tick = entry.tick + Math.ceil(tickIntoSection / beatSize) * beatSize;
|
|
832
|
+
while (tick < toTick) {
|
|
833
|
+
const tickPos = tick;
|
|
834
|
+
const currentEntry = this._meterMap.getEntryAt(tickPos);
|
|
835
|
+
if (currentEntry.tick !== entry.tick) {
|
|
836
|
+
entry = currentEntry;
|
|
837
|
+
beatSize = this._meterMap.ticksPerBeat(tickPos);
|
|
838
|
+
}
|
|
839
|
+
const isAccent = this._meterMap.isBarBoundary(tickPos);
|
|
628
840
|
events.push({
|
|
629
|
-
|
|
841
|
+
tick: tickPos,
|
|
630
842
|
isAccent,
|
|
631
843
|
buffer: isAccent ? this._accentBuffer : this._normalBuffer
|
|
632
844
|
});
|
|
845
|
+
beatSize = this._meterMap.ticksPerBeat(tickPos);
|
|
846
|
+
tick += beatSize;
|
|
633
847
|
}
|
|
634
848
|
return events;
|
|
635
849
|
}
|
|
@@ -649,10 +863,10 @@ var MetronomePlayer = class {
|
|
|
649
863
|
);
|
|
650
864
|
}
|
|
651
865
|
});
|
|
652
|
-
|
|
866
|
+
const transportTime = this._tempoMap.ticksToSeconds(event.tick);
|
|
867
|
+
source.start(this._toAudioTime(transportTime));
|
|
653
868
|
}
|
|
654
|
-
onPositionJump(
|
|
655
|
-
this.silence();
|
|
869
|
+
onPositionJump(_newTick) {
|
|
656
870
|
}
|
|
657
871
|
silence() {
|
|
658
872
|
for (const source of this._activeSources) {
|
|
@@ -685,25 +899,30 @@ var Transport = class _Transport {
|
|
|
685
899
|
this._soloedTrackIds = /* @__PURE__ */ new Set();
|
|
686
900
|
this._mutedTrackIds = /* @__PURE__ */ new Set();
|
|
687
901
|
this._playing = false;
|
|
902
|
+
this._loopEnabled = false;
|
|
903
|
+
this._loopStartSeconds = 0;
|
|
688
904
|
this._listeners = /* @__PURE__ */ new Map();
|
|
689
905
|
this._audioContext = audioContext;
|
|
690
906
|
const sampleRate = options.sampleRate ?? audioContext.sampleRate;
|
|
691
907
|
const ppqn = options.ppqn ?? 960;
|
|
692
908
|
const tempo = options.tempo ?? 120;
|
|
693
|
-
const
|
|
909
|
+
const numerator = options.numerator ?? 4;
|
|
910
|
+
const denominator = options.denominator ?? 4;
|
|
694
911
|
const lookahead = options.schedulerLookahead ?? 0.2;
|
|
695
|
-
_Transport._validateOptions(sampleRate, ppqn, tempo,
|
|
912
|
+
_Transport._validateOptions(sampleRate, ppqn, tempo, numerator, denominator, lookahead);
|
|
696
913
|
this._clock = new Clock(audioContext);
|
|
697
|
-
this.
|
|
914
|
+
this._sampleTimeline = new SampleTimeline(sampleRate);
|
|
915
|
+
this._meterMap = new MeterMap(ppqn, numerator, denominator);
|
|
916
|
+
this._tempoMap = new TempoMap(ppqn, tempo);
|
|
917
|
+
this._scheduler = new Scheduler(this._tempoMap, {
|
|
698
918
|
lookahead,
|
|
699
|
-
onLoop: (
|
|
700
|
-
|
|
919
|
+
onLoop: (loopStartSeconds, loopEndSeconds, currentTimeSeconds) => {
|
|
920
|
+
const timeToBoundary = loopEndSeconds - currentTimeSeconds;
|
|
921
|
+
this._clock.seekTo(loopStartSeconds - timeToBoundary);
|
|
701
922
|
}
|
|
702
923
|
});
|
|
703
|
-
this._sampleTimeline
|
|
704
|
-
this.
|
|
705
|
-
this._tempoMap = new TempoMap(ppqn, tempo);
|
|
706
|
-
this._initAudioGraph(audioContext, beatsPerBar);
|
|
924
|
+
this._sampleTimeline.setTempoMap(this._tempoMap);
|
|
925
|
+
this._initAudioGraph(audioContext);
|
|
707
926
|
this._timer = new Timer(() => {
|
|
708
927
|
const time = this._clock.getTime();
|
|
709
928
|
if (this._endTime !== void 0 && time >= this._endTime) {
|
|
@@ -726,7 +945,8 @@ var Transport = class _Transport {
|
|
|
726
945
|
this._scheduler.reset(currentTime);
|
|
727
946
|
this._endTime = endTime;
|
|
728
947
|
this._clock.start();
|
|
729
|
-
this.
|
|
948
|
+
const currentTick = this._tempoMap.secondsToTicks(currentTime);
|
|
949
|
+
this._clipPlayer.onPositionJump(currentTick);
|
|
730
950
|
this._timer.start();
|
|
731
951
|
this._playing = true;
|
|
732
952
|
this._emit("play");
|
|
@@ -762,12 +982,17 @@ var Transport = class _Transport {
|
|
|
762
982
|
this._endTime = void 0;
|
|
763
983
|
if (wasPlaying) {
|
|
764
984
|
this._clock.start();
|
|
765
|
-
this.
|
|
985
|
+
const seekTick = this._tempoMap.secondsToTicks(time);
|
|
986
|
+
this._clipPlayer.onPositionJump(seekTick);
|
|
766
987
|
this._timer.start();
|
|
767
988
|
}
|
|
768
989
|
}
|
|
769
990
|
getCurrentTime() {
|
|
770
|
-
|
|
991
|
+
const t = this._clock.getTime();
|
|
992
|
+
if (this._loopEnabled && t < this._loopStartSeconds) {
|
|
993
|
+
return this._loopStartSeconds;
|
|
994
|
+
}
|
|
995
|
+
return t;
|
|
771
996
|
}
|
|
772
997
|
isPlaying() {
|
|
773
998
|
return this._playing;
|
|
@@ -883,27 +1108,89 @@ var Transport = class _Transport {
|
|
|
883
1108
|
this._masterNode.setVolume(volume);
|
|
884
1109
|
}
|
|
885
1110
|
// --- Loop ---
|
|
886
|
-
|
|
887
|
-
|
|
1111
|
+
/** Primary loop API — ticks as source of truth */
|
|
1112
|
+
setLoop(enabled, startTick, endTick) {
|
|
1113
|
+
if (enabled && startTick >= endTick) {
|
|
1114
|
+
console.warn(
|
|
1115
|
+
"[waveform-playlist] Transport.setLoop: startTick (" + startTick + ") must be less than endTick (" + endTick + ")"
|
|
1116
|
+
);
|
|
1117
|
+
return;
|
|
1118
|
+
}
|
|
1119
|
+
this._loopEnabled = enabled;
|
|
1120
|
+
this._loopStartSeconds = this._tempoMap.ticksToSeconds(startTick);
|
|
1121
|
+
this._scheduler.setLoop(enabled, startTick, endTick);
|
|
1122
|
+
this._clipPlayer.setLoop(enabled, startTick, endTick);
|
|
1123
|
+
this._emit("loop");
|
|
1124
|
+
}
|
|
1125
|
+
/** Convenience — converts seconds to ticks */
|
|
1126
|
+
setLoopSeconds(enabled, startSec, endSec) {
|
|
1127
|
+
const startTick = this._tempoMap.secondsToTicks(startSec);
|
|
1128
|
+
const endTick = this._tempoMap.secondsToTicks(endSec);
|
|
1129
|
+
this.setLoop(enabled, startTick, endTick);
|
|
1130
|
+
}
|
|
1131
|
+
/** Convenience — sets loop in samples */
|
|
1132
|
+
setLoopSamples(enabled, startSample, endSample) {
|
|
1133
|
+
if (enabled && (!Number.isFinite(startSample) || !Number.isFinite(endSample))) {
|
|
1134
|
+
console.warn(
|
|
1135
|
+
"[waveform-playlist] Transport.setLoopSamples: non-finite sample values (" + startSample + ", " + endSample + ")"
|
|
1136
|
+
);
|
|
1137
|
+
return;
|
|
1138
|
+
}
|
|
1139
|
+
if (enabled && startSample >= endSample) {
|
|
888
1140
|
console.warn(
|
|
889
|
-
"[waveform-playlist] Transport.
|
|
1141
|
+
"[waveform-playlist] Transport.setLoopSamples: startSample (" + startSample + ") must be less than endSample (" + endSample + ")"
|
|
890
1142
|
);
|
|
891
1143
|
return;
|
|
892
1144
|
}
|
|
893
|
-
this.
|
|
894
|
-
this.
|
|
1145
|
+
const startTick = this._sampleTimeline.samplesToTicks(startSample);
|
|
1146
|
+
const endTick = this._sampleTimeline.samplesToTicks(endSample);
|
|
1147
|
+
this._loopEnabled = enabled;
|
|
1148
|
+
this._loopStartSeconds = this._tempoMap.ticksToSeconds(startTick);
|
|
1149
|
+
this._clipPlayer.setLoopSamples(enabled, startSample, endSample);
|
|
1150
|
+
this._scheduler.setLoop(enabled, startTick, endTick);
|
|
895
1151
|
this._emit("loop");
|
|
896
1152
|
}
|
|
897
1153
|
// --- Tempo ---
|
|
898
|
-
setTempo(bpm) {
|
|
899
|
-
this._tempoMap.setTempo(bpm);
|
|
1154
|
+
setTempo(bpm, atTick) {
|
|
1155
|
+
this._tempoMap.setTempo(bpm, atTick);
|
|
1156
|
+
this._emit("tempochange");
|
|
1157
|
+
}
|
|
1158
|
+
getTempo(atTick) {
|
|
1159
|
+
return this._tempoMap.getTempo(atTick);
|
|
1160
|
+
}
|
|
1161
|
+
// --- Meter ---
|
|
1162
|
+
setMeter(numerator, denominator, atTick) {
|
|
1163
|
+
this._meterMap.setMeter(numerator, denominator, atTick);
|
|
1164
|
+
this._emit("meterchange");
|
|
1165
|
+
}
|
|
1166
|
+
getMeter(atTick) {
|
|
1167
|
+
return this._meterMap.getMeter(atTick);
|
|
1168
|
+
}
|
|
1169
|
+
removeMeter(atTick) {
|
|
1170
|
+
this._meterMap.removeMeter(atTick);
|
|
1171
|
+
this._emit("meterchange");
|
|
1172
|
+
}
|
|
1173
|
+
clearMeters() {
|
|
1174
|
+
this._meterMap.clearMeters();
|
|
1175
|
+
this._emit("meterchange");
|
|
1176
|
+
}
|
|
1177
|
+
clearTempos() {
|
|
1178
|
+
this._tempoMap.clearTempos();
|
|
900
1179
|
this._emit("tempochange");
|
|
901
1180
|
}
|
|
902
|
-
|
|
903
|
-
return this.
|
|
1181
|
+
barToTick(bar) {
|
|
1182
|
+
return this._meterMap.barToTick(bar);
|
|
904
1183
|
}
|
|
905
|
-
|
|
906
|
-
this.
|
|
1184
|
+
tickToBar(tick) {
|
|
1185
|
+
return this._meterMap.tickToBar(tick);
|
|
1186
|
+
}
|
|
1187
|
+
/** Convert transport time (seconds) to tick position, using the tempo map. */
|
|
1188
|
+
timeToTick(seconds) {
|
|
1189
|
+
return this._tempoMap.secondsToTicks(seconds);
|
|
1190
|
+
}
|
|
1191
|
+
/** Convert tick position to transport time (seconds), using the tempo map. */
|
|
1192
|
+
tickToTime(tick) {
|
|
1193
|
+
return this._tempoMap.ticksToSeconds(tick);
|
|
907
1194
|
}
|
|
908
1195
|
// --- Metronome ---
|
|
909
1196
|
setMetronomeEnabled(enabled) {
|
|
@@ -950,7 +1237,7 @@ var Transport = class _Transport {
|
|
|
950
1237
|
this._listeners.clear();
|
|
951
1238
|
}
|
|
952
1239
|
// --- Private ---
|
|
953
|
-
static _validateOptions(sampleRate, ppqn, tempo,
|
|
1240
|
+
static _validateOptions(sampleRate, ppqn, tempo, numerator, denominator, lookahead) {
|
|
954
1241
|
if (sampleRate <= 0) {
|
|
955
1242
|
throw new Error(
|
|
956
1243
|
"[waveform-playlist] Transport: sampleRate must be positive, got " + sampleRate
|
|
@@ -964,9 +1251,14 @@ var Transport = class _Transport {
|
|
|
964
1251
|
if (tempo <= 0) {
|
|
965
1252
|
throw new Error("[waveform-playlist] Transport: tempo must be positive, got " + tempo);
|
|
966
1253
|
}
|
|
967
|
-
if (
|
|
1254
|
+
if (!Number.isInteger(numerator) || numerator < 1 || numerator > 32) {
|
|
1255
|
+
throw new Error(
|
|
1256
|
+
"[waveform-playlist] Transport: numerator must be an integer 1-32, got " + numerator
|
|
1257
|
+
);
|
|
1258
|
+
}
|
|
1259
|
+
if (denominator <= 0 || (denominator & denominator - 1) !== 0 || denominator > 32) {
|
|
968
1260
|
throw new Error(
|
|
969
|
-
"[waveform-playlist] Transport:
|
|
1261
|
+
"[waveform-playlist] Transport: denominator must be a power of 2 (1-32), got " + denominator
|
|
970
1262
|
);
|
|
971
1263
|
}
|
|
972
1264
|
if (lookahead <= 0) {
|
|
@@ -975,19 +1267,23 @@ var Transport = class _Transport {
|
|
|
975
1267
|
);
|
|
976
1268
|
}
|
|
977
1269
|
}
|
|
978
|
-
_initAudioGraph(audioContext
|
|
1270
|
+
_initAudioGraph(audioContext) {
|
|
979
1271
|
this._masterNode = new MasterNode(audioContext);
|
|
980
1272
|
this._masterNode.output.connect(audioContext.destination);
|
|
981
1273
|
const toAudioTime = (transportTime) => this._clock.toAudioTime(transportTime);
|
|
982
|
-
this._clipPlayer = new ClipPlayer(
|
|
1274
|
+
this._clipPlayer = new ClipPlayer(
|
|
1275
|
+
audioContext,
|
|
1276
|
+
this._sampleTimeline,
|
|
1277
|
+
this._tempoMap,
|
|
1278
|
+
toAudioTime
|
|
1279
|
+
);
|
|
983
1280
|
this._metronomePlayer = new MetronomePlayer(
|
|
984
1281
|
audioContext,
|
|
985
1282
|
this._tempoMap,
|
|
986
|
-
this.
|
|
1283
|
+
this._meterMap,
|
|
987
1284
|
this._masterNode.input,
|
|
988
1285
|
toAudioTime
|
|
989
1286
|
);
|
|
990
|
-
this._metronomePlayer.setBeatsPerBar(beatsPerBar);
|
|
991
1287
|
this._scheduler.addListener(this._clipPlayer);
|
|
992
1288
|
this._scheduler.addListener(this._metronomePlayer);
|
|
993
1289
|
}
|
|
@@ -1083,7 +1379,7 @@ var NativePlayoutAdapter = class {
|
|
|
1083
1379
|
this._transport.setTrackPan(trackId, pan);
|
|
1084
1380
|
}
|
|
1085
1381
|
setLoop(enabled, start, end) {
|
|
1086
|
-
this._transport.
|
|
1382
|
+
this._transport.setLoopSeconds(enabled, start, end);
|
|
1087
1383
|
}
|
|
1088
1384
|
dispose() {
|
|
1089
1385
|
this._transport.dispose();
|
|
@@ -1094,12 +1390,12 @@ var NativePlayoutAdapter = class {
|
|
|
1094
1390
|
ClipPlayer,
|
|
1095
1391
|
Clock,
|
|
1096
1392
|
MasterNode,
|
|
1393
|
+
MeterMap,
|
|
1097
1394
|
MetronomePlayer,
|
|
1098
1395
|
NativePlayoutAdapter,
|
|
1099
1396
|
SampleTimeline,
|
|
1100
1397
|
Scheduler,
|
|
1101
1398
|
TempoMap,
|
|
1102
|
-
TickTimeline,
|
|
1103
1399
|
Timer,
|
|
1104
1400
|
TrackNode,
|
|
1105
1401
|
Transport
|