@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.mjs
CHANGED
|
@@ -49,12 +49,15 @@ var Clock = class {
|
|
|
49
49
|
|
|
50
50
|
// src/core/scheduler.ts
|
|
51
51
|
var Scheduler = class {
|
|
52
|
-
constructor(options = {}) {
|
|
52
|
+
constructor(tempoMap, options = {}) {
|
|
53
53
|
this._rightEdge = 0;
|
|
54
|
+
// integer ticks
|
|
54
55
|
this._listeners = /* @__PURE__ */ new Set();
|
|
55
56
|
this._loopEnabled = false;
|
|
56
57
|
this._loopStart = 0;
|
|
58
|
+
// integer ticks
|
|
57
59
|
this._loopEnd = 0;
|
|
60
|
+
this._tempoMap = tempoMap;
|
|
58
61
|
this._lookahead = options.lookahead ?? 0.2;
|
|
59
62
|
this._onLoop = options.onLoop;
|
|
60
63
|
}
|
|
@@ -64,25 +67,40 @@ var Scheduler = class {
|
|
|
64
67
|
removeListener(listener) {
|
|
65
68
|
this._listeners.delete(listener);
|
|
66
69
|
}
|
|
67
|
-
|
|
68
|
-
|
|
70
|
+
/** Primary API — ticks as source of truth */
|
|
71
|
+
setLoop(enabled, startTick, endTick) {
|
|
72
|
+
if (enabled && (!Number.isFinite(startTick) || !Number.isFinite(endTick))) {
|
|
73
|
+
console.warn(
|
|
74
|
+
"[waveform-playlist] Scheduler.setLoop: non-finite tick values (" + startTick + ", " + endTick + ")"
|
|
75
|
+
);
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
if (enabled && startTick >= endTick) {
|
|
69
79
|
console.warn(
|
|
70
|
-
"[waveform-playlist] Scheduler.setLoop:
|
|
80
|
+
"[waveform-playlist] Scheduler.setLoop: startTick (" + startTick + ") must be less than endTick (" + endTick + ")"
|
|
71
81
|
);
|
|
72
82
|
return;
|
|
73
83
|
}
|
|
74
84
|
this._loopEnabled = enabled;
|
|
75
|
-
this._loopStart =
|
|
76
|
-
this._loopEnd =
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
85
|
+
this._loopStart = Math.round(startTick);
|
|
86
|
+
this._loopEnd = Math.round(endTick);
|
|
87
|
+
}
|
|
88
|
+
/** Convenience — converts seconds to ticks via TempoMap */
|
|
89
|
+
setLoopSeconds(enabled, startSec, endSec) {
|
|
90
|
+
const startTick = this._tempoMap.secondsToTicks(startSec);
|
|
91
|
+
const endTick = this._tempoMap.secondsToTicks(endSec);
|
|
92
|
+
this.setLoop(enabled, startTick, endTick);
|
|
93
|
+
}
|
|
94
|
+
/** Reset scheduling cursor. Takes seconds (from Clock), converts to ticks. */
|
|
95
|
+
reset(timeSeconds) {
|
|
96
|
+
this._rightEdge = this._tempoMap.secondsToTicks(timeSeconds);
|
|
97
|
+
}
|
|
98
|
+
/** Advance the scheduling window. Takes seconds (from Clock), converts to ticks. */
|
|
99
|
+
advance(currentTimeSeconds) {
|
|
100
|
+
const targetTick = this._tempoMap.secondsToTicks(currentTimeSeconds + this._lookahead);
|
|
83
101
|
if (this._loopEnabled && this._loopEnd > this._loopStart) {
|
|
84
102
|
const loopDuration = this._loopEnd - this._loopStart;
|
|
85
|
-
let remaining =
|
|
103
|
+
let remaining = targetTick - this._rightEdge;
|
|
86
104
|
while (remaining > 0) {
|
|
87
105
|
const distToEnd = this._loopEnd - this._rightEdge;
|
|
88
106
|
if (distToEnd <= 0 || distToEnd > remaining) {
|
|
@@ -95,21 +113,25 @@ var Scheduler = class {
|
|
|
95
113
|
for (const listener of this._listeners) {
|
|
96
114
|
listener.onPositionJump(this._loopStart);
|
|
97
115
|
}
|
|
98
|
-
this._onLoop?.(
|
|
116
|
+
this._onLoop?.(
|
|
117
|
+
this._tempoMap.ticksToSeconds(this._loopStart),
|
|
118
|
+
this._tempoMap.ticksToSeconds(this._loopEnd),
|
|
119
|
+
currentTimeSeconds
|
|
120
|
+
);
|
|
99
121
|
this._rightEdge = this._loopStart;
|
|
100
122
|
if (loopDuration <= 0) break;
|
|
101
123
|
}
|
|
102
124
|
return;
|
|
103
125
|
}
|
|
104
|
-
if (
|
|
105
|
-
this._generateAndConsume(this._rightEdge,
|
|
106
|
-
this._rightEdge =
|
|
126
|
+
if (targetTick > this._rightEdge) {
|
|
127
|
+
this._generateAndConsume(this._rightEdge, targetTick);
|
|
128
|
+
this._rightEdge = targetTick;
|
|
107
129
|
}
|
|
108
130
|
}
|
|
109
|
-
_generateAndConsume(
|
|
131
|
+
_generateAndConsume(fromTick, toTick) {
|
|
110
132
|
for (const listener of this._listeners) {
|
|
111
133
|
try {
|
|
112
|
-
const events = listener.generate(
|
|
134
|
+
const events = listener.generate(fromTick, toTick);
|
|
113
135
|
for (const event of events) {
|
|
114
136
|
try {
|
|
115
137
|
listener.consume(event);
|
|
@@ -159,44 +181,36 @@ var Timer = class {
|
|
|
159
181
|
// src/timeline/sample-timeline.ts
|
|
160
182
|
var SampleTimeline = class {
|
|
161
183
|
constructor(sampleRate) {
|
|
184
|
+
this._tempoMap = null;
|
|
162
185
|
this._sampleRate = sampleRate;
|
|
163
186
|
}
|
|
164
187
|
get sampleRate() {
|
|
165
188
|
return this._sampleRate;
|
|
166
189
|
}
|
|
190
|
+
setTempoMap(tempoMap) {
|
|
191
|
+
this._tempoMap = tempoMap;
|
|
192
|
+
}
|
|
167
193
|
samplesToSeconds(samples) {
|
|
168
194
|
return samples / this._sampleRate;
|
|
169
195
|
}
|
|
170
196
|
secondsToSamples(seconds) {
|
|
171
197
|
return Math.round(seconds * this._sampleRate);
|
|
172
198
|
}
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
get ppqn() {
|
|
181
|
-
return this._ppqn;
|
|
182
|
-
}
|
|
183
|
-
ticksPerBeat() {
|
|
184
|
-
return this._ppqn;
|
|
185
|
-
}
|
|
186
|
-
ticksPerBar(beatsPerBar) {
|
|
187
|
-
return this._ppqn * beatsPerBar;
|
|
188
|
-
}
|
|
189
|
-
toPosition(ticks, beatsPerBar) {
|
|
190
|
-
const ticksPerBar = this.ticksPerBar(beatsPerBar);
|
|
191
|
-
const bar = Math.floor(ticks / ticksPerBar) + 1;
|
|
192
|
-
const remaining = ticks % ticksPerBar;
|
|
193
|
-
const beat = Math.floor(remaining / this._ppqn) + 1;
|
|
194
|
-
const tick = remaining % this._ppqn;
|
|
195
|
-
return { bar, beat, tick };
|
|
199
|
+
ticksToSamples(ticks) {
|
|
200
|
+
if (!this._tempoMap) {
|
|
201
|
+
throw new Error(
|
|
202
|
+
"[waveform-playlist] SampleTimeline: tempoMap not set \u2014 call setTempoMap() first"
|
|
203
|
+
);
|
|
204
|
+
}
|
|
205
|
+
return Math.round(this._tempoMap.ticksToSeconds(ticks) * this._sampleRate);
|
|
196
206
|
}
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
207
|
+
samplesToTicks(samples) {
|
|
208
|
+
if (!this._tempoMap) {
|
|
209
|
+
throw new Error(
|
|
210
|
+
"[waveform-playlist] SampleTimeline: tempoMap not set \u2014 call setTempoMap() first"
|
|
211
|
+
);
|
|
212
|
+
}
|
|
213
|
+
return this._tempoMap.secondsToTicks(samples / this._sampleRate);
|
|
200
214
|
}
|
|
201
215
|
};
|
|
202
216
|
|
|
@@ -244,7 +258,7 @@ var TempoMap = class {
|
|
|
244
258
|
const entry = this._entries[lo];
|
|
245
259
|
const secondsIntoSegment = seconds - entry.secondsAtTick;
|
|
246
260
|
const ticksPerSecond = entry.bpm / 60 * this._ppqn;
|
|
247
|
-
return entry.tick + secondsIntoSegment * ticksPerSecond;
|
|
261
|
+
return Math.round(entry.tick + secondsIntoSegment * ticksPerSecond);
|
|
248
262
|
}
|
|
249
263
|
beatsToSeconds(beats) {
|
|
250
264
|
return this.ticksToSeconds(beats * this._ppqn);
|
|
@@ -252,6 +266,10 @@ var TempoMap = class {
|
|
|
252
266
|
secondsToBeats(seconds) {
|
|
253
267
|
return this.secondsToTicks(seconds) / this._ppqn;
|
|
254
268
|
}
|
|
269
|
+
clearTempos() {
|
|
270
|
+
const first = this._entries[0];
|
|
271
|
+
this._entries = [{ tick: 0, bpm: first.bpm, secondsAtTick: 0 }];
|
|
272
|
+
}
|
|
255
273
|
_ticksToSecondsInternal(ticks) {
|
|
256
274
|
const entry = this._entryAt(ticks);
|
|
257
275
|
const ticksIntoSegment = ticks - entry.tick;
|
|
@@ -284,6 +302,179 @@ var TempoMap = class {
|
|
|
284
302
|
}
|
|
285
303
|
};
|
|
286
304
|
|
|
305
|
+
// src/timeline/meter-map.ts
|
|
306
|
+
function isPowerOf2(n) {
|
|
307
|
+
return n > 0 && (n & n - 1) === 0;
|
|
308
|
+
}
|
|
309
|
+
var MeterMap = class {
|
|
310
|
+
constructor(ppqn, numerator = 4, denominator = 4) {
|
|
311
|
+
this._ppqn = ppqn;
|
|
312
|
+
this._entries = [{ tick: 0, numerator, denominator, barAtTick: 0 }];
|
|
313
|
+
}
|
|
314
|
+
get ppqn() {
|
|
315
|
+
return this._ppqn;
|
|
316
|
+
}
|
|
317
|
+
getMeter(atTick = 0) {
|
|
318
|
+
const entry = this._entryAt(atTick);
|
|
319
|
+
return { numerator: entry.numerator, denominator: entry.denominator };
|
|
320
|
+
}
|
|
321
|
+
setMeter(numerator, denominator, atTick = 0) {
|
|
322
|
+
this._validateMeter(numerator, denominator);
|
|
323
|
+
if (atTick < 0) {
|
|
324
|
+
throw new Error("[waveform-playlist] MeterMap: atTick must be non-negative, got " + atTick);
|
|
325
|
+
}
|
|
326
|
+
if (atTick === 0) {
|
|
327
|
+
this._entries[0] = { ...this._entries[0], numerator, denominator };
|
|
328
|
+
this._resnapDownstreamEntries(0);
|
|
329
|
+
this._recomputeCache(0);
|
|
330
|
+
return;
|
|
331
|
+
}
|
|
332
|
+
const snapped = this._snapToBarBoundary(atTick);
|
|
333
|
+
if (snapped !== atTick) {
|
|
334
|
+
console.warn(
|
|
335
|
+
"[waveform-playlist] MeterMap.setMeter: tick " + atTick + " is not on a bar boundary, snapped to " + snapped
|
|
336
|
+
);
|
|
337
|
+
}
|
|
338
|
+
let i = this._entries.length - 1;
|
|
339
|
+
while (i > 0 && this._entries[i].tick > snapped) i--;
|
|
340
|
+
if (this._entries[i].tick === snapped) {
|
|
341
|
+
this._entries[i] = { ...this._entries[i], numerator, denominator };
|
|
342
|
+
} else {
|
|
343
|
+
const barAtTick = this._computeBarAtTick(snapped);
|
|
344
|
+
this._entries.splice(i + 1, 0, { tick: snapped, numerator, denominator, barAtTick });
|
|
345
|
+
i = i + 1;
|
|
346
|
+
}
|
|
347
|
+
this._resnapDownstreamEntries(i);
|
|
348
|
+
this._recomputeCache(i);
|
|
349
|
+
}
|
|
350
|
+
removeMeter(atTick) {
|
|
351
|
+
if (atTick === 0) {
|
|
352
|
+
throw new Error("[waveform-playlist] MeterMap: cannot remove meter at tick 0");
|
|
353
|
+
}
|
|
354
|
+
const idx = this._entries.findIndex((e) => e.tick === atTick);
|
|
355
|
+
if (idx > 0) {
|
|
356
|
+
this._entries.splice(idx, 1);
|
|
357
|
+
this._recomputeCache(idx);
|
|
358
|
+
} else if (idx === -1) {
|
|
359
|
+
console.warn("[waveform-playlist] MeterMap.removeMeter: no entry at tick " + atTick);
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
clearMeters() {
|
|
363
|
+
const first = this._entries[0];
|
|
364
|
+
this._entries = [{ ...first, barAtTick: 0 }];
|
|
365
|
+
}
|
|
366
|
+
ticksPerBeat(atTick = 0) {
|
|
367
|
+
const entry = this._entryAt(atTick);
|
|
368
|
+
return this._ppqn * (4 / entry.denominator);
|
|
369
|
+
}
|
|
370
|
+
ticksPerBar(atTick = 0) {
|
|
371
|
+
const entry = this._entryAt(atTick);
|
|
372
|
+
return entry.numerator * this._ppqn * (4 / entry.denominator);
|
|
373
|
+
}
|
|
374
|
+
barToTick(bar) {
|
|
375
|
+
if (bar < 1) {
|
|
376
|
+
throw new Error("[waveform-playlist] MeterMap: bar must be >= 1, got " + bar);
|
|
377
|
+
}
|
|
378
|
+
const targetBar = bar - 1;
|
|
379
|
+
for (let i = 0; i < this._entries.length; i++) {
|
|
380
|
+
const nextBar = i < this._entries.length - 1 ? this._entries[i + 1].barAtTick : Infinity;
|
|
381
|
+
if (targetBar < nextBar) {
|
|
382
|
+
const barsInto = targetBar - this._entries[i].barAtTick;
|
|
383
|
+
const tpb = this._ticksPerBarForEntry(this._entries[i]);
|
|
384
|
+
return this._entries[i].tick + barsInto * tpb;
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
return 0;
|
|
388
|
+
}
|
|
389
|
+
tickToBar(tick) {
|
|
390
|
+
const entry = this._entryAt(tick);
|
|
391
|
+
const ticksInto = tick - entry.tick;
|
|
392
|
+
const tpb = this._ticksPerBarForEntry(entry);
|
|
393
|
+
return entry.barAtTick + Math.floor(ticksInto / tpb) + 1;
|
|
394
|
+
}
|
|
395
|
+
isBarBoundary(tick) {
|
|
396
|
+
const entry = this._entryAt(tick);
|
|
397
|
+
const ticksInto = tick - entry.tick;
|
|
398
|
+
const tpb = this._ticksPerBarForEntry(entry);
|
|
399
|
+
return ticksInto % tpb === 0;
|
|
400
|
+
}
|
|
401
|
+
/** Internal: get the full entry at a tick (for MetronomePlayer beat grid anchoring) */
|
|
402
|
+
getEntryAt(tick) {
|
|
403
|
+
return this._entryAt(tick);
|
|
404
|
+
}
|
|
405
|
+
_entryAt(tick) {
|
|
406
|
+
let lo = 0;
|
|
407
|
+
let hi = this._entries.length - 1;
|
|
408
|
+
while (lo < hi) {
|
|
409
|
+
const mid = lo + hi + 1 >> 1;
|
|
410
|
+
if (this._entries[mid].tick <= tick) {
|
|
411
|
+
lo = mid;
|
|
412
|
+
} else {
|
|
413
|
+
hi = mid - 1;
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
return this._entries[lo];
|
|
417
|
+
}
|
|
418
|
+
_ticksPerBarForEntry(entry) {
|
|
419
|
+
return entry.numerator * this._ppqn * (4 / entry.denominator);
|
|
420
|
+
}
|
|
421
|
+
_snapToBarBoundary(atTick) {
|
|
422
|
+
const entry = this._entryAt(atTick);
|
|
423
|
+
const tpb = this._ticksPerBarForEntry(entry);
|
|
424
|
+
const ticksInto = atTick - entry.tick;
|
|
425
|
+
if (ticksInto % tpb === 0) return atTick;
|
|
426
|
+
return entry.tick + Math.ceil(ticksInto / tpb) * tpb;
|
|
427
|
+
}
|
|
428
|
+
_computeBarAtTick(tick) {
|
|
429
|
+
const entry = this._entryAt(tick);
|
|
430
|
+
const ticksInto = tick - entry.tick;
|
|
431
|
+
const tpb = this._ticksPerBarForEntry(entry);
|
|
432
|
+
return entry.barAtTick + ticksInto / tpb;
|
|
433
|
+
}
|
|
434
|
+
_recomputeCache(fromIndex) {
|
|
435
|
+
for (let i = Math.max(1, fromIndex); i < this._entries.length; i++) {
|
|
436
|
+
const prev = this._entries[i - 1];
|
|
437
|
+
const tickDelta = this._entries[i].tick - prev.tick;
|
|
438
|
+
const tpb = this._ticksPerBarForEntry(prev);
|
|
439
|
+
this._entries[i] = {
|
|
440
|
+
...this._entries[i],
|
|
441
|
+
barAtTick: prev.barAtTick + tickDelta / tpb
|
|
442
|
+
};
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
/**
|
|
446
|
+
* After changing a meter entry, re-snap downstream entries to bar boundaries
|
|
447
|
+
* of their preceding meter so barAtTick stays integer.
|
|
448
|
+
*/
|
|
449
|
+
_resnapDownstreamEntries(fromIndex) {
|
|
450
|
+
for (let i = Math.max(1, fromIndex + 1); i < this._entries.length; i++) {
|
|
451
|
+
const prev = this._entries[i - 1];
|
|
452
|
+
const tpb = this._ticksPerBarForEntry(prev);
|
|
453
|
+
const tick = this._entries[i].tick;
|
|
454
|
+
const ticksIntoPrev = tick - prev.tick;
|
|
455
|
+
if (ticksIntoPrev % tpb !== 0) {
|
|
456
|
+
const snapped = prev.tick + Math.ceil(ticksIntoPrev / tpb) * tpb;
|
|
457
|
+
console.warn(
|
|
458
|
+
"[waveform-playlist] MeterMap: meter change moved entry from tick " + tick + " to " + snapped + " (bar boundary alignment)"
|
|
459
|
+
);
|
|
460
|
+
this._entries[i] = { ...this._entries[i], tick: snapped };
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
_validateMeter(numerator, denominator) {
|
|
465
|
+
if (!Number.isInteger(numerator) || numerator < 1 || numerator > 32) {
|
|
466
|
+
throw new Error(
|
|
467
|
+
"[waveform-playlist] MeterMap: numerator must be an integer 1-32, got " + numerator
|
|
468
|
+
);
|
|
469
|
+
}
|
|
470
|
+
if (!isPowerOf2(denominator) || denominator > 32) {
|
|
471
|
+
throw new Error(
|
|
472
|
+
"[waveform-playlist] MeterMap: denominator must be a power of 2 (1-32), got " + denominator
|
|
473
|
+
);
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
};
|
|
477
|
+
|
|
287
478
|
// src/audio/master-node.ts
|
|
288
479
|
var MasterNode = class {
|
|
289
480
|
constructor(audioContext) {
|
|
@@ -369,14 +560,15 @@ var TrackNode = class {
|
|
|
369
560
|
|
|
370
561
|
// src/audio/clip-player.ts
|
|
371
562
|
var ClipPlayer = class {
|
|
372
|
-
constructor(audioContext, sampleTimeline, toAudioTime) {
|
|
563
|
+
constructor(audioContext, sampleTimeline, tempoMap, toAudioTime) {
|
|
373
564
|
this._tracks = /* @__PURE__ */ new Map();
|
|
374
565
|
this._trackNodes = /* @__PURE__ */ new Map();
|
|
375
566
|
this._activeSources = /* @__PURE__ */ new Map();
|
|
376
567
|
this._loopEnabled = false;
|
|
377
|
-
this.
|
|
568
|
+
this._loopEndSamples = 0;
|
|
378
569
|
this._audioContext = audioContext;
|
|
379
570
|
this._sampleTimeline = sampleTimeline;
|
|
571
|
+
this._tempoMap = tempoMap;
|
|
380
572
|
this._toAudioTime = toAudioTime;
|
|
381
573
|
}
|
|
382
574
|
setTracks(tracks, trackNodes) {
|
|
@@ -386,41 +578,50 @@ var ClipPlayer = class {
|
|
|
386
578
|
this._tracks.set(track.id, { track, clips: track.clips });
|
|
387
579
|
}
|
|
388
580
|
}
|
|
389
|
-
|
|
581
|
+
/** Set loop region using ticks. startTick is unused — loop clamping only needs
|
|
582
|
+
* the end boundary; mid-clip restart at loopStart is handled by onPositionJump. */
|
|
583
|
+
setLoop(enabled, _startTick, endTick) {
|
|
584
|
+
this._loopEnabled = enabled;
|
|
585
|
+
this._loopEndSamples = this._sampleTimeline.ticksToSamples(endTick);
|
|
586
|
+
}
|
|
587
|
+
/** Set loop region using samples directly */
|
|
588
|
+
setLoopSamples(enabled, _startSample, endSample) {
|
|
390
589
|
this._loopEnabled = enabled;
|
|
391
|
-
this.
|
|
590
|
+
this._loopEndSamples = endSample;
|
|
392
591
|
}
|
|
393
592
|
updateTrack(trackId, track) {
|
|
394
593
|
this._tracks.set(trackId, { track, clips: track.clips });
|
|
395
594
|
this._silenceTrack(trackId);
|
|
396
595
|
}
|
|
397
|
-
generate(
|
|
596
|
+
generate(fromTick, toTick) {
|
|
398
597
|
const events = [];
|
|
598
|
+
const fromSample = this._sampleTimeline.ticksToSamples(fromTick);
|
|
599
|
+
const toSample = this._sampleTimeline.ticksToSamples(toTick);
|
|
399
600
|
for (const [trackId, state] of this._tracks) {
|
|
400
601
|
for (const clip of state.clips) {
|
|
401
602
|
if (clip.durationSamples === 0) continue;
|
|
402
603
|
if (!clip.audioBuffer) continue;
|
|
403
|
-
const
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
if (this._loopEnabled && clipStartTime + duration > this._loopEnd) {
|
|
412
|
-
duration = this._loopEnd - clipStartTime;
|
|
604
|
+
const clipStartSample = clip.startSample;
|
|
605
|
+
if (clipStartSample < fromSample) continue;
|
|
606
|
+
if (clipStartSample >= toSample) continue;
|
|
607
|
+
const fadeInDurationSamples = clip.fadeIn ? clip.fadeIn.duration ?? 0 : 0;
|
|
608
|
+
const fadeOutDurationSamples = clip.fadeOut ? clip.fadeOut.duration ?? 0 : 0;
|
|
609
|
+
let durationSamples = clip.durationSamples;
|
|
610
|
+
if (this._loopEnabled && clipStartSample + durationSamples > this._loopEndSamples) {
|
|
611
|
+
durationSamples = this._loopEndSamples - clipStartSample;
|
|
413
612
|
}
|
|
613
|
+
const clipTick = this._sampleTimeline.samplesToTicks(clipStartSample);
|
|
414
614
|
events.push({
|
|
415
615
|
trackId,
|
|
416
616
|
clipId: clip.id,
|
|
417
617
|
audioBuffer: clip.audioBuffer,
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
618
|
+
tick: clipTick,
|
|
619
|
+
startSample: clipStartSample,
|
|
620
|
+
offsetSamples: clip.offsetSamples,
|
|
621
|
+
durationSamples,
|
|
421
622
|
gain: clip.gain,
|
|
422
|
-
|
|
423
|
-
|
|
623
|
+
fadeInDurationSamples,
|
|
624
|
+
fadeOutDurationSamples
|
|
424
625
|
});
|
|
425
626
|
}
|
|
426
627
|
}
|
|
@@ -434,18 +635,25 @@ var ClipPlayer = class {
|
|
|
434
635
|
);
|
|
435
636
|
return;
|
|
436
637
|
}
|
|
437
|
-
|
|
638
|
+
const sampleRate = this._sampleTimeline.sampleRate;
|
|
639
|
+
const offsetSeconds = event.offsetSamples / sampleRate;
|
|
640
|
+
const durationSeconds = event.durationSamples / sampleRate;
|
|
641
|
+
if (offsetSeconds >= event.audioBuffer.duration) {
|
|
642
|
+
console.warn(
|
|
643
|
+
"[waveform-playlist] ClipPlayer.consume: offset (" + offsetSeconds + "s) exceeds audioBuffer.duration (" + event.audioBuffer.duration + 's) for clipId "' + event.clipId + '" \u2014 clip will not play'
|
|
644
|
+
);
|
|
438
645
|
return;
|
|
439
646
|
}
|
|
440
647
|
const source = this._audioContext.createBufferSource();
|
|
441
648
|
source.buffer = event.audioBuffer;
|
|
442
|
-
const
|
|
649
|
+
const transportSeconds = this._tempoMap.ticksToSeconds(event.tick);
|
|
650
|
+
const when = this._toAudioTime(transportSeconds);
|
|
443
651
|
const gainNode = this._audioContext.createGain();
|
|
444
652
|
gainNode.gain.value = event.gain;
|
|
445
|
-
let fadeIn = event.
|
|
446
|
-
let fadeOut = event.
|
|
447
|
-
if (fadeIn + fadeOut >
|
|
448
|
-
const ratio =
|
|
653
|
+
let fadeIn = event.fadeInDurationSamples / sampleRate;
|
|
654
|
+
let fadeOut = event.fadeOutDurationSamples / sampleRate;
|
|
655
|
+
if (fadeIn + fadeOut > durationSeconds) {
|
|
656
|
+
const ratio = durationSeconds / (fadeIn + fadeOut);
|
|
449
657
|
fadeIn *= ratio;
|
|
450
658
|
fadeOut *= ratio;
|
|
451
659
|
}
|
|
@@ -454,9 +662,9 @@ var ClipPlayer = class {
|
|
|
454
662
|
gainNode.gain.linearRampToValueAtTime(event.gain, when + fadeIn);
|
|
455
663
|
}
|
|
456
664
|
if (fadeOut > 0) {
|
|
457
|
-
const fadeOutStart = when +
|
|
665
|
+
const fadeOutStart = when + durationSeconds - fadeOut;
|
|
458
666
|
gainNode.gain.setValueAtTime(event.gain, fadeOutStart);
|
|
459
|
-
gainNode.gain.linearRampToValueAtTime(0, when +
|
|
667
|
+
gainNode.gain.linearRampToValueAtTime(0, when + durationSeconds);
|
|
460
668
|
}
|
|
461
669
|
source.connect(gainNode);
|
|
462
670
|
gainNode.connect(trackNode.input);
|
|
@@ -472,33 +680,37 @@ var ClipPlayer = class {
|
|
|
472
680
|
console.warn("[waveform-playlist] ClipPlayer: error disconnecting gain node:", String(err));
|
|
473
681
|
}
|
|
474
682
|
});
|
|
475
|
-
source.start(when,
|
|
683
|
+
source.start(when, offsetSeconds, durationSeconds);
|
|
476
684
|
}
|
|
477
|
-
onPositionJump(
|
|
685
|
+
onPositionJump(newTick) {
|
|
478
686
|
this.silence();
|
|
687
|
+
const newSample = this._sampleTimeline.ticksToSamples(newTick);
|
|
479
688
|
for (const [trackId, state] of this._tracks) {
|
|
480
689
|
for (const clip of state.clips) {
|
|
481
690
|
if (clip.durationSamples === 0) continue;
|
|
482
691
|
if (!clip.audioBuffer) continue;
|
|
483
|
-
const
|
|
484
|
-
const
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
692
|
+
const clipStartSample = clip.startSample;
|
|
693
|
+
const clipEndSample = clipStartSample + clip.durationSamples;
|
|
694
|
+
if (clipStartSample <= newSample && clipEndSample > newSample) {
|
|
695
|
+
const offsetIntoClipSamples = newSample - clipStartSample;
|
|
696
|
+
const offsetSamples = clip.offsetSamples + offsetIntoClipSamples;
|
|
697
|
+
let durationSamples = clipEndSample - newSample;
|
|
698
|
+
if (this._loopEnabled && newSample + durationSamples > this._loopEndSamples) {
|
|
699
|
+
durationSamples = this._loopEndSamples - newSample;
|
|
700
|
+
}
|
|
701
|
+
if (durationSamples <= 0) continue;
|
|
702
|
+
const fadeOutDurationSamples = clip.fadeOut ? clip.fadeOut.duration ?? 0 : 0;
|
|
492
703
|
this.consume({
|
|
493
704
|
trackId,
|
|
494
705
|
clipId: clip.id,
|
|
495
706
|
audioBuffer: clip.audioBuffer,
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
707
|
+
tick: newTick,
|
|
708
|
+
startSample: newSample,
|
|
709
|
+
offsetSamples,
|
|
710
|
+
durationSamples,
|
|
499
711
|
gain: clip.gain,
|
|
500
|
-
|
|
501
|
-
|
|
712
|
+
fadeInDurationSamples: 0,
|
|
713
|
+
fadeOutDurationSamples
|
|
502
714
|
});
|
|
503
715
|
}
|
|
504
716
|
}
|
|
@@ -550,15 +762,14 @@ var ClipPlayer = class {
|
|
|
550
762
|
|
|
551
763
|
// src/audio/metronome-player.ts
|
|
552
764
|
var MetronomePlayer = class {
|
|
553
|
-
constructor(audioContext, tempoMap,
|
|
765
|
+
constructor(audioContext, tempoMap, meterMap, destination, toAudioTime) {
|
|
554
766
|
this._enabled = false;
|
|
555
|
-
this._beatsPerBar = 4;
|
|
556
767
|
this._accentBuffer = null;
|
|
557
768
|
this._normalBuffer = null;
|
|
558
769
|
this._activeSources = /* @__PURE__ */ new Set();
|
|
559
770
|
this._audioContext = audioContext;
|
|
560
771
|
this._tempoMap = tempoMap;
|
|
561
|
-
this.
|
|
772
|
+
this._meterMap = meterMap;
|
|
562
773
|
this._destination = destination;
|
|
563
774
|
this._toAudioTime = toAudioTime;
|
|
564
775
|
}
|
|
@@ -568,31 +779,34 @@ var MetronomePlayer = class {
|
|
|
568
779
|
this.silence();
|
|
569
780
|
}
|
|
570
781
|
}
|
|
571
|
-
setBeatsPerBar(beats) {
|
|
572
|
-
this._beatsPerBar = beats;
|
|
573
|
-
}
|
|
574
782
|
setClickSounds(accent, normal) {
|
|
575
783
|
this._accentBuffer = accent;
|
|
576
784
|
this._normalBuffer = normal;
|
|
577
785
|
}
|
|
578
|
-
generate(
|
|
786
|
+
generate(fromTick, toTick) {
|
|
579
787
|
if (!this._enabled || !this._accentBuffer || !this._normalBuffer) {
|
|
580
788
|
return [];
|
|
581
789
|
}
|
|
582
790
|
const events = [];
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
const
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
const
|
|
589
|
-
const
|
|
590
|
-
|
|
791
|
+
let entry = this._meterMap.getEntryAt(fromTick);
|
|
792
|
+
let beatSize = this._meterMap.ticksPerBeat(fromTick);
|
|
793
|
+
const tickIntoSection = fromTick - entry.tick;
|
|
794
|
+
let tick = entry.tick + Math.ceil(tickIntoSection / beatSize) * beatSize;
|
|
795
|
+
while (tick < toTick) {
|
|
796
|
+
const tickPos = tick;
|
|
797
|
+
const currentEntry = this._meterMap.getEntryAt(tickPos);
|
|
798
|
+
if (currentEntry.tick !== entry.tick) {
|
|
799
|
+
entry = currentEntry;
|
|
800
|
+
beatSize = this._meterMap.ticksPerBeat(tickPos);
|
|
801
|
+
}
|
|
802
|
+
const isAccent = this._meterMap.isBarBoundary(tickPos);
|
|
591
803
|
events.push({
|
|
592
|
-
|
|
804
|
+
tick: tickPos,
|
|
593
805
|
isAccent,
|
|
594
806
|
buffer: isAccent ? this._accentBuffer : this._normalBuffer
|
|
595
807
|
});
|
|
808
|
+
beatSize = this._meterMap.ticksPerBeat(tickPos);
|
|
809
|
+
tick += beatSize;
|
|
596
810
|
}
|
|
597
811
|
return events;
|
|
598
812
|
}
|
|
@@ -612,10 +826,10 @@ var MetronomePlayer = class {
|
|
|
612
826
|
);
|
|
613
827
|
}
|
|
614
828
|
});
|
|
615
|
-
|
|
829
|
+
const transportTime = this._tempoMap.ticksToSeconds(event.tick);
|
|
830
|
+
source.start(this._toAudioTime(transportTime));
|
|
616
831
|
}
|
|
617
|
-
onPositionJump(
|
|
618
|
-
this.silence();
|
|
832
|
+
onPositionJump(_newTick) {
|
|
619
833
|
}
|
|
620
834
|
silence() {
|
|
621
835
|
for (const source of this._activeSources) {
|
|
@@ -648,25 +862,30 @@ var Transport = class _Transport {
|
|
|
648
862
|
this._soloedTrackIds = /* @__PURE__ */ new Set();
|
|
649
863
|
this._mutedTrackIds = /* @__PURE__ */ new Set();
|
|
650
864
|
this._playing = false;
|
|
865
|
+
this._loopEnabled = false;
|
|
866
|
+
this._loopStartSeconds = 0;
|
|
651
867
|
this._listeners = /* @__PURE__ */ new Map();
|
|
652
868
|
this._audioContext = audioContext;
|
|
653
869
|
const sampleRate = options.sampleRate ?? audioContext.sampleRate;
|
|
654
870
|
const ppqn = options.ppqn ?? 960;
|
|
655
871
|
const tempo = options.tempo ?? 120;
|
|
656
|
-
const
|
|
872
|
+
const numerator = options.numerator ?? 4;
|
|
873
|
+
const denominator = options.denominator ?? 4;
|
|
657
874
|
const lookahead = options.schedulerLookahead ?? 0.2;
|
|
658
|
-
_Transport._validateOptions(sampleRate, ppqn, tempo,
|
|
875
|
+
_Transport._validateOptions(sampleRate, ppqn, tempo, numerator, denominator, lookahead);
|
|
659
876
|
this._clock = new Clock(audioContext);
|
|
660
|
-
this.
|
|
877
|
+
this._sampleTimeline = new SampleTimeline(sampleRate);
|
|
878
|
+
this._meterMap = new MeterMap(ppqn, numerator, denominator);
|
|
879
|
+
this._tempoMap = new TempoMap(ppqn, tempo);
|
|
880
|
+
this._scheduler = new Scheduler(this._tempoMap, {
|
|
661
881
|
lookahead,
|
|
662
|
-
onLoop: (
|
|
663
|
-
|
|
882
|
+
onLoop: (loopStartSeconds, loopEndSeconds, currentTimeSeconds) => {
|
|
883
|
+
const timeToBoundary = loopEndSeconds - currentTimeSeconds;
|
|
884
|
+
this._clock.seekTo(loopStartSeconds - timeToBoundary);
|
|
664
885
|
}
|
|
665
886
|
});
|
|
666
|
-
this._sampleTimeline
|
|
667
|
-
this.
|
|
668
|
-
this._tempoMap = new TempoMap(ppqn, tempo);
|
|
669
|
-
this._initAudioGraph(audioContext, beatsPerBar);
|
|
887
|
+
this._sampleTimeline.setTempoMap(this._tempoMap);
|
|
888
|
+
this._initAudioGraph(audioContext);
|
|
670
889
|
this._timer = new Timer(() => {
|
|
671
890
|
const time = this._clock.getTime();
|
|
672
891
|
if (this._endTime !== void 0 && time >= this._endTime) {
|
|
@@ -689,7 +908,8 @@ var Transport = class _Transport {
|
|
|
689
908
|
this._scheduler.reset(currentTime);
|
|
690
909
|
this._endTime = endTime;
|
|
691
910
|
this._clock.start();
|
|
692
|
-
this.
|
|
911
|
+
const currentTick = this._tempoMap.secondsToTicks(currentTime);
|
|
912
|
+
this._clipPlayer.onPositionJump(currentTick);
|
|
693
913
|
this._timer.start();
|
|
694
914
|
this._playing = true;
|
|
695
915
|
this._emit("play");
|
|
@@ -725,12 +945,17 @@ var Transport = class _Transport {
|
|
|
725
945
|
this._endTime = void 0;
|
|
726
946
|
if (wasPlaying) {
|
|
727
947
|
this._clock.start();
|
|
728
|
-
this.
|
|
948
|
+
const seekTick = this._tempoMap.secondsToTicks(time);
|
|
949
|
+
this._clipPlayer.onPositionJump(seekTick);
|
|
729
950
|
this._timer.start();
|
|
730
951
|
}
|
|
731
952
|
}
|
|
732
953
|
getCurrentTime() {
|
|
733
|
-
|
|
954
|
+
const t = this._clock.getTime();
|
|
955
|
+
if (this._loopEnabled && t < this._loopStartSeconds) {
|
|
956
|
+
return this._loopStartSeconds;
|
|
957
|
+
}
|
|
958
|
+
return t;
|
|
734
959
|
}
|
|
735
960
|
isPlaying() {
|
|
736
961
|
return this._playing;
|
|
@@ -846,27 +1071,89 @@ var Transport = class _Transport {
|
|
|
846
1071
|
this._masterNode.setVolume(volume);
|
|
847
1072
|
}
|
|
848
1073
|
// --- Loop ---
|
|
849
|
-
|
|
850
|
-
|
|
1074
|
+
/** Primary loop API — ticks as source of truth */
|
|
1075
|
+
setLoop(enabled, startTick, endTick) {
|
|
1076
|
+
if (enabled && startTick >= endTick) {
|
|
1077
|
+
console.warn(
|
|
1078
|
+
"[waveform-playlist] Transport.setLoop: startTick (" + startTick + ") must be less than endTick (" + endTick + ")"
|
|
1079
|
+
);
|
|
1080
|
+
return;
|
|
1081
|
+
}
|
|
1082
|
+
this._loopEnabled = enabled;
|
|
1083
|
+
this._loopStartSeconds = this._tempoMap.ticksToSeconds(startTick);
|
|
1084
|
+
this._scheduler.setLoop(enabled, startTick, endTick);
|
|
1085
|
+
this._clipPlayer.setLoop(enabled, startTick, endTick);
|
|
1086
|
+
this._emit("loop");
|
|
1087
|
+
}
|
|
1088
|
+
/** Convenience — converts seconds to ticks */
|
|
1089
|
+
setLoopSeconds(enabled, startSec, endSec) {
|
|
1090
|
+
const startTick = this._tempoMap.secondsToTicks(startSec);
|
|
1091
|
+
const endTick = this._tempoMap.secondsToTicks(endSec);
|
|
1092
|
+
this.setLoop(enabled, startTick, endTick);
|
|
1093
|
+
}
|
|
1094
|
+
/** Convenience — sets loop in samples */
|
|
1095
|
+
setLoopSamples(enabled, startSample, endSample) {
|
|
1096
|
+
if (enabled && (!Number.isFinite(startSample) || !Number.isFinite(endSample))) {
|
|
1097
|
+
console.warn(
|
|
1098
|
+
"[waveform-playlist] Transport.setLoopSamples: non-finite sample values (" + startSample + ", " + endSample + ")"
|
|
1099
|
+
);
|
|
1100
|
+
return;
|
|
1101
|
+
}
|
|
1102
|
+
if (enabled && startSample >= endSample) {
|
|
851
1103
|
console.warn(
|
|
852
|
-
"[waveform-playlist] Transport.
|
|
1104
|
+
"[waveform-playlist] Transport.setLoopSamples: startSample (" + startSample + ") must be less than endSample (" + endSample + ")"
|
|
853
1105
|
);
|
|
854
1106
|
return;
|
|
855
1107
|
}
|
|
856
|
-
this.
|
|
857
|
-
this.
|
|
1108
|
+
const startTick = this._sampleTimeline.samplesToTicks(startSample);
|
|
1109
|
+
const endTick = this._sampleTimeline.samplesToTicks(endSample);
|
|
1110
|
+
this._loopEnabled = enabled;
|
|
1111
|
+
this._loopStartSeconds = this._tempoMap.ticksToSeconds(startTick);
|
|
1112
|
+
this._clipPlayer.setLoopSamples(enabled, startSample, endSample);
|
|
1113
|
+
this._scheduler.setLoop(enabled, startTick, endTick);
|
|
858
1114
|
this._emit("loop");
|
|
859
1115
|
}
|
|
860
1116
|
// --- Tempo ---
|
|
861
|
-
setTempo(bpm) {
|
|
862
|
-
this._tempoMap.setTempo(bpm);
|
|
1117
|
+
setTempo(bpm, atTick) {
|
|
1118
|
+
this._tempoMap.setTempo(bpm, atTick);
|
|
1119
|
+
this._emit("tempochange");
|
|
1120
|
+
}
|
|
1121
|
+
getTempo(atTick) {
|
|
1122
|
+
return this._tempoMap.getTempo(atTick);
|
|
1123
|
+
}
|
|
1124
|
+
// --- Meter ---
|
|
1125
|
+
setMeter(numerator, denominator, atTick) {
|
|
1126
|
+
this._meterMap.setMeter(numerator, denominator, atTick);
|
|
1127
|
+
this._emit("meterchange");
|
|
1128
|
+
}
|
|
1129
|
+
getMeter(atTick) {
|
|
1130
|
+
return this._meterMap.getMeter(atTick);
|
|
1131
|
+
}
|
|
1132
|
+
removeMeter(atTick) {
|
|
1133
|
+
this._meterMap.removeMeter(atTick);
|
|
1134
|
+
this._emit("meterchange");
|
|
1135
|
+
}
|
|
1136
|
+
clearMeters() {
|
|
1137
|
+
this._meterMap.clearMeters();
|
|
1138
|
+
this._emit("meterchange");
|
|
1139
|
+
}
|
|
1140
|
+
clearTempos() {
|
|
1141
|
+
this._tempoMap.clearTempos();
|
|
863
1142
|
this._emit("tempochange");
|
|
864
1143
|
}
|
|
865
|
-
|
|
866
|
-
return this.
|
|
1144
|
+
barToTick(bar) {
|
|
1145
|
+
return this._meterMap.barToTick(bar);
|
|
867
1146
|
}
|
|
868
|
-
|
|
869
|
-
this.
|
|
1147
|
+
tickToBar(tick) {
|
|
1148
|
+
return this._meterMap.tickToBar(tick);
|
|
1149
|
+
}
|
|
1150
|
+
/** Convert transport time (seconds) to tick position, using the tempo map. */
|
|
1151
|
+
timeToTick(seconds) {
|
|
1152
|
+
return this._tempoMap.secondsToTicks(seconds);
|
|
1153
|
+
}
|
|
1154
|
+
/** Convert tick position to transport time (seconds), using the tempo map. */
|
|
1155
|
+
tickToTime(tick) {
|
|
1156
|
+
return this._tempoMap.ticksToSeconds(tick);
|
|
870
1157
|
}
|
|
871
1158
|
// --- Metronome ---
|
|
872
1159
|
setMetronomeEnabled(enabled) {
|
|
@@ -913,7 +1200,7 @@ var Transport = class _Transport {
|
|
|
913
1200
|
this._listeners.clear();
|
|
914
1201
|
}
|
|
915
1202
|
// --- Private ---
|
|
916
|
-
static _validateOptions(sampleRate, ppqn, tempo,
|
|
1203
|
+
static _validateOptions(sampleRate, ppqn, tempo, numerator, denominator, lookahead) {
|
|
917
1204
|
if (sampleRate <= 0) {
|
|
918
1205
|
throw new Error(
|
|
919
1206
|
"[waveform-playlist] Transport: sampleRate must be positive, got " + sampleRate
|
|
@@ -927,9 +1214,14 @@ var Transport = class _Transport {
|
|
|
927
1214
|
if (tempo <= 0) {
|
|
928
1215
|
throw new Error("[waveform-playlist] Transport: tempo must be positive, got " + tempo);
|
|
929
1216
|
}
|
|
930
|
-
if (
|
|
1217
|
+
if (!Number.isInteger(numerator) || numerator < 1 || numerator > 32) {
|
|
1218
|
+
throw new Error(
|
|
1219
|
+
"[waveform-playlist] Transport: numerator must be an integer 1-32, got " + numerator
|
|
1220
|
+
);
|
|
1221
|
+
}
|
|
1222
|
+
if (denominator <= 0 || (denominator & denominator - 1) !== 0 || denominator > 32) {
|
|
931
1223
|
throw new Error(
|
|
932
|
-
"[waveform-playlist] Transport:
|
|
1224
|
+
"[waveform-playlist] Transport: denominator must be a power of 2 (1-32), got " + denominator
|
|
933
1225
|
);
|
|
934
1226
|
}
|
|
935
1227
|
if (lookahead <= 0) {
|
|
@@ -938,19 +1230,23 @@ var Transport = class _Transport {
|
|
|
938
1230
|
);
|
|
939
1231
|
}
|
|
940
1232
|
}
|
|
941
|
-
_initAudioGraph(audioContext
|
|
1233
|
+
_initAudioGraph(audioContext) {
|
|
942
1234
|
this._masterNode = new MasterNode(audioContext);
|
|
943
1235
|
this._masterNode.output.connect(audioContext.destination);
|
|
944
1236
|
const toAudioTime = (transportTime) => this._clock.toAudioTime(transportTime);
|
|
945
|
-
this._clipPlayer = new ClipPlayer(
|
|
1237
|
+
this._clipPlayer = new ClipPlayer(
|
|
1238
|
+
audioContext,
|
|
1239
|
+
this._sampleTimeline,
|
|
1240
|
+
this._tempoMap,
|
|
1241
|
+
toAudioTime
|
|
1242
|
+
);
|
|
946
1243
|
this._metronomePlayer = new MetronomePlayer(
|
|
947
1244
|
audioContext,
|
|
948
1245
|
this._tempoMap,
|
|
949
|
-
this.
|
|
1246
|
+
this._meterMap,
|
|
950
1247
|
this._masterNode.input,
|
|
951
1248
|
toAudioTime
|
|
952
1249
|
);
|
|
953
|
-
this._metronomePlayer.setBeatsPerBar(beatsPerBar);
|
|
954
1250
|
this._scheduler.addListener(this._clipPlayer);
|
|
955
1251
|
this._scheduler.addListener(this._metronomePlayer);
|
|
956
1252
|
}
|
|
@@ -1046,7 +1342,7 @@ var NativePlayoutAdapter = class {
|
|
|
1046
1342
|
this._transport.setTrackPan(trackId, pan);
|
|
1047
1343
|
}
|
|
1048
1344
|
setLoop(enabled, start, end) {
|
|
1049
|
-
this._transport.
|
|
1345
|
+
this._transport.setLoopSeconds(enabled, start, end);
|
|
1050
1346
|
}
|
|
1051
1347
|
dispose() {
|
|
1052
1348
|
this._transport.dispose();
|
|
@@ -1056,12 +1352,12 @@ export {
|
|
|
1056
1352
|
ClipPlayer,
|
|
1057
1353
|
Clock,
|
|
1058
1354
|
MasterNode,
|
|
1355
|
+
MeterMap,
|
|
1059
1356
|
MetronomePlayer,
|
|
1060
1357
|
NativePlayoutAdapter,
|
|
1061
1358
|
SampleTimeline,
|
|
1062
1359
|
Scheduler,
|
|
1063
1360
|
TempoMap,
|
|
1064
|
-
TickTimeline,
|
|
1065
1361
|
Timer,
|
|
1066
1362
|
TrackNode,
|
|
1067
1363
|
Transport
|