Passed
Push — master ( 151c07...899c45 )
by Rafael S.
02:51
created

T_IMPORT ➔ ???   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

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