precompute DELTA_TABLE and reorganize decompression loop

There are 1424 possible deltas given 89 steps and 16 codes. We can
quickly compute those and reuse them to save time.

Knowing the exact size of the waveform we can re-author the
decompression loop to take advantage of that. We can place the block
header decompression first in the outer while loop and then place an
inner loop with the 2 samples per block decompression unwrapped. The
first sample reads from the stream and the second uses the other 4 bits.
This commit is contained in:
Michael "Z" Goddard 2018-10-22 16:55:58 -04:00
parent c12bf02b33
commit 80820c6ba6
No known key found for this signature in database
GPG key ID: 762CD40DD5349872

View file

@ -23,6 +23,36 @@ const INDEX_TABLE = [
-1, -1, -1, -1, 2, 4, 6, 8
];
let _deltaTable = null;
/**
* Build a table of deltas from the 89 possible steps and 16 codes.
* @return {Array<number>} computed delta values
*/
const deltaTable = function () {
if (_deltaTable === null) {
const NUM_STEPS = STEP_TABLE.length;
const NUM_INDICES = INDEX_TABLE.length;
_deltaTable = new Array(NUM_STEPS * NUM_INDICES).fill(0);
let i = 0;
for (let index = 0; index < NUM_STEPS; index++) {
for (let code = 0; code < NUM_INDICES; code++) {
const step = STEP_TABLE[index];
let delta = 0;
if (code & 4) delta += step;
if (code & 2) delta += step >> 1;
if (code & 1) delta += step >> 2;
delta += step >> 3;
_deltaTable[i++] = (code & 8) ? -delta : delta;
}
}
}
return _deltaTable;
};
/**
* Decode wav audio files that have been compressed with the ADPCM format.
* This is necessary because, while web browsers have native decoders for many audio
@ -156,7 +186,6 @@ class ADPCMSoundDecoder {
*/
imaDecompress (compressedData, blockSize, out) {
let sample;
let step;
let code;
let delta;
let index = 0;
@ -168,40 +197,48 @@ class ADPCMSoundDecoder {
compressedData.position = 0;
const size = out.length;
const samplesAfterBlockHeader = (blockSize - 4) * 2;
const DELTA_TABLE = deltaTable();
let i = 0;
while (i < size) {
if (((compressedData.position % blockSize) === 0) && (lastByte < 0)) { // read block header
if (compressedData.getBytesAvailable() === 0) break;
// read block header
sample = compressedData.readInt16();
index = compressedData.readUint8();
compressedData.position++; // skip extra header byte
if (index > 88) index = 88;
out[i++] = sample / 32768;
} else {
const blockLength = Math.min(samplesAfterBlockHeader, size - i);
const blockStart = i;
while (i - blockStart < blockLength) {
// read 4-bit code and compute delta from previous sample
if (lastByte < 0) {
if (compressedData.getBytesAvailable() === 0) break;
lastByte = compressedData.readUint8();
code = lastByte & 0xF;
} else {
code = (lastByte >> 4) & 0xF;
lastByte = -1;
}
step = STEP_TABLE[index];
delta = 0;
if (code & 4) delta += step;
if (code & 2) delta += step >> 1;
if (code & 1) delta += step >> 2;
delta += step >> 3;
delta = DELTA_TABLE[index * 16 + code];
// compute next index
index += INDEX_TABLE[code];
if (index > 88) index = 88;
if (index < 0) index = 0;
else if (index < 0) index = 0;
// compute and output sample
sample += (code & 8) ? -delta : delta;
sample += delta;
if (sample > 32767) sample = 32767;
if (sample < -32768) sample = -32768;
else if (sample < -32768) sample = -32768;
out[i++] = sample / 32768;
// use 4-bit code from lastByte and compute delta from previous
// sample
code = (lastByte >> 4) & 0xF;
delta = DELTA_TABLE[index * 16 + code];
// compute next index
index += INDEX_TABLE[code];
if (index > 88) index = 88;
else if (index < 0) index = 0;
// compute and output sample
sample += delta;
if (sample > 32767) sample = 32767;
else if (sample < -32768) sample = -32768;
out[i++] = sample / 32768;
}
}