@remotion/media 4.0.519 → 4.0.521

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.
@@ -0,0 +1,23 @@
1
+ import type { AudioBufferSlice } from '../make-iterator-with-priming';
2
+ type PlanarAudio = Float32Array[];
3
+ export declare class StreamingPitchShifter {
4
+ private readonly numberOfChannels;
5
+ private readonly stretcher;
6
+ private readonly resampler;
7
+ private readonly outputQueue;
8
+ private totalInputFrames;
9
+ private totalOutputFrames;
10
+ constructor({ numberOfChannels, sampleRate, toneFrequency }: {
11
+ numberOfChannels: number;
12
+ sampleRate: number;
13
+ toneFrequency: number;
14
+ });
15
+ append(audio: PlanarAudio): PlanarAudio;
16
+ private takeAvailableOutput;
17
+ finalize(): Float32Array<ArrayBuffer>[];
18
+ }
19
+ export declare function pitchShiftAudioIterator({ iterator, toneFrequency }: {
20
+ iterator: AsyncGenerator<AudioBufferSlice, void, unknown>;
21
+ toneFrequency: number;
22
+ }): AsyncGenerator<AudioBufferSlice, void, unknown>;
23
+ export {};
@@ -16,7 +16,7 @@ export declare const anchorToContinuousTime: ({ anchor, unloopedTimeInSeconds, p
16
16
  unloopedTimeInSeconds: number;
17
17
  playbackRate: number;
18
18
  }) => number;
19
- export declare const audioIteratorManager: ({ audioTrack, delayPlaybackHandleIfNotPremounting, sharedAudioContext, getSequenceEndTimestamp, getSequenceDurationInSeconds, getMediaEndTimestamp, getStartTime, initialMuted, initialVolume, drawDebugOverlay, }: {
19
+ export declare const audioIteratorManager: ({ audioTrack, delayPlaybackHandleIfNotPremounting, sharedAudioContext, getSequenceEndTimestamp, getSequenceDurationInSeconds, getMediaEndTimestamp, getStartTime, initialMuted, initialVolume, toneFrequency, drawDebugOverlay, }: {
20
20
  audioTrack: InputAudioTrack;
21
21
  delayPlaybackHandleIfNotPremounting: () => DelayPlaybackIfNotPremounting;
22
22
  sharedAudioContext: SharedAudioContextForMediaPlayer;
@@ -26,6 +26,7 @@ export declare const audioIteratorManager: ({ audioTrack, delayPlaybackHandleIfN
26
26
  getStartTime: () => number;
27
27
  initialMuted: boolean;
28
28
  initialVolume: number;
29
+ toneFrequency: number;
29
30
  drawDebugOverlay: () => void;
30
31
  }) => {
31
32
  startAudioIterator: ({ nonce, playbackRate, startFromSecond, unloopedStartFromSecond, scheduleAudioNode, getTargetTime, logLevel, loop, unscheduleAudioNode, getAudioContextCurrentTimeMockedInTest, }: {
@@ -74,6 +75,7 @@ export declare const audioIteratorManager: ({ audioTrack, delayPlaybackHandleIfN
74
75
  getTotalAudioScheduledInSeconds: () => number;
75
76
  setMuted: (newMuted: boolean) => void;
76
77
  setVolume: (volume: number) => void;
78
+ setToneFrequency: (newToneFrequency: number) => void;
77
79
  scheduleAudioChunk: ({ buffer, mediaTimestamp, originalUnloopedMediaTimestamp, sourceOffsetInSeconds, sourceDurationInSeconds, playbackRate, scheduleAudioNode, logLevel, }: {
78
80
  buffer: AudioBuffer;
79
81
  mediaTimestamp: number;
@@ -53,6 +53,7 @@ export declare const drawPreviewOverlay: ({ context, audioTime, audioContextStat
53
53
  getTotalAudioScheduledInSeconds: () => number;
54
54
  setMuted: (newMuted: boolean) => void;
55
55
  setVolume: (volume: number) => void;
56
+ setToneFrequency: (newToneFrequency: number) => void;
56
57
  scheduleAudioChunk: ({ buffer, mediaTimestamp, originalUnloopedMediaTimestamp, sourceOffsetInSeconds, sourceDurationInSeconds, playbackRate, scheduleAudioNode, logLevel, }: {
57
58
  buffer: AudioBuffer;
58
59
  mediaTimestamp: number;
@@ -75,6 +75,19 @@ var getLoopDisplay = ({
75
75
  };
76
76
  };
77
77
 
78
+ // src/validate-tone-frequency.ts
79
+ var validateToneFrequency = ({
80
+ toneFrequency,
81
+ component
82
+ }) => {
83
+ if (toneFrequency === undefined) {
84
+ return;
85
+ }
86
+ if (typeof toneFrequency !== "number" || !Number.isFinite(toneFrequency) || toneFrequency < 0.01 || toneFrequency > 2) {
87
+ throw new TypeError(`The \`toneFrequency\` prop of <${component}> must be a finite number between 0.01 and 2, but got ${String(toneFrequency)}.`);
88
+ }
89
+ };
90
+
78
91
  // src/audio/audio-for-preview.tsx
79
92
  import { useContext as useContext2, useEffect, useMemo, useRef, useState } from "react";
80
93
  import {
@@ -248,6 +261,455 @@ var getTrimStartForAudioNode = ({
248
261
  return sourceStartOffsetInSeconds + offsetBecauseOfTrim + offsetBecauseOfTooLate;
249
262
  };
250
263
 
264
+ // src/audio/pitch-shift.ts
265
+ var REFERENCE_SAMPLE_RATE = 48000;
266
+ var REFERENCE_HOP_SIZE = 512;
267
+ var makePlanarAudio = (numberOfChannels, length) => {
268
+ return new Array(numberOfChannels).fill(null).map(() => new Float32Array(length));
269
+ };
270
+ var ensurePlanarCapacity = ({
271
+ buffers,
272
+ requiredLength
273
+ }) => {
274
+ if (buffers[0].length >= requiredLength) {
275
+ return buffers;
276
+ }
277
+ let newLength = buffers[0].length;
278
+ while (newLength < requiredLength) {
279
+ newLength *= 2;
280
+ }
281
+ return buffers.map((buffer) => {
282
+ const expanded = new Float32Array(newLength);
283
+ expanded.set(buffer);
284
+ return expanded;
285
+ });
286
+ };
287
+
288
+ class PlanarAudioQueue {
289
+ chunks = [];
290
+ length = 0;
291
+ push(audio) {
292
+ if (audio[0].length === 0) {
293
+ return;
294
+ }
295
+ this.chunks.push(audio);
296
+ this.length += audio[0].length;
297
+ }
298
+ take(numberOfFrames, numberOfChannels) {
299
+ const framesToTake = Math.min(numberOfFrames, this.length);
300
+ const result = makePlanarAudio(numberOfChannels, framesToTake);
301
+ let written = 0;
302
+ while (written < framesToTake) {
303
+ const first = this.chunks[0];
304
+ const available = first[0].length;
305
+ const count = Math.min(available, framesToTake - written);
306
+ for (let channel = 0;channel < numberOfChannels; channel++) {
307
+ result[channel].set(first[channel].subarray(0, count), written);
308
+ }
309
+ if (count === available) {
310
+ this.chunks.shift();
311
+ } else {
312
+ this.chunks[0] = first.map((channel) => channel.subarray(count));
313
+ }
314
+ written += count;
315
+ this.length -= count;
316
+ }
317
+ return result;
318
+ }
319
+ getLength() {
320
+ return this.length;
321
+ }
322
+ }
323
+
324
+ class StreamingTimeStretcher {
325
+ numberOfChannels;
326
+ factor;
327
+ hopSize;
328
+ windowSize;
329
+ searchRadius;
330
+ analysisHop;
331
+ input;
332
+ inputLength = 0;
333
+ output;
334
+ outputLength = 0;
335
+ analysisPosition = 0;
336
+ synthesisPosition = 0;
337
+ initialized = false;
338
+ finalized = false;
339
+ totalInputFrames = 0;
340
+ totalOutputFrames = 0;
341
+ constructor({
342
+ numberOfChannels,
343
+ sampleRate,
344
+ factor
345
+ }) {
346
+ this.numberOfChannels = numberOfChannels;
347
+ this.factor = factor;
348
+ this.hopSize = Math.max(32, Math.round(REFERENCE_HOP_SIZE * sampleRate / REFERENCE_SAMPLE_RATE));
349
+ this.windowSize = this.hopSize * 2;
350
+ this.searchRadius = this.hopSize;
351
+ this.analysisHop = this.hopSize / factor;
352
+ this.input = makePlanarAudio(numberOfChannels, 65536);
353
+ this.output = makePlanarAudio(numberOfChannels, 65536);
354
+ }
355
+ append(audio) {
356
+ if (this.finalized) {
357
+ throw new Error("Cannot append audio after the time stretcher was finalized.");
358
+ }
359
+ const { length } = audio[0];
360
+ this.input = ensurePlanarCapacity({
361
+ buffers: this.input,
362
+ requiredLength: this.inputLength + length
363
+ });
364
+ for (let channel = 0;channel < this.numberOfChannels; channel++) {
365
+ this.input[channel].set(audio[channel], this.inputLength);
366
+ }
367
+ this.inputLength += length;
368
+ this.totalInputFrames += length;
369
+ this.process();
370
+ return this.drainFinalizedOutput();
371
+ }
372
+ findBestAnalysisPosition({
373
+ expectedPosition,
374
+ nextSynthesisPosition
375
+ }) {
376
+ const minimum = Math.max(0, Math.floor(expectedPosition - this.searchRadius));
377
+ const maximum = Math.min(this.inputLength - this.windowSize, Math.ceil(expectedPosition + this.searchRadius));
378
+ let bestPosition = minimum;
379
+ let bestCorrelation = -Infinity;
380
+ for (let candidate = minimum;candidate <= maximum; candidate += 4) {
381
+ let dotProduct = 0;
382
+ let previousEnergy = 0;
383
+ let candidateEnergy = 0;
384
+ for (let channel = 0;channel < this.numberOfChannels; channel++) {
385
+ const previous = this.output[channel];
386
+ const incoming = this.input[channel];
387
+ for (let frame = 0;frame < this.hopSize; frame += 2) {
388
+ const previousValue = previous[nextSynthesisPosition + frame];
389
+ const candidateValue = incoming[candidate + frame];
390
+ dotProduct += previousValue * candidateValue;
391
+ previousEnergy += previousValue * previousValue;
392
+ candidateEnergy += candidateValue * candidateValue;
393
+ }
394
+ }
395
+ const correlation = dotProduct / (Math.sqrt(previousEnergy * candidateEnergy) || Number.EPSILON);
396
+ if (correlation > bestCorrelation) {
397
+ bestCorrelation = correlation;
398
+ bestPosition = candidate;
399
+ }
400
+ }
401
+ const fineMinimum = Math.max(minimum, bestPosition - 4);
402
+ const fineMaximum = Math.min(maximum, bestPosition + 4);
403
+ for (let candidate = fineMinimum;candidate <= fineMaximum; candidate++) {
404
+ let dotProduct = 0;
405
+ let previousEnergy = 0;
406
+ let candidateEnergy = 0;
407
+ for (let channel = 0;channel < this.numberOfChannels; channel++) {
408
+ const previous = this.output[channel];
409
+ const incoming = this.input[channel];
410
+ for (let frame = 0;frame < this.hopSize; frame++) {
411
+ const previousValue = previous[nextSynthesisPosition + frame];
412
+ const candidateValue = incoming[candidate + frame];
413
+ dotProduct += previousValue * candidateValue;
414
+ previousEnergy += previousValue * previousValue;
415
+ candidateEnergy += candidateValue * candidateValue;
416
+ }
417
+ }
418
+ const correlation = dotProduct / (Math.sqrt(previousEnergy * candidateEnergy) || Number.EPSILON);
419
+ if (correlation > bestCorrelation) {
420
+ bestCorrelation = correlation;
421
+ bestPosition = candidate;
422
+ }
423
+ }
424
+ return bestPosition;
425
+ }
426
+ process() {
427
+ if (!this.initialized) {
428
+ if (this.inputLength < this.windowSize + this.searchRadius) {
429
+ return;
430
+ }
431
+ for (let channel = 0;channel < this.numberOfChannels; channel++) {
432
+ this.output[channel].set(this.input[channel].subarray(0, this.windowSize));
433
+ }
434
+ this.outputLength = this.windowSize;
435
+ this.initialized = true;
436
+ }
437
+ while (true) {
438
+ const expectedPosition = this.analysisPosition + this.analysisHop;
439
+ if (expectedPosition + this.searchRadius + this.windowSize > this.inputLength) {
440
+ break;
441
+ }
442
+ const nextSynthesisPosition = this.synthesisPosition + this.hopSize;
443
+ this.output = ensurePlanarCapacity({
444
+ buffers: this.output,
445
+ requiredLength: nextSynthesisPosition + this.windowSize
446
+ });
447
+ const bestPosition = this.findBestAnalysisPosition({
448
+ expectedPosition,
449
+ nextSynthesisPosition
450
+ });
451
+ for (let channel = 0;channel < this.numberOfChannels; channel++) {
452
+ for (let frame = 0;frame < this.hopSize; frame++) {
453
+ const fadeIn = 0.5 - 0.5 * Math.cos(Math.PI * (frame + 1) / (this.hopSize + 1));
454
+ const outputIndex = nextSynthesisPosition + frame;
455
+ this.output[channel][outputIndex] = this.output[channel][outputIndex] * (1 - fadeIn) + this.input[channel][bestPosition + frame] * fadeIn;
456
+ }
457
+ this.output[channel].set(this.input[channel].subarray(bestPosition + this.hopSize, bestPosition + this.windowSize), nextSynthesisPosition + this.hopSize);
458
+ }
459
+ this.analysisPosition = expectedPosition;
460
+ this.synthesisPosition = nextSynthesisPosition;
461
+ this.outputLength = nextSynthesisPosition + this.windowSize;
462
+ }
463
+ }
464
+ drainFinalizedOutput() {
465
+ if (!this.initialized) {
466
+ return makePlanarAudio(this.numberOfChannels, 0);
467
+ }
468
+ const finalizedLength = Math.max(0, this.synthesisPosition + this.hopSize);
469
+ const result = this.output.map((channel) => channel.slice(0, finalizedLength));
470
+ this.totalOutputFrames += finalizedLength;
471
+ for (let channel = 0;channel < this.numberOfChannels; channel++) {
472
+ this.output[channel].copyWithin(0, finalizedLength, this.outputLength);
473
+ }
474
+ this.outputLength -= finalizedLength;
475
+ this.synthesisPosition -= finalizedLength;
476
+ const inputFramesToDiscard = Math.max(0, Math.floor(this.analysisPosition) - this.searchRadius);
477
+ for (let channel = 0;channel < this.numberOfChannels; channel++) {
478
+ this.input[channel].copyWithin(0, inputFramesToDiscard, this.inputLength);
479
+ }
480
+ this.inputLength -= inputFramesToDiscard;
481
+ this.analysisPosition -= inputFramesToDiscard;
482
+ return result;
483
+ }
484
+ finalize() {
485
+ if (this.finalized) {
486
+ throw new Error("The time stretcher has already been finalized.");
487
+ }
488
+ this.finalized = true;
489
+ const targetLength = Math.round(this.totalInputFrames * this.factor);
490
+ const padding = makePlanarAudio(this.numberOfChannels, this.windowSize + this.searchRadius * 2);
491
+ this.input = ensurePlanarCapacity({
492
+ buffers: this.input,
493
+ requiredLength: this.inputLength + padding[0].length
494
+ });
495
+ for (let channel = 0;channel < this.numberOfChannels; channel++) {
496
+ this.input[channel].set(padding[channel], this.inputLength);
497
+ }
498
+ this.inputLength += padding[0].length;
499
+ this.process();
500
+ const finalized = this.drainFinalizedOutput();
501
+ const remaining = Math.max(0, targetLength - this.totalOutputFrames + finalized[0].length);
502
+ if (finalized[0].length >= remaining) {
503
+ return finalized.map((channel) => channel.slice(0, remaining));
504
+ }
505
+ const result = makePlanarAudio(this.numberOfChannels, remaining);
506
+ for (let channel = 0;channel < this.numberOfChannels; channel++) {
507
+ result[channel].set(finalized[channel]);
508
+ }
509
+ return result;
510
+ }
511
+ }
512
+
513
+ class StreamingLinearResampler {
514
+ numberOfChannels;
515
+ step;
516
+ input;
517
+ inputLength = 0;
518
+ position = 0;
519
+ constructor({
520
+ numberOfChannels,
521
+ step
522
+ }) {
523
+ this.numberOfChannels = numberOfChannels;
524
+ this.step = step;
525
+ this.input = makePlanarAudio(numberOfChannels, 65536);
526
+ }
527
+ append(audio) {
528
+ this.input = ensurePlanarCapacity({
529
+ buffers: this.input,
530
+ requiredLength: this.inputLength + audio[0].length
531
+ });
532
+ for (let channel = 0;channel < this.numberOfChannels; channel++) {
533
+ this.input[channel].set(audio[channel], this.inputLength);
534
+ }
535
+ this.inputLength += audio[0].length;
536
+ return this.process(false);
537
+ }
538
+ process(finalizing) {
539
+ const outputLength = Math.max(0, Math.floor((this.inputLength - (finalizing ? 0 : 1) - this.position) / this.step) + 1);
540
+ const result = makePlanarAudio(this.numberOfChannels, outputLength);
541
+ for (let outputFrame = 0;outputFrame < outputLength; outputFrame++) {
542
+ const leftIndex = Math.floor(this.position);
543
+ const rightIndex = Math.min(leftIndex + 1, this.inputLength - 1);
544
+ const fraction = this.position - leftIndex;
545
+ for (let channel = 0;channel < this.numberOfChannels; channel++) {
546
+ const left = this.input[channel][leftIndex];
547
+ const right = this.input[channel][rightIndex];
548
+ result[channel][outputFrame] = left + (right - left) * fraction;
549
+ }
550
+ this.position += this.step;
551
+ }
552
+ const discard = Math.min(Math.floor(this.position), this.inputLength);
553
+ for (let channel = 0;channel < this.numberOfChannels; channel++) {
554
+ this.input[channel].copyWithin(0, discard, this.inputLength);
555
+ }
556
+ this.inputLength -= discard;
557
+ this.position -= discard;
558
+ return result;
559
+ }
560
+ finalize() {
561
+ return this.process(true);
562
+ }
563
+ }
564
+
565
+ class StreamingPitchShifter {
566
+ numberOfChannels;
567
+ stretcher;
568
+ resampler;
569
+ outputQueue = new PlanarAudioQueue;
570
+ totalInputFrames = 0;
571
+ totalOutputFrames = 0;
572
+ constructor({
573
+ numberOfChannels,
574
+ sampleRate,
575
+ toneFrequency
576
+ }) {
577
+ this.numberOfChannels = numberOfChannels;
578
+ this.stretcher = new StreamingTimeStretcher({
579
+ numberOfChannels,
580
+ sampleRate,
581
+ factor: toneFrequency
582
+ });
583
+ this.resampler = new StreamingLinearResampler({
584
+ numberOfChannels,
585
+ step: toneFrequency
586
+ });
587
+ }
588
+ append(audio) {
589
+ this.totalInputFrames += audio[0].length;
590
+ const stretched = this.stretcher.append(audio);
591
+ this.outputQueue.push(this.resampler.append(stretched));
592
+ return this.takeAvailableOutput();
593
+ }
594
+ takeAvailableOutput() {
595
+ const availableInputFrames = this.totalInputFrames - this.totalOutputFrames;
596
+ const framesToTake = Math.min(availableInputFrames, this.outputQueue.getLength());
597
+ const result = this.outputQueue.take(framesToTake, this.numberOfChannels);
598
+ this.totalOutputFrames += framesToTake;
599
+ return result;
600
+ }
601
+ finalize() {
602
+ this.outputQueue.push(this.resampler.append(this.stretcher.finalize()));
603
+ this.outputQueue.push(this.resampler.finalize());
604
+ const remaining = this.totalInputFrames - this.totalOutputFrames;
605
+ const available = this.outputQueue.take(Math.min(remaining, this.outputQueue.getLength()), this.numberOfChannels);
606
+ const result = makePlanarAudio(this.numberOfChannels, remaining);
607
+ for (let channel = 0;channel < this.numberOfChannels; channel++) {
608
+ result[channel].set(available[channel]);
609
+ }
610
+ this.totalOutputFrames += remaining;
611
+ return result;
612
+ }
613
+ }
614
+ var getPlanarSlice = (slice) => {
615
+ const { buffer } = slice.buffer;
616
+ const startFrame = Math.max(0, Math.round(slice.sourceOffsetInSeconds * buffer.sampleRate));
617
+ const numberOfFrames = Math.min(buffer.length - startFrame, Math.round(slice.sourceDurationInSeconds * buffer.sampleRate));
618
+ return new Array(buffer.numberOfChannels).fill(null).map((_, channel) => buffer.getChannelData(channel).slice(startFrame, startFrame + numberOfFrames));
619
+ };
620
+ var makeAudioBufferSlice = ({
621
+ audio,
622
+ timelineTimestamp,
623
+ sampleRate
624
+ }) => {
625
+ const buffer = new AudioBuffer({
626
+ length: audio[0].length,
627
+ numberOfChannels: audio.length,
628
+ sampleRate
629
+ });
630
+ for (let channel = 0;channel < audio.length; channel++) {
631
+ buffer.copyToChannel(new Float32Array(audio[channel]), channel);
632
+ }
633
+ const duration = audio[0].length / sampleRate;
634
+ return {
635
+ buffer: { buffer, timestamp: timelineTimestamp, duration },
636
+ timelineTimestamp,
637
+ sourceOffsetInSeconds: 0,
638
+ sourceDurationInSeconds: duration
639
+ };
640
+ };
641
+ async function* pitchShiftAudioIterator({
642
+ iterator,
643
+ toneFrequency
644
+ }) {
645
+ if (toneFrequency === 1) {
646
+ yield* iterator;
647
+ return;
648
+ }
649
+ let shifter = null;
650
+ let sampleRate = 0;
651
+ let numberOfChannels = 0;
652
+ let segmentStart = 0;
653
+ let segmentInputFrames = 0;
654
+ let segmentOutputFrames = 0;
655
+ const flush = () => {
656
+ if (!shifter) {
657
+ return null;
658
+ }
659
+ const audio = shifter.finalize();
660
+ const slice = audio[0].length === 0 ? null : makeAudioBufferSlice({
661
+ audio,
662
+ timelineTimestamp: segmentStart + segmentOutputFrames / sampleRate,
663
+ sampleRate
664
+ });
665
+ shifter = null;
666
+ return slice;
667
+ };
668
+ for await (const slice of iterator) {
669
+ const planar = getPlanarSlice(slice);
670
+ if (planar[0].length === 0) {
671
+ continue;
672
+ }
673
+ const nextSampleRate = slice.buffer.buffer.sampleRate;
674
+ const nextNumberOfChannels = slice.buffer.buffer.numberOfChannels;
675
+ const expectedTimestamp = segmentStart + segmentInputFrames / sampleRate;
676
+ const startsNewSegment = !shifter || nextSampleRate !== sampleRate || nextNumberOfChannels !== numberOfChannels || Math.abs(slice.timelineTimestamp - expectedTimestamp) > 1.5 / nextSampleRate;
677
+ if (startsNewSegment) {
678
+ const previousSegmentFinalSlice = flush();
679
+ if (previousSegmentFinalSlice) {
680
+ yield previousSegmentFinalSlice;
681
+ }
682
+ sampleRate = nextSampleRate;
683
+ numberOfChannels = nextNumberOfChannels;
684
+ segmentStart = slice.timelineTimestamp;
685
+ segmentInputFrames = 0;
686
+ segmentOutputFrames = 0;
687
+ shifter = new StreamingPitchShifter({
688
+ numberOfChannels,
689
+ sampleRate,
690
+ toneFrequency
691
+ });
692
+ }
693
+ if (!shifter) {
694
+ throw new Error("Pitch shifter was not initialized.");
695
+ }
696
+ segmentInputFrames += planar[0].length;
697
+ const output = shifter.append(planar);
698
+ if (output[0].length > 0) {
699
+ yield makeAudioBufferSlice({
700
+ audio: output,
701
+ timelineTimestamp: segmentStart + segmentOutputFrames / sampleRate,
702
+ sampleRate
703
+ });
704
+ segmentOutputFrames += output[0].length;
705
+ }
706
+ }
707
+ const finalSlice = flush();
708
+ if (finalSlice) {
709
+ yield finalSlice;
710
+ }
711
+ }
712
+
251
713
  // src/audio/sort-by-priority.ts
252
714
  class StaleWaiterError extends Error {
253
715
  constructor() {
@@ -436,10 +898,12 @@ var audioIteratorManager = ({
436
898
  getStartTime,
437
899
  initialMuted,
438
900
  initialVolume,
901
+ toneFrequency,
439
902
  drawDebugOverlay
440
903
  }) => {
441
904
  let muted = initialMuted;
442
905
  let currentVolume = Math.max(0, initialVolume);
906
+ let currentToneFrequency = toneFrequency;
443
907
  let currentSeek = null;
444
908
  const gainNode = sharedAudioContext.audioContext.createGain();
445
909
  gainNode.gain.value = muted ? 0 : currentVolume;
@@ -651,7 +1115,7 @@ var audioIteratorManager = ({
651
1115
  mediaStartInSeconds: startFromSecond
652
1116
  };
653
1117
  const maximumContinuousTimestamp = startFromSecond + getSequenceDurationInSeconds() * playbackRate;
654
- const source = loop ? makeLoopingIterator({
1118
+ const unshiftedSource = loop ? makeLoopingIterator({
655
1119
  audioSink,
656
1120
  seekTimeInSeconds: startFromSecond,
657
1121
  loopStartInSeconds: getStartTime(),
@@ -662,6 +1126,10 @@ var audioIteratorManager = ({
662
1126
  timeToSeek: startFromSecond,
663
1127
  maximumTimestamp
664
1128
  });
1129
+ const source = pitchShiftAudioIterator({
1130
+ iterator: unshiftedSource,
1131
+ toneFrequency: currentToneFrequency
1132
+ });
665
1133
  const iterator = makeAudioIterator({
666
1134
  startFromSecond,
667
1135
  iterator: source,
@@ -796,6 +1264,9 @@ var audioIteratorManager = ({
796
1264
  currentVolume = Math.max(0, volume);
797
1265
  gainNode.gain.value = muted ? 0 : currentVolume;
798
1266
  },
1267
+ setToneFrequency: (newToneFrequency) => {
1268
+ currentToneFrequency = newToneFrequency;
1269
+ },
799
1270
  scheduleAudioChunk,
800
1271
  waitForNScheduledNodes
801
1272
  };
@@ -1749,6 +2220,7 @@ class MediaPlayer {
1749
2220
  src;
1750
2221
  logLevel;
1751
2222
  playbackRate;
2223
+ toneFrequency;
1752
2224
  globalPlaybackRate;
1753
2225
  audioStreamIndex;
1754
2226
  sharedAudioContext;
@@ -1779,6 +2251,7 @@ class MediaPlayer {
1779
2251
  trimBefore,
1780
2252
  trimAfter,
1781
2253
  playbackRate,
2254
+ toneFrequency,
1782
2255
  globalPlaybackRate,
1783
2256
  audioStreamIndex,
1784
2257
  fps,
@@ -1801,6 +2274,7 @@ class MediaPlayer {
1801
2274
  this.logLevel = logLevel;
1802
2275
  this.sharedAudioContext = sharedAudioContext;
1803
2276
  this.playbackRate = playbackRate;
2277
+ this.toneFrequency = toneFrequency;
1804
2278
  this.globalPlaybackRate = globalPlaybackRate;
1805
2279
  this.loop = loop;
1806
2280
  this.trimBefore = trimBefore;
@@ -1974,6 +2448,7 @@ class MediaPlayer {
1974
2448
  getStartTime: () => this.getStartTime(),
1975
2449
  initialMuted,
1976
2450
  initialVolume,
2451
+ toneFrequency: this.toneFrequency,
1977
2452
  drawDebugOverlay: this.drawDebugOverlay,
1978
2453
  getSequenceDurationInSeconds: () => this.getSequenceDurationInSeconds()
1979
2454
  });
@@ -2132,6 +2607,17 @@ class MediaPlayer {
2132
2607
  await this.seekTo(unloopedTimeInSeconds);
2133
2608
  }
2134
2609
  }
2610
+ async setToneFrequency(toneFrequency, unloopedTimeInSeconds) {
2611
+ if (this.toneFrequency !== toneFrequency) {
2612
+ this.toneFrequency = toneFrequency;
2613
+ if (!this.audioIteratorManager) {
2614
+ return;
2615
+ }
2616
+ this.audioIteratorManager.setToneFrequency(toneFrequency);
2617
+ this.audioIteratorManager.destroyIterator();
2618
+ await this.seekTo(unloopedTimeInSeconds);
2619
+ }
2620
+ }
2135
2621
  async setGlobalPlaybackRate(rate, unloopedTimeInSeconds) {
2136
2622
  const previousRate = this.globalPlaybackRate;
2137
2623
  if (previousRate !== rate) {
@@ -2319,6 +2805,7 @@ var useCommonEffects = ({
2319
2805
  effectiveMuted,
2320
2806
  userPreferredVolume,
2321
2807
  playbackRate,
2808
+ toneFrequency,
2322
2809
  globalPlaybackRate,
2323
2810
  fps,
2324
2811
  sequenceOffset,
@@ -2394,6 +2881,13 @@ var useCommonEffects = ({
2394
2881
  }
2395
2882
  mediaPlayer.setPlaybackRate(playbackRate, currentTimeRef.current);
2396
2883
  }, [playbackRate, mediaPlayerReady, mediaPlayerRef, currentTimeRef]);
2884
+ useLayoutEffect(() => {
2885
+ const mediaPlayer = mediaPlayerRef.current;
2886
+ if (!mediaPlayer || !mediaPlayerReady) {
2887
+ return;
2888
+ }
2889
+ mediaPlayer.setToneFrequency(toneFrequency, currentTimeRef.current);
2890
+ }, [toneFrequency, mediaPlayerReady, mediaPlayerRef, currentTimeRef]);
2397
2891
  useLayoutEffect(() => {
2398
2892
  const mediaPlayer = mediaPlayerRef.current;
2399
2893
  if (!mediaPlayer || !mediaPlayerReady) {
@@ -2456,7 +2950,6 @@ var useCommonEffects = ({
2456
2950
  import { jsx } from "react/jsx-runtime";
2457
2951
  var {
2458
2952
  useUnsafeVideoConfig,
2459
- Timeline,
2460
2953
  SharedAudioContext,
2461
2954
  usePlayerMutedState,
2462
2955
  useMediaVolumeState,
@@ -2464,7 +2957,9 @@ var {
2464
2957
  evaluateVolume,
2465
2958
  warnAboutTooHighVolume,
2466
2959
  usePreload,
2467
- SequenceContext
2960
+ SequenceContext,
2961
+ usePlaying,
2962
+ useBuffering
2468
2963
  } = Internals9;
2469
2964
  var AudioForPreviewAssertedShowing = ({
2470
2965
  src,
@@ -2494,10 +2989,11 @@ var AudioForPreviewAssertedShowing = ({
2494
2989
  const mediaPlayerRef = useRef(null);
2495
2990
  const initialTrimBeforeRef = useRef(trimBefore);
2496
2991
  const initialTrimAfterRef = useRef(trimAfter);
2992
+ const initialToneFrequencyRef = useRef(toneFrequency ?? 1);
2497
2993
  const [initialRequestInit] = useState(requestInit);
2498
2994
  const [mediaPlayerReady, setMediaPlayerReady] = useState(false);
2499
2995
  const [shouldFallbackToNativeAudio, setShouldFallbackToNativeAudio] = useState(false);
2500
- const [playing] = Timeline.usePlayingState();
2996
+ const playing = usePlaying();
2501
2997
  const { playbackRate: globalPlaybackRate } = Internals9.usePlaybackRate();
2502
2998
  const sharedAudioContext = useContext2(SharedAudioContext);
2503
2999
  const buffer = useBufferState();
@@ -2524,12 +3020,8 @@ var AudioForPreviewAssertedShowing = ({
2524
3020
  const isPremounting = Boolean(parentSequence?.premounting);
2525
3021
  const isPostmounting = Boolean(parentSequence?.postmounting);
2526
3022
  const sequenceOffset = (parentSequence?.absoluteFrom ?? 0) / videoConfig.fps;
2527
- const bufferingContext = useContext2(Internals9.BufferingContextReact);
2528
- if (!bufferingContext) {
2529
- throw new Error("useMediaPlayback must be used inside a <BufferingContext>");
2530
- }
2531
3023
  const effectiveMuted = muted || playerMuted || userPreferredVolume <= 0;
2532
- const isPlayerBuffering = Internals9.useIsPlayerBuffering(bufferingContext);
3024
+ const isPlayerBuffering = useBuffering();
2533
3025
  const initialPlaying = useRef(playing && !isPlayerBuffering);
2534
3026
  const initialIsPremounting = useRef(isPremounting);
2535
3027
  const initialIsPostmounting = useRef(isPostmounting);
@@ -2551,6 +3043,7 @@ var AudioForPreviewAssertedShowing = ({
2551
3043
  effectiveMuted,
2552
3044
  userPreferredVolume,
2553
3045
  playbackRate,
3046
+ toneFrequency: toneFrequency ?? 1,
2554
3047
  globalPlaybackRate,
2555
3048
  fps: videoConfig.fps,
2556
3049
  sequenceOffset,
@@ -2594,6 +3087,7 @@ var AudioForPreviewAssertedShowing = ({
2594
3087
  fps: videoConfig.fps,
2595
3088
  canvas: null,
2596
3089
  playbackRate: initialPlaybackRate.current,
3090
+ toneFrequency: initialToneFrequencyRef.current,
2597
3091
  audioStreamIndex: audioStreamIndex ?? null,
2598
3092
  debugOverlay: false,
2599
3093
  bufferState: buffer,
@@ -3582,6 +4076,10 @@ var makeKeyframeBank = async ({
3582
4076
  };
3583
4077
  const ensureEnoughFramesForTimestamp = async (timestampInSeconds, logLevel, fps) => {
3584
4078
  while (!hasDecodedEnoughForTimestamp(timestampInSeconds)) {
4079
+ deleteFramesBeforeTimestamp({
4080
+ logLevel: parentLogLevel,
4081
+ timestampInSeconds: timestampInSeconds - getSafeWindowOfMonotonicity(fps)
4082
+ });
3585
4083
  const sample = await sampleIterator.next();
3586
4084
  if (sample.value) {
3587
4085
  addFrame(sample.value, logLevel);
@@ -5201,6 +5699,7 @@ var { validateMediaProps } = Internals19;
5201
5699
  var audioSchema = {
5202
5700
  src: {
5203
5701
  type: "asset",
5702
+ assetType: "audio",
5204
5703
  default: undefined,
5205
5704
  description: "Source",
5206
5705
  keyframable: false
@@ -5225,6 +5724,16 @@ var audioSchema = {
5225
5724
  hiddenFromList: false,
5226
5725
  keyframable: false
5227
5726
  },
5727
+ toneFrequency: {
5728
+ type: "number",
5729
+ min: 0.01,
5730
+ max: 2,
5731
+ step: 0.01,
5732
+ default: 1,
5733
+ description: "Pitch",
5734
+ hiddenFromList: false,
5735
+ keyframable: false
5736
+ },
5228
5737
  muted: { type: "boolean", default: false, description: "Muted" },
5229
5738
  loop: { type: "boolean", default: false, description: "Loop" }
5230
5739
  };
@@ -5306,6 +5815,10 @@ var AudioInner = (props) => {
5306
5815
  throw new TypeError(`The \`<Audio>\` tag requires a string for \`src\`, but got ${JSON.stringify(props.src)} instead.`);
5307
5816
  }
5308
5817
  validateMediaProps({ playbackRate: props.playbackRate, volume: props.volume }, "Audio");
5818
+ validateToneFrequency({
5819
+ toneFrequency: props.toneFrequency,
5820
+ component: "Audio"
5821
+ });
5309
5822
  if (sequenceDurationInFrames === 0) {
5310
5823
  return null;
5311
5824
  }
@@ -5459,7 +5972,6 @@ var warnAboutObjectFitInStyleOrClassName = ({
5459
5972
  import { jsx as jsx4 } from "react/jsx-runtime";
5460
5973
  var {
5461
5974
  useUnsafeVideoConfig: useUnsafeVideoConfig2,
5462
- Timeline: Timeline2,
5463
5975
  SharedAudioContext: SharedAudioContext2,
5464
5976
  usePlayerMutedState: usePlayerMutedState2,
5465
5977
  useMediaVolumeState: useMediaVolumeState2,
@@ -5468,12 +5980,15 @@ var {
5468
5980
  warnAboutTooHighVolume: warnAboutTooHighVolume2,
5469
5981
  usePreload: usePreload2,
5470
5982
  SequenceContext: SequenceContext2,
5471
- useEffectChainState
5983
+ useEffectChainState,
5984
+ usePlaying: usePlaying2,
5985
+ useBuffering: useBuffering2
5472
5986
  } = Internals22;
5473
5987
  var VideoForPreviewAssertedShowing = ({
5474
5988
  src: unpreloadedSrc,
5475
5989
  style,
5476
5990
  playbackRate,
5991
+ toneFrequency,
5477
5992
  logLevel,
5478
5993
  className,
5479
5994
  muted,
@@ -5511,7 +6026,7 @@ var VideoForPreviewAssertedShowing = ({
5511
6026
  const [initialRequestInit] = useState4(requestInit);
5512
6027
  const [mediaPlayerReady, setMediaPlayerReady] = useState4(false);
5513
6028
  const [shouldFallbackToNativeVideo, setShouldFallbackToNativeVideo] = useState4(false);
5514
- const [playing] = Timeline2.usePlayingState();
6029
+ const playing = usePlaying2();
5515
6030
  const { playbackRate: globalPlaybackRate } = Internals22.usePlaybackRate();
5516
6031
  const sharedAudioContext = useContext4(SharedAudioContext2);
5517
6032
  const buffer = useBufferState2();
@@ -5549,17 +6064,14 @@ var VideoForPreviewAssertedShowing = ({
5549
6064
  const currentTimeRef = useRef2(currentTime);
5550
6065
  currentTimeRef.current = currentTime;
5551
6066
  const preloadedSrc = usePreload2(src);
5552
- const buffering = useContext4(Internals22.BufferingContextReact);
5553
- if (!buffering) {
5554
- throw new Error("useMediaPlayback must be used inside a <BufferingContext>");
5555
- }
5556
6067
  const effectiveMuted = muted || playerMuted || userPreferredVolume <= 0;
5557
- const isPlayerBuffering = Internals22.useIsPlayerBuffering(buffering);
6068
+ const isPlayerBuffering = useBuffering2();
5558
6069
  const initialPlaying = useRef2(playing && !isPlayerBuffering);
5559
6070
  const initialIsPremounting = useRef2(isPremounting);
5560
6071
  const initialIsPostmounting = useRef2(isPostmounting);
5561
6072
  const initialGlobalPlaybackRate = useRef2(globalPlaybackRate);
5562
6073
  const initialPlaybackRate = useRef2(playbackRate);
6074
+ const initialToneFrequency = useRef2(toneFrequency);
5563
6075
  const initialMuted = useRef2(effectiveMuted);
5564
6076
  const initialVolume = useRef2(userPreferredVolume);
5565
6077
  const initialSequenceDuration = useRef2(videoConfig.durationInFrames);
@@ -5620,6 +6132,7 @@ var VideoForPreviewAssertedShowing = ({
5620
6132
  trimBefore: initialTrimBeforeRef.current,
5621
6133
  fps: videoConfig.fps,
5622
6134
  playbackRate: initialPlaybackRate.current,
6135
+ toneFrequency: initialToneFrequency.current,
5623
6136
  audioStreamIndex,
5624
6137
  debugOverlay,
5625
6138
  bufferState: buffer,
@@ -5751,6 +6264,7 @@ var VideoForPreviewAssertedShowing = ({
5751
6264
  effectiveMuted,
5752
6265
  userPreferredVolume,
5753
6266
  playbackRate,
6267
+ toneFrequency,
5754
6268
  globalPlaybackRate,
5755
6269
  fps: videoConfig.fps,
5756
6270
  sequenceOffset,
@@ -5801,6 +6315,7 @@ var VideoForPreviewAssertedShowing = ({
5801
6315
  trimAfter,
5802
6316
  trimBefore,
5803
6317
  playbackRate,
6318
+ toneFrequency,
5804
6319
  loopVolumeCurveBehavior,
5805
6320
  name: "<Html5Video> (fallback)",
5806
6321
  loop,
@@ -6226,6 +6741,7 @@ var {
6226
6741
  var videoSchema = {
6227
6742
  src: {
6228
6743
  type: "asset",
6744
+ assetType: "video",
6229
6745
  default: undefined,
6230
6746
  description: "Source",
6231
6747
  keyframable: false
@@ -6250,6 +6766,16 @@ var videoSchema = {
6250
6766
  hiddenFromList: false,
6251
6767
  keyframable: false
6252
6768
  },
6769
+ toneFrequency: {
6770
+ type: "number",
6771
+ min: 0.01,
6772
+ max: 2,
6773
+ step: 0.01,
6774
+ default: 1,
6775
+ description: "Pitch",
6776
+ hiddenFromList: false,
6777
+ keyframable: false
6778
+ },
6253
6779
  muted: { type: "boolean", default: false, description: "Muted" },
6254
6780
  loop: { type: "boolean", default: false, description: "Loop" },
6255
6781
  ...Internals24.transformSchema,
@@ -6309,6 +6835,7 @@ var InnerVideo = ({
6309
6835
  trimAfter
6310
6836
  });
6311
6837
  validateMediaProps2({ playbackRate, volume }, "Video");
6838
+ validateToneFrequency({ toneFrequency, component: "Video" });
6312
6839
  if (environment.isRendering) {
6313
6840
  return /* @__PURE__ */ jsx6(VideoForRendering, {
6314
6841
  ...props,
@@ -6350,6 +6877,7 @@ var InnerVideo = ({
6350
6877
  muted,
6351
6878
  onVideoFrame,
6352
6879
  playbackRate,
6880
+ toneFrequency: toneFrequency ?? 1,
6353
6881
  src,
6354
6882
  style,
6355
6883
  volume,
@@ -26,6 +26,7 @@ export declare class MediaPlayer {
26
26
  private src;
27
27
  private logLevel;
28
28
  private playbackRate;
29
+ private toneFrequency;
29
30
  private globalPlaybackRate;
30
31
  private audioStreamIndex;
31
32
  private sharedAudioContext;
@@ -47,7 +48,7 @@ export declare class MediaPlayer {
47
48
  private initializationPromise;
48
49
  private premountAwareDelayPlayback;
49
50
  private seekPromiseChain;
50
- constructor({ canvas, src, logLevel, sharedAudioContext, loop, trimBefore, trimAfter, playbackRate, globalPlaybackRate, audioStreamIndex, fps, debugOverlay, bufferState, isPremounting, isPostmounting, durationInFrames, onVideoFrameCallback, playing, sequenceOffset, credentials, requestInit, tagType, getEffects, getEffectChainState }: {
51
+ constructor({ canvas, src, logLevel, sharedAudioContext, loop, trimBefore, trimAfter, playbackRate, toneFrequency, globalPlaybackRate, audioStreamIndex, fps, debugOverlay, bufferState, isPremounting, isPostmounting, durationInFrames, onVideoFrameCallback, playing, sequenceOffset, credentials, requestInit, tagType, getEffects, getEffectChainState }: {
51
52
  canvas: HTMLCanvasElement | OffscreenCanvas | null;
52
53
  src: string;
53
54
  logLevel: LogLevel;
@@ -56,6 +57,7 @@ export declare class MediaPlayer {
56
57
  trimBefore: number | undefined;
57
58
  trimAfter: number | undefined;
58
59
  playbackRate: number;
60
+ toneFrequency: number;
59
61
  globalPlaybackRate: number;
60
62
  audioStreamIndex: number | null;
61
63
  fps: number;
@@ -98,6 +100,7 @@ export declare class MediaPlayer {
98
100
  setTrimAfter(trimAfter: number | undefined, unloopedTimeInSeconds: number): Promise<void>;
99
101
  setDebugOverlay(debugOverlay: boolean): void;
100
102
  setPlaybackRate(rate: number, unloopedTimeInSeconds: number): Promise<void>;
103
+ setToneFrequency(toneFrequency: number, unloopedTimeInSeconds: number): Promise<void>;
101
104
  setGlobalPlaybackRate(rate: number, unloopedTimeInSeconds: number): Promise<void>;
102
105
  setFps(fps: number, unloopedTimeInSeconds: number): Promise<void>;
103
106
  setIsPremounting(isPremounting: boolean): void;
@@ -1,6 +1,6 @@
1
1
  import type React from 'react';
2
2
  import type { MediaPlayer } from './media-player';
3
- export declare const useCommonEffects: ({ mediaPlayerRef, mediaPlayerReady, currentTimeRef, playing, isPlayerBuffering, frame, trimBefore, trimAfter, effectiveMuted, userPreferredVolume, playbackRate, globalPlaybackRate, fps, sequenceOffset, loop, durationInFrames, isPremounting, isPostmounting, currentTime, logLevel, label, }: {
3
+ export declare const useCommonEffects: ({ mediaPlayerRef, mediaPlayerReady, currentTimeRef, playing, isPlayerBuffering, frame, trimBefore, trimAfter, effectiveMuted, userPreferredVolume, playbackRate, toneFrequency, globalPlaybackRate, fps, sequenceOffset, loop, durationInFrames, isPremounting, isPostmounting, currentTime, logLevel, label, }: {
4
4
  readonly mediaPlayerRef: React.RefObject<MediaPlayer | null>;
5
5
  readonly mediaPlayerReady: boolean;
6
6
  readonly currentTimeRef: React.RefObject<number>;
@@ -12,6 +12,7 @@ export declare const useCommonEffects: ({ mediaPlayerRef, mediaPlayerReady, curr
12
12
  readonly effectiveMuted: boolean;
13
13
  readonly userPreferredVolume: number;
14
14
  readonly playbackRate: number;
15
+ readonly toneFrequency: number;
15
16
  readonly globalPlaybackRate: number;
16
17
  readonly fps: number;
17
18
  readonly sequenceOffset: number;
@@ -0,0 +1,4 @@
1
+ export declare const validateToneFrequency: ({ toneFrequency, component, }: {
2
+ toneFrequency: number | undefined;
3
+ component: "Audio" | "Video";
4
+ }) => void;
@@ -7,6 +7,7 @@ type VideoForPreviewProps = NativeVideoProps & {
7
7
  readonly src: string;
8
8
  readonly style: React.CSSProperties | undefined;
9
9
  readonly playbackRate: number;
10
+ readonly toneFrequency: number;
10
11
  readonly logLevel: LogLevel;
11
12
  readonly className: string | undefined;
12
13
  readonly muted: boolean;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@remotion/media",
3
- "version": "4.0.519",
3
+ "version": "4.0.521",
4
4
  "main": "dist/index.js",
5
5
  "types": "dist/index.d.ts",
6
6
  "module": "dist/esm/index.mjs",
@@ -22,17 +22,17 @@
22
22
  "make": "tsgo && bun --env-file=../.env.bundle bundle.ts"
23
23
  },
24
24
  "dependencies": {
25
- "mediabunny": "1.55.1",
26
- "remotion": "4.0.519",
27
- "zod": "4.4.3"
25
+ "mediabunny": "1.55.5",
26
+ "remotion": "4.0.521",
27
+ "zod": "4.5.4"
28
28
  },
29
29
  "peerDependencies": {
30
30
  "react": ">=16.8.0",
31
31
  "react-dom": ">=16.8.0"
32
32
  },
33
33
  "devDependencies": {
34
- "@remotion/eslint-config-internal": "4.0.519",
35
- "@remotion/player": "4.0.519",
34
+ "@remotion/eslint-config-internal": "4.0.521",
35
+ "@remotion/player": "4.0.521",
36
36
  "@vitest/browser-webdriverio": "4.0.9",
37
37
  "eslint": "9.19.0",
38
38
  "react": "19.2.3",