Completed
Push — master ( 6a4ba0...a82e92 )
by Rafael S.
03:16
created

index.js ➔ readLISTSubChunks_   B

Complexity

Conditions 5
Paths 5

Size

Total Lines 31
Code Lines 23

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
cc 5
eloc 23
c 2
b 0
f 0
nc 5
nop 3
dl 0
loc 31
rs 8.8613
1
/*
2
 * Copyright (c) 2017-2018 Rafael da Silva Rocha.
3
 *
4
 * Permission is hereby granted, free of charge, to any person obtaining
5
 * a copy of this software and associated documentation files (the
6
 * "Software"), to deal in the Software without restriction, including
7
 * without limitation the rights to use, copy, modify, merge, publish,
8
 * distribute, sublicense, and/or sell copies of the Software, and to
9
 * permit persons to whom the Software is furnished to do so, subject to
10
 * the following conditions:
11
 *
12
 * The above copyright notice and this permission notice shall be
13
 * included in all copies or substantial portions of the Software.
14
 *
15
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
19
 * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
20
 * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
21
 * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22
 *
23
 */
24
25
/**
26
 * @fileoverview The WaveFile class.
27
 * @see https://github.com/rochars/wavefile
28
 */
29
30
/** @module wavefile */
31
32
import bitDepthLib from 'bitdepth';
33
import * as imaadpcm from 'imaadpcm';
34
import * as alawmulaw from 'alawmulaw';
35
import {encode, decode} from 'base64-arraybuffer-es6';
36
import {unpackArray, packArrayTo, unpackArrayTo, unpackString,
37
  unpack, packTo, packStringTo, packString, pack} from 'byte-data';
38
39
/**
40
 * A class to read, write and process wav files.
41
 */
42
export default class WaveFile {
43
44
  /**
45
   * @param {?Uint8Array=} bytes A wave file buffer.
46
   * @throws {Error} If no 'RIFF' chunk is found.
47
   * @throws {Error} If no 'fmt ' chunk is found.
48
   * @throws {Error} If no 'data' chunk is found.
49
   */
50
  constructor(bytes=null) {
51
    
52
    /**
53
     * Audio formats.
54
     * Formats not listed here should be set to 65534,
55
     * the code for WAVE_FORMAT_EXTENSIBLE
56
     * @enum {number}
57
     * @private
58
     */
59
    this.WAV_AUDIO_FORMATS = {
60
      '4': 17,
61
      '8': 1,
62
      '8a': 6,
63
      '8m': 7,
64
      '16': 1,
65
      '24': 1,
66
      '32': 1,
67
      '32f': 3,
68
      '64': 3
69
    };
70
    /** @type {number} */
71
    this.head_ = 0;
72
    /** @type {!Object} */
73
    this.uInt32_ = {bits: 32, be: false};
74
    /** @type {!Object} */
75
    this.uInt16_ = {bits: 16, be: false};
76
    /**
77
     * The container identifier.
78
     * 'RIFF', 'RIFX' and 'RF64' are supported.
79
     * @type {string}
80
     */
81
    this.container = '';
82
    /**
83
     * @type {number}
84
     */
85
    this.chunkSize = 0;
86
    /**
87
     * The format.
88
     * Always 'WAVE'.
89
     * @type {string}
90
     */
91
    this.format = '';
92
    /**
93
     * The data of the 'fmt' chunk.
94
     * @type {!Object<string, *>}
95
     */
96
    this.fmt = {
97
      /** @type {string} */
98
      chunkId: '',
99
      /** @type {number} */
100
      chunkSize: 0,
101
      /** @type {number} */
102
      audioFormat: 0,
103
      /** @type {number} */
104
      numChannels: 0,
105
      /** @type {number} */
106
      sampleRate: 0,
107
      /** @type {number} */
108
      byteRate: 0,
109
      /** @type {number} */
110
      blockAlign: 0,
111
      /** @type {number} */
112
      bitsPerSample: 0,
113
      /** @type {number} */
114
      cbSize: 0,
115
      /** @type {number} */
116
      validBitsPerSample: 0,
117
      /** @type {number} */
118
      dwChannelMask: 0,
119
      /**
120
       * 4 32-bit values representing a 128-bit ID
121
       * @type {!Array<number>}
122
       */
123
      subformat: []
124
    };
125
    /**
126
     * The data of the 'fact' chunk.
127
     * @type {!Object<string, *>}
128
     */
129
    this.fact = {
130
      /** @type {string} */
131
      chunkId: '',
132
      /** @type {number} */
133
      chunkSize: 0,
134
      /** @type {number} */
135
      dwSampleLength: 0
136
    };
137
    /**
138
     * The data of the 'cue ' chunk.
139
     * @type {!Object<string, *>}
140
     */
141
    this.cue = {
142
      /** @type {string} */
143
      chunkId: '',
144
      /** @type {number} */
145
      chunkSize: 0,
146
      /** @type {number} */
147
      dwCuePoints: 0,
148
      /** @type {!Array<!Object>} */
149
      points: [],
150
    };
151
    /**
152
     * The data of the 'smpl' chunk.
153
     * @type {!Object<string, *>}
154
     */
155
    this.smpl = {
156
      /** @type {string} */
157
      chunkId: '',
158
      /** @type {number} */
159
      chunkSize: 0,
160
      /** @type {number} */
161
      dwManufacturer: 0,
162
      /** @type {number} */
163
      dwProduct: 0,
164
      /** @type {number} */
165
      dwSamplePeriod: 0,
166
      /** @type {number} */
167
      dwMIDIUnityNote: 0,
168
      /** @type {number} */
169
      dwMIDIPitchFraction: 0,
170
      /** @type {number} */
171
      dwSMPTEFormat: 0,
172
      /** @type {number} */
173
      dwSMPTEOffset: 0,
174
      /** @type {number} */
175
      dwNumSampleLoops: 0,
176
      /** @type {number} */
177
      dwSamplerData: 0,
178
      /** @type {!Array<!Object>} */
179
      loops: []
180
    };
181
    /**
182
     * The data of the 'bext' chunk.
183
     * @type {!Object<string, *>}
184
     */
185
    this.bext = {
186
      /** @type {string} */
187
      chunkId: '',
188
      /** @type {number} */
189
      chunkSize: 0,
190
      /** @type {string} */
191
      description: '', //256
192
      /** @type {string} */
193
      originator: '', //32
194
      /** @type {string} */
195
      originatorReference: '', //32
196
      /** @type {string} */
197
      originationDate: '', //10
198
      /** @type {string} */
199
      originationTime: '', //8
200
      /**
201
       * 2 32-bit values, timeReference high and low
202
       * @type {!Array<number>}
203
       */
204
      timeReference: [0, 0],
205
      /** @type {number} */
206
      version: 0, //WORD
207
      /** @type {string} */
208
      UMID: '', // 64 chars
209
      /** @type {number} */
210
      loudnessValue: 0, //WORD
211
      /** @type {number} */
212
      loudnessRange: 0, //WORD
213
      /** @type {number} */
214
      maxTruePeakLevel: 0, //WORD
215
      /** @type {number} */
216
      maxMomentaryLoudness: 0, //WORD
217
      /** @type {number} */
218
      maxShortTermLoudness: 0, //WORD
219
      /** @type {string} */
220
      reserved: '', //180
221
      /** @type {string} */
222
      codingHistory: '' // string, unlimited
223
    };
224
    /**
225
     * The data of the 'ds64' chunk.
226
     * Used only with RF64 files.
227
     * @type {!Object<string, *>}
228
     */
229
    this.ds64 = {
230
      /** @type {string} */
231
      chunkId: '',
232
      /** @type {number} */
233
      chunkSize: 0,
234
      /** @type {number} */
235
      riffSizeHigh: 0, // DWORD
236
      /** @type {number} */
237
      riffSizeLow: 0, // DWORD
238
      /** @type {number} */
239
      dataSizeHigh: 0, // DWORD
240
      /** @type {number} */
241
      dataSizeLow: 0, // DWORD
242
      /** @type {number} */
243
      originationTime: 0, // DWORD
244
      /** @type {number} */
245
      sampleCountHigh: 0, // DWORD
246
      /** @type {number} */
247
      sampleCountLow: 0 // DWORD
248
      /** @type {number} */
249
      //'tableLength': 0, // DWORD
250
      /** @type {!Array<number>} */
251
      //'table': []
252
    };
253
    /**
254
     * The data of the 'data' chunk.
255
     * @type {!Object<string, *>}
256
     */
257
    this.data = {
258
      /** @type {string} */
259
      chunkId: '',
260
      /** @type {number} */
261
      chunkSize: 0,
262
      /** @type {!Uint8Array} */
263
      samples: new Uint8Array(0)
264
    };
265
    /**
266
     * The data of the 'LIST' chunks.
267
     * Each item in this list look like this:
268
     *  {
269
     *      chunkId: '',
270
     *      chunkSize: 0,
271
     *      format: '',
272
     *      subChunks: []
273
     *   }
274
     * @type {!Array<!Object>}
275
     */
276
    this.LIST = [];
277
    /**
278
     * The data of the 'junk' chunk.
279
     * @type {!Object<string, *>}
280
     */
281
    this.junk = {
282
      /** @type {string} */
283
      chunkId: '',
284
      /** @type {number} */
285
      chunkSize: 0,
286
      /** @type {!Array<number>} */
287
      chunkData: []
288
    };
289
    /**
290
     * The bit depth code according to the samples.
291
     * @type {string}
292
     */
293
    this.bitDepth = '0';
294
    /**
295
     * A object defining the start and end of all chunks in a wav buffer.
296
     * @type {!Object}
297
     */
298
    this.signature = {};
299
    /**
300
     * @type {!Object}
301
     * @private
302
     */
303
    this.dataType = {};
304
    // Load a file from the buffer if one was passed
305
    // when creating the object
306
    if (bytes) {
307
      this.fromBuffer(bytes);
308
    }
309
  }
310
311
  /**
312
   * Return the sample at a given index.
313
   * @param {number} index The sample index.
314
   * @return {number} The sample.
315
   * @throws {Error} If the sample index is off range.
316
   */
317
  getSample(index) {
318
    index = index * (this.dataType.bits / 8);
319
    if (index + this.dataType.bits / 8 > this.data.samples.length) {
320
      throw new Error('Range error');
321
    }
322
    return unpack(
323
      this.data.samples.slice(index, index + this.dataType.bits / 8),
324
      this.dataType);
325
  }
326
327
  /**
328
   * Set the sample at a given index.
329
   * @param {number} index The sample index.
330
   * @param {number} sample The sample.
331
   * @throws {Error} If the sample index is off range.
332
   */
333
  setSample(index, sample) {
334
    index = index * (this.dataType.bits / 8);
335
    if (index + this.dataType.bits / 8 > this.data.samples.length) {
336
      throw new Error('Range error');
337
    }
338
    packTo(sample, this.dataType, this.data.samples, index);
339
  }
340
341
  /**
342
   * Set up the WaveFile object based on the arguments passed.
343
   * @param {number} numChannels The number of channels
344
   *    (Integer numbers: 1 for mono, 2 stereo and so on).
345
   * @param {number} sampleRate The sample rate.
346
   *    Integer numbers like 8000, 44100, 48000, 96000, 192000.
347
   * @param {string} bitDepthCode The audio bit depth code.
348
   *    One of '4', '8', '8a', '8m', '16', '24', '32', '32f', '64'
349
   *    or any value between '8' and '32' (like '12').
350
   * @param {!Array<number>|!Array<!Array<number>>|!ArrayBufferView} samples
351
   *    The samples. Must be in the correct range according to the bit depth.
352
   * @param {?Object} options Optional. Used to force the container
353
   *    as RIFX with {'container': 'RIFX'}
354
   * @throws {Error} If any argument does not meet the criteria.
355
   */
356
  fromScratch(numChannels, sampleRate, bitDepthCode, samples, options={}) {
357
    if (!options.container) {
358
      options.container = 'RIFF';
359
    }
360
    this.container = options.container;
361
    this.bitDepth = bitDepthCode;
362
    samples = this.interleave_(samples);
363
    this.updateDataType_();
364
    /** @type {number} */
365
    let numBytes = this.dataType.bits / 8;
366
    this.data.samples = new Uint8Array(samples.length * numBytes);
367
    packArrayTo(samples, this.dataType, this.data.samples);
368
    this.clearHeader_();
369
    this.makeWavHeader(
370
      bitDepthCode, numChannels, sampleRate,
371
      numBytes, this.data.samples.length, options);
372
    this.data.chunkId = 'data';
373
    this.data.chunkSize = this.data.samples.length;
374
    this.validateWavHeader_();
375
  }
376
377
  /**
378
   * Set up the WaveFile object from a byte buffer.
379
   * @param {!Uint8Array} bytes The buffer.
380
   * @param {boolean=} samples True if the samples should be loaded.
381
   * @throws {Error} If container is not RIFF, RIFX or RF64.
382
   * @throws {Error} If no 'fmt ' chunk is found.
383
   * @throws {Error} If no 'data' chunk is found.
384
   */
385
  fromBuffer(bytes, samples=true) {
386
    this.clearHeader_();
387
    this.readWavBuffer(bytes, samples);
388
    this.bitDepthFromFmt_();
389
    this.updateDataType_();
390
  }
391
392
  /**
393
   * Return a byte buffer representig the WaveFile object as a .wav file.
394
   * The return value of this method can be written straight to disk.
395
   * @return {!Uint8Array} A .wav file.
396
   * @throws {Error} If any property of the object appears invalid.
397
   */
398
  toBuffer() {
399
    this.validateWavHeader_();
400
    return this.writeWavBuffer();
401
  }
402
403
  /**
404
   * Use a .wav file encoded as a base64 string to load the WaveFile object.
405
   * @param {string} base64String A .wav file as a base64 string.
406
   * @throws {Error} If any property of the object appears invalid.
407
   */
408
  fromBase64(base64String) {
409
    this.fromBuffer(new Uint8Array(decode(base64String)));
410
  }
411
412
  /**
413
   * Return a base64 string representig the WaveFile object as a .wav file.
414
   * @return {string} A .wav file as a base64 string.
415
   * @throws {Error} If any property of the object appears invalid.
416
   */
417
  toBase64() {
418
    /** @type {!Uint8Array} */
419
    let buffer = this.toBuffer();
420
    return encode(buffer, 0, buffer.length);
421
  }
422
423
  /**
424
   * Return a DataURI string representig the WaveFile object as a .wav file.
425
   * The return of this method can be used to load the audio in browsers.
426
   * @return {string} A .wav file as a DataURI.
427
   * @throws {Error} If any property of the object appears invalid.
428
   */
429
  toDataURI() {
430
    return 'data:audio/wav;base64,' + this.toBase64();
431
  }
432
433
  /**
434
   * Use a .wav file encoded as a DataURI to load the WaveFile object.
435
   * @param {string} dataURI A .wav file as DataURI.
436
   * @throws {Error} If any property of the object appears invalid.
437
   */
438
  fromDataURI(dataURI) {
439
    this.fromBase64(dataURI.replace('data:audio/wav;base64,', ''));
440
  }
441
442
  /**
443
   * Force a file as RIFF.
444
   */
445
  toRIFF() {
446
    this.fromScratch(
447
      this.fmt.numChannels,
448
      this.fmt.sampleRate,
449
      this.bitDepth,
450
      unpackArray(this.data.samples, this.dataType));
451
  }
452
453
  /**
454
   * Force a file as RIFX.
455
   */
456
  toRIFX() {
457
    this.fromScratch(
458
      this.fmt.numChannels,
459
      this.fmt.sampleRate,
460
      this.bitDepth,
461
      unpackArray(this.data.samples, this.dataType),
462
      {container: 'RIFX'});
463
  }
464
465
  /**
466
   * Change the bit depth of the samples.
467
   * @param {string} newBitDepth The new bit depth of the samples.
468
   *    One of '8' ... '32' (integers), '32f' or '64' (floats)
469
   * @param {boolean} changeResolution A boolean indicating if the
470
   *    resolution of samples should be actually changed or not.
471
   * @throws {Error} If the bit depth is not valid.
472
   */
473
  toBitDepth(newBitDepth, changeResolution=true) {
474
    /** @type {string} */
475
    let toBitDepth = newBitDepth;
476
    /** @type {string} */
477
    let thisBitDepth = this.bitDepth;
478
    if (!changeResolution) {
479
      if (newBitDepth != '32f') {
480
        toBitDepth = this.dataType.bits.toString();
481
      }
482
      thisBitDepth = this.dataType.bits;
483
    }
484
    this.assureUncompressed_();
485
    /** @type {number} */
486
    let sampleCount = this.data.samples.length / (this.dataType.bits / 8);
487
    /** @type {!Float64Array} */
488
    let typedSamplesInput = new Float64Array(sampleCount);
489
    /** @type {!Float64Array} */
490
    let typedSamplesOutput = new Float64Array(sampleCount);
491
    unpackArrayTo(this.data.samples, this.dataType, typedSamplesInput);
492
    if (thisBitDepth == "32f" || thisBitDepth == "64") {
493
      this.truncateSamples_(typedSamplesInput);
494
    }
495
    bitDepthLib(
496
      typedSamplesInput, thisBitDepth, toBitDepth, typedSamplesOutput);
497
    this.fromScratch(
498
      this.fmt.numChannels,
499
      this.fmt.sampleRate,
500
      newBitDepth,
501
      typedSamplesOutput,
502
      {container: this.correctContainer_()});
503
  }
504
505
  /**
506
   * Encode a 16-bit wave file as 4-bit IMA ADPCM.
507
   * @throws {Error} If sample rate is not 8000.
508
   * @throws {Error} If number of channels is not 1.
509
   */
510
  toIMAADPCM() {
511
    if (this.fmt.sampleRate !== 8000) {
512
      throw new Error(
513
        'Only 8000 Hz files can be compressed as IMA-ADPCM.');
514
    } else if (this.fmt.numChannels !== 1) {
515
      throw new Error(
516
        'Only mono files can be compressed as IMA-ADPCM.');
517
    } else {
518
      this.assure16Bit_();
519
      /** @type {!Int16Array} */
520
      let output = new Int16Array(this.data.samples.length / 2);
521
      unpackArrayTo(this.data.samples, this.dataType, output);
522
      this.fromScratch(
523
        this.fmt.numChannels,
524
        this.fmt.sampleRate,
525
        '4',
526
        imaadpcm.encode(output),
527
        {container: this.correctContainer_()});
528
    }
529
  }
530
531
  /**
532
   * Decode a 4-bit IMA ADPCM wave file as a 16-bit wave file.
533
   * @param {string} bitDepthCode The new bit depth of the samples.
534
   *    One of '8' ... '32' (integers), '32f' or '64' (floats).
535
   *    Optional. Default is 16.
536
   */
537
  fromIMAADPCM(bitDepthCode='16') {
538
    this.fromScratch(
539
      this.fmt.numChannels,
540
      this.fmt.sampleRate,
541
      '16',
542
      imaadpcm.decode(this.data.samples, this.fmt.blockAlign),
543
      {container: this.correctContainer_()});
544
    if (bitDepthCode != '16') {
545
      this.toBitDepth(bitDepthCode);
546
    }
547
  }
548
549
  /**
550
   * Encode a 16-bit wave file as 8-bit A-Law.
551
   */
552
  toALaw() {
553
    this.assure16Bit_();
554
    /** @type {!Int16Array} */
555
    let output = new Int16Array(this.data.samples.length / 2);
556
    unpackArrayTo(this.data.samples, this.dataType, output);
557
    this.fromScratch(
558
      this.fmt.numChannels,
559
      this.fmt.sampleRate,
560
      '8a',
561
      alawmulaw.alaw.encode(output),
562
      {container: this.correctContainer_()});
563
  }
564
565
  /**
566
   * Decode a 8-bit A-Law wave file into a 16-bit wave file.
567
   * @param {string} bitDepthCode The new bit depth of the samples.
568
   *    One of '8' ... '32' (integers), '32f' or '64' (floats).
569
   *    Optional. Default is 16.
570
   */
571
  fromALaw(bitDepthCode='16') {
572
    this.fromScratch(
573
      this.fmt.numChannels,
574
      this.fmt.sampleRate,
575
      '16',
576
      alawmulaw.alaw.decode(this.data.samples),
577
      {container: this.correctContainer_()});
578
    if (bitDepthCode != '16') {
579
      this.toBitDepth(bitDepthCode);
580
    }
581
  }
582
583
  /**
584
   * Encode 16-bit wave file as 8-bit mu-Law.
585
   */
586
  toMuLaw() {
587
    this.assure16Bit_();
588
    /** @type {!Int16Array} */
589
    let output = new Int16Array(this.data.samples.length / 2);
590
    unpackArrayTo(this.data.samples, this.dataType, output);
591
    this.fromScratch(
592
      this.fmt.numChannels,
593
      this.fmt.sampleRate,
594
      '8m',
595
      alawmulaw.mulaw.encode(output),
596
      {container: this.correctContainer_()});
597
  }
598
599
  /**
600
   * Decode a 8-bit mu-Law wave file into a 16-bit wave file.
601
   * @param {string} bitDepthCode The new bit depth of the samples.
602
   *    One of '8' ... '32' (integers), '32f' or '64' (floats).
603
   *    Optional. Default is 16.
604
   */
605
  fromMuLaw(bitDepthCode='16') {
606
    this.fromScratch(
607
      this.fmt.numChannels,
608
      this.fmt.sampleRate,
609
      '16',
610
      alawmulaw.mulaw.decode(this.data.samples),
611
      {container: this.correctContainer_()});
612
    if (bitDepthCode != '16') {
613
      this.toBitDepth(bitDepthCode);
614
    }
615
  }
616
617
  /**
618
   * Write a RIFF tag in the INFO chunk. If the tag do not exist,
619
   * then it is created. It if exists, it is overwritten.
620
   * @param {string} tag The tag name.
621
   * @param {string} value The tag value.
622
   * @throws {Error} If the tag name is not valid.
623
   */
624
  setTag(tag, value) {
625
    tag = this.fixTagName_(tag);
626
    /** @type {!Object} */
627
    let index = this.getTagIndex_(tag);
628
    if (index.TAG !== null) {
629
      this.LIST[index.LIST].subChunks[index.TAG].chunkSize =
630
        value.length + 1;
631
      this.LIST[index.LIST].subChunks[index.TAG].value = value;
632
    } else if (index.LIST !== null) {
633
      this.LIST[index.LIST].subChunks.push({
634
        chunkId: tag,
635
        chunkSize: value.length + 1,
636
        value: value});
637
    } else {
638
      this.LIST.push({
639
        chunkId: 'LIST',
640
        chunkSize: 8 + value.length + 1,
641
        format: 'INFO',
642
        subChunks: []});
643
      this.LIST[this.LIST.length - 1].subChunks.push({
644
        chunkId: tag,
645
        chunkSize: value.length + 1,
646
        value: value});
647
    }
648
  }
649
650
  /**
651
   * Return the value of a RIFF tag in the INFO chunk.
652
   * @param {string} tag The tag name.
653
   * @return {?string} The value if the tag is found, null otherwise.
654
   */
655
  getTag(tag) {
656
    /** @type {!Object} */
657
    let index = this.getTagIndex_(tag);
658
    if (index.TAG !== null) {
659
      return this.LIST[index.LIST].subChunks[index.TAG].value;
660
    }
661
    return null;
662
  }
663
664
  /**
665
   * Return a Object<tag, value> with the RIFF tags in the file.
666
   * @return {!Object<string, string>} The file tags.
667
   */
668
  listTags() {
669
    /** @type {?number} */
670
    let index = this.getLISTINFOIndex_();
671
    /** @type {!Object} */
672
    let tags = {};
673
    if (index !== null) {
674
      for (let i = 0, len = this.LIST[index].subChunks.length; i < len; i++) {
675
        tags[this.LIST[index].subChunks[i].chunkId] =
676
          this.LIST[index].subChunks[i].value;
677
      }
678
    }
679
    return tags;
680
  }
681
682
  /**
683
   * Remove a RIFF tag in the INFO chunk.
684
   * @param {string} tag The tag name.
685
   * @return {boolean} True if a tag was deleted.
686
   */
687
  deleteTag(tag) {
688
    /** @type {!Object} */
689
    let index = this.getTagIndex_(tag);
690
    if (index.TAG !== null) {
691
      this.LIST[index.LIST].subChunks.splice(index.TAG, 1);
692
      return true;
693
    }
694
    return false;
695
  }
696
697
  /**
698
   * Create a cue point in the wave file.
699
   * @param {number} position The cue point position in milliseconds.
700
   * @param {string} labl The LIST adtl labl text of the marker. Optional.
701
   */
702
  setCuePoint(position, labl='') {
703
    this.cue.chunkId = 'cue ';
704
    position = (position * this.fmt.sampleRate) / 1000;
705
    /** @type {!Array<!Object>} */
706
    let existingPoints = this.getCuePoints_();
707
    this.clearLISTadtl_();
708
    /** @type {number} */
709
    let len = this.cue.points.length;
710
    this.cue.points = [];
711
    /** @type {boolean} */
712
    let hasSet = false;
713
    if (len === 0) {
714
      this.setCuePoint_(position, 1, labl);
715
    } else {
716
      for (let i = 0; i < len; i++) {
717
        if (existingPoints[i].dwPosition > position && !hasSet) {
718
          this.setCuePoint_(position, i + 1, labl);
719
          this.setCuePoint_(
720
            existingPoints[i].dwPosition,
721
            i + 2,
722
            existingPoints[i].label);
723
          hasSet = true;
724
        } else {
725
          this.setCuePoint_(
726
            existingPoints[i].dwPosition,
727
            i + 1,
728
            existingPoints[i].label);
729
        }
730
      }
731
      if (!hasSet) {
732
        this.setCuePoint_(position, this.cue.points.length + 1, labl);
733
      }
734
    }
735
    this.cue.dwCuePoints = this.cue.points.length;
736
  }
737
738
  /**
739
   * Remove a cue point from a wave file.
740
   * @param {number} index the index of the point. First is 1,
741
   *    second is 2, and so on.
742
   */
743
  deleteCuePoint(index) {
744
    this.cue.chunkId = 'cue ';
745
    /** @type {!Array<!Object>} */
746
    let existingPoints = this.getCuePoints_();
747
    this.clearLISTadtl_();
748
    /** @type {number} */
749
    let len = this.cue.points.length;
750
    this.cue.points = [];
751
    for (let i = 0; i < len; i++) {
752
      if (i + 1 !== index) {
753
        this.setCuePoint_(
754
          existingPoints[i].dwPosition,
755
          i + 1,
756
          existingPoints[i].label);
757
      }
758
    }
759
    this.cue.dwCuePoints = this.cue.points.length;
760
    if (this.cue.dwCuePoints) {
761
      this.cue.chunkId = 'cue ';
762
    } else {
763
      this.cue.chunkId = '';
764
      this.clearLISTadtl_();
765
    }
766
  }
767
768
  /**
769
   * Return an array with all cue points in the file, in the order they appear
770
   * in the file.
771
   * The difference between this method and using the list in WaveFile.cue
772
   * is that the return value of this method includes the position in
773
   * milliseconds of each cue point (WaveFile.cue only have the sample offset)
774
   * @return {!Array<!Object>}
775
   */
776
  listCuePoints() {
777
    /** @type {!Array<!Object>} */
778
    let points = this.getCuePoints_();
779
    for (let i = 0, len = points.length; i < len; i++) {
780
      points[i].milliseconds =
781
        (points[i].dwPosition / this.fmt.sampleRate) * 1000;
782
    }
783
    return points;
784
  }
785
786
  /**
787
   * Update the label of a cue point.
788
   * @param {number} pointIndex The ID of the cue point.
789
   * @param {string} label The new text for the label.
790
   */
791
  updateLabel(pointIndex, label) {
792
    /** @type {?number} */
793
    let cIndex = this.getAdtlChunk_();
794
    if (cIndex !== null) {
795
      for (let i = 0, len = this.LIST[cIndex].subChunks.length; i < len; i++) {
796
        if (this.LIST[cIndex].subChunks[i].dwName ==
797
            pointIndex) {
798
          this.LIST[cIndex].subChunks[i].value = label;
799
        }
800
      }
801
    }
802
  }
803
804
  /**
805
   * Set the string code of the bit depth based on the 'fmt ' chunk.
806
   * @private
807
   */
808
  bitDepthFromFmt_() {
809
    if (this.fmt.audioFormat === 3 && this.fmt.bitsPerSample === 32) {
810
      this.bitDepth = '32f';
811
    } else if (this.fmt.audioFormat === 6) {
812
      this.bitDepth = '8a';
813
    } else if (this.fmt.audioFormat === 7) {
814
      this.bitDepth = '8m';
815
    } else {
816
      this.bitDepth = this.fmt.bitsPerSample.toString();
817
    }
818
  }
819
  
820
  /**
821
   * Push a new cue point in this.cue.points.
822
   * @param {number} position The position in milliseconds.
823
   * @param {number} dwName the dwName of the cue point
824
   * @private
825
   */
826
  setCuePoint_(position, dwName, label) {
827
    this.cue.points.push({
828
      dwName: dwName,
829
      dwPosition: position,
830
      fccChunk: 'data',
831
      dwChunkStart: 0,
832
      dwBlockStart: 0,
833
      dwSampleOffset: position,
834
    });
835
    this.setLabl_(dwName, label);
836
  }
837
838
  /**
839
   * Return an array with all cue points in the file, in the order they appear
840
   * in the file.
841
   * @return {!Array<!Object>}
842
   * @private
843
   */
844
  getCuePoints_() {
845
    /** @type {!Array<!Object>} */
846
    let points = [];
847
    for (let i = 0, len = this.cue.points.length; i < len; i++) {
848
      points.push({
849
        dwPosition: this.cue.points[i].dwPosition,
850
        label: this.getLabelForCuePoint_(
851
          this.cue.points[i].dwName)});
852
    }
853
    return points;
854
  }
855
856
  /**
857
   * Return the label of a cue point.
858
   * @param {number} pointDwName The ID of the cue point.
859
   * @return {string}
860
   * @private
861
   */
862
  getLabelForCuePoint_(pointDwName) {
863
    /** @type {?number} */
864
    let cIndex = this.getAdtlChunk_();
865
    if (cIndex !== null) {
866
      for (let i = 0, len = this.LIST[cIndex].subChunks.length; i < len; i++) {
867
        if (this.LIST[cIndex].subChunks[i].dwName ==
868
            pointDwName) {
869
          return this.LIST[cIndex].subChunks[i].value;
870
        }
871
      }
872
    }
873
    return '';
874
  }
875
876
  /**
877
   * Clear any LIST chunk labeled as 'adtl'.
878
   * @private
879
   */
880
  clearLISTadtl_() {
881
    for (let i = 0, len = this.LIST.length; i < len; i++) {
882
      if (this.LIST[i].format == 'adtl') {
883
        this.LIST.splice(i);
884
      }
885
    }
886
  }
887
888
  /**
889
   * Create a new 'labl' subchunk in a 'LIST' chunk of type 'adtl'.
890
   * @param {number} dwName The ID of the cue point.
891
   * @param {string} label The label for the cue point.
892
   * @private
893
   */
894
  setLabl_(dwName, label) {
895
    /** @type {?number} */
896
    let adtlIndex = this.getAdtlChunk_();
897
    if (adtlIndex === null) {
898
      this.LIST.push({
899
        chunkId: 'LIST',
900
        chunkSize: 4,
901
        format: 'adtl',
902
        subChunks: []});
903
      adtlIndex = this.LIST.length - 1;
904
    }
905
    this.setLabelText_(adtlIndex === null ? 0 : adtlIndex, dwName, label);
906
  }
907
908
  /**
909
   * Create a new 'labl' subchunk in a 'LIST' chunk of type 'adtl'.
910
   * @param {number} adtlIndex The index of the 'adtl' LIST in this.LIST.
911
   * @param {number} dwName The ID of the cue point.
912
   * @param {string} label The label for the cue point.
913
   * @private
914
   */
915
  setLabelText_(adtlIndex, dwName, label) {
916
    this.LIST[adtlIndex].subChunks.push({
917
      chunkId: 'labl',
918
      chunkSize: label.length,
919
      dwName: dwName,
920
      value: label
921
    });
922
    this.LIST[adtlIndex].chunkSize += label.length + 4 + 4 + 4 + 1;
923
  }
924
925
  /**
926
   * Return the index of the 'adtl' LIST in this.LIST.
927
   * @return {?number}
928
   * @private
929
   */
930
  getAdtlChunk_() {
931
    for (let i = 0, len = this.LIST.length; i < len; i++) {
932
      if (this.LIST[i].format == 'adtl') {
933
        return i;
934
      }
935
    }
936
    return null;
937
  }
938
939
  /**
940
   * Return the index of the INFO chunk in the LIST chunk.
941
   * @return {?number} the index of the INFO chunk.
942
   * @private
943
   */
944
  getLISTINFOIndex_() {
945
    /** @type {?number} */
946
    let index = null;
947
    for (let i = 0, len = this.LIST.length; i < len; i++) {
948
      if (this.LIST[i].format === 'INFO') {
949
        index = i;
950
        break;
951
      }
952
    }
953
    return index;
954
  }
955
956
  /**
957
   * Return the index of a tag in a FILE chunk.
958
   * @param {string} tag The tag name.
959
   * @return {!Object<string, ?number>}
960
   *    Object.LIST is the INFO index in LIST
961
   *    Object.TAG is the tag index in the INFO
962
   * @private
963
   */
964
  getTagIndex_(tag) {
965
    /** @type {!Object<string, ?number>} */
966
    let index = {LIST: null, TAG: null};
967
    for (let i = 0, len = this.LIST.length; i < len; i++) {
968
      if (this.LIST[i].format == 'INFO') {
969
        index.LIST = i;
970
        for (let j=0, subLen = this.LIST[i].subChunks.length; j < subLen; j++) {
971
          if (this.LIST[i].subChunks[j].chunkId == tag) {
972
            index.TAG = j;
973
            break;
974
          }
975
        }
976
        break;
977
      }
978
    }
979
    return index;
980
  }
981
982
  /**
983
   * Fix a RIFF tag format if possible, throw an error otherwise.
984
   * @param {string} tag The tag name.
985
   * @return {string} The tag name in proper fourCC format.
986
   * @private
987
   */
988
  fixTagName_(tag) {
989
    if (tag.constructor !== String) {
990
      throw new Error('Invalid tag name.');
991
    } else if (tag.length < 4) {
992
      for (let i = 0, len = 4 - tag.length; i < len; i++) {
993
        tag += ' ';
994
      }
995
    }
996
    return tag;
997
  }
998
999
  /**
1000
   * Reset attributes that should emptied when a file is
1001
   * created with the fromScratch() or fromBuffer() methods.
1002
   * @private
1003
   */
1004
  clearHeader_() {
1005
    this.fmt.cbSize = 0;
1006
    this.fmt.validBitsPerSample = 0;
1007
    this.fact.chunkId = '';
1008
    this.ds64.chunkId = '';
1009
  }
1010
1011
  /**
1012
   * Make the file 16-bit if it is not.
1013
   * @private
1014
   */
1015
  assure16Bit_() {
1016
    this.assureUncompressed_();
1017
    if (this.bitDepth != '16') {
1018
      this.toBitDepth('16');
1019
    }
1020
  }
1021
1022
  /**
1023
   * Uncompress the samples in case of a compressed file.
1024
   * @private
1025
   */
1026
  assureUncompressed_() {
1027
    if (this.bitDepth == '8a') {
1028
      this.fromALaw();
1029
    } else if (this.bitDepth == '8m') {
1030
      this.fromMuLaw();
1031
    } else if (this.bitDepth == '4') {
1032
      this.fromIMAADPCM();
1033
    }
1034
  }
1035
1036
  /**
1037
   * Set up the WaveFile object from a byte buffer.
1038
   * @param {!Array<number>|!Array<!Array<number>>|!ArrayBufferView}
1039
   *   samples The samples.
1040
   * @private
1041
   */
1042
  interleave_(samples) {
1043
    if (samples.length > 0) {
1044
      if (samples[0].constructor === Array) {
1045
        /** @type {!Array<number>} */
1046
        let finalSamples = [];
1047
        for (let i = 0, len = samples[0].length; i < len; i++) {
1048
          for (let j = 0, subLen = samples.length; j < subLen; j++) {
1049
            finalSamples.push(samples[j][i]);
1050
          }
1051
        }
1052
        samples = finalSamples;
1053
      }
1054
    }
1055
    return samples;
1056
  }
1057
1058
  /**
1059
   * Update the type definition used to read and write the samples.
1060
   * @private
1061
   */
1062
  updateDataType_() {
1063
    this.dataType = {
1064
      bits: ((parseInt(this.bitDepth, 10) - 1) | 7) + 1,
1065
      fp: this.bitDepth == '32f' || this.bitDepth == '64',
1066
      signed: this.bitDepth != '8',
1067
      be: this.container == 'RIFX'
1068
    };
1069
    if (['4', '8a', '8m'].indexOf(this.bitDepth) > -1 ) {
1070
      this.dataType.bits = 8;
1071
      this.dataType.signed = false;
1072
    }
1073
  }
1074
1075
  /**
1076
   * Return 'RIFF' if the container is 'RF64', the current container name
1077
   * otherwise. Used to enforce 'RIFF' when RF64 is not allowed.
1078
   * @return {string}
1079
   * @private
1080
   */
1081
  correctContainer_() {
1082
    return this.container == 'RF64' ? 'RIFF' : this.container;
1083
  }
1084
1085
  /**
1086
   * Truncate float samples on over and underflow.
1087
   * @private
1088
   */
1089
  truncateSamples_(samples) {
1090
    for (let i = 0, len = samples.length; i < len; i++) {
1091
      if (samples[i] > 1) {
1092
        samples[i] = 1;
1093
      } else if (samples[i] < -1) {
1094
        samples[i] = -1;
1095
      }
1096
    }
1097
  }
1098
1099
  /**
1100
   * Return a .wav file byte buffer with the data from the WaveFile object.
1101
   * The return value of this method can be written straight to disk.
1102
   * @return {!Uint8Array} The wav file bytes.
1103
   * @private
1104
   */
1105
  writeWavBuffer() {
1106
    this.uInt16_.be = this.container === 'RIFX';
1107
    this.uInt32_.be = this.uInt16_.be;
1108
    /** @type {!Array<!Array<number>>} */
1109
    let fileBody = [
1110
      this.getJunkBytes_(),
1111
      this.getDs64Bytes_(),
1112
      this.getBextBytes_(),
1113
      this.getFmtBytes_(),
1114
      this.getFactBytes_(),
1115
      packString(this.data.chunkId),
1116
      pack(this.data.samples.length, this.uInt32_),
1117
      this.data.samples,
1118
      this.getCueBytes_(),
1119
      this.getSmplBytes_(),
1120
      this.getLISTBytes_()
1121
    ];
1122
    /** @type {number} */
1123
    let fileBodyLength = 0;
1124
    for (let i=0; i<fileBody.length; i++) {
1125
      fileBodyLength += fileBody[i].length;
1126
    }
1127
    /** @type {!Uint8Array} */
1128
    let file = new Uint8Array(fileBodyLength + 12);
1129
    /** @type {number} */
1130
    let index = 0;
1131
    index = packStringTo(this.container, file, index);
1132
    index = packTo(fileBodyLength + 4, this.uInt32_, file, index);
1133
    index = packStringTo(this.format, file, index);
1134
    for (let i=0; i<fileBody.length; i++) {
1135
      file.set(fileBody[i], index);
1136
      index += fileBody[i].length;
1137
    }
1138
    return file;
1139
  }
1140
1141
  /**
1142
   * Return the bytes of the 'bext' chunk.
1143
   * @private
1144
   */
1145
  getBextBytes_() {
1146
    /** @type {!Array<number>} */
1147
    let bytes = [];
1148
    this.enforceBext_();
1149
    if (this.bext.chunkId) {
1150
      this.bext.chunkSize = 602 + this.bext.codingHistory.length;
1151
      bytes = bytes.concat(
1152
        packString(this.bext.chunkId),
1153
        pack(602 + this.bext.codingHistory.length, this.uInt32_),
1154
        this.writeString_(this.bext.description, 256),
1155
        this.writeString_(this.bext.originator, 32),
1156
        this.writeString_(this.bext.originatorReference, 32),
1157
        this.writeString_(this.bext.originationDate, 10),
1158
        this.writeString_(this.bext.originationTime, 8),
1159
        pack(this.bext.timeReference[0], this.uInt32_),
1160
        pack(this.bext.timeReference[1], this.uInt32_),
1161
        pack(this.bext.version, this.uInt16_),
1162
        this.writeString_(this.bext.UMID, 64),
1163
        pack(this.bext.loudnessValue, this.uInt16_),
1164
        pack(this.bext.loudnessRange, this.uInt16_),
1165
        pack(this.bext.maxTruePeakLevel, this.uInt16_),
1166
        pack(this.bext.maxMomentaryLoudness, this.uInt16_),
1167
        pack(this.bext.maxShortTermLoudness, this.uInt16_),
1168
        this.writeString_(this.bext.reserved, 180),
1169
        this.writeString_(
1170
          this.bext.codingHistory, this.bext.codingHistory.length));
1171
    }
1172
    return bytes;
1173
  }
1174
1175
  /**
1176
   * Make sure a 'bext' chunk is created if BWF data was created in a file.
1177
   * @private
1178
   */
1179
  enforceBext_() {
1180
    for (let prop in this.bext) {
1181
      if (this.bext.hasOwnProperty(prop)) {
1182
        if (this.bext[prop] && prop != 'timeReference') {
1183
          this.bext.chunkId = 'bext';
1184
          break;
1185
        }
1186
      }
1187
    }
1188
    if (this.bext.timeReference[0] || this.bext.timeReference[1]) {
1189
      this.bext.chunkId = 'bext';
1190
    }
1191
  }
1192
1193
  /**
1194
   * Return the bytes of the 'ds64' chunk.
1195
   * @return {!Array<number>} The 'ds64' chunk bytes.
1196
   * @private
1197
   */
1198
  getDs64Bytes_() {
1199
    /** @type {!Array<number>} */
1200
    let bytes = [];
1201
    if (this.ds64.chunkId) {
1202
      bytes = bytes.concat(
1203
        packString(this.ds64.chunkId),
1204
        pack(this.ds64.chunkSize, this.uInt32_),
1205
        pack(this.ds64.riffSizeHigh, this.uInt32_),
1206
        pack(this.ds64.riffSizeLow, this.uInt32_),
1207
        pack(this.ds64.dataSizeHigh, this.uInt32_),
1208
        pack(this.ds64.dataSizeLow, this.uInt32_),
1209
        pack(this.ds64.originationTime, this.uInt32_),
1210
        pack(this.ds64.sampleCountHigh, this.uInt32_),
1211
        pack(this.ds64.sampleCountLow, this.uInt32_));
1212
    }
1213
    //if (this.ds64.tableLength) {
1214
    //  ds64Bytes = ds64Bytes.concat(
1215
    //    pack(this.ds64.tableLength, this.uInt32_),
1216
    //    this.ds64.table);
1217
    //}
1218
    return bytes;
1219
  }
1220
1221
  /**
1222
   * Return the bytes of the 'cue ' chunk.
1223
   * @return {!Array<number>} The 'cue ' chunk bytes.
1224
   * @private
1225
   */
1226
  getCueBytes_() {
1227
    /** @type {!Array<number>} */
1228
    let bytes = [];
1229
    if (this.cue.chunkId) {
1230
      /** @type {!Array<number>} */
1231
      let cuePointsBytes = this.getCuePointsBytes_();
1232
      bytes = bytes.concat(
1233
        packString(this.cue.chunkId),
1234
        pack(cuePointsBytes.length + 4, this.uInt32_),
1235
        pack(this.cue.dwCuePoints, this.uInt32_),
1236
        cuePointsBytes);
1237
    }
1238
    return bytes;
1239
  }
1240
1241
  /**
1242
   * Return the bytes of the 'cue ' points.
1243
   * @return {!Array<number>} The 'cue ' points as an array of bytes.
1244
   * @private
1245
   */
1246
  getCuePointsBytes_() {
1247
    /** @type {!Array<number>} */
1248
    let points = [];
1249
    for (let i=0; i<this.cue.dwCuePoints; i++) {
1250
      points = points.concat(
1251
        pack(this.cue.points[i].dwName, this.uInt32_),
1252
        pack(this.cue.points[i].dwPosition, this.uInt32_),
1253
        packString(this.cue.points[i].fccChunk),
1254
        pack(this.cue.points[i].dwChunkStart, this.uInt32_),
1255
        pack(this.cue.points[i].dwBlockStart, this.uInt32_),
1256
        pack(this.cue.points[i].dwSampleOffset, this.uInt32_));
1257
    }
1258
    return points;
1259
  }
1260
1261
  /**
1262
   * Return the bytes of the 'smpl' chunk.
1263
   * @return {!Array<number>} The 'smpl' chunk bytes.
1264
   * @private
1265
   */
1266
  getSmplBytes_() {
1267
    /** @type {!Array<number>} */
1268
    let bytes = [];
1269
    if (this.smpl.chunkId) {
1270
      /** @type {!Array<number>} */
1271
      let smplLoopsBytes = this.getSmplLoopsBytes_();
1272
      bytes = bytes.concat(
1273
        packString(this.smpl.chunkId),
1274
        pack(smplLoopsBytes.length + 36, this.uInt32_),
1275
        pack(this.smpl.dwManufacturer, this.uInt32_),
1276
        pack(this.smpl.dwProduct, this.uInt32_),
1277
        pack(this.smpl.dwSamplePeriod, this.uInt32_),
1278
        pack(this.smpl.dwMIDIUnityNote, this.uInt32_),
1279
        pack(this.smpl.dwMIDIPitchFraction, this.uInt32_),
1280
        pack(this.smpl.dwSMPTEFormat, this.uInt32_),
1281
        pack(this.smpl.dwSMPTEOffset, this.uInt32_),
1282
        pack(this.smpl.dwNumSampleLoops, this.uInt32_),
1283
        pack(this.smpl.dwSamplerData, this.uInt32_),
1284
        smplLoopsBytes);
1285
    }
1286
    return bytes;
1287
  }
1288
1289
  /**
1290
   * Return the bytes of the 'smpl' loops.
1291
   * @return {!Array<number>} The 'smpl' loops as an array of bytes.
1292
   * @private
1293
   */
1294
  getSmplLoopsBytes_() {
1295
    /** @type {!Array<number>} */
1296
    let loops = [];
1297
    for (let i=0; i<this.smpl.dwNumSampleLoops; i++) {
1298
      loops = loops.concat(
1299
        pack(this.smpl.loops[i].dwName, this.uInt32_),
1300
        pack(this.smpl.loops[i].dwType, this.uInt32_),
1301
        pack(this.smpl.loops[i].dwStart, this.uInt32_),
1302
        pack(this.smpl.loops[i].dwEnd, this.uInt32_),
1303
        pack(this.smpl.loops[i].dwFraction, this.uInt32_),
1304
        pack(this.smpl.loops[i].dwPlayCount, this.uInt32_));
1305
    }
1306
    return loops;
1307
  }
1308
1309
  /**
1310
   * Return the bytes of the 'fact' chunk.
1311
   * @return {!Array<number>} The 'fact' chunk bytes.
1312
   * @private
1313
   */
1314
  getFactBytes_() {
1315
    /** @type {!Array<number>} */
1316
    let bytes = [];
1317
    if (this.fact.chunkId) {
1318
      bytes = bytes.concat(
1319
        packString(this.fact.chunkId),
1320
        pack(this.fact.chunkSize, this.uInt32_),
1321
        pack(this.fact.dwSampleLength, this.uInt32_));
1322
    }
1323
    return bytes;
1324
  }
1325
1326
  /**
1327
   * Return the bytes of the 'fmt ' chunk.
1328
   * @return {!Array<number>} The 'fmt' chunk bytes.
1329
   * @throws {Error} if no 'fmt ' chunk is present.
1330
   * @private
1331
   */
1332
  getFmtBytes_() {
1333
    /** @type {!Array<number>} */
1334
    let fmtBytes = [];
1335
    if (this.fmt.chunkId) {
1336
      return fmtBytes.concat(
1337
        packString(this.fmt.chunkId),
1338
        pack(this.fmt.chunkSize, this.uInt32_),
1339
        pack(this.fmt.audioFormat, this.uInt16_),
1340
        pack(this.fmt.numChannels, this.uInt16_),
1341
        pack(this.fmt.sampleRate, this.uInt32_),
1342
        pack(this.fmt.byteRate, this.uInt32_),
1343
        pack(this.fmt.blockAlign, this.uInt16_),
1344
        pack(this.fmt.bitsPerSample, this.uInt16_),
1345
        this.getFmtExtensionBytes_());
1346
    }
1347
    throw Error('Could not find the "fmt " chunk');
1348
  }
1349
1350
  /**
1351
   * Return the bytes of the fmt extension fields.
1352
   * @return {!Array<number>} The fmt extension bytes.
1353
   * @private
1354
   */
1355
  getFmtExtensionBytes_() {
1356
    /** @type {!Array<number>} */
1357
    let extension = [];
1358
    if (this.fmt.chunkSize > 16) {
1359
      extension = extension.concat(
1360
        pack(this.fmt.cbSize, this.uInt16_));
1361
    }
1362
    if (this.fmt.chunkSize > 18) {
1363
      extension = extension.concat(
1364
        pack(this.fmt.validBitsPerSample, this.uInt16_));
1365
    }
1366
    if (this.fmt.chunkSize > 20) {
1367
      extension = extension.concat(
1368
        pack(this.fmt.dwChannelMask, this.uInt32_));
1369
    }
1370
    if (this.fmt.chunkSize > 24) {
1371
      extension = extension.concat(
1372
        pack(this.fmt.subformat[0], this.uInt32_),
1373
        pack(this.fmt.subformat[1], this.uInt32_),
1374
        pack(this.fmt.subformat[2], this.uInt32_),
1375
        pack(this.fmt.subformat[3], this.uInt32_));
1376
    }
1377
    return extension;
1378
  }
1379
1380
  /**
1381
   * Return the bytes of the 'LIST' chunk.
1382
   * @return {!Array<number>} The 'LIST' chunk bytes.
1383
   */
1384
  getLISTBytes_() {
1385
    /** @type {!Array<number>} */
1386
    let bytes = [];
1387
    for (let i=0; i<this.LIST.length; i++) {
1388
      /** @type {!Array<number>} */
1389
      let subChunksBytes = this.getLISTSubChunksBytes_(
1390
          this.LIST[i].subChunks, this.LIST[i].format);
1391
      bytes = bytes.concat(
1392
        packString(this.LIST[i].chunkId),
1393
        pack(subChunksBytes.length + 4, this.uInt32_),
1394
        packString(this.LIST[i].format),
1395
        subChunksBytes);
1396
    }
1397
    return bytes;
1398
  }
1399
1400
  /**
1401
   * Return the bytes of the sub chunks of a 'LIST' chunk.
1402
   * @param {!Array<!Object>} subChunks The 'LIST' sub chunks.
1403
   * @param {string} format The format of the 'LIST' chunk.
1404
   *    Currently supported values are 'adtl' or 'INFO'.
1405
   * @return {!Array<number>} The sub chunk bytes.
1406
   * @private
1407
   */
1408
  getLISTSubChunksBytes_(subChunks, format) {
1409
    /** @type {!Array<number>} */
1410
    let bytes = [];
1411
    for (let i=0; i<subChunks.length; i++) {
1412
      if (format == 'INFO') {
1413
        bytes = bytes.concat(
1414
          packString(subChunks[i].chunkId),
1415
          pack(subChunks[i].value.length + 1, this.uInt32_),
1416
          this.writeString_(
1417
            subChunks[i].value, subChunks[i].value.length));
1418
        bytes.push(0);
1419
      } else if (format == 'adtl') {
1420
        if (['labl', 'note'].indexOf(subChunks[i].chunkId) > -1) {
1421
          bytes = bytes.concat(
1422
            packString(subChunks[i].chunkId),
1423
            pack(
1424
              subChunks[i].value.length + 4 + 1, this.uInt32_),
1425
            pack(subChunks[i].dwName, this.uInt32_),
1426
            this.writeString_(
1427
              subChunks[i].value,
1428
              subChunks[i].value.length));
1429
          bytes.push(0);
1430
        } else if (subChunks[i].chunkId == 'ltxt') {
1431
          bytes = bytes.concat(
1432
            this.getLtxtChunkBytes_(subChunks[i]));
1433
        }
1434
      }
1435
      if (bytes.length % 2) {
1436
        bytes.push(0);
1437
      }
1438
    }
1439
    return bytes;
1440
  }
1441
1442
  /**
1443
   * Return the bytes of a 'ltxt' chunk.
1444
   * @param {!Object} ltxt the 'ltxt' chunk.
1445
   * @private
1446
   */
1447
  getLtxtChunkBytes_(ltxt) {
1448
    return [].concat(
1449
      packString(ltxt.chunkId),
1450
      pack(ltxt.value.length + 20, this.uInt32_),
1451
      pack(ltxt.dwName, this.uInt32_),
1452
      pack(ltxt.dwSampleLength, this.uInt32_),
1453
      pack(ltxt.dwPurposeID, this.uInt32_),
1454
      pack(ltxt.dwCountry, this.uInt16_),
1455
      pack(ltxt.dwLanguage, this.uInt16_),
1456
      pack(ltxt.dwDialect, this.uInt16_),
1457
      pack(ltxt.dwCodePage, this.uInt16_),
1458
      this.writeString_(ltxt.value, ltxt.value.length));
1459
  }
1460
1461
  /**
1462
   * Return the bytes of the 'junk' chunk.
1463
   * @private
1464
   */
1465
  getJunkBytes_() {
1466
    /** @type {!Array<number>} */
1467
    let bytes = [];
1468
    if (this.junk.chunkId) {
1469
      return bytes.concat(
1470
        packString(this.junk.chunkId),
1471
        pack(this.junk.chunkData.length, this.uInt32_),
1472
        this.junk.chunkData);
1473
    }
1474
    return bytes;
1475
  }
1476
1477
  /**
1478
   * Set up the WaveFile object from a byte buffer.
1479
   * @param {!Uint8Array} buffer The buffer.
1480
   * @param {boolean} samples True if the samples should be loaded.
1481
   * @throws {Error} If container is not RIFF, RIFX or RF64.
1482
   * @throws {Error} If no 'fmt ' chunk is found.
1483
   * @throws {Error} If no 'data' chunk is found.
1484
   */
1485
  readWavBuffer(buffer, samples) {
1486
    this.head_ = 0;
1487
    this.readRIFFChunk_(buffer);
1488
    this.getSignature_(buffer);
1489
    this.readDs64Chunk_(buffer);
1490
    this.readFmtChunk_(buffer);
1491
    this.readFactChunk_(buffer);
1492
    this.readBextChunk_(buffer);
1493
    this.readCueChunk_(buffer);
1494
    this.readSmplChunk_(buffer);
1495
    this.readDataChunk_(buffer, samples);
1496
    this.readJunkChunk_(buffer);
1497
    this.readLISTChunk_(buffer);
1498
  }
1499
1500
  /**
1501
   * Return the chunks in a RIFF/RIFX file.
1502
   * @param {!Uint8Array} buffer The file bytes.
1503
   */
1504
  getSignature_(buffer) {
1505
      this.head_ = 0;
1506
      /** @type {string} */
1507
      let chunkId = this.getChunkId_(buffer, 0);
1508
      this.uInt32_.be = chunkId == 'RIFX';
1509
      /** @type {string} */
1510
      let format = unpackString(buffer, 8, 12);
1511
      this.head_ += 4;
1512
      this.signature = {
1513
          chunkId: chunkId,
1514
          chunkSize: this.getChunkSize_(buffer, 0),
1515
          format: format,
1516
          subChunks: this.getSubChunksIndex_(buffer)
1517
      };
1518
  }
1519
1520
  /**
1521
    * Find a chunk by its fourCC_ in a array of RIFF chunks.
1522
    * @param {string} chunkId The chunk fourCC_.
1523
    * @param {boolean} multiple True if there may be multiple chunks
1524
    *    with the same chunkId.
1525
    * @return {Object}
1526
    */
1527
  findChunk_(chunkId, multiple=false) {
1528
    /** @type {!Array<!Object>} */
1529
    let chunks = this.signature.subChunks;
1530
    /** @type {!Array<!Object>} */
1531
    let chunk = [];
1532
    for (let i=0; i<chunks.length; i++) {
1533
      if (chunks[i].chunkId == chunkId) {
1534
        if (multiple) {
1535
          chunk.push(chunks[i]);
1536
        } else {
1537
          return chunks[i];
1538
        }
1539
      }
1540
    }
1541
    if (chunkId == 'LIST') {
1542
      return chunk.length ? chunk : null;
1543
    }
1544
    return null;
1545
  }
1546
1547
  /**
1548
   * Return the sub chunks of a RIFF file.
1549
   * @param {!Uint8Array} buffer the RIFF file bytes.
1550
   * @return {!Array<Object>} The subchunks of a RIFF/RIFX or LIST chunk.
1551
   * @private
1552
   */
1553
  getSubChunksIndex_(buffer) {
1554
      /** @type {!Array<!Object>} */
1555
      let chunks = [];
1556
      /** @type {number} */
1557
      let i = this.head_;
1558
      while(i <= buffer.length - 8) {
1559
          chunks.push(this.getSubChunkIndex_(buffer, i));
1560
          i += 8 + chunks[chunks.length - 1].chunkSize;
1561
          i = i % 2 ? i + 1 : i;
1562
      }
1563
      return chunks;
1564
  }
1565
1566
  /**
1567
   * Return a sub chunk from a RIFF file.
1568
   * @param {!Uint8Array} buffer the RIFF file bytes.
1569
   * @param {number} index The start index of the chunk.
1570
   * @return {!Object} A subchunk of a RIFF/RIFX or LIST chunk.
1571
   * @private
1572
   */
1573
  getSubChunkIndex_(buffer, index) {
1574
      /** @type {!Object} */
1575
      let chunk = {
1576
          chunkId: this.getChunkId_(buffer, index),
1577
          chunkSize: this.getChunkSize_(buffer, index),
1578
      };
1579
      if (chunk.chunkId == 'LIST') {
1580
          chunk.format = unpackString(buffer, index + 8, index + 12);
1581
          this.head_ += 4;
1582
          chunk.subChunks = this.getSubChunksIndex_(buffer);
1583
      } else {
1584
          /** @type {number} */
1585
          let realChunkSize = chunk.chunkSize % 2 ?
1586
              chunk.chunkSize + 1 : chunk.chunkSize;
1587
          this.head_ = index + 8 + realChunkSize;
1588
          chunk.chunkData = {
1589
              start: index + 8,
1590
              end: this.head_
1591
          };
1592
      }
1593
      return chunk;
1594
  }
1595
1596
  /**
1597
   * Return the fourCC_ of a chunk.
1598
   * @param {!Uint8Array} buffer the RIFF file bytes.
1599
   * @param {number} index The start index of the chunk.
1600
   * @return {string} The id of the chunk.
1601
   * @private
1602
   */
1603
  getChunkId_(buffer, index) {
1604
      this.head_ += 4;
1605
      return unpackString(buffer, index, index + 4);
1606
  }
1607
1608
  /**
1609
   * Return the size of a chunk.
1610
   * @param {!Uint8Array} buffer the RIFF file bytes.
1611
   * @param {number} index The start index of the chunk.
1612
   * @return {number} The size of the chunk without the id and size fields.
1613
   * @private
1614
   */
1615
  getChunkSize_(buffer, index) {
1616
      this.head_ += 4;
1617
      return unpack(buffer, this.uInt32_, index + 4);
1618
  }
1619
1620
1621
1622
  /**
1623
   * Read the RIFF chunk a wave file.
1624
   * @param {!Uint8Array} bytes A wav buffer.
1625
   * @throws {Error} If no 'RIFF' chunk is found.
1626
   * @private
1627
   */
1628
  readRIFFChunk_(bytes) {
1629
    this.head_ = 0;
1630
    this.container = this.readString_(bytes, 4);
1631
    if (['RIFF', 'RIFX', 'RF64'].indexOf(this.container) === -1) {
1632
      throw Error('Not a supported format.');
1633
    }
1634
    this.uInt16_.be = this.container === 'RIFX';
1635
    this.uInt32_.be = this.uInt16_.be;
1636
    this.chunkSize = this.read_(bytes, this.uInt32_);
1637
    this.format = this.readString_(bytes, 4);
1638
    if (this.format != 'WAVE') {
1639
      throw Error('Could not find the "WAVE" format identifier');
1640
    }
1641
  }
1642
1643
  /**
1644
   * Read the 'fmt ' chunk of a wave file.
1645
   * @param {!Uint8Array} buffer The wav file buffer.
1646
   * @throws {Error} If no 'fmt ' chunk is found.
1647
   * @private
1648
   */
1649
  readFmtChunk_(buffer) {
1650
    /** @type {?Object} */
1651
    let chunk = this.findChunk_('fmt ');
1652
    if (chunk) {
1653
      this.head_ = chunk.chunkData.start;
1654
      this.fmt.chunkId = chunk.chunkId;
1655
      this.fmt.chunkSize = chunk.chunkSize;
1656
      this.fmt.audioFormat = this.read_(buffer, this.uInt16_);
1657
      this.fmt.numChannels = this.read_(buffer, this.uInt16_);
1658
      this.fmt.sampleRate = this.read_(buffer, this.uInt32_);
1659
      this.fmt.byteRate = this.read_(buffer, this.uInt32_);
1660
      this.fmt.blockAlign = this.read_(buffer, this.uInt16_);
1661
      this.fmt.bitsPerSample = this.read_(buffer, this.uInt16_);
1662
      this.readFmtExtension_(buffer);
1663
    } else {
1664
      throw Error('Could not find the "fmt " chunk');
1665
    }
1666
  }
1667
1668
  /**
1669
   * Read the 'fmt ' chunk extension.
1670
   * @param {!Uint8Array} buffer The wav file buffer.
1671
   * @private
1672
   */
1673
  readFmtExtension_(buffer) {
1674
    if (this.fmt.chunkSize > 16) {
1675
      this.fmt.cbSize = this.read_(buffer, this.uInt16_);
1676
      if (this.fmt.chunkSize > 18) {
1677
        this.fmt.validBitsPerSample = this.read_(buffer, this.uInt16_);
1678
        if (this.fmt.chunkSize > 20) {
1679
          this.fmt.dwChannelMask = this.read_(buffer, this.uInt32_);
1680
          this.fmt.subformat = [
1681
            this.read_(buffer, this.uInt32_),
1682
            this.read_(buffer, this.uInt32_),
1683
            this.read_(buffer, this.uInt32_),
1684
            this.read_(buffer, this.uInt32_)];
1685
        }
1686
      }
1687
    }
1688
  }
1689
1690
  /**
1691
   * Read the 'fact' chunk of a wav file.
1692
   * @param {!Uint8Array} buffer The wav file buffer.
1693
   * @private
1694
   */
1695
  readFactChunk_(buffer) {
1696
    /** @type {?Object} */
1697
    let chunk = this.findChunk_('fact');
1698
    if (chunk) {
1699
      this.head_ = chunk.chunkData.start;
1700
      this.fact.chunkId = chunk.chunkId;
1701
      this.fact.chunkSize = chunk.chunkSize;
1702
      this.fact.dwSampleLength = this.read_(buffer, this.uInt32_);
1703
    }
1704
  }
1705
1706
  /**
1707
   * Read the 'cue ' chunk of a wave file.
1708
   * @param {!Uint8Array} buffer The wav file buffer.
1709
   * @private
1710
   */
1711
  readCueChunk_(buffer) {
1712
    /** @type {?Object} */
1713
    let chunk = this.findChunk_('cue ');
1714
    if (chunk) {
1715
      this.head_ = chunk.chunkData.start;
1716
      this.cue.chunkId = chunk.chunkId;
1717
      this.cue.chunkSize = chunk.chunkSize;
1718
      this.cue.dwCuePoints = this.read_(buffer, this.uInt32_);
1719
      for (let i = 0; i < this.cue.dwCuePoints; i++) {
1720
        this.cue.points.push({
1721
          dwName: this.read_(buffer, this.uInt32_),
1722
          dwPosition: this.read_(buffer, this.uInt32_),
1723
          fccChunk: this.readString_(buffer, 4),
1724
          dwChunkStart: this.read_(buffer, this.uInt32_),
1725
          dwBlockStart: this.read_(buffer, this.uInt32_),
1726
          dwSampleOffset: this.read_(buffer, this.uInt32_),
1727
        });
1728
      }
1729
    }
1730
  }
1731
1732
  /**
1733
   * Read the 'smpl' chunk of a wave file.
1734
   * @param {!Uint8Array} buffer The wav file buffer.
1735
   * @private
1736
   */
1737
  readSmplChunk_(buffer) {
1738
    /** @type {?Object} */
1739
    let chunk = this.findChunk_('smpl');
1740
    if (chunk) {
1741
      this.head_ = chunk.chunkData.start;
1742
      this.smpl.chunkId = chunk.chunkId;
1743
      this.smpl.chunkSize = chunk.chunkSize;
1744
      this.smpl.dwManufacturer = this.read_(buffer, this.uInt32_);
1745
      this.smpl.dwProduct = this.read_(buffer, this.uInt32_);
1746
      this.smpl.dwSamplePeriod = this.read_(buffer, this.uInt32_);
1747
      this.smpl.dwMIDIUnityNote = this.read_(buffer, this.uInt32_);
1748
      this.smpl.dwMIDIPitchFraction = this.read_(buffer, this.uInt32_);
1749
      this.smpl.dwSMPTEFormat = this.read_(buffer, this.uInt32_);
1750
      this.smpl.dwSMPTEOffset = this.read_(buffer, this.uInt32_);
1751
      this.smpl.dwNumSampleLoops = this.read_(buffer, this.uInt32_);
1752
      this.smpl.dwSamplerData = this.read_(buffer, this.uInt32_);
1753
      for (let i = 0; i < this.smpl.dwNumSampleLoops; i++) {
1754
        this.smpl.loops.push({
1755
          dwName: this.read_(buffer, this.uInt32_),
1756
          dwType: this.read_(buffer, this.uInt32_),
1757
          dwStart: this.read_(buffer, this.uInt32_),
1758
          dwEnd: this.read_(buffer, this.uInt32_),
1759
          dwFraction: this.read_(buffer, this.uInt32_),
1760
          dwPlayCount: this.read_(buffer, this.uInt32_),
1761
        });
1762
      }
1763
    }
1764
  }
1765
1766
  /**
1767
   * Read the 'data' chunk of a wave file.
1768
   * @param {!Uint8Array} buffer The wav file buffer.
1769
   * @param {boolean} samples True if the samples should be loaded.
1770
   * @throws {Error} If no 'data' chunk is found.
1771
   * @private
1772
   */
1773
  readDataChunk_(buffer, samples) {
1774
    /** @type {?Object} */
1775
    let chunk = this.findChunk_('data');
1776
    if (chunk) {
1777
      this.data.chunkId = 'data';
1778
      this.data.chunkSize = chunk.chunkSize;
1779
      if (samples) {
1780
        this.data.samples = buffer.slice(
1781
          chunk.chunkData.start,
1782
          chunk.chunkData.end);
1783
      }
1784
    } else {
1785
      throw Error('Could not find the "data" chunk');
1786
    }
1787
  }
1788
1789
  /**
1790
   * Read the 'bext' chunk of a wav file.
1791
   * @param {!Uint8Array} buffer The wav file buffer.
1792
   * @private
1793
   */
1794
  readBextChunk_(buffer) {
1795
    /** @type {?Object} */
1796
    let chunk = this.findChunk_('bext');
1797
    if (chunk) {
1798
      this.head_ = chunk.chunkData.start;
1799
      this.bext.chunkId = chunk.chunkId;
1800
      this.bext.chunkSize = chunk.chunkSize;
1801
      this.bext.description = this.readString_(buffer, 256);
1802
      this.bext.originator = this.readString_(buffer, 32);
1803
      this.bext.originatorReference = this.readString_(buffer, 32);
1804
      this.bext.originationDate = this.readString_(buffer, 10);
1805
      this.bext.originationTime = this.readString_(buffer, 8);
1806
      this.bext.timeReference = [
1807
        this.read_(buffer, this.uInt32_),
1808
        this.read_(buffer, this.uInt32_)];
1809
      this.bext.version = this.read_(buffer, this.uInt16_);
1810
      this.bext.UMID = this.readString_(buffer, 64);
1811
      this.bext.loudnessValue = this.read_(buffer, this.uInt16_);
1812
      this.bext.loudnessRange = this.read_(buffer, this.uInt16_);
1813
      this.bext.maxTruePeakLevel = this.read_(buffer, this.uInt16_);
1814
      this.bext.maxMomentaryLoudness = this.read_(buffer, this.uInt16_);
1815
      this.bext.maxShortTermLoudness = this.read_(buffer, this.uInt16_);
1816
      this.bext.reserved = this.readString_(buffer, 180);
1817
      this.bext.codingHistory = this.readString_(
1818
        buffer, this.bext.chunkSize - 602);
1819
    }
1820
  }
1821
1822
  /**
1823
   * Read the 'ds64' chunk of a wave file.
1824
   * @param {!Uint8Array} buffer The wav file buffer.
1825
   * @throws {Error} If no 'ds64' chunk is found and the file is RF64.
1826
   * @private
1827
   */
1828
  readDs64Chunk_(buffer) {
1829
    /** @type {?Object} */
1830
    let chunk = this.findChunk_('ds64');
1831
    if (chunk) {
1832
      this.head_ = chunk.chunkData.start;
1833
      this.ds64.chunkId = chunk.chunkId;
1834
      this.ds64.chunkSize = chunk.chunkSize;
1835
      this.ds64.riffSizeHigh = this.read_(buffer, this.uInt32_);
1836
      this.ds64.riffSizeLow = this.read_(buffer, this.uInt32_);
1837
      this.ds64.dataSizeHigh = this.read_(buffer, this.uInt32_);
1838
      this.ds64.dataSizeLow = this.read_(buffer, this.uInt32_);
1839
      this.ds64.originationTime = this.read_(buffer, this.uInt32_);
1840
      this.ds64.sampleCountHigh = this.read_(buffer, this.uInt32_);
1841
      this.ds64.sampleCountLow = this.read_(buffer, this.uInt32_);
1842
      //if (wav.ds64.chunkSize > 28) {
1843
      //  wav.ds64.tableLength = unpack(
1844
      //    chunkData.slice(28, 32), uInt32_);
1845
      //  wav.ds64.table = chunkData.slice(
1846
      //     32, 32 + wav.ds64.tableLength);
1847
      //}
1848
    } else {
1849
      if (this.container == 'RF64') {
1850
        throw Error('Could not find the "ds64" chunk');
1851
      }
1852
    }
1853
  }
1854
1855
  /**
1856
   * Read the 'LIST' chunks of a wave file.
1857
   * @param {!Uint8Array} buffer The wav file buffer.
1858
   * @private
1859
   */
1860
  readLISTChunk_(buffer) {
1861
    /** @type {?Object} */
1862
    let listChunks = this.findChunk_('LIST', true);
1863
    if (listChunks !== null) {
1864
      for (let j=0; j < listChunks.length; j++) {
1865
        /** @type {!Object} */
1866
        let subChunk = listChunks[j];
1867
        this.LIST.push({
1868
          chunkId: subChunk.chunkId,
1869
          chunkSize: subChunk.chunkSize,
1870
          format: subChunk.format,
1871
          subChunks: []});
1872
        for (let x=0; x<subChunk.subChunks.length; x++) {
1873
          this.readLISTSubChunks_(subChunk.subChunks[x],
1874
            subChunk.format, buffer);
1875
        }
1876
      }
1877
    }
1878
  }
1879
1880
  /**
1881
   * Read the sub chunks of a 'LIST' chunk.
1882
   * @param {!Object} subChunk The 'LIST' subchunks.
1883
   * @param {string} format The 'LIST' format, 'adtl' or 'INFO'.
1884
   * @param {!Uint8Array} buffer The wav file buffer.
1885
   * @private
1886
   */
1887
  readLISTSubChunks_(subChunk, format, buffer) {
1888
    if (format == 'adtl') {
1889
      if (['labl', 'note','ltxt'].indexOf(subChunk.chunkId) > -1) {
1890
        this.head_ = subChunk.chunkData.start;
1891
        /** @type {!Object<string, string|number>} */
1892
        let item = {
1893
          chunkId: subChunk.chunkId,
1894
          chunkSize: subChunk.chunkSize,
1895
          dwName: this.read_(buffer, this.uInt32_)
1896
        };
1897
        if (subChunk.chunkId == 'ltxt') {
1898
          item.dwSampleLength = this.read_(buffer, this.uInt32_);
1899
          item.dwPurposeID = this.read_(buffer, this.uInt32_);
1900
          item.dwCountry = this.read_(buffer, this.uInt16_);
1901
          item.dwLanguage = this.read_(buffer, this.uInt16_);
1902
          item.dwDialect = this.read_(buffer, this.uInt16_);
1903
          item.dwCodePage = this.read_(buffer, this.uInt16_);
1904
        }
1905
        item.value = this.readZSTR_(buffer, this.head_);
1906
        this.LIST[this.LIST.length - 1].subChunks.push(item);
1907
      }
1908
    // RIFF INFO tags like ICRD, ISFT, ICMT
1909
    } else if(format == 'INFO') {
1910
      this.head_ = subChunk.chunkData.start;
1911
      this.LIST[this.LIST.length - 1].subChunks.push({
1912
        chunkId: subChunk.chunkId,
1913
        chunkSize: subChunk.chunkSize,
1914
        value: this.readZSTR_(buffer, this.head_)
1915
      });
1916
    }
1917
  }
1918
1919
  /**
1920
   * Read the 'junk' chunk of a wave file.
1921
   * @param {!Uint8Array} buffer The wav file buffer.
1922
   * @private
1923
   */
1924
  readJunkChunk_(buffer) {
1925
    /** @type {?Object} */
1926
    let chunk = this.findChunk_('junk');
1927
    if (chunk) {
1928
      this.junk = {
1929
        chunkId: chunk.chunkId,
1930
        chunkSize: chunk.chunkSize,
1931
        chunkData: [].slice.call(buffer.slice(
1932
          chunk.chunkData.start,
1933
          chunk.chunkData.end))
1934
      };
1935
    }
1936
  }
1937
1938
  /**
1939
   * Define the header of a wav file.
1940
   * @param {string} bitDepthCode The audio bit depth
1941
   * @param {number} numChannels The number of channels
1942
   * @param {number} sampleRate The sample rate.
1943
   * @param {number} numBytes The number of bytes each sample use.
1944
   * @param {number} samplesLength The length of the samples in bytes.
1945
   * @param {!Object} options The extra options, like container defintion.
1946
   * @private
1947
   */
1948
  makeWavHeader(
1949
    bitDepthCode, numChannels, sampleRate, numBytes, samplesLength, options) {
1950
    if (bitDepthCode == '4') {
1951
      this.createADPCMHeader_(
1952
        bitDepthCode, numChannels, sampleRate, numBytes, samplesLength, options);
1953
1954
    } else if (bitDepthCode == '8a' || bitDepthCode == '8m') {
1955
      this.createALawMulawHeader_(
1956
        bitDepthCode, numChannels, sampleRate, numBytes, samplesLength, options);
1957
1958
    } else if(Object.keys(this.WAV_AUDIO_FORMATS).indexOf(bitDepthCode) == -1 ||
1959
        numChannels > 2) {
1960
      this.createExtensibleHeader_(
1961
        bitDepthCode, numChannels, sampleRate, numBytes, samplesLength, options);
1962
1963
    } else {
1964
      this.createPCMHeader_(
1965
        bitDepthCode, numChannels, sampleRate, numBytes, samplesLength, options);      
1966
    }
1967
  }
1968
1969
  /**
1970
   * Create the header of a linear PCM wave file.
1971
   * @param {string} bitDepthCode The audio bit depth
1972
   * @param {number} numChannels The number of channels
1973
   * @param {number} sampleRate The sample rate.
1974
   * @param {number} numBytes The number of bytes each sample use.
1975
   * @param {number} samplesLength The length of the samples in bytes.
1976
   * @param {!Object} options The extra options, like container defintion.
1977
   * @private
1978
   */
1979
  createPCMHeader_(
1980
    bitDepthCode, numChannels, sampleRate, numBytes, samplesLength, options) {
1981
    this.container = options.container;
1982
    this.chunkSize = 36 + samplesLength;
1983
    this.format = 'WAVE';
1984
    this.bitDepth = bitDepthCode;
1985
    this.fmt = {
1986
      chunkId: 'fmt ',
1987
      chunkSize: 16,
1988
      audioFormat: this.WAV_AUDIO_FORMATS[bitDepthCode] || 65534,
1989
      numChannels: numChannels,
1990
      sampleRate: sampleRate,
1991
      byteRate: (numChannels * numBytes) * sampleRate,
1992
      blockAlign: numChannels * numBytes,
1993
      bitsPerSample: parseInt(bitDepthCode, 10),
1994
      cbSize: 0,
1995
      validBitsPerSample: 0,
1996
      dwChannelMask: 0,
1997
      subformat: []
1998
    };
1999
  }
2000
2001
  /**
2002
   * Create the header of a ADPCM wave file.
2003
   * @param {string} bitDepthCode The audio bit depth
2004
   * @param {number} numChannels The number of channels
2005
   * @param {number} sampleRate The sample rate.
2006
   * @param {number} numBytes The number of bytes each sample use.
2007
   * @param {number} samplesLength The length of the samples in bytes.
2008
   * @param {!Object} options The extra options, like container defintion.
2009
   * @private
2010
   */
2011
  createADPCMHeader_(
2012
    bitDepthCode, numChannels, sampleRate, numBytes, samplesLength, options) {
2013
    this.createPCMHeader_(
2014
      bitDepthCode, numChannels, sampleRate, numBytes, samplesLength, options);
2015
    this.chunkSize = 40 + samplesLength;
2016
    this.fmt.chunkSize = 20;
2017
    this.fmt.byteRate = 4055;
2018
    this.fmt.blockAlign = 256;
2019
    this.fmt.bitsPerSample = 4;
2020
    this.fmt.cbSize = 2;
2021
    this.fmt.validBitsPerSample = 505;
2022
    this.fact = {
2023
      chunkId: 'fact',
2024
      chunkSize: 4,
2025
      dwSampleLength: samplesLength * 2
2026
    };
2027
  }
2028
2029
  /**
2030
   * Create the header of WAVE_FORMAT_EXTENSIBLE file.
2031
   * @param {string} bitDepthCode The audio bit depth
2032
   * @param {number} numChannels The number of channels
2033
   * @param {number} sampleRate The sample rate.
2034
   * @param {number} numBytes The number of bytes each sample use.
2035
   * @param {number} samplesLength The length of the samples in bytes.
2036
   * @param {!Object} options The extra options, like container defintion.
2037
   * @private
2038
   */
2039
  createExtensibleHeader_(
2040
      bitDepthCode, numChannels, sampleRate, numBytes, samplesLength, options) {
2041
    this.createPCMHeader_(
2042
      bitDepthCode, numChannels, sampleRate, numBytes, samplesLength, options);
2043
    this.chunkSize = 36 + 24 + samplesLength;
2044
    this.fmt.chunkSize = 40;
2045
    this.fmt.bitsPerSample = ((parseInt(bitDepthCode, 10) - 1) | 7) + 1;
2046
    this.fmt.cbSize = 22;
2047
    this.fmt.validBitsPerSample = parseInt(bitDepthCode, 10);
2048
    this.fmt.dwChannelMask = this.getDwChannelMask_(numChannels);
2049
    // subformat 128-bit GUID as 4 32-bit values
2050
    // only supports uncompressed integer PCM samples
2051
    this.fmt.subformat = [1, 1048576, 2852126848, 1905997824];
2052
  }
2053
2054
  /**
2055
   * Create the header of mu-Law and A-Law wave files.
2056
   * @param {string} bitDepthCode The audio bit depth
2057
   * @param {number} numChannels The number of channels
2058
   * @param {number} sampleRate The sample rate.
2059
   * @param {number} numBytes The number of bytes each sample use.
2060
   * @param {number} samplesLength The length of the samples in bytes.
2061
   * @param {!Object} options The extra options, like container defintion.
2062
   * @private
2063
   */
2064
  createALawMulawHeader_(
2065
      bitDepthCode, numChannels, sampleRate, numBytes, samplesLength, options) {
2066
    this.createPCMHeader_(
2067
      bitDepthCode, numChannels, sampleRate, numBytes, samplesLength, options);
2068
    this.chunkSize = 40 + samplesLength;
2069
    this.fmt.chunkSize = 20;
2070
    this.fmt.cbSize = 2;
2071
    this.fmt.validBitsPerSample = 8;
2072
    this.fact = {
2073
      chunkId: 'fact',
2074
      chunkSize: 4,
2075
      dwSampleLength: samplesLength
2076
    };
2077
  }
2078
2079
  /**
2080
   * Get the value for dwChannelMask according to the number of channels.
2081
   * @return {number} the dwChannelMask value.
2082
   * @private
2083
   */
2084
  getDwChannelMask_(numChannels) {
2085
    /** @type {number} */
2086
    let dwChannelMask = 0;
2087
    // mono = FC
2088
    if (numChannels === 1) {
2089
      dwChannelMask = 0x4;
2090
    // stereo = FL, FR
2091
    } else if (numChannels === 2) {
2092
      dwChannelMask = 0x3;
2093
    // quad = FL, FR, BL, BR
2094
    } else if (numChannels === 4) {
2095
      dwChannelMask = 0x33;
2096
    // 5.1 = FL, FR, FC, LF, BL, BR
2097
    } else if (numChannels === 6) {
2098
      dwChannelMask = 0x3F;
2099
    // 7.1 = FL, FR, FC, LF, BL, BR, SL, SR
2100
    } else if (numChannels === 8) {
2101
      dwChannelMask = 0x63F;
2102
    }
2103
    return dwChannelMask;
2104
  }
2105
2106
  /**
2107
   * Validate the header of the file.
2108
   * @throws {Error} If any property of the object appears invalid.
2109
   * @private
2110
   */
2111
  validateWavHeader_() {
2112
    this.validateBitDepth_();
2113
    this.validateNumChannels_();
2114
    this.validateSampleRate_();
2115
  }
2116
2117
  /**
2118
   * Validate the bit depth.
2119
   * @return {boolean} True is the bit depth is valid.
2120
   * @throws {Error} If bit depth is invalid.
2121
   * @private
2122
   */
2123
  validateBitDepth_() {
2124
    if (!this.WAV_AUDIO_FORMATS[this.bitDepth]) {
2125
      if (parseInt(this.bitDepth, 10) > 8 &&
2126
          parseInt(this.bitDepth, 10) < 54) {
2127
        return true;
2128
      }
2129
      throw new Error('Invalid bit depth.');
2130
    }
2131
    return true;
2132
  }
2133
2134
  /**
2135
   * Validate the number of channels.
2136
   * @return {boolean} True is the number of channels is valid.
2137
   * @throws {Error} If the number of channels is invalid.
2138
   * @private
2139
   */
2140
  validateNumChannels_() {
2141
    /** @type {number} */
2142
    let blockAlign = this.fmt.numChannels * this.fmt.bitsPerSample / 8;
2143
    if (this.fmt.numChannels < 1 || blockAlign > 65535) {
2144
      throw new Error('Invalid number of channels.');
2145
    }
2146
    return true;
2147
  }
2148
2149
  /**
2150
   * Validate the sample rate value.
2151
   * @return {boolean} True is the sample rate is valid.
2152
   * @throws {Error} If the sample rate is invalid.
2153
   * @private
2154
   */
2155
  validateSampleRate_() {
2156
    /** @type {number} */
2157
    let byteRate = this.fmt.numChannels *
2158
      (this.fmt.bitsPerSample / 8) * this.fmt.sampleRate;
2159
    if (this.fmt.sampleRate < 1 || byteRate > 4294967295) {
2160
      throw new Error('Invalid sample rate.');
2161
    }
2162
    return true;
2163
  }
2164
2165
  /**
2166
   * Write a variable size string as bytes. If the string is smaller
2167
   * than the max size the output array is filled with 0s.
2168
   * @param {string} str The string to be written as bytes.
2169
   * @param {number} maxSize the max size of the string.
2170
   * @return {!Array<number>} The bytes.
2171
   * @private
2172
   */
2173
  writeString_(str, maxSize, push=true) {
2174
    /** @type {!Array<number>} */   
2175
    let bytes = packString(str);
2176
    if (push) {
2177
      for (let i=bytes.length; i<maxSize; i++) {
2178
        bytes.push(0);
2179
      }  
2180
    }
2181
    return bytes;
2182
  }
2183
2184
  /**
2185
   * Read bytes as a ZSTR string.
2186
   * @param {!Uint8Array} bytes The bytes.
2187
   * @return {string} The string.
2188
   * @private
2189
   */
2190
  readZSTR_(bytes, index=0) {
2191
    /** @type {string} */
2192
    let str = '';
2193
    for (let i = index; i < bytes.length; i++) {
2194
      this.head_++;
2195
      if (bytes[i] === 0) {
2196
        break;
2197
      }
2198
      str += unpackString(bytes, i, i + 1);
2199
    }
2200
    return str;
2201
  }
2202
2203
  /**
2204
   * Read bytes as a string from a RIFF chunk.
2205
   * @param {!Uint8Array} bytes The bytes.
2206
   * @param {number} maxSize the max size of the string.
2207
   * @return {string} The string.
2208
   * @private
2209
   */
2210
  readString_(bytes, maxSize) {
2211
    /** @type {string} */
2212
    let str = '';
2213
    str = unpackString(bytes, this.head_, this.head_ + maxSize);
2214
    this.head_ += maxSize;
2215
    return str;
2216
  }
2217
2218
  /**
2219
   * Read a number from a chunk.
2220
   * @param {!Uint8Array} bytes The chunk bytes.
2221
   * @param {!Object} bdType The type definition.
2222
   * @return {number} The number.
2223
   * @private
2224
   */
2225
  read_(bytes, bdType) {
2226
    /** @type {number} */
2227
    let size = bdType.bits / 8;
2228
    /** @type {number} */
2229
    let value = unpack(bytes, bdType, this.head_);
2230
    this.head_ += size;
2231
    return value;
2232
  }
2233
}
2234