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