0

Hi so i'm trying to make a Drum-Kit, for this i'm using AudioContext API. My issue is when I use it exactly 50 times than it stops working. Only thing I found to make it work is to close the previous AudioContext that was used, thing is that it makes a "clicky" sound due to the sound stoping abruptly. Any ideas on what to do? Here is what i'm working with to not make it stop at 50 uses:

let i = 0;
let audioContext;
let volumeControl;

// Credit to MDN for AudioContext and StereoPannerNode tutorial.
function playSound(src, volume, pitch, stereo) {
    if (audioContext != null) {
        //volumeControl.gain.value = 0;
        audioContext.close();
    }

    console.log(i++);

    audioContext = new  AudioContext();
    const stereoControl = new StereoPannerNode(audioContext);

    volumeControl = audioContext.createGain();
    volumeControl.gain.value = volume;
    stereoControl.pan.value = stereo;

    const source = audioContext.createBufferSource();

    const request = new XMLHttpRequest();
    request.open('GET', src, true);
    request.responseType = 'arraybuffer';

    request.onload = function() {
        const audioData = request.response;

        audioContext.decodeAudioData(audioData, function(buffer) {
            source.buffer = buffer;
            source.playbackRate.value = pitch;
            source.connect(volumeControl).connect(stereoControl).connect(audioContext.destination);
        });

    };

    request.send();
    source.play = source.start;
    source.play();
}
miile7
  • 2,547
  • 3
  • 23
  • 38
Strum
  • 21
  • 2

1 Answers1

0

Don't create an audio context for each sound, but create a single one for your page and add nodes to it. Something like this...

const audioContext = new AudioContext();
function playSound(src, volume, pitch, stereo) {
  const stereoControl = audioContext.createStereoPanner();
  const volumeControl = audioContext.createGain();
  volumeControl.gain.value = volume;
  stereoControl.pan.value = stereo;
  const source = audioContext.createBufferSource();
  const request = new XMLHttpRequest();
  request.open("GET", src, true);
  request.responseType = "arraybuffer";

  request.onload = function() {
    const audioData = request.response;

    audioContext.decodeAudioData(audioData, function(buffer) {
      source.buffer = buffer;
      source.playbackRate.value = pitch;
      source
        .connect(volumeControl)
        .connect(stereoControl)
        .connect(audioContext.destination);
      source.start();
    });
  };

  request.send();
}

In addition, for a drumkit thing, you'll want to either preload all samples, or at the very least cache the decoded audio buffers and not do a request every time for them:

const cache = {};
const audioContext = new AudioContext();

function loadSound(src) {
  if (cache[src]) {
    // Already cached
    return Promise.resolve(cache[src]);
  }
  return new Promise(resolve => {
    const request = new XMLHttpRequest();
    request.open("GET", src, true);
    request.responseType = "arraybuffer";
    request.onload = function() {
      const audioData = request.response;
      audioContext.decodeAudioData(audioData, function(buffer) {
        cache[src] = buffer;
        resolve(buffer);
      });
    };
    request.send();
  });
}

function playSound(src, volume, pitch, stereo) {
  loadSound(src).then(buffer => {
    const stereoControl = audioContext.createStereoPanner();
    const volumeControl = audioContext.createGain();
    volumeControl.gain.value = volume;
    stereoControl.pan.value = stereo;
    const source = audioContext.createBufferSource();
    source.buffer = buffer;
    source.playbackRate.value = pitch;
    source
      .connect(volumeControl)
      .connect(stereoControl)
      .connect(audioContext.destination);
    source.start();
  });
}

AKX
  • 152,115
  • 15
  • 115
  • 172
  • How would I go about preloading all sounds :) ?At the moment each "pads" have an attribute callaed "data-src" which gives the path to the sound I want to play. Would looping through all divs that have the class "pads" and loading a new audio file with the attribute save them in the cache? – Strum May 06 '20 at 09:00
  • @Strum There, added an example with a promise-based audio buffer loader that caches the samples it's loaded. – AKX May 06 '20 at 09:50