@remotion/media 4.0.519 → 4.0.520

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,
@@ -5225,6 +5719,16 @@ var audioSchema = {
5225
5719
  hiddenFromList: false,
5226
5720
  keyframable: false
5227
5721
  },
5722
+ toneFrequency: {
5723
+ type: "number",
5724
+ min: 0.01,
5725
+ max: 2,
5726
+ step: 0.01,
5727
+ default: 1,
5728
+ description: "Pitch",
5729
+ hiddenFromList: false,
5730
+ keyframable: false
5731
+ },
5228
5732
  muted: { type: "boolean", default: false, description: "Muted" },
5229
5733
  loop: { type: "boolean", default: false, description: "Loop" }
5230
5734
  };
@@ -5306,6 +5810,10 @@ var AudioInner = (props) => {
5306
5810
  throw new TypeError(`The \`<Audio>\` tag requires a string for \`src\`, but got ${JSON.stringify(props.src)} instead.`);
5307
5811
  }
5308
5812
  validateMediaProps({ playbackRate: props.playbackRate, volume: props.volume }, "Audio");
5813
+ validateToneFrequency({
5814
+ toneFrequency: props.toneFrequency,
5815
+ component: "Audio"
5816
+ });
5309
5817
  if (sequenceDurationInFrames === 0) {
5310
5818
  return null;
5311
5819
  }
@@ -5459,7 +5967,6 @@ var warnAboutObjectFitInStyleOrClassName = ({
5459
5967
  import { jsx as jsx4 } from "react/jsx-runtime";
5460
5968
  var {
5461
5969
  useUnsafeVideoConfig: useUnsafeVideoConfig2,
5462
- Timeline: Timeline2,
5463
5970
  SharedAudioContext: SharedAudioContext2,
5464
5971
  usePlayerMutedState: usePlayerMutedState2,
5465
5972
  useMediaVolumeState: useMediaVolumeState2,
@@ -5468,12 +5975,15 @@ var {
5468
5975
  warnAboutTooHighVolume: warnAboutTooHighVolume2,
5469
5976
  usePreload: usePreload2,
5470
5977
  SequenceContext: SequenceContext2,
5471
- useEffectChainState
5978
+ useEffectChainState,
5979
+ usePlaying: usePlaying2,
5980
+ useBuffering: useBuffering2
5472
5981
  } = Internals22;
5473
5982
  var VideoForPreviewAssertedShowing = ({
5474
5983
  src: unpreloadedSrc,
5475
5984
  style,
5476
5985
  playbackRate,
5986
+ toneFrequency,
5477
5987
  logLevel,
5478
5988
  className,
5479
5989
  muted,
@@ -5511,7 +6021,7 @@ var VideoForPreviewAssertedShowing = ({
5511
6021
  const [initialRequestInit] = useState4(requestInit);
5512
6022
  const [mediaPlayerReady, setMediaPlayerReady] = useState4(false);
5513
6023
  const [shouldFallbackToNativeVideo, setShouldFallbackToNativeVideo] = useState4(false);
5514
- const [playing] = Timeline2.usePlayingState();
6024
+ const playing = usePlaying2();
5515
6025
  const { playbackRate: globalPlaybackRate } = Internals22.usePlaybackRate();
5516
6026
  const sharedAudioContext = useContext4(SharedAudioContext2);
5517
6027
  const buffer = useBufferState2();
@@ -5549,17 +6059,14 @@ var VideoForPreviewAssertedShowing = ({
5549
6059
  const currentTimeRef = useRef2(currentTime);
5550
6060
  currentTimeRef.current = currentTime;
5551
6061
  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
6062
  const effectiveMuted = muted || playerMuted || userPreferredVolume <= 0;
5557
- const isPlayerBuffering = Internals22.useIsPlayerBuffering(buffering);
6063
+ const isPlayerBuffering = useBuffering2();
5558
6064
  const initialPlaying = useRef2(playing && !isPlayerBuffering);
5559
6065
  const initialIsPremounting = useRef2(isPremounting);
5560
6066
  const initialIsPostmounting = useRef2(isPostmounting);
5561
6067
  const initialGlobalPlaybackRate = useRef2(globalPlaybackRate);
5562
6068
  const initialPlaybackRate = useRef2(playbackRate);
6069
+ const initialToneFrequency = useRef2(toneFrequency);
5563
6070
  const initialMuted = useRef2(effectiveMuted);
5564
6071
  const initialVolume = useRef2(userPreferredVolume);
5565
6072
  const initialSequenceDuration = useRef2(videoConfig.durationInFrames);
@@ -5620,6 +6127,7 @@ var VideoForPreviewAssertedShowing = ({
5620
6127
  trimBefore: initialTrimBeforeRef.current,
5621
6128
  fps: videoConfig.fps,
5622
6129
  playbackRate: initialPlaybackRate.current,
6130
+ toneFrequency: initialToneFrequency.current,
5623
6131
  audioStreamIndex,
5624
6132
  debugOverlay,
5625
6133
  bufferState: buffer,
@@ -5751,6 +6259,7 @@ var VideoForPreviewAssertedShowing = ({
5751
6259
  effectiveMuted,
5752
6260
  userPreferredVolume,
5753
6261
  playbackRate,
6262
+ toneFrequency,
5754
6263
  globalPlaybackRate,
5755
6264
  fps: videoConfig.fps,
5756
6265
  sequenceOffset,
@@ -5801,6 +6310,7 @@ var VideoForPreviewAssertedShowing = ({
5801
6310
  trimAfter,
5802
6311
  trimBefore,
5803
6312
  playbackRate,
6313
+ toneFrequency,
5804
6314
  loopVolumeCurveBehavior,
5805
6315
  name: "<Html5Video> (fallback)",
5806
6316
  loop,
@@ -6250,6 +6760,16 @@ var videoSchema = {
6250
6760
  hiddenFromList: false,
6251
6761
  keyframable: false
6252
6762
  },
6763
+ toneFrequency: {
6764
+ type: "number",
6765
+ min: 0.01,
6766
+ max: 2,
6767
+ step: 0.01,
6768
+ default: 1,
6769
+ description: "Pitch",
6770
+ hiddenFromList: false,
6771
+ keyframable: false
6772
+ },
6253
6773
  muted: { type: "boolean", default: false, description: "Muted" },
6254
6774
  loop: { type: "boolean", default: false, description: "Loop" },
6255
6775
  ...Internals24.transformSchema,
@@ -6309,6 +6829,7 @@ var InnerVideo = ({
6309
6829
  trimAfter
6310
6830
  });
6311
6831
  validateMediaProps2({ playbackRate, volume }, "Video");
6832
+ validateToneFrequency({ toneFrequency, component: "Video" });
6312
6833
  if (environment.isRendering) {
6313
6834
  return /* @__PURE__ */ jsx6(VideoForRendering, {
6314
6835
  ...props,
@@ -6350,6 +6871,7 @@ var InnerVideo = ({
6350
6871
  muted,
6351
6872
  onVideoFrame,
6352
6873
  playbackRate,
6874
+ toneFrequency: toneFrequency ?? 1,
6353
6875
  src,
6354
6876
  style,
6355
6877
  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.520",
4
4
  "main": "dist/index.js",
5
5
  "types": "dist/index.d.ts",
6
6
  "module": "dist/esm/index.mjs",
@@ -22,8 +22,8 @@
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",
25
+ "mediabunny": "1.55.5",
26
+ "remotion": "4.0.520",
27
27
  "zod": "4.4.3"
28
28
  },
29
29
  "peerDependencies": {
@@ -31,8 +31,8 @@
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.520",
35
+ "@remotion/player": "4.0.520",
36
36
  "@vitest/browser-webdriverio": "4.0.9",
37
37
  "eslint": "9.19.0",
38
38
  "react": "19.2.3",