Passed
Push — master ( 102b32...38760e )
by Rafael S.
02:10
created

index.js (3 issues)

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