Passed
Push — master ( f5fd42...ebd91d )
by Rafael S.
57s
created

index.js ➔ updateEncoder_   B

Complexity

Conditions 6
Paths 36

Size

Total Lines 18

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 6
c 1
b 0
f 0
nc 36
nop 2
dl 0
loc 18
rs 8.8571
1
/*
2
 * imaadpcm: IMA ADPCM codec in JavaScript.
3
 * https://github.com/rochars/imaadpcm
4
 *
5
 * Copyright (c) 2017-2018 Rafael da Silva Rocha.
6
 *
7
 * Permission is hereby granted, free of charge, to any person obtaining
8
 * a copy of this software and associated documentation files (the
9
 * "Software"), to deal in the Software without restriction, including
10
 * without limitation the rights to use, copy, modify, merge, publish,
11
 * distribute, sublicense, and/or sell copies of the Software, and to
12
 * permit persons to whom the Software is furnished to do so, subject to
13
 * the following conditions:
14
 *
15
 * The above copyright notice and this permission notice shall be
16
 * included in all copies or substantial portions of the Software.
17
 *
18
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
19
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
20
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
21
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
22
 * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
23
 * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
24
 * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
25
 *
26
 */
27
28
/**
29
 * @fileoverview imaadpcm public API and private methods.
30
 */
31
32
/** @module imaadpcm */
33
34
/**
35
 * @type {!Array<number>}
36
 * @private
37
 */
38
const INDEX_TABLE = [
39
    -1, -1, -1, -1, 2, 4, 6, 8,
40
    -1, -1, -1, -1, 2, 4, 6, 8];
41
/**
42
 * @type {!Array<number>}
43
 * @private
44
 */
45
const STEP_TABLE = [
46
    7, 8, 9, 10, 11, 12, 13, 14,
47
    16, 17, 19, 21, 23, 25, 28, 31,
48
    34, 37, 41, 45, 50, 55, 60, 66,
49
    73, 80, 88, 97, 107, 118, 130, 143,
50
    157, 173, 190, 209, 230, 253, 279, 307,
51
    337, 371, 408, 449, 494, 544, 598, 658,
52
    724, 796, 876, 963, 1060, 1166, 1282, 1411,
53
    1552, 1707, 1878, 2066, 2272, 2499, 2749, 3024,
54
    3327, 3660, 4026, 4428, 4871, 5358, 5894, 6484,
55
    7132, 7845, 8630, 9493, 10442, 11487, 12635, 13899,
56
    15289, 16818, 18500, 20350, 22385, 24623, 27086, 29794,
57
    32767];
58
/**
59
 * @type {number}
60
 * @private
61
 */
62
let encoderPredicted_ = 0;
63
/**
64
 * @type {number}
65
 * @private
66
 */
67
let encoderIndex_ = 0;
68
/**
69
 * @type {number}
70
 * @private
71
 */
72
let encoderStep_ = 7;
0 ignored issues
show
Unused Code introduced by
The variable encoderStep_ seems to be never used. Consider removing it.
Loading history...
73
/**
74
 * @type {number}
75
 * @private
76
 */
77
let decoderPredicted_ = 0;
78
/**
79
 * @type {number}
80
 * @private
81
 */
82
let decoderIndex_ = 0;
83
/**
84
 * @type {number}
85
 * @private
86
 */
87
let decoderStep_ = 7;
88
89
/**
90
 * Encode 16-bit PCM samples into 4-bit IMA ADPCM samples.
91
 * @param {!Array<number>} samples A array of samples.
92
 * @return {!Array<number>}
93
 */
94
function encode(samples) {
95
    /** @type {!Array<number>} */
96
    let adpcmSamples = [];
97
    /** @type {Array<number>} */
98
    let block = [];
99
    for (let i=0; i<samples.length; i++) {
100
        block.push(samples[i]);
101
        if ((i % 505 == 0 && i != 0) || i == samples.length - 1) {
102
            adpcmSamples = adpcmSamples.concat(encodeBlock(block));
103
            block = [];
104
        }
105
    }
106
    return adpcmSamples;
107
}
108
109
/**
110
 * Decode IMA ADPCM samples into 16-bit PCM samples.
111
 * @param {!Array<number>} adpcmSamples A array of ADPCM samples.
112
 * @param {number} blockAlign The block size.
113
 * @return {!Array<number>}
114
 */
115
function decode(adpcmSamples, blockAlign=256) {
116
    /** @type {!Array<number>} */
117
    let samples = [];
118
    /** @type {!Array<number>} */
119
    let block = [];
120
    for (let i=0; i<adpcmSamples.length; i++) {
121
        if (i % blockAlign == 0 && i != 0) {            
122
            samples = samples.concat(decodeBlock(block));
123
            block = [];
124
        }
125
        block.push(adpcmSamples[i]);
126
    }
127
    return samples;
128
}
129
130
/**
131
 * Encode a block of 505 16-bit samples as 4-bit ADPCM samples.
132
 * @param {!Array<number>} block A sample block of 505 samples.
133
 * @return {!Array<number>}
134
 */
135
function encodeBlock(block) {
136
    /** @type {!Array<number>} */
137
    let adpcmSamples = blockHead_(block[0]);
138
    for (let i=3; i<block.length; i+=2) {
139
        /** @type {number} */
140
        let sample2 = encodeSample_(block[i]);
141
        /** @type {number} */
142
        let sample = encodeSample_(block[i + 1]);
143
        adpcmSamples.push((sample << 4) | sample2);
144
    }
145
    while (adpcmSamples.length < 256) {
146
        adpcmSamples.push(0);
147
    }
148
    return adpcmSamples;
149
}
150
151
/**
152
 * Decode a block of ADPCM samples into 16-bit PCM samples.
153
 * @param {!Array<number>} block A adpcm sample block.
154
 * @return {!Array<number>}
155
 */
156
function decodeBlock(block) {
157
    decoderPredicted_ = sign_((block[1] << 8) | block[0]);
158
    decoderIndex_ = block[2];
159
    decoderStep_ = STEP_TABLE[decoderIndex_];
160
    /** @type {!Array<number>} */
161
    let result = [
162
            decoderPredicted_,
163
            sign_((block[3] << 8) | block[2])
164
        ];
165
    for (let i=4; i<block.length; i++) {
166
        /** @type {number} */
167
        let original_sample = block[i];
168
        /** @type {number} */
169
        let second_sample = original_sample >> 4;
170
        /** @type {number} */
171
        let first_sample = (second_sample << 4) ^ original_sample;
172
        result.push(decodeSample_(first_sample));
173
        result.push(decodeSample_(second_sample));
174
    }
175
    return result;
176
}
177
178
/**
179
 * Sign a 16-bit integer.
180
 * @param {number} num A 16-bit integer.
181
 * @return {number}
182
 * @private
183
 */
184
function sign_(num) {
185
    return num > 32768 ? num - 65536 : num;
186
}
187
188
/**
189
 * Compress a 16-bit PCM sample into a 4-bit ADPCM sample.
190
 * @param {number} sample The sample.
191
 * @return {number}
192
 * @private
193
 */
194
function encodeSample_(sample) {
195
    /** @type {number} */
196
    let delta = sample - encoderPredicted_;
197
    /** @type {number} */
198
    let value = 0;
199
    if (delta >= 0) {
200
        value = 0;
201
    } else {
202
        value = 8;
203
        delta = -delta;
204
    }
205
    /** @type {number} */
206
    let step = STEP_TABLE[encoderIndex_];
207
    /** @type {number} */
208
    let diff = step >> 3;
209
    if (delta > step) {
210
        value |= 4;
211
        delta -= step;
212
        diff += step;
213
    }
214
    step >>= 1;
215
    if (delta > step) {
216
        value |= 2;
217
        delta -= step;
218
        diff += step;
219
    }
220
    step >>= 1;
221
    if (delta > step) {
222
        value |= 1;
223
        diff += step;
224
    }
225
    updateEncoder_(value, diff);
226
    return value;
227
}
228
229
/**
230
 * Set the value for encoderPredicted_ and encoderIndex_
231
 * after each sample is compressed.
232
 * @param {number} value The compressed ADPCM sample
233
 * @param {number} diff The calculated difference
234
 * @private
235
 */
236
function updateEncoder_(value, diff) {
237
    if (value & 8) {
0 ignored issues
show
introduced by
You have used a bitwise operator & in a condition. Did you maybe want to use the logical operator &&
Loading history...
238
        encoderPredicted_ -= diff;
239
    } else {
240
        encoderPredicted_ += diff;
241
    }
242
    if (encoderPredicted_ < -0x8000) {
243
        encoderPredicted_ = -0x8000;
244
    } else if (encoderPredicted_ > 0x7fff) {
245
        encoderPredicted_ = 0x7fff;
246
    }
247
    encoderIndex_ += INDEX_TABLE[value & 7];
248
    if (encoderIndex_ < 0) {
249
        encoderIndex_ = 0;
250
    } else if (encoderIndex_ > 88) {
251
        encoderIndex_ = 88;
252
    }
253
}
254
255
/**
256
 * Decode a 4-bit ADPCM sample into a 16-bit PCM sample.
257
 * @param {number} nibble A 4-bit adpcm sample.
258
 * @return {number}
259
 * @private
260
 */
261
function decodeSample_(nibble) {
262
    /** @type {number} */
263
    let difference = 0;
264
    if (nibble & 4) {
0 ignored issues
show
introduced by
You have used a bitwise operator & in a condition. Did you maybe want to use the logical operator &&
Loading history...
265
        difference += decoderStep_;
266
    }
267
    if (nibble & 2) {
0 ignored issues
show
introduced by
You have used a bitwise operator & in a condition. Did you maybe want to use the logical operator &&
Loading history...
268
        difference += decoderStep_ >> 1;
269
    }
270
    if (nibble & 1) {
0 ignored issues
show
introduced by
You have used a bitwise operator & in a condition. Did you maybe want to use the logical operator &&
Loading history...
271
        difference += decoderStep_ >> 2;
272
    }
273
    difference += decoderStep_ >> 3;
274
    if (nibble & 8) {
0 ignored issues
show
introduced by
You have used a bitwise operator & in a condition. Did you maybe want to use the logical operator &&
Loading history...
275
        difference = -difference;
276
    }
277
    decoderPredicted_ += difference;
278
    if (decoderPredicted_ > 32767) {
279
        decoderPredicted_ = 32767;
280
    } else if (decoderPredicted_ < -32767) {
281
        decoderPredicted_ = -32767;
282
    }
283
    updateDecoder_(nibble);
284
    return decoderPredicted_;
285
}
286
287
/**
288
 * Update the index and step after decoding a sample.
289
 * @param {number} nibble A 4-bit adpcm sample.
290
 * @private
291
 */
292
function updateDecoder_(nibble) {
293
    decoderIndex_ += INDEX_TABLE[nibble];
294
    if (decoderIndex_ < 0) {
295
        decoderIndex_ = 0;
296
    } else if (decoderIndex_ > 88) {
297
        decoderIndex_ = 88;
298
    }
299
    decoderStep_ = STEP_TABLE[decoderIndex_];
300
}
301
302
/**
303
 * Return the head of a ADPCM sample block.
304
 * @param {number} sample The first sample of the block.
305
 * @return {!Array<number>}
306
 * @private
307
 */
308
function blockHead_(sample) {
309
    encodeSample_(sample);
310
    /** @type {!Array<number>} */
311
    let adpcmSamples = [];
312
    adpcmSamples.push(sample & 0xFF);
313
    adpcmSamples.push((sample >> 8) & 0xFF);
314
    adpcmSamples.push(encoderIndex_);
315
    adpcmSamples.push(0);
316
    return adpcmSamples;
317
}
318
319
/** @export */
320
module.exports.encode = encode;
321
/** @export */
322
module.exports.decode = decode;
323
/** @export */
324
module.exports.encodeBlock = encodeBlock;
325
/** @export */
326
module.exports.decodeBlock = decodeBlock;
327