Passed
Branch v8.x-zerodeps (bdf9eb)
by Rafael S.
02:02
created

lib/wavio.js   F

Complexity

Total Complexity 137
Complexity/F 2.74

Size

Lines of Code 1177
Function Count 50

Duplication

Duplicated Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 0
wmc 137
eloc 603
c 1
b 0
f 0
nc 1
mnd 5
bc 132
fnc 50
dl 0
loc 1177
rs 1.997
bpm 2.64
cpm 2.74
noi 3

50 Functions

Rating   Name   Duplication   Size   Complexity  
A wavio.js ➔ getBextBytes_ 0 29 2
A wavio.js ➔ createADPCMHeader_ 0 15 1
A wavio.js ➔ read_ 0 8 1
A wavio.js ➔ readLISTChunk_ 0 20 4
A wavio.js ➔ validateBitDepth_ 0 10 4
A wavio.js ➔ correctContainer_ 0 3 2
A wavio.js ➔ getSmplLoopsBytes_ 0 14 2
A wavio.js ➔ createALawMulawHeader_ 0 11 1
A wavio.js ➔ readFactChunk_ 0 10 2
A wavio.js ➔ getLISTBytes_ 0 15 2
A wavio.js ➔ constructor 0 46 1
B wavio.js ➔ getLISTSubChunksBytes_ 0 33 7
A wavio.js ➔ getDs64Bytes_ 0 22 2
A wavio.js ➔ createWaveFile_ 0 33 3
A wavio.js ➔ readSmplChunk_ 0 28 3
A wavio.js ➔ getCueBytes_ 0 14 2
A wavio.js ➔ updateDataType_ 0 13 2
A wavio.js ➔ getSmplBytes_ 0 22 2
A wavio.js ➔ readCueChunk_ 0 20 3
B wavio.js ➔ enforceBext_ 0 13 7
A wavio.js ➔ readWavBuffer 0 18 1
A wavio.js ➔ readString_ 0 9 2
A wavio.js ➔ readDataChunk_ 0 15 3
A wavio.js ➔ LEorBE_ 0 7 1
A wavio.js ➔ createExtensibleHeader_ 0 13 1
A wavio.js ➔ readFmtChunk_ 0 18 2
A wavio.js ➔ getFactBytes_ 0 11 2
B wavio.js ➔ getDwChannelMask_ 0 21 6
A wavio.js ➔ getFmtBytes_ 0 17 2
A wavio.js ➔ writeString_ 0 10 3
A wavio.js ➔ readZSTR_ 0 12 3
A wavio.js ➔ bitDepthFromFmt_ 0 11 5
B wavio.js ➔ readLISTSubChunks_ 0 31 5
A wavio.js ➔ readFmtExtension_ 0 16 4
A wavio.js ➔ createPCMHeader_ 0 17 2
A wavio.js ➔ realBitDepth_ 0 6 2
A wavio.js ➔ getJunkBytes_ 0 11 2
A wavio.js ➔ readDs64Chunk_ 0 26 3
A wavio.js ➔ getLtxtChunkBytes_ 0 13 1
A wavio.js ➔ readRIFFChunk_ 0 13 3
A wavio.js ➔ truncateSamples 0 13 5
A wavio.js ➔ getFmtExtensionBytes_ 0 24 5
A wavio.js ➔ readBextChunk_ 0 27 2
A wavio.js ➔ readJunkChunk_ 0 13 2
A wavio.js ➔ validateNumChannels_ 0 8 3
B wavio.js ➔ findChunk_ 0 17 6
A wavio.js ➔ clearHeader_ 0 6 1
A wavio.js ➔ validateSampleRate_ 0 9 3
A wavio.js ➔ getCuePointsBytes_ 0 14 2
A wavio.js ➔ validateHeader_ 0 5 1

How to fix   Complexity   

Complexity

Complex classes like lib/wavio.js often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

1
/*
2
 * Copyright (c) 2017-2018 Rafael da Silva Rocha.
3
 *
4
 * Permission is hereby granted, free of charge, to any person obtaining
5
 * a copy of this software and associated documentation files (the
6
 * "Software"), to deal in the Software without restriction, including
7
 * without limitation the rights to use, copy, modify, merge, publish,
8
 * distribute, sublicense, and/or sell copies of the Software, and to
9
 * permit persons to whom the Software is furnished to do so, subject to
10
 * the following conditions:
11
 *
12
 * The above copyright notice and this permission notice shall be
13
 * included in all copies or substantial portions of the Software.
14
 *
15
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
19
 * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
20
 * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
21
 * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22
 *
23
 */
24
25
/**
26
 * @fileoverview A class to read and write wav data.
27
 * @see https://github.com/rochars/wavefile
28
 */
29
30
import riffChunks from './riff-chunks.js';
31
import {pack, unpackFrom, unpackString, packStringTo, packTo,
32
  packString, unpackArray, packArrayTo, unpackArrayTo} from 'byte-data';
0 ignored issues
show
Unused Code introduced by
The variable unpackArray seems to be never used. Consider removing it.
Loading history...
Unused Code introduced by
The variable unpackArrayTo seems to be never used. Consider removing it.
Loading history...
Unused Code introduced by
The variable packArrayTo seems to be never used. Consider removing it.
Loading history...
33
34
// @type {WavStruct}
35
import WavStruct from './wavstruct.js';
36
37
/**
38
 * A class to read and write wav data.
39
 * @extends WavStruct
40
 */
41
export default class WavIO extends WavStruct {
42
43
  constructor() {
44
    super();
45
    /**
46
     * @type {!Object}
47
     * @private
48
     */
49
    this.uInt16_ = {bits: 16, be: false};
50
    /**
51
     * @type {!Object}
52
     * @private
53
     */
54
    this.uInt32_ = {bits: 32, be: false};
55
    /**
56
     * The bit depth code according to the samples.
57
     * @type {string}
58
     */
59
    this.bitDepth = '0';
60
    /**
61
     * Audio formats.
62
     * Formats not listed here will be set to 65534
63
     * and treated as WAVE_FORMAT_EXTENSIBLE
64
     * @enum {number}
65
     * @private
66
     */
67
    this.audioFormats_ = {
68
      '4': 17,
69
      '8': 1,
70
      '8a': 6,
71
      '8m': 7,
72
      '16': 1,
73
      '24': 1,
74
      '32': 1,
75
      '32f': 3,
76
      '64': 3
77
    };
78
    /**
79
     * @type {number}
80
     * @private
81
     */
82
    this.head_ = 0;
83
    /**
84
     * @type {!Object}
85
     * @private
86
     */
87
    this.dataType = {};
88
  }
89
90
  /**
91
   * Write a variable size string as bytes. If the string is smaller
92
   * than the max size the output array is filled with 0s.
93
   * @param {string} str The string to be written as bytes.
94
   * @param {number} maxSize the max size of the string.
95
   * @return {!Array<number>} The bytes.
96
   * @private
97
   */
98
  writeString_(str, maxSize, push=true) {
99
    /** @type {!Array<number>} */   
100
    let bytes = packString(str);
101
    if (push) {
102
      for (let i=bytes.length; i<maxSize; i++) {
103
        bytes.push(0);
104
      }  
105
    }
106
    return bytes;
107
  }
108
109
  /**
110
   * Return the bytes of the 'bext' chunk.
111
   * @return {!Array<number>} The 'bext' chunk bytes.
112
   * @private
113
   */
114
  getBextBytes_() {
115
    /** @type {!Array<number>} */
116
    let bytes = [];
117
    this.enforceBext_();
118
    if (this.bext.chunkId) {
119
      this.bext.chunkSize = 602 + this.bext.codingHistory.length;
120
      bytes = bytes.concat(
121
        packString(this.bext.chunkId),
122
        pack(602 + this.bext.codingHistory.length, this.uInt32_),
123
        this.writeString_(this.bext.description, 256),
124
        this.writeString_(this.bext.originator, 32),
125
        this.writeString_(this.bext.originatorReference, 32),
126
        this.writeString_(this.bext.originationDate, 10),
127
        this.writeString_(this.bext.originationTime, 8),
128
        pack(this.bext.timeReference[0], this.uInt32_),
129
        pack(this.bext.timeReference[1], this.uInt32_),
130
        pack(this.bext.version, this.uInt16_),
131
        this.writeString_(this.bext.UMID, 64),
132
        pack(this.bext.loudnessValue, this.uInt16_),
133
        pack(this.bext.loudnessRange, this.uInt16_),
134
        pack(this.bext.maxTruePeakLevel, this.uInt16_),
135
        pack(this.bext.maxMomentaryLoudness, this.uInt16_),
136
        pack(this.bext.maxShortTermLoudness, this.uInt16_),
137
        this.writeString_(this.bext.reserved, 180),
138
        this.writeString_(
139
          this.bext.codingHistory, this.bext.codingHistory.length));
140
    }
141
    return bytes;
142
  }
143
144
  /**
145
   * Make sure a 'bext' chunk is created if BWF data was created in a file.
146
   * @private
147
   */
148
  enforceBext_() {
149
    for (var prop in this.bext) {
150
      if (this.bext.hasOwnProperty(prop)) {
151
        if (this.bext[prop] && prop != 'timeReference') {
152
          this.bext.chunkId = 'bext';
153
          break;
154
        }
155
      }
156
    }
157
    if (this.bext.timeReference[0] || this.bext.timeReference[1]) {
158
      this.bext.chunkId = 'bext';
159
    }
160
  }
161
162
  /**
163
   * Return the bytes of the 'ds64' chunk.
164
   * @return {!Array<number>} The 'ds64' chunk bytes.
165
   * @private
166
   */
167
  getDs64Bytes_() {
168
    /** @type {!Array<number>} */
169
    let bytes = [];
170
    if (this.ds64.chunkId) {
171
      bytes = bytes.concat(
172
        packString(this.ds64.chunkId),
173
        pack(this.ds64.chunkSize, this.uInt32_),
174
        pack(this.ds64.riffSizeHigh, this.uInt32_),
175
        pack(this.ds64.riffSizeLow, this.uInt32_),
176
        pack(this.ds64.dataSizeHigh, this.uInt32_),
177
        pack(this.ds64.dataSizeLow, this.uInt32_),
178
        pack(this.ds64.originationTime, this.uInt32_),
179
        pack(this.ds64.sampleCountHigh, this.uInt32_),
180
        pack(this.ds64.sampleCountLow, this.uInt32_));
181
    }
182
    //if (this.ds64.tableLength) {
183
    //  ds64Bytes = ds64Bytes.concat(
184
    //    pack(this.ds64.tableLength, this.uInt32_),
185
    //    this.ds64.table);
186
    //}
187
    return bytes;
188
  }
189
190
  /**
191
   * Return the bytes of the 'cue ' chunk.
192
   * @return {!Array<number>} The 'cue ' chunk bytes.
193
   * @private
194
   */
195
  getCueBytes_() {
196
    /** @type {!Array<number>} */
197
    let bytes = [];
198
    if (this.cue.chunkId) {
199
      /** @type {!Array<number>} */
200
      let cuePointsBytes = this.getCuePointsBytes_();
201
      bytes = bytes.concat(
202
        packString(this.cue.chunkId),
203
        pack(cuePointsBytes.length + 4, this.uInt32_),
204
        pack(this.cue.dwCuePoints, this.uInt32_),
205
        cuePointsBytes);
206
    }
207
    return bytes;
208
  }
209
210
  /**
211
   * Return the bytes of the 'cue ' points.
212
   * @return {!Array<number>} The 'cue ' points as an array of bytes.
213
   * @private
214
   */
215
  getCuePointsBytes_() {
216
    /** @type {!Array<number>} */
217
    let points = [];
218
    for (let i=0; i<this.cue.dwCuePoints; i++) {
219
      points = points.concat(
220
        pack(this.cue.points[i].dwName, this.uInt32_),
221
        pack(this.cue.points[i].dwPosition, this.uInt32_),
222
        packString(this.cue.points[i].fccChunk),
223
        pack(this.cue.points[i].dwChunkStart, this.uInt32_),
224
        pack(this.cue.points[i].dwBlockStart, this.uInt32_),
225
        pack(this.cue.points[i].dwSampleOffset, this.uInt32_));
226
    }
227
    return points;
228
  }
229
230
  /**
231
   * Return the bytes of the 'smpl' chunk.
232
   * @return {!Array<number>} The 'smpl' chunk bytes.
233
   * @private
234
   */
235
  getSmplBytes_() {
236
    /** @type {!Array<number>} */
237
    let bytes = [];
238
    if (this.smpl.chunkId) {
239
      /** @type {!Array<number>} */
240
      let smplLoopsBytes = this.getSmplLoopsBytes_();
241
      bytes = bytes.concat(
242
        packString(this.smpl.chunkId),
243
        pack(smplLoopsBytes.length + 36, this.uInt32_),
244
        pack(this.smpl.dwManufacturer, this.uInt32_),
245
        pack(this.smpl.dwProduct, this.uInt32_),
246
        pack(this.smpl.dwSamplePeriod, this.uInt32_),
247
        pack(this.smpl.dwMIDIUnityNote, this.uInt32_),
248
        pack(this.smpl.dwMIDIPitchFraction, this.uInt32_),
249
        pack(this.smpl.dwSMPTEFormat, this.uInt32_),
250
        pack(this.smpl.dwSMPTEOffset, this.uInt32_),
251
        pack(this.smpl.dwNumSampleLoops, this.uInt32_),
252
        pack(this.smpl.dwSamplerData, this.uInt32_),
253
        smplLoopsBytes);
254
    }
255
    return bytes;
256
  }
257
258
  /**
259
   * Return the bytes of the 'smpl' loops.
260
   * @return {!Array<number>} The 'smpl' loops as an array of bytes.
261
   * @private
262
   */
263
  getSmplLoopsBytes_() {
264
    /** @type {!Array<number>} */
265
    let loops = [];
266
    for (let i=0; i<this.smpl.dwNumSampleLoops; i++) {
267
      loops = loops.concat(
268
        pack(this.smpl.loops[i].dwName, this.uInt32_),
269
        pack(this.smpl.loops[i].dwType, this.uInt32_),
270
        pack(this.smpl.loops[i].dwStart, this.uInt32_),
271
        pack(this.smpl.loops[i].dwEnd, this.uInt32_),
272
        pack(this.smpl.loops[i].dwFraction, this.uInt32_),
273
        pack(this.smpl.loops[i].dwPlayCount, this.uInt32_));
274
    }
275
    return loops;
276
  }
277
278
  /**
279
   * Return the bytes of the 'fact' chunk.
280
   * @return {!Array<number>} The 'fact' chunk bytes.
281
   * @private
282
   */
283
  getFactBytes_() {
284
    /** @type {!Array<number>} */
285
    let bytes = [];
286
    if (this.fact.chunkId) {
287
      bytes = bytes.concat(
288
        packString(this.fact.chunkId),
289
        pack(this.fact.chunkSize, this.uInt32_),
290
        pack(this.fact.dwSampleLength, this.uInt32_));
291
    }
292
    return bytes;
293
  }
294
295
  /**
296
   * Return the bytes of the 'fmt ' chunk.
297
   * @return {!Array<number>} The 'fmt' chunk bytes.
298
   * @throws {Error} if no 'fmt ' chunk is present.
299
   * @private
300
   */
301
  getFmtBytes_() {
302
    /** @type {!Array<number>} */
303
    let fmtBytes = [];
304
    if (this.fmt.chunkId) {
305
      return fmtBytes.concat(
306
        packString(this.fmt.chunkId),
307
        pack(this.fmt.chunkSize, this.uInt32_),
308
        pack(this.fmt.audioFormat, this.uInt16_),
309
        pack(this.fmt.numChannels, this.uInt16_),
310
        pack(this.fmt.sampleRate, this.uInt32_),
311
        pack(this.fmt.byteRate, this.uInt32_),
312
        pack(this.fmt.blockAlign, this.uInt16_),
313
        pack(this.fmt.bitsPerSample, this.uInt16_),
314
        this.getFmtExtensionBytes_());
315
    }
316
    throw Error('Could not find the "fmt " chunk');
317
  }
318
319
  /**
320
   * Return the bytes of the fmt extension fields.
321
   * @return {!Array<number>} The fmt extension bytes.
322
   * @private
323
   */
324
  getFmtExtensionBytes_() {
325
    /** @type {!Array<number>} */
326
    let extension = [];
327
    if (this.fmt.chunkSize > 16) {
328
      extension = extension.concat(
329
        pack(this.fmt.cbSize, this.uInt16_));
330
    }
331
    if (this.fmt.chunkSize > 18) {
332
      extension = extension.concat(
333
        pack(this.fmt.validBitsPerSample, this.uInt16_));
334
    }
335
    if (this.fmt.chunkSize > 20) {
336
      extension = extension.concat(
337
        pack(this.fmt.dwChannelMask, this.uInt32_));
338
    }
339
    if (this.fmt.chunkSize > 24) {
340
      extension = extension.concat(
341
        pack(this.fmt.subformat[0], this.uInt32_),
342
        pack(this.fmt.subformat[1], this.uInt32_),
343
        pack(this.fmt.subformat[2], this.uInt32_),
344
        pack(this.fmt.subformat[3], this.uInt32_));
345
    }
346
    return extension;
347
  }
348
349
  /**
350
   * Return the bytes of the 'LIST' chunk.
351
   * @return {!Array<number>} The 'LIST' chunk bytes.
352
   */
353
  getLISTBytes_() {
354
    /** @type {!Array<number>} */
355
    let bytes = [];
356
    for (let i=0; i<this.LIST.length; i++) {
357
      /** @type {!Array<number>} */
358
      let subChunksBytes = this.getLISTSubChunksBytes_(
359
          this.LIST[i].subChunks, this.LIST[i].format);
360
      bytes = bytes.concat(
361
        packString(this.LIST[i].chunkId),
362
        pack(subChunksBytes.length + 4, this.uInt32_),
363
        packString(this.LIST[i].format),
364
        subChunksBytes);
365
    }
366
    return bytes;
367
  }
368
369
  /**
370
   * Return the bytes of the sub chunks of a 'LIST' chunk.
371
   * @param {!Array<!Object>} subChunks The 'LIST' sub chunks.
372
   * @param {string} format The format of the 'LIST' chunk.
373
   *    Currently supported values are 'adtl' or 'INFO'.
374
   * @return {!Array<number>} The sub chunk bytes.
375
   * @private
376
   */
377
  getLISTSubChunksBytes_(subChunks, format) {
378
    /** @type {!Array<number>} */
379
    let bytes = [];
380
    for (let i=0; i<subChunks.length; i++) {
381
      if (format == 'INFO') {
382
        bytes = bytes.concat(
383
          packString(subChunks[i].chunkId),
384
          pack(subChunks[i].value.length + 1, this.uInt32_),
385
          this.writeString_(
386
            subChunks[i].value, subChunks[i].value.length));
387
        bytes.push(0);
388
      } else if (format == 'adtl') {
389
        if (['labl', 'note'].indexOf(subChunks[i].chunkId) > -1) {
390
          bytes = bytes.concat(
391
            packString(subChunks[i].chunkId),
392
            pack(
393
              subChunks[i].value.length + 4 + 1, this.uInt32_),
394
            pack(subChunks[i].dwName, this.uInt32_),
395
            this.writeString_(
396
              subChunks[i].value,
397
              subChunks[i].value.length));
398
          bytes.push(0);
399
        } else if (subChunks[i].chunkId == 'ltxt') {
400
          bytes = bytes.concat(
401
            this.getLtxtChunkBytes_(subChunks[i]));
402
        }
403
      }
404
      if (bytes.length % 2) {
405
        bytes.push(0);
406
      }
407
    }
408
    return bytes;
409
  }
410
411
  /**
412
   * Return the bytes of a 'ltxt' chunk.
413
   * @param {!Object} ltxt the 'ltxt' chunk.
414
   * @return {!Array<number>} The 'ltxt' chunk bytes.
415
   * @private
416
   */
417
  getLtxtChunkBytes_(ltxt) {
418
    return [].concat(
419
      packString(ltxt.chunkId),
420
      pack(ltxt.value.length + 20, this.uInt32_),
421
      pack(ltxt.dwName, this.uInt32_),
422
      pack(ltxt.dwSampleLength, this.uInt32_),
423
      pack(ltxt.dwPurposeID, this.uInt32_),
424
      pack(ltxt.dwCountry, this.uInt16_),
425
      pack(ltxt.dwLanguage, this.uInt16_),
426
      pack(ltxt.dwDialect, this.uInt16_),
427
      pack(ltxt.dwCodePage, this.uInt16_),
428
      this.writeString_(ltxt.value, ltxt.value.length));
429
  }
430
431
  /**
432
   * Return the bytes of the 'junk' chunk.
433
   * @return {!Array<number>} The 'junk' chunk bytes.
434
   * @private
435
   */
436
  getJunkBytes_() {
437
    /** @type {!Array<number>} */
438
    let bytes = [];
439
    if (this.junk.chunkId) {
440
      return bytes.concat(
441
        packString(this.junk.chunkId),
442
        pack(this.junk.chunkData.length, this.uInt32_),
443
        this.junk.chunkData);
444
    }
445
    return bytes;
446
  }
447
448
  /**
449
   * Return 'RIFF' if the container is 'RF64', the current container name
450
   * otherwise. Used to enforce 'RIFF' when RF64 is not allowed.
451
   * @return {string}
452
   * @private
453
   */
454
  correctContainer_() {
455
    return this.container == 'RF64' ? 'RIFF' : this.container;
456
  }
457
458
  /**
459
   * Set the string code of the bit depth based on the 'fmt ' chunk.
460
   * @private
461
   */
462
  bitDepthFromFmt_() {
463
    if (this.fmt.audioFormat === 3 && this.fmt.bitsPerSample === 32) {
464
      this.bitDepth = '32f';
465
    } else if (this.fmt.audioFormat === 6) {
466
      this.bitDepth = '8a';
467
    } else if (this.fmt.audioFormat === 7) {
468
      this.bitDepth = '8m';
469
    } else {
470
      this.bitDepth = this.fmt.bitsPerSample.toString();
471
    }
472
  }
473
474
  /**
475
   * Return a .wav file byte buffer with the data from the WaveFile object.
476
   * The return value of this method can be written straight to disk.
477
   * @return {!Uint8Array} The wav file bytes.
478
   * @private
479
   */
480
  createWaveFile_() {
481
    /** @type {!Array<!Array<number>>} */
482
    let fileBody = [
483
      this.getJunkBytes_(),
484
      this.getDs64Bytes_(),
485
      this.getBextBytes_(),
486
      this.getFmtBytes_(),
487
      this.getFactBytes_(),
488
      packString(this.data.chunkId),
489
      pack(this.data.samples.length, this.uInt32_),
490
      this.data.samples,
491
      this.getCueBytes_(),
492
      this.getSmplBytes_(),
493
      this.getLISTBytes_()
494
    ];
495
    /** @type {number} */
496
    let fileBodyLength = 0;
497
    for (let i=0; i<fileBody.length; i++) {
498
      fileBodyLength += fileBody[i].length;
499
    }
500
    /** @type {!Uint8Array} */
501
    let file = new Uint8Array(fileBodyLength + 12);
502
    /** @type {number} */
503
    let index = 0;
504
    index = packStringTo(this.container, file, index);
505
    index = packTo(fileBodyLength + 4, this.uInt32_, file, index);
506
    index = packStringTo(this.format, file, index);
507
    for (let i=0; i<fileBody.length; i++) {
508
      file.set(fileBody[i], index);
509
      index += fileBody[i].length;
510
    }
511
    return file;
512
  }
513
514
  /**
515
   * Update the type definition used to read and write the samples.
516
   * @private
517
   */
518
  updateDataType_() {
519
    /** @type {!Object} */
520
    this.dataType = {
521
      bits: ((parseInt(this.bitDepth, 10) - 1) | 7) + 1,
522
      float: this.bitDepth == '32f' || this.bitDepth == '64',
523
      signed: this.bitDepth != '8',
524
      be: this.container == 'RIFX'
525
    };
526
    if (['4', '8a', '8m'].indexOf(this.bitDepth) > -1 ) {
527
      this.dataType.bits = 8;
528
      this.dataType.signed = false;
529
    }
530
  }
531
532
  /**
533
   * Set up the WaveFile object from a byte buffer.
534
   * @param {!Uint8Array} buffer The buffer.
535
   * @param {boolean=} samples True if the samples should be loaded.
536
   * @throws {Error} If container is not RIFF, RIFX or RF64.
537
   * @throws {Error} If no 'fmt ' chunk is found.
538
   * @throws {Error} If no 'data' chunk is found.
539
   */
540
  readWavBuffer(buffer, samples=true) {
541
    this.head_ = 0;
542
    this.clearHeader_();
543
    this.readRIFFChunk_(buffer);
544
    /** @type {!Object} */
545
    let chunk = riffChunks(buffer);
546
    this.readDs64Chunk_(buffer, chunk.subChunks);
547
    this.readFmtChunk_(buffer, chunk.subChunks);
548
    this.readFactChunk_(buffer, chunk.subChunks);
549
    this.readBextChunk_(buffer, chunk.subChunks);
550
    this.readCueChunk_(buffer, chunk.subChunks);
551
    this.readSmplChunk_(buffer, chunk.subChunks);
552
    this.readDataChunk_(buffer, chunk.subChunks, samples);
553
    this.readJunkChunk_(buffer, chunk.subChunks);
554
    this.readLISTChunk_(buffer, chunk.subChunks);
555
    this.bitDepthFromFmt_();
556
    this.updateDataType_();
557
  }
558
559
  /**
560
   * Create the header of a ADPCM wave file.
561
   * @param {string} bitDepthCode The audio bit depth
562
   * @param {number} numChannels The number of channels
563
   * @param {number} sampleRate The sample rate.
564
   * @param {number} numBytes The number of bytes each sample use.
565
   * @param {!Object} options The extra options, like container defintion.
566
   * @private
567
   */
568
  createADPCMHeader_(bitDepthCode, numChannels, sampleRate, numBytes, options) {
569
    this.createPCMHeader_(
570
      bitDepthCode, numChannels, sampleRate, numBytes, options);
571
    this.chunkSize = 40 + this.data.samples.length;
572
    this.fmt.chunkSize = 20;
573
    this.fmt.byteRate = 4055;
574
    this.fmt.blockAlign = 256;
575
    this.fmt.bitsPerSample = 4;
576
    this.fmt.cbSize = 2;
577
    this.fmt.validBitsPerSample = 505;
578
    this.fact.chunkId = 'fact';
579
    this.fact.chunkSize = 4;
580
    this.fact.dwSampleLength = this.data.samples.length * 2;
581
    this.data.chunkSize = this.data.samples.length;
582
  }
583
584
  /**
585
   * Create the header of WAVE_FORMAT_EXTENSIBLE file.
586
   * @param {string} bitDepthCode The audio bit depth
587
   * @param {number} numChannels The number of channels
588
   * @param {number} sampleRate The sample rate.
589
   * @param {number} numBytes The number of bytes each sample use.
590
   * @param {!Object} options The extra options, like container defintion.
591
   * @private
592
   */
593
  createExtensibleHeader_(
594
      bitDepthCode, numChannels, sampleRate, numBytes, options) {
595
    this.createPCMHeader_(
596
      bitDepthCode, numChannels, sampleRate, numBytes, options);
597
    this.chunkSize = 36 + 24 + this.data.samples.length;
598
    this.fmt.chunkSize = 40;
599
    this.fmt.bitsPerSample = ((parseInt(bitDepthCode, 10) - 1) | 7) + 1;
600
    this.fmt.cbSize = 22;
601
    this.fmt.validBitsPerSample = parseInt(bitDepthCode, 10);
602
    this.fmt.dwChannelMask = this.getDwChannelMask_();
603
    // subformat 128-bit GUID as 4 32-bit values
604
    // only supports uncompressed integer PCM samples
605
    this.fmt.subformat = [1, 1048576, 2852126848, 1905997824];
606
  }
607
608
  /**
609
   * Get the value for dwChannelMask according to the number of channels.
610
   * @return {number} the dwChannelMask value.
611
   * @private
612
   */
613
  getDwChannelMask_() {
614
    /** @type {number} */
615
    let dwChannelMask = 0;
616
    // mono = FC
617
    if (this.fmt.numChannels === 1) {
618
      dwChannelMask = 0x4;
619
    // stereo = FL, FR
620
    } else if (this.fmt.numChannels === 2) {
621
      dwChannelMask = 0x3;
622
    // quad = FL, FR, BL, BR
623
    } else if (this.fmt.numChannels === 4) {
624
      dwChannelMask = 0x33;
625
    // 5.1 = FL, FR, FC, LF, BL, BR
626
    } else if (this.fmt.numChannels === 6) {
627
      dwChannelMask = 0x3F;
628
    // 7.1 = FL, FR, FC, LF, BL, BR, SL, SR
629
    } else if (this.fmt.numChannels === 8) {
630
      dwChannelMask = 0x63F;
631
    }
632
    return dwChannelMask;
633
  }
634
635
  /**
636
   * Create the header of mu-Law and A-Law wave files.
637
   * @param {string} bitDepthCode The audio bit depth
638
   * @param {number} numChannels The number of channels
639
   * @param {number} sampleRate The sample rate.
640
   * @param {number} numBytes The number of bytes each sample use.
641
   * @param {!Object} options The extra options, like container defintion.
642
   * @private
643
   */
644
  createALawMulawHeader_(
645
      bitDepthCode, numChannels, sampleRate, numBytes, options) {
646
    this.createPCMHeader_(
647
      bitDepthCode, numChannels, sampleRate, numBytes, options);
648
    this.chunkSize = 40 + this.data.samples.length;
649
    this.fmt.chunkSize = 20;
650
    this.fmt.cbSize = 2;
651
    this.fmt.validBitsPerSample = 8;
652
    this.fact.chunkId = 'fact';
653
    this.fact.chunkSize = 4;
654
    this.fact.dwSampleLength = this.data.samples.length;
655
  }
656
657
  /**
658
   * Create the header of a linear PCM wave file.
659
   * @param {string} bitDepthCode The audio bit depth
660
   * @param {number} numChannels The number of channels
661
   * @param {number} sampleRate The sample rate.
662
   * @param {number} numBytes The number of bytes each sample use.
663
   * @param {!Object} options The extra options, like container defintion.
664
   * @private
665
   */
666
  createPCMHeader_(bitDepthCode, numChannels, sampleRate, numBytes, options) {
667
    this.clearHeader_();
668
    this.container = options.container;
669
    this.chunkSize = 36 + this.data.samples.length;
670
    this.format = 'WAVE';
671
    this.fmt.chunkId = 'fmt ';
672
    this.fmt.chunkSize = 16;
673
    this.fmt.byteRate = (numChannels * numBytes) * sampleRate;
674
    this.fmt.blockAlign = numChannels * numBytes;
675
    this.fmt.audioFormat = this.audioFormats_[bitDepthCode] ?
676
      this.audioFormats_[bitDepthCode] : 65534;
677
    this.fmt.numChannels = numChannels;
678
    this.fmt.sampleRate = sampleRate;
679
    this.fmt.bitsPerSample = parseInt(bitDepthCode, 10);
680
    this.fmt.cbSize = 0;
681
    this.fmt.validBitsPerSample = 0;
682
  }
683
684
  /**
685
   * Return the closest greater number of bits for a number of bits that
686
   * do not fill a full sequence of bytes.
687
   * @param {string} bitDepthCode The bit depth.
688
   * @return {string}
689
   * @private
690
   */
691
  realBitDepth_(bitDepthCode) {
692
    if (bitDepthCode != '32f') {
693
      bitDepthCode = (((parseInt(bitDepthCode, 10) - 1) | 7) + 1).toString();
694
    }
695
    return bitDepthCode;
696
  }
697
698
  /**
699
   * Validate the header of the file.
700
   * @throws {Error} If any property of the object appears invalid.
701
   * @private
702
   */
703
  validateHeader_() {
704
    this.validateBitDepth_();
705
    this.validateNumChannels_();
706
    this.validateSampleRate_();
707
  }
708
709
  /**
710
   * Validate the bit depth.
711
   * @return {boolean} True is the bit depth is valid.
712
   * @throws {Error} If bit depth is invalid.
713
   * @private
714
   */
715
  validateBitDepth_() {
716
    if (!this.audioFormats_[this.bitDepth]) {
717
      if (parseInt(this.bitDepth, 10) > 8 &&
718
          parseInt(this.bitDepth, 10) < 54) {
719
        return true;
720
      }
721
      throw new Error('Invalid bit depth.');
722
    }
723
    return true;
724
  }
725
726
  /**
727
   * Validate the number of channels.
728
   * @return {boolean} True is the number of channels is valid.
729
   * @throws {Error} If the number of channels is invalid.
730
   * @private
731
   */
732
  validateNumChannels_() {
733
    /** @type {number} */
734
    let blockAlign = this.fmt.numChannels * this.fmt.bitsPerSample / 8;
735
    if (this.fmt.numChannels < 1 || blockAlign > 65535) {
736
      throw new Error('Invalid number of channels.');
737
    }
738
    return true;
739
  }
740
741
  /**
742
   * Validate the sample rate value.
743
   * @return {boolean} True is the sample rate is valid.
744
   * @throws {Error} If the sample rate is invalid.
745
   * @private
746
   */
747
  validateSampleRate_() {
748
    /** @type {number} */
749
    let byteRate = this.fmt.numChannels *
750
      (this.fmt.bitsPerSample / 8) * this.fmt.sampleRate;
751
    if (this.fmt.sampleRate < 1 || byteRate > 4294967295) {
752
      throw new Error('Invalid sample rate.');
753
    }
754
    return true;
755
  }
756
757
  /**
758
   * Reset attributes that should emptied when a file is
759
   * created with the fromScratch() or fromBuffer() methods.
760
   * @private
761
   */
762
  clearHeader_() {
763
    this.fmt.cbSize = 0;
764
    this.fmt.validBitsPerSample = 0;
765
    this.fact.chunkId = '';
766
    this.ds64.chunkId = '';
767
  }
768
769
  /**
770
   * Set up to work wih big-endian or little-endian files.
771
   * The types used are changed to LE or BE. If the
772
   * the file is big-endian (RIFX), true is returned.
773
   * @return {boolean} True if the file is RIFX.
774
   * @private
775
   */
776
  LEorBE_() {
777
    /** @type {boolean} */
778
    let bigEndian = this.container === 'RIFX';
779
    this.uInt16_.be = bigEndian;
780
    this.uInt32_.be = bigEndian;
781
    return bigEndian;
782
  }
783
784
  /**
785
   * Find a chunk by its fourCC_ in a array of RIFF chunks.
786
   * @param {!Object} chunks The wav file chunks.
787
   * @param {string} chunkId The chunk fourCC_.
788
   * @param {boolean} multiple True if there may be multiple chunks
789
   *    with the same chunkId.
790
   * @return {?Array<!Object>}
791
   * @private
792
   */
793
  findChunk_(chunks, chunkId, multiple=false) {
794
    /** @type {!Array<!Object>} */
795
    let chunk = [];
796
    for (let i=0; i<chunks.length; i++) {
797
      if (chunks[i].chunkId == chunkId) {
798
        if (multiple) {
799
          chunk.push(chunks[i]);
800
        } else {
801
          return chunks[i];
802
        }
803
      }
804
    }
805
    if (chunkId == 'LIST') {
806
      return chunk.length ? chunk : null;
807
    }
808
    return null;
809
  }
810
811
  /**
812
   * Read the RIFF chunk a wave file.
813
   * @param {!Uint8Array} bytes A wav buffer.
814
   * @throws {Error} If no 'RIFF' chunk is found.
815
   * @private
816
   */
817
  readRIFFChunk_(bytes) {
818
    this.head_ = 0;
819
    this.container = this.readString_(bytes, 4);
820
    if (['RIFF', 'RIFX', 'RF64'].indexOf(this.container) === -1) {
821
      throw Error('Not a supported format.');
822
    }
823
    this.LEorBE_();
824
    this.chunkSize = this.read_(bytes, this.uInt32_);
825
    this.format = this.readString_(bytes, 4);
826
    if (this.format != 'WAVE') {
827
      throw Error('Could not find the "WAVE" format identifier');
828
    }
829
  }
830
831
  /**
832
   * Read the 'fmt ' chunk of a wave file.
833
   * @param {!Uint8Array} buffer The wav file buffer.
834
   * @param {!Object} signature The file signature.
835
   * @throws {Error} If no 'fmt ' chunk is found.
836
   * @private
837
   */
838
  readFmtChunk_(buffer, signature) {
839
    /** @type {?Object} */
840
    let chunk = this.findChunk_(signature, 'fmt ');
841
    if (chunk) {
842
      this.head_ = chunk.chunkData.start;
843
      this.fmt.chunkId = chunk.chunkId;
844
      this.fmt.chunkSize = chunk.chunkSize;
845
      this.fmt.audioFormat = this.read_(buffer, this.uInt16_);
846
      this.fmt.numChannels = this.read_(buffer, this.uInt16_);
847
      this.fmt.sampleRate = this.read_(buffer, this.uInt32_);
848
      this.fmt.byteRate = this.read_(buffer, this.uInt32_);
849
      this.fmt.blockAlign = this.read_(buffer, this.uInt16_);
850
      this.fmt.bitsPerSample = this.read_(buffer, this.uInt16_);
851
      this.readFmtExtension_(buffer);
852
    } else {
853
      throw Error('Could not find the "fmt " chunk');
854
    }
855
  }
856
857
  /**
858
   * Read the 'fmt ' chunk extension.
859
   * @param {!Uint8Array} buffer The wav file buffer.
860
   * @private
861
   */
862
  readFmtExtension_(buffer) {
863
    if (this.fmt.chunkSize > 16) {
864
      this.fmt.cbSize = this.read_(buffer, this.uInt16_);
865
      if (this.fmt.chunkSize > 18) {
866
        this.fmt.validBitsPerSample = this.read_(buffer, this.uInt16_);
867
        if (this.fmt.chunkSize > 20) {
868
          this.fmt.dwChannelMask = this.read_(buffer, this.uInt32_);
869
          this.fmt.subformat = [
870
            this.read_(buffer, this.uInt32_),
871
            this.read_(buffer, this.uInt32_),
872
            this.read_(buffer, this.uInt32_),
873
            this.read_(buffer, this.uInt32_)];
874
        }
875
      }
876
    }
877
  }
878
879
  /**
880
   * Read the 'fact' chunk of a wav file.
881
   * @param {!Uint8Array} buffer The wav file buffer.
882
   * @param {!Object} signature The file signature.
883
   * @private
884
   */
885
  readFactChunk_(buffer, signature) {
886
    /** @type {?Object} */
887
    let chunk = this.findChunk_(signature, 'fact');
888
    if (chunk) {
889
      this.head_ = chunk.chunkData.start;
890
      this.fact.chunkId = chunk.chunkId;
891
      this.fact.chunkSize = chunk.chunkSize;
892
      this.fact.dwSampleLength = this.read_(buffer, this.uInt32_);
893
    }
894
  }
895
896
  /**
897
   * Read the 'cue ' chunk of a wave file.
898
   * @param {!Uint8Array} buffer The wav file buffer.
899
   * @param {!Object} signature The file signature.
900
   * @private
901
   */
902
  readCueChunk_(buffer, signature) {
903
    /** @type {?Object} */
904
    let chunk = this.findChunk_(signature, 'cue ');
905
    if (chunk) {
906
      this.head_ = chunk.chunkData.start;
907
      this.cue.chunkId = chunk.chunkId;
908
      this.cue.chunkSize = chunk.chunkSize;
909
      this.cue.dwCuePoints = this.read_(buffer, this.uInt32_);
910
      for (let i=0; i<this.cue.dwCuePoints; i++) {
911
        this.cue.points.push({
912
          dwName: this.read_(buffer, this.uInt32_),
913
          dwPosition: this.read_(buffer, this.uInt32_),
914
          fccChunk: this.readString_(buffer, 4),
915
          dwChunkStart: this.read_(buffer, this.uInt32_),
916
          dwBlockStart: this.read_(buffer, this.uInt32_),
917
          dwSampleOffset: this.read_(buffer, this.uInt32_),
918
        });
919
      }
920
    }
921
  }
922
923
  /**
924
   * Read the 'smpl' chunk of a wave file.
925
   * @param {!Uint8Array} buffer The wav file buffer.
926
   * @param {!Object} signature The file signature.
927
   * @private
928
   */
929
  readSmplChunk_(buffer, signature) {
930
    /** @type {?Object} */
931
    let chunk = this.findChunk_(signature, 'smpl');
932
    if (chunk) {
933
      this.head_ = chunk.chunkData.start;
934
      this.smpl.chunkId = chunk.chunkId;
935
      this.smpl.chunkSize = chunk.chunkSize;
936
      this.smpl.dwManufacturer = this.read_(buffer, this.uInt32_);
937
      this.smpl.dwProduct = this.read_(buffer, this.uInt32_);
938
      this.smpl.dwSamplePeriod = this.read_(buffer, this.uInt32_);
939
      this.smpl.dwMIDIUnityNote = this.read_(buffer, this.uInt32_);
940
      this.smpl.dwMIDIPitchFraction = this.read_(buffer, this.uInt32_);
941
      this.smpl.dwSMPTEFormat = this.read_(buffer, this.uInt32_);
942
      this.smpl.dwSMPTEOffset = this.read_(buffer, this.uInt32_);
943
      this.smpl.dwNumSampleLoops = this.read_(buffer, this.uInt32_);
944
      this.smpl.dwSamplerData = this.read_(buffer, this.uInt32_);
945
      for (let i=0; i<this.smpl.dwNumSampleLoops; i++) {
946
        this.smpl.loops.push({
947
          dwName: this.read_(buffer, this.uInt32_),
948
          dwType: this.read_(buffer, this.uInt32_),
949
          dwStart: this.read_(buffer, this.uInt32_),
950
          dwEnd: this.read_(buffer, this.uInt32_),
951
          dwFraction: this.read_(buffer, this.uInt32_),
952
          dwPlayCount: this.read_(buffer, this.uInt32_),
953
        });
954
      }
955
    }
956
  }
957
958
  /**
959
   * Read the 'data' chunk of a wave file.
960
   * @param {!Uint8Array} buffer The wav file buffer.
961
   * @param {!Object} signature The file signature.
962
   * @param {boolean} samples True if the samples should be loaded.
963
   * @throws {Error} If no 'data' chunk is found.
964
   * @private
965
   */
966
  readDataChunk_(buffer, signature, samples) {
967
    /** @type {?Object} */
968
    let chunk = this.findChunk_(signature, 'data');
969
    if (chunk) {
970
      this.data.chunkId = 'data';
971
      this.data.chunkSize = chunk.chunkSize;
972
      if (samples) {
973
        this.data.samples = buffer.slice(
974
          chunk.chunkData.start,
975
          chunk.chunkData.end);
976
      }
977
    } else {
978
      throw Error('Could not find the "data" chunk');
979
    }
980
  }
981
982
  /**
983
   * Read the 'bext' chunk of a wav file.
984
   * @param {!Uint8Array} buffer The wav file buffer.
985
   * @param {!Object} signature The file signature.
986
   * @private
987
   */
988
  readBextChunk_(buffer, signature) {
989
    /** @type {?Object} */
990
    let chunk = this.findChunk_(signature, 'bext');
991
    if (chunk) {
992
      this.head_ = chunk.chunkData.start;
993
      this.bext.chunkId = chunk.chunkId;
994
      this.bext.chunkSize = chunk.chunkSize;
995
      this.bext.description = this.readString_(buffer, 256);
996
      this.bext.originator = this.readString_(buffer, 32);
997
      this.bext.originatorReference = this.readString_(buffer, 32);
998
      this.bext.originationDate = this.readString_(buffer, 10);
999
      this.bext.originationTime = this.readString_(buffer, 8);
1000
      this.bext.timeReference = [
1001
        this.read_(buffer, this.uInt32_),
1002
        this.read_(buffer, this.uInt32_)];
1003
      this.bext.version = this.read_(buffer, this.uInt16_);
1004
      this.bext.UMID = this.readString_(buffer, 64);
1005
      this.bext.loudnessValue = this.read_(buffer, this.uInt16_);
1006
      this.bext.loudnessRange = this.read_(buffer, this.uInt16_);
1007
      this.bext.maxTruePeakLevel = this.read_(buffer, this.uInt16_);
1008
      this.bext.maxMomentaryLoudness = this.read_(buffer, this.uInt16_);
1009
      this.bext.maxShortTermLoudness = this.read_(buffer, this.uInt16_);
1010
      this.bext.reserved = this.readString_(buffer, 180);
1011
      this.bext.codingHistory = this.readString_(
1012
        buffer, this.bext.chunkSize - 602);
1013
    }
1014
  }
1015
1016
  /**
1017
   * Read the 'ds64' chunk of a wave file.
1018
   * @param {!Uint8Array} buffer The wav file buffer.
1019
   * @param {!Object} signature The file signature.
1020
   * @throws {Error} If no 'ds64' chunk is found and the file is RF64.
1021
   * @private
1022
   */
1023
  readDs64Chunk_(buffer, signature) {
1024
    /** @type {?Object} */
1025
    let chunk = this.findChunk_(signature, 'ds64');
1026
    if (chunk) {
1027
      this.head_ = chunk.chunkData.start;
1028
      this.ds64.chunkId = chunk.chunkId;
1029
      this.ds64.chunkSize = chunk.chunkSize;
1030
      this.ds64.riffSizeHigh = this.read_(buffer, this.uInt32_);
1031
      this.ds64.riffSizeLow = this.read_(buffer, this.uInt32_);
1032
      this.ds64.dataSizeHigh = this.read_(buffer, this.uInt32_);
1033
      this.ds64.dataSizeLow = this.read_(buffer, this.uInt32_);
1034
      this.ds64.originationTime = this.read_(buffer, this.uInt32_);
1035
      this.ds64.sampleCountHigh = this.read_(buffer, this.uInt32_);
1036
      this.ds64.sampleCountLow = this.read_(buffer, this.uInt32_);
1037
      //if (this.ds64.chunkSize > 28) {
1038
      //  this.ds64.tableLength = unpack(
1039
      //    chunkData.slice(28, 32), this.uInt32_);
1040
      //  this.ds64.table = chunkData.slice(
1041
      //     32, 32 + this.ds64.tableLength); 
1042
      //}
1043
    } else {
1044
      if (this.container == 'RF64') {
1045
        throw Error('Could not find the "ds64" chunk');  
1046
      }
1047
    }
1048
  }
1049
1050
  /**
1051
   * Read the 'LIST' chunks of a wave file.
1052
   * @param {!Uint8Array} buffer The wav file buffer.
1053
   * @param {!Object} signature The file signature.
1054
   * @private
1055
   */
1056
  readLISTChunk_(buffer, signature) {
1057
    /** @type {?Object} */
1058
    let listChunks = this.findChunk_(signature, 'LIST', true);
1059
    if (listChunks === null) {
1060
      return;
1061
    }
1062
    for (let j=0; j < listChunks.length; j++) {
1063
      /** @type {!Object} */
1064
      let subChunk = listChunks[j];
1065
      this.LIST.push({
1066
        chunkId: subChunk.chunkId,
1067
        chunkSize: subChunk.chunkSize,
1068
        format: subChunk.format,
1069
        subChunks: []});
1070
      for (let x=0; x<subChunk.subChunks.length; x++) {
1071
        this.readLISTSubChunks_(subChunk.subChunks[x],
1072
          subChunk.format, buffer);
1073
      }
1074
    }
1075
  }
1076
1077
  /**
1078
   * Read the sub chunks of a 'LIST' chunk.
1079
   * @param {!Object} subChunk The 'LIST' subchunks.
1080
   * @param {string} format The 'LIST' format, 'adtl' or 'INFO'.
1081
   * @param {!Uint8Array} buffer The wav file buffer.
1082
   * @private
1083
   */
1084
  readLISTSubChunks_(subChunk, format, buffer) {
1085
    if (format == 'adtl') {
1086
      if (['labl', 'note','ltxt'].indexOf(subChunk.chunkId) > -1) {
1087
        this.head_ = subChunk.chunkData.start;
1088
        /** @type {!Object<string, string|number>} */
1089
        let item = {
1090
          chunkId: subChunk.chunkId,
1091
          chunkSize: subChunk.chunkSize,
1092
          dwName: this.read_(buffer, this.uInt32_)
1093
        };
1094
        if (subChunk.chunkId == 'ltxt') {
1095
          item.dwSampleLength = this.read_(buffer, this.uInt32_);
1096
          item.dwPurposeID = this.read_(buffer, this.uInt32_);
1097
          item.dwCountry = this.read_(buffer, this.uInt16_);
1098
          item.dwLanguage = this.read_(buffer, this.uInt16_);
1099
          item.dwDialect = this.read_(buffer, this.uInt16_);
1100
          item.dwCodePage = this.read_(buffer, this.uInt16_);
1101
        }
1102
        item.value = this.readZSTR_(buffer, this.head_);
1103
        this.LIST[this.LIST.length - 1].subChunks.push(item);
1104
      }
1105
    // RIFF INFO tags like ICRD, ISFT, ICMT
1106
    } else if(format == 'INFO') {
1107
      this.head_ = subChunk.chunkData.start;
1108
      this.LIST[this.LIST.length - 1].subChunks.push({
1109
        chunkId: subChunk.chunkId,
1110
        chunkSize: subChunk.chunkSize,
1111
        value: this.readZSTR_(buffer,  this.head_)
1112
      });
1113
    }
1114
  }
1115
1116
  /**
1117
   * Read the 'junk' chunk of a wave file.
1118
   * @param {!Uint8Array} buffer The wav file buffer.
1119
   * @param {!Object} signature The file signature.
1120
   * @private
1121
   */
1122
  readJunkChunk_(buffer, signature) {
1123
    /** @type {?Object} */
1124
    let chunk = this.findChunk_(signature, 'junk');
1125
    if (chunk) {
1126
      this.junk = {
1127
        chunkId: chunk.chunkId,
1128
        chunkSize: chunk.chunkSize,
1129
        chunkData: [].slice.call(buffer.slice(
1130
          chunk.chunkData.start,
1131
          chunk.chunkData.end))
1132
      };
1133
    }
1134
  }
1135
1136
  /**
1137
   * Read bytes as a ZSTR string.
1138
   * @param {!Uint8Array} bytes The bytes.
1139
   * @return {string} The string.
1140
   * @private
1141
   */
1142
  readZSTR_(bytes, index=0) {
1143
    /** @type {string} */
1144
    let str = '';
1145
    for (let i=index; i<bytes.length; i++) {
1146
      this.head_++;
1147
      if (bytes[i] === 0) {
1148
        break;
1149
      }
1150
      str += unpackString(bytes, i, 1);
1151
    }
1152
    return str;
1153
  }
1154
1155
  /**
1156
   * Read bytes as a string from a RIFF chunk.
1157
   * @param {!Uint8Array} bytes The bytes.
1158
   * @param {number} maxSize the max size of the string.
1159
   * @return {string} The string.
1160
   * @private
1161
   */
1162
  readString_(bytes, maxSize) {
1163
    /** @type {string} */
1164
    let str = '';
1165
    for (let i=0; i<maxSize; i++) {
1166
      str += unpackString(bytes, this.head_, 1);
1167
      this.head_++;
1168
    }
1169
    return str;
1170
  }
1171
1172
  /**
1173
   * Read a number from a chunk.
1174
   * @param {!Uint8Array} bytes The chunk bytes.
1175
   * @param {!Object} bdType The type definition.
1176
   * @return {number} The number.
1177
   * @private
1178
   */
1179
  read_(bytes, bdType) {
1180
    /** @type {number} */
1181
    let size = bdType.bits / 8;
1182
    /** @type {number} */
1183
    let value = unpackFrom(bytes, bdType, this.head_);
1184
    this.head_ += size;
1185
    return value;
1186
  }
1187
1188
1189
  /**
1190
   * Truncate float samples on over and underflow.
1191
   * @private
1192
   */
1193
  truncateSamples(samples) {
1194
    if (this.fmt.audioFormat === 3) {
1195
      /** @type {number} */   
1196
      let len = samples.length;
1197
      for (let i=0; i<len; i++) {
1198
        if (samples[i] > 1) {
1199
          samples[i] = 1;
1200
        } else if (samples[i] < -1) {
1201
          samples[i] = -1;
1202
        }
1203
      }
1204
    }
1205
  }
1206
}
1207