Skip to content Skip to sidebar Skip to footer

Mixing Two Audio Buffers, Put One On Background Of Another By Using Web Audio Api

I want to mix two audio sources by put one song as background of another into single source. for example, i have input :

Solution 1:

Two approach originally posted at Is it possible to mix multiple audio files on top of each other preferably with javascript, adjusted to process File objects at change event of <input type="file"> element.

The first approach utilizes OfflineAudioContext(), AudioContext.createBufferSource(), AudioContext.createMediaStreamDestination(), Promise constructor, Promise.all(), MediaRecorder() to mix audio tracks, then offer mixed audio file for download.

var div = document.querySelector("div");

functionhandleFilesSelect(input) {
  div.innerHTML = "loading audio tracks.. please wait";
  var files = Array.from(input.files);
  var duration = 60000;
  var chunks = [];
  var audio = newAudioContext();
  var mixedAudio = audio.createMediaStreamDestination();
  var player = newAudio();
  var context;
  var recorder;
  var description = "";
  
  player.controls = "controls";
  
  functionget(file) {
    description += file.name.replace(/\..*|\s+/g, "");
    returnnewPromise(function(resolve, reject) {
      var reader = newFileReader;
      reader.readAsArrayBuffer(file);
      reader.onload = function() {
        resolve(reader.result)
      }
    })
  }

  functionstopMix(duration, ...media) {
    setTimeout(function(media) {
      media.forEach(function(node) {
        node.stop()
      })
    }, duration, media)
  }

  Promise.all(files.map(get)).then(function(data) {
      var len = Math.max.apply(Math, data.map(function(buffer) {
        return buffer.byteLength
      }));
      context = newOfflineAudioContext(2, len, 44100);
      returnPromise.all(data.map(function(buffer) {
          return audio.decodeAudioData(buffer)
            .then(function(bufferSource) {
              var source = context.createBufferSource();
              source.buffer = bufferSource;
              source.connect(context.destination);
              return source.start()
            })
        }))
        .then(function() {
          return context.startRendering()
        })
        .then(function(renderedBuffer) {
          returnnewPromise(function(resolve) {
            var mix = audio.createBufferSource();
            mix.buffer = renderedBuffer;
            mix.connect(audio.destination);
            mix.connect(mixedAudio);
            recorder = newMediaRecorder(mixedAudio.stream);
            recorder.start(0);
            mix.start(0);
            div.innerHTML = "playing and recording tracks..";
            // stop playback and recorder in 60 secondsstopMix(duration, mix, recorder)

            recorder.ondataavailable = function(event) {
              chunks.push(event.data);
            };

            recorder.onstop = function(event) {
              var blob = newBlob(chunks, {
                "type": "audio/ogg; codecs=opus"
              });
              console.log("recording complete");
              resolve(blob)
            };
          })
        })
        .then(function(blob) {
          console.log(blob);
          div.innerHTML = "mixed audio tracks ready for download..";
          var audioDownload = URL.createObjectURL(blob);
          var a = document.createElement("a");
          a.download = description + "." + blob.type.replace(/.+\/|;.+/g, "");
          a.href = audioDownload;
          a.innerHTML = a.download;
          document.body.appendChild(a);
          a.insertAdjacentHTML("afterend", "<br>");
          player.src = audioDownload;
          document.body.appendChild(player);
        })
    })
    .catch(function(e) {
      console.log(e)
    });

}
<!DOCTYPE html><html><head></head><body><inputid="files"type="file"name="files[]"accept="audio/*"multipleonchange="handleFilesSelect(this)" /><div></div></body></html>

The second approach uses AudioContext.createChannelMerger(), AudioContext.createChannelSplitter()

var div = document.querySelector("div");

functionhandleFilesSelect(input) {

  div.innerHTML = "loading audio tracks.. please wait";
  var files = Array.from(input.files);
  var chunks = [];
  var channels = [
    [0, 1],
    [1, 0]
  ];
  var audio = newAudioContext();
  var player = newAudio();
  var merger = audio.createChannelMerger(2);
  var splitter = audio.createChannelSplitter(2);
  var mixedAudio = audio.createMediaStreamDestination();
  var duration = 60000;
  var context;
  var recorder;
  var audioDownload;
  var description = "";

  player.controls = "controls";

  functionget(file) {
    description += file.name.replace(/\..*|\s+/g, "");
    console.log(description);
    returnnewPromise(function(resolve, reject) {
      var reader = newFileReader;
      reader.readAsArrayBuffer(file);
      reader.onload = function() {
        resolve(reader.result)
      }
    })
  }

  functionstopMix(duration, ...media) {
    setTimeout(function(media) {
      media.forEach(function(node) {
        node.stop()
      })
    }, duration, media)
  }

  Promise.all(files.map(get)).then(function(data) {
      returnPromise.all(data.map(function(buffer, index) {
          return audio.decodeAudioData(buffer)
            .then(function(bufferSource) {
              var channel = channels[index];
              var source = audio.createBufferSource();
              source.buffer = bufferSource;
              source.connect(splitter);
              splitter.connect(merger, channel[0], channel[1]);          
              return source
            })
        }))
        .then(function(audionodes) {
          merger.connect(mixedAudio);
          merger.connect(audio.destination);
          recorder = newMediaRecorder(mixedAudio.stream);
          recorder.start(0);
          audionodes.forEach(function(node, index) {
            node.start(0)
          });
          
          div.innerHTML = "playing and recording tracks..";
          
          stopMix(duration, ...audionodes, recorder);

          recorder.ondataavailable = function(event) {
            chunks.push(event.data);
          };

          recorder.onstop = function(event) {
            var blob = newBlob(chunks, {
              "type": "audio/ogg; codecs=opus"
            });
            audioDownload = URL.createObjectURL(blob);
            var a = document.createElement("a");
            a.download = description + "." + blob.type.replace(/.+\/|;.+/g, "");
            a.href = audioDownload;
            a.innerHTML = a.download;
            player.src = audioDownload;
            document.body.appendChild(a);
            document.body.appendChild(player);
          };
        })
    })
    .catch(function(e) {
      console.log(e)
    });
}
<!DOCTYPE html><html><head></head><body><inputid="files"type="file"name="files[]"accept="audio/*"multipleonchange="handleFilesSelect(this)" /><div></div></body></html>

Solution 2:

I just want to complement the excellent answer of guest271314 and post here the solution based on answer of guest271314 for second scenario (the second source is microphone input). Actually is client karaoke. Script:

window.AudioContext = window.AudioContext || window.webkitAudioContext;
var context = newwindow.AudioContext();
var playbackTrack = null;

functionhandleFileSelect(event){

     var file = event.files[0];
     var freader = newFileReader();

    freader.onload = function (e) {     
        context.decodeAudioData(e.target.result, function (buf) {

            playbackTrack = context.createBufferSource();
            playbackTrack.buffer = buf;

            var karaokeButton = document.getElementById("karaoke_start");
            karaokeButton.style.display = "inline-block";
            karaokeButton.addEventListener("click", function(){
                startKaraoke();
            });
        });
    };

    freader.readAsArrayBuffer(file);
}

functionstopMix(duration, mediaRecorder) {
    setTimeout(function(mediaRecorder) {
      mediaRecorder.stop();
      context.close();
    }, duration, mediaRecorder)
 }

functionstartKaraoke(){

    navigator.mediaDevices.getUserMedia({audio: true,video: false})
        .then(function(stream) {
            var mixedAudio = context.createMediaStreamDestination();
            var merger = context.createChannelMerger(2);
            var splitter = context.createChannelSplitter(2);
            var duration = 5000;

            var chunks = [];
            var channel1 = [0,1];
            var channel2 = [1, 0];

            var gainNode = context.createGain();
            playbackTrack.connect(gainNode);
            gainNode.connect(splitter);
            gainNode.gain.value = 0.5; // From 0 to 1
            splitter.connect(merger, channel1[0], channel1[1]);

            var microphone = context.createMediaStreamSource(stream);
            microphone.connect(splitter);
            splitter.connect(merger, channel2[0], channel2[1]);

            merger.connect(mixedAudio);
            merger.connect(context.destination);

            playbackTrack.start(0);
            var mediaRecorder = newMediaRecorder(mixedAudio.stream);
            mediaRecorder.start(1);

            mediaRecorder.ondataavailable = function (event) {
                chunks.push(event.data);
            }

            mediaRecorder.onstop = function(event) {
              var player = newAudio();
              player.controls = "controls";

              var blob = newBlob(chunks, {
                "type": "audio/mp3"
              });

              audioDownload = URL.createObjectURL(blob);
              var a = document.createElement("a");
              a.download = "karaokefile." + blob.type.replace(/.+\/|;.+/g, "");
              a.href = audioDownload;
              a.innerHTML = a.download;
              player.src = audioDownload;
              document.body.appendChild(a);
              document.body.appendChild(player);
            };

            stopMix(duration, mediaRecorder);
        })
        .catch(function(error) {
          console.log('error: ' + error);
        });

}

And Html:

<input id="file"type="file" 
         name="file" 
         accept="audio/*" 
         onchange="handleFileSelect(this)" />
  <span id="karaoke_start" style="display:none;background-color:yellow;cursor:pointer;">start karaoke</span>

Here the working plnkr example: plnkr

Post a Comment for "Mixing Two Audio Buffers, Put One On Background Of Another By Using Web Audio Api"