Passed
Push — master ( 62ab07...d927b7 )
by Rafael S.
01:47
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 uInt8_ = {"bits": 8};
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
         */
48
        this.container = "";
49
        /**
50
         * @type {!number}
51
         */
52
        this.chunkSize = 0;
53
        /**
54
         * The format.
55
         * Always "WAVE".
56
         * @type {!string}
57
         */
58
        this.format = "";
59
        /**
60
         * The data of the "fmt" chunk.
61
         * @type {!Object<string, *>}
62
         */
63
        this.fmt = {
64
            /** @type {!string} */
65
            "chunkId": "",
66
            /** @type {!number} */
67
            "chunkSize": 0,
68
            /** @type {!number} */
69
            "audioFormat": 0,
70
            /** @type {!number} */
71
            "numChannels": 0,
72
            /** @type {!number} */
73
            "sampleRate": 0,
74
            /** @type {!number} */
75
            "byteRate": 0,
76
            /** @type {!number} */
77
            "blockAlign": 0,
78
            /** @type {!number} */
79
            "bitsPerSample": 0,
80
            /** @type {!number} */
81
            "cbSize": 0,
82
            /** @type {!number} */
83
            "validBitsPerSample": 0,
84
            /** @type {!number} */
85
            "dwChannelMask": 0,
86
            /**
87
             * 4 32-bit values representing a 128-bit ID
88
             * @type {!Array<number>} 
89
             */
90
            "subformat": []
91
        };
92
        /**
93
         * The data of the "fact" chunk.
94
         * @type {!Object<string, *>} 
95
         */
96
        this.fact = {
97
            /** @type {!string} */
98
            "chunkId": "",
99
            /** @type {!number} */
100
            "chunkSize": 0,
101
            /** @type {!number} */
102
            "dwSampleLength": 0
103
        };
104
        /**
105
         * The data of the "cue " chunk.
106
         * @type {!Object<string, *>} 
107
         */
108
        this.cue = {
109
            /** @type {!string} */
110
            "chunkId": "",
111
            /** @type {!number} */
112
            "chunkSize": 0,
113
            /** @type {!number} */
114
            "dwCuePoints": 0,
115
            /** @type {!Array<Object>} */
116
            "points": [],
117
        };
118
        /**
119
         * The data of the "bext" chunk.
120
         * @type {!Object<string, *>}
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
         * The data of the "ds64" chunk.
163
         * Used only with RF64 files.
164
         * @type {!Object<string, *>}
165
         */
166
        this.ds64 = {
167
            /** @type {!string} */
168
            "chunkId": "",
169
            /** @type {!number} */
170
            "chunkSize": 0,
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<number>} */
188
            "table": []
189
        };
190
        /**
191
         * The data of the "data" chunk.
192
         * @type {Object}
193
         */
194
        this.data = {
195
            /** @type {!string} */
196
            "chunkId": "",
197
            /** @type {!number} */
198
            "chunkSize": 0,
199
            /** @type {!Array<number>} */
200
            "samples": []
201
        };
202
        /**
203
         * If the data in data.samples is interleaved or not.
204
         * @type {!boolean}
205
         */
206
        this.isInterleaved = true;
207
        /**
208
         * @type {!string}
209
         */
210
        this.bitDepth = "0";
211
        /**
212
         * Audio formats.
213
         * Formats not listed here will be set to 65534
214
         * and treated as WAVE_FORMAT_EXTENSIBLE
215
         * @enum {!number}
216
         * @private
217
         */
218
        this.audioFormats_ = {
219
            "4": 17,
220
            "8": 1,
221
            "8a": 6,
222
            "8m": 7,
223
            "16": 1,
224
            "24": 1,
225
            "32": 1,
226
            "32f": 3,
227
            "40": 65534,
228
            "48": 65534,
229
            "64": 3
230
        };
231
        /**
232
         * @type {!number}
233
         * @private
234
         */
235
        this.head_ = 0;
236
        /**
237
         * If the "fact" chunk should be enforced or not.
238
         * @type {!boolean}
239
         * @private 
240
         */
241
        this.enforceFact_ = false;
242
        // Load a file from the buffer if one was passed
243
        // when creating the object
244
        if(bytes) {
245
            this.fromBuffer(bytes);
246
        }
247
    }
248
249
    /**
250
     * Set up a WaveFile object based on the arguments passed.
251
     * @param {!number} numChannels The number of channels
252
     *     (Integer numbers: 1 for mono, 2 stereo and so on).
253
     * @param {!number} sampleRate The sample rate.
254
     *     Integer numbers like 8000, 44100, 48000, 96000, 192000.
255
     * @param {!string} bitDepth The audio bit depth.
256
     *     One of "4", "8", "8a", "8m", "16", "24", "32", "32f", "64"
257
     *     or any value between "8" and "32".
258
     * @param {!Array<number>} samples Array of samples to be written.
259
     *     The samples must be in the correct range according to the
260
     *     bit depth.
261
     * @throws {Error} If any argument does not meet the criteria.
262
     */
263
    fromScratch(numChannels, sampleRate, bitDepth, samples, options={}) {
264
        if (!options["container"]) {
265
            options["container"] = "RIFF";
266
        }
267
        // closest nuber of bytes if not / 8
268
        let numBytes = (((parseInt(bitDepth, 10) - 1) | 7) + 1) / 8;
269
        this.clearHeader_();
270
        this.bitDepth = bitDepth;
271
        // Normal PCM file header
272
        this.container = options["container"];
273
        this.chunkSize = 36 + samples.length * numBytes;
274
        this.format = "WAVE";
275
        this.fmt.chunkId = "fmt ";
276
        this.fmt.chunkSize = 16;
277
        this.fmt.byteRate = (numChannels * numBytes) * sampleRate;
278
        this.fmt.blockAlign = numChannels * numBytes;
279
        this.fmt.audioFormat = this.audioFormats_[bitDepth] ?
280
            this.audioFormats_[bitDepth] : 65534;
281
        this.fmt.numChannels = numChannels;
282
        this.fmt.sampleRate = sampleRate;
283
        this.fmt.bitsPerSample = parseInt(bitDepth, 10);
284
        this.data.chunkId = "data";
285
        this.data.samples = samples;
286
        // interleave the samples if they were passed de-interleaved
287
        if (samples.length > 0) {
288
            if (samples[0].constructor === Array) {
289
                this.isInterleaved = false;
290
                this.interleave();
291
            }
292
        }
293
        this.data.chunkSize = samples.length * numBytes;
294
        // IMA ADPCM header
295
        if (bitDepth == "4") {
296
            this.chunkSize = 44 + samples.length;
297
            this.fmt.chunkSize = 20;
298
            this.fmt.byteRate = 4055;
299
            this.fmt.blockAlign = 256;
300
            this.fmt.bitsPerSample = 4;
301
            this.fmt.cbSize = 2;
302
            this.fmt.validBitsPerSample = 505;
303
            this.fact.chunkId = "fact";
304
            this.fact.chunkSize = 4;
305
            this.fact.dwSampleLength = samples.length * 2;
306
            this.data.chunkSize = samples.length;
307
        }
308
        // A-Law and mu-Law header
309
        if (bitDepth == "8a" || bitDepth == "8m") {
310
            this.chunkSize = 44 + samples.length;
311
            this.fmt.chunkSize = 20;
312
            this.fmt.cbSize = 2;
313
            this.fmt.validBitsPerSample = 8;
314
            this.fact.chunkId = "fact";
315
            this.fact.chunkSize = 4;
316
            this.fact.dwSampleLength = samples.length;
317
        }
318
        // WAVE_FORMAT_EXTENSIBLE
319
        if (this.fmt.audioFormat == 65534) {
320
            this.chunkSize = 36 + 24 + samples.length * numBytes;
321
            this.fmt.chunkSize = 40;
322
            this.fmt.bitsPerSample = ((parseInt(bitDepth, 10) - 1) | 7) + 1;
323
            this.fmt.cbSize = 22;
324
            this.fmt.validBitsPerSample = parseInt(bitDepth, 10);
325
            this.fmt.dwChannelMask = 0;
326
            // subformat 128-bit GUID as 4 32-bit values
327
            // only supports uncompressed integer PCM samples
328
            this.fmt.subformat = [1, 1048576, 2852126848, 1905997824];
329
        }
330
        this.checkWriteInput_();
331
    }
332
333
    /**
334
     * Init a WaveFile object from a byte buffer.
335
     * @param {!Uint8Array|!Array<number>} bytes The buffer.
336
     * @throws {Error} If container is not RIFF or RIFX.
337
     * @throws {Error} If no "fmt " chunk is found.
338
     * @throws {Error} If no "fact" chunk is found and "fact" is needed.
339
     * @throws {Error} If no "data" chunk is found.
340
     */
341
    fromBuffer(bytes) {
342
        this.readRIFFChunk_(bytes);
343
        let bigEndian = this.container == "RIFX";
344
        let chunk = riffChunks_.read(bytes, bigEndian);
345
        this.readDs64Chunk_(chunk["subChunks"]);
346
        this.readFmtChunk_(chunk["subChunks"]);
347
        this.readFactChunk_(chunk["subChunks"]);
348
        this.readBextChunk_(chunk["subChunks"]);
349
        this.readCueChunk_(chunk["subChunks"]);
350
        this.readDataChunk_(chunk["subChunks"], {"be": bigEndian});
351
        this.bitDepthFromFmt_();
352
    }
353
354
    /**
355
     * Return a byte buffer representig the WaveFile object as a wav file.
356
     * The return value of this method can be written straight to disk.
357
     * @return {!Uint8Array} A .wav file.
358
     * @throws {Error} If any property of the object appears invalid.
359
     */
360
    toBuffer() {
361
        this.checkWriteInput_();
362
        this.assureInterleaved_();
363
        return this.createWaveFile_();
364
    }
365
366
    /**
367
     * Force a file as RIFF. Do not work with RF64 files.
368
     */
369
    toRIFF() {
370
        if (this.container == "RF64") {
371
            this.fromScratch(
372
                this.fmt.numChannels,
373
                this.fmt.sampleRate,
374
                this.bitDepth,
375
                this.data.samples);
376
        } else {
377
            this.container = "RIFF";
378
            this.LEorBE_();
379
        }
380
    }
381
382
    /**
383
     * Force a file as RIFX. Do not work with RF64 files.
384
     */
385
    toRIFX() {
386
        if (this.container == "RF64") {
387
            this.fromScratch(
388
                this.fmt.numChannels,
389
                this.fmt.sampleRate,
390
                this.bitDepth,
391
                this.data.samples,
392
                {"container": "RIFX"});
393
        } else {
394
            this.container = "RIFX";
395
            this.LEorBE_();
396
        }
397
    }
398
399
    /**
400
     * Change the bit depth of the samples.
401
     * @param {!string} bitDepth The new bit depth of the samples.
402
     *      One of "8" ... "32" (integers), "32f" or "64" (floats)
403
     * @param {!boolean} changeResolution A boolean indicating if the
404
     *      resolution of samples should be actually changed or not.
405
     * @throws {Error} If the bit depth is not valid.
406
     */
407
    toBitDepth(bitDepth, changeResolution=true) {
408
        let toBitDepth = bitDepth;
409
        let thisBitDepth = this.bitDepth;
410
        if (!changeResolution) {
411
            toBitDepth = this.realBitDepth_(bitDepth);
412
            thisBitDepth = this.realBitDepth_(this.bitDepth);
413
        }
414
        this.assureInterleaved_();
415
        bitDepth_.toBitDepth(this.data.samples, thisBitDepth, toBitDepth);
416
        this.fromScratch(
417
            this.fmt.numChannels,
418
            this.fmt.sampleRate,
419
            bitDepth,
420
            this.data.samples,
421
            {"container": this.correctContainer_()});
422
    }
423
424
    /**
425
     * Interleave multi-channel samples.
426
     */
427
    interleave() {
428
        if (!this.isInterleaved) {
429
            let finalSamples = [];
430
            let numChannels = this.data.samples[0].length;
431
            for (let i = 0; i < numChannels; i++) {
432
                for (let j = 0; j < this.data.samples.length; j++) {
433
                    finalSamples.push(this.data.samples[j][i]);
434
                }
435
            }
436
            this.data.samples = finalSamples;
437
            this.isInterleaved = true;    
438
        }
439
    }
440
441
    /**
442
     * De-interleave samples into multiple channels.
443
     */
444
    deInterleave() {
445
        if (this.isInterleaved) {
446
            let finalSamples = [];
447
            let i;
448
            for (i = 0; i < this.fmt.numChannels; i++) {
449
                finalSamples[i] = [];
450
            }
451
            i = 0;
0 ignored issues
show
Complexity Coding Style introduced by
You seem to be assigning a new value to the loop variable i here. Please check if this was indeed your intention. Even if it was, consider using another kind of loop instead.
Loading history...
452
            let j;
453
            while (i < this.data.samples.length) {
454
                for (j = 0; j < this.fmt.numChannels; j++) {
455
                    finalSamples[j].push(this.data.samples[i+j]);
456
                }
457
                i += j;
458
            }
459
            this.data.samples = finalSamples;
460
            this.isInterleaved = false;
461
        }
462
    }
463
464
    /**
465
     * Encode a 16-bit wave file as 4-bit IMA ADPCM.
466
     * @throws {Error} If sample rate is not 8000.
467
     * @throws {Error} If number of channels is not 1.
468
     */
469
    toIMAADPCM() {
470
        if (this.fmt.sampleRate != 8000) {
471
            throw new Error(
472
                "Only 8000 Hz files can be compressed as IMA-ADPCM.");
473
        } else if(this.fmt.numChannels != 1) {
0 ignored issues
show
Comparing this.fmt.numChannels to 1 using the != operator is not safe. Consider using !== instead.
Loading history...
474
            throw new Error(
475
                "Only mono files can be compressed as IMA-ADPCM.");
476
        } else {
477
            this.assure16Bit_();
478
            this.fromScratch(
479
                this.fmt.numChannels,
480
                this.fmt.sampleRate,
481
                "4",
482
                imaadpcm_.encode(this.data.samples),
483
                {"container": this.correctContainer_()});
484
        }
485
    }
486
487
    /**
488
     * Decode a 4-bit IMA ADPCM wave file as a 16-bit wave file.
489
     * @param {!string} bitDepth The new bit depth of the samples.
490
     *      One of "8" ... "32" (integers), "32f" or "64" (floats).
491
     *      Optional. Default is 16.
492
     */
493
    fromIMAADPCM(bitDepth="16") {
494
        this.fromScratch(
495
            this.fmt.numChannels,
496
            this.fmt.sampleRate,
497
            "16",
498
            imaadpcm_.decode(this.data.samples, this.fmt.blockAlign),
499
            {"container": this.correctContainer_()});
500
        this.toBitDepth(bitDepth);
501
    }
502
503
    /**
504
     * Encode 16-bit wave file as 8-bit A-Law.
505
     */
506
    toALaw() {
507
        this.assure16Bit_();
508
        this.assureInterleaved_();
509
        this.fromScratch(
510
            this.fmt.numChannels,
511
            this.fmt.sampleRate,
512
            "8a",
513
            alawmulaw_.alaw.encode(this.data.samples),
514
            {"container": this.correctContainer_()});
515
    }
516
517
    /**
518
     * Decode a 8-bit A-Law wave file into a 16-bit wave file.
519
     * @param {!string} bitDepth The new bit depth of the samples.
520
     *      One of "8" ... "32" (integers), "32f" or "64" (floats).
521
     *      Optional. Default is 16.
522
     */
523
    fromALaw(bitDepth="16") {
524
        this.fromScratch(
525
            this.fmt.numChannels,
526
            this.fmt.sampleRate,
527
            "16",
528
            alawmulaw_.alaw.decode(this.data.samples),
529
            {"container": this.correctContainer_()});
530
        this.toBitDepth(bitDepth);
531
    }
532
533
    /**
534
     * Encode 16-bit wave file as 8-bit mu-Law.
535
     */
536
    toMuLaw() {
537
        this.assure16Bit_();
538
        this.assureInterleaved_();
539
        this.fromScratch(
540
            this.fmt.numChannels,
541
            this.fmt.sampleRate,
542
            "8m",
543
            alawmulaw_.mulaw.encode(this.data.samples),
544
            {"container": this.correctContainer_()});
545
    }
546
547
    /**
548
     * Decode a 8-bit mu-Law wave file into a 16-bit wave file.
549
     * @param {!string} bitDepth The new bit depth of the samples.
550
     *      One of "8" ... "32" (integers), "32f" or "64" (floats).
551
     *      Optional. Default is 16.
552
     */
553
    fromMuLaw(bitDepth="16") {
554
        this.fromScratch(
555
            this.fmt.numChannels,
556
            this.fmt.sampleRate,
557
            "16",
558
            alawmulaw_.mulaw.decode(this.data.samples),
559
            {"container": this.correctContainer_()});
560
        this.toBitDepth(bitDepth);
561
    }
562
563
    /**
564
     * Return the closest greater number of bits for a number of bits that
565
     * do not fill a full sequence of bytes.
566
     * @param {!string} bitDepth The bit depth.
567
     * @return {!string}
568
     */
569
    realBitDepth_(bitDepth) {
570
        if (bitDepth != "32f") {
571
            bitDepth = (((parseInt(bitDepth, 10) - 1) | 7) + 1).toString();
572
        }
573
        return bitDepth;
574
    }
575
576
    /**
577
     * Validate the input for wav writing.
578
     * @throws {Error} If any property of the object appears invalid.
579
     * @private
580
     */
581
    checkWriteInput_() {
582
        this.validateBitDepth_();
583
        this.validateNumChannels_();
584
        this.validateSampleRate_();
585
    }
586
587
    /**
588
     * Validate the bit depth.
589
     * @return {!boolean} True is the bit depth is valid.
590
     * @throws {Error} If bit depth is invalid.
591
     * @private
592
     */
593
    validateBitDepth_() {
594
        if (!this.audioFormats_[this.bitDepth]) {
595
            if (parseInt(this.bitDepth, 10) > 8 &&
596
                    parseInt(this.bitDepth, 10) < 54) {
597
                return true;
598
            }
599
            throw new Error("Invalid bit depth.");
600
        }
601
        return true;
602
    }
603
604
    /**
605
     * Validate the number of channels.
606
     * @return {!boolean} True is the number of channels is valid.
607
     * @throws {Error} If the number of channels is invalid.
608
     * @private
609
     */
610
    validateNumChannels_() {
611
        let blockAlign = this.fmt.numChannels * this.fmt.bitsPerSample / 8;
612
        if (this.fmt.numChannels < 1 || blockAlign > 65535) {
613
            throw new Error("Invalid number of channels.");
614
        }
615
        return true;
616
    }
617
618
    /**
619
     * Validate the sample rate value.
620
     * @return {!boolean} True is the sample rate is valid.
621
     * @throws {Error} If the sample rate is invalid.
622
     * @private
623
     */
624
    validateSampleRate_() {
625
        let byteRate = this.fmt.numChannels *
626
            (this.fmt.bitsPerSample / 8) * this.fmt.sampleRate;
627
        if (this.fmt.sampleRate < 1 || byteRate > 4294967295) {
628
            throw new Error("Invalid sample rate.");
629
        }
630
        return true;
631
    }
632
633
    /**
634
     * Reset attributes that should emptied when a file is
635
     * created with the fromScratch() method.
636
     * @private
637
     */
638
    clearHeader_() {
639
        this.fmt.cbSize = 0;
640
        this.fmt.validBitsPerSample = 0;
641
        this.fact.chunkId = "";
642
        this.ds64.chunkId = "";
643
    }
644
645
    /**
646
     * Make the file 16-bit if it is not.
647
     * @private
648
     */
649
    assure16Bit_() {
650
        this.assureUncompressed_();
651
        if (this.bitDepth != "16") {
652
            this.toBitDepth("16");
653
        }
654
    }
655
656
    /**
657
     * Uncompress the samples in case of a compressed file.
658
     * @private
659
     */
660
    assureUncompressed_() {
661
        if (this.bitDepth == "8a") {
662
            this.fromALaw();
663
        } else if(this.bitDepth == "8m") {
664
            this.fromMuLaw();
665
        } else if (this.bitDepth == "4") {
666
            this.fromIMAADPCM();
667
        }
668
    }
669
670
    /**
671
     * Interleave the samples in case they are de-Interleaved.
672
     * @private
673
     */
674
    assureInterleaved_() {
675
        if (!this.isInterleaved) {
676
            this.interleave();
677
        }
678
    }
679
680
    /**
681
     * Set up to work wih big-endian or little-endian files.
682
     * The types used are changed to LE or BE. If the
683
     * the file is big-endian (RIFX), true is returned.
684
     * @return {!boolean} True if the file is RIFX.
685
     * @private
686
     */
687
    LEorBE_() {
688
        let bigEndian = this.container === "RIFX";
689
        uInt8_["be"] = bigEndian;
690
        uInt16_["be"] = bigEndian;
691
        uInt32_["be"] = bigEndian;
692
        return bigEndian;
693
    }
694
695
    /**
696
     * Find a chunk by its fourCC_ in a array of RIFF chunks.
697
     * @param {!Array<!Object>} chunks The wav file chunks.
698
     * @param {!string} chunkId The chunk fourCC_.
699
     * @return {Object|null}
700
     * @private
701
     */
702
    findChunk_(chunks, chunkId) {
703
        for (let i = 0; i<chunks.length; i++) {
704
            if (chunks[i]["chunkId"] == chunkId) {
705
                return chunks[i];
706
            }
707
        }
708
        return null;
709
    }
710
711
    /**
712
     * Read the RIFF chunk a wave file.
713
     * @param {!Uint8Array|!Array<number>} bytes A wav buffer.
714
     * @throws {Error} If no "RIFF" chunk is found.
715
     * @private
716
     */
717
    readRIFFChunk_(bytes) {
718
        this.container = byteData_.unpack(
719
            bytes.slice(0, 4), fourCC_);
720
        if (["RIFF", "RIFX", "RF64"].indexOf(this.container) === -1) {
721
            throw Error("Not a supported format.");
722
        }
723
        this.LEorBE_();
724
        this.chunkSize = byteData_.unpack(bytes.slice(4, 8), uInt32_);
725
        this.format = byteData_.unpack(
726
            bytes.slice(8, 12), fourCC_);
727
        if (this.format != "WAVE") {
728
            throw Error("Could not find the 'WAVE' format identifier");
729
        }
730
    }
731
732
    /**
733
     * Read the "fmt " chunk of a wave file.
734
     * @param {!Array<!Object>} chunks The wav file chunks.
735
     * @throws {Error} If no "fmt " chunk is found.
736
     * @private
737
     */
738
    readFmtChunk_(chunks) {
739
        let chunk = this.findChunk_(chunks, "fmt ");
740
        if (chunk) {
741
            let chunkData = chunk["chunkData"];
742
            this.fmt.chunkId = "fmt ";
743
            this.fmt.chunkSize = chunk["chunkSize"];
744
            this.fmt.audioFormat = byteData_.unpack(
745
                chunkData.slice(0, 2), uInt16_);
746
            this.fmt.numChannels = byteData_.unpack(
747
                chunkData.slice(2, 4), uInt16_);
748
            this.fmt.sampleRate = byteData_.unpack(
749
                chunkData.slice(4, 8), uInt32_);
750
            this.fmt.byteRate = byteData_.unpack(
751
                chunkData.slice(8, 12), uInt32_);
752
            this.fmt.blockAlign = byteData_.unpack(
753
                chunkData.slice(12, 14), uInt16_);
754
            this.fmt.bitsPerSample = byteData_.unpack(
755
                    chunkData.slice(14, 16), uInt16_);
756
            this.readFmtExtension_(chunk);
757
        } else {
758
            throw Error("Could not find the 'fmt ' chunk");
759
        }
760
    }
761
762
    /**
763
     * Read the "fmt " chunk extension.
764
     * @param {!Object<string, *>} chunk The "fmt " chunk.
765
     * @private
766
     */
767
    readFmtExtension_(chunk) {
768
        let chunkData = chunk["chunkData"];
769
        if (this.fmt.chunkSize > 16) {
770
            this.fmt.cbSize = byteData_.unpack(
771
                chunkData.slice(16, 18), uInt16_);
772
            if (this.fmt.chunkSize > 18) {
773
                this.fmt.validBitsPerSample = byteData_.unpack(
774
                    chunkData.slice(18, 20), uInt16_);
775
                if (this.fmt.chunkSize > 20) {
776
                    this.fmt.dwChannelMask = byteData_.unpack(
777
                        chunkData.slice(20, 24), uInt32_);
778
                    this.fmt.subformat = [
779
                        byteData_.unpack(
780
                            chunkData.slice(24, 28), uInt32_),
781
                        byteData_.unpack(
782
                            chunkData.slice(28, 32), uInt32_),
783
                        byteData_.unpack(
784
                            chunkData.slice(32, 36), uInt32_),
785
                        byteData_.unpack(
786
                            chunkData.slice(36, 40), uInt32_)];
787
                }
788
            }
789
        }
790
    }
791
792
    /**
793
     * Read the "fact" chunk of a wav file.
794
     * @param {!Array<Object>} chunks The wav file chunks.
795
     * @throws {Error} If no "fact" chunk is found.
796
     * @private
797
     */
798
    readFactChunk_(chunks) {
799
        let chunk = this.findChunk_(chunks, "fact");
800
        if (chunk) {
801
            this.fact.chunkId = "fact";
802
            this.fact.chunkSize = chunk["chunkSize"];
803
            this.fact.dwSampleLength = byteData_.unpack(
804
                chunk["chunkData"].slice(0, 4), uInt32_);
805
        } else if (this.enforceFact_) {
806
            throw Error("Could not find the 'fact' chunk");
807
        }
808
    }
809
810
    /**
811
     * Read the "cue " chunk of a wave file.
812
     * @param {!Array<Object>} chunks The RIFF file chunks.
813
     * @private
814
     */
815
    readCueChunk_(chunks) {
816
        let chunk = this.findChunk_(chunks, "cue ");
817
        if (chunk) {
818
            let chunkData = chunk["chunkData"];
819
            this.cue.chunkId = "cue ";
820
            this.cue.chunkSize = chunk["chunkSize"];
821
            this.cue.dwCuePoints = byteData_.unpack(
822
                chunkData.slice(0, 4), uInt32_);
823
            for (let i=0; i<this.cue.dwCuePoints; i++) {
824
                let offset = 4 + (i * 24);
825
                this.cue.points.push({
826
                    "dwName": byteData_.unpack(
827
                        chunkData.slice(offset, offset + 4), uInt32_),
828
                    "dwPosition": byteData_.unpack(
829
                        chunkData.slice(offset + 4, offset + 8), uInt32_),
830
                    "fccChunk": byteData_.unpack(
831
                        chunkData.slice(offset + 8, offset + 12), fourCC_),
832
                    "dwChunkStart": byteData_.unpack(
833
                        chunkData.slice(offset + 12, offset + 16), uInt32_),
834
                    "dwBlockStart": byteData_.unpack(
835
                        chunkData.slice(offset + 16, offset + 20), uInt32_),
836
                    "dwSampleOffset": byteData_.unpack(
837
                        chunkData.slice(offset + 20, offset + 24), uInt32_),
838
                });
839
            }
840
        }
841
    }
842
843
    /**
844
     * Read the "data" chunk of a wave file.
845
     * @param {!Array<Object>} chunks The RIFF file chunks.
846
     * @param {!Object<string, *>} options The type definition.
847
     * @throws {Error} If no "data" chunk is found.
848
     * @private
849
     */
850
    readDataChunk_(chunks, options) {
851
        let chunk = this.findChunk_(chunks, "data");
852
        if (chunk) {
853
            this.data.chunkId = "data";
854
            this.data.chunkSize = chunk["chunkSize"];
855
            this.samplesFromBytes_(chunk["chunkData"], options);
856
        } else {
857
            throw Error("Could not find the 'data' chunk");
858
        }
859
    }
860
861
    /**
862
     * Read the "bext" chunk of a wav file.
863
     * @param {!Array<Object>} chunks The wav file chunks.
864
     * @private
865
     */
866
    readBextChunk_(chunks) {
867
        let chunk = this.findChunk_(chunks, "bext");
868
        if (chunk) {
869
            this.head_ = 0;
870
            let chunkData = chunk["chunkData"];
871
            this.bext = {
872
                "chunkId": "bext",
873
                "chunkSize": chunkData.length,
874
                "description": this.readString_(chunkData, 256),
875
                "originator": this.readString_(chunkData, 32),
876
                "originatorReference": this.readString_(chunkData, 32),
877
                "originationDate": this.readString_(chunkData, 10),
878
                "originationTime": this.readString_(chunkData, 8),
879
                "timeReference": [
880
                        this.readFromChunk_(chunkData, uInt32_),
881
                        this.readFromChunk_(chunkData, uInt32_)], 
882
                "version": this.readFromChunk_(
883
                    chunkData, uInt16_),
884
                "UMID": this.readString_(chunkData, 64),
885
                "loudnessValue": this.readFromChunk_(
886
                    chunkData, uInt16_),
887
                "loudnessRange": this.readFromChunk_(
888
                    chunkData, uInt16_),
889
                "maxTruePeakLevel": this.readFromChunk_(
890
                    chunkData, uInt16_),
891
                "maxMomentaryLoudness": this.readFromChunk_(
892
                    chunkData, uInt16_),
893
                "maxShortTermLoudness": this.readFromChunk_(
894
                    chunkData, uInt16_),
895
                "reserved": this.readString_(chunkData, 180),
896
                "codingHistory": this.readString_(
897
                    chunkData, chunkData.length - 602),
898
            };
899
        }
900
    }
901
902
    /**
903
     * Read the "fmt " chunk of a wave file.
904
     * @param {!Array<Object>} chunks The wav file chunks.
905
     * @throws {Error} If no "fmt " chunk is found.
906
     * @private
907
     */
908
    readDs64Chunk_(chunks) {
909
        let chunk = this.findChunk_(chunks, "ds64");
910
        if (chunk) {
911
            let chunkData = chunk["chunkData"];
912
            this.ds64.chunkId = chunk["chunkId"];
913
            this.ds64.chunkSize = chunk["chunkSize"];
914
            this.ds64.riffSizeHigh = byteData_.unpack(
915
                chunkData.slice(0, 4), uInt32_);
916
            this.ds64.riffSizeLow = byteData_.unpack(
917
                chunkData.slice(4, 8), uInt32_);
918
            this.ds64.dataSizeHigh = byteData_.unpack(
919
                chunkData.slice(8, 12), uInt32_);
920
            this.ds64.dataSizeLow = byteData_.unpack(
921
                chunkData.slice(12, 16), uInt32_);
922
            this.ds64.originationTime = byteData_.unpack(
923
                chunkData.slice(16, 20), uInt32_);
924
            this.ds64.sampleCountHigh = byteData_.unpack(
925
                chunkData.slice(20, 24), uInt32_);
926
            this.ds64.sampleCountLow = byteData_.unpack(
927
                chunkData.slice(24, 28), uInt32_);
928
            //if (this.ds64.chunkSize > 28) {
929
            //    this.ds64.tableLength = byteData_.unpack(
930
            //        chunkData.slice(28, 32), uInt32_);
931
            //    this.ds64.table = chunkData.slice(32, 32 + this.ds64.tableLength);    
932
            //}
933
        } else {
934
            if (this.container == "RF64") {
935
                throw Error("Could not find the 'ds64' chunk");    
936
            }
937
        }
938
    }
939
940
    /**
941
     * Read bytes as a string from a RIFF chunk.
942
     * @param {!Array<number>|!Uint8Array} bytes The bytes.
943
     * @param {!number} maxSize the max size of the string.
944
     * @return {!string} The string.
945
     * @private
946
     */
947
    readString_(bytes, maxSize) {
948
        let str = "";
949
        for (let i=0; i<maxSize; i++) {
950
            str += byteData_.unpack([bytes[this.head_]], chr_);
951
            this.head_++;
952
        }
953
        return str;
954
    }
955
956
    /**
957
     * Read a number from a chunk.
958
     * @param {!Array<number>|!Uint8Array} bytes The chunk bytes.
959
     * @param {!Object} bdType The type definition.
960
     * @return {!number} The number.
961
     * @private
962
     */
963
    readFromChunk_(bytes, bdType) {
964
        let size = bdType["bits"] / 8;
965
        let value = byteData_.unpack(
966
            bytes.slice(this.head_, this.head_ + size), bdType);
967
        this.head_ += size;
968
        return value;
969
    }
970
971
    /**
972
     * Write a variable size string as bytes. If the string is smaller
973
     * than the max size the output array is filled with 0s.
974
     * @param {!string} str The string to be written as bytes.
975
     * @param {!number} maxSize the max size of the string.
976
     * @return {!Array<number>} The bytes.
977
     * @private
978
     */
979
    writeString_(str, maxSize) {
980
        let bytes = byteData_.packArray(str, chr_);
981
        for (let i=bytes.length; i<maxSize; i++) {
982
            bytes.push(0);
983
        }
984
        return bytes;
985
    }
986
987
    /**
988
     * Turn the samples to bytes.
989
     * @param {!Object<string, *>} options Type options.
990
     * @return {!Array<number>} The bytes.
991
     * @private
992
     */
993
    samplesToBytes_(options) {
994
        options["bits"] = this.fmt.bitsPerSample == 4 ?
995
            8 : this.fmt.bitsPerSample;
996
        options["signed"] = options["bits"] == 8 ? false : true;
997
        options["float"] = (this.fmt.audioFormat == 3  ||
998
                this.fmt.bitsPerSample == 64) ? true : false;
999
        let bytes = byteData_.packArray(
1000
            this.data.samples, options);
1001
        if (bytes.length % 2) {
1002
            bytes.push(0);
1003
        }
1004
        return bytes;
1005
    }
1006
1007
    /**
1008
     * Turn bytes to samples and load them in the data.samples property.
1009
     * @param {!Array<number>|!Uint8Array} bytes The bytes.
1010
     * @param {!Object<string, *>} options The type definition.
1011
     * @private
1012
     */
1013
    samplesFromBytes_(bytes, options) {
1014
        options["bits"] = this.fmt.bitsPerSample == 4 ?
1015
            8 : this.fmt.bitsPerSample;
1016
        options["signed"] = options["bits"] == 8 ? false : true;
1017
        options["float"] = (this.fmt.audioFormat == 3 || 
1018
            this.fmt.bitsPerSample == 64) ? true : false;
1019
        this.data.samples = byteData_.unpackArray(
1020
            bytes, options);
1021
    }
1022
1023
    /**
1024
     * Return the bytes of the "bext" chunk.
1025
     * @return {!Array<number>} The "bext" chunk bytes.
1026
     * @private
1027
     */
1028
    getBextBytes_() {
1029
        if (this.bext.chunkId) {
1030
            return [].concat(
1031
                byteData_.pack(this.bext.chunkId, fourCC_),
1032
                byteData_.pack(this.bext.chunkSize, uInt32_),
1033
                this.writeString_(this.bext.description, 256),
1034
                this.writeString_(this.bext.originator, 32),
1035
                this.writeString_(this.bext.originatorReference, 32),
1036
                this.writeString_(this.bext.originationDate, 10),
1037
                this.writeString_(this.bext.originationTime, 8),
1038
                byteData_.pack(this.bext.timeReference[0], uInt32_),
1039
                byteData_.pack(this.bext.timeReference[1], uInt32_),
1040
                byteData_.pack(this.bext.version, uInt16_),
1041
                this.writeString_(this.bext.UMID, 64),
1042
                byteData_.pack(this.bext.loudnessValue, uInt16_),
1043
                byteData_.pack(this.bext.loudnessRange, uInt16_),
1044
                byteData_.pack(this.bext.maxTruePeakLevel, uInt16_),
1045
                byteData_.pack(this.bext.maxMomentaryLoudness, uInt16_),
1046
                byteData_.pack(this.bext.maxShortTermLoudness, uInt16_),
1047
                this.writeString_(this.bext.reserved, 180),
1048
                this.writeString_(
1049
                    this.bext.codingHistory,
1050
                    this.bext.chunkSize - 602));
1051
        }
1052
        return [];
1053
    }
1054
1055
    /**
1056
     * Return the bytes of the "ds64" chunk.
1057
     * @return {!Array<number>} The "ds64" chunk bytes.
1058
     * @private
1059
     */
1060
    getDs64Bytes_() {
1061
        let ds64Bytes = [];
1062
        if (this.ds64["chunkId"]) {
1063
            ds64Bytes = ds64Bytes.concat(
1064
                byteData_.pack(this.ds64.chunkId, fourCC_),
1065
                byteData_.pack(this.ds64.chunkSize, uInt32_),
1066
                byteData_.pack(this.ds64.riffSizeHigh, uInt32_),
1067
                byteData_.pack(this.ds64.riffSizeLow, uInt32_),
1068
                byteData_.pack(this.ds64.dataSizeHigh, uInt32_),
1069
                byteData_.pack(this.ds64.dataSizeLow, uInt32_),
1070
                byteData_.pack(this.ds64.originationTime, uInt32_),
1071
                byteData_.pack(this.ds64.sampleCountHigh, uInt32_),
1072
                byteData_.pack(this.ds64.sampleCountLow, uInt32_));          
1073
        }
1074
        //if (this.ds64.tableLength) {
1075
        //    ds64Bytes = ds64Bytes.concat(
1076
        //        byteData_.pack(this.ds64.tableLength, uInt32_),
1077
        //        this.ds64.table);
1078
        //}
1079
        return ds64Bytes;
1080
    }
1081
1082
    /**
1083
     * Return the bytes of the "cue " chunk.
1084
     * @return {!Array<number>} The "cue " chunk bytes.
1085
     * @private
1086
     */
1087
    getCueBytes_() {
1088
        if (this.cue.chunkId) {
1089
            return [].concat(
1090
                byteData_.pack(this.cue.chunkId, fourCC_),
1091
                byteData_.pack(this.cue.chunkSize, uInt32_),
1092
                byteData_.pack(this.cue.dwCuePoints, uInt32_),
1093
                this.getCuePointsBytes_());
1094
        }
1095
        return [];
1096
    }
1097
1098
    /**
1099
     * Return the bytes of the "cue " points.
1100
     * @return {!Array<number>} The "cue " points as an array of bytes.
1101
     * @private
1102
     */
1103
    getCuePointsBytes_() {
1104
        let points = [];
1105
        for (let i=0; i<this.cue.dwCuePoints; i++) {
1106
            points = points.concat(
1107
                    byteData_.pack(this.cue.points[i]["dwName"], uInt32_),
1108
                    byteData_.pack(this.cue.points[i]["dwPosition"], uInt32_),
1109
                    byteData_.pack(this.cue.points[i]["fccChunk"], fourCC_),
1110
                    byteData_.pack(this.cue.points[i]["dwChunkStart"], uInt32_),
1111
                    byteData_.pack(this.cue.points[i]["dwBlockStart"], uInt32_),
1112
                    byteData_.pack(this.cue.points[i]["dwSampleOffset"], uInt32_)
1113
                );
1114
        }
1115
        return points;
1116
    }
1117
1118
    /**
1119
     * Return the bytes of the "fact" chunk.
1120
     * @return {!Array<number>} The "fact" chunk bytes.
1121
     * @private
1122
     */
1123
    getFactBytes_() {
1124
        if (this.fact.chunkId) {
1125
            return [].concat(
1126
                byteData_.pack(this.fact.chunkId, fourCC_),
1127
                byteData_.pack(this.fact.chunkSize, uInt32_),
1128
                byteData_.pack(this.fact.dwSampleLength, uInt32_));
1129
        }
1130
        return [];
1131
    }
1132
1133
    /**
1134
     * Return the bytes of the "fmt " chunk.
1135
     * @return {!Array<number>} The "fmt" chunk bytes.
1136
     * @private
1137
     * @throws {Error} if no "fmt " chunk is present.
1138
     */
1139
    getFmtBytes_() {
1140
        if (this.fmt.chunkId) {
1141
            return [].concat(
1142
                byteData_.pack(this.fmt.chunkId, fourCC_),
1143
                byteData_.pack(this.fmt.chunkSize, uInt32_),
1144
                byteData_.pack(this.fmt.audioFormat, uInt16_),
1145
                byteData_.pack(this.fmt.numChannels, uInt16_),
1146
                byteData_.pack(this.fmt.sampleRate, uInt32_),
1147
                byteData_.pack(this.fmt.byteRate, uInt32_),
1148
                byteData_.pack(this.fmt.blockAlign, uInt16_),
1149
                byteData_.pack(this.fmt.bitsPerSample, uInt16_),
1150
                this.getFmtExtensionBytes_()
1151
            );
1152
        } else {
0 ignored issues
show
Comprehensibility introduced by
else is not necessary here since all if branches return, consider removing it to reduce nesting and make code more readable.
Loading history...
1153
            throw Error("Could not find the 'fmt ' chunk");
1154
        }
1155
    }
1156
1157
    /**
1158
     * Return the bytes of the fmt extension fields.
1159
     * @return {!Array<number>} The fmt extension bytes.
1160
     * @private
1161
     */
1162
    getFmtExtensionBytes_() {
1163
        let extension = [];
1164
        if (this.fmt.chunkSize > 16) {
1165
            extension = extension.concat(
1166
                byteData_.pack(this.fmt.cbSize, uInt16_));
1167
        }
1168
        if (this.fmt.chunkSize > 18) {
1169
            extension = extension.concat(
1170
                byteData_.pack(this.fmt.validBitsPerSample, uInt16_));
1171
        }
1172
        if (this.fmt.chunkSize > 20) {
1173
            extension = extension.concat(
1174
                byteData_.pack(this.fmt.dwChannelMask, uInt32_));
1175
        }
1176
        if (this.fmt.chunkSize > 24) {
1177
            extension = extension.concat(
1178
                byteData_.pack(this.fmt.subformat[0], uInt32_),
1179
                byteData_.pack(this.fmt.subformat[1], uInt32_),
1180
                byteData_.pack(this.fmt.subformat[2], uInt32_),
1181
                byteData_.pack(this.fmt.subformat[3], uInt32_));
1182
        }
1183
        return extension;
1184
    }
1185
1186
    /**
1187
     * Return "RIFF" if the container is "RF64", the current
1188
     * container name otherwise.
1189
     * Used to enforce "RIFF" as the container in operations where
1190
     * RF64 is not allowed.
1191
     * @private
1192
     */
1193
    correctContainer_() {
1194
        return this.container == "RF64" ? "RIFF" : this.container;
1195
    }
1196
1197
    /**
1198
     * Set the string code of the bit depth based on the "fmt " chunk.
1199
     * @private
1200
     */
1201
     bitDepthFromFmt_() {
1202
        if (this.fmt.audioFormat == 3 && this.fmt.bitsPerSample == 32) {
1203
            this.bitDepth = "32f";
1204
        } else if (this.fmt.audioFormat == 6) {
1205
            this.bitDepth = "8a";
1206
        } else if (this.fmt.audioFormat == 7) {
1207
            this.bitDepth = "8m";
1208
        } else {
1209
            this.bitDepth = this.fmt.bitsPerSample.toString();
1210
        }
1211
     }
1212
    
1213
    /**
1214
     * Return a .wav file byte buffer with the data from the WaveFile object.
1215
     * The return value of this method can be written straight to disk.
1216
     * @return {!Uint8Array} The wav file bytes.
1217
     * @private
1218
     */
1219
    createWaveFile_() {
1220
        let options = {"be": this.LEorBE_()};
1221
        return new Uint8Array([].concat(
1222
            byteData_.pack(this.container, fourCC_),
1223
            byteData_.pack(this.chunkSize, uInt32_),
1224
            byteData_.pack(this.format, fourCC_),
1225
            this.getDs64Bytes_(),
1226
            this.getBextBytes_(),
1227
            this.getFmtBytes_(),
1228
            this.getFactBytes_(),
1229
            byteData_.pack(this.data.chunkId, fourCC_),
1230
            byteData_.pack(this.data.chunkSize, uInt32_),
1231
            this.samplesToBytes_(options),
1232
            this.getCueBytes_()));
1233
    }
1234
}
1235
1236
module.exports = WaveFile;
1237