Completed
Branch master (c74642)
by Rafael S.
01:45
created

index.js (4 issues)

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