Passed
Branch wavefile-rw (d3e828)
by Rafael S.
02:30
created

WaveFileParser.getSample   A

Complexity

Conditions 2

Size

Total Lines 9
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

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