home ~ projects ~ socials

Play Multiple Tracks With The Web Audio API

NOTE: See also: id: 2ugi4q3u

I'm building an audio stem player like splitter.fm1 using the Web Audio API2. The first step was building a prototype the plays multiple tracks in sync with each other. I did that by refining an example3 from MDN.

HTML

The HTML page doesn't have much to it. It calls the javascript and provides a start button that can be used if the browser prevents autoplay.

<!DOCTYPE html>
<html lang="en">

<head>
  <title>Stem Player Test</title>
  <script src="player.js"></script>
</head>
<body>
  <button id="startButton">Start</button>
</body>
</html>

JavaScript Version 2

The first version is further below. Leaving it there for reference. This is the second version. Note that there's nothing here to make sure all the files are downloaded. The audio could start before they call come down, but they will sync up as soon as they are loaded.

let audioCtx = null;

const tracks = [
  { id: "gracie", url: "audio/gracie.mp3" },
  { id: "paino", url: "audio/plucks.mp3" }
];

const init = () => {
  console.log("init");
  window.startButton.addEventListener("click", play);
  play();
}

async function play() {
  let offset = 0;
  console.log("playing")
  audioCtx = new AudioContext();
  tracks.forEach(async (track) => {
    const response = await fetch(track.url);
    const arrayBuffer = await response.arrayBuffer();
    track.track = await audioCtx.decodeAudioData(arrayBuffer);
    track.source = new AudioBufferSourceNode(audioCtx, {
      buffer: track.track,
    });
    track.source.connect(audioCtx.destination);
    if (offset == 0) {
      track.source.start();
      offset = audioCtx.currentTime;
    } else {
      track.source.start(0, audioCtx.currentTime - offset);
    }
    console.log(track)
  })
}

document.addEventListener("DOMContentLoaded", init);

JavaScript Version 1

The JavaScript does the heavy lifting with the Web Audio API. It expects two tracks at audio/gracie.mp3 and audio/piano.mp3. The primary function of this example is to show multiple tracks. I also added a panner module into the audio chain since that's what I need for what I'm doing.

let audioCtx = null;

const init = () => {
  console.log("init");
  window.startButton.addEventListener("click", play);
  play();
}

const play = () => {
  audioCtx = new AudioContext();
  loadFile("audio/gracie.mp3").then((track) => {
    playTrack(track);
  })
  loadFile("audio/piano.mp3").then((track) => {
    playTrack(track);
  })
}

async function loadFile(filepath) {
  const response = await fetch(filepath);
  const arrayBuffer = await response.arrayBuffer();
  const track = await audioCtx.decodeAudioData(arrayBuffer);
  return track;
}

let offset = 0;
function playTrack(audioBuffer) {
  const trackSource = new AudioBufferSourceNode(audioCtx, {
    buffer: audioBuffer,
  });
  const pannerOptions = { pan: 0 };
  const panner = new StereoPannerNode(audioCtx, pannerOptions);
  trackSource.connect(panner).connect(audioCtx.destination);
  if (offset == 0) {
    trackSource.start();
    offset = audioCtx.currentTime;
  } else {
    trackSource.start(0, audioCtx.currentTime - offset);
  }
  return trackSource;
}

document.addEventListener("DOMContentLoaded", init);

TODO

    TODO: Splitter.fm note

    TODO: Web Audio Note

    TODO: Multitrack example

-- end of line --