Search code examples
javascriptaudiobufferhtml5-audioweb-audio-api

Reverse Audio in JavaScript/Angular


I want to implement an audio player's rewind functionality so than I hear the audio playing in fast rewind while I'm holding the rewind button down.

I have an AudioBuffer and I am reversing it, but when playing it, I can't hear anything. However, the fast forward mode I implemented does work fine:

private gainNode: GainNode = {} as GainNode;
private aCtx = new AudioContext({ sampleRate: 16000 });
private source?: AudioBufferSourceNode;

// [...]

if (this.source) {
  this.source = this.aCtx.createBufferSource();

  let bufferToPlay = this.speechAudioBufferService.getModifyedBuffer;

  if (this.speechAudioBufferService.getModifyedBuffer && this.fastRewind) {
    const actualBuffer = this.speechAudioBufferService.getModifyedBuffer;
    const channels = actualBuffer.numberOfChannels;
    const length = actualBuffer.length;
    const reversedBuffer = this.aCtx.createBuffer(channels, length, actualBuffer.sampleRate);

    for (let channel = 0; channel < channels; channel++) {
      const originalData = actualBuffer.getChannelData(channel);
      const reversedData = reversedBuffer.getChannelData(channel);

      for (let i = 0; i < length; i++) {
        reversedData[i] = originalData[length - i - 1];
      }
    }

    bufferToPlay?.copyFromChannel(reversedBuffer.getChannelData(0), 0);
  }

  if (bufferToPlay) {
    this.source.buffer = bufferToPlay;
  }

  this.source.playbackRate.value = this.playRate();

  this.source
    .connect(this.gainNode)
    .connect(this.aCtx.destination);

  this.source.start(0, this.positionAndDurationService.currentPosition);

Solution

  • Your code is incorrect, as you fill in reversedData but never put that data back into reversedBuffer:

    const originalData = actualBuffer.getChannelData(channel);
    const reversedData = reversedBuffer.getChannelData(channel);
    
    for (let i = 0; i < length; i++) {
      reversedData[i] = originalData[length - i - 1];
    }
    

    Where does reversedData go now? Nowhere. So, when you run:

    bufferToPlay?.copyFromChannel(reversedBuffer.getChannelData(0), 0);
    

    You are just copying one channel from an empty buffer (reversedBuffer) into bufferToPlay.

    Here's a simpler way to do it, copying all channels and using Float32Array and its .revese() method:

    if (this.speechAudioBufferService.getModifyedBuffer && this.fastRewind) {
      const {
        numberOfChannels,
        length,
        sampleRate,
      } = bufferToPlay.numberOfChannels;
    
      const reversedBuffer = this.aCtx.createBuffer(
        numberOfChannels,
        length,
        sampleRate,
      );
    
      for (let channel = 0; channel < numberOfChannels; ++channel) {
        const reversedSamples = bufferToPlay.getChannelData(channel).reverse();
    
        reversedBuffer.copyToChannel(reversedSamples, channel);
      }
    
      bufferToPlay = reversedBuffer;
    }