Skip to content

Instantly share code, notes, and snippets.

@revolunet
Last active January 29, 2022 14:01
Show Gist options
  • Save revolunet/46d4187c3b6f28632a91421c1f2a9fad to your computer and use it in GitHub Desktop.
Save revolunet/46d4187c3b6f28632a91421c1f2a9fad 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')
@peLiaL
Copy link

peLiaL commented Aug 29, 2017

Hi,
I'm developing a project based on an Android app and a web server. The Android app sends a real time stream of byte arrays that contains the data of PCM audio encoded. The server receive that stream and a possible web client should listen the audio that is recording the Android app. I was understanding your code and I have tested, adapting it to my code. I have been able to reproduce the audio stream, but there are a lot of noise that makes difficult listen the audio.

Do you have any suggestions to remove that noise?
Thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment