@waveform-playlist/core 11.3.1 → 12.1.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 +125 -10
- package/dist/index.d.ts +125 -10
- package/dist/index.js +301 -61
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +292 -60
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -24,8 +24,12 @@ __export(index_exports, {
|
|
|
24
24
|
MAX_CANVAS_WIDTH: () => MAX_CANVAS_WIDTH,
|
|
25
25
|
MIN_PIXELS_PER_UNIT: () => MIN_PIXELS_PER_UNIT,
|
|
26
26
|
PPQN: () => PPQN,
|
|
27
|
+
appendPeaks: () => appendPeaks,
|
|
28
|
+
appendToAudioBuffer: () => appendToAudioBuffer,
|
|
27
29
|
applyFadeIn: () => applyFadeIn,
|
|
28
30
|
applyFadeOut: () => applyFadeOut,
|
|
31
|
+
audibleLatencySamples: () => audibleLatencySamples,
|
|
32
|
+
calculateDuration: () => calculateDuration,
|
|
29
33
|
clipDurationTime: () => clipDurationTime,
|
|
30
34
|
clipEndTime: () => clipEndTime,
|
|
31
35
|
clipOffsetTime: () => clipOffsetTime,
|
|
@@ -33,16 +37,21 @@ __export(index_exports, {
|
|
|
33
37
|
clipStartTime: () => clipStartTime,
|
|
34
38
|
clipsOverlap: () => clipsOverlap,
|
|
35
39
|
computeMusicalTicks: () => computeMusicalTicks,
|
|
40
|
+
concatenateAudioData: () => concatenateAudioData,
|
|
41
|
+
createAudioBuffer: () => createAudioBuffer,
|
|
36
42
|
createClip: () => createClip,
|
|
37
43
|
createClipFromSeconds: () => createClipFromSeconds,
|
|
44
|
+
createClipFromTicks: () => createClipFromTicks,
|
|
38
45
|
createTimeline: () => createTimeline,
|
|
39
46
|
createTrack: () => createTrack,
|
|
40
47
|
dBToNormalized: () => dBToNormalized,
|
|
48
|
+
detectMeterChanges: () => detectMeterChanges,
|
|
41
49
|
exponentialCurve: () => exponentialCurve,
|
|
42
50
|
findGaps: () => findGaps,
|
|
43
51
|
gainToDb: () => gainToDb,
|
|
44
52
|
gainToNormalized: () => gainToNormalized,
|
|
45
53
|
generateCurve: () => generateCurve,
|
|
54
|
+
generatePeaks: () => generatePeaks,
|
|
46
55
|
getClipsAtSample: () => getClipsAtSample,
|
|
47
56
|
getClipsInRange: () => getClipsInRange,
|
|
48
57
|
getShortcutLabel: () => getShortcutLabel,
|
|
@@ -64,7 +73,6 @@ __export(index_exports, {
|
|
|
64
73
|
sortClipsByTime: () => sortClipsByTime,
|
|
65
74
|
ticksPerBar: () => ticksPerBar,
|
|
66
75
|
ticksPerBeat: () => ticksPerBeat,
|
|
67
|
-
ticksToBarBeatLabel: () => ticksToBarBeatLabel,
|
|
68
76
|
ticksToSamples: () => ticksToSamples,
|
|
69
77
|
trackChannelCount: () => trackChannelCount
|
|
70
78
|
});
|
|
@@ -74,10 +82,50 @@ module.exports = __toCommonJS(index_exports);
|
|
|
74
82
|
var MAX_CANVAS_WIDTH = 1e3;
|
|
75
83
|
|
|
76
84
|
// src/types/clip.ts
|
|
85
|
+
function createClipFromTicks(options) {
|
|
86
|
+
const { startTick, ticksToSeconds, bpm, ppqn } = options;
|
|
87
|
+
if (startTick < 0) {
|
|
88
|
+
throw new Error("createClipFromTicks: startTick must be non-negative");
|
|
89
|
+
}
|
|
90
|
+
let toSeconds;
|
|
91
|
+
if (ticksToSeconds) {
|
|
92
|
+
toSeconds = ticksToSeconds;
|
|
93
|
+
} else if (bpm !== void 0 && ppqn !== void 0) {
|
|
94
|
+
toSeconds = (tick) => tick * 60 / (ppqn * bpm);
|
|
95
|
+
} else {
|
|
96
|
+
throw new Error(
|
|
97
|
+
"createClipFromTicks: either ticksToSeconds callback or both bpm and ppqn are required"
|
|
98
|
+
);
|
|
99
|
+
}
|
|
100
|
+
const sampleRate = options.audioBuffer?.sampleRate ?? options.sampleRate ?? options.waveformData?.sample_rate;
|
|
101
|
+
if (sampleRate === void 0) {
|
|
102
|
+
throw new Error("createClipFromTicks: sampleRate is required when audioBuffer is not provided");
|
|
103
|
+
}
|
|
104
|
+
const startSample = Math.round(toSeconds(startTick) * sampleRate);
|
|
105
|
+
return createClip({
|
|
106
|
+
audioBuffer: options.audioBuffer,
|
|
107
|
+
startSample,
|
|
108
|
+
startTick,
|
|
109
|
+
durationSamples: options.durationSamples,
|
|
110
|
+
offsetSamples: options.offsetSamples,
|
|
111
|
+
gain: options.gain,
|
|
112
|
+
name: options.name,
|
|
113
|
+
color: options.color,
|
|
114
|
+
fadeIn: options.fadeIn,
|
|
115
|
+
fadeOut: options.fadeOut,
|
|
116
|
+
waveformData: options.waveformData,
|
|
117
|
+
sampleRate: options.sampleRate,
|
|
118
|
+
sourceDurationSamples: options.sourceDurationSamples,
|
|
119
|
+
midiNotes: options.midiNotes,
|
|
120
|
+
midiChannel: options.midiChannel,
|
|
121
|
+
midiProgram: options.midiProgram
|
|
122
|
+
});
|
|
123
|
+
}
|
|
77
124
|
function createClip(options) {
|
|
78
125
|
const {
|
|
79
126
|
audioBuffer,
|
|
80
127
|
startSample,
|
|
128
|
+
startTick,
|
|
81
129
|
offsetSamples = 0,
|
|
82
130
|
gain = 1,
|
|
83
131
|
name,
|
|
@@ -111,6 +159,7 @@ function createClip(options) {
|
|
|
111
159
|
id: generateId(),
|
|
112
160
|
audioBuffer,
|
|
113
161
|
startSample,
|
|
162
|
+
startTick,
|
|
114
163
|
durationSamples,
|
|
115
164
|
offsetSamples,
|
|
116
165
|
sampleRate,
|
|
@@ -157,6 +206,7 @@ function createClipFromSeconds(options) {
|
|
|
157
206
|
return createClip({
|
|
158
207
|
audioBuffer,
|
|
159
208
|
startSample: Math.round(startTime * sampleRate),
|
|
209
|
+
startTick: options.startTick,
|
|
160
210
|
durationSamples: Math.round(duration * sampleRate),
|
|
161
211
|
offsetSamples: Math.round(offset * sampleRate),
|
|
162
212
|
sampleRate,
|
|
@@ -306,14 +356,6 @@ function samplesToTicks(samples, bpm, sampleRate, ppqn = PPQN) {
|
|
|
306
356
|
function snapToGrid(ticks, gridSizeTicks) {
|
|
307
357
|
return Math.round(ticks / gridSizeTicks) * gridSizeTicks;
|
|
308
358
|
}
|
|
309
|
-
function ticksToBarBeatLabel(ticks, timeSignature, ppqn = PPQN) {
|
|
310
|
-
const barTicks = ticksPerBar(timeSignature, ppqn);
|
|
311
|
-
const beatTicks = ticksPerBeat(timeSignature, ppqn);
|
|
312
|
-
const bar = Math.floor(ticks / barTicks) + 1;
|
|
313
|
-
const beatInBar = Math.floor(ticks % barTicks / beatTicks) + 1;
|
|
314
|
-
if (beatInBar === 1) return `${bar}`;
|
|
315
|
-
return `${bar}.${beatInBar}`;
|
|
316
|
-
}
|
|
317
359
|
|
|
318
360
|
// src/utils/dBUtils.ts
|
|
319
361
|
var DEFAULT_FLOOR = -100;
|
|
@@ -349,11 +391,12 @@ function gainToNormalized(gain, floor = DEFAULT_FLOOR) {
|
|
|
349
391
|
|
|
350
392
|
// src/utils/musicalTicks.ts
|
|
351
393
|
function snapToTicks(snapTo, timeSignature, ppqn = 960) {
|
|
394
|
+
const ts = timeSignature;
|
|
352
395
|
switch (snapTo) {
|
|
353
396
|
case "bar":
|
|
354
|
-
return ticksPerBar(
|
|
397
|
+
return ticksPerBar(ts, ppqn);
|
|
355
398
|
case "beat":
|
|
356
|
-
return ticksPerBeat(
|
|
399
|
+
return ticksPerBeat(ts, ppqn);
|
|
357
400
|
case "1/2":
|
|
358
401
|
return ppqn * 2;
|
|
359
402
|
case "1/4":
|
|
@@ -379,22 +422,20 @@ function snapToTicks(snapTo, timeSignature, ppqn = 960) {
|
|
|
379
422
|
var MIN_PIXELS_PER_UNIT = 8;
|
|
380
423
|
var MIN_PIXELS_PER_LABEL = 60;
|
|
381
424
|
function computeMusicalTicks(params) {
|
|
382
|
-
const {
|
|
383
|
-
|
|
384
|
-
|
|
425
|
+
const { meterEntries, ticksPerPixel, startPixel, endPixel, ppqn = 960 } = params;
|
|
426
|
+
const firstMeter = meterEntries[0] ?? { tick: 0, numerator: 4, denominator: 4 };
|
|
427
|
+
if (ticksPerPixel <= 0 || ppqn <= 0 || firstMeter.denominator <= 0) {
|
|
428
|
+
return { ticks: [], pixelsPerQuarterNote: 0, zoomLevel: "coarse" };
|
|
385
429
|
}
|
|
386
|
-
const
|
|
387
|
-
const tpBar = ticksPerBar(timeSignature, ppqn);
|
|
430
|
+
const pixelsPerQuarterNote = ppqn / ticksPerPixel;
|
|
388
431
|
const tpEighth = ppqn / 2;
|
|
389
432
|
const tpSixteenth = ppqn / 4;
|
|
390
|
-
const pixelsPerBar = tpBar / ticksPerPixel;
|
|
391
|
-
const pixelsPerBeat = tpBeat / ticksPerPixel;
|
|
392
433
|
const pixelsPerEighth = tpEighth / ticksPerPixel;
|
|
393
434
|
const pixelsPerSixteenth = tpSixteenth / ticksPerPixel;
|
|
394
435
|
let zoomLevel;
|
|
395
|
-
if (
|
|
436
|
+
if (pixelsPerQuarterNote * 4 < MIN_PIXELS_PER_UNIT) {
|
|
396
437
|
zoomLevel = "coarse";
|
|
397
|
-
} else if (
|
|
438
|
+
} else if (pixelsPerQuarterNote < MIN_PIXELS_PER_UNIT) {
|
|
398
439
|
zoomLevel = "bar";
|
|
399
440
|
} else if (pixelsPerEighth < MIN_PIXELS_PER_UNIT) {
|
|
400
441
|
zoomLevel = "beat";
|
|
@@ -403,64 +444,255 @@ function computeMusicalTicks(params) {
|
|
|
403
444
|
} else {
|
|
404
445
|
zoomLevel = "sixteenth";
|
|
405
446
|
}
|
|
406
|
-
let
|
|
407
|
-
let
|
|
447
|
+
let coarseQuarterNoteStep;
|
|
448
|
+
let coarseQuarterNotes = 0;
|
|
408
449
|
if (zoomLevel === "coarse") {
|
|
409
|
-
|
|
410
|
-
while (
|
|
411
|
-
|
|
450
|
+
coarseQuarterNotes = 2;
|
|
451
|
+
while (coarseQuarterNotes * ppqn / ticksPerPixel < MIN_PIXELS_PER_UNIT) {
|
|
452
|
+
coarseQuarterNotes *= 2;
|
|
412
453
|
}
|
|
413
|
-
|
|
414
|
-
coarseBarStep = multiplier;
|
|
415
|
-
} else if (zoomLevel === "bar") {
|
|
416
|
-
stepTicks = tpBar;
|
|
417
|
-
} else if (zoomLevel === "beat") {
|
|
418
|
-
stepTicks = tpBeat;
|
|
419
|
-
} else if (zoomLevel === "eighth") {
|
|
420
|
-
stepTicks = tpEighth;
|
|
421
|
-
} else {
|
|
422
|
-
stepTicks = tpSixteenth;
|
|
454
|
+
coarseQuarterNoteStep = coarseQuarterNotes;
|
|
423
455
|
}
|
|
424
456
|
const startTick = startPixel * ticksPerPixel;
|
|
425
457
|
const endTick = endPixel * ticksPerPixel;
|
|
426
|
-
const
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
458
|
+
const segments = [];
|
|
459
|
+
{
|
|
460
|
+
let cumulativeBars = 0;
|
|
461
|
+
for (let i = 0; i < meterEntries.length; i++) {
|
|
462
|
+
const meter = meterEntries[i];
|
|
463
|
+
const segmentStart = meter.tick;
|
|
464
|
+
const segmentEnd = i + 1 < meterEntries.length ? meterEntries[i + 1].tick : Number.MAX_SAFE_INTEGER;
|
|
465
|
+
const ts = [meter.numerator, meter.denominator];
|
|
466
|
+
const tpBar = ticksPerBar(ts, ppqn);
|
|
467
|
+
segments.push({
|
|
468
|
+
segmentStartTick: segmentStart,
|
|
469
|
+
segmentEndTick: segmentEnd,
|
|
470
|
+
meter,
|
|
471
|
+
barOffset: cumulativeBars
|
|
472
|
+
});
|
|
473
|
+
if (segmentEnd !== Number.MAX_SAFE_INTEGER) {
|
|
474
|
+
const segmentLen = segmentEnd - segmentStart;
|
|
475
|
+
cumulativeBars += Math.floor(segmentLen / tpBar);
|
|
476
|
+
}
|
|
432
477
|
}
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
478
|
+
}
|
|
479
|
+
const ticks = [];
|
|
480
|
+
for (const { segmentStartTick, segmentEndTick, meter, barOffset } of segments) {
|
|
481
|
+
const ts = [meter.numerator, meter.denominator];
|
|
482
|
+
const tpBeat = ticksPerBeat(ts, ppqn);
|
|
483
|
+
const tpBar = ticksPerBar(ts, ppqn);
|
|
484
|
+
let stepTicks;
|
|
485
|
+
if (zoomLevel === "coarse") {
|
|
486
|
+
stepTicks = coarseQuarterNotes * ppqn;
|
|
487
|
+
} else if (zoomLevel === "bar") {
|
|
488
|
+
stepTicks = tpBar;
|
|
489
|
+
} else if (zoomLevel === "beat") {
|
|
490
|
+
stepTicks = tpBeat;
|
|
491
|
+
} else if (zoomLevel === "eighth") {
|
|
492
|
+
stepTicks = tpEighth;
|
|
438
493
|
} else {
|
|
439
|
-
|
|
494
|
+
stepTicks = tpSixteenth;
|
|
440
495
|
}
|
|
441
|
-
const
|
|
442
|
-
|
|
443
|
-
if (
|
|
444
|
-
|
|
445
|
-
}
|
|
446
|
-
|
|
496
|
+
const segmentTickStart = Math.max(segmentStartTick, startTick);
|
|
497
|
+
const segmentTickEnd = Math.min(segmentEndTick - 1, endTick);
|
|
498
|
+
if (segmentTickStart > segmentTickEnd) {
|
|
499
|
+
continue;
|
|
500
|
+
}
|
|
501
|
+
const offsetIntoSegment = segmentTickStart - segmentStartTick;
|
|
502
|
+
const firstStepOffset = Math.floor(offsetIntoSegment / stepTicks) * stepTicks;
|
|
503
|
+
const firstStepTick = segmentStartTick + firstStepOffset;
|
|
504
|
+
for (let tick = firstStepTick; tick <= segmentTickEnd && tick < segmentEndTick; tick += stepTicks) {
|
|
505
|
+
const pixel = tick / ticksPerPixel;
|
|
506
|
+
if (pixel < startPixel || pixel > endPixel) {
|
|
507
|
+
continue;
|
|
508
|
+
}
|
|
509
|
+
const tickOffsetInSegment = tick - segmentStartTick;
|
|
510
|
+
let type;
|
|
511
|
+
if (tickOffsetInSegment % tpBar === 0) {
|
|
512
|
+
type = "major";
|
|
513
|
+
} else if (tickOffsetInSegment % tpBeat === 0) {
|
|
514
|
+
type = "minor";
|
|
515
|
+
} else {
|
|
516
|
+
type = "minorMinor";
|
|
517
|
+
}
|
|
518
|
+
const barIndexInSegment = Math.floor(tickOffsetInSegment / tpBar);
|
|
519
|
+
const barIndex = barOffset + barIndexInSegment;
|
|
520
|
+
let label;
|
|
521
|
+
if (type === "major") {
|
|
522
|
+
label = `${barIndex + 1}`;
|
|
523
|
+
} else if (type === "minor" && tpBeat / ticksPerPixel >= MIN_PIXELS_PER_LABEL) {
|
|
524
|
+
const beatInBar = Math.floor(tickOffsetInSegment % tpBar / tpBeat) + 1;
|
|
525
|
+
label = `${barIndex + 1}.${beatInBar}`;
|
|
526
|
+
}
|
|
527
|
+
ticks.push({ pixel, type, barIndex, ...label !== void 0 ? { label } : {} });
|
|
447
528
|
}
|
|
448
|
-
ticks.push({ pixel, type, barIndex, ...label !== void 0 ? { label } : {} });
|
|
449
529
|
}
|
|
530
|
+
ticks.sort((a, b) => a.pixel - b.pixel);
|
|
450
531
|
const result = {
|
|
451
532
|
ticks,
|
|
452
|
-
|
|
453
|
-
pixelsPerBeat,
|
|
533
|
+
pixelsPerQuarterNote,
|
|
454
534
|
zoomLevel,
|
|
455
|
-
...
|
|
535
|
+
...coarseQuarterNoteStep !== void 0 ? { coarseQuarterNoteStep } : {}
|
|
456
536
|
};
|
|
457
537
|
return result;
|
|
458
538
|
}
|
|
459
|
-
function snapTickToGrid(tick, snapTo,
|
|
539
|
+
function snapTickToGrid(tick, snapTo, meterEntries, ppqn = 960) {
|
|
460
540
|
if (snapTo === "off") return tick;
|
|
461
|
-
|
|
541
|
+
let meter = meterEntries[0] ?? { tick: 0, numerator: 4, denominator: 4 };
|
|
542
|
+
for (const entry of meterEntries) {
|
|
543
|
+
if (entry.tick <= tick) {
|
|
544
|
+
meter = entry;
|
|
545
|
+
} else {
|
|
546
|
+
break;
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
const ts = [meter.numerator, meter.denominator];
|
|
550
|
+
const gridSize = snapToTicks(snapTo, ts, ppqn);
|
|
462
551
|
if (gridSize <= 0) return tick;
|
|
463
|
-
|
|
552
|
+
const offset = tick - meter.tick;
|
|
553
|
+
return meter.tick + Math.round(offset / gridSize) * gridSize;
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
// src/utils/meterDetection.ts
|
|
557
|
+
function detectMeterChanges(beats, firstBeatTick, ppqn) {
|
|
558
|
+
const DEFAULT_NUMERATOR = 4;
|
|
559
|
+
const DENOMINATOR = 4;
|
|
560
|
+
const defaultResult = [
|
|
561
|
+
{ tick: 0, numerator: DEFAULT_NUMERATOR, denominator: DENOMINATOR }
|
|
562
|
+
];
|
|
563
|
+
if (beats.length === 0) {
|
|
564
|
+
return defaultResult;
|
|
565
|
+
}
|
|
566
|
+
const firstDownbeatIndex = beats.findIndex((b) => b.beat === 1);
|
|
567
|
+
if (firstDownbeatIndex === -1) {
|
|
568
|
+
return defaultResult;
|
|
569
|
+
}
|
|
570
|
+
const bars = [];
|
|
571
|
+
let barStartBeatIndex = firstDownbeatIndex;
|
|
572
|
+
for (let i = firstDownbeatIndex + 1; i < beats.length; i++) {
|
|
573
|
+
if (beats[i].beat === 1) {
|
|
574
|
+
bars.push({ beatIndex: barStartBeatIndex, count: i - barStartBeatIndex });
|
|
575
|
+
barStartBeatIndex = i;
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
if (bars.length === 0) {
|
|
579
|
+
return defaultResult;
|
|
580
|
+
}
|
|
581
|
+
const rawEntries = [];
|
|
582
|
+
let prevNumerator = -1;
|
|
583
|
+
for (const bar of bars) {
|
|
584
|
+
const numerator = bar.count;
|
|
585
|
+
if (numerator !== prevNumerator) {
|
|
586
|
+
const tick = firstBeatTick + bar.beatIndex * ppqn;
|
|
587
|
+
rawEntries.push({ tick, numerator, denominator: DENOMINATOR });
|
|
588
|
+
prevNumerator = numerator;
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
if (rawEntries.length === 0) {
|
|
592
|
+
return defaultResult;
|
|
593
|
+
}
|
|
594
|
+
if (rawEntries[0].tick === 0) {
|
|
595
|
+
return rawEntries;
|
|
596
|
+
}
|
|
597
|
+
const tick0Entry = {
|
|
598
|
+
tick: 0,
|
|
599
|
+
numerator: rawEntries[0].numerator,
|
|
600
|
+
denominator: DENOMINATOR
|
|
601
|
+
};
|
|
602
|
+
const rest = rawEntries[0].numerator === tick0Entry.numerator ? rawEntries.slice(1) : rawEntries;
|
|
603
|
+
return [tick0Entry, ...rest];
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
// src/utils/peaksGenerator.ts
|
|
607
|
+
function generatePeaks(samples, samplesPerPixel, bits = 16) {
|
|
608
|
+
const numPeaks = Math.ceil(samples.length / samplesPerPixel);
|
|
609
|
+
const peakArray = bits === 8 ? new Int8Array(numPeaks * 2) : new Int16Array(numPeaks * 2);
|
|
610
|
+
const maxValue = 2 ** (bits - 1);
|
|
611
|
+
for (let i = 0; i < numPeaks; i++) {
|
|
612
|
+
const start = i * samplesPerPixel;
|
|
613
|
+
const end = Math.min(start + samplesPerPixel, samples.length);
|
|
614
|
+
let min = 0;
|
|
615
|
+
let max = 0;
|
|
616
|
+
for (let j = start; j < end; j++) {
|
|
617
|
+
const value = samples[j];
|
|
618
|
+
if (value < min) min = value;
|
|
619
|
+
if (value > max) max = value;
|
|
620
|
+
}
|
|
621
|
+
peakArray[i * 2] = Math.max(-maxValue, Math.floor(min * maxValue));
|
|
622
|
+
peakArray[i * 2 + 1] = Math.min(maxValue - 1, Math.floor(max * maxValue));
|
|
623
|
+
}
|
|
624
|
+
return peakArray;
|
|
625
|
+
}
|
|
626
|
+
function appendPeaks(existingPeaks, newSamples, samplesPerPixel, totalSamplesProcessed, bits = 16) {
|
|
627
|
+
const maxValue = 2 ** (bits - 1);
|
|
628
|
+
const remainder = totalSamplesProcessed % samplesPerPixel;
|
|
629
|
+
let offset = 0;
|
|
630
|
+
if (remainder > 0 && existingPeaks.length > 0) {
|
|
631
|
+
const samplesToComplete = samplesPerPixel - remainder;
|
|
632
|
+
const endIndex = Math.min(samplesToComplete, newSamples.length);
|
|
633
|
+
let min = existingPeaks[existingPeaks.length - 2] / maxValue;
|
|
634
|
+
let max = existingPeaks[existingPeaks.length - 1] / maxValue;
|
|
635
|
+
for (let i = 0; i < endIndex; i++) {
|
|
636
|
+
const value = newSamples[i];
|
|
637
|
+
if (value < min) min = value;
|
|
638
|
+
if (value > max) max = value;
|
|
639
|
+
}
|
|
640
|
+
const updated = new (bits === 8 ? Int8Array : Int16Array)(existingPeaks.length);
|
|
641
|
+
updated.set(existingPeaks);
|
|
642
|
+
updated[existingPeaks.length - 2] = Math.max(-maxValue, Math.floor(min * maxValue));
|
|
643
|
+
updated[existingPeaks.length - 1] = Math.min(maxValue - 1, Math.floor(max * maxValue));
|
|
644
|
+
offset = endIndex;
|
|
645
|
+
const newPeaks2 = generatePeaks(newSamples.slice(offset), samplesPerPixel, bits);
|
|
646
|
+
const result2 = new (bits === 8 ? Int8Array : Int16Array)(updated.length + newPeaks2.length);
|
|
647
|
+
result2.set(updated);
|
|
648
|
+
result2.set(newPeaks2, updated.length);
|
|
649
|
+
return result2;
|
|
650
|
+
}
|
|
651
|
+
const newPeaks = generatePeaks(newSamples.slice(offset), samplesPerPixel, bits);
|
|
652
|
+
const result = new (bits === 8 ? Int8Array : Int16Array)(existingPeaks.length + newPeaks.length);
|
|
653
|
+
result.set(existingPeaks);
|
|
654
|
+
result.set(newPeaks, existingPeaks.length);
|
|
655
|
+
return result;
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
// src/utils/audioBufferUtils.ts
|
|
659
|
+
function concatenateAudioData(chunks) {
|
|
660
|
+
const totalLength = chunks.reduce((sum, chunk) => sum + chunk.length, 0);
|
|
661
|
+
const result = new Float32Array(totalLength);
|
|
662
|
+
let offset = 0;
|
|
663
|
+
for (const chunk of chunks) {
|
|
664
|
+
result.set(chunk, offset);
|
|
665
|
+
offset += chunk.length;
|
|
666
|
+
}
|
|
667
|
+
return result;
|
|
668
|
+
}
|
|
669
|
+
function createAudioBuffer(audioContext, channelData, sampleRate, channelCount = 1) {
|
|
670
|
+
const channels = channelData instanceof Float32Array ? [channelData] : channelData;
|
|
671
|
+
const length = channels[0]?.length ?? 0;
|
|
672
|
+
const buffer = audioContext.createBuffer(channelCount, length, sampleRate);
|
|
673
|
+
for (let ch = 0; ch < Math.min(channelCount, channels.length); ch++) {
|
|
674
|
+
buffer.copyToChannel(new Float32Array(channels[ch]), ch);
|
|
675
|
+
}
|
|
676
|
+
return buffer;
|
|
677
|
+
}
|
|
678
|
+
function appendToAudioBuffer(audioContext, existingBuffer, newSamples, sampleRate) {
|
|
679
|
+
if (!existingBuffer) {
|
|
680
|
+
return createAudioBuffer(audioContext, [newSamples], sampleRate);
|
|
681
|
+
}
|
|
682
|
+
const existingData = existingBuffer.getChannelData(0);
|
|
683
|
+
const combined = concatenateAudioData([existingData, newSamples]);
|
|
684
|
+
return createAudioBuffer(audioContext, [combined], sampleRate);
|
|
685
|
+
}
|
|
686
|
+
function calculateDuration(sampleCount, sampleRate) {
|
|
687
|
+
return sampleCount / sampleRate;
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
// src/utils/latency.ts
|
|
691
|
+
function audibleLatencySamples(outputLatency, lookAhead, sampleRate) {
|
|
692
|
+
const total = outputLatency + lookAhead;
|
|
693
|
+
if (!Number.isFinite(total) || !Number.isFinite(sampleRate)) return 0;
|
|
694
|
+
if (total <= 0 || sampleRate <= 0) return 0;
|
|
695
|
+
return Math.floor(total * sampleRate);
|
|
464
696
|
}
|
|
465
697
|
|
|
466
698
|
// src/clipTimeHelpers.ts
|
|
@@ -621,8 +853,12 @@ var getShortcutLabel = (shortcut) => {
|
|
|
621
853
|
MAX_CANVAS_WIDTH,
|
|
622
854
|
MIN_PIXELS_PER_UNIT,
|
|
623
855
|
PPQN,
|
|
856
|
+
appendPeaks,
|
|
857
|
+
appendToAudioBuffer,
|
|
624
858
|
applyFadeIn,
|
|
625
859
|
applyFadeOut,
|
|
860
|
+
audibleLatencySamples,
|
|
861
|
+
calculateDuration,
|
|
626
862
|
clipDurationTime,
|
|
627
863
|
clipEndTime,
|
|
628
864
|
clipOffsetTime,
|
|
@@ -630,16 +866,21 @@ var getShortcutLabel = (shortcut) => {
|
|
|
630
866
|
clipStartTime,
|
|
631
867
|
clipsOverlap,
|
|
632
868
|
computeMusicalTicks,
|
|
869
|
+
concatenateAudioData,
|
|
870
|
+
createAudioBuffer,
|
|
633
871
|
createClip,
|
|
634
872
|
createClipFromSeconds,
|
|
873
|
+
createClipFromTicks,
|
|
635
874
|
createTimeline,
|
|
636
875
|
createTrack,
|
|
637
876
|
dBToNormalized,
|
|
877
|
+
detectMeterChanges,
|
|
638
878
|
exponentialCurve,
|
|
639
879
|
findGaps,
|
|
640
880
|
gainToDb,
|
|
641
881
|
gainToNormalized,
|
|
642
882
|
generateCurve,
|
|
883
|
+
generatePeaks,
|
|
643
884
|
getClipsAtSample,
|
|
644
885
|
getClipsInRange,
|
|
645
886
|
getShortcutLabel,
|
|
@@ -661,7 +902,6 @@ var getShortcutLabel = (shortcut) => {
|
|
|
661
902
|
sortClipsByTime,
|
|
662
903
|
ticksPerBar,
|
|
663
904
|
ticksPerBeat,
|
|
664
|
-
ticksToBarBeatLabel,
|
|
665
905
|
ticksToSamples,
|
|
666
906
|
trackChannelCount
|
|
667
907
|
});
|