diff --git a/cmd/audio-player/adpcm.js b/cmd/audio-player/adpcm.js index 396f2b5a..784145b7 100644 --- a/cmd/audio-player/adpcm.js +++ b/cmd/audio-player/adpcm.js @@ -27,121 +27,129 @@ LICENSE Reference algorithms for ADPCM compression and decompression are in part 6. */ -// Table of index changes (see spec). -const indexTable = [ - -1, -1, -1, -1, 2, 4, 6, 8, - -1, -1, -1, -1, 2, 4, 6, 8 -]; -// Quantize step size table (see spec). -const stepTable = [ - 7, 8, 9, 10, 11, 12, 13, 14, - 16, 17, 19, 21, 23, 25, 28, 31, - 34, 37, 41, 45, 50, 55, 60, 66, - 73, 80, 88, 97, 107, 118, 130, 143, - 157, 173, 190, 209, 230, 253, 279, 307, - 337, 371, 408, 449, 494, 544, 598, 658, - 724, 796, 876, 963, 1060, 1166, 1282, 1411, - 1552, 1707, 1878, 2066, 2272, 2499, 2749, 3024, - 3327, 3660, 4026, 4428, 4871, 5358, 5894, 6484, - 7132, 7845, 8630, 9493, 10442, 11487, 12635, 13899, - 15289, 16818, 18500, 20350, 22385, 24623, 27086, 29794, - 32767 -]; - -const byteDepth = 2; // We are working with 16-bit samples. TODO(Trek): make configurable. -const headSize = 8; // Number of bytes in the header of ADPCM. -const chunkLenSize = 4; - -let est = 0; // Estimation of sample based on quantised ADPCM nibble. -let idx = 0; // Index to step used for estimation. -let step = 0; - -// decodeSample takes 4 bits which represents a single ADPCM nibble, and returns a 16 bit decoded PCM sample. -function decodeSample(nibble) { - let diff = 0; - if ((nibble & 4) != 0) { - diff += step; - } - if ((nibble & 2) != 0) { - diff += step >> 1; - } - if ((nibble & 1) != 0) { - diff += step >> 2; - } - diff += step >> 3; - - if ((nibble & 8) != 0) { - diff = -diff; - } - est += diff; - idx += indexTable[nibble]; - - if (idx < 0) { - idx = 0; - } else if (idx > stepTable.length - 1) { - idx = stepTable.length - 1; +class Decoder { + constructor() { + this.est = 0; // Estimation of sample based on quantised ADPCM nibble. + this.idx = 0; // Index to step used for estimation. + this.step = 0; } - step = stepTable[idx]; + // Table of index changes (see spec). + static get indexTable() { + return [ + -1, -1, -1, -1, 2, 4, 6, 8, + -1, -1, -1, -1, 2, 4, 6, 8 + ]; + } - result = est; - return result; -} + // Quantize step size table (see spec). + static get stepTable() { + return [ + 7, 8, 9, 10, 11, 12, 13, 14, + 16, 17, 19, 21, 23, 25, 28, 31, + 34, 37, 41, 45, 50, 55, 60, 66, + 73, 80, 88, 97, 107, 118, 130, 143, + 157, 173, 190, 209, 230, 253, 279, 307, + 337, 371, 408, 449, 494, 544, 598, 658, + 724, 796, 876, 963, 1060, 1166, 1282, 1411, + 1552, 1707, 1878, 2066, 2272, 2499, 2749, 3024, + 3327, 3660, 4026, 4428, 4871, 5358, 5894, 6484, + 7132, 7845, 8630, 9493, 10442, 11487, 12635, 13899, + 15289, 16818, 18500, 20350, 22385, 24623, 27086, 29794, + 32767 + ]; + } -// decode takes an array of bytes of arbitrary length representing adpcm and decodes it into pcm. -function decode(b) { - // Iterate over each chunk and decode it. - let chunkLen; - let result = []; - for (let off = 0; off + headSize <= b.length; off += chunkLen) { - // Read length of chunk and check if whole chunk exists. - chunkLen = bytesToInt32(b.slice(off, off + chunkLenSize)) - if (off + chunkLen > b.length) { - break; + static get byteDepth() { return 2; } // We are working with 16-bit samples. TODO(Trek): make configurable. + static get headSize() { return 8; } // Number of bytes in the header of ADPCM. + static get chunkLenSize() { return 4; } + + // decodeSample takes 4 bits which represents a single ADPCM nibble, and returns a 16 bit decoded PCM sample. + decodeSample(nibble) { + let diff = 0; + if ((nibble & 4) != 0) { + diff += this.step; + } + if ((nibble & 2) != 0) { + diff += this.step >> 1; + } + if ((nibble & 1) != 0) { + diff += this.step >> 2; + } + diff += this.step >> 3; + + if ((nibble & 8) != 0) { + diff = -diff; + } + this.est += diff; + this.idx += Decoder.indexTable[nibble]; + + if (this.idx < 0) { + this.idx = 0; + } else if (this.idx > Decoder.stepTable.length - 1) { + this.idx = Decoder.stepTable.length - 1; } - // Initialize Decoder with first 4 bytes of b. - est = bytesToInt16(b.slice(off + chunkLenSize, off + chunkLenSize + byteDepth)); - idx = b[off + chunkLenSize + byteDepth]; - step = stepTable[idx]; + this.step = Decoder.stepTable[this.idx]; - result.push(...b.slice(off + chunkLenSize, off + chunkLenSize + byteDepth)); - - for (let i = off + headSize; i < off + chunkLen - b[off + chunkLenSize + 3]; i++) { - let twoNibs = b[i]; - let nib2 = twoNibs >> 4; - let nib1 = (nib2 << 4) ^ twoNibs; - - let sample1 = int16ToBytes(decodeSample(nib1)); - result.push(...sample1); - - let sample2 = int16ToBytes(decodeSample(nib2)); - result.push(...sample2); - } - if (b[off + chunkLenSize + 3] == 1) { - let padNib = b[off + chunkLen - 1]; - let sample = int16ToBytes(decodeSample(padNib)); - result.push(...sample); - } + return this.est; } - return result; -} -// int16ToBytes takes a number assumed to be an int 16 and converts it to an array containing bytes (Little Endian). -function int16ToBytes(num) { - return [(num & 0x00ff), (num & 0xff00) >> 8]; -} + // decode takes an array of bytes of arbitrary length representing adpcm and decodes it into pcm. + decode(b) { + // Iterate over each chunk and decode it. + let chunkLen; + let result = []; + for (let off = 0; off + Decoder.headSize <= b.length; off += chunkLen) { + // Read length of chunk and check if whole chunk exists. + chunkLen = Decoder.bytesToInt32(b.slice(off, off + Decoder.chunkLenSize)) + if (off + chunkLen > b.length) { + break; + } -// bytesToInt16 takes an array of bytes (assumed to be values between 0 and 255), interprates them as little endian and converts it to an int16. -function bytesToInt16(b) { - return (b[0] | (b[1] << 8)); -} + // Initialize Decoder with first 4 bytes of b. + this.est = Decoder.bytesToInt16(b.slice(off + Decoder.chunkLenSize, off + Decoder.chunkLenSize + Decoder.byteDepth)); + this.idx = b[off + Decoder.chunkLenSize + Decoder.byteDepth]; + this.step = Decoder.stepTable[this.idx]; -// bytesToInt32 takes an array of bytes (assumed to be values between 0 and 255), interprates them as little endian and converts it to an int32. -function bytesToInt32(b) { - return (b[0] | - (b[1] << 8) | - (b[2] << 16) | - (b[3] << 24)); + result.push(...b.slice(off + Decoder.chunkLenSize, off + Decoder.chunkLenSize + Decoder.byteDepth)); + + for (let i = off + Decoder.headSize; i < off + chunkLen - b[off + Decoder.chunkLenSize + 3]; i++) { + let twoNibs = b[i]; + let nib2 = twoNibs >> 4; + let nib1 = (nib2 << 4) ^ twoNibs; + + let sample1 = Decoder.int16ToBytes(this.decodeSample(nib1)); + result.push(...sample1); + + let sample2 = Decoder.int16ToBytes(this.decodeSample(nib2)); + result.push(...sample2); + } + if (b[off + Decoder.chunkLenSize + 3] == 1) { + let padNib = b[off + chunkLen - 1]; + let sample = Decoder.int16ToBytes(this.decodeSample(padNib)); + result.push(...sample); + } + } + return result; + } + + // int16ToBytes takes a number assumed to be an int 16 and converts it to an array containing bytes (Little Endian). + static int16ToBytes(num) { + return [(num & 0x00ff), (num & 0xff00) >> 8]; + } + + // bytesToInt16 takes an array of bytes (assumed to be values between 0 and 255), interprates them as little endian and converts it to an int16. + static bytesToInt16(b) { + return (b[0] | (b[1] << 8)); + } + + // bytesToInt32 takes an array of bytes (assumed to be values between 0 and 255), interprates them as little endian and converts it to an int32. + static bytesToInt32(b) { + return (b[0] | + (b[1] << 8) | + (b[2] << 16) | + (b[3] << 24)); + } } \ No newline at end of file diff --git a/cmd/audio-player/main.js b/cmd/audio-player/main.js index d15eaccb..7cc07077 100644 --- a/cmd/audio-player/main.js +++ b/cmd/audio-player/main.js @@ -31,11 +31,13 @@ function playFile() { reader.onload = event => { bytes = new Uint8Array(event.target.result) + let dec = new Decoder(); + // Decode adpcm to pcm. - var decoded = decode(Array.from(bytes)) + let decoded = dec.decode(Array.from(bytes)) // Convert raw pcm to wav TODO(Trek): make these configurable. - var wav = pcmToWav(decoded, 48000, 1, 16); + let wav = pcmToWav(decoded, 48000, 1, 16); // Play wav data in player. const blob = new Blob([Uint8Array.from(wav)], { @@ -57,8 +59,8 @@ function playFile() { // getQuery gets everything after the question mark in the URL. function getQuery() { - var regex = new RegExp("\\?(.*)"); - var match = regex.exec(window.location.href); + let regex = new RegExp("\\?(.*)"); + let match = regex.exec(window.location.href); if (match == null) { return ''; } else { @@ -68,7 +70,7 @@ function getQuery() { // load gets the file from the given url and displays a link for download. function load() { - var url = document.getElementById('url').value; + let url = document.getElementById('url').value; if (url == "") { url = getQuery() document.getElementById('url').value = url; @@ -80,7 +82,7 @@ function load() { return; } - var request = new XMLHttpRequest(); + let request = new XMLHttpRequest(); request.responseType = "blob"; request.onreadystatechange = function () { if (request.readyState === XMLHttpRequest.DONE) { @@ -91,7 +93,7 @@ function load() { dataURL = URL.createObjectURL(data); - var link = document.getElementById("link"); + let link = document.getElementById("link"); link.href = dataURL; link.download = "media.ts"; link.innerHTML = "Download";