diff --git a/cmd/audio-player/adpcm.js b/cmd/audio-player/adpcm.js index eb11bd26..0648fbaa 100644 --- a/cmd/audio-player/adpcm.js +++ b/cmd/audio-player/adpcm.js @@ -61,7 +61,7 @@ class Decoder { ]; } - static get byteDepth() { return 2; } // We are working with 16-bit samples. TODO(Trek): make configurable. + static get byteDepth() { return 2; } // We are working with 16-bit samples. static get headSize() { return 8; } // Number of bytes in the header of ADPCM. static get chunkLenSize() { return 4; } // Length in bytes of the chunk length field in header. static get compFact() { return 4; } // In general ADPCM compresses by a factor of 4. @@ -99,9 +99,10 @@ class Decoder { // decode takes an array of bytes of arbitrary length representing adpcm and decodes it into pcm. decode(b) { + let result = new Uint16Array(Decoder.decBytes(b)/Decoder.byteDepth); + let resultOff = 0; // 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)) @@ -109,38 +110,37 @@ class Decoder { break; } - // Initialize Decoder with first 4 bytes of b. + // Initialize Decoder. 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]; - result.push(...b.slice(off + Decoder.chunkLenSize, off + Decoder.chunkLenSize + Decoder.byteDepth)); + result[resultOff] = Decoder.bytesToInt16(b.slice(off + Decoder.chunkLenSize, off + Decoder.chunkLenSize + Decoder.byteDepth)); + resultOff++; 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); + let sample1 = this.decodeSample(nib1); + result[resultOff] = sample1; + resultOff++; + + let sample2 = this.decodeSample(nib2); + result[resultOff] = sample2; + resultOff++; } if (b[off + Decoder.chunkLenSize + 3] == 1) { let padNib = b[off + chunkLen - 1]; - let sample = Decoder.int16ToBytes(this.decodeSample(padNib)); - result.push(...sample); + let sample = this.decodeSample(padNib); + result[resultOff] = sample; + resultOff++; } } 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)); diff --git a/cmd/audio-player/main.js b/cmd/audio-player/main.js index 7cc07077..a71d1687 100644 --- a/cmd/audio-player/main.js +++ b/cmd/audio-player/main.js @@ -29,18 +29,18 @@ function playFile() { const reader = new FileReader() reader.onload = event => { - bytes = new Uint8Array(event.target.result) + bytes = new Uint8Array(event.target.result); let dec = new Decoder(); // Decode adpcm to pcm. - let decoded = dec.decode(Array.from(bytes)) + let decoded = dec.decode(bytes); // Convert raw pcm to wav TODO(Trek): make these configurable. - let wav = pcmToWav(decoded, 48000, 1, 16); + let wav = pcmToWav(new Uint8Array(decoded.buffer), 48000, 1, 16); // Play wav data in player. - const blob = new Blob([Uint8Array.from(wav)], { + const blob = new Blob([wav], { type: 'audio/wav' }); const url = URL.createObjectURL(blob); diff --git a/cmd/audio-player/pcm-to-wav.js b/cmd/audio-player/pcm-to-wav.js index f0e44506..32848abb 100644 --- a/cmd/audio-player/pcm-to-wav.js +++ b/cmd/audio-player/pcm-to-wav.js @@ -24,41 +24,74 @@ LICENSE // pcmToWav takes raw pcm data along with the sample rate, number of channels and bit-depth, // and adds a WAV header to it so that it can be read and played by common players. -// Input and output data bytes are represented as arrays of 8 bit integers. +// Input should be a Uint16Array containing 16 bit PCM samples, output will be a Uint8Array representing the bytes of the wav file. // WAV spec.: http://soundfile.sapp.org/doc/WaveFormat/ function pcmToWav(data, rate, channels, bitdepth) { - subChunk2ID = [100, 97, 116, 97]; // "data". - subChunk2Size = int32ToBytes(data.length); + let subChunk2ID = [100, 97, 116, 97]; // "data". + let subChunk2Size = int32ToBytes(data.length); - subChunk1ID = [102, 109, 116, 32]; // "fmt ". - subChunk1Size = int32ToBytes(16); - audioFmt = int16ToBytes(1); // 1 = PCM. - numChannels = int16ToBytes(channels); - sampleRate = int32ToBytes(rate); - byteRate = int32ToBytes(rate * channels * bitdepth / 8); - blockAlign = int16ToBytes(channels * bitdepth / 8); - bitsPerSample = int16ToBytes(bitdepth) + let subChunk1ID = [102, 109, 116, 32]; // "fmt ". + let subChunk1Size = int32ToBytes(16); + let audioFmt = int16ToBytes(1); // 1 = PCM. + let numChannels = int16ToBytes(channels); + let sampleRate = int32ToBytes(rate); + let byteRate = int32ToBytes(rate * channels * bitdepth / 8); + let blockAlign = int16ToBytes(channels * bitdepth / 8); + let bitsPerSample = int16ToBytes(bitdepth) - chunkID = [82, 73, 70, 70]; // "RIFF". - chunkSize = int32ToBytes(36 + data.length); - format = [87, 65, 86, 69]; // "WAVE". + let chunkID = [82, 73, 70, 70]; // "RIFF". + let chunkSize = int32ToBytes(36 + data.length); + let format = [87, 65, 86, 69]; // "WAVE". - result = chunkID; - result.push(...chunkSize, ...format, ...subChunk1ID, ...subChunk1Size, ...audioFmt, ...numChannels, ...sampleRate, ...byteRate, ...blockAlign, ...bitsPerSample, ...subChunk2ID, ...subChunk2Size); - return result.concat(data); + let result = new Uint8Array((data.length*2) + 44); + let off = 0; + + result.set(chunkID, off); + off += 4; + result.set(chunkSize, off); + off += 4; + result.set(format, off); + off += 4; + result.set(subChunk1ID, off); + off += 4; + result.set(subChunk1Size, off); + off += 4; + result.set(audioFmt, off); + off += 2; + result.set(numChannels, off); + off += 2; + result.set(sampleRate, off); + off += 4; + result.set(byteRate, off); + off += 4; + result.set(blockAlign, off); + off += 2; + result.set(bitsPerSample, off); + off += 2; + result.set(subChunk2ID, off); + off += 4; + result.set(subChunk2Size, off); + off += 4; + + result.set(data, off); + + return result; } // int32ToBytes takes a number assumed to be an int 32 and converts it to an array containing bytes (Little Endian). function int32ToBytes(num) { - return [ - (num & 0x000000ff), - (num & 0x0000ff00) >> 8, - (num & 0x00ff0000) >> 16, - (num & 0xff000000) >> 24 - ]; + let b = new Uint8Array(4); + b[0] = (num & 0x000000ff); + b[1] = (num & 0x0000ff00) >> 8; + b[2] = (num & 0x00ff0000) >> 16; + b[3] = (num & 0xff000000) >> 24; + return b; } // 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]; + let b = new Uint8Array(2); + b[0] = (num & 0x00ff); + b[1] = (num & 0xff00) >> 8; + return b; } \ No newline at end of file