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