Skip to content

Instantly share code, notes, and snippets.

@mhseiden
Last active March 23, 2021 09:19
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save mhseiden/8642472 to your computer and use it in GitHub Desktop.
Save mhseiden/8642472 to your computer and use it in GitHub Desktop.
Downloading, Decoding, and Downsampling an Audio File with the WebAudio API
/**
* In this gist, assume that we're using Chrome, and the following variables are in scope and have already been assigned to the following:
*
* var url = "http://upload.wikimedia.org/wikipedia/commons/b/be/Toccata_et_Fugue_BWV565.ogg";
* var webaudio = new webkitAudioContext();
* var renderCallback = function renderCallback(data) { return "render!"; } // a function which will render the given audio data on a canvas
*/
// Fetch the data with an AJAX request
var xhr = new XMLHttpRequest();
// Force the browser to return the result as a byte-array
xhr.responseType = "arraybuffer";
// Initialize the AJAX request as a "GET" for the given url
xhr.open("GET", url);
// Attach a callback to process the OGG data once the AJAX request has finished
xhr.addEventListener("readystatechange", function() {
// Only try to process the data if the AJAX request is in a finished state
if (4 === xhr.readyState) {
// Decode the AJAX response (which is an arraybuffer) into its uncompessed LPCM form
webaudio.decodeAudioData(xhr.response, function(aud) {
// Programmatically determine the number of samples-per-bin used for downsampling
var downsample = Math.ceil(aud.getChannelData(0).length / 100000 /* we only want 100k bins, to keep things fast */);
// Track the maximum value we've encountered, so that we can normalize the downsampled data
var max = Number.MIN_VALUE;
// Create an output buffer to hold the downsampled audio data
var buf = new Float32Array(aud.length / downsample);
// Iterate through the bins in the output buffer to create the downsampled result
for(var bin = 0, len1 = buf.length; bin < len1; ++bin) {
// Aggregate all of the samples in all of the channels, which belong in this bucket
var avg = 0.0;
// Iterate through all of the channels in the audio file (making this a mono-downsample)
for(var chan = 0, len3 = aud.numberOfChannels; chan < len3; ++chan) {
// Use a local variable to make the code a bit more readable :-D
var chanData = aud.getChannelData(chan);
// Aggregate all of the samples that belongs in this bin
for(var idx = bin * downsample, len2 = idx + downsample; idx < len2; ++idx) {
avg += chanData[idx];
}
}
// Find the overall average amplitude using the aggregated sum of amplitudes in the bin
buf[bin] = (avg / (aud.numberOfChannels * downsample));
// Update the maximum value seen in the downsampled-output buffer
max = Math.max(max, Math.abs(buf[bin]));
}
// Normalize the output buffer using the maximum value that we saw
for(bin = 0; bin < len1; ++bin) {
buf[bin] = buf[bin] / max;
}
// Pass the ouput buffer along to the function which will do the actual rendering
renderCallback(buf);
});
}
});
xhr.send();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment