-
-
Save innocenat/5c5a48365930b691c863 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!DOCTYPE html> | |
<html> | |
<body> | |
</body> | |
<script> | |
// Load sound | |
var xhr = new XMLHttpRequest(); | |
xhr.open('GET', '//localhost/file.wav', true); | |
xhr.onprogress = function (evt) { | |
if (evt.lengthComputable) { | |
console.log("Progress: " + evt.loaded + "/" + evt.total + " (" + (100*evt.loaded/evt.total) + "%)"); | |
} else { | |
console.log("Progress: unavailable"); | |
} | |
}; | |
var audCtx = new AudioContext(); | |
window.data_bufSrc = undefined; | |
xhr.onload = function () { | |
console.log("File loaded."); | |
var d = new DataView(xhr.response); | |
if (d.getUint32(0) != 0x52494646 || d.getUint32(8) != 0x57415645) { | |
console.error('Not a .wav file!'); | |
return; | |
} | |
// Read metadata | |
var data = {}; | |
var meta = false; | |
var file_length = d.getUint32(4, true) + 8; | |
console.log('Length: ' + file_length); | |
var current_offset = 12; | |
var audioBuffer; | |
while (current_offset < xhr.response.byteLength) { | |
// Chunk header | |
var chunk_type = d.getUint32(current_offset); | |
var chunk_size = d.getUint32(current_offset+4, true); | |
current_offset += 8; | |
console.log("Chunk: " + chunk_type + "; size=" + chunk_size); | |
if (chunk_type == 0x666d7420) { // "fmt " | |
if (chunk_size < 14) { | |
console.error('Malformed wav file: invalid wav header size'); | |
return; | |
} | |
console.log('Found meta chunk!'); | |
data.format = d.getUint16(current_offset, true); | |
data.num_chan = d.getUint16(current_offset+2, true); | |
data.sample_rate = d.getUint32(current_offset+4, true); | |
data.byte_rate = d.getUint32(current_offset+8, true); | |
data.block_align = d.getUint16(current_offset+12, true); | |
if (chunk_size >= 16) | |
data.bps = d.getUint16(current_offset+14, true); | |
else | |
data.bps = 16; // FIXME not sure on this... | |
if (chunk_size >= 18) | |
data.extraSize = d.getUint16(current_offset+16, true); | |
if (data.format == 0xFFFE) { // Wave Format Extensible | |
if (!data.extraSize || data.extraSize < 22) { | |
console.error('WAVEEXT Format Error!'); | |
return; | |
} | |
data.sample = d.getUint16(current_offset+18, true); | |
data.channel_mask = d.getUint32(current_offset+20, true); | |
data.guid = readByteArray(d, current_offset+24, 16); | |
// GUID | |
var intLPCM = [0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x80, 0x00, 0x00, 0xAA, 0x00, 0x38, 0x9B, 0x71]; | |
var floatLPCM = [0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x80, 0x00, 0x00, 0xAA, 0x00, 0x38, 0x9B, 0x71]; | |
if (arraysEqual(data.guid, intLPCM)) { | |
console.log('Integer LPCM found.'); | |
} else if (arraysEqual(data.guid, floatLPCM)) { | |
console.log('Float LPCM found.'); | |
} else { | |
console.error("Invalid data format!"); | |
return; | |
} | |
} else if (data.format == 1) { // Integer LPCM | |
console.log('Integer LPCM found.'); | |
} else if (data.format == 3) { | |
console.log('Float LPCM found.'); | |
} else { | |
console.error("Invalid data format!"); | |
return; | |
} | |
meta = true; | |
current_offset += chunk_size; | |
} else if (chunk_type == 0x64617461) { // "data" | |
if (!meta) { | |
console.error('Malformed wav file: data chunk before fmt chunk'); | |
return; | |
} | |
console.log('Found data chunk!'); | |
// Calculate audio length | |
var bytePerSample = data.num_chan * data.bps / 8; | |
var bytePerSecond = bytePerSample * data.sample_rate; | |
var second = chunk_size / bytePerSecond; | |
var sampleCount = chunk_size / bytePerSample; | |
console.log("File length: %d (%d:%d)", second, second/60, second%60); | |
// Create AudioBuffer | |
audioBuffer = audCtx.createBuffer(data.num_chan, sampleCount, data.sample_rate); | |
var chanBuff = []; | |
for (var i = 0; i < data.num_chan; i++) { | |
chanBuff[i] = audioBuffer.getChannelData(i); | |
} | |
// Read data to AudioBuffer | |
var ofst = current_offset; | |
for (var sample = 0; sample < sampleCount; sample++) { | |
for (var i = 0; i < data.num_chan; i++) { | |
switch (data.bps) { // Integer only for now | |
case 8: | |
var dat = d.getUint8(ofst); | |
chanBuff[i][sample] = (dat - 128) / 128; | |
break; | |
case 16: | |
var dat = d.getInt16(ofst, true); | |
chanBuff[i][sample] = dat / (1 << 15); | |
break; | |
case 32: | |
var dat = d.getInt32(ofst, true); | |
chanBuff[i][sample] = dat / (1 << 31); | |
break; | |
} | |
ofst += data.bps / 8; | |
} | |
} | |
console.log("Demuxing complete.") | |
current_offset += chunk_size; | |
break; // stop processing | |
} else { // we are skipping 'fact' chunk if present | |
// Bypass chunk | |
console.log("Skipping: " + chunk_type); | |
current_offset += chunk_size; | |
} | |
} | |
window.data_bufSrc = audCtx.createBufferSource(); | |
window.data_bufSrc.buffer = audioBuffer; | |
window.data_bufSrc.connect(audCtx.destination); | |
window.data_bufSrc.start(0); | |
console.log("Done!"); | |
} | |
function arraysEqual(a, b) { | |
if (a === b) return true; | |
if (a == null || b == null) return false; | |
if (a.length != b.length) return false; | |
for (var i = 0; i < a.length; i++) { | |
if (a[i] !== b[i]) return false; | |
} | |
return true; | |
} | |
function readByteArray(d, o, s) { | |
var r = []; | |
for (var i = 0; i < s; i++) { | |
r.push(d.getUint8(o+i)); | |
} | |
return r; | |
} | |
xhr.responseType = "arraybuffer"; | |
xhr.send(); | |
console.log("Loading file..."); | |
</script> | |
</html> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment