Passed
Push — master ( e3a958...4c9638 )
by Rafael S.
02:11
created

index.js (3 issues)

1
/*
2
 * wavefile
3
 * Read & write wave files with 4, 8, 11, 12, 16, 20, 24, 32 & 64-bit data.
4
 * Copyright (c) 2017-2018 Rafael da Silva Rocha.
5
 * https://github.com/rochars/wavefile
6
 *
7
 */
8
9
/** @private */
10
const bitDepth_ = require("bitdepth");
11
/** @private */
12
const riffChunks_ = require("riff-chunks");
13
/** @private */
14
const imaadpcm_ = require("imaadpcm");
15
/** @private */
16
const alawmulaw_ = require("alawmulaw");
17
/** @private */
18
const byteData_ = require("byte-data");
19
/** @private */
20
const encodeBase64 = require("base64-arraybuffer").encode;
21
/** @private */
22
const uInt16_ = {"bits": 16};
23
/** @private */
24
const uInt32_ = {"bits": 32};
25
/** @private */
26
const fourCC_ = {"bits": 32, "char": true};
27
/** @private */
28
const chr_ = {"bits": 8, "char": true};
29
30
/**
31
 * Class representing a wav file.
32
 */
33
class WaveFile {
34
35
    /**
36
     * @param {Uint8Array|Array<number>} bytes A wave file buffer.
37
     * @throws {Error} If no "RIFF" chunk is found.
38
     * @throws {Error} If no "fmt " chunk is found.
39
     * @throws {Error} If no "fact" chunk is found and "fact" is needed.
40
     * @throws {Error} If no "data" chunk is found.
41
     */
42
    constructor(bytes) {
43
        /**
44
         * The container identifier.
45
         * Only "RIFF" and "RIFX" are supported.
46
         * @type {!string}
47
         * @export
48
         */
49
        this.container = "";
50
        /**
51
         * @type {!number}
52
         * @export
53
         */
54
        this.chunkSize = 0;
55
        /**
56
         * The format.
57
         * Always "WAVE".
58
         * @type {!string}
59
         * @export
60
         */
61
        this.format = "";
62
        /**
63
         * The data of the "fmt" chunk.
64
         * @type {!Object<!string, *>}
65
         * @export
66
         */
67
        this.fmt = {
68
            /** @export @type {!string} */
69
            "chunkId": "",
70
            /** @export @type {!number} */
71
            "chunkSize": 0,
72
            /** @export @type {!number} */
73
            "audioFormat": 0,
74
            /** @export @type {!number} */
75
            "numChannels": 0,
76
            /** @export @type {!number} */
77
            "sampleRate": 0,
78
            /** @export @type {!number} */
79
            "byteRate": 0,
80
            /** @export @type {!number} */
81
            "blockAlign": 0,
82
            /** @export @type {!number} */
83
            "bitsPerSample": 0,
84
            /** @export @type {!number} */
85
            "cbSize": 0,
86
            /** @export @type {!number} */
87
            "validBitsPerSample": 0,
88
            /** @export @type {!number} */
89
            "dwChannelMask": 0,
90
            /**
91
             * 4 32-bit values representing a 128-bit ID
92
             * @export @type {!Array<number>}
93
             */
94
            "subformat": []
95
        };
96
        /**
97
         * The data of the "fact" chunk.
98
         * @type {!Object<!string, *>}
99
         * @export
100
         */
101
        this.fact = {
102
            /** @export @type {!string} */
103
            "chunkId": "",
104
            /** @export @type {!number} */
105
            "chunkSize": 0,
106
            /** @export @type {!number} */
107
            "dwSampleLength": 0
108
        };
109
        /**
110
         * The data of the "cue " chunk.
111
         * @type {!Object<!string, *>}
112
         * @export
113
         */
114
        this.cue = {
115
            /** @export @type {!string} */
116
            "chunkId": "",
117
            /** @export @type {!number} */
118
            "chunkSize": 0,
119
            /** @export @type {!number} */
120
            "dwCuePoints": 0,
121
            /** @export @type {!Array<Object>} */
122
            "points": [],
123
        };
124
        /**
125
         * The data of the "bext" chunk.
126
         * @type {!Object<!string, *>}
127
         * @export
128
         */
129
        this.bext = {
130
            /** @export @type {!string} */
131
            "chunkId": "",
132
            /** @export @type {!number} */
133
            "chunkSize": 0,
134
            /** @export @type {!string} */
135
            "description": "", //256
136
            /** @export @type {!string} */
137
            "originator": "", //32
138
            /** @export @type {!string} */
139
            "originatorReference": "", //32
140
            /** @export @type {!string} */
141
            "originationDate": "", //10
142
            /** @export @type {!string} */
143
            "originationTime": "", //8
144
            /**
145
             * 2 32-bit values, timeReference high and low
146
             * @export @type {!Array<number>}
147
             */
148
            "timeReference": [],
149
            /** @export @type {!number} */
150
            "version": 0, //WORD
151
            /** @export @type {!string} */
152
            "UMID": "", // 64 chars
153
            /** @export @type {!number} */
154
            "loudnessValue": 0, //WORD
155
            /** @export @type {!number} */
156
            "loudnessRange": 0, //WORD
157
            /** @export @type {!number} */
158
            "maxTruePeakLevel": 0, //WORD
159
            /** @export @type {!number} */
160
            "maxMomentaryLoudness": 0, //WORD
161
            /** @export @type {!number} */
162
            "maxShortTermLoudness": 0, //WORD
163
            /** @export @type {!string} */
164
            "reserved": "", //180
165
            /** @export @type {!string} */
166
            "codingHistory": "" // string, unlimited
167
        };
168
        /**
169
         * The data of the "ds64" chunk.
170
         * Used only with RF64 files.
171
         * @type {!Object<!string, *>}
172
         * @export
173
         */
174
        this.ds64 = {
175
            /** @type {!string} */
176
            "chunkId": "",
177
            /** @export @type {!number} */
178
            "chunkSize": 0,
179
            /** @export @type {!number} */
180
            "riffSizeHigh": 0, // DWORD
181
            /** @export @type {!number} */
182
            "riffSizeLow": 0, // DWORD
183
            /** @export @type {!number} */
184
            "dataSizeHigh": 0, // DWORD
185
            /** @export @type {!number} */
186
            "dataSizeLow": 0, // DWORD
187
            /** @export @type {!number} */
188
            "originationTime": 0, // DWORD
189
            /** @export @type {!number} */
190
            "sampleCountHigh": 0, // DWORD
191
            /** @export @type {!number} */
192
            "sampleCountLow": 0, // DWORD
193
            /** @export @type {!number} */
194
            //"tableLength": 0, // DWORD
195
            /** @export @type {!Array<number>} */
196
            //"table": []
197
        };
198
        /**
199
         * The data of the "data" chunk.
200
         * @type {!Object<!string, *>}
201
         * @export
202
         */
203
        this.data = {
204
            /** @export @type {!string} */
205
            "chunkId": "",
206
            /** @export @type {!number} */
207
            "chunkSize": 0,
208
            /** @export @type {!Array<number>} */
209
            "samples": []
210
        };
211
        /**
212
         * The data of the "LIST" chunks.
213
         * Each item in this list must have this signature:
214
         *  {
215
         *      "chunkId": "",
216
         *      "chunkSize": 0,
217
         *      "format": "",
218
         *      "subChunks": []
219
         *   }
220
         * @type {!Array<Object>}
221
         * @export
222
         */
223
        this.LIST = [];
224
        /**
225
         * The data of the "junk" chunk.
226
         * @type {!Object<!string, *>}
227
         * @export
228
         */
229
        this.junk = {
230
            /** @export @type {!string} */
231
            "chunkId": "",
232
            /** @export @type {!number} */
233
            "chunkSize": 0,
234
            /** @export @type {!Array<number>} */
235
            "chunkData": []
236
        };
237
        /**
238
         * If the data in data.samples is interleaved or not.
239
         * @type {!boolean}
240
         * @export
241
         */
242
        this.isInterleaved = true;
243
        /**
244
         * @type {!string}
245
         * @export
246
         */
247
        this.bitDepth = "0";
248
        /**
249
         * Audio formats.
250
         * Formats not listed here will be set to 65534
251
         * and treated as WAVE_FORMAT_EXTENSIBLE
252
         * @enum {!number}
253
         * @private
254
         */
255
        this.audioFormats_ = {
256
            "4": 17,
257
            "8": 1,
258
            "8a": 6,
259
            "8m": 7,
260
            "16": 1,
261
            "24": 1,
262
            "32": 1,
263
            "32f": 3,
264
            "40": 65534,
265
            "48": 65534,
266
            "64": 3
267
        };
268
        /**
269
         * @type {!number}
270
         * @private
271
         */
272
        this.head_ = 0;
273
        /**
274
         * If the "fact" chunk should be enforced or not.
275
         * @type {!boolean}
276
         * @export
277
         */
278
        this.enforceFact = false;
279
        // Load a file from the buffer if one was passed
280
        // when creating the object
281
        if(bytes) {
282
            this.fromBuffer(bytes);
283
        }
284
    }
285
286
    /**
287
     * Set up a WaveFile object based on the arguments passed.
288
     * @param {!number} numChannels The number of channels
289
     *     (Integer numbers: 1 for mono, 2 stereo and so on).
290
     * @param {!number} sampleRate The sample rate.
291
     *     Integer numbers like 8000, 44100, 48000, 96000, 192000.
292
     * @param {!string} bitDepth The audio bit depth.
293
     *     One of "4", "8", "8a", "8m", "16", "24", "32", "32f", "64"
294
     *     or any value between "8" and "32".
295
     * @param {!Array<number>} samples Array of samples to be written.
296
     *     The samples must be in the correct range according to the
297
     *     bit depth.
298
     * @throws {Error} If any argument does not meet the criteria.
299
     * @export
300
     */
301
    fromScratch(numChannels, sampleRate, bitDepth, samples, options={}) {
302
        if (!options["container"]) {
303
            options["container"] = "RIFF";
304
        }
305
        // closest nuber of bytes if not / 8
306
        let numBytes = (((parseInt(bitDepth, 10) - 1) | 7) + 1) / 8;
307
        this.clearHeader_();
308
        this.bitDepth = bitDepth;
309
        // Normal PCM file header
310
        this.container = options["container"];
311
        this.chunkSize = 36 + samples.length * numBytes;
312
        this.format = "WAVE";
313
        this.fmt.chunkId = "fmt ";
314
        this.fmt.chunkSize = 16;
315
        this.fmt.byteRate = (numChannels * numBytes) * sampleRate;
316
        this.fmt.blockAlign = numChannels * numBytes;
317
        this.fmt.audioFormat = this.audioFormats_[bitDepth] ?
318
            this.audioFormats_[bitDepth] : 65534;
319
        this.fmt.numChannels = numChannels;
320
        this.fmt.sampleRate = sampleRate;
321
        this.fmt.bitsPerSample = parseInt(bitDepth, 10);
322
        this.fmt.cbSize = 0;
323
        this.fmt.validBitsPerSample = 0;
324
        this.data.chunkId = "data";
325
        this.data.samples = samples;
326
        // interleave the samples if they were passed de-interleaved
327
        if (samples.length > 0) {
328
            if (samples[0].constructor === Array) {
329
                this.isInterleaved = false;
330
                this.interleave();
331
            }
332
        }
333
        this.data.chunkSize = samples.length * numBytes;
334
        // IMA ADPCM header
335
        if (bitDepth == "4") {
336
            this.chunkSize = 40 + samples.length;
337
            this.fmt.chunkSize = 20;
338
            this.fmt.byteRate = 4055;
339
            this.fmt.blockAlign = 256;
340
            this.fmt.bitsPerSample = 4;
341
            this.fmt.cbSize = 2;
342
            this.fmt.validBitsPerSample = 505;
343
            this.fact.chunkId = "fact";
344
            this.fact.chunkSize = 4;
345
            this.fact.dwSampleLength = samples.length * 2;
346
            this.data.chunkSize = samples.length;
347
        }
348
        // A-Law and mu-Law header
349
        if (bitDepth == "8a" || bitDepth == "8m") {
350
            this.chunkSize = 40 + samples.length;
351
            this.fmt.chunkSize = 20;
352
            this.fmt.cbSize = 2;
353
            this.fmt.validBitsPerSample = 8;
354
            this.fact.chunkId = "fact";
355
            this.fact.chunkSize = 4;
356
            this.fact.dwSampleLength = samples.length;
357
        }
358
        // WAVE_FORMAT_EXTENSIBLE
359
        if (this.fmt.audioFormat == 65534) {
360
            this.chunkSize = 36 + 24 + samples.length * numBytes;
361
            this.fmt.chunkSize = 40;
362
            this.fmt.bitsPerSample = ((parseInt(bitDepth, 10) - 1) | 7) + 1;
363
            this.fmt.cbSize = 22;
364
            this.fmt.validBitsPerSample = parseInt(bitDepth, 10);
365
            this.fmt.dwChannelMask = 0;
366
            // subformat 128-bit GUID as 4 32-bit values
367
            // only supports uncompressed integer PCM samples
368
            this.fmt.subformat = [1, 1048576, 2852126848, 1905997824];
369
        }
370
        this.checkWriteInput_();
371
        this.LEorBE_();
372
    }
373
374
    /**
375
     * Init a WaveFile object from a byte buffer.
376
     * @param {!Uint8Array|!Array<number>} bytes The buffer.
377
     * @throws {Error} If container is not RIFF or RIFX.
378
     * @throws {Error} If no "fmt " chunk is found.
379
     * @throws {Error} If no "fact" chunk is found and "fact" is needed.
380
     * @throws {Error} If no "data" chunk is found.
381
     * @export
382
     */
383
    fromBuffer(bytes) {
384
        this.clearHeader_();
385
        this.readRIFFChunk_(bytes);
386
        let bigEndian = this.container == "RIFX";
0 ignored issues
show
The variable bigEndian seems to be never used. Consider removing it.
Loading history...
387
        let chunk = riffChunks_.read(bytes);
388
        this.readDs64Chunk_(chunk["subChunks"]);
389
        this.readFmtChunk_(chunk["subChunks"]);
390
        this.readFactChunk_(chunk["subChunks"]);
391
        this.readBextChunk_(chunk["subChunks"]);
392
        this.readCueChunk_(chunk["subChunks"]);
393
        this.readDataChunk_(chunk["subChunks"]);
394
        this.readLISTChunk_(chunk["subChunks"]);
395
        this.readJunkChunk_(chunk["subChunks"]);
396
        this.bitDepthFromFmt_();
397
    }
398
399
    /**
400
     * Return a byte buffer representig the WaveFile object as a wav file.
401
     * The return value of this method can be written straight to disk.
402
     * @return {!Uint8Array} A .wav file.
403
     * @throws {Error} If any property of the object appears invalid.
404
     * @export
405
     */
406
    toBuffer() {
407
        this.checkWriteInput_();
408
        this.assureInterleaved_();
409
        return this.createWaveFile_();
410
    }
411
412
    /**
413
     * Return a base64 string representig the WaveFile object as a wav file.
414
     * @return {string} A .wav file as a base64 string.
415
     * @throws {Error} If any property of the object appears invalid.
416
     * @export
417
     */
418
    toBase64() {
419
        return encodeBase64(this.toBuffer());
420
    }
421
422
    /**
423
     * Return a base64 string representig the WaveFile object as a wav file.
424
     * The return value of this method can be used to load the audio in browsers.
425
     * @return {string} A .wav file as a DataURI.
426
     * @throws {Error} If any property of the object appears invalid.
427
     * @export
428
     */
429
    toDataURI() {
430
        return "data:audio/wav;base64," + this.toBase64();
431
    }
432
433
    /**
434
     * Force a file as RIFF.
435
     * @export
436
     */
437
    toRIFF() {
438
        if (this.container == "RF64") {
439
            this.fromScratch(
440
                this.fmt.numChannels,
441
                this.fmt.sampleRate,
442
                this.bitDepth,
443
                this.data.samples);
444
        } else {
445
            this.container = "RIFF";
446
            this.LEorBE_();
447
        }
448
    }
449
450
    /**
451
     * Force a file as RIFX.
452
     * @export
453
     */
454
    toRIFX() {
455
        if (this.container == "RF64") {
456
            this.fromScratch(
457
                this.fmt.numChannels,
458
                this.fmt.sampleRate,
459
                this.bitDepth,
460
                this.data.samples,
461
                {"container": "RIFX"});
462
        } else {
463
            this.container = "RIFX";
464
            this.LEorBE_();
465
        }
466
    }
467
468
    /**
469
     * Change the bit depth of the samples.
470
     * @param {!string} bitDepth The new bit depth of the samples.
471
     *      One of "8" ... "32" (integers), "32f" or "64" (floats)
472
     * @param {!boolean} changeResolution A boolean indicating if the
473
     *      resolution of samples should be actually changed or not.
474
     * @throws {Error} If the bit depth is not valid.
475
     * @export
476
     */
477
    toBitDepth(bitDepth, changeResolution=true) {
478
        let toBitDepth = bitDepth;
479
        let thisBitDepth = this.bitDepth;
480
        if (!changeResolution) {
481
            toBitDepth = this.realBitDepth_(bitDepth);
482
            thisBitDepth = this.realBitDepth_(this.bitDepth);
483
        }
484
        this.assureInterleaved_();
485
        this.assureUncompressed_();
486
        bitDepth_.toBitDepth(this.data.samples, thisBitDepth, toBitDepth);
487
        this.fromScratch(
488
            this.fmt.numChannels,
489
            this.fmt.sampleRate,
490
            bitDepth,
491
            this.data.samples,
492
            {"container": this.correctContainer_()});
493
    }
494
495
    /**
496
     * Interleave multi-channel samples.
497
     * @export
498
     */
499
    interleave() {
500
        if (!this.isInterleaved) {
501
            let finalSamples = [];
502
            let numChannels = this.data.samples[0].length;
503
            for (let i = 0; i < numChannels; i++) {
504
                for (let j = 0; j < this.data.samples.length; j++) {
505
                    finalSamples.push(this.data.samples[j][i]);
506
                }
507
            }
508
            this.data.samples = finalSamples;
509
            this.isInterleaved = true;    
510
        }
511
    }
512
513
    /**
514
     * De-interleave samples into multiple channels.
515
     * @export
516
     */
517
    deInterleave() {
518
        if (this.isInterleaved) {
519
            let finalSamples = [];
520
            let i;
521
            for (i = 0; i < this.fmt.numChannels; i++) {
522
                finalSamples[i] = [];
523
            }
524
            i = 0;
525
            let j;
526
            while (i < this.data.samples.length) {
527
                for (j = 0; j < this.fmt.numChannels; j++) {
528
                    finalSamples[j].push(this.data.samples[i+j]);
529
                }
530
                i += j;
531
            }
532
            this.data.samples = finalSamples;
533
            this.isInterleaved = false;
534
        }
535
    }
536
537
    /**
538
     * Encode a 16-bit wave file as 4-bit IMA ADPCM.
539
     * @throws {Error} If sample rate is not 8000.
540
     * @throws {Error} If number of channels is not 1.
541
     * @export
542
     */
543
    toIMAADPCM() {
544
        if (this.fmt.sampleRate != 8000) {
545
            throw new Error(
546
                "Only 8000 Hz files can be compressed as IMA-ADPCM.");
547
        } else if(this.fmt.numChannels != 1) {
548
            throw new Error(
549
                "Only mono files can be compressed as IMA-ADPCM.");
550
        } else {
551
            this.assure16Bit_();
552
            this.fromScratch(
553
                this.fmt.numChannels,
554
                this.fmt.sampleRate,
555
                "4",
556
                imaadpcm_.encode(this.data.samples),
557
                {"container": this.correctContainer_()});
558
        }
559
    }
560
561
    /**
562
     * Decode a 4-bit IMA ADPCM wave file as a 16-bit wave file.
563
     * @param {!string} bitDepth The new bit depth of the samples.
564
     *      One of "8" ... "32" (integers), "32f" or "64" (floats).
565
     *      Optional. Default is 16.
566
     * @export
567
     */
568
    fromIMAADPCM(bitDepth="16") {
569
        this.fromScratch(
570
            this.fmt.numChannels,
571
            this.fmt.sampleRate,
572
            "16",
573
            imaadpcm_.decode(this.data.samples, this.fmt.blockAlign),
574
            {"container": this.correctContainer_()});
575
        if (bitDepth != "16") {
576
            this.toBitDepth(bitDepth);
577
        }
578
    }
579
580
    /**
581
     * Encode 16-bit wave file as 8-bit A-Law.
582
     * @export
583
     */
584
    toALaw() {
585
        this.assure16Bit_();
586
        this.assureInterleaved_();
587
        this.fromScratch(
588
            this.fmt.numChannels,
589
            this.fmt.sampleRate,
590
            "8a",
591
            alawmulaw_.alaw.encode(this.data.samples),
592
            {"container": this.correctContainer_()});
593
    }
594
595
    /**
596
     * Decode a 8-bit A-Law wave file into a 16-bit wave file.
597
     * @param {!string} bitDepth The new bit depth of the samples.
598
     *      One of "8" ... "32" (integers), "32f" or "64" (floats).
599
     *      Optional. Default is 16.
600
     * @export
601
     */
602
    fromALaw(bitDepth="16") {
603
        this.fromScratch(
604
            this.fmt.numChannels,
605
            this.fmt.sampleRate,
606
            "16",
607
            alawmulaw_.alaw.decode(this.data.samples),
608
            {"container": this.correctContainer_()});
609
        if (bitDepth != "16") {
610
            this.toBitDepth(bitDepth);
611
        }
612
    }
613
614
    /**
615
     * Encode 16-bit wave file as 8-bit mu-Law.
616
     * @export
617
     */
618
    toMuLaw() {
619
        this.assure16Bit_();
620
        this.assureInterleaved_();
621
        this.fromScratch(
622
            this.fmt.numChannels,
623
            this.fmt.sampleRate,
624
            "8m",
625
            alawmulaw_.mulaw.encode(this.data.samples),
626
            {"container": this.correctContainer_()});
627
    }
628
629
    /**
630
     * Decode a 8-bit mu-Law wave file into a 16-bit wave file.
631
     * @param {!string} bitDepth The new bit depth of the samples.
632
     *      One of "8" ... "32" (integers), "32f" or "64" (floats).
633
     *      Optional. Default is 16.
634
     * @export
635
     */
636
    fromMuLaw(bitDepth="16") {
637
        this.fromScratch(
638
            this.fmt.numChannels,
639
            this.fmt.sampleRate,
640
            "16",
641
            alawmulaw_.mulaw.decode(this.data.samples),
642
            {"container": this.correctContainer_()});
643
        if (bitDepth != "16") {
644
            this.toBitDepth(bitDepth);
645
        }
646
    }
647
648
    /**
649
     * Return the closest greater number of bits for a number of bits that
650
     * do not fill a full sequence of bytes.
651
     * @param {!string} bitDepth The bit depth.
652
     * @return {!string}
653
     */
654
    realBitDepth_(bitDepth) {
655
        if (bitDepth != "32f") {
656
            bitDepth = (((parseInt(bitDepth, 10) - 1) | 7) + 1).toString();
657
        }
658
        return bitDepth;
659
    }
660
661
    /**
662
     * Validate the input for wav writing.
663
     * @throws {Error} If any property of the object appears invalid.
664
     * @private
665
     */
666
    checkWriteInput_() {
667
        this.validateBitDepth_();
668
        this.validateNumChannels_();
669
        this.validateSampleRate_();
670
    }
671
672
    /**
673
     * Validate the bit depth.
674
     * @return {!boolean} True is the bit depth is valid.
675
     * @throws {Error} If bit depth is invalid.
676
     * @private
677
     */
678
    validateBitDepth_() {
679
        if (!this.audioFormats_[this.bitDepth]) {
680
            if (parseInt(this.bitDepth, 10) > 8 &&
681
                    parseInt(this.bitDepth, 10) < 54) {
682
                return true;
683
            }
684
            throw new Error("Invalid bit depth.");
685
        }
686
        return true;
687
    }
688
689
    /**
690
     * Validate the number of channels.
691
     * @return {!boolean} True is the number of channels is valid.
692
     * @throws {Error} If the number of channels is invalid.
693
     * @private
694
     */
695
    validateNumChannels_() {
696
        let blockAlign = this.fmt.numChannels * this.fmt.bitsPerSample / 8;
697
        if (this.fmt.numChannels < 1 || blockAlign > 65535) {
698
            throw new Error("Invalid number of channels.");
699
        }
700
        return true;
701
    }
702
703
    /**
704
     * Validate the sample rate value.
705
     * @return {!boolean} True is the sample rate is valid.
706
     * @throws {Error} If the sample rate is invalid.
707
     * @private
708
     */
709
    validateSampleRate_() {
710
        let byteRate = this.fmt.numChannels *
711
            (this.fmt.bitsPerSample / 8) * this.fmt.sampleRate;
712
        if (this.fmt.sampleRate < 1 || byteRate > 4294967295) {
713
            throw new Error("Invalid sample rate.");
714
        }
715
        return true;
716
    }
717
718
    /**
719
     * Reset attributes that should emptied when a file is
720
     * created with the fromScratch() or fromBuffer() methods.
721
     * @private
722
     */
723
    clearHeader_() {
724
        this.fmt.cbSize = 0;
725
        this.fmt.validBitsPerSample = 0;
726
        this.fact.chunkId = "";
727
        this.ds64.chunkId = "";
728
    }
729
730
    /**
731
     * Make the file 16-bit if it is not.
732
     * @private
733
     */
734
    assure16Bit_() {
735
        this.assureUncompressed_();
736
        if (this.bitDepth != "16") {
737
            this.toBitDepth("16");
738
        }
739
    }
740
741
    /**
742
     * Uncompress the samples in case of a compressed file.
743
     * @private
744
     */
745
    assureUncompressed_() {
746
        if (this.bitDepth == "8a") {
747
            this.fromALaw();
748
        } else if(this.bitDepth == "8m") {
749
            this.fromMuLaw();
750
        } else if (this.bitDepth == "4") {
751
            this.fromIMAADPCM();
752
        }
753
    }
754
755
    /**
756
     * Interleave the samples in case they are de-Interleaved.
757
     * @private
758
     */
759
    assureInterleaved_() {
760
        if (!this.isInterleaved) {
761
            this.interleave();
762
        }
763
    }
764
765
    /**
766
     * Set up to work wih big-endian or little-endian files.
767
     * The types used are changed to LE or BE. If the
768
     * the file is big-endian (RIFX), true is returned.
769
     * @return {!boolean} True if the file is RIFX.
770
     * @private
771
     */
772
    LEorBE_() {
773
        let bigEndian = this.container === "RIFX";
774
        uInt16_["be"] = bigEndian;
775
        uInt32_["be"] = bigEndian;
776
        return bigEndian;
777
    }
778
779
    /**
780
     * Find a chunk by its fourCC_ in a array of RIFF chunks.
781
     * @param {!Array<!Object>} chunks The wav file chunks.
782
     * @param {!string} chunkId The chunk fourCC_.
783
     * @param {boolean} multiple True if there may be multiple chunks
784
     *      with the same chunkId.
785
     * @return {Object|Array<Object>|null}
786
     * @private
787
     */
788
    findChunk_(chunks, chunkId, multiple=false) {
789
        let chunk = [];
790
        for (let i=0; i<chunks.length; i++) {
791
            if (chunks[i]["chunkId"] == chunkId) {
792
                if (multiple) {
793
                    chunk.push(chunks[i]);
794
                } else {
795
                    return chunks[i];
796
                }
797
            }
798
        }
799
        if (chunkId == "LIST") {
800
            return chunk.length ? chunk : null;
801
        }
802
        return null;
803
    }
804
805
    /**
806
     * Read the RIFF chunk a wave file.
807
     * @param {!Uint8Array|!Array<number>} bytes A wav buffer.
808
     * @throws {Error} If no "RIFF" chunk is found.
809
     * @private
810
     */
811
    readRIFFChunk_(bytes) {
812
        this.head_ = 0;
813
        this.container = this.readString_(bytes, 4);
814
        if (["RIFF", "RIFX", "RF64"].indexOf(this.container) === -1) {
815
            throw Error("Not a supported format.");
816
        }
817
        this.LEorBE_();
818
        this.chunkSize = this.read_(bytes, uInt32_);
819
        this.format = this.readString_(bytes, 4);
820
        if (this.format != "WAVE") {
821
            throw Error("Could not find the 'WAVE' format identifier");
822
        }
823
    }
824
825
    /**
826
     * Read the "fmt " chunk of a wave file.
827
     * @param {!Array<!Object>} chunks The wav file chunks.
828
     * @throws {Error} If no "fmt " chunk is found.
829
     * @private
830
     */
831
    readFmtChunk_(chunks) {
832
        let chunk = this.findChunk_(chunks, "fmt ");
833
        if (chunk) {
834
            this.head_ = 0;
835
            let chunkData = chunk["chunkData"];
836
            this.fmt.chunkId = chunk["chunkId"];
837
            this.fmt.chunkSize = chunk["chunkSize"];
838
            this.fmt.audioFormat = this.read_(chunkData, uInt16_);
839
            this.fmt.numChannels = this.read_(chunkData, uInt16_);
840
            this.fmt.sampleRate = this.read_(chunkData, uInt32_);
841
            this.fmt.byteRate = this.read_(chunkData, uInt32_);
842
            this.fmt.blockAlign = this.read_(chunkData, uInt16_);
843
            this.fmt.bitsPerSample = this.read_(chunkData, uInt16_);
844
            this.readFmtExtension_(chunkData);
845
        } else {
846
            throw Error("Could not find the 'fmt ' chunk");
847
        }
848
    }
849
850
    /**
851
     * Read the "fmt " chunk extension.
852
     * @param {!Array<number>} chunkData The "fmt " chunk.
853
     * @private
854
     */
855
    readFmtExtension_(chunkData) {
856
        if (this.fmt.chunkSize > 16) {
857
            this.fmt.cbSize = this.read_(
858
                chunkData, uInt16_);
859
            if (this.fmt.chunkSize > 18) {
860
                this.fmt.validBitsPerSample = this.read_(chunkData, uInt16_);
861
                if (this.fmt.chunkSize > 20) {
862
                    this.fmt.dwChannelMask = this.read_(chunkData, uInt32_);
863
                    this.fmt.subformat = [
864
                        this.read_(chunkData, uInt32_),
865
                        this.read_(chunkData, uInt32_),
866
                        this.read_(chunkData, uInt32_),
867
                        this.read_(chunkData, uInt32_)];
868
                }
869
            }
870
        }
871
    }
872
873
    /**
874
     * Read the "fact" chunk of a wav file.
875
     * @param {!Array<Object>} chunks The wav file chunks.
876
     * @throws {Error} If no "fact" chunk is found.
877
     * @private
878
     */
879
    readFactChunk_(chunks) {
880
        let chunk = this.findChunk_(chunks, "fact");
881
        if (chunk) {
882
            this.head_ = 0;
883
            this.fact.chunkId = chunk["chunkId"];
884
            this.fact.chunkSize = chunk["chunkSize"];
885
            this.fact.dwSampleLength = this.read_(chunk["chunkData"], uInt32_);
886
        } else if (this.enforceFact) {
887
            throw Error("Could not find the 'fact' chunk");
888
        }
889
    }
890
891
    /**
892
     * Read the "cue " chunk of a wave file.
893
     * @param {!Array<Object>} chunks The RIFF file chunks.
894
     * @private
895
     */
896
    readCueChunk_(chunks) {
897
        let chunk = this.findChunk_(chunks, "cue ");
898
        if (chunk) {
899
            this.head_ = 0;
900
            let chunkData = chunk["chunkData"];
901
            this.cue.chunkId = chunk["chunkId"];
902
            this.cue.chunkSize = chunk["chunkSize"];
903
            this.cue.dwCuePoints = this.read_(chunkData, uInt32_);
904
            for (let i=0; i<this.cue.dwCuePoints; i++) {
905
                this.cue.points.push({
906
                    "dwName": this.read_(chunkData, uInt32_),
907
                    "dwPosition": this.read_(chunkData, uInt32_),
908
                    "fccChunk": this.readString_(chunkData, 4),
909
                    "dwChunkStart": this.read_(chunkData, uInt32_),
910
                    "dwBlockStart": this.read_(chunkData, uInt32_),
911
                    "dwSampleOffset": this.read_(chunkData, uInt32_),
912
                });
913
            }
914
        }
915
    }
916
917
    /**
918
     * Read the "data" chunk of a wave file.
919
     * @param {!Array<Object>} chunks The RIFF file chunks.
920
     * @throws {Error} If no "data" chunk is found.
921
     * @private
922
     */
923
    readDataChunk_(chunks) {
924
        let chunk = this.findChunk_(chunks, "data");
925
        if (chunk) {
926
            this.data.chunkId = "data";
927
            this.data.chunkSize = chunk["chunkSize"];
928
            this.samplesFromBytes_(chunk["chunkData"]);
929
        } else {
930
            throw Error("Could not find the 'data' chunk");
931
        }
932
    }
933
934
    /**
935
     * Read the "bext" chunk of a wav file.
936
     * @param {!Array<Object>} chunks The wav file chunks.
937
     * @private
938
     */
939
    readBextChunk_(chunks) {
940
        let chunk = this.findChunk_(chunks, "bext");
941
        if (chunk) {
942
            this.head_ = 0;
943
            let chunkData = chunk["chunkData"];
944
            this.bext.chunkId = chunk["chunkId"];
945
            this.bext.chunkSize = chunk["chunkSize"];
946
            this.bext.description = this.readString_(chunkData, 256);
947
            this.bext.originator = this.readString_(chunkData, 32);
948
            this.bext.originatorReference = this.readString_(chunkData, 32);
949
            this.bext.originationDate = this.readString_(chunkData, 10);
950
            this.bext.originationTime = this.readString_(chunkData, 8);
951
            this.bext.timeReference = [
952
                this.read_(chunkData, uInt32_),
953
                this.read_(chunkData, uInt32_)];
954
            this.bext.version = this.read_(chunkData, uInt16_);
955
            this.bext.UMID = this.readString_(chunkData, 64);
956
            this.bext.loudnessValue = this.read_(chunkData, uInt16_);
957
            this.bext.loudnessRange = this.read_(chunkData, uInt16_);
958
            this.bext.maxTruePeakLevel = this.read_(chunkData, uInt16_);
959
            this.bext.maxMomentaryLoudness = this.read_(chunkData, uInt16_);
960
            this.bext.maxShortTermLoudness = this.read_(chunkData, uInt16_);
961
            this.bext.reserved = this.readString_(chunkData, 180);
962
            this.bext.codingHistory = this.readString_(
963
                chunkData, this.bext.chunkSize - 602);
964
        }
965
    }
966
967
    /**
968
     * Read the "ds64" chunk of a wave file.
969
     * @param {!Array<Object>} chunks The wav file chunks.
970
     * @throws {Error} If no "ds64" chunk is found and the file is RF64.
971
     * @private
972
     */
973
    readDs64Chunk_(chunks) {
974
        let chunk = this.findChunk_(chunks, "ds64");
975
        if (chunk) {
976
            this.head_ = 0;
977
            let chunkData = chunk["chunkData"];
978
            this.ds64.chunkId = chunk["chunkId"];
979
            this.ds64.chunkSize = chunk["chunkSize"];
980
            this.ds64.riffSizeHigh = this.read_(chunkData, uInt32_);
981
            this.ds64.riffSizeLow = this.read_(chunkData, uInt32_);
982
            this.ds64.dataSizeHigh = this.read_(chunkData, uInt32_);
983
            this.ds64.dataSizeLow = this.read_(chunkData, uInt32_);
984
            this.ds64.originationTime = this.read_(chunkData, uInt32_);
985
            this.ds64.sampleCountHigh = this.read_(chunkData, uInt32_);
986
            this.ds64.sampleCountLow = this.read_(chunkData, uInt32_);
987
            //if (this.ds64.chunkSize > 28) {
988
            //    this.ds64.tableLength = byteData_.unpack(
989
            //        chunkData.slice(28, 32), uInt32_);
990
            //    this.ds64.table = chunkData.slice(
991
            //         32, 32 + this.ds64.tableLength); 
992
            //}
993
        } else {
994
            if (this.container == "RF64") {
995
                throw Error("Could not find the 'ds64' chunk");    
996
            }
997
        }
998
    }
999
1000
    /**
1001
     * Read the "LIST" chunks of a wave file.
1002
     * @param {!Array<Object>} chunks The wav file chunks.
1003
     * @private
1004
     */
1005
    readLISTChunk_(chunks) {
1006
        let listChunks = this.findChunk_(chunks, "LIST", true);
1007
        if (listChunks == null) {
0 ignored issues
show
Comparing listChunks to null using the == operator is not safe. Consider using === instead.
Loading history...
1008
            return;
1009
        }
1010
        for (let j=0; j<listChunks.length; j++) {
1011
            let subChunk = listChunks[j];
1012
            this.LIST.push({
1013
                "chunkId": subChunk["chunkId"],
1014
                "chunkSize": subChunk["chunkSize"],
1015
                "format": subChunk["format"],
1016
                "chunkData": subChunk["chunkData"],
1017
                "subChunks": []});
1018
            for (let x=0; x<subChunk["subChunks"].length; x++) {
1019
                this.readLISTSubChunks_(subChunk["subChunks"][x],
1020
                    subChunk["format"]);
1021
            }
1022
        }
1023
    }
1024
1025
    /**
1026
     * Read the sub chunks of a "LIST" chunk.
1027
     * @param {!Object} subChunk The "LIST" subchunks.
1028
     * @param {!string} format The "LIST" format, "adtl" or "INFO".
1029
     * @private
1030
     */
1031
    readLISTSubChunks_(subChunk, format) {
1032
        // 'labl', 'note', 'ltxt', 'file'
1033
        if (format == 'adtl') {
1034
            if (["labl", "note"].indexOf(subChunk["chunkId"]) > -1) {
1035
                this.LIST[this.LIST.length - 1]["subChunks"].push({
1036
                    "chunkId": subChunk["chunkId"],
1037
                    "chunkSize": subChunk["chunkSize"],
1038
                    "dwName": byteData_.unpack(
1039
                        subChunk["chunkData"].slice(0, 4),uInt32_),
1040
                    "value": this.readZSTR_(subChunk["chunkData"].slice(4))
1041
                });
1042
            }
1043
        // RIFF 'INFO' tags like ICRD, ISFT, ICMT
1044
        // https://sno.phy.queensu.ca/~phil/exiftool/TagNames/RIFF.html#Info
1045
        } else if(format == 'INFO') {
1046
            this.LIST[this.LIST.length - 1]["subChunks"].push({
1047
                "chunkId": subChunk["chunkId"],
1048
                "chunkSize": subChunk["chunkSize"],
1049
                "value": this.readZSTR_(subChunk["chunkData"].slice(0))
1050
            });
1051
        } //else {
1052
        //    this.LIST[this.LIST.length - 1]["subChunks"].push({
1053
        //        "chunkId": subChunk["chunkId"],
1054
        //        "chunkSize": subChunk["chunkSize"],
1055
        //        "value": subChunk["chunkData"]
1056
        //    });
1057
        //}
1058
    }
1059
1060
    /**
1061
     * Read the "junk" chunk of a wave file.
1062
     * @param {!Array<Object>} chunks The wav file chunks.
1063
     * @private
1064
     */
1065
    readJunkChunk_(chunks) {
1066
        let chunk = this.findChunk_(chunks, "junk");
1067
        if (chunk) {
1068
            this.junk = {
1069
                "chunkId": chunk["chunkId"],
1070
                "chunkSize": chunk["chunkSize"],
1071
                "chunkData": chunk["chunkData"]
1072
            };
1073
        }
1074
    }
1075
1076
    /**
1077
     * Read bytes as a ZSTR string.
1078
     * @param {!Array<number>|!Uint8Array} bytes The bytes.
1079
     * @return {!string} The string.
1080
     * @private
1081
     */
1082
    readZSTR_(bytes) {
1083
        let str = "";
1084
        for (let i=0; i<bytes.length; i++) {
1085
            if (bytes[i] == 0) {
0 ignored issues
show
Comparing bytes.i to 0 using the == operator is not safe. Consider using === instead.
Loading history...
1086
                break;
1087
            }
1088
            str += byteData_.unpack([bytes[i]], chr_);
1089
        }
1090
        return str;
1091
    }
1092
1093
    /**
1094
     * Read bytes as a string from a RIFF chunk.
1095
     * @param {!Array<number>|!Uint8Array} bytes The bytes.
1096
     * @param {!number} maxSize the max size of the string.
1097
     * @return {!string} The string.
1098
     * @private
1099
     */
1100
    readString_(bytes, maxSize) {
1101
        let str = "";
1102
        for (let i=0; i<maxSize; i++) {
1103
            str += byteData_.unpack([bytes[this.head_]], chr_);
1104
            this.head_++;
1105
        }
1106
        return str;
1107
    }
1108
1109
    /**
1110
     * Read a number from a chunk.
1111
     * @param {!Array<number>|!Uint8Array} bytes The chunk bytes.
1112
     * @param {!Object} bdType The type definition.
1113
     * @return {number} The number.
1114
     * @private
1115
     */
1116
    read_(bytes, bdType) {
1117
        let size = bdType["bits"] / 8;
1118
        let value = byteData_.unpack(
1119
            bytes.slice(this.head_, this.head_ + size), bdType);
1120
        this.head_ += size;
1121
        return value;
1122
    }
1123
1124
    /**
1125
     * Write a variable size string as bytes. If the string is smaller
1126
     * than the max size the output array is filled with 0s.
1127
     * @param {!string} str The string to be written as bytes.
1128
     * @param {!number} maxSize the max size of the string.
1129
     * @return {!Array<number>} The bytes.
1130
     * @private
1131
     */
1132
    writeString_(str, maxSize, push=true) {
1133
        
1134
        let bytes = byteData_.packArray(str, chr_);
1135
        if (push) {
1136
            for (let i=bytes.length; i<maxSize; i++) {
1137
                bytes.push(0);
1138
            }    
1139
        }
1140
        return bytes;
1141
    }
1142
1143
    /**
1144
     * Turn the samples to bytes.
1145
     * @return {!Array<number>} The bytes.
1146
     * @private
1147
     */
1148
    samplesToBytes_() {
1149
        let bdType = {
1150
            "be": this.container === "RIFX",
1151
            "bits": this.fmt.bitsPerSample == 4 ? 8 : this.fmt.bitsPerSample,
1152
            "float": this.fmt.audioFormat == 3 ? true : false
1153
        };
1154
        bdType["signed"] = bdType["bits"] == 8 ? false : true;
1155
        let bytes = byteData_.packArray(this.data.samples, bdType);
1156
        if (bytes.length % 2) {
1157
            bytes.push(0);
1158
        }
1159
        return bytes;
1160
    }
1161
1162
    /**
1163
     * Turn bytes to samples and load them in the data.samples property.
1164
     * @param {!Array<number>|!Uint8Array} bytes The bytes.
1165
     * @private
1166
     */
1167
    samplesFromBytes_(bytes) {
1168
        let bdType = {
1169
            "be": this.container === "RIFX",
1170
            "bits": this.fmt.bitsPerSample == 4 ? 8 : this.fmt.bitsPerSample,
1171
            "float": this.fmt.audioFormat == 3 ? true : false
1172
        };
1173
        bdType["signed"] = bdType["bits"] == 8 ? false : true;
1174
        this.data.samples = byteData_.unpackArray(bytes, bdType);
1175
    }
1176
1177
    /**
1178
     * Return the bytes of the "bext" chunk.
1179
     * @return {!Array<number>} The "bext" chunk bytes.
1180
     * @private
1181
     */
1182
    getBextBytes_() {
1183
        let bextBytes = [];
1184
        if (this.bext.chunkId) {
1185
            return bextBytes.concat(
1186
                byteData_.pack(this.bext.chunkId, fourCC_),
1187
                byteData_.pack(602 + this.bext.codingHistory.length, uInt32_),
1188
                this.writeString_(this.bext.description, 256),
1189
                this.writeString_(this.bext.originator, 32),
1190
                this.writeString_(this.bext.originatorReference, 32),
1191
                this.writeString_(this.bext.originationDate, 10),
1192
                this.writeString_(this.bext.originationTime, 8),
1193
                byteData_.pack(this.bext.timeReference[0], uInt32_),
1194
                byteData_.pack(this.bext.timeReference[1], uInt32_),
1195
                byteData_.pack(this.bext.version, uInt16_),
1196
                this.writeString_(this.bext.UMID, 64),
1197
                byteData_.pack(this.bext.loudnessValue, uInt16_),
1198
                byteData_.pack(this.bext.loudnessRange, uInt16_),
1199
                byteData_.pack(this.bext.maxTruePeakLevel, uInt16_),
1200
                byteData_.pack(this.bext.maxMomentaryLoudness, uInt16_),
1201
                byteData_.pack(this.bext.maxShortTermLoudness, uInt16_),
1202
                this.writeString_(this.bext.reserved, 180),
1203
                this.writeString_(
1204
                    this.bext.codingHistory, this.bext.codingHistory.length));
1205
        }
1206
        return bextBytes;
1207
    }
1208
1209
    /**
1210
     * Return the bytes of the "ds64" chunk.
1211
     * @return {!Array<number>} The "ds64" chunk bytes.
1212
     * @private
1213
     */
1214
    getDs64Bytes_() {
1215
        let ds64Bytes = [];
1216
        if (this.ds64.chunkId) {
1217
            ds64Bytes = ds64Bytes.concat(
1218
                byteData_.pack(this.ds64.chunkId, fourCC_),
1219
                byteData_.pack(this.ds64.chunkSize, uInt32_), // 
1220
                byteData_.pack(this.ds64.riffSizeHigh, uInt32_),
1221
                byteData_.pack(this.ds64.riffSizeLow, uInt32_),
1222
                byteData_.pack(this.ds64.dataSizeHigh, uInt32_),
1223
                byteData_.pack(this.ds64.dataSizeLow, uInt32_),
1224
                byteData_.pack(this.ds64.originationTime, uInt32_),
1225
                byteData_.pack(this.ds64.sampleCountHigh, uInt32_),
1226
                byteData_.pack(this.ds64.sampleCountLow, uInt32_));          
1227
        }
1228
        //if (this.ds64.tableLength) {
1229
        //    ds64Bytes = ds64Bytes.concat(
1230
        //        byteData_.pack(this.ds64.tableLength, uInt32_),
1231
        //        this.ds64.table);
1232
        //}
1233
        return ds64Bytes;
1234
    }
1235
1236
    /**
1237
     * Return the bytes of the "cue " chunk.
1238
     * @return {!Array<number>} The "cue " chunk bytes.
1239
     * @private
1240
     */
1241
    getCueBytes_() {
1242
        let cueBytes = [];
1243
        if (this.cue.chunkId) {
1244
            return cueBytes.concat(
1245
                byteData_.pack(this.cue.chunkId, fourCC_),
1246
                byteData_.pack(this.getCueSize_() - 8, uInt32_),
1247
                byteData_.pack(this.cue.dwCuePoints, uInt32_),
1248
                this.getCuePointsBytes_());
1249
        }
1250
        return cueBytes;
1251
    }
1252
1253
    /**
1254
     * Return the bytes of the "cue " points.
1255
     * @return {!Array<number>} The "cue " points as an array of bytes.
1256
     * @private
1257
     */
1258
    getCuePointsBytes_() {
1259
        let points = [];
1260
        for (let i=0; i<this.cue.dwCuePoints; i++) {
1261
            points = points.concat(
1262
                byteData_.pack(this.cue.points[i]["dwName"], uInt32_),
1263
                byteData_.pack(this.cue.points[i]["dwPosition"], uInt32_),
1264
                byteData_.pack(this.cue.points[i]["fccChunk"], fourCC_),
1265
                byteData_.pack(this.cue.points[i]["dwChunkStart"], uInt32_),
1266
                byteData_.pack(this.cue.points[i]["dwBlockStart"], uInt32_),
1267
                byteData_.pack(this.cue.points[i]["dwSampleOffset"], uInt32_));
1268
        }
1269
        return points;
1270
    }
1271
1272
    /**
1273
     * Return the bytes of the "fact" chunk.
1274
     * @return {!Array<number>} The "fact" chunk bytes.
1275
     * @private
1276
     */
1277
    getFactBytes_() {
1278
        let factBytes = [];
1279
        if (this.fact.chunkId) {
1280
            return factBytes.concat(
1281
                byteData_.pack(this.fact.chunkId, fourCC_),
1282
                byteData_.pack(this.fact.chunkSize, uInt32_),
1283
                byteData_.pack(this.fact.dwSampleLength, uInt32_));
1284
        }
1285
        return factBytes;
1286
    }
1287
1288
    /**
1289
     * Return the bytes of the "fmt " chunk.
1290
     * @return {!Array<number>} The "fmt" chunk bytes.
1291
     * @throws {Error} if no "fmt " chunk is present.
1292
     * @private
1293
     */
1294
    getFmtBytes_() {
1295
        if (this.fmt.chunkId) {
1296
            return [].concat(
1297
                byteData_.pack(this.fmt.chunkId, fourCC_),
1298
                byteData_.pack(this.fmt.chunkSize, uInt32_),
1299
                byteData_.pack(this.fmt.audioFormat, uInt16_),
1300
                byteData_.pack(this.fmt.numChannels, uInt16_),
1301
                byteData_.pack(this.fmt.sampleRate, uInt32_),
1302
                byteData_.pack(this.fmt.byteRate, uInt32_),
1303
                byteData_.pack(this.fmt.blockAlign, uInt16_),
1304
                byteData_.pack(this.fmt.bitsPerSample, uInt16_),
1305
                this.getFmtExtensionBytes_()
1306
            );
1307
        } else {
1308
            throw Error("Could not find the 'fmt ' chunk");
1309
        }
1310
    }
1311
1312
    /**
1313
     * Return the bytes of the fmt extension fields.
1314
     * @return {!Array<number>} The fmt extension bytes.
1315
     * @private
1316
     */
1317
    getFmtExtensionBytes_() {
1318
        let extension = [];
1319
        if (this.fmt.chunkSize > 16) {
1320
            extension = extension.concat(
1321
                byteData_.pack(this.fmt.cbSize, uInt16_));
1322
        }
1323
        if (this.fmt.chunkSize > 18) {
1324
            extension = extension.concat(
1325
                byteData_.pack(this.fmt.validBitsPerSample, uInt16_));
1326
        }
1327
        if (this.fmt.chunkSize > 20) {
1328
            extension = extension.concat(
1329
                byteData_.pack(this.fmt.dwChannelMask, uInt32_));
1330
        }
1331
        if (this.fmt.chunkSize > 24) {
1332
            extension = extension.concat(
1333
                byteData_.pack(this.fmt.subformat[0], uInt32_),
1334
                byteData_.pack(this.fmt.subformat[1], uInt32_),
1335
                byteData_.pack(this.fmt.subformat[2], uInt32_),
1336
                byteData_.pack(this.fmt.subformat[3], uInt32_));
1337
        }
1338
        return extension;
1339
    }
1340
1341
    /**
1342
    * Return the size of the "LIST" chunk subchunks.
1343
    * @param {number} i The index of the LIST chunk.
1344
    * @return {number} The "LIST" chunk size in bytes.
1345
    * @private
1346
    */
1347
    getLISTChunksSize_(i) {
1348
        let chunkSize = 12;
1349
        for (let j=0; j<this.LIST[i]["subChunks"].length; j++) {
1350
            if (this.LIST[i]["format"] == 'adtl') {
1351
                chunkSize += this.LIST[i]["subChunks"][j]["value"].length + 13;
1352
            } else if(this.LIST[i]["format"] == 'INFO') {
1353
                chunkSize += this.LIST[i]["subChunks"][j]["value"].length + 9;
1354
            }
1355
            if (chunkSize % 2) {
1356
                chunkSize++;
1357
            }
1358
        }
1359
        return chunkSize;
1360
    }
1361
1362
    /**
1363
    * Return the size of the "LIST" chunk.
1364
    * @return {number} The "LIST" chunk size in bytes.
1365
    * @export for tests
1366
    */
1367
    getLISTSize_() {
1368
        let LISTSize = 0;
1369
        for (let i=0; i<this.LIST.length; i++) {
1370
            LISTSize += this.getLISTChunksSize_(i);   
1371
        }
1372
        return LISTSize;
1373
    }
1374
1375
    /**
1376
     * Return the bytes of the "LIST" chunk.
1377
     * @return {!Array<number>} The "LIST" chunk bytes.
1378
     * @export for tests
1379
     */
1380
    getLISTBytes_() {
1381
        let bytes = [];
1382
        for (let i=0; i<this.LIST.length; i++) {
1383
            let chunkSize = this.getLISTChunksSize_(i) - 8;
1384
            bytes = bytes.concat(
1385
                byteData_.pack(this.LIST[i]["chunkId"], fourCC_),
1386
                byteData_.pack(chunkSize, uInt32_),
1387
                byteData_.pack(this.LIST[i]["format"], fourCC_),
1388
                this.getLISTSubChunksBytes_(
1389
                    this.LIST[i]["subChunks"],
1390
                    this.LIST[i]["format"]));
1391
        }
1392
        return bytes;
1393
    }
1394
1395
    /**
1396
     * Return the bytes of the "LIST" sub chunks.
1397
     * @param {!Array<Object>} subChunks The "LIST" sub chunks.
1398
     * @param {!string} format The format of the "LIST" chunk: "adtl" or "INFO".
1399
     * @return {!Array<number>} The sub chunk bytes.
1400
     * @private
1401
     */
1402
    getLISTSubChunksBytes_(subChunks, format) {
1403
        let bytes = [];
1404
        for (let i=0; i<subChunks.length; i++) {
1405
            bytes = bytes.concat(byteData_.pack(subChunks[i]["chunkId"], fourCC_));
1406
            if (format == "INFO") {
1407
                bytes = bytes.concat(
1408
                    byteData_.pack(subChunks[i]["value"].length + 1, uInt32_),
1409
                    this.writeString_(subChunks[i]["value"], subChunks[i]["value"].length));
1410
                bytes.push(0);
1411
            } else if (format == "adtl") {
1412
                bytes = bytes.concat(
1413
                    byteData_.pack(subChunks[i]["value"].length + 4 + 1, uInt32_),
1414
                    byteData_.pack(subChunks[i]["dwName"], uInt32_),
1415
                    this.writeString_(subChunks[i]["value"], subChunks[i]["value"].length));
1416
                bytes.push(0);
1417
            } //else {
1418
            //    bytes = bytes.concat(
1419
            //        byteData_.pack(subChunks[i]["chunkData"].length, uInt32_),
1420
            //        subChunks[i]["chunkData"]
1421
            //    );
1422
            //}
1423
            if (bytes.length % 2) {
1424
                bytes.push(0);
1425
            }
1426
        }
1427
        return bytes;
1428
    }
1429
1430
    /**
1431
     * Return the bytes of the "junk" chunk.
1432
     * @return {!Array<number>} The "junk" chunk bytes.
1433
     * @private
1434
     */
1435
    getJunkBytes_() {
1436
        let junkBytes = [];
1437
        if (this.junk.chunkId) {
1438
            return junkBytes.concat(
1439
                byteData_.pack(this.junk.chunkId, fourCC_),
1440
                byteData_.pack(this.junk.chunkData.length, uInt32_),
1441
                this.junk.chunkData);
1442
        }
1443
        return junkBytes;
1444
    }
1445
1446
    /**
1447
     * Return "RIFF" if the container is "RF64", the current container name
1448
     * otherwise. Used to enforce "RIFF" when RF64 is not allowed.
1449
     * @return {!string}
1450
     * @private
1451
     */
1452
    correctContainer_() {
1453
        return this.container == "RF64" ? "RIFF" : this.container;
1454
    }
1455
1456
    /**
1457
     * Set the string code of the bit depth based on the "fmt " chunk.
1458
     * @private
1459
     */
1460
     bitDepthFromFmt_() {
1461
        if (this.fmt.audioFormat == 3 && this.fmt.bitsPerSample == 32) {
1462
            this.bitDepth = "32f";
1463
        } else if (this.fmt.audioFormat == 6) {
1464
            this.bitDepth = "8a";
1465
        } else if (this.fmt.audioFormat == 7) {
1466
            this.bitDepth = "8m";
1467
        } else {
1468
            this.bitDepth = this.fmt.bitsPerSample.toString();
1469
        }
1470
     }
1471
    
1472
    /**
1473
     * Return a the size of the "cue " chunk in bytes.
1474
     * @return {!number} The chunk size in bytes.
1475
     * @private
1476
     */  
1477
    getCueSize_() {
1478
        let cueSize = 12;
1479
        for (let i=0; i<this.cue.points.length; i++) {
1480
            cueSize += 24;
1481
        }
1482
        return cueSize;
1483
    }
1484
1485
    /**
1486
     * Return the RIFF chunkSize of the file in bytes.
1487
     * @return {!number} The wav file size in bytes.
1488
     * @private
1489
     */    
1490
    getFileSize_() {
1491
        let fileSize = 0;
1492
        let numBytes = (((parseInt(this.bitDepth, 10) - 1) | 7) + 1) / 8;
1493
        if ([1, 3].indexOf(this.fmt.audioFormat) > -1) {
1494
            fileSize = 36 + this.data.samples.length * numBytes;
1495
        } else if (["8a", "8m", "4"].indexOf(this.bitDepth) > -1) {
1496
            fileSize = 40 + this.data.samples.length;
1497
        } else {
1498
            fileSize = 36 + 24 + this.data.samples.length * numBytes;
1499
        }
1500
        if (this.bext.chunkId) {
1501
            fileSize += 610 + this.bext.codingHistory.length;
1502
        }
1503
        if (this.cue.chunkId) {
1504
            fileSize += this.getCueSize_();
1505
        }
1506
        if (this.fact.chunkId) {
1507
            fileSize += 12;
1508
        }
1509
        if (this.LIST.length) {
1510
            fileSize += this.getLISTSize_();
1511
        }
1512
        if (this.ds64.chunkId) {
1513
            fileSize += 36;
1514
        }
1515
        if (this.junk.chunkId) {
1516
            fileSize += this.junk.chunkData.length + 8;
1517
        }
1518
        return fileSize % 2 ? fileSize + 1 : fileSize;
1519
    }
1520
1521
    /**
1522
     * Return a .wav file byte buffer with the data from the WaveFile object.
1523
     * The return value of this method can be written straight to disk.
1524
     * @return {!Uint8Array} The wav file bytes.
1525
     * @private
1526
     */
1527
    createWaveFile_() {
1528
        return new Uint8Array([].concat(
1529
            byteData_.pack(this.container, fourCC_),
1530
            byteData_.pack(this.getFileSize_(), uInt32_),
1531
            byteData_.pack(this.format, fourCC_),
1532
            this.getJunkBytes_(),
1533
            this.getDs64Bytes_(),
1534
            this.getBextBytes_(),
1535
            this.getFmtBytes_(),
1536
            this.getFactBytes_(),
1537
            byteData_.pack(this.data.chunkId, fourCC_),
1538
            byteData_.pack(this.data.chunkSize, uInt32_),
1539
            this.samplesToBytes_(),
1540
            this.getCueBytes_(),
1541
            this.getLISTBytes_()));            
1542
    }
1543
}
1544
1545
module.exports = WaveFile;
1546