1 | const ArrayBufferStream = require('./ArrayBufferStream');
|
2 | const log = require('./log');
|
3 |
|
4 |
|
5 |
|
6 |
|
7 |
|
8 | const STEP_TABLE = [
|
9 | 7, 8, 9, 10, 11, 12, 13, 14, 16, 17, 19, 21, 23, 25, 28, 31, 34, 37, 41, 45,
|
10 | 50, 55, 60, 66, 73, 80, 88, 97, 107, 118, 130, 143, 157, 173, 190, 209, 230,
|
11 | 253, 279, 307, 337, 371, 408, 449, 494, 544, 598, 658, 724, 796, 876, 963,
|
12 | 1060, 1166, 1282, 1411, 1552, 1707, 1878, 2066, 2272, 2499, 2749, 3024, 3327,
|
13 | 3660, 4026, 4428, 4871, 5358, 5894, 6484, 7132, 7845, 8630, 9493, 10442, 11487,
|
14 | 12635, 13899, 15289, 16818, 18500, 20350, 22385, 24623, 27086, 29794, 32767
|
15 | ];
|
16 |
|
17 |
|
18 |
|
19 |
|
20 |
|
21 | const INDEX_TABLE = [
|
22 | -1, -1, -1, -1, 2, 4, 6, 8,
|
23 | -1, -1, -1, -1, 2, 4, 6, 8
|
24 | ];
|
25 |
|
26 | let _deltaTable = null;
|
27 |
|
28 |
|
29 |
|
30 |
|
31 |
|
32 | const deltaTable = function () {
|
33 | if (_deltaTable === null) {
|
34 | const NUM_STEPS = STEP_TABLE.length;
|
35 | const NUM_INDICES = INDEX_TABLE.length;
|
36 | _deltaTable = new Array(NUM_STEPS * NUM_INDICES).fill(0);
|
37 | let i = 0;
|
38 |
|
39 | for (let index = 0; index < NUM_STEPS; index++) {
|
40 | for (let code = 0; code < NUM_INDICES; code++) {
|
41 | const step = STEP_TABLE[index];
|
42 |
|
43 | let delta = 0;
|
44 | if (code & 4) delta += step;
|
45 | if (code & 2) delta += step >> 1;
|
46 | if (code & 1) delta += step >> 2;
|
47 | delta += step >> 3;
|
48 | _deltaTable[i++] = (code & 8) ? -delta : delta;
|
49 | }
|
50 | }
|
51 | }
|
52 |
|
53 | return _deltaTable;
|
54 | };
|
55 |
|
56 |
|
57 |
|
58 |
|
59 |
|
60 |
|
61 |
|
62 |
|
63 | class ADPCMSoundDecoder {
|
64 | |
65 |
|
66 |
|
67 |
|
68 | constructor (audioContext) {
|
69 | this.audioContext = audioContext;
|
70 | }
|
71 |
|
72 | |
73 |
|
74 |
|
75 |
|
76 | static get STEP_TABLE () {
|
77 | return STEP_TABLE;
|
78 | }
|
79 |
|
80 | |
81 |
|
82 |
|
83 |
|
84 | static get INDEX_TABLE () {
|
85 | return INDEX_TABLE;
|
86 | }
|
87 |
|
88 | |
89 |
|
90 |
|
91 |
|
92 |
|
93 |
|
94 | decode (audioData) {
|
95 |
|
96 | return new Promise((resolve, reject) => {
|
97 | const stream = new ArrayBufferStream(audioData);
|
98 |
|
99 | const riffStr = stream.readUint8String(4);
|
100 | if (riffStr !== 'RIFF') {
|
101 | log.warn('incorrect adpcm wav header');
|
102 | reject(new Error('incorrect adpcm wav header'));
|
103 | }
|
104 |
|
105 | const lengthInHeader = stream.readInt32();
|
106 | if ((lengthInHeader + 8) !== audioData.byteLength) {
|
107 | log.warn(`adpcm wav length in header: ${lengthInHeader} is incorrect`);
|
108 | }
|
109 |
|
110 | const wavStr = stream.readUint8String(4);
|
111 | if (wavStr !== 'WAVE') {
|
112 | log.warn('incorrect adpcm wav header');
|
113 | reject(new Error('incorrect adpcm wav header'));
|
114 | }
|
115 |
|
116 | const formatChunk = this.extractChunk('fmt ', stream);
|
117 | this.encoding = formatChunk.readUint16();
|
118 | this.channels = formatChunk.readUint16();
|
119 | this.samplesPerSecond = formatChunk.readUint32();
|
120 | this.bytesPerSecond = formatChunk.readUint32();
|
121 | this.blockAlignment = formatChunk.readUint16();
|
122 | this.bitsPerSample = formatChunk.readUint16();
|
123 | formatChunk.position += 2;
|
124 | this.samplesPerBlock = formatChunk.readUint16();
|
125 | this.adpcmBlockSize = ((this.samplesPerBlock - 1) / 2) + 4;
|
126 |
|
127 | const compressedData = this.extractChunk('data', stream);
|
128 | const sampleCount = this.numberOfSamples(compressedData, this.adpcmBlockSize);
|
129 |
|
130 | const buffer = this.audioContext.createBuffer(1, sampleCount, this.samplesPerSecond);
|
131 | this.imaDecompress(compressedData, this.adpcmBlockSize, buffer.getChannelData(0));
|
132 |
|
133 | resolve(buffer);
|
134 | });
|
135 | }
|
136 |
|
137 | |
138 |
|
139 |
|
140 |
|
141 |
|
142 |
|
143 | extractChunk (chunkType, stream) {
|
144 | stream.position = 12;
|
145 | while (stream.position < (stream.getLength() - 8)) {
|
146 | const typeStr = stream.readUint8String(4);
|
147 | const chunkSize = stream.readInt32();
|
148 | if (typeStr === chunkType) {
|
149 | const chunk = stream.extract(chunkSize);
|
150 | return chunk;
|
151 | }
|
152 | stream.position += chunkSize;
|
153 |
|
154 | }
|
155 | }
|
156 |
|
157 | |
158 |
|
159 |
|
160 |
|
161 |
|
162 |
|
163 | numberOfSamples (compressedData, blockSize) {
|
164 | if (!compressedData) return 0;
|
165 |
|
166 | compressedData.position = 0;
|
167 |
|
168 | const available = compressedData.getBytesAvailable();
|
169 | const blocks = (available / blockSize) | 0;
|
170 |
|
171 | const fullBlocks = (blocks * (2 * (blockSize - 4))) + 1;
|
172 |
|
173 |
|
174 | const subBlock = Math.max((available % blockSize) - 4, 0) * 2;
|
175 |
|
176 | const incompleteBlock = Math.min(available % blockSize, 1);
|
177 | return fullBlocks + subBlock + incompleteBlock;
|
178 | }
|
179 |
|
180 | |
181 |
|
182 |
|
183 |
|
184 |
|
185 |
|
186 |
|
187 | imaDecompress (compressedData, blockSize, out) {
|
188 | let sample;
|
189 | let code;
|
190 | let delta;
|
191 | let index = 0;
|
192 | let lastByte = -1;
|
193 |
|
194 |
|
195 | if (!compressedData) return;
|
196 |
|
197 | compressedData.position = 0;
|
198 |
|
199 | const size = out.length;
|
200 | const samplesAfterBlockHeader = (blockSize - 4) * 2;
|
201 |
|
202 | const DELTA_TABLE = deltaTable();
|
203 |
|
204 | let i = 0;
|
205 | while (i < size) {
|
206 |
|
207 | sample = compressedData.readInt16();
|
208 | index = compressedData.readUint8();
|
209 | compressedData.position++;
|
210 | if (index > 88) index = 88;
|
211 | out[i++] = sample / 32768;
|
212 |
|
213 | const blockLength = Math.min(samplesAfterBlockHeader, size - i);
|
214 | const blockStart = i;
|
215 | while (i - blockStart < blockLength) {
|
216 |
|
217 | lastByte = compressedData.readUint8();
|
218 | code = lastByte & 0xF;
|
219 | delta = DELTA_TABLE[(index * 16) + code];
|
220 |
|
221 | index += INDEX_TABLE[code];
|
222 | if (index > 88) index = 88;
|
223 | else if (index < 0) index = 0;
|
224 |
|
225 | sample += delta;
|
226 | if (sample > 32767) sample = 32767;
|
227 | else if (sample < -32768) sample = -32768;
|
228 | out[i++] = sample / 32768;
|
229 |
|
230 |
|
231 |
|
232 | code = (lastByte >> 4) & 0xF;
|
233 | delta = DELTA_TABLE[(index * 16) + code];
|
234 |
|
235 | index += INDEX_TABLE[code];
|
236 | if (index > 88) index = 88;
|
237 | else if (index < 0) index = 0;
|
238 |
|
239 | sample += delta;
|
240 | if (sample > 32767) sample = 32767;
|
241 | else if (sample < -32768) sample = -32768;
|
242 | out[i++] = sample / 32768;
|
243 | }
|
244 | }
|
245 | }
|
246 | }
|
247 |
|
248 | module.exports = ADPCMSoundDecoder;
|