Passed
Push — master ( 2e86fa...201d87 )
by Rafael S.
01:50
created

index.js   A

Size

Lines of Code 1059

Duplication

Duplicated Lines 0
Ratio 0 %

Importance

Changes 6
Bugs 1 Features 0
Metric Value
c 6
b 1
f 0
nc 1
dl 0
loc 1059
rs 9.4313
noi 0

1 Function

Rating   Name   Duplication   Size   Complexity  
B ➔ ??? 0 211 2
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
let uInt8_ = require("byte-data").uInt8;
21
/** @private */
22
let uInt16_ = require("byte-data").uInt16;
23
/** @private */
24
let uInt32_ = require("byte-data").uInt32;
25
26
/**
27
 * Class representing a wav file.
28
 */
29
class WaveFile {
30
31
    /**
32
     * @param {!Uint8Array|!Array<number>} bytes A wave file buffer.
33
     * @throws {Error} If no "RIFF" chunk is found.
34
     * @throws {Error} If no "fmt " chunk is found.
35
     * @throws {Error} If no "fact" chunk is found and "fact" is needed.
36
     * @throws {Error} If no "data" chunk is found.
37
     */
38
    constructor(bytes) {
39
        /**
40
         * The container identifier.
41
         * Only "RIFF" and "RIFX" are supported.
42
         * @type {!string}
43
         */
44
        this.container = "";
45
        /** @type {!number} */
46
        this.chunkSize = 0;
47
        /**
48
         * The format.
49
         * Always "WAVE".
50
         * @type {!string}
51
         */
52
        this.format = "";
53
54
        /**
55
         * The data of the "fmt" chunk.
56
         * @type {!Object<string, *>}
57
         * @nocollapse
58
         */
59
        this.fmt = {
60
            /** @type {!string} */
61
            "chunkId": "",
62
            /** @type {!number} */
63
            "chunkSize": 0,
64
            /** @type {!number} */
65
            "audioFormat": 0,
66
            /** @type {!number} */
67
            "numChannels": 0,
68
            /** @type {!number} */
69
            "sampleRate": 0,
70
            /** @type {!number} */
71
            "byteRate": 0,
72
            /** @type {!number} */
73
            "blockAlign": 0,
74
            /** @type {!number} */
75
            "bitsPerSample": 0,
76
            /** @type {!number} */
77
            "cbSize": 0,
78
            /** @type {!number} */
79
            "validBitsPerSample": 0,
80
            /** @type {!number} */
81
            "dwChannelMask": 0,
82
            /**
83
             * 4 32-bit values representing a 128-bit ID
84
             * @type {!Array<number>}
85
             */
86
            "subformat": []
87
        };
88
89
        /**
90
         * The data of the "fact" chunk.
91
         * @type {!Object<string, *>}
92
         * @nocollapse
93
         */
94
        this.fact = {
95
            /** @type {!string} */
96
            "chunkId": "",
97
            /** @type {!number} */
98
            "chunkSize": 0,
99
            /** @type {!number} */
100
            "dwSampleLength": 0
101
        };
102
        
103
        /**
104
         * The data of the "cue " chunk.
105
         * @type {!Object<string, *>}
106
         * @nocollapse
107
         */
108
        this.cue = {
109
            /** @type {!string} */
110
            "chunkId": "",
111
            /** @type {!number} */
112
            "chunkSize": 0,
113
            /** @type {!Array<number>} */
114
            "chunkData_": []
115
        };
116
117
        /**
118
         * The data of the "bext" chunk.
119
         * @type {!Object<string, *>}
120
         * @nocollapse
121
         */
122
        this.bext = {
123
            /** @type {!string} */
124
            "chunkId": "",
125
            /** @type {!number} */
126
            "chunkSize": 0,
127
            /** @type {!string} */
128
            "description": "", //256
129
            /** @type {!string} */
130
            "originator": "", //32
131
            /** @type {!string} */
132
            "originatorReference": "", //32
133
            /** @type {!string} */
134
            "originationDate": "", //10
135
            /** @type {!string} */
136
            "originationTime": "", //8
137
            /**
138
             * 2 32-bit values, timeReference high and low
139
             * @type {!Array<number>}
140
             */
141
            "timeReference": [],
142
            /** @type {number} */
143
            "version": 0, //WORD
144
            /** @type {!string} */
145
            "UMID": "", // 64 chars
146
            /** @type {!number} */
147
            "loudnessValue": 0, //WORD
148
            /** @type {!number} */
149
            "loudnessRange": 0, //WORD
150
            /** @type {!number} */
151
            "maxTruePeakLevel": 0, //WORD
152
            /** @type {!number} */
153
            "maxMomentaryLoudness": 0, //WORD
154
            /** @type {!number} */
155
            "maxShortTermLoudness": 0, //WORD
156
            /** @type {!string} */
157
            "reserved": "", //180
158
            /** @type {!string} */
159
            "codingHistory": "" // string, unlimited
160
        };
161
162
        /**
163
         * The data of the "ds64" chunk.
164
         * Used only with RF64 files.
165
         * @type {!Object<string, *>}
166
         * @nocollapse
167
         */
168
        this.ds64 = {
169
            /** @type {!string} */
170
            "chunkId": "",
171
            /** @type {!number} */
172
            "riffSizeHigh": 0, // DWORD
173
            /** @type {!number} */
174
            "riffSizeLow": 0, // DWORD
175
            /** @type {!number} */
176
            "dataSizeHigh": 0, // DWORD
177
            /** @type {!number} */
178
            "dataSizeLow": 0, // DWORD
179
            /** @type {!number} */
180
            "originationTime": 0, // DWORD
181
            /** @type {!number} */
182
            "sampleCountHigh": 0, // DWORD
183
            /** @type {!number} */
184
            "sampleCountLow": 0, // DWORD
185
            /** @type {!number} */
186
            "tableLength": 0, // DWORD
187
            /** @type {!Array} */
188
            "table": []
189
        };
190
191
        /**
192
         * The data of the "data" chunk.
193
         * @type {!Object<string, *>}
194
         * @nocollapse
195
         */
196
        this.data = {
197
            /** @type {!string} */
198
            "chunkId": "",
199
            /** @type {!number} */
200
            "chunkSize": 0,
201
            /** @type {!Array<number>} */
202
            "samples": []
203
        };
204
205
        /**
206
         * @type {!string}
207
         */
208
        this.bitDepth = "0";
209
210
        /**
211
         * Header formats.
212
         * Formats not listed here will be 65534.
213
         * @enum {number}
214
         * @const
215
         */
216
        this.headerFormats_ = {
217
            "4": 17,
218
            "8": 1,
219
            "8a": 6,
220
            "8m": 7,
221
            "16": 1,
222
            "24": 1,
223
            "32": 1,
224
            "32f": 3,
225
            "40": 65534,
226
            "48": 65534,
227
            "64": 3
228
        };
229
        /**
230
         * @type {!number}
231
         * @private
232
         */
233
        this.head_ = 0;
234
        /**
235
         * If the "fact" chunk should be enforced or not.
236
         * @type {!boolean}
237
         */
238
        this.enforceFact_ = false;
239
        /**
240
         * If the data in data.samples is interleaved or not.
241
         * @type {!boolean}
242
         */
243
        this.isInterleaved = true;
244
245
        if(bytes) {
246
            this.fromBuffer(bytes);
247
        }
248
    }
249
250
    /**
251
     * Set up a WaveFile object based on the arguments passed.
252
     * @param {!number} numChannels The number of channels
253
     *     (Integer numbers: 1 for mono, 2 stereo and so on).
254
     * @param {!number} sampleRate The sample rate.
255
     *     Integer numbers like 8000, 44100, 48000, 96000, 192000.
256
     * @param {!string} bitDepth The audio bit depth.
257
     *     One of "4", "8", "8a", "8m", "16", "24", "32", "32f", "64"
258
     *     or any value between "8" and "32".
259
     * @param {!Array<number>} samples Array of samples to be written.
260
     *     The samples must be in the correct range according to the
261
     *     bit depth.
262
     * @throws {Error} If any argument does not meet the criteria.
263
     */
264
    fromScratch(numChannels, sampleRate, bitDepth, samples, options={}) {
265
        if (!options["container"]) {
266
            options["container"] = "RIFF";
267
        }
268
        
269
        // closest nuber of bytes if not / 8
270
        let numBytes = (((parseInt(bitDepth, 10) - 1) | 7) + 1) / 8;
271
        this.clearHeader_();
272
        this.bitDepth = bitDepth;
273
274
        // Normal PCM file header
275
        this.container = options["container"];
276
        this.chunkSize = 36 + samples.length * numBytes;
277
        this.format = "WAVE";
278
        this.fmt.chunkId = "fmt ";
279
        this.fmt.chunkSize = 16;
280
        this.fmt.byteRate = (numChannels * numBytes) * sampleRate;
281
        this.fmt.blockAlign = numChannels * numBytes;
282
        this.fmt.audioFormat = this.headerFormats_[bitDepth] ?
283
            this.headerFormats_[bitDepth] : 65534;
284
        this.fmt.numChannels = numChannels;
285
        this.fmt.sampleRate = sampleRate;
286
        this.fmt.bitsPerSample = parseInt(bitDepth, 10);
287
        this.data.chunkId = "data";
288
        this.data.samples = samples;
289
        // interleave the samples if they were passed de-interleaved
290
        if (samples.length > 0) {
291
            if (samples[0].constructor === Array) {
292
                this.isInterleaved = false;
293
                this.interleave();
294
            }
295
        }
296
        this.data.chunkSize = samples.length * numBytes;
297
298
        // IMA ADPCM header
299
        if (bitDepth == "4") {
300
            this.chunkSize = 44 + samples.length;
301
            this.fmt.chunkSize = 20;
302
            this.fmt.byteRate = 4055;
303
            this.fmt.blockAlign = 256;
304
            this.fmt.bitsPerSample = 4;
305
            this.fmt.cbSize = 2;
306
            this.fmt.validBitsPerSample = 505;
307
            this.fact.chunkId = "fact";
308
            this.fact.chunkSize = 4;
309
            this.fact.dwSampleLength = samples.length * 2;
310
            this.data.chunkSize = samples.length;
311
        }
312
        // A-Law and mu-Law header
313
        if (bitDepth == "8a" || bitDepth == "8m") {
314
            this.chunkSize = 44 + samples.length;
315
            this.fmt.chunkSize = 20;
316
            this.fmt.cbSize = 2;
317
            this.fmt.validBitsPerSample = 8;
318
            this.fact.chunkId = "fact";
319
            this.fact.chunkSize = 4;
320
            this.fact.dwSampleLength = samples.length;
321
        }
322
        // WAVE_FORMAT_EXTENSIBLE
323
        if (this.fmt.audioFormat == 65534) {
324
            this.chunkSize = 36 + 24 + samples.length * numBytes;
325
            this.fmt.chunkSize = 40;
326
            this.fmt.bitsPerSample = ((parseInt(bitDepth, 10) - 1) | 7) + 1;
327
            this.fmt.cbSize = 22;
328
            this.fmt.validBitsPerSample = parseInt(bitDepth, 10);
329
            this.fmt.dwChannelMask = 0;
330
            // subformat 128-bit GUID as 4 32-bit values
331
            // only supports uncompressed integer PCM samples
332
            this.fmt.subformat = [1, 1048576, 2852126848, 1905997824];
333
        }
334
    }
335
336
    /**
337
     * Init a WaveFile object from a byte buffer.
338
     * @param {!Uint8Array|!Array<number>} bytes The buffer.
339
     * @throws {Error} If container is not RIFF or RIFX.
340
     * @throws {Error} If no "fmt " chunk is found.
341
     * @throws {Error} If no "fact" chunk is found and "fact" is needed.
342
     * @throws {Error} If no "data" chunk is found.
343
     */
344
    fromBuffer(bytes) {
345
        this.readRIFFChunk_(bytes);
346
        let bigEndian = this.container == "RIFX";
347
        let chunk = riffChunks_.read(bytes, bigEndian);
348
        this.readFmtChunk_(chunk["subChunks"]);
349
        this.readFactChunk_(chunk["subChunks"]);
350
        this.readBextChunk_(chunk["subChunks"]);
351
        this.readCueChunk_(chunk["subChunks"]);
352
        this.readDataChunk_(
353
                chunk["subChunks"],
354
                {"be": bigEndian, "single": true});
355
        if (this.fmt.audioFormat == 3 && this.fmt.bitsPerSample == 32) {
356
            this.bitDepth = "32f";
357
        } else if (this.fmt.audioFormat == 6) {
358
            this.bitDepth = "8a";
359
        } else if (this.fmt.audioFormat == 7) {
360
            this.bitDepth = "8m";
361
        } else {
362
            this.bitDepth = this.fmt.bitsPerSample.toString();
363
        }
364
    }
365
366
    /**
367
     * Return a byte buffer representig the WaveFile object as a wav file.
368
     * @return {Uint8Array}
369
     * @throws {Error} If any property of the object appears invalid.
370
     */
371
    toBuffer() {
372
        this.checkWriteInput_();
373
        this.assureInterleaved_();
374
        return new Uint8Array(this.createWaveFile_());
375
    }
376
377
    /**
378
     * Turn the file to RIFF.
379
     */
380
    toRIFF() {
381
        this.container = "RIFF";
382
        this.LEorBE_();
383
    }
384
385
    /**
386
     * Turn the file to RIFX.
387
     */
388
    toRIFX() {
389
        this.container = "RIFX";
390
        this.LEorBE_();
391
    }
392
393
    /**
394
     * Change the bit depth of the samples.
395
     * @param {!string} bitDepth The new bit depth of the samples.
396
     *      One of "8" ... "32" (integers), "32f" or "64" (floats)
397
     * @param {!boolean} changeResolution A boolean indicating if the
398
     *      resolution of samples should be actually changed or not.
399
     * @throws {Error} If the bit depth is not valid.
400
     */
401
    toBitDepth(bitDepth, changeResolution=true) {
402
        let toBitDepth = bitDepth;
403
        let thisBitDepth = this.bitDepth;
404
        if (!changeResolution) {
405
            toBitDepth = this.realBitDepth_(bitDepth);
406
            thisBitDepth = this.realBitDepth_(this.bitDepth);
407
        }
408
        this.assureInterleaved_();
409
        bitDepth_.toBitDepth(this.data.samples, thisBitDepth, toBitDepth);
410
        this.fromScratch(
411
            this.fmt.numChannels,
412
            this.fmt.sampleRate,
413
            bitDepth,
414
            this.data.samples,
415
            {"container": this.container}
416
        );
417
    }
418
419
    /**
420
     * Interleave multi-channel samples.
421
     */
422
    interleave() {
423
        if (!this.isInterleaved) {
424
            let finalSamples = [];
425
            let numChannels = this.data.samples[0].length;
426
            for (let i = 0; i < numChannels; i++) {
427
                for (let j = 0; j < this.data.samples.length; j++) {
428
                    finalSamples.push(this.data.samples[j][i]);
429
                }
430
            }
431
            this.data.samples = finalSamples;
432
            this.isInterleaved = true;    
433
        }
434
    }
435
436
    /**
437
     * De-interleave samples into multiple channels.
438
     */
439
    deInterleave() {
440
        if (this.isInterleaved) {
441
            let finalSamples = [];
442
            let i;
443
            for (i = 0; i < this.fmt.numChannels; i++) {
444
                finalSamples[i] = [];
445
            }
446
            i = 0;
447
            let j;
448
            while (i < this.data.samples.length) {
449
                for (j = 0; j < this.fmt.numChannels; j++) {
450
                    finalSamples[j].push(this.data.samples[i+j]);
451
                }
452
                i += j;
453
            }
454
            this.data.samples = finalSamples;
455
            this.isInterleaved = false;
456
        }
457
    }
458
459
    /**
460
     * Encode a 16-bit wave file as 4-bit IMA ADPCM.
461
     * @throws {Error} If sample rate is not 8000.
462
     * @throws {Error} If number of channels is not 1.
463
     */
464
    toIMAADPCM() {
465
        if (this.fmt.sampleRate != 8000) {
466
            throw new Error(
467
                "Only 8000 Hz files can be compressed as IMA-ADPCM.");
468
        } else if(this.fmt.numChannels != 1) {
469
            throw new Error(
470
                "Only mono files can be compressed as IMA-ADPCM.");
471
        } else {
472
            this.assure16Bit_();
473
            this.fromScratch(
474
                this.fmt.numChannels,
475
                this.fmt.sampleRate,
476
                "4",
477
                imaadpcm_.encode(this.data.samples),
478
                {"container": this.container}
479
            );
480
        }
481
    }
482
483
    /**
484
     * Decode a 4-bit IMA ADPCM wave file as a 16-bit wave file.
485
     */
486
    fromIMAADPCM() {
487
        this.fromScratch(
488
            this.fmt.numChannels,
489
            this.fmt.sampleRate,
490
            "16",
491
            imaadpcm_.decode(this.data.samples, this.fmt.blockAlign),
492
            {"container": this.container}
493
        );
494
    }
495
496
    /**
497
     * Encode 16-bit wave file as 8-bit A-Law.
498
     */
499
    toALaw() {
500
        this.assure16Bit_();
501
        this.assureInterleaved_();
502
        this.fromScratch(
503
            this.fmt.numChannels,
504
            this.fmt.sampleRate,
505
            "8a",
506
            alawmulaw_.alaw.encode(this.data.samples),
507
            {"container": this.container}
508
        );
509
    }
510
511
    /**
512
     * Decode a 8-bit A-Law wave file into a 16-bit wave file.
513
     */
514
    fromALaw() {
515
        this.fromScratch(
516
            this.fmt.numChannels,
517
            this.fmt.sampleRate,
518
            "16",
519
            alawmulaw_.alaw.decode(this.data.samples),
520
            {"container": this.container}
521
        );
522
    }
523
524
    /**
525
     * Encode 16-bit wave file as 8-bit mu-Law.
526
     */
527
    toMuLaw() {
528
        this.assure16Bit_();
529
        this.assureInterleaved_();
530
        this.fromScratch(
531
            this.fmt.numChannels,
532
            this.fmt.sampleRate,
533
            "8m",
534
            alawmulaw_.mulaw.encode(this.data.samples),
535
            {"container": this.container}
536
        );
537
    }
538
539
    /**
540
     * Decode a 8-bit mu-Law wave file into a 16-bit wave file.
541
     */
542
    fromMuLaw() {
543
        this.fromScratch(
544
            this.fmt.numChannels,
545
            this.fmt.sampleRate,
546
            "16",
547
            alawmulaw_.mulaw.decode(this.data.samples),
548
            {"container": this.container}
549
        );
550
    }
551
552
    /**
553
     * Get the closest greater number of bits for a number of bits that
554
     * do not fill a full sequence of bytes.
555
     * @param {!string} bitDepth The bit depth.
556
     * @return {string}
557
     */
558
    realBitDepth_(bitDepth) {
559
        if (bitDepth != "32f") {
560
            bitDepth = (((parseInt(bitDepth, 10) - 1) | 7) + 1).toString();
561
        }
562
        return bitDepth;
563
    }
564
565
    /**
566
     * Validate the input for wav writing.
567
     * @throws {Error} If any property of the object appears invalid.
568
     * @private
569
     */
570
    checkWriteInput_() {
571
        this.validateBitDepth_();
572
        this.validateNumChannels_();
573
        this.validateSampleRate_();
574
    }
575
576
    /**
577
     * Validate the bit depth.
578
     * @throws {Error} If bit depth is invalid.
579
     * @private
580
     */
581
    validateBitDepth_() {
582
        if (!this.headerFormats_[this.bitDepth]) {
583
            if (parseInt(this.bitDepth, 10) > 8 &&
584
                    parseInt(this.bitDepth, 10) < 54) {
585
                return true;
586
            }
587
            throw new Error("Invalid bit depth.");
588
        }
589
        return true;
590
    }
591
592
    /**
593
     * Validate the number of channels.
594
     * @throws {Error} If the number of channels is invalid.
595
     * @private
596
     */
597
    validateNumChannels_() {
598
        let blockAlign = this.fmt.numChannels * this.fmt.bitsPerSample / 8;
599
        if (this.fmt.numChannels < 1 || blockAlign > 65535) {
600
            throw new Error("Invalid number of channels.");
601
        }
602
        return true;
603
    }
604
605
    /**
606
     * Validate the sample rate value.
607
     * @throws {Error} If the sample rate is invalid.
608
     * @private
609
     */
610
    validateSampleRate_() {
611
        let byteRate = this.fmt.numChannels *
612
            (this.fmt.bitsPerSample / 8) * this.fmt.sampleRate;
613
        if (this.fmt.sampleRate < 1 || byteRate > 4294967295) {
614
            throw new Error("Invalid sample rate.");
615
        }
616
        return true;
617
    }
618
619
    /**
620
     * Reset the attributes related to the "fact" chunk.
621
     * @private
622
     */
623
    clearHeader_() {
624
        this.fmt.cbSize = 0;
625
        this.fmt.validBitsPerSample = 0;
626
        this.fact.chunkId = "";
627
        this.fact.chunkSize = 0;
628
        this.fact.dwSampleLength = 0;
629
    }
630
631
    /**
632
     * Make the file 16-bit if it is not.
633
     * @private
634
     */
635
    assure16Bit_() {
636
        this.assureUncompressed_();
637
        if (this.bitDepth != "16") {
638
            this.toBitDepth("16");
639
        }
640
    }
641
642
    /**
643
     * Uncompress the samples in case of a compressed file.
644
     * @private
645
     */
646
    assureUncompressed_() {
647
        if (this.bitDepth == "8a") {
648
            this.fromALaw();
649
        } else if(this.bitDepth == "8m") {
650
            this.fromMuLaw();
651
        } else if (this.bitDepth == "4") {
652
            this.fromIMAADPCM();
653
        }
654
    }
655
656
    /**
657
     * Interleave the samples in case they are de-Interleaved.
658
     * @private
659
     */
660
    assureInterleaved_() {
661
        if (!this.isInterleaved) {
662
            this.interleave();
663
        }
664
    }
665
666
    /**
667
     * Read the RIFF chunk a wave file.
668
     * @param {!Uint8Array|!Array<number>} bytes A wav buffer.
669
     * @throws {Error} If no "RIFF" chunk is found.
670
     * @private
671
     */
672
    readRIFFChunk_(bytes) {
673
        this.container = byteData_.unpackArray(
674
            bytes.slice(0, 4), byteData_.chr);
675
        if (this.container != "RIFF" && this.container != "RIFX") {
676
            throw Error("Not a supported format.");
677
        }
678
        this.LEorBE_();
679
        this.chunkSize = byteData_.unpack(bytes.slice(4, 8), uInt32_);
680
        this.format = byteData_.unpackArray(
681
            bytes.slice(8, 12), byteData_.chr);
682
        if (this.format != "WAVE") {
683
            throw Error("Could not find the 'WAVE' format identifier");
684
        }
685
    }
686
687
    /**
688
     * Set up to work wih big-endian or little-endian files.
689
     * The types used are changed from LE or BE. If the
690
     * the file is big-endian (RIFX), true is returned.
691
     * @private
692
     */
693
    LEorBE_() {
694
        let bigEndian = this.container == "RIFX";
695
        uInt8_.be = bigEndian;
696
        uInt16_.be = bigEndian;
697
        uInt32_.be = bigEndian;
698
        return bigEndian;
699
    }
700
701
    /**
702
     * Read the "fmt " chunk of a wave file.
703
     * @param {!Object<string, *>} chunks The wav file chunks.
704
     * @throws {Error} If no "fmt " chunk is found.
705
     * @private
706
     */
707
    readFmtChunk_(chunks) {
708
        let chunk = this.findChunk_(chunks, "fmt ");
709
        if (chunk) {
710
            this.fmt.chunkId = "fmt ";
711
            this.fmt.chunkSize = chunk["chunkSize"];
712
            this.fmt.audioFormat = byteData_.unpack(
713
                chunk["chunkData"].slice(0, 2), uInt16_);
714
            this.fmt.numChannels = byteData_.unpack(
715
                chunk["chunkData"].slice(2, 4), uInt16_);
716
            this.fmt.sampleRate = byteData_.unpack(
717
                chunk["chunkData"].slice(4, 8), uInt32_);
718
            this.fmt.byteRate = byteData_.unpack(
719
                chunk["chunkData"].slice(8, 12), uInt32_);
720
            this.fmt.blockAlign = byteData_.unpack(
721
                chunk["chunkData"].slice(12, 14), uInt16_);
722
            this.fmt.bitsPerSample = byteData_.unpack(
723
                    chunk["chunkData"].slice(14, 16), uInt16_);
724
            this.readFmtExtension_(chunk);
725
        } else {
726
            throw Error("Could not find the 'fmt ' chunk");
727
        }
728
    }
729
730
    /**
731
     * Read the "fmt " chunk extension.
732
     * @param {!Object<string, *>} chunk The "fmt " chunk.
733
     * @private
734
     */
735
    readFmtExtension_(chunk) {
736
        let chunkData = chunk["chunkData"];
737
        if (this.fmt.chunkSize > 16) {
738
            this.fmt.cbSize = byteData_.unpack(
739
                chunkData.slice(16, 18), uInt16_);
740
            if (this.fmt.chunkSize > 18) {
741
                this.fmt.validBitsPerSample = byteData_.unpack(
742
                    chunkData.slice(18, 20), uInt16_);
743
                if (this.fmt.chunkSize > 20) {
744
                    this.fmt.dwChannelMask = byteData_.unpack(
745
                        chunkData.slice(20, 24), uInt32_);
746
                    this.fmt.subformat = [
747
                        byteData_.unpack(
748
                            chunkData.slice(24, 28), uInt32_),
749
                        byteData_.unpack(
750
                            chunkData.slice(28, 32), uInt32_),
751
                        byteData_.unpack(
752
                            chunkData.slice(32, 36), uInt32_),
753
                        byteData_.unpack(
754
                            chunkData.slice(36, 40), uInt32_)];
755
                }
756
            }
757
        }
758
    }
759
760
    /**
761
     * Read the "fact" chunk of a wav file.
762
     * @param {!Object<string, *>} chunks The wav file chunks.
763
     * @throws {Error} If no "fact" chunk is found.
764
     * @private
765
     */
766
    readFactChunk_(chunks) {
767
        let chunk = this.findChunk_(chunks, "fact");
768
        if (chunk) {
769
            this.fact.chunkId = "fact";
770
            this.fact.chunkSize = chunk["chunkSize"];
771
            this.fact.dwSampleLength = byteData_.unpack(
772
                chunk["chunkData"].slice(0, 4), uInt32_);
773
        } else if (this.enforceFact_) {
774
            throw Error("Could not find the 'fact' chunk");
775
        }
776
    }
777
778
    /**
779
     * Read the "bext" chunk of a wav file.
780
     * @param {!Object<string, *>} chunks The wav file chunks.
781
     * @private
782
     */
783
    readBextChunk_(chunks) {
784
        let chunk = this.findChunk_(chunks, "bext");
785
        if (chunk) {
786
            let chunkData = chunk["chunkData"];
787
            this.head_ = 0;
788
            this.bext =  {
789
                "chunkId": "bext",
790
                "chunkSize": chunkData.length,
791
                "description": this.readString_(chunkData, 256),
792
                "originator": this.readString_(chunkData, 32),
793
                "originatorReference": this.readString_(chunkData, 32),
794
                "originationDate": this.readString_(chunkData, 10),
795
                "originationTime": this.readString_(chunkData, 8),
796
                "timeReference": [
797
                        this.readFromChunk_(chunkData, uInt32_),
798
                        this.readFromChunk_(chunkData, uInt32_)], 
799
                "version": this.readFromChunk_(
800
                    chunkData, uInt16_),
801
                "UMID": this.readString_(chunkData, 64),
802
                "loudnessValue": this.readFromChunk_(
803
                    chunkData, uInt16_),
804
                "loudnessRange": this.readFromChunk_(
805
                    chunkData, uInt16_),
806
                "maxTruePeakLevel": this.readFromChunk_(
807
                    chunkData, uInt16_),
808
                "maxMomentaryLoudness": this.readFromChunk_(
809
                    chunkData, uInt16_),
810
                "maxShortTermLoudness": this.readFromChunk_(
811
                    chunkData, uInt16_),
812
                "reserved": this.readString_(chunkData, 180),
813
                "codingHistory": this.readString_(
814
                    chunkData, chunkData.length - 602),
815
            };
816
        }
817
    }
818
819
    /**
820
     * Read bytes as a string from a RIFF chunk.
821
     * @param {!Array<number>|!Uint8Array} bytes The bytes.
822
     * @param {!number} maxSize the max size of the string.
823
     * @private
824
     */
825
    readString_(bytes, maxSize) {
826
        let str = "";
827
        for (let i=0; i<maxSize; i++) {
828
            str += byteData_.unpack([bytes[this.head_]], byteData_.chr);
829
            this.head_++;
830
        }
831
        return str;
832
    }
833
834
    /**
835
     * Read a number from a chunk.
836
     * @param {!Array<number>|!Uint8Array} bytes The chunk bytes.
837
     * @param {!Object<string, *>} bdType The byte-data corresponding type.
838
     * @private
839
     */
840
    readFromChunk_(bytes, bdType) {
841
        let size = bdType.bits / 8;
842
        let value = byteData_.unpack(
843
            bytes.slice(this.head_, this.head_ + size), bdType);
844
        this.head_ += size;
845
        return value;
846
    }
847
848
    /**
849
     * Write a variable size string as bytes.
850
     * If the string is smaller than the max size it 
851
     * is filled with 0s.
852
     * @param {!string} str The string to be written as bytes.
853
     * @param {!number} maxSize the max size of the string.
854
     * @private
855
     */
856
    writeString_(str, maxSize) {
857
        let bytes = byteData_.packArray(str, byteData_.chr);
858
        for (let i=bytes.length; i<maxSize; i++) {
859
            bytes.push(0);
860
        }
861
        return bytes;
862
    }
863
864
    /**
865
     * Read the "cue " chunk of a wave file.
866
     * @param {!Object<string, *>} chunks The RIFF file chunks.
867
     * @private
868
     */
869
    readCueChunk_(chunks) {
870
        let chunk = this.findChunk_(chunks, "cue ");
871
        if (chunk) {
872
            this.cue.chunkId = "cue ";
873
            this.cue.chunkSize = chunk["chunkSize"];
874
            this.cue.chunkData_ = chunk["chunkData"];
875
        }
876
    }
877
878
    /**
879
     * Read the "data" chunk of a wave file.
880
     * @param {!Object<string, *>} chunks The RIFF file chunks.
881
     * @param {!Object<string, *>} options Type options.
882
     * @throws {Error} If no "data" chunk is found.
883
     * @private
884
     */
885
    readDataChunk_(chunks, options) {
886
        let chunk = this.findChunk_(chunks, "data");
887
        if (chunk) {
888
            this.data.chunkId = "data";
889
            this.data.chunkSize = chunk["chunkSize"];
890
            this.samplesFromBytes_(chunk["chunkData"], options);
891
        } else {
892
            throw Error("Could not find the 'data' chunk");
893
        }
894
    }
895
896
    /**
897
     * Find and return the start offset of the data chunk on a wave file.
898
     * @param {!Array<number>|!Uint8Array} bytes A wav file buffer.
899
     * @param {!Object<string, *>} options Type options.
900
     * @private
901
     */
902
    samplesFromBytes_(bytes, options) {
903
        options.bits = this.fmt.bitsPerSample == 4 ?
904
            8 : this.fmt.bitsPerSample;
905
        options.signed = options.bits == 8 ? false : true;
906
        options.float = (this.fmt.audioFormat == 3 || 
907
            this.fmt.bitsPerSample == 64) ? true : false;
908
        options.single = false;
909
        this.data.samples = byteData_.unpackArray(
910
            bytes, new byteData_.Type(options));
911
    }
912
913
    /**
914
     * Find a chunk by its FourCC in a array of RIFF chunks.
915
     * @param {!Object<string, Object>} chunks The wav file chunks.
916
     * @param {!string} fourCC The chunk fourCC.
917
     * @return {Object|null}
918
     * @private
919
     */
920
    findChunk_(chunks, fourCC) {
921
        for (let i = 0; i<chunks.length; i++) {
922
            if (chunks[i]["chunkId"] == fourCC) {
923
                return chunks[i];
924
            }
925
        }
926
        return null;
927
    }
928
929
    /**
930
     * Turn samples to bytes.
931
     * @param {!Object<string, *>} options Type options.
932
     * @private
933
     */
934
    samplesToBytes_(options) {
935
        options.bits = this.fmt.bitsPerSample == 4 ?
936
            8 : this.fmt.bitsPerSample;
937
        options.signed = options.bits == 8 ? false : true;
938
        options.float = (this.fmt.audioFormat == 3  ||
939
                this.fmt.bitsPerSample == 64) ? true : false;
940
        let bytes = byteData_.packArray(
941
            this.data.samples, new byteData_.Type(options));
942
        if (bytes.length % 2) {
943
            bytes.push(0);
944
        }
945
        return bytes;
946
    }
947
948
    /**
949
     * Get the bytes of the "bext" chunk.
950
     * @return {!Array<number>} The "bext" chunk bytes.
951
     * @private
952
     */
953
    getBextBytes_() {
954
        if (this.bext.chunkId) {
955
            return [].concat(
956
                byteData_.packArray(this.bext.chunkId, byteData_.chr),
957
                byteData_.pack(this.bext.chunkSize, uInt32_),
958
                this.writeString_(this.bext.description, 256),
959
                this.writeString_(this.bext.originator, 32),
960
                this.writeString_(this.bext.originatorReference, 32),
961
                this.writeString_(this.bext.originationDate, 10),
962
                this.writeString_(this.bext.originationTime, 8),
963
                byteData_.pack(this.bext.timeReference[0], uInt32_),
964
                byteData_.pack(this.bext.timeReference[1], uInt32_),
965
                byteData_.pack(this.bext.version, uInt16_),
966
                this.writeString_(this.bext.UMID, 64),
967
                byteData_.pack(this.bext.loudnessValue, uInt16_),
968
                byteData_.pack(this.bext.loudnessRange, uInt16_),
969
                byteData_.pack(this.bext.maxTruePeakLevel, uInt16_),
970
                byteData_.pack(this.bext.maxMomentaryLoudness, uInt16_),
971
                byteData_.pack(this.bext.maxShortTermLoudness, uInt16_),
972
                this.writeString_(this.bext.reserved, 180),
973
                this.writeString_(
974
                    this.bext.codingHistory,
975
                    this.bext.chunkSize - 602));
976
        }
977
        return [];
978
    }
979
980
    /**
981
     * Get the bytes of the "cue " chunk.
982
     * @return {!Array<number>} The "cue " chunk bytes.
983
     * @private
984
     */
985
    getCueBytes_() {
986
        if (this.cue.chunkId) {
987
            return [].concat(
988
                byteData_.packArray(this.cue.chunkId, byteData_.chr),
989
                byteData_.pack(this.cue.chunkSize, uInt32_),
990
                this.cue.chunkData_);
991
        }
992
        return [];
993
    }
994
995
    /**
996
     * Get the bytes of the "fact" chunk.
997
     * @return {!Array<number>} The "fact" chunk bytes.
998
     * @private
999
     */
1000
    getFactBytes_() {
1001
        if (this.fact.chunkId) {
1002
            return [].concat(
1003
                byteData_.packArray(this.fact.chunkId, byteData_.chr),
1004
                byteData_.pack(this.fact.chunkSize, uInt32_),
1005
                byteData_.pack(this.fact.dwSampleLength, uInt32_));
1006
        }
1007
        return [];
1008
    }
1009
1010
    /**
1011
     * Get the bytes of the fmt extension fields.
1012
     * @return {!Array<number>} The fmt extension bytes.
1013
     * @private
1014
     */
1015
    getFmtExtensionBytes_() {
1016
        let extension = [];
1017
        if (this.fmt.chunkSize > 16) {
1018
            extension = extension.concat(
1019
                byteData_.pack(this.fmt.cbSize, uInt16_));
1020
        }
1021
        if (this.fmt.chunkSize > 18) {
1022
            extension = extension.concat(
1023
                byteData_.pack(this.fmt.validBitsPerSample, uInt16_));
1024
        }
1025
        if (this.fmt.chunkSize > 20) {
1026
            extension = extension.concat(
1027
                byteData_.pack(this.fmt.dwChannelMask, uInt32_));
1028
        }
1029
        if (this.fmt.chunkSize > 24) {
1030
            extension = extension.concat(
1031
                byteData_.pack(this.fmt.subformat[0], uInt32_),
1032
                byteData_.pack(this.fmt.subformat[1], uInt32_),
1033
                byteData_.pack(this.fmt.subformat[2], uInt32_),
1034
                byteData_.pack(this.fmt.subformat[3], uInt32_));
1035
        }
1036
        return extension;
1037
    }
1038
    
1039
    /**
1040
     * Turn a WaveFile object into a file.
1041
     * @return {!Array<number>} The wav file bytes.
1042
     * @private
1043
     */
1044
    createWaveFile_() {
1045
        let options = {"be": this.LEorBE_()};
1046
        return [].concat(
1047
            byteData_.packArray(this.container, byteData_.chr),
1048
            byteData_.pack(this.chunkSize, uInt32_),
1049
            byteData_.packArray(this.format, byteData_.chr),
1050
            this.getBextBytes_(),
1051
            byteData_.packArray(this.fmt.chunkId, byteData_.chr),
1052
            byteData_.pack(this.fmt.chunkSize, uInt32_),
1053
            byteData_.pack(this.fmt.audioFormat, uInt16_),
1054
            byteData_.pack(this.fmt.numChannels, uInt16_),
1055
            byteData_.pack(this.fmt.sampleRate, uInt32_),
1056
            byteData_.pack(this.fmt.byteRate, uInt32_),
1057
            byteData_.pack(this.fmt.blockAlign, uInt16_),
1058
            byteData_.pack(this.fmt.bitsPerSample, uInt16_),
1059
            this.getFmtExtensionBytes_(),
1060
            this.getFactBytes_(),
1061
            byteData_.packArray(this.data.chunkId, byteData_.chr),
1062
            byteData_.pack(this.data.chunkSize, uInt32_),
1063
            this.samplesToBytes_(options),
1064
            this.getCueBytes_());
1065
    }
1066
}
1067
1068
module.exports = WaveFile;
1069