I'm recording some audio in the browser and then want to loop it seamlessly, avoiding clicks etc when starting. This means fading it and out.
I can ramp the volume up and down once, but I can't find anyway to trigger Web Audio's 'ramp to value at time' every time the loop starts again.
Is there an easy way to do this? I've got 10 of these buffers looping so I'd like to avoid lots of costly setinterval checks if possible...
let source = audioContext.createBufferSource();
let gain = audioContext.createGain();
gain.gain.value = 0.01;
source.buffer = decodedData;
songLength = decodedData.duration;
source.loop = true;
source.connect(gain);
gain.connect(audioContext.destination);
source.start(0);
// fade in and out
gain.gain.exponentialRampToValueAtTime(0.2, audioContext.currentTime + 1);
gain.gain.exponentialRampToValueAtTime(0.01, audioContext.currentTime + songLength);
Consider listening to the ended
event and re-trigger the playback:
class FadeInLoop {
ctx
audioBuffer
gainNode
isPlaying = true
constructor(ctx, url) {
this.ctx = ctx
this.audioBuffer = fetch(url)
.then(response => response.arrayBuffer())
.then(arrayBuffer => ctx.decodeAudioData(arrayBuffer))
this.gainNode = ctx.createGain()
this.gainNode.connect(ctx.destination)
}
async start() {
this.isPlaying = true
const source = ctx.createBufferSource()
this.source = source
source.addEventListener('ended', e => {
if (this.isPlaying) { // repeat unless stop() was called
this.start()
}
})
source.connect(this.gainNode)
source.buffer = await this.audioBuffer
const now = this.ctx.currentTime
this.gainNode.gain.setValueAtTime(Number.EPSILON, now);
this.gainNode.gain.exponentialRampToValueAtTime(1, now + 0.055)
source.start(0)
}
stop() {
this.isPlaying = false
this.source?.stop()
}
}
const ctx = new AudioContext({ latencyHint: 'interactive' })
const loop = new FadeInLoop(ctx, 'https://batman.dev/static/71474264/loop.mp3')
<button onclick="loop.start()">Start</button>
<button onclick="loop.stop()">Stop</button>