Passed
Push — master ( 8b0f51...317dbe )
by Rafael S.
02:45
created

WaveFileParser.readSmplChunk_   A

Complexity

Conditions 3

Size

Total Lines 28
Code Lines 23

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 23
dl 0
loc 28
rs 9.328
c 0
b 0
f 0
cc 3
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 WaveFileParser class.
27
 * @see https://github.com/rochars/wavefile
28
 */
29
30
import RIFFFile from './riff-file';
31
import writeString from './write-string';
32
import {packTo, packStringTo, packString, pack} from 'byte-data';
33
34
/**
35
 * A class to read and write wav files.
36
 */
37
export default class WaveFileParser extends RIFFFile {
38
39
  constructor() {
40
    super();
41
    /**
42
     * Audio formats.
43
     * Formats not listed here should be set to 65534,
44
     * the code for WAVE_FORMAT_EXTENSIBLE
45
     * @enum {number}
46
     * @protected
47
     */
48
    this.WAV_AUDIO_FORMATS = {
49
      '4': 17,
50
      '8': 1,
51
      '8a': 6,
52
      '8m': 7,
53
      '16': 1,
54
      '24': 1,
55
      '32': 1,
56
      '32f': 3,
57
      '64': 3
58
    };
59
    /**
60
     * The data of the 'fmt' chunk.
61
     * @type {!Object<string, *>}
62
     */
63
    this.fmt = {
64
      /** @type {string} */
65
      chunkId: '',
66
      /** @type {number} */
67
      chunkSize: 0,
68
      /** @type {number} */
69
      audioFormat: 0,
70
      /** @type {number} */
71
      numChannels: 0,
72
      /** @type {number} */
73
      sampleRate: 0,
74
      /** @type {number} */
75
      byteRate: 0,
76
      /** @type {number} */
77
      blockAlign: 0,
78
      /** @type {number} */
79
      bitsPerSample: 0,
80
      /** @type {number} */
81
      cbSize: 0,
82
      /** @type {number} */
83
      validBitsPerSample: 0,
84
      /** @type {number} */
85
      dwChannelMask: 0,
86
      /**
87
       * 4 32-bit values representing a 128-bit ID
88
       * @type {!Array<number>}
89
       */
90
      subformat: []
91
    };
92
    /**
93
     * The data of the 'fact' chunk.
94
     * @type {!Object<string, *>}
95
     */
96
    this.fact = {
97
      /** @type {string} */
98
      chunkId: '',
99
      /** @type {number} */
100
      chunkSize: 0,
101
      /** @type {number} */
102
      dwSampleLength: 0
103
    };
104
    /**
105
     * The data of the 'cue ' chunk.
106
     * @type {!Object<string, *>}
107
     */
108
    this.cue = {
109
      /** @type {string} */
110
      chunkId: '',
111
      /** @type {number} */
112
      chunkSize: 0,
113
      /** @type {number} */
114
      dwCuePoints: 0,
115
      /** @type {!Array<!Object>} */
116
      points: [],
117
    };
118
    /**
119
     * The data of the 'smpl' chunk.
120
     * @type {!Object<string, *>}
121
     */
122
    this.smpl = {
123
      /** @type {string} */
124
      chunkId: '',
125
      /** @type {number} */
126
      chunkSize: 0,
127
      /** @type {number} */
128
      dwManufacturer: 0,
129
      /** @type {number} */
130
      dwProduct: 0,
131
      /** @type {number} */
132
      dwSamplePeriod: 0,
133
      /** @type {number} */
134
      dwMIDIUnityNote: 0,
135
      /** @type {number} */
136
      dwMIDIPitchFraction: 0,
137
      /** @type {number} */
138
      dwSMPTEFormat: 0,
139
      /** @type {number} */
140
      dwSMPTEOffset: 0,
141
      /** @type {number} */
142
      dwNumSampleLoops: 0,
143
      /** @type {number} */
144
      dwSamplerData: 0,
145
      /** @type {!Array<!Object>} */
146
      loops: []
147
    };
148
    /**
149
     * The data of the 'bext' chunk.
150
     * @type {!Object<string, *>}
151
     */
152
    this.bext = {
153
      /** @type {string} */
154
      chunkId: '',
155
      /** @type {number} */
156
      chunkSize: 0,
157
      /** @type {string} */
158
      description: '', //256
159
      /** @type {string} */
160
      originator: '', //32
161
      /** @type {string} */
162
      originatorReference: '', //32
163
      /** @type {string} */
164
      originationDate: '', //10
165
      /** @type {string} */
166
      originationTime: '', //8
167
      /**
168
       * 2 32-bit values, timeReference high and low
169
       * @type {!Array<number>}
170
       */
171
      timeReference: [0, 0],
172
      /** @type {number} */
173
      version: 0, //WORD
174
      /** @type {string} */
175
      UMID: '', // 64 chars
176
      /** @type {number} */
177
      loudnessValue: 0, //WORD
178
      /** @type {number} */
179
      loudnessRange: 0, //WORD
180
      /** @type {number} */
181
      maxTruePeakLevel: 0, //WORD
182
      /** @type {number} */
183
      maxMomentaryLoudness: 0, //WORD
184
      /** @type {number} */
185
      maxShortTermLoudness: 0, //WORD
186
      /** @type {string} */
187
      reserved: '', //180
188
      /** @type {string} */
189
      codingHistory: '' // string, unlimited
190
    };
191
    /**
192
     * The data of the 'ds64' chunk.
193
     * Used only with RF64 files.
194
     * @type {!Object<string, *>}
195
     */
196
    this.ds64 = {
197
      /** @type {string} */
198
      chunkId: '',
199
      /** @type {number} */
200
      chunkSize: 0,
201
      /** @type {number} */
202
      riffSizeHigh: 0, // DWORD
203
      /** @type {number} */
204
      riffSizeLow: 0, // DWORD
205
      /** @type {number} */
206
      dataSizeHigh: 0, // DWORD
207
      /** @type {number} */
208
      dataSizeLow: 0, // DWORD
209
      /** @type {number} */
210
      originationTime: 0, // DWORD
211
      /** @type {number} */
212
      sampleCountHigh: 0, // DWORD
213
      /** @type {number} */
214
      sampleCountLow: 0 // DWORD
215
      /** @type {number} */
216
      //'tableLength': 0, // DWORD
217
      /** @type {!Array<number>} */
218
      //'table': []
219
    };
220
    /**
221
     * The data of the 'data' chunk.
222
     * @type {!Object<string, *>}
223
     */
224
    this.data = {
225
      /** @type {string} */
226
      chunkId: '',
227
      /** @type {number} */
228
      chunkSize: 0,
229
      /** @type {!Uint8Array} */
230
      samples: new Uint8Array(0)
231
    };
232
    /**
233
     * The data of the 'LIST' chunks.
234
     * Each item in this list look like this:
235
     *  {
236
     *      chunkId: '',
237
     *      chunkSize: 0,
238
     *      format: '',
239
     *      subChunks: []
240
     *   }
241
     * @type {!Array<!Object>}
242
     */
243
    this.LIST = [];
244
    /**
245
     * The data of the 'junk' chunk.
246
     * @type {!Object<string, *>}
247
     */
248
    this.junk = {
249
      /** @type {string} */
250
      chunkId: '',
251
      /** @type {number} */
252
      chunkSize: 0,
253
      /** @type {!Array<number>} */
254
      chunkData: []
255
    };
256
    /**
257
     * The bit depth code according to the samples.
258
     * @type {string}
259
     */
260
    this.bitDepth = '0';
261
    /**
262
     * @type {!Object}
263
     * @protected
264
     */
265
    this.dataType = {};
266
  }
267
268
  /**
269
   * Set up the WaveFileParser object from a byte buffer.
270
   * @param {!Uint8Array} wavBuffer The buffer.
271
   * @param {boolean=} samples True if the samples should be loaded.
272
   * @throws {Error} If container is not RIFF, RIFX or RF64.
273
   * @throws {Error} If format is not WAVE.
274
   * @throws {Error} If no 'fmt ' chunk is found.
275
   * @throws {Error} If no 'data' chunk is found.
276
   */
277
  readBuffer(wavBuffer, samples=true) {
278
    this.clearHeader();
279
    this.head_ = 0;
280
    this.readRIFFChunk(wavBuffer);
281
    if (this.format != 'WAVE') {
282
      throw Error('Could not find the "WAVE" format identifier');
283
    }
284
    this.setSignature(wavBuffer);
285
    this.readDs64Chunk_(wavBuffer);
286
    this.readFmtChunk_(wavBuffer);
287
    this.readFactChunk_(wavBuffer);
288
    this.readBextChunk_(wavBuffer);
289
    this.readCueChunk_(wavBuffer);
290
    this.readSmplChunk_(wavBuffer);
291
    this.readDataChunk_(wavBuffer, samples);
292
    this.readJunkChunk_(wavBuffer);
293
    this.readLISTChunk_(wavBuffer);
294
    this.bitDepthFromFmt_();
295
    this.updateDataType();
296
  }
297
298
  /**
299
   * Return a byte buffer representig the WaveFileParser object as a .wav file.
300
   * The return value of this method can be written straight to disk.
301
   * @return {!Uint8Array} A wav file.
302
   * @throws {Error} If bit depth is invalid.
303
   * @throws {Error} If the number of channels is invalid.
304
   * @throws {Error} If the sample rate is invalid.
305
   */
306
  writeBuffer() {
307
    this.validateWavHeader();
308
    return this.writeWavBuffer_();
309
  }
310
311
  /**
312
   * Reset some attributes of the object.
313
   * @protected
314
   */
315
  clearHeader() {
316
    this.fmt.cbSize = 0;
317
    this.fmt.validBitsPerSample = 0;
318
    this.fact.chunkId = '';
319
    this.ds64.chunkId = '';
320
  }
321
322
  /**
323
   * Validate the header of the file.
324
   * @throws {Error} If bit depth is invalid.
325
   * @throws {Error} If the number of channels is invalid.
326
   * @throws {Error} If the sample rate is invalid.
327
   * @protected
328
   */
329
  validateWavHeader() {
330
    this.validateBitDepth_();
331
    this.validateNumChannels_();
332
    this.validateSampleRate_();
333
  }
334
335
  /**
336
   * Update the type definition used to read and write the samples.
337
   * @protected
338
   */
339
  updateDataType() {
340
    this.dataType = {
341
      bits: ((parseInt(this.bitDepth, 10) - 1) | 7) + 1,
342
      fp: this.bitDepth == '32f' || this.bitDepth == '64',
343
      signed: this.bitDepth != '8',
344
      be: this.container == 'RIFX'
345
    };
346
    if (['4', '8a', '8m'].indexOf(this.bitDepth) > -1 ) {
347
      this.dataType.bits = 8;
348
      this.dataType.signed = false;
349
    }
350
  }
351
352
  /**
353
   * Set the string code of the bit depth based on the 'fmt ' chunk.
354
   * @private
355
   */
356
  bitDepthFromFmt_() {
357
    if (this.fmt.audioFormat === 3 && this.fmt.bitsPerSample === 32) {
358
      this.bitDepth = '32f';
359
    } else if (this.fmt.audioFormat === 6) {
360
      this.bitDepth = '8a';
361
    } else if (this.fmt.audioFormat === 7) {
362
      this.bitDepth = '8m';
363
    } else {
364
      this.bitDepth = this.fmt.bitsPerSample.toString();
365
    }
366
  }
367
368
  /**
369
   * Return a .wav file byte buffer with the data from the WaveFileParser object.
370
   * The return value of this method can be written straight to disk.
371
   * @return {!Uint8Array} The wav file bytes.
372
   * @private
373
   */
374
  writeWavBuffer_() {
375
    this.uInt16_.be = this.container === 'RIFX';
376
    this.uInt32_.be = this.uInt16_.be;
377
    /** @type {!Array<!Array<number>>} */
378
    let fileBody = [
379
      this.getJunkBytes_(),
380
      this.getDs64Bytes_(),
381
      this.getBextBytes_(),
382
      this.getFmtBytes_(),
383
      this.getFactBytes_(),
384
      packString(this.data.chunkId),
385
      pack(this.data.samples.length, this.uInt32_),
386
      this.data.samples,
387
      this.getCueBytes_(),
388
      this.getSmplBytes_(),
389
      this.getLISTBytes_()
390
    ];
391
    /** @type {number} */
392
    let fileBodyLength = 0;
393
    for (let i=0; i<fileBody.length; i++) {
394
      fileBodyLength += fileBody[i].length;
395
    }
396
    /** @type {!Uint8Array} */
397
    let file = new Uint8Array(fileBodyLength + 12);
398
    /** @type {number} */
399
    let index = 0;
400
    index = packStringTo(this.container, file, index);
401
    index = packTo(fileBodyLength + 4, this.uInt32_, file, index);
402
    index = packStringTo(this.format, file, index);
403
    for (let i=0; i<fileBody.length; i++) {
404
      file.set(fileBody[i], index);
405
      index += fileBody[i].length;
406
    }
407
    return file;
408
  }
409
410
  /**
411
   * Return the bytes of the 'bext' chunk.
412
   * @private
413
   */
414
  getBextBytes_() {
415
    /** @type {!Array<number>} */
416
    let bytes = [];
417
    this.enforceBext_();
418
    if (this.bext.chunkId) {
419
      this.bext.chunkSize = 602 + this.bext.codingHistory.length;
420
      bytes = bytes.concat(
421
        packString(this.bext.chunkId),
422
        pack(602 + this.bext.codingHistory.length, this.uInt32_),
423
        writeString(this.bext.description, 256),
424
        writeString(this.bext.originator, 32),
425
        writeString(this.bext.originatorReference, 32),
426
        writeString(this.bext.originationDate, 10),
427
        writeString(this.bext.originationTime, 8),
428
        pack(this.bext.timeReference[0], this.uInt32_),
429
        pack(this.bext.timeReference[1], this.uInt32_),
430
        pack(this.bext.version, this.uInt16_),
431
        writeString(this.bext.UMID, 64),
432
        pack(this.bext.loudnessValue, this.uInt16_),
433
        pack(this.bext.loudnessRange, this.uInt16_),
434
        pack(this.bext.maxTruePeakLevel, this.uInt16_),
435
        pack(this.bext.maxMomentaryLoudness, this.uInt16_),
436
        pack(this.bext.maxShortTermLoudness, this.uInt16_),
437
        writeString(this.bext.reserved, 180),
438
        writeString(
439
          this.bext.codingHistory, this.bext.codingHistory.length));
440
    }
441
    return bytes;
442
  }
443
444
  /**
445
   * Make sure a 'bext' chunk is created if BWF data was created in a file.
446
   * @private
447
   */
448
  enforceBext_() {
449
    for (let prop in this.bext) {
450
      if (this.bext.hasOwnProperty(prop)) {
451
        if (this.bext[prop] && prop != 'timeReference') {
452
          this.bext.chunkId = 'bext';
453
          break;
454
        }
455
      }
456
    }
457
    if (this.bext.timeReference[0] || this.bext.timeReference[1]) {
458
      this.bext.chunkId = 'bext';
459
    }
460
  }
461
462
  /**
463
   * Return the bytes of the 'ds64' chunk.
464
   * @return {!Array<number>} The 'ds64' chunk bytes.
465
   * @private
466
   */
467
  getDs64Bytes_() {
468
    /** @type {!Array<number>} */
469
    let bytes = [];
470
    if (this.ds64.chunkId) {
471
      bytes = bytes.concat(
472
        packString(this.ds64.chunkId),
473
        pack(this.ds64.chunkSize, this.uInt32_),
474
        pack(this.ds64.riffSizeHigh, this.uInt32_),
475
        pack(this.ds64.riffSizeLow, this.uInt32_),
476
        pack(this.ds64.dataSizeHigh, this.uInt32_),
477
        pack(this.ds64.dataSizeLow, this.uInt32_),
478
        pack(this.ds64.originationTime, this.uInt32_),
479
        pack(this.ds64.sampleCountHigh, this.uInt32_),
480
        pack(this.ds64.sampleCountLow, this.uInt32_));
481
    }
482
    //if (this.ds64.tableLength) {
483
    //  ds64Bytes = ds64Bytes.concat(
484
    //    pack(this.ds64.tableLength, this.uInt32_),
485
    //    this.ds64.table);
486
    //}
487
    return bytes;
488
  }
489
490
  /**
491
   * Return the bytes of the 'cue ' chunk.
492
   * @return {!Array<number>} The 'cue ' chunk bytes.
493
   * @private
494
   */
495
  getCueBytes_() {
496
    /** @type {!Array<number>} */
497
    let bytes = [];
498
    if (this.cue.chunkId) {
499
      /** @type {!Array<number>} */
500
      let cuePointsBytes = this.getCuePointsBytes_();
501
      bytes = bytes.concat(
502
        packString(this.cue.chunkId),
503
        pack(cuePointsBytes.length + 4, this.uInt32_),
504
        pack(this.cue.dwCuePoints, this.uInt32_),
505
        cuePointsBytes);
506
    }
507
    return bytes;
508
  }
509
510
  /**
511
   * Return the bytes of the 'cue ' points.
512
   * @return {!Array<number>} The 'cue ' points as an array of bytes.
513
   * @private
514
   */
515
  getCuePointsBytes_() {
516
    /** @type {!Array<number>} */
517
    let points = [];
518
    for (let i=0; i<this.cue.dwCuePoints; i++) {
519
      points = points.concat(
520
        pack(this.cue.points[i].dwName, this.uInt32_),
521
        pack(this.cue.points[i].dwPosition, this.uInt32_),
522
        packString(this.cue.points[i].fccChunk),
523
        pack(this.cue.points[i].dwChunkStart, this.uInt32_),
524
        pack(this.cue.points[i].dwBlockStart, this.uInt32_),
525
        pack(this.cue.points[i].dwSampleOffset, this.uInt32_));
526
    }
527
    return points;
528
  }
529
530
  /**
531
   * Return the bytes of the 'smpl' chunk.
532
   * @return {!Array<number>} The 'smpl' chunk bytes.
533
   * @private
534
   */
535
  getSmplBytes_() {
536
    /** @type {!Array<number>} */
537
    let bytes = [];
538
    if (this.smpl.chunkId) {
539
      /** @type {!Array<number>} */
540
      let smplLoopsBytes = this.getSmplLoopsBytes_();
541
      bytes = bytes.concat(
542
        packString(this.smpl.chunkId),
543
        pack(smplLoopsBytes.length + 36, this.uInt32_),
544
        pack(this.smpl.dwManufacturer, this.uInt32_),
545
        pack(this.smpl.dwProduct, this.uInt32_),
546
        pack(this.smpl.dwSamplePeriod, this.uInt32_),
547
        pack(this.smpl.dwMIDIUnityNote, this.uInt32_),
548
        pack(this.smpl.dwMIDIPitchFraction, this.uInt32_),
549
        pack(this.smpl.dwSMPTEFormat, this.uInt32_),
550
        pack(this.smpl.dwSMPTEOffset, this.uInt32_),
551
        pack(this.smpl.dwNumSampleLoops, this.uInt32_),
552
        pack(this.smpl.dwSamplerData, this.uInt32_),
553
        smplLoopsBytes);
554
    }
555
    return bytes;
556
  }
557
558
  /**
559
   * Return the bytes of the 'smpl' loops.
560
   * @return {!Array<number>} The 'smpl' loops as an array of bytes.
561
   * @private
562
   */
563
  getSmplLoopsBytes_() {
564
    /** @type {!Array<number>} */
565
    let loops = [];
566
    for (let i=0; i<this.smpl.dwNumSampleLoops; i++) {
567
      loops = loops.concat(
568
        pack(this.smpl.loops[i].dwName, this.uInt32_),
569
        pack(this.smpl.loops[i].dwType, this.uInt32_),
570
        pack(this.smpl.loops[i].dwStart, this.uInt32_),
571
        pack(this.smpl.loops[i].dwEnd, this.uInt32_),
572
        pack(this.smpl.loops[i].dwFraction, this.uInt32_),
573
        pack(this.smpl.loops[i].dwPlayCount, this.uInt32_));
574
    }
575
    return loops;
576
  }
577
578
  /**
579
   * Return the bytes of the 'fact' chunk.
580
   * @return {!Array<number>} The 'fact' chunk bytes.
581
   * @private
582
   */
583
  getFactBytes_() {
584
    /** @type {!Array<number>} */
585
    let bytes = [];
586
    if (this.fact.chunkId) {
587
      bytes = bytes.concat(
588
        packString(this.fact.chunkId),
589
        pack(this.fact.chunkSize, this.uInt32_),
590
        pack(this.fact.dwSampleLength, this.uInt32_));
591
    }
592
    return bytes;
593
  }
594
595
  /**
596
   * Return the bytes of the 'fmt ' chunk.
597
   * @return {!Array<number>} The 'fmt' chunk bytes.
598
   * @throws {Error} if no 'fmt ' chunk is present.
599
   * @private
600
   */
601
  getFmtBytes_() {
602
    /** @type {!Array<number>} */
603
    let fmtBytes = [];
604
    if (this.fmt.chunkId) {
605
      return fmtBytes.concat(
606
        packString(this.fmt.chunkId),
607
        pack(this.fmt.chunkSize, this.uInt32_),
608
        pack(this.fmt.audioFormat, this.uInt16_),
609
        pack(this.fmt.numChannels, this.uInt16_),
610
        pack(this.fmt.sampleRate, this.uInt32_),
611
        pack(this.fmt.byteRate, this.uInt32_),
612
        pack(this.fmt.blockAlign, this.uInt16_),
613
        pack(this.fmt.bitsPerSample, this.uInt16_),
614
        this.getFmtExtensionBytes_());
615
    }
616
    throw Error('Could not find the "fmt " chunk');
617
  }
618
619
  /**
620
   * Return the bytes of the fmt extension fields.
621
   * @return {!Array<number>} The fmt extension bytes.
622
   * @private
623
   */
624
  getFmtExtensionBytes_() {
625
    /** @type {!Array<number>} */
626
    let extension = [];
627
    if (this.fmt.chunkSize > 16) {
628
      extension = extension.concat(
629
        pack(this.fmt.cbSize, this.uInt16_));
630
    }
631
    if (this.fmt.chunkSize > 18) {
632
      extension = extension.concat(
633
        pack(this.fmt.validBitsPerSample, this.uInt16_));
634
    }
635
    if (this.fmt.chunkSize > 20) {
636
      extension = extension.concat(
637
        pack(this.fmt.dwChannelMask, this.uInt32_));
638
    }
639
    if (this.fmt.chunkSize > 24) {
640
      extension = extension.concat(
641
        pack(this.fmt.subformat[0], this.uInt32_),
642
        pack(this.fmt.subformat[1], this.uInt32_),
643
        pack(this.fmt.subformat[2], this.uInt32_),
644
        pack(this.fmt.subformat[3], this.uInt32_));
645
    }
646
    return extension;
647
  }
648
649
  /**
650
   * Return the bytes of the 'LIST' chunk.
651
   * @return {!Array<number>} The 'LIST' chunk bytes.
652
   * @private
653
   */
654
  getLISTBytes_() {
655
    /** @type {!Array<number>} */
656
    let bytes = [];
657
    for (let i=0; i<this.LIST.length; i++) {
658
      /** @type {!Array<number>} */
659
      let subChunksBytes = this.getLISTSubChunksBytes_(
660
          this.LIST[i].subChunks, this.LIST[i].format);
661
      bytes = bytes.concat(
662
        packString(this.LIST[i].chunkId),
663
        pack(subChunksBytes.length + 4, this.uInt32_),
664
        packString(this.LIST[i].format),
665
        subChunksBytes);
666
    }
667
    return bytes;
668
  }
669
670
  /**
671
   * Return the bytes of the sub chunks of a 'LIST' chunk.
672
   * @param {!Array<!Object>} subChunks The 'LIST' sub chunks.
673
   * @param {string} format The format of the 'LIST' chunk.
674
   *    Currently supported values are 'adtl' or 'INFO'.
675
   * @return {!Array<number>} The sub chunk bytes.
676
   * @private
677
   */
678
  getLISTSubChunksBytes_(subChunks, format) {
679
    /** @type {!Array<number>} */
680
    let bytes = [];
681
    for (let i=0; i<subChunks.length; i++) {
682
      if (format == 'INFO') {
683
        bytes = bytes.concat(
684
          packString(subChunks[i].chunkId),
685
          pack(subChunks[i].value.length + 1, this.uInt32_),
686
          writeString(
687
            subChunks[i].value, subChunks[i].value.length));
688
        bytes.push(0);
689
      } else if (format == 'adtl') {
690
        if (['labl', 'note'].indexOf(subChunks[i].chunkId) > -1) {
691
          bytes = bytes.concat(
692
            packString(subChunks[i].chunkId),
693
            pack(
694
              subChunks[i].value.length + 4 + 1, this.uInt32_),
695
            pack(subChunks[i].dwName, this.uInt32_),
696
            writeString(
697
              subChunks[i].value,
698
              subChunks[i].value.length));
699
          bytes.push(0);
700
        } else if (subChunks[i].chunkId == 'ltxt') {
701
          bytes = bytes.concat(
702
            this.getLtxtChunkBytes_(subChunks[i]));
703
        }
704
      }
705
      if (bytes.length % 2) {
706
        bytes.push(0);
707
      }
708
    }
709
    return bytes;
710
  }
711
712
  /**
713
   * Return the bytes of a 'ltxt' chunk.
714
   * @param {!Object} ltxt the 'ltxt' chunk.
715
   * @private
716
   */
717
  getLtxtChunkBytes_(ltxt) {
718
    return [].concat(
719
      packString(ltxt.chunkId),
720
      pack(ltxt.value.length + 20, this.uInt32_),
721
      pack(ltxt.dwName, this.uInt32_),
722
      pack(ltxt.dwSampleLength, this.uInt32_),
723
      pack(ltxt.dwPurposeID, this.uInt32_),
724
      pack(ltxt.dwCountry, this.uInt16_),
725
      pack(ltxt.dwLanguage, this.uInt16_),
726
      pack(ltxt.dwDialect, this.uInt16_),
727
      pack(ltxt.dwCodePage, this.uInt16_),
728
      writeString(ltxt.value, ltxt.value.length));
729
  }
730
731
  /**
732
   * Return the bytes of the 'junk' chunk.
733
   * @private
734
   */
735
  getJunkBytes_() {
736
    /** @type {!Array<number>} */
737
    let bytes = [];
738
    if (this.junk.chunkId) {
739
      return bytes.concat(
740
        packString(this.junk.chunkId),
741
        pack(this.junk.chunkData.length, this.uInt32_),
742
        this.junk.chunkData);
743
    }
744
    return bytes;
745
  }
746
747
  /**
748
   * Read the 'fmt ' chunk of a wave file.
749
   * @param {!Uint8Array} buffer The wav file buffer.
750
   * @throws {Error} If no 'fmt ' chunk is found.
751
   * @private
752
   */
753
  readFmtChunk_(buffer) {
754
    /** @type {?Object} */
755
    let chunk = this.findChunk('fmt ');
756
    if (chunk) {
757
      this.head_ = chunk.chunkData.start;
758
      this.fmt.chunkId = chunk.chunkId;
759
      this.fmt.chunkSize = chunk.chunkSize;
760
      this.fmt.audioFormat = this.readNumber(buffer, this.uInt16_);
761
      this.fmt.numChannels = this.readNumber(buffer, this.uInt16_);
762
      this.fmt.sampleRate = this.readNumber(buffer, this.uInt32_);
763
      this.fmt.byteRate = this.readNumber(buffer, this.uInt32_);
764
      this.fmt.blockAlign = this.readNumber(buffer, this.uInt16_);
765
      this.fmt.bitsPerSample = this.readNumber(buffer, this.uInt16_);
766
      this.readFmtExtension_(buffer);
767
    } else {
768
      throw Error('Could not find the "fmt " chunk');
769
    }
770
  }
771
772
  /**
773
   * Read the 'fmt ' chunk extension.
774
   * @param {!Uint8Array} buffer The wav file buffer.
775
   * @private
776
   */
777
  readFmtExtension_(buffer) {
778
    if (this.fmt.chunkSize > 16) {
779
      this.fmt.cbSize = this.readNumber(buffer, this.uInt16_);
780
      if (this.fmt.chunkSize > 18) {
781
        this.fmt.validBitsPerSample = this.readNumber(buffer, this.uInt16_);
782
        if (this.fmt.chunkSize > 20) {
783
          this.fmt.dwChannelMask = this.readNumber(buffer, this.uInt32_);
784
          this.fmt.subformat = [
785
            this.readNumber(buffer, this.uInt32_),
786
            this.readNumber(buffer, this.uInt32_),
787
            this.readNumber(buffer, this.uInt32_),
788
            this.readNumber(buffer, this.uInt32_)];
789
        }
790
      }
791
    }
792
  }
793
794
  /**
795
   * Read the 'fact' chunk of a wav file.
796
   * @param {!Uint8Array} buffer The wav file buffer.
797
   * @private
798
   */
799
  readFactChunk_(buffer) {
800
    /** @type {?Object} */
801
    let chunk = this.findChunk('fact');
802
    if (chunk) {
803
      this.head_ = chunk.chunkData.start;
804
      this.fact.chunkId = chunk.chunkId;
805
      this.fact.chunkSize = chunk.chunkSize;
806
      this.fact.dwSampleLength = this.readNumber(buffer, this.uInt32_);
807
    }
808
  }
809
810
  /**
811
   * Read the 'cue ' chunk of a wave file.
812
   * @param {!Uint8Array} buffer The wav file buffer.
813
   * @private
814
   */
815
  readCueChunk_(buffer) {
816
    /** @type {?Object} */
817
    let chunk = this.findChunk('cue ');
818
    if (chunk) {
819
      this.head_ = chunk.chunkData.start;
820
      this.cue.chunkId = chunk.chunkId;
821
      this.cue.chunkSize = chunk.chunkSize;
822
      this.cue.dwCuePoints = this.readNumber(buffer, this.uInt32_);
823
      for (let i = 0; i < this.cue.dwCuePoints; i++) {
824
        this.cue.points.push({
825
          dwName: this.readNumber(buffer, this.uInt32_),
826
          dwPosition: this.readNumber(buffer, this.uInt32_),
827
          fccChunk: this.readString(buffer, 4),
828
          dwChunkStart: this.readNumber(buffer, this.uInt32_),
829
          dwBlockStart: this.readNumber(buffer, this.uInt32_),
830
          dwSampleOffset: this.readNumber(buffer, this.uInt32_),
831
        });
832
      }
833
    }
834
  }
835
836
  /**
837
   * Read the 'smpl' chunk of a wave file.
838
   * @param {!Uint8Array} buffer The wav file buffer.
839
   * @private
840
   */
841
  readSmplChunk_(buffer) {
842
    /** @type {?Object} */
843
    let chunk = this.findChunk('smpl');
844
    if (chunk) {
845
      this.head_ = chunk.chunkData.start;
846
      this.smpl.chunkId = chunk.chunkId;
847
      this.smpl.chunkSize = chunk.chunkSize;
848
      this.smpl.dwManufacturer = this.readNumber(buffer, this.uInt32_);
849
      this.smpl.dwProduct = this.readNumber(buffer, this.uInt32_);
850
      this.smpl.dwSamplePeriod = this.readNumber(buffer, this.uInt32_);
851
      this.smpl.dwMIDIUnityNote = this.readNumber(buffer, this.uInt32_);
852
      this.smpl.dwMIDIPitchFraction = this.readNumber(buffer, this.uInt32_);
853
      this.smpl.dwSMPTEFormat = this.readNumber(buffer, this.uInt32_);
854
      this.smpl.dwSMPTEOffset = this.readNumber(buffer, this.uInt32_);
855
      this.smpl.dwNumSampleLoops = this.readNumber(buffer, this.uInt32_);
856
      this.smpl.dwSamplerData = this.readNumber(buffer, this.uInt32_);
857
      for (let i = 0; i < this.smpl.dwNumSampleLoops; i++) {
858
        this.smpl.loops.push({
859
          dwName: this.readNumber(buffer, this.uInt32_),
860
          dwType: this.readNumber(buffer, this.uInt32_),
861
          dwStart: this.readNumber(buffer, this.uInt32_),
862
          dwEnd: this.readNumber(buffer, this.uInt32_),
863
          dwFraction: this.readNumber(buffer, this.uInt32_),
864
          dwPlayCount: this.readNumber(buffer, this.uInt32_),
865
        });
866
      }
867
    }
868
  }
869
870
  /**
871
   * Read the 'data' chunk of a wave file.
872
   * @param {!Uint8Array} buffer The wav file buffer.
873
   * @param {boolean} samples True if the samples should be loaded.
874
   * @throws {Error} If no 'data' chunk is found.
875
   * @private
876
   */
877
  readDataChunk_(buffer, samples) {
878
    /** @type {?Object} */
879
    let chunk = this.findChunk('data');
880
    if (chunk) {
881
      this.data.chunkId = 'data';
882
      this.data.chunkSize = chunk.chunkSize;
883
      if (samples) {
884
        this.data.samples = buffer.slice(
885
          chunk.chunkData.start,
886
          chunk.chunkData.end);
887
      }
888
    } else {
889
      throw Error('Could not find the "data" chunk');
890
    }
891
  }
892
893
  /**
894
   * Read the 'bext' chunk of a wav file.
895
   * @param {!Uint8Array} buffer The wav file buffer.
896
   * @private
897
   */
898
  readBextChunk_(buffer) {
899
    /** @type {?Object} */
900
    let chunk = this.findChunk('bext');
901
    if (chunk) {
902
      this.head_ = chunk.chunkData.start;
903
      this.bext.chunkId = chunk.chunkId;
904
      this.bext.chunkSize = chunk.chunkSize;
905
      this.bext.description = this.readString(buffer, 256);
906
      this.bext.originator = this.readString(buffer, 32);
907
      this.bext.originatorReference = this.readString(buffer, 32);
908
      this.bext.originationDate = this.readString(buffer, 10);
909
      this.bext.originationTime = this.readString(buffer, 8);
910
      this.bext.timeReference = [
911
        this.readNumber(buffer, this.uInt32_),
912
        this.readNumber(buffer, this.uInt32_)];
913
      this.bext.version = this.readNumber(buffer, this.uInt16_);
914
      this.bext.UMID = this.readString(buffer, 64);
915
      this.bext.loudnessValue = this.readNumber(buffer, this.uInt16_);
916
      this.bext.loudnessRange = this.readNumber(buffer, this.uInt16_);
917
      this.bext.maxTruePeakLevel = this.readNumber(buffer, this.uInt16_);
918
      this.bext.maxMomentaryLoudness = this.readNumber(buffer, this.uInt16_);
919
      this.bext.maxShortTermLoudness = this.readNumber(buffer, this.uInt16_);
920
      this.bext.reserved = this.readString(buffer, 180);
921
      this.bext.codingHistory = this.readString(
922
        buffer, this.bext.chunkSize - 602);
923
    }
924
  }
925
926
  /**
927
   * Read the 'ds64' chunk of a wave file.
928
   * @param {!Uint8Array} buffer The wav file buffer.
929
   * @throws {Error} If no 'ds64' chunk is found and the file is RF64.
930
   * @private
931
   */
932
  readDs64Chunk_(buffer) {
933
    /** @type {?Object} */
934
    let chunk = this.findChunk('ds64');
935
    if (chunk) {
936
      this.head_ = chunk.chunkData.start;
937
      this.ds64.chunkId = chunk.chunkId;
938
      this.ds64.chunkSize = chunk.chunkSize;
939
      this.ds64.riffSizeHigh = this.readNumber(buffer, this.uInt32_);
940
      this.ds64.riffSizeLow = this.readNumber(buffer, this.uInt32_);
941
      this.ds64.dataSizeHigh = this.readNumber(buffer, this.uInt32_);
942
      this.ds64.dataSizeLow = this.readNumber(buffer, this.uInt32_);
943
      this.ds64.originationTime = this.readNumber(buffer, this.uInt32_);
944
      this.ds64.sampleCountHigh = this.readNumber(buffer, this.uInt32_);
945
      this.ds64.sampleCountLow = this.readNumber(buffer, this.uInt32_);
946
      //if (wav.ds64.chunkSize > 28) {
947
      //  wav.ds64.tableLength = unpack(
948
      //    chunkData.slice(28, 32), uInt32_);
949
      //  wav.ds64.table = chunkData.slice(
950
      //     32, 32 + wav.ds64.tableLength);
951
      //}
952
    } else {
953
      if (this.container == 'RF64') {
954
        throw Error('Could not find the "ds64" chunk');
955
      }
956
    }
957
  }
958
959
  /**
960
   * Read the 'LIST' chunks of a wave file.
961
   * @param {!Uint8Array} buffer The wav file buffer.
962
   * @private
963
   */
964
  readLISTChunk_(buffer) {
965
    /** @type {?Object} */
966
    let listChunks = this.findChunk('LIST', true);
967
    if (listChunks !== null) {
968
      for (let j=0; j < listChunks.length; j++) {
969
        /** @type {!Object} */
970
        let subChunk = listChunks[j];
971
        this.LIST.push({
972
          chunkId: subChunk.chunkId,
973
          chunkSize: subChunk.chunkSize,
974
          format: subChunk.format,
975
          subChunks: []});
976
        for (let x=0; x<subChunk.subChunks.length; x++) {
977
          this.readLISTSubChunks_(subChunk.subChunks[x],
978
            subChunk.format, buffer);
979
        }
980
      }
981
    }
982
  }
983
984
  /**
985
   * Read the sub chunks of a 'LIST' chunk.
986
   * @param {!Object} subChunk The 'LIST' subchunks.
987
   * @param {string} format The 'LIST' format, 'adtl' or 'INFO'.
988
   * @param {!Uint8Array} buffer The wav file buffer.
989
   * @private
990
   */
991
  readLISTSubChunks_(subChunk, format, buffer) {
992
    if (format == 'adtl') {
993
      if (['labl', 'note','ltxt'].indexOf(subChunk.chunkId) > -1) {
994
        this.head_ = subChunk.chunkData.start;
995
        /** @type {!Object<string, string|number>} */
996
        let item = {
997
          chunkId: subChunk.chunkId,
998
          chunkSize: subChunk.chunkSize,
999
          dwName: this.readNumber(buffer, this.uInt32_)
1000
        };
1001
        if (subChunk.chunkId == 'ltxt') {
1002
          item.dwSampleLength = this.readNumber(buffer, this.uInt32_);
1003
          item.dwPurposeID = this.readNumber(buffer, this.uInt32_);
1004
          item.dwCountry = this.readNumber(buffer, this.uInt16_);
1005
          item.dwLanguage = this.readNumber(buffer, this.uInt16_);
1006
          item.dwDialect = this.readNumber(buffer, this.uInt16_);
1007
          item.dwCodePage = this.readNumber(buffer, this.uInt16_);
1008
        }
1009
        item.value = this.readZSTR(buffer, this.head_);
1010
        this.LIST[this.LIST.length - 1].subChunks.push(item);
1011
      }
1012
    // RIFF INFO tags like ICRD, ISFT, ICMT
1013
    } else if(format == 'INFO') {
1014
      this.head_ = subChunk.chunkData.start;
1015
      this.LIST[this.LIST.length - 1].subChunks.push({
1016
        chunkId: subChunk.chunkId,
1017
        chunkSize: subChunk.chunkSize,
1018
        value: this.readZSTR(buffer, this.head_)
1019
      });
1020
    }
1021
  }
1022
1023
  /**
1024
   * Read the 'junk' chunk of a wave file.
1025
   * @param {!Uint8Array} buffer The wav file buffer.
1026
   * @private
1027
   */
1028
  readJunkChunk_(buffer) {
1029
    /** @type {?Object} */
1030
    let chunk = this.findChunk('junk');
1031
    if (chunk) {
1032
      this.junk = {
1033
        chunkId: chunk.chunkId,
1034
        chunkSize: chunk.chunkSize,
1035
        chunkData: [].slice.call(buffer.slice(
1036
          chunk.chunkData.start,
1037
          chunk.chunkData.end))
1038
      };
1039
    }
1040
  }
1041
1042
  /**
1043
   * Validate the bit depth.
1044
   * @return {boolean} True is the bit depth is valid.
1045
   * @throws {Error} If bit depth is invalid.
1046
   * @private
1047
   */
1048
  validateBitDepth_() {
1049
    if (!this.WAV_AUDIO_FORMATS[this.bitDepth]) {
1050
      if (parseInt(this.bitDepth, 10) > 8 &&
1051
          parseInt(this.bitDepth, 10) < 54) {
1052
        return true;
1053
      }
1054
      throw new Error('Invalid bit depth.');
1055
    }
1056
    return true;
1057
  }
1058
1059
  /**
1060
   * Validate the number of channels.
1061
   * @return {boolean} True is the number of channels is valid.
1062
   * @throws {Error} If the number of channels is invalid.
1063
   * @private
1064
   */
1065
  validateNumChannels_() {
1066
    /** @type {number} */
1067
    let blockAlign = this.fmt.numChannels * this.fmt.bitsPerSample / 8;
1068
    if (this.fmt.numChannels < 1 || blockAlign > 65535) {
1069
      throw new Error('Invalid number of channels.');
1070
    }
1071
    return true;
1072
  }
1073
1074
  /**
1075
   * Validate the sample rate value.
1076
   * @return {boolean} True is the sample rate is valid.
1077
   * @throws {Error} If the sample rate is invalid.
1078
   * @private
1079
   */
1080
  validateSampleRate_() {
1081
    /** @type {number} */
1082
    let byteRate = this.fmt.numChannels *
1083
      (this.fmt.bitsPerSample / 8) * this.fmt.sampleRate;
1084
    if (this.fmt.sampleRate < 1 || byteRate > 4294967295) {
1085
      throw new Error('Invalid sample rate.');
1086
    }
1087
    return true;
1088
  }
1089
}
1090