@waveform-playlist/core 11.3.0 → 12.0.0
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.mts +181 -3
- package/dist/index.d.ts +181 -3
- package/dist/index.js +379 -12
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +364 -10
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -2,10 +2,50 @@
|
|
|
2
2
|
var MAX_CANVAS_WIDTH = 1e3;
|
|
3
3
|
|
|
4
4
|
// src/types/clip.ts
|
|
5
|
+
function createClipFromTicks(options) {
|
|
6
|
+
const { startTick, ticksToSeconds, bpm, ppqn } = options;
|
|
7
|
+
if (startTick < 0) {
|
|
8
|
+
throw new Error("createClipFromTicks: startTick must be non-negative");
|
|
9
|
+
}
|
|
10
|
+
let toSeconds;
|
|
11
|
+
if (ticksToSeconds) {
|
|
12
|
+
toSeconds = ticksToSeconds;
|
|
13
|
+
} else if (bpm !== void 0 && ppqn !== void 0) {
|
|
14
|
+
toSeconds = (tick) => tick * 60 / (ppqn * bpm);
|
|
15
|
+
} else {
|
|
16
|
+
throw new Error(
|
|
17
|
+
"createClipFromTicks: either ticksToSeconds callback or both bpm and ppqn are required"
|
|
18
|
+
);
|
|
19
|
+
}
|
|
20
|
+
const sampleRate = options.audioBuffer?.sampleRate ?? options.sampleRate ?? options.waveformData?.sample_rate;
|
|
21
|
+
if (sampleRate === void 0) {
|
|
22
|
+
throw new Error("createClipFromTicks: sampleRate is required when audioBuffer is not provided");
|
|
23
|
+
}
|
|
24
|
+
const startSample = Math.round(toSeconds(startTick) * sampleRate);
|
|
25
|
+
return createClip({
|
|
26
|
+
audioBuffer: options.audioBuffer,
|
|
27
|
+
startSample,
|
|
28
|
+
startTick,
|
|
29
|
+
durationSamples: options.durationSamples,
|
|
30
|
+
offsetSamples: options.offsetSamples,
|
|
31
|
+
gain: options.gain,
|
|
32
|
+
name: options.name,
|
|
33
|
+
color: options.color,
|
|
34
|
+
fadeIn: options.fadeIn,
|
|
35
|
+
fadeOut: options.fadeOut,
|
|
36
|
+
waveformData: options.waveformData,
|
|
37
|
+
sampleRate: options.sampleRate,
|
|
38
|
+
sourceDurationSamples: options.sourceDurationSamples,
|
|
39
|
+
midiNotes: options.midiNotes,
|
|
40
|
+
midiChannel: options.midiChannel,
|
|
41
|
+
midiProgram: options.midiProgram
|
|
42
|
+
});
|
|
43
|
+
}
|
|
5
44
|
function createClip(options) {
|
|
6
45
|
const {
|
|
7
46
|
audioBuffer,
|
|
8
47
|
startSample,
|
|
48
|
+
startTick,
|
|
9
49
|
offsetSamples = 0,
|
|
10
50
|
gain = 1,
|
|
11
51
|
name,
|
|
@@ -39,6 +79,7 @@ function createClip(options) {
|
|
|
39
79
|
id: generateId(),
|
|
40
80
|
audioBuffer,
|
|
41
81
|
startSample,
|
|
82
|
+
startTick,
|
|
42
83
|
durationSamples,
|
|
43
84
|
offsetSamples,
|
|
44
85
|
sampleRate,
|
|
@@ -85,6 +126,7 @@ function createClipFromSeconds(options) {
|
|
|
85
126
|
return createClip({
|
|
86
127
|
audioBuffer,
|
|
87
128
|
startSample: Math.round(startTime * sampleRate),
|
|
129
|
+
startTick: options.startTick,
|
|
88
130
|
durationSamples: Math.round(duration * sampleRate),
|
|
89
131
|
offsetSamples: Math.round(offset * sampleRate),
|
|
90
132
|
sampleRate,
|
|
@@ -234,14 +276,6 @@ function samplesToTicks(samples, bpm, sampleRate, ppqn = PPQN) {
|
|
|
234
276
|
function snapToGrid(ticks, gridSizeTicks) {
|
|
235
277
|
return Math.round(ticks / gridSizeTicks) * gridSizeTicks;
|
|
236
278
|
}
|
|
237
|
-
function ticksToBarBeatLabel(ticks, timeSignature, ppqn = PPQN) {
|
|
238
|
-
const barTicks = ticksPerBar(timeSignature, ppqn);
|
|
239
|
-
const beatTicks = ticksPerBeat(timeSignature, ppqn);
|
|
240
|
-
const bar = Math.floor(ticks / barTicks) + 1;
|
|
241
|
-
const beatInBar = Math.floor(ticks % barTicks / beatTicks) + 1;
|
|
242
|
-
if (beatInBar === 1) return `${bar}`;
|
|
243
|
-
return `${bar}.${beatInBar}`;
|
|
244
|
-
}
|
|
245
279
|
|
|
246
280
|
// src/utils/dBUtils.ts
|
|
247
281
|
var DEFAULT_FLOOR = -100;
|
|
@@ -266,12 +300,313 @@ function normalizedToDb(normalized, floor = DEFAULT_FLOOR) {
|
|
|
266
300
|
const clamped = Math.max(0, normalized);
|
|
267
301
|
return clamped * -floor + floor;
|
|
268
302
|
}
|
|
303
|
+
function gainToDb(gain) {
|
|
304
|
+
return 20 * Math.log10(Math.max(gain, 1e-4));
|
|
305
|
+
}
|
|
269
306
|
function gainToNormalized(gain, floor = DEFAULT_FLOOR) {
|
|
270
307
|
if (gain <= 0) return 0;
|
|
271
308
|
const db = 20 * Math.log10(gain);
|
|
272
309
|
return dBToNormalized(db, floor);
|
|
273
310
|
}
|
|
274
311
|
|
|
312
|
+
// src/utils/musicalTicks.ts
|
|
313
|
+
function snapToTicks(snapTo, timeSignature, ppqn = 960) {
|
|
314
|
+
const ts = timeSignature;
|
|
315
|
+
switch (snapTo) {
|
|
316
|
+
case "bar":
|
|
317
|
+
return ticksPerBar(ts, ppqn);
|
|
318
|
+
case "beat":
|
|
319
|
+
return ticksPerBeat(ts, ppqn);
|
|
320
|
+
case "1/2":
|
|
321
|
+
return ppqn * 2;
|
|
322
|
+
case "1/4":
|
|
323
|
+
return ppqn;
|
|
324
|
+
case "1/8":
|
|
325
|
+
return ppqn / 2;
|
|
326
|
+
case "1/16":
|
|
327
|
+
return ppqn / 4;
|
|
328
|
+
case "1/32":
|
|
329
|
+
return ppqn / 8;
|
|
330
|
+
case "1/2T":
|
|
331
|
+
return Math.round(ppqn * 2 * 2 / 3);
|
|
332
|
+
case "1/4T":
|
|
333
|
+
return Math.round(ppqn * 2 / 3);
|
|
334
|
+
case "1/8T":
|
|
335
|
+
return Math.round(ppqn * 2 / 6);
|
|
336
|
+
case "1/16T":
|
|
337
|
+
return Math.round(ppqn * 2 / 12);
|
|
338
|
+
case "off":
|
|
339
|
+
return 0;
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
var MIN_PIXELS_PER_UNIT = 8;
|
|
343
|
+
var MIN_PIXELS_PER_LABEL = 60;
|
|
344
|
+
function computeMusicalTicks(params) {
|
|
345
|
+
const { meterEntries, ticksPerPixel, startPixel, endPixel, ppqn = 960 } = params;
|
|
346
|
+
const firstMeter = meterEntries[0] ?? { tick: 0, numerator: 4, denominator: 4 };
|
|
347
|
+
if (ticksPerPixel <= 0 || ppqn <= 0 || firstMeter.denominator <= 0) {
|
|
348
|
+
return { ticks: [], pixelsPerQuarterNote: 0, zoomLevel: "coarse" };
|
|
349
|
+
}
|
|
350
|
+
const pixelsPerQuarterNote = ppqn / ticksPerPixel;
|
|
351
|
+
const tpEighth = ppqn / 2;
|
|
352
|
+
const tpSixteenth = ppqn / 4;
|
|
353
|
+
const pixelsPerEighth = tpEighth / ticksPerPixel;
|
|
354
|
+
const pixelsPerSixteenth = tpSixteenth / ticksPerPixel;
|
|
355
|
+
let zoomLevel;
|
|
356
|
+
if (pixelsPerQuarterNote * 4 < MIN_PIXELS_PER_UNIT) {
|
|
357
|
+
zoomLevel = "coarse";
|
|
358
|
+
} else if (pixelsPerQuarterNote < MIN_PIXELS_PER_UNIT) {
|
|
359
|
+
zoomLevel = "bar";
|
|
360
|
+
} else if (pixelsPerEighth < MIN_PIXELS_PER_UNIT) {
|
|
361
|
+
zoomLevel = "beat";
|
|
362
|
+
} else if (pixelsPerSixteenth < MIN_PIXELS_PER_UNIT) {
|
|
363
|
+
zoomLevel = "eighth";
|
|
364
|
+
} else {
|
|
365
|
+
zoomLevel = "sixteenth";
|
|
366
|
+
}
|
|
367
|
+
let coarseQuarterNoteStep;
|
|
368
|
+
let coarseQuarterNotes = 0;
|
|
369
|
+
if (zoomLevel === "coarse") {
|
|
370
|
+
coarseQuarterNotes = 2;
|
|
371
|
+
while (coarseQuarterNotes * ppqn / ticksPerPixel < MIN_PIXELS_PER_UNIT) {
|
|
372
|
+
coarseQuarterNotes *= 2;
|
|
373
|
+
}
|
|
374
|
+
coarseQuarterNoteStep = coarseQuarterNotes;
|
|
375
|
+
}
|
|
376
|
+
const startTick = startPixel * ticksPerPixel;
|
|
377
|
+
const endTick = endPixel * ticksPerPixel;
|
|
378
|
+
const segments = [];
|
|
379
|
+
{
|
|
380
|
+
let cumulativeBars = 0;
|
|
381
|
+
for (let i = 0; i < meterEntries.length; i++) {
|
|
382
|
+
const meter = meterEntries[i];
|
|
383
|
+
const segmentStart = meter.tick;
|
|
384
|
+
const segmentEnd = i + 1 < meterEntries.length ? meterEntries[i + 1].tick : Number.MAX_SAFE_INTEGER;
|
|
385
|
+
const ts = [meter.numerator, meter.denominator];
|
|
386
|
+
const tpBar = ticksPerBar(ts, ppqn);
|
|
387
|
+
segments.push({
|
|
388
|
+
segmentStartTick: segmentStart,
|
|
389
|
+
segmentEndTick: segmentEnd,
|
|
390
|
+
meter,
|
|
391
|
+
barOffset: cumulativeBars
|
|
392
|
+
});
|
|
393
|
+
if (segmentEnd !== Number.MAX_SAFE_INTEGER) {
|
|
394
|
+
const segmentLen = segmentEnd - segmentStart;
|
|
395
|
+
cumulativeBars += Math.floor(segmentLen / tpBar);
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
const ticks = [];
|
|
400
|
+
for (const { segmentStartTick, segmentEndTick, meter, barOffset } of segments) {
|
|
401
|
+
const ts = [meter.numerator, meter.denominator];
|
|
402
|
+
const tpBeat = ticksPerBeat(ts, ppqn);
|
|
403
|
+
const tpBar = ticksPerBar(ts, ppqn);
|
|
404
|
+
let stepTicks;
|
|
405
|
+
if (zoomLevel === "coarse") {
|
|
406
|
+
stepTicks = coarseQuarterNotes * ppqn;
|
|
407
|
+
} else if (zoomLevel === "bar") {
|
|
408
|
+
stepTicks = tpBar;
|
|
409
|
+
} else if (zoomLevel === "beat") {
|
|
410
|
+
stepTicks = tpBeat;
|
|
411
|
+
} else if (zoomLevel === "eighth") {
|
|
412
|
+
stepTicks = tpEighth;
|
|
413
|
+
} else {
|
|
414
|
+
stepTicks = tpSixteenth;
|
|
415
|
+
}
|
|
416
|
+
const segmentTickStart = Math.max(segmentStartTick, startTick);
|
|
417
|
+
const segmentTickEnd = Math.min(segmentEndTick - 1, endTick);
|
|
418
|
+
if (segmentTickStart > segmentTickEnd) {
|
|
419
|
+
continue;
|
|
420
|
+
}
|
|
421
|
+
const offsetIntoSegment = segmentTickStart - segmentStartTick;
|
|
422
|
+
const firstStepOffset = Math.floor(offsetIntoSegment / stepTicks) * stepTicks;
|
|
423
|
+
const firstStepTick = segmentStartTick + firstStepOffset;
|
|
424
|
+
for (let tick = firstStepTick; tick <= segmentTickEnd && tick < segmentEndTick; tick += stepTicks) {
|
|
425
|
+
const pixel = tick / ticksPerPixel;
|
|
426
|
+
if (pixel < startPixel || pixel > endPixel) {
|
|
427
|
+
continue;
|
|
428
|
+
}
|
|
429
|
+
const tickOffsetInSegment = tick - segmentStartTick;
|
|
430
|
+
let type;
|
|
431
|
+
if (tickOffsetInSegment % tpBar === 0) {
|
|
432
|
+
type = "major";
|
|
433
|
+
} else if (tickOffsetInSegment % tpBeat === 0) {
|
|
434
|
+
type = "minor";
|
|
435
|
+
} else {
|
|
436
|
+
type = "minorMinor";
|
|
437
|
+
}
|
|
438
|
+
const barIndexInSegment = Math.floor(tickOffsetInSegment / tpBar);
|
|
439
|
+
const barIndex = barOffset + barIndexInSegment;
|
|
440
|
+
let label;
|
|
441
|
+
if (type === "major") {
|
|
442
|
+
label = `${barIndex + 1}`;
|
|
443
|
+
} else if (type === "minor" && tpBeat / ticksPerPixel >= MIN_PIXELS_PER_LABEL) {
|
|
444
|
+
const beatInBar = Math.floor(tickOffsetInSegment % tpBar / tpBeat) + 1;
|
|
445
|
+
label = `${barIndex + 1}.${beatInBar}`;
|
|
446
|
+
}
|
|
447
|
+
ticks.push({ pixel, type, barIndex, ...label !== void 0 ? { label } : {} });
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
ticks.sort((a, b) => a.pixel - b.pixel);
|
|
451
|
+
const result = {
|
|
452
|
+
ticks,
|
|
453
|
+
pixelsPerQuarterNote,
|
|
454
|
+
zoomLevel,
|
|
455
|
+
...coarseQuarterNoteStep !== void 0 ? { coarseQuarterNoteStep } : {}
|
|
456
|
+
};
|
|
457
|
+
return result;
|
|
458
|
+
}
|
|
459
|
+
function snapTickToGrid(tick, snapTo, meterEntries, ppqn = 960) {
|
|
460
|
+
if (snapTo === "off") return tick;
|
|
461
|
+
let meter = meterEntries[0] ?? { tick: 0, numerator: 4, denominator: 4 };
|
|
462
|
+
for (const entry of meterEntries) {
|
|
463
|
+
if (entry.tick <= tick) {
|
|
464
|
+
meter = entry;
|
|
465
|
+
} else {
|
|
466
|
+
break;
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
const ts = [meter.numerator, meter.denominator];
|
|
470
|
+
const gridSize = snapToTicks(snapTo, ts, ppqn);
|
|
471
|
+
if (gridSize <= 0) return tick;
|
|
472
|
+
const offset = tick - meter.tick;
|
|
473
|
+
return meter.tick + Math.round(offset / gridSize) * gridSize;
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
// src/utils/meterDetection.ts
|
|
477
|
+
function detectMeterChanges(beats, firstBeatTick, ppqn) {
|
|
478
|
+
const DEFAULT_NUMERATOR = 4;
|
|
479
|
+
const DENOMINATOR = 4;
|
|
480
|
+
const defaultResult = [
|
|
481
|
+
{ tick: 0, numerator: DEFAULT_NUMERATOR, denominator: DENOMINATOR }
|
|
482
|
+
];
|
|
483
|
+
if (beats.length === 0) {
|
|
484
|
+
return defaultResult;
|
|
485
|
+
}
|
|
486
|
+
const firstDownbeatIndex = beats.findIndex((b) => b.beat === 1);
|
|
487
|
+
if (firstDownbeatIndex === -1) {
|
|
488
|
+
return defaultResult;
|
|
489
|
+
}
|
|
490
|
+
const bars = [];
|
|
491
|
+
let barStartBeatIndex = firstDownbeatIndex;
|
|
492
|
+
for (let i = firstDownbeatIndex + 1; i < beats.length; i++) {
|
|
493
|
+
if (beats[i].beat === 1) {
|
|
494
|
+
bars.push({ beatIndex: barStartBeatIndex, count: i - barStartBeatIndex });
|
|
495
|
+
barStartBeatIndex = i;
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
if (bars.length === 0) {
|
|
499
|
+
return defaultResult;
|
|
500
|
+
}
|
|
501
|
+
const rawEntries = [];
|
|
502
|
+
let prevNumerator = -1;
|
|
503
|
+
for (const bar of bars) {
|
|
504
|
+
const numerator = bar.count;
|
|
505
|
+
if (numerator !== prevNumerator) {
|
|
506
|
+
const tick = firstBeatTick + bar.beatIndex * ppqn;
|
|
507
|
+
rawEntries.push({ tick, numerator, denominator: DENOMINATOR });
|
|
508
|
+
prevNumerator = numerator;
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
if (rawEntries.length === 0) {
|
|
512
|
+
return defaultResult;
|
|
513
|
+
}
|
|
514
|
+
if (rawEntries[0].tick === 0) {
|
|
515
|
+
return rawEntries;
|
|
516
|
+
}
|
|
517
|
+
const tick0Entry = {
|
|
518
|
+
tick: 0,
|
|
519
|
+
numerator: rawEntries[0].numerator,
|
|
520
|
+
denominator: DENOMINATOR
|
|
521
|
+
};
|
|
522
|
+
const rest = rawEntries[0].numerator === tick0Entry.numerator ? rawEntries.slice(1) : rawEntries;
|
|
523
|
+
return [tick0Entry, ...rest];
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
// src/utils/peaksGenerator.ts
|
|
527
|
+
function generatePeaks(samples, samplesPerPixel, bits = 16) {
|
|
528
|
+
const numPeaks = Math.ceil(samples.length / samplesPerPixel);
|
|
529
|
+
const peakArray = bits === 8 ? new Int8Array(numPeaks * 2) : new Int16Array(numPeaks * 2);
|
|
530
|
+
const maxValue = 2 ** (bits - 1);
|
|
531
|
+
for (let i = 0; i < numPeaks; i++) {
|
|
532
|
+
const start = i * samplesPerPixel;
|
|
533
|
+
const end = Math.min(start + samplesPerPixel, samples.length);
|
|
534
|
+
let min = 0;
|
|
535
|
+
let max = 0;
|
|
536
|
+
for (let j = start; j < end; j++) {
|
|
537
|
+
const value = samples[j];
|
|
538
|
+
if (value < min) min = value;
|
|
539
|
+
if (value > max) max = value;
|
|
540
|
+
}
|
|
541
|
+
peakArray[i * 2] = Math.max(-maxValue, Math.floor(min * maxValue));
|
|
542
|
+
peakArray[i * 2 + 1] = Math.min(maxValue - 1, Math.floor(max * maxValue));
|
|
543
|
+
}
|
|
544
|
+
return peakArray;
|
|
545
|
+
}
|
|
546
|
+
function appendPeaks(existingPeaks, newSamples, samplesPerPixel, totalSamplesProcessed, bits = 16) {
|
|
547
|
+
const maxValue = 2 ** (bits - 1);
|
|
548
|
+
const remainder = totalSamplesProcessed % samplesPerPixel;
|
|
549
|
+
let offset = 0;
|
|
550
|
+
if (remainder > 0 && existingPeaks.length > 0) {
|
|
551
|
+
const samplesToComplete = samplesPerPixel - remainder;
|
|
552
|
+
const endIndex = Math.min(samplesToComplete, newSamples.length);
|
|
553
|
+
let min = existingPeaks[existingPeaks.length - 2] / maxValue;
|
|
554
|
+
let max = existingPeaks[existingPeaks.length - 1] / maxValue;
|
|
555
|
+
for (let i = 0; i < endIndex; i++) {
|
|
556
|
+
const value = newSamples[i];
|
|
557
|
+
if (value < min) min = value;
|
|
558
|
+
if (value > max) max = value;
|
|
559
|
+
}
|
|
560
|
+
const updated = new (bits === 8 ? Int8Array : Int16Array)(existingPeaks.length);
|
|
561
|
+
updated.set(existingPeaks);
|
|
562
|
+
updated[existingPeaks.length - 2] = Math.max(-maxValue, Math.floor(min * maxValue));
|
|
563
|
+
updated[existingPeaks.length - 1] = Math.min(maxValue - 1, Math.floor(max * maxValue));
|
|
564
|
+
offset = endIndex;
|
|
565
|
+
const newPeaks2 = generatePeaks(newSamples.slice(offset), samplesPerPixel, bits);
|
|
566
|
+
const result2 = new (bits === 8 ? Int8Array : Int16Array)(updated.length + newPeaks2.length);
|
|
567
|
+
result2.set(updated);
|
|
568
|
+
result2.set(newPeaks2, updated.length);
|
|
569
|
+
return result2;
|
|
570
|
+
}
|
|
571
|
+
const newPeaks = generatePeaks(newSamples.slice(offset), samplesPerPixel, bits);
|
|
572
|
+
const result = new (bits === 8 ? Int8Array : Int16Array)(existingPeaks.length + newPeaks.length);
|
|
573
|
+
result.set(existingPeaks);
|
|
574
|
+
result.set(newPeaks, existingPeaks.length);
|
|
575
|
+
return result;
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
// src/utils/audioBufferUtils.ts
|
|
579
|
+
function concatenateAudioData(chunks) {
|
|
580
|
+
const totalLength = chunks.reduce((sum, chunk) => sum + chunk.length, 0);
|
|
581
|
+
const result = new Float32Array(totalLength);
|
|
582
|
+
let offset = 0;
|
|
583
|
+
for (const chunk of chunks) {
|
|
584
|
+
result.set(chunk, offset);
|
|
585
|
+
offset += chunk.length;
|
|
586
|
+
}
|
|
587
|
+
return result;
|
|
588
|
+
}
|
|
589
|
+
function createAudioBuffer(audioContext, channelData, sampleRate, channelCount = 1) {
|
|
590
|
+
const channels = channelData instanceof Float32Array ? [channelData] : channelData;
|
|
591
|
+
const length = channels[0]?.length ?? 0;
|
|
592
|
+
const buffer = audioContext.createBuffer(channelCount, length, sampleRate);
|
|
593
|
+
for (let ch = 0; ch < Math.min(channelCount, channels.length); ch++) {
|
|
594
|
+
buffer.copyToChannel(new Float32Array(channels[ch]), ch);
|
|
595
|
+
}
|
|
596
|
+
return buffer;
|
|
597
|
+
}
|
|
598
|
+
function appendToAudioBuffer(audioContext, existingBuffer, newSamples, sampleRate) {
|
|
599
|
+
if (!existingBuffer) {
|
|
600
|
+
return createAudioBuffer(audioContext, [newSamples], sampleRate);
|
|
601
|
+
}
|
|
602
|
+
const existingData = existingBuffer.getChannelData(0);
|
|
603
|
+
const combined = concatenateAudioData([existingData, newSamples]);
|
|
604
|
+
return createAudioBuffer(audioContext, [combined], sampleRate);
|
|
605
|
+
}
|
|
606
|
+
function calculateDuration(sampleCount, sampleRate) {
|
|
607
|
+
return sampleCount / sampleRate;
|
|
608
|
+
}
|
|
609
|
+
|
|
275
610
|
// src/clipTimeHelpers.ts
|
|
276
611
|
function clipStartTime(clip) {
|
|
277
612
|
return clip.startSample / clip.sampleRate;
|
|
@@ -285,6 +620,12 @@ function clipOffsetTime(clip) {
|
|
|
285
620
|
function clipDurationTime(clip) {
|
|
286
621
|
return clip.durationSamples / clip.sampleRate;
|
|
287
622
|
}
|
|
623
|
+
function trackChannelCount(track) {
|
|
624
|
+
return track.clips.reduce(
|
|
625
|
+
(max, clip) => Math.max(max, clip.audioBuffer?.numberOfChannels ?? 1),
|
|
626
|
+
1
|
|
627
|
+
);
|
|
628
|
+
}
|
|
288
629
|
function clipPixelWidth(startSample, durationSamples, samplesPerPixel) {
|
|
289
630
|
return Math.floor((startSample + durationSamples) / samplesPerPixel) - Math.floor(startSample / samplesPerPixel);
|
|
290
631
|
}
|
|
@@ -421,24 +762,35 @@ var getShortcutLabel = (shortcut) => {
|
|
|
421
762
|
export {
|
|
422
763
|
InteractionState,
|
|
423
764
|
MAX_CANVAS_WIDTH,
|
|
765
|
+
MIN_PIXELS_PER_UNIT,
|
|
424
766
|
PPQN,
|
|
767
|
+
appendPeaks,
|
|
768
|
+
appendToAudioBuffer,
|
|
425
769
|
applyFadeIn,
|
|
426
770
|
applyFadeOut,
|
|
771
|
+
calculateDuration,
|
|
427
772
|
clipDurationTime,
|
|
428
773
|
clipEndTime,
|
|
429
774
|
clipOffsetTime,
|
|
430
775
|
clipPixelWidth,
|
|
431
776
|
clipStartTime,
|
|
432
777
|
clipsOverlap,
|
|
778
|
+
computeMusicalTicks,
|
|
779
|
+
concatenateAudioData,
|
|
780
|
+
createAudioBuffer,
|
|
433
781
|
createClip,
|
|
434
782
|
createClipFromSeconds,
|
|
783
|
+
createClipFromTicks,
|
|
435
784
|
createTimeline,
|
|
436
785
|
createTrack,
|
|
437
786
|
dBToNormalized,
|
|
787
|
+
detectMeterChanges,
|
|
438
788
|
exponentialCurve,
|
|
439
789
|
findGaps,
|
|
790
|
+
gainToDb,
|
|
440
791
|
gainToNormalized,
|
|
441
792
|
generateCurve,
|
|
793
|
+
generatePeaks,
|
|
442
794
|
getClipsAtSample,
|
|
443
795
|
getClipsInRange,
|
|
444
796
|
getShortcutLabel,
|
|
@@ -454,11 +806,13 @@ export {
|
|
|
454
806
|
samplesToTicks,
|
|
455
807
|
secondsToPixels,
|
|
456
808
|
secondsToSamples,
|
|
809
|
+
snapTickToGrid,
|
|
457
810
|
snapToGrid,
|
|
811
|
+
snapToTicks,
|
|
458
812
|
sortClipsByTime,
|
|
459
813
|
ticksPerBar,
|
|
460
814
|
ticksPerBeat,
|
|
461
|
-
|
|
462
|
-
|
|
815
|
+
ticksToSamples,
|
|
816
|
+
trackChannelCount
|
|
463
817
|
};
|
|
464
818
|
//# sourceMappingURL=index.mjs.map
|