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