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