@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.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;
|
|
@@ -277,11 +311,12 @@ function gainToNormalized(gain, floor = DEFAULT_FLOOR) {
|
|
|
277
311
|
|
|
278
312
|
// src/utils/musicalTicks.ts
|
|
279
313
|
function snapToTicks(snapTo, timeSignature, ppqn = 960) {
|
|
314
|
+
const ts = timeSignature;
|
|
280
315
|
switch (snapTo) {
|
|
281
316
|
case "bar":
|
|
282
|
-
return ticksPerBar(
|
|
317
|
+
return ticksPerBar(ts, ppqn);
|
|
283
318
|
case "beat":
|
|
284
|
-
return ticksPerBeat(
|
|
319
|
+
return ticksPerBeat(ts, ppqn);
|
|
285
320
|
case "1/2":
|
|
286
321
|
return ppqn * 2;
|
|
287
322
|
case "1/4":
|
|
@@ -307,22 +342,20 @@ function snapToTicks(snapTo, timeSignature, ppqn = 960) {
|
|
|
307
342
|
var MIN_PIXELS_PER_UNIT = 8;
|
|
308
343
|
var MIN_PIXELS_PER_LABEL = 60;
|
|
309
344
|
function computeMusicalTicks(params) {
|
|
310
|
-
const {
|
|
311
|
-
|
|
312
|
-
|
|
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" };
|
|
313
349
|
}
|
|
314
|
-
const
|
|
315
|
-
const tpBar = ticksPerBar(timeSignature, ppqn);
|
|
350
|
+
const pixelsPerQuarterNote = ppqn / ticksPerPixel;
|
|
316
351
|
const tpEighth = ppqn / 2;
|
|
317
352
|
const tpSixteenth = ppqn / 4;
|
|
318
|
-
const pixelsPerBar = tpBar / ticksPerPixel;
|
|
319
|
-
const pixelsPerBeat = tpBeat / ticksPerPixel;
|
|
320
353
|
const pixelsPerEighth = tpEighth / ticksPerPixel;
|
|
321
354
|
const pixelsPerSixteenth = tpSixteenth / ticksPerPixel;
|
|
322
355
|
let zoomLevel;
|
|
323
|
-
if (
|
|
356
|
+
if (pixelsPerQuarterNote * 4 < MIN_PIXELS_PER_UNIT) {
|
|
324
357
|
zoomLevel = "coarse";
|
|
325
|
-
} else if (
|
|
358
|
+
} else if (pixelsPerQuarterNote < MIN_PIXELS_PER_UNIT) {
|
|
326
359
|
zoomLevel = "bar";
|
|
327
360
|
} else if (pixelsPerEighth < MIN_PIXELS_PER_UNIT) {
|
|
328
361
|
zoomLevel = "beat";
|
|
@@ -331,64 +364,247 @@ function computeMusicalTicks(params) {
|
|
|
331
364
|
} else {
|
|
332
365
|
zoomLevel = "sixteenth";
|
|
333
366
|
}
|
|
334
|
-
let
|
|
335
|
-
let
|
|
367
|
+
let coarseQuarterNoteStep;
|
|
368
|
+
let coarseQuarterNotes = 0;
|
|
336
369
|
if (zoomLevel === "coarse") {
|
|
337
|
-
|
|
338
|
-
while (
|
|
339
|
-
|
|
370
|
+
coarseQuarterNotes = 2;
|
|
371
|
+
while (coarseQuarterNotes * ppqn / ticksPerPixel < MIN_PIXELS_PER_UNIT) {
|
|
372
|
+
coarseQuarterNotes *= 2;
|
|
340
373
|
}
|
|
341
|
-
|
|
342
|
-
coarseBarStep = multiplier;
|
|
343
|
-
} else if (zoomLevel === "bar") {
|
|
344
|
-
stepTicks = tpBar;
|
|
345
|
-
} else if (zoomLevel === "beat") {
|
|
346
|
-
stepTicks = tpBeat;
|
|
347
|
-
} else if (zoomLevel === "eighth") {
|
|
348
|
-
stepTicks = tpEighth;
|
|
349
|
-
} else {
|
|
350
|
-
stepTicks = tpSixteenth;
|
|
374
|
+
coarseQuarterNoteStep = coarseQuarterNotes;
|
|
351
375
|
}
|
|
352
376
|
const startTick = startPixel * ticksPerPixel;
|
|
353
377
|
const endTick = endPixel * ticksPerPixel;
|
|
354
|
-
const
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
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
|
+
}
|
|
360
397
|
}
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
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;
|
|
366
413
|
} else {
|
|
367
|
-
|
|
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;
|
|
368
420
|
}
|
|
369
|
-
const
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
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 } : {} });
|
|
375
448
|
}
|
|
376
|
-
ticks.push({ pixel, type, barIndex, ...label !== void 0 ? { label } : {} });
|
|
377
449
|
}
|
|
450
|
+
ticks.sort((a, b) => a.pixel - b.pixel);
|
|
378
451
|
const result = {
|
|
379
452
|
ticks,
|
|
380
|
-
|
|
381
|
-
pixelsPerBeat,
|
|
453
|
+
pixelsPerQuarterNote,
|
|
382
454
|
zoomLevel,
|
|
383
|
-
...
|
|
455
|
+
...coarseQuarterNoteStep !== void 0 ? { coarseQuarterNoteStep } : {}
|
|
384
456
|
};
|
|
385
457
|
return result;
|
|
386
458
|
}
|
|
387
|
-
function snapTickToGrid(tick, snapTo,
|
|
459
|
+
function snapTickToGrid(tick, snapTo, meterEntries, ppqn = 960) {
|
|
388
460
|
if (snapTo === "off") return tick;
|
|
389
|
-
|
|
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);
|
|
390
471
|
if (gridSize <= 0) return tick;
|
|
391
|
-
|
|
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;
|
|
392
608
|
}
|
|
393
609
|
|
|
394
610
|
// src/clipTimeHelpers.ts
|
|
@@ -548,8 +764,11 @@ export {
|
|
|
548
764
|
MAX_CANVAS_WIDTH,
|
|
549
765
|
MIN_PIXELS_PER_UNIT,
|
|
550
766
|
PPQN,
|
|
767
|
+
appendPeaks,
|
|
768
|
+
appendToAudioBuffer,
|
|
551
769
|
applyFadeIn,
|
|
552
770
|
applyFadeOut,
|
|
771
|
+
calculateDuration,
|
|
553
772
|
clipDurationTime,
|
|
554
773
|
clipEndTime,
|
|
555
774
|
clipOffsetTime,
|
|
@@ -557,16 +776,21 @@ export {
|
|
|
557
776
|
clipStartTime,
|
|
558
777
|
clipsOverlap,
|
|
559
778
|
computeMusicalTicks,
|
|
779
|
+
concatenateAudioData,
|
|
780
|
+
createAudioBuffer,
|
|
560
781
|
createClip,
|
|
561
782
|
createClipFromSeconds,
|
|
783
|
+
createClipFromTicks,
|
|
562
784
|
createTimeline,
|
|
563
785
|
createTrack,
|
|
564
786
|
dBToNormalized,
|
|
787
|
+
detectMeterChanges,
|
|
565
788
|
exponentialCurve,
|
|
566
789
|
findGaps,
|
|
567
790
|
gainToDb,
|
|
568
791
|
gainToNormalized,
|
|
569
792
|
generateCurve,
|
|
793
|
+
generatePeaks,
|
|
570
794
|
getClipsAtSample,
|
|
571
795
|
getClipsInRange,
|
|
572
796
|
getShortcutLabel,
|
|
@@ -588,7 +812,6 @@ export {
|
|
|
588
812
|
sortClipsByTime,
|
|
589
813
|
ticksPerBar,
|
|
590
814
|
ticksPerBeat,
|
|
591
|
-
ticksToBarBeatLabel,
|
|
592
815
|
ticksToSamples,
|
|
593
816
|
trackChannelCount
|
|
594
817
|
};
|