When you pause an audio element with JavaScript using audio.pause();
you can hear an audible pop.
This occurs when a waveform is stopped/cut in the middle of a wave, rather than at a zero-crossing.
I have been looking for a way to resolve this and seen a lot of people suggesting quickly fading out like this:
var fadeAudio = setInterval(function () {
if (audio.volume <= 0.0) {
clearInterval(fadeAudio);
audio.volume = 0;
}
if (audio.volume != 0.0){
audio.volume -= 0.1;
}
}, 200);
This however will give a worse end result with a descending series of clicks instead.
I have found this code to add an 'S' curve fade at the beginning of the file but am having difficulty modifying it to fade out a file instead.
var ctx = new AudioContext();
var audio = new Audio('https://samples-files.com/samples/Audio/wav/sample-file-4.wav');
var gainNode = ctx.createGain();
var source = ctx.createBufferSource();
function create_curve_buffer(length, phase) {
var curve = new Float32Array(length);
for (var i = 0; i < length; i++) {
curve[i] = (Math.sin((Math.PI * i / length) - phase)) /2 + 0.5;
}
return curve;
};
audio.addEventListener('canplaythrough', function(){
source.connect(ctx.destination);
source.connect(gainNode);
gainNode.connect(ctx.destination);
curve = create_curve_buffer(ctx.sampleRate, (Math.PI/2));
start = ctx.currentTime;
gainNode.gain.setValueCurveAtTime(curve, start, 5);
});
<button id='play' onClick='audio.play();'>Play</button>
<br />
<button id='stop' onClick='audio.pause();'>Stop</button>
I have basically just tried reversing the curve by doing curve.reverse();
after the for loop and calling the function with the stop button instead of the canplaythrough
event.
Then I get an error saying Failed to execute 'setValueCurveAtTime' on 'AudioParam': setValueCurveAtTime(..., 0, 5) overlaps setValueCurveAtTime(..., 0, 5)
when I click stop.
What am I doing wrong here?
Final code:
var audioContext = new (window.AudioContext || window.webkitAudioContext)();
var source;
var gainNode = audioContext.createGain();
var audioBuffer;
async function fetchTrack() {
var audioData = await fetchAudio("https://samples-files.com/samples/Audio/wav/sample-file-4.wav");
audioContext.decodeAudioData(audioData, onDecoded, onDecodeError);
}
function fetchAudio(url) {
return new Promise((resolve, reject) => {
var request = new XMLHttpRequest();
request.open("GET", url, true);
request.responseType = "arraybuffer";
request.onload = () => resolve(request.response);
request.onerror = (e) => reject(e);
request.send();
});
}
function onDecoded(buffer) {
audioBuffer = buffer;
}
function onDecodeError(e) {
console.log("Error decoding buffer: " + e.message);
console.log(e);
}
function createCurve(length, phase) {
var curve = new Float32Array(length);
for (i = 0; i < length; ++i) {
curve[i] = (Math.sin((Math.PI * i / length) - phase)) /2 + 0.5;
}
curve.reverse();
return curve;
};
async function fade(){
var curve = createCurve(audioContext.sampleRate, (Math.PI/2));
var start = audioContext.currentTime;
gainNode.gain.setValueCurveAtTime(curve, start, 1);
setTimeout(stop,1000);
}
function stop(){
source.stop();
}
function play(){
source = audioContext.createBufferSource();
source.buffer = audioBuffer;
source.connect(gainNode);
gainNode.connect(audioContext.destination);
gainNode.gain.setValueAtTime(1, 0)
audioContext.currentTime = 0;
source.start();
}
fetchTrack();
<button id='play' onClick='play();'>Play</button>
<br />
<button id='stop' onClick='fade();'>Stop</button>