How to loop wav in web audio a certain amount of times?

Viewed 772

I know there's an option loop=true on an AudioBufferSourceNode. But I just want to repeat the wav a certain number of times. For example, how might I repeat it twice?

var url  = 'main.wav';

var context = new AudioContext();
var source = context.createBufferSource();

source.connect(context.destination);

var request = new XMLHttpRequest();
request.open('GET', url, true); 
request.responseType = 'arraybuffer';
request.onload = function() {
    context.decodeAudioData(request.response, function(response) {

        source.buffer = response;

        source.start(0);
        source.start(source.buffer.duration); // doesn't work "cannot call start more than once."
        //source.loop = true; // this is infinite amount of times, not good

    }, function () { console.error('The request failed.'); } );
}
request.send();

I also tried to create a second buffer:

var source2 = context.createBufferSource();

// inside callback
source2.buffer = response; // same response so I don't need to load this separately
source2.start(source.buffer.duration); // doesn't work

But that didn't work either. Any ideas?

4 Answers

Your approach with using second buffer is almost correct. You just need to use absolute start time, not relative. To obtain absolute time sum audio context's current time and duration of the sound:

// Play first sound now
var source1 = context.createBufferSource();
source1.buffer = response;
source1.start(context.currentTime);  // This is the same as source1.start();

// Play second sound with a delay
var source2 = context.createBufferSource();
source2.buffer = response;
source2.start(context.currentTime + response.duration);

This approach provides gapless playback (if your files are gapless).

You can wrap this into a simple function:

function play(context, buffer, delay=0) {
    var source = context.createBufferSource();
    source.buffer = buffer;
    source.start(context.currentTime + delay);
    return source;
}

...

// Play the sound twice, one after another
play(context, response, 0);
play(context, response, response.duration);

One solution would be to allow the loop behavior of your source and then schedule a callback to stop() the sources playback after a period of time that would allow playback of the sound file multiple times:

const url = 'main.wav';
const context = new AudioContext();
const source = context.createBufferSource();

// Tracks the number to playback cycles remaining
const playbackCount = 5;

source.connect(context.destination);

const request = new XMLHttpRequest();
request.open('GET', url, true);
request.responseType = 'arraybuffer';
request.onload = () => {

  context.decodeAudioData(request.response, (response) => {

    source.buffer = response;

    // Schedule sound to stop at future time, after which sound
    // should have played "playbackCount" number of times
    setTimeout(() => {
        source.stop();
    }, response.duration * playbackCount * 1000);

    // Enable loop behavior
    source.loop = true;

    source.start(0);
  }, () => {
    console.error('The request failed.');
  });
}
request.send();

You can set the AudioBufferSource's loop to true and call

source.stop( ctx.currentTime + audioBuffer.duration * repeatTime )

(async () => {  
  const url = 'https://dl.dropboxusercontent.com/s/1cdwpm3gca9mlo0/kick.mp3';
  const buf = await fetch( url ).then( (r) => r.arrayBuffer() );
  const ctx = new AudioContext();
  const audioBuffer = await ctx.decodeAudioData( buf );
  
  btn.onclick = (e) => {
    const source = ctx.createBufferSource();
    source.buffer = audioBuffer;
    source.loop = true;
    source.connect( ctx.destination );
    source.start( 0 );
    source.stop( ctx.currentTime + audioBuffer.duration * inp.valueAsNumber );
  };
  btn.disabled = false;
})().catch( console.error );
<label> repeat<input id="inp" type="number" min="1" max="20" value="5"></label>
<button id="btn" disabled>start</button>

Another alternative is to use the 3-arg version of start(). Something like:

let nReps = 2; /* Number of times to repeat the source */
let s = new AudioBufferSourceNode(context, 
             {buffer: buffer, loop: true});
s.start(startTime, 0, buffer.duration * nReps);
Related