Skip to content

Instantly share code, notes, and snippets.

@hillct
Forked from revolunet/create-wav-from-buffer.js
Created October 9, 2019 20:56
Show Gist options
  • Save hillct/b1b993470f0294e818c52df730448fa2 to your computer and use it in GitHub Desktop.
Save hillct/b1b993470f0294e818c52df730448fa2 to your computer and use it in GitHub Desktop.
web audio + wav buffering
/*
Goal : instantly play any wav source without download the file and without <audio/>
Idea is to use the fetch streaming API and pass raw data to web audio
My use case is playng a wav file
following http://stackoverflow.com/questions/38589614/webaudio-streaming-with-fetch-domexception-unable-to-decode-audio-data/38593356#38593356
you can try this example here : http://www.ee.columbia.edu/~dpwe/sounds/music/
(just paste the code in the chrome console)
Problem : dirty sound
*/
// from https://gist.github.com/asanoboy/3979747
class Wav {
constructor(opt_params) {
this._sampleRate = opt_params && opt_params.sampleRate ? opt_params.sampleRate : 44100;
this._channels = opt_params && opt_params.channels ? opt_params.channels : 2;
this._eof = true;
this._bufferNeedle = 0;
this._buffer;
}
setBuffer(buffer) {
this._buffer = this.getWavInt16Array(buffer);
this._bufferNeedle = 0;
this._internalBuffer = '';
this._hasOutputHeader = false;
this._eof = false;
}
getBuffer(len) {
var rt;
if( this._bufferNeedle + len >= this._buffer.length ){
rt = new Int16Array(this._buffer.length - this._bufferNeedle);
this._eof = true;
}
else {
rt = new Int16Array(len);
}
for(var i=0; i<rt.length; i++){
rt[i] = this._buffer[i+this._bufferNeedle];
}
this._bufferNeedle += rt.length;
return rt.buffer;
}
eof() {
return this._eof;
}
getWavInt16Array(buffer) {
var intBuffer = new Int16Array(buffer.length + 23), tmp;
intBuffer[0] = 0x4952; // "RI"
intBuffer[1] = 0x4646; // "FF"
intBuffer[2] = (2*buffer.length + 15) & 0x0000ffff; // RIFF size
intBuffer[3] = ((2*buffer.length + 15) & 0xffff0000) >> 16; // RIFF size
intBuffer[4] = 0x4157; // "WA"
intBuffer[5] = 0x4556; // "VE"
intBuffer[6] = 0x6d66; // "fm"
intBuffer[7] = 0x2074; // "t "
intBuffer[8] = 0x0012; // fmt chunksize: 18
intBuffer[9] = 0x0000; //
intBuffer[10] = 0x0001; // format tag : 1
intBuffer[11] = this._channels; // channels: 2
intBuffer[12] = this._sampleRate & 0x0000ffff; // sample per sec
intBuffer[13] = (this._sampleRate & 0xffff0000) >> 16; // sample per sec
intBuffer[14] = (2*this._channels*this._sampleRate) & 0x0000ffff; // byte per sec
intBuffer[15] = ((2*this._channels*this._sampleRate) & 0xffff0000) >> 16; // byte per sec
intBuffer[16] = 0x0004; // block align
intBuffer[17] = 0x0010; // bit per sample
intBuffer[18] = 0x0000; // cb size
intBuffer[19] = 0x6164; // "da"
intBuffer[20] = 0x6174; // "ta"
intBuffer[21] = (2*buffer.length) & 0x0000ffff; // data size[byte]
intBuffer[22] = ((2*buffer.length) & 0xffff0000) >> 16; // data size[byte]
for (var i = 0; i < buffer.length; i++) {
tmp = buffer[i];
if (tmp >= 1) {
intBuffer[i+23] = (1 << 15) - 1;
}
else if (tmp <= -1) {
intBuffer[i+23] = -(1 << 15);
}
else {
intBuffer[i+23] = Math.round(tmp * (1 << 15));
}
}
return intBuffer;
}
}
// factory
function createWavFromBuffer(buffer, sampleRate) {
var wav = new Wav({
sampleRate: sampleRate,
channels: 1
});
wav.setBuffer(buffer);
return wav;
}
// ArrayBuffer -> Float32Array
var convertBlock = function(buffer) {
var incomingData = new Uint8Array(buffer);
var i, l = incomingData.length;
var outputData = new Float32Array(incomingData.length);
for (i = 0; i < l; i++) {
outputData[i] = (incomingData[i] - 128) / 128.0;
}
return outputData;
}
// function createLinkFromBlob(b) {
// var URLObject = window.webkitURL || window.URL;
// var url = URLObject.createObjectURL(b);
// // console.log(url);
// var link = document.createElement('a');
// link.href = url
// link.textContent = 'play';
// document.body.appendChild(link);
// }
// Wav -> blob
function getBlobFromWav(wav) {
var srclist = [];
while( !wav.eof() ){
srclist.push(wav.getBuffer(1000));
}
return new Blob(srclist, {type:'audio/wav'});
}
// adapted from http://stackoverflow.com/questions/20475982/choppy-inaudible-playback-with-chunked-audio-through-web-audio-api
function play(url) {
var context = new (window.AudioContext || window.webkitAudioContext)();
var audioStack = [];
// fetch API + streaming
// see https://jakearchibald.com/2015/thats-so-fetch/
fetch(url).then(function(response) {
var reader = response.body.getReader();
function read() {
// on each received chunk
return reader.read().then(({ value, done })=> {
if (value && value.buffer) {
// create a new .wav with the chunk
let wav = createWavFromBuffer(convertBlock(value.buffer), 44100);
// transform to blob
let blob = getBlobFromWav(wav);
// read blob and send to audio
var reader = new FileReader();
reader.onloadend = function() {
context.decodeAudioData(reader.result, function(buf) {
audioStack.push(buf);
if (audioStack.length) {
scheduleBuffers();
}
});
};
reader.readAsArrayBuffer(blob);
}
if (done) {
console.log('done');
return;
}
// read next
read()
});
}
// start reading
read();
})
var nextTime = 0;
function scheduleBuffers() {
while ( audioStack.length) {
var buffer = audioStack.shift();
var source = context.createBufferSource();
source.buffer = buffer;
source.connect(context.destination);
if (nextTime == 0)
nextTime = context.currentTime + 0.1; /// add 50ms latency to work well across systems - tune this if you like
source.start(nextTime);
nextTime += source.buffer.duration; // Make the next buffer wait the length of the last buffer before being played
};
}
}
// try http://www.ee.columbia.edu/~dpwe/sounds/music/
play('http://www.ee.columbia.edu/~dpwe/sounds/music/africa-toto.wav')
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment