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

index.js (1 issue)

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