Passed
Branch wavefile-reader (73452d)
by Rafael S.
06:21
created

lib/wavefile-reader.js   F

Complexity

Total Complexity 72
Complexity/F 2.77

Size

Lines of Code 802
Function Count 26

Duplication

Duplicated Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
wmc 72
eloc 371
mnd 46
bc 46
fnc 26
dl 0
loc 802
rs 2.64
bpm 1.7692
cpm 2.7692
noi 0
c 0
b 0
f 0

26 Functions

Rating   Name   Duplication   Size   Complexity  
A WaveFileReader.getAdtlChunk_ 0 8 3
B WaveFileReader.constructor 0 240 2
A WaveFileReader.readSmplChunk_ 0 28 3
A WaveFileReader.readUInt16 0 6 1
A WaveFileReader.readBextChunk_ 0 27 2
A WaveFileReader.getTagIndex_ 0 17 5
B WaveFileReader.readLISTSubChunks_ 0 31 5
A WaveFileReader.fromBuffer 0 19 2
A WaveFileReader.readDs64Chunk_ 0 26 3
A WaveFileReader.readFmtExtension_ 0 16 4
A WaveFileReader.readCueChunk_ 0 20 3
A WaveFileReader.bitDepthFromFmt_ 0 11 4
A WaveFileReader.readFmtChunk_ 0 18 2
A WaveFileReader.clearHeader 0 6 1
A WaveFileReader.readLISTChunk_ 0 19 4
A WaveFileReader.readJunkChunk_ 0 13 2
A WaveFileReader.getLISTINFOIndex_ 0 11 3
A WaveFileReader.getCuePoints_ 0 11 2
A WaveFileReader.readDataChunk_ 0 15 3
A WaveFileReader.getTag 0 8 2
A WaveFileReader.listTags 0 13 3
A WaveFileReader.getLabelForCuePoint_ 0 13 4
A WaveFileReader.readZSTR 0 9 3
A WaveFileReader.updateDataType 0 12 2
A WaveFileReader.readFactChunk_ 0 10 2
A WaveFileReader.listCuePoints 0 9 2

How to fix   Complexity   

Complexity

Complex classes like lib/wavefile-reader.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-2019 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 The WaveFileReader class.
27
 * @see https://github.com/rochars/wavefile
28
 */
29
30
/** @module wavefile */
31
32
import RIFFFile from './riff-file';
33
import validateNumChannels from './validate-num-channels'; 
34
import validateSampleRate from './validate-sample-rate';
35
import {unpackString, unpack} from 'byte-data';
36
37
/**
38
 * A class to read wav files.
39
 */
40
export default class WaveFileReader extends RIFFFile {
41
42
  /**
43
   * @param {?Uint8Array=} wavBuffer A wave file buffer.
44
   * @throws {Error} If container is not RIFF, RIFX or RF64.
45
   * @throws {Error} If format is not WAVE.
46
   * @throws {Error} If no 'fmt ' chunk is found.
47
   * @throws {Error} If no 'data' chunk is found.
48
   */
49
  constructor(wavBuffer=null) {
50
    super();
51
    // Include 'RF64' as a supported container format
52
    this.supported_containers.push('RF64');
53
    /**
54
     * Audio formats.
55
     * Formats not listed here should be set to 65534,
56
     * the code for WAVE_FORMAT_EXTENSIBLE
57
     * @enum {number}
58
     * @protected
59
     */
60
    this.WAV_AUDIO_FORMATS = {
61
      '4': 17,
62
      '8': 1,
63
      '8a': 6,
64
      '8m': 7,
65
      '16': 1,
66
      '24': 1,
67
      '32': 1,
68
      '32f': 3,
69
      '64': 3
70
    };
71
    /**
72
     * The data of the 'fmt' chunk.
73
     * @type {!Object<string, *>}
74
     */
75
    this.fmt = {
76
      /** @type {string} */
77
      chunkId: '',
78
      /** @type {number} */
79
      chunkSize: 0,
80
      /** @type {number} */
81
      audioFormat: 0,
82
      /** @type {number} */
83
      numChannels: 0,
84
      /** @type {number} */
85
      sampleRate: 0,
86
      /** @type {number} */
87
      byteRate: 0,
88
      /** @type {number} */
89
      blockAlign: 0,
90
      /** @type {number} */
91
      bitsPerSample: 0,
92
      /** @type {number} */
93
      cbSize: 0,
94
      /** @type {number} */
95
      validBitsPerSample: 0,
96
      /** @type {number} */
97
      dwChannelMask: 0,
98
      /**
99
       * 4 32-bit values representing a 128-bit ID
100
       * @type {!Array<number>}
101
       */
102
      subformat: []
103
    };
104
    /**
105
     * The data of the 'fact' chunk.
106
     * @type {!Object<string, *>}
107
     */
108
    this.fact = {
109
      /** @type {string} */
110
      chunkId: '',
111
      /** @type {number} */
112
      chunkSize: 0,
113
      /** @type {number} */
114
      dwSampleLength: 0
115
    };
116
    /**
117
     * The data of the 'cue ' chunk.
118
     * @type {!Object<string, *>}
119
     */
120
    this.cue = {
121
      /** @type {string} */
122
      chunkId: '',
123
      /** @type {number} */
124
      chunkSize: 0,
125
      /** @type {number} */
126
      dwCuePoints: 0,
127
      /** @type {!Array<!Object>} */
128
      points: [],
129
    };
130
    /**
131
     * The data of the 'smpl' chunk.
132
     * @type {!Object<string, *>}
133
     */
134
    this.smpl = {
135
      /** @type {string} */
136
      chunkId: '',
137
      /** @type {number} */
138
      chunkSize: 0,
139
      /** @type {number} */
140
      dwManufacturer: 0,
141
      /** @type {number} */
142
      dwProduct: 0,
143
      /** @type {number} */
144
      dwSamplePeriod: 0,
145
      /** @type {number} */
146
      dwMIDIUnityNote: 0,
147
      /** @type {number} */
148
      dwMIDIPitchFraction: 0,
149
      /** @type {number} */
150
      dwSMPTEFormat: 0,
151
      /** @type {number} */
152
      dwSMPTEOffset: 0,
153
      /** @type {number} */
154
      dwNumSampleLoops: 0,
155
      /** @type {number} */
156
      dwSamplerData: 0,
157
      /** @type {!Array<!Object>} */
158
      loops: []
159
    };
160
    /**
161
     * The data of the 'bext' chunk.
162
     * @type {!Object<string, *>}
163
     */
164
    this.bext = {
165
      /** @type {string} */
166
      chunkId: '',
167
      /** @type {number} */
168
      chunkSize: 0,
169
      /** @type {string} */
170
      description: '', //256
171
      /** @type {string} */
172
      originator: '', //32
173
      /** @type {string} */
174
      originatorReference: '', //32
175
      /** @type {string} */
176
      originationDate: '', //10
177
      /** @type {string} */
178
      originationTime: '', //8
179
      /**
180
       * 2 32-bit values, timeReference high and low
181
       * @type {!Array<number>}
182
       */
183
      timeReference: [0, 0],
184
      /** @type {number} */
185
      version: 0, //WORD
186
      /** @type {string} */
187
      UMID: '', // 64 chars
188
      /** @type {number} */
189
      loudnessValue: 0, //WORD
190
      /** @type {number} */
191
      loudnessRange: 0, //WORD
192
      /** @type {number} */
193
      maxTruePeakLevel: 0, //WORD
194
      /** @type {number} */
195
      maxMomentaryLoudness: 0, //WORD
196
      /** @type {number} */
197
      maxShortTermLoudness: 0, //WORD
198
      /** @type {string} */
199
      reserved: '', //180
200
      /** @type {string} */
201
      codingHistory: '' // string, unlimited
202
    };
203
    /**
204
     * The data of the 'ds64' chunk.
205
     * Used only with RF64 files.
206
     * @type {!Object<string, *>}
207
     */
208
    this.ds64 = {
209
      /** @type {string} */
210
      chunkId: '',
211
      /** @type {number} */
212
      chunkSize: 0,
213
      /** @type {number} */
214
      riffSizeHigh: 0, // DWORD
215
      /** @type {number} */
216
      riffSizeLow: 0, // DWORD
217
      /** @type {number} */
218
      dataSizeHigh: 0, // DWORD
219
      /** @type {number} */
220
      dataSizeLow: 0, // DWORD
221
      /** @type {number} */
222
      originationTime: 0, // DWORD
223
      /** @type {number} */
224
      sampleCountHigh: 0, // DWORD
225
      /** @type {number} */
226
      sampleCountLow: 0 // DWORD
227
      /** @type {number} */
228
      //'tableLength': 0, // DWORD
229
      /** @type {!Array<number>} */
230
      //'table': []
231
    };
232
    /**
233
     * The data of the 'data' chunk.
234
     * @type {!Object<string, *>}
235
     */
236
    this.data = {
237
      /** @type {string} */
238
      chunkId: '',
239
      /** @type {number} */
240
      chunkSize: 0,
241
      /** @type {!Uint8Array} */
242
      samples: new Uint8Array(0)
243
    };
244
    /**
245
     * The data of the 'LIST' chunks.
246
     * Each item in this list look like this:
247
     *  {
248
     *      chunkId: '',
249
     *      chunkSize: 0,
250
     *      format: '',
251
     *      subChunks: []
252
     *   }
253
     * @type {!Array<!Object>}
254
     */
255
    this.LIST = [];
256
    /**
257
     * The data of the 'junk' chunk.
258
     * @type {!Object<string, *>}
259
     */
260
    this.junk = {
261
      /** @type {string} */
262
      chunkId: '',
263
      /** @type {number} */
264
      chunkSize: 0,
265
      /** @type {!Array<number>} */
266
      chunkData: []
267
    };
268
    /**
269
     * The bit depth code according to the samples.
270
     * @type {string}
271
     */
272
    this.bitDepth = '0';
273
    /**
274
     * @type {!Object}
275
     * @protected
276
     */
277
    this.dataType = {};
278
    /**
279
     * @type {!Object}
280
     * @protected
281
     */
282
    this.uInt16 = {bits: 16, be: false};
283
    // Load a file from the buffer if one was passed
284
    // when creating the object
285
    if (wavBuffer) {
286
      this.fromBuffer(wavBuffer);
287
    }
288
  }
289
290
  /**
291
   * Set up the WaveFileParser object from a byte buffer.
292
   * @param {!Uint8Array} wavBuffer The buffer.
293
   * @param {boolean=} samples True if the samples should be loaded.
294
   * @throws {Error} If container is not RIFF, RIFX or RF64.
295
   * @throws {Error} If format is not WAVE.
296
   * @throws {Error} If no 'fmt ' chunk is found.
297
   * @throws {Error} If no 'data' chunk is found.
298
   */
299
  fromBuffer(wavBuffer, samples=true) {
300
    this.clearHeader();
301
    this.setSignature(wavBuffer);
302
    this.uInt16.be = this.uInt32.be;
303
    if (this.format != 'WAVE') {
304
      throw Error('Could not find the "WAVE" format identifier');
305
    }
306
    this.readDs64Chunk_(wavBuffer);
307
    this.readFmtChunk_(wavBuffer);
308
    this.readFactChunk_(wavBuffer);
309
    this.readBextChunk_(wavBuffer);
310
    this.readCueChunk_(wavBuffer);
311
    this.readSmplChunk_(wavBuffer);
312
    this.readDataChunk_(wavBuffer, samples);
313
    this.readJunkChunk_(wavBuffer);
314
    this.readLISTChunk_(wavBuffer);
315
    this.bitDepthFromFmt_();
316
    this.updateDataType();
317
  }
318
319
  /**
320
   * Return the value of a RIFF tag in the INFO chunk.
321
   * @param {string} tag The tag name.
322
   * @return {?string} The value if the tag is found, null otherwise.
323
   */
324
  getTag(tag) {
325
    /** @type {!Object} */
326
    let index = this.getTagIndex_(tag);
327
    if (index.TAG !== null) {
328
      return this.LIST[index.LIST].subChunks[index.TAG].value;
329
    }
330
    return null;
331
  }
332
333
  /**
334
   * Return a Object<tag, value> with the RIFF tags in the file.
335
   * @return {!Object<string, string>} The file tags.
336
   */
337
  listTags() {
338
    /** @type {?number} */
339
    let index = this.getLISTINFOIndex_();
340
    /** @type {!Object} */
341
    let tags = {};
342
    if (index !== null) {
343
      for (let i = 0, len = this.LIST[index].subChunks.length; i < len; i++) {
344
        tags[this.LIST[index].subChunks[i].chunkId] =
345
          this.LIST[index].subChunks[i].value;
346
      }
347
    }
348
    return tags;
349
  }
350
351
  /**
352
   * Return an array with all cue points in the file, in the order they appear
353
   * in the file.
354
   * The difference between this method and using the list in WaveFile.cue
355
   * is that the return value of this method includes the position in
356
   * milliseconds of each cue point (WaveFile.cue only have the sample offset)
357
   * @return {!Array<!Object>}
358
   */
359
  listCuePoints() {
360
    /** @type {!Array<!Object>} */
361
    let points = this.getCuePoints_();
362
    for (let i = 0, len = points.length; i < len; i++) {
363
      points[i].milliseconds =
364
        (points[i].dwPosition / this.fmt.sampleRate) * 1000;
365
    }
366
    return points;
367
  }
368
369
  /**
370
   * Reset some attributes of the object.
371
   * @protected
372
   * @ignore
373
   */
374
  clearHeader() {
375
    this.fmt.cbSize = 0;
376
    this.fmt.validBitsPerSample = 0;
377
    this.fact.chunkId = '';
378
    this.ds64.chunkId = '';
379
  }
380
381
  /**
382
   * Update the type definition used to read and write the samples.
383
   * @protected
384
   */
385
  updateDataType() {
386
    this.dataType = {
387
      bits: ((parseInt(this.bitDepth, 10) - 1) | 7) + 1,
388
      fp: this.bitDepth == '32f' || this.bitDepth == '64',
389
      signed: this.bitDepth != '8',
390
      be: this.container == 'RIFX'
391
    };
392
    if (['4', '8a', '8m'].indexOf(this.bitDepth) > -1 ) {
393
      this.dataType.bits = 8;
394
      this.dataType.signed = false;
395
    }
396
  }
397
398
  /**
399
   * Set the string code of the bit depth based on the 'fmt ' chunk.
400
   * @private
401
   */
402
  bitDepthFromFmt_() {
403
    if (this.fmt.audioFormat === 3 && this.fmt.bitsPerSample === 32) {
404
      this.bitDepth = '32f';
405
    } else if (this.fmt.audioFormat === 6) {
406
      this.bitDepth = '8a';
407
    } else if (this.fmt.audioFormat === 7) {
408
      this.bitDepth = '8m';
409
    } else {
410
      this.bitDepth = this.fmt.bitsPerSample.toString();
411
    }
412
  }
413
414
  /**
415
   * Return an array with all cue points in the file, in the order they appear
416
   * in the file.
417
   * @return {!Array<!Object>}
418
   * @private
419
   */
420
  getCuePoints_() {
421
    /** @type {!Array<!Object>} */
422
    let points = [];
423
    for (let i = 0, len = this.cue.points.length; i < len; i++) {
424
      points.push({
425
        dwPosition: this.cue.points[i].dwPosition,
426
        label: this.getLabelForCuePoint_(
427
          this.cue.points[i].dwName)});
428
    }
429
    return points;
430
  }
431
432
  /**
433
   * Return the label of a cue point.
434
   * @param {number} pointDwName The ID of the cue point.
435
   * @return {string}
436
   * @private
437
   */
438
  getLabelForCuePoint_(pointDwName) {
439
    /** @type {?number} */
440
    let cIndex = this.getAdtlChunk_();
441
    if (cIndex !== null) {
442
      for (let i = 0, len = this.LIST[cIndex].subChunks.length; i < len; i++) {
443
        if (this.LIST[cIndex].subChunks[i].dwName ==
444
            pointDwName) {
445
          return this.LIST[cIndex].subChunks[i].value;
446
        }
447
      }
448
    }
449
    return '';
450
  }
451
452
  /**
453
   * Return the index of the INFO chunk in the LIST chunk.
454
   * @return {?number} the index of the INFO chunk.
455
   * @private
456
   */
457
  getLISTINFOIndex_() {
458
    /** @type {?number} */
459
    let index = null;
460
    for (let i = 0, len = this.LIST.length; i < len; i++) {
461
      if (this.LIST[i].format === 'INFO') {
462
        index = i;
463
        break;
464
      }
465
    }
466
    return index;
467
  }
468
469
  /**
470
   * Return the index of the 'adtl' LIST in this.LIST.
471
   * @return {?number}
472
   * @private
473
   */
474
  getAdtlChunk_() {
475
    for (let i = 0, len = this.LIST.length; i < len; i++) {
476
      if (this.LIST[i].format == 'adtl') {
477
        return i;
478
      }
479
    }
480
    return null;
481
  }
482
483
  /**
484
   * Return the index of a tag in a FILE chunk.
485
   * @param {string} tag The tag name.
486
   * @return {!Object<string, ?number>}
487
   *    Object.LIST is the INFO index in LIST
488
   *    Object.TAG is the tag index in the INFO
489
   * @private
490
   */
491
  getTagIndex_(tag) {
492
    /** @type {!Object<string, ?number>} */
493
    let index = {LIST: null, TAG: null};
494
    for (let i = 0, len = this.LIST.length; i < len; i++) {
495
      if (this.LIST[i].format == 'INFO') {
496
        index.LIST = i;
497
        for (let j=0, subLen = this.LIST[i].subChunks.length; j < subLen; j++) {
498
          if (this.LIST[i].subChunks[j].chunkId == tag) {
499
            index.TAG = j;
500
            break;
501
          }
502
        }
503
        break;
504
      }
505
    }
506
    return index;
507
  }
508
509
  /**
510
   * Read the 'fmt ' chunk of a wave file.
511
   * @param {!Uint8Array} buffer The wav file buffer.
512
   * @throws {Error} If no 'fmt ' chunk is found.
513
   * @private
514
   */
515
  readFmtChunk_(buffer) {
516
    /** @type {?Object} */
517
    let chunk = this.findChunk('fmt ');
518
    if (chunk) {
519
      this.head = chunk.chunkData.start;
520
      this.fmt.chunkId = chunk.chunkId;
521
      this.fmt.chunkSize = chunk.chunkSize;
522
      this.fmt.audioFormat = this.readUInt16(buffer);
523
      this.fmt.numChannels = this.readUInt16(buffer);
524
      this.fmt.sampleRate = this.readUInt32(buffer);
525
      this.fmt.byteRate = this.readUInt32(buffer);
526
      this.fmt.blockAlign = this.readUInt16(buffer);
527
      this.fmt.bitsPerSample = this.readUInt16(buffer);
528
      this.readFmtExtension_(buffer);
529
    } else {
530
      throw Error('Could not find the "fmt " chunk');
531
    }
532
  }
533
534
  /**
535
   * Read the 'fmt ' chunk extension.
536
   * @param {!Uint8Array} buffer The wav file buffer.
537
   * @private
538
   */
539
  readFmtExtension_(buffer) {
540
    if (this.fmt.chunkSize > 16) {
541
      this.fmt.cbSize = this.readUInt16(buffer);
542
      if (this.fmt.chunkSize > 18) {
543
        this.fmt.validBitsPerSample = this.readUInt16(buffer);
544
        if (this.fmt.chunkSize > 20) {
545
          this.fmt.dwChannelMask = this.readUInt32(buffer);
546
          this.fmt.subformat = [
547
            this.readUInt32(buffer),
548
            this.readUInt32(buffer),
549
            this.readUInt32(buffer),
550
            this.readUInt32(buffer)];
551
        }
552
      }
553
    }
554
  }
555
556
  /**
557
   * Read the 'fact' chunk of a wav file.
558
   * @param {!Uint8Array} buffer The wav file buffer.
559
   * @private
560
   */
561
  readFactChunk_(buffer) {
562
    /** @type {?Object} */
563
    let chunk = this.findChunk('fact');
564
    if (chunk) {
565
      this.head = chunk.chunkData.start;
566
      this.fact.chunkId = chunk.chunkId;
567
      this.fact.chunkSize = chunk.chunkSize;
568
      this.fact.dwSampleLength = this.readUInt32(buffer);
569
    }
570
  }
571
572
  /**
573
   * Read the 'cue ' chunk of a wave file.
574
   * @param {!Uint8Array} buffer The wav file buffer.
575
   * @private
576
   */
577
  readCueChunk_(buffer) {
578
    /** @type {?Object} */
579
    let chunk = this.findChunk('cue ');
580
    if (chunk) {
581
      this.head = chunk.chunkData.start;
582
      this.cue.chunkId = chunk.chunkId;
583
      this.cue.chunkSize = chunk.chunkSize;
584
      this.cue.dwCuePoints = this.readUInt32(buffer);
585
      for (let i = 0; i < this.cue.dwCuePoints; i++) {
586
        this.cue.points.push({
587
          dwName: this.readUInt32(buffer),
588
          dwPosition: this.readUInt32(buffer),
589
          fccChunk: this.readString(buffer, 4),
590
          dwChunkStart: this.readUInt32(buffer),
591
          dwBlockStart: this.readUInt32(buffer),
592
          dwSampleOffset: this.readUInt32(buffer),
593
        });
594
      }
595
    }
596
  }
597
598
  /**
599
   * Read the 'smpl' chunk of a wave file.
600
   * @param {!Uint8Array} buffer The wav file buffer.
601
   * @private
602
   */
603
  readSmplChunk_(buffer) {
604
    /** @type {?Object} */
605
    let chunk = this.findChunk('smpl');
606
    if (chunk) {
607
      this.head = chunk.chunkData.start;
608
      this.smpl.chunkId = chunk.chunkId;
609
      this.smpl.chunkSize = chunk.chunkSize;
610
      this.smpl.dwManufacturer = this.readUInt32(buffer);
611
      this.smpl.dwProduct = this.readUInt32(buffer);
612
      this.smpl.dwSamplePeriod = this.readUInt32(buffer);
613
      this.smpl.dwMIDIUnityNote = this.readUInt32(buffer);
614
      this.smpl.dwMIDIPitchFraction = this.readUInt32(buffer);
615
      this.smpl.dwSMPTEFormat = this.readUInt32(buffer);
616
      this.smpl.dwSMPTEOffset = this.readUInt32(buffer);
617
      this.smpl.dwNumSampleLoops = this.readUInt32(buffer);
618
      this.smpl.dwSamplerData = this.readUInt32(buffer);
619
      for (let i = 0; i < this.smpl.dwNumSampleLoops; i++) {
620
        this.smpl.loops.push({
621
          dwName: this.readUInt32(buffer),
622
          dwType: this.readUInt32(buffer),
623
          dwStart: this.readUInt32(buffer),
624
          dwEnd: this.readUInt32(buffer),
625
          dwFraction: this.readUInt32(buffer),
626
          dwPlayCount: this.readUInt32(buffer),
627
        });
628
      }
629
    }
630
  }
631
632
  /**
633
   * Read the 'data' chunk of a wave file.
634
   * @param {!Uint8Array} buffer The wav file buffer.
635
   * @param {boolean} samples True if the samples should be loaded.
636
   * @throws {Error} If no 'data' chunk is found.
637
   * @private
638
   */
639
  readDataChunk_(buffer, samples) {
640
    /** @type {?Object} */
641
    let chunk = this.findChunk('data');
642
    if (chunk) {
643
      this.data.chunkId = 'data';
644
      this.data.chunkSize = chunk.chunkSize;
645
      if (samples) {
646
        this.data.samples = buffer.slice(
647
          chunk.chunkData.start,
648
          chunk.chunkData.end);
649
      }
650
    } else {
651
      throw Error('Could not find the "data" chunk');
652
    }
653
  }
654
655
  /**
656
   * Read the 'bext' chunk of a wav file.
657
   * @param {!Uint8Array} buffer The wav file buffer.
658
   * @private
659
   */
660
  readBextChunk_(buffer) {
661
    /** @type {?Object} */
662
    let chunk = this.findChunk('bext');
663
    if (chunk) {
664
      this.head = chunk.chunkData.start;
665
      this.bext.chunkId = chunk.chunkId;
666
      this.bext.chunkSize = chunk.chunkSize;
667
      this.bext.description = this.readString(buffer, 256);
668
      this.bext.originator = this.readString(buffer, 32);
669
      this.bext.originatorReference = this.readString(buffer, 32);
670
      this.bext.originationDate = this.readString(buffer, 10);
671
      this.bext.originationTime = this.readString(buffer, 8);
672
      this.bext.timeReference = [
673
        this.readUInt32(buffer),
674
        this.readUInt32(buffer)];
675
      this.bext.version = this.readUInt16(buffer);
676
      this.bext.UMID = this.readString(buffer, 64);
677
      this.bext.loudnessValue = this.readUInt16(buffer);
678
      this.bext.loudnessRange = this.readUInt16(buffer);
679
      this.bext.maxTruePeakLevel = this.readUInt16(buffer);
680
      this.bext.maxMomentaryLoudness = this.readUInt16(buffer);
681
      this.bext.maxShortTermLoudness = this.readUInt16(buffer);
682
      this.bext.reserved = this.readString(buffer, 180);
683
      this.bext.codingHistory = this.readString(
684
        buffer, this.bext.chunkSize - 602);
685
    }
686
  }
687
688
  /**
689
   * Read the 'ds64' chunk of a wave file.
690
   * @param {!Uint8Array} buffer The wav file buffer.
691
   * @throws {Error} If no 'ds64' chunk is found and the file is RF64.
692
   * @private
693
   */
694
  readDs64Chunk_(buffer) {
695
    /** @type {?Object} */
696
    let chunk = this.findChunk('ds64');
697
    if (chunk) {
698
      this.head = chunk.chunkData.start;
699
      this.ds64.chunkId = chunk.chunkId;
700
      this.ds64.chunkSize = chunk.chunkSize;
701
      this.ds64.riffSizeHigh = this.readUInt32(buffer);
702
      this.ds64.riffSizeLow = this.readUInt32(buffer);
703
      this.ds64.dataSizeHigh = this.readUInt32(buffer);
704
      this.ds64.dataSizeLow = this.readUInt32(buffer);
705
      this.ds64.originationTime = this.readUInt32(buffer);
706
      this.ds64.sampleCountHigh = this.readUInt32(buffer);
707
      this.ds64.sampleCountLow = this.readUInt32(buffer);
708
      //if (wav.ds64.chunkSize > 28) {
709
      //  wav.ds64.tableLength = unpack(
710
      //    chunkData.slice(28, 32), uInt32_);
711
      //  wav.ds64.table = chunkData.slice(
712
      //     32, 32 + wav.ds64.tableLength);
713
      //}
714
    } else {
715
      if (this.container == 'RF64') {
716
        throw Error('Could not find the "ds64" chunk');
717
      }
718
    }
719
  }
720
721
  /**
722
   * Read the 'LIST' chunks of a wave file.
723
   * @param {!Uint8Array} buffer The wav file buffer.
724
   * @private
725
   */
726
  readLISTChunk_(buffer) {
727
    /** @type {?Object} */
728
    let listChunks = this.findChunk('LIST', true);
729
    if (listChunks !== null) {
730
      for (let j=0; j < listChunks.length; j++) {
731
        /** @type {!Object} */
732
        let subChunk = listChunks[j];
733
        this.LIST.push({
734
          chunkId: subChunk.chunkId,
735
          chunkSize: subChunk.chunkSize,
736
          format: subChunk.format,
737
          subChunks: []});
738
        for (let x=0; x<subChunk.subChunks.length; x++) {
739
          this.readLISTSubChunks_(subChunk.subChunks[x],
740
            subChunk.format, buffer);
741
        }
742
      }
743
    }
744
  }
745
746
  /**
747
   * Read the sub chunks of a 'LIST' chunk.
748
   * @param {!Object} subChunk The 'LIST' subchunks.
749
   * @param {string} format The 'LIST' format, 'adtl' or 'INFO'.
750
   * @param {!Uint8Array} buffer The wav file buffer.
751
   * @private
752
   */
753
  readLISTSubChunks_(subChunk, format, buffer) {
754
    if (format == 'adtl') {
755
      if (['labl', 'note','ltxt'].indexOf(subChunk.chunkId) > -1) {
756
        this.head = subChunk.chunkData.start;
757
        /** @type {!Object<string, string|number>} */
758
        let item = {
759
          chunkId: subChunk.chunkId,
760
          chunkSize: subChunk.chunkSize,
761
          dwName: this.readUInt32(buffer)
762
        };
763
        if (subChunk.chunkId == 'ltxt') {
764
          item.dwSampleLength = this.readUInt32(buffer);
765
          item.dwPurposeID = this.readUInt32(buffer);
766
          item.dwCountry = this.readUInt16(buffer);
767
          item.dwLanguage = this.readUInt16(buffer);
768
          item.dwDialect = this.readUInt16(buffer);
769
          item.dwCodePage = this.readUInt16(buffer);
770
        }
771
        item.value = this.readZSTR(buffer, this.head);
772
        this.LIST[this.LIST.length - 1].subChunks.push(item);
773
      }
774
    // RIFF INFO tags like ICRD, ISFT, ICMT
775
    } else if(format == 'INFO') {
776
      this.head = subChunk.chunkData.start;
777
      this.LIST[this.LIST.length - 1].subChunks.push({
778
        chunkId: subChunk.chunkId,
779
        chunkSize: subChunk.chunkSize,
780
        value: this.readZSTR(buffer, this.head)
781
      });
782
    }
783
  }
784
785
  /**
786
   * Read the 'junk' chunk of a wave file.
787
   * @param {!Uint8Array} buffer The wav file buffer.
788
   * @private
789
   */
790
  readJunkChunk_(buffer) {
791
    /** @type {?Object} */
792
    let chunk = this.findChunk('junk');
793
    if (chunk) {
794
      this.junk = {
795
        chunkId: chunk.chunkId,
796
        chunkSize: chunk.chunkSize,
797
        chunkData: [].slice.call(buffer.slice(
798
          chunk.chunkData.start,
799
          chunk.chunkData.end))
800
      };
801
    }
802
  }
803
804
  /**
805
   * Read bytes as a ZSTR string.
806
   * @param {!Uint8Array} bytes The bytes.
807
   * @param {number} index the index to start reading.
808
   * @return {string} The string.
809
   * @protected
810
   */
811
  readZSTR(bytes, index=0) {
812
    for (let i = index; i < bytes.length; i++) {
813
      this.head++;
814
      if (bytes[i] === 0) {
815
        break;
816
      }
817
    }
818
    return unpackString(bytes, index, this.head - 1);
819
  }
820
821
  /**
822
   * Read a number from a chunk.
823
   * @param {!Uint8Array} bytes The chunk bytes.
824
   * @return {number} The number.
825
   * @protected
826
   */
827
  readUInt16(bytes) {
828
    /** @type {number} */
829
    let value = unpack(bytes, this.uInt16, this.head);
830
    this.head += 2;
831
    return value;
832
  }
833
}
834