Completed
Push — master ( 4cb80e...4062ba )
by Rafael S.
82:57 queued 76:01
created

index.js (2 issues)

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