Passed
Push — master ( efa12d...62ab07 )
by Rafael S.
02:05
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
    }
331
332
    /**
333
     * Init a WaveFile object from a byte buffer.
334
     * @param {!Uint8Array|!Array<number>} bytes The buffer.
335
     * @throws {Error} If container is not RIFF or RIFX.
336
     * @throws {Error} If no "fmt " chunk is found.
337
     * @throws {Error} If no "fact" chunk is found and "fact" is needed.
338
     * @throws {Error} If no "data" chunk is found.
339
     */
340
    fromBuffer(bytes) {
341
        this.readRIFFChunk_(bytes);
342
        let bigEndian = this.container == "RIFX";
343
        let chunk = riffChunks_.read(bytes, bigEndian);
344
        this.readDs64Chunk_(chunk["subChunks"]);
345
        this.readFmtChunk_(chunk["subChunks"]);
346
        this.readFactChunk_(chunk["subChunks"]);
347
        this.readBextChunk_(chunk["subChunks"]);
348
        this.readCueChunk_(chunk["subChunks"]);
349
        this.readDataChunk_(chunk["subChunks"], {"be": bigEndian});
350
        this.bitDepthFromFmt_();
351
    }
352
353
    /**
354
     * Return a byte buffer representig the WaveFile object as a wav file.
355
     * The return value of this method can be written straight to disk.
356
     * @return {!Uint8Array} A .wav file.
357
     * @throws {Error} If any property of the object appears invalid.
358
     */
359
    toBuffer() {
360
        this.checkWriteInput_();
361
        this.assureInterleaved_();
362
        return this.createWaveFile_();
363
    }
364
365
    /**
366
     * Force a file as RIFF. Do not work with RF64 files.
367
     */
368
    toRIFF() {
369
        if (this.container == "RF64") {
370
            this.fromScratch(
371
                this.fmt.numChannels,
372
                this.fmt.sampleRate,
373
                this.bitDepth,
374
                this.data.samples);
375
        } else {
376
            this.container = "RIFF";
377
            this.LEorBE_();
378
        }
379
    }
380
381
    /**
382
     * Force a file as RIFX. Do not work with RF64 files.
383
     */
384
    toRIFX() {
385
        if (this.container == "RF64") {
386
            this.fromScratch(
387
                this.fmt.numChannels,
388
                this.fmt.sampleRate,
389
                this.bitDepth,
390
                this.data.samples,
391
                {"container": "RIFX"});
392
        } else {
393
            this.container = "RIFX";
394
            this.LEorBE_();
395
        }
396
    }
397
398
    /**
399
     * Change the bit depth of the samples.
400
     * @param {!string} bitDepth The new bit depth of the samples.
401
     *      One of "8" ... "32" (integers), "32f" or "64" (floats)
402
     * @param {!boolean} changeResolution A boolean indicating if the
403
     *      resolution of samples should be actually changed or not.
404
     * @throws {Error} If the bit depth is not valid.
405
     */
406
    toBitDepth(bitDepth, changeResolution=true) {
407
        let toBitDepth = bitDepth;
408
        let thisBitDepth = this.bitDepth;
409
        if (!changeResolution) {
410
            toBitDepth = this.realBitDepth_(bitDepth);
411
            thisBitDepth = this.realBitDepth_(this.bitDepth);
412
        }
413
        this.assureInterleaved_();
414
        bitDepth_.toBitDepth(this.data.samples, thisBitDepth, toBitDepth);
415
        this.fromScratch(
416
            this.fmt.numChannels,
417
            this.fmt.sampleRate,
418
            bitDepth,
419
            this.data.samples,
420
            {"container": this.correctContainer_()});
421
    }
422
423
    /**
424
     * Interleave multi-channel samples.
425
     */
426
    interleave() {
427
        if (!this.isInterleaved) {
428
            let finalSamples = [];
429
            let numChannels = this.data.samples[0].length;
430
            for (let i = 0; i < numChannels; i++) {
431
                for (let j = 0; j < this.data.samples.length; j++) {
432
                    finalSamples.push(this.data.samples[j][i]);
433
                }
434
            }
435
            this.data.samples = finalSamples;
436
            this.isInterleaved = true;    
437
        }
438
    }
439
440
    /**
441
     * De-interleave samples into multiple channels.
442
     */
443
    deInterleave() {
444
        if (this.isInterleaved) {
445
            let finalSamples = [];
446
            let i;
447
            for (i = 0; i < this.fmt.numChannels; i++) {
448
                finalSamples[i] = [];
449
            }
450
            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...
451
            let j;
452
            while (i < this.data.samples.length) {
453
                for (j = 0; j < this.fmt.numChannels; j++) {
454
                    finalSamples[j].push(this.data.samples[i+j]);
455
                }
456
                i += j;
457
            }
458
            this.data.samples = finalSamples;
459
            this.isInterleaved = false;
460
        }
461
    }
462
463
    /**
464
     * Encode a 16-bit wave file as 4-bit IMA ADPCM.
465
     * @throws {Error} If sample rate is not 8000.
466
     * @throws {Error} If number of channels is not 1.
467
     */
468
    toIMAADPCM() {
469
        if (this.fmt.sampleRate != 8000) {
470
            throw new Error(
471
                "Only 8000 Hz files can be compressed as IMA-ADPCM.");
472
        } 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...
473
            throw new Error(
474
                "Only mono files can be compressed as IMA-ADPCM.");
475
        } else {
476
            this.assure16Bit_();
477
            this.fromScratch(
478
                this.fmt.numChannels,
479
                this.fmt.sampleRate,
480
                "4",
481
                imaadpcm_.encode(this.data.samples),
482
                {"container": this.correctContainer_()});
483
        }
484
    }
485
486
    /**
487
     * Decode a 4-bit IMA ADPCM wave file as a 16-bit wave file.
488
     */
489
    fromIMAADPCM() {
490
        this.fromScratch(
491
            this.fmt.numChannels,
492
            this.fmt.sampleRate,
493
            "16",
494
            imaadpcm_.decode(this.data.samples, this.fmt.blockAlign),
495
            {"container": this.correctContainer_()});
496
    }
497
498
    /**
499
     * Encode 16-bit wave file as 8-bit A-Law.
500
     */
501
    toALaw() {
502
        this.assure16Bit_();
503
        this.assureInterleaved_();
504
        this.fromScratch(
505
            this.fmt.numChannels,
506
            this.fmt.sampleRate,
507
            "8a",
508
            alawmulaw_.alaw.encode(this.data.samples),
509
            {"container": this.correctContainer_()});
510
    }
511
512
    /**
513
     * Decode a 8-bit A-Law wave file into a 16-bit wave file.
514
     */
515
    fromALaw() {
516
        this.fromScratch(
517
            this.fmt.numChannels,
518
            this.fmt.sampleRate,
519
            "16",
520
            alawmulaw_.alaw.decode(this.data.samples),
521
            {"container": this.correctContainer_()});
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.correctContainer_()});
536
    }
537
538
    /**
539
     * Decode a 8-bit mu-Law wave file into a 16-bit wave file.
540
     */
541
    fromMuLaw() {
542
        this.fromScratch(
543
            this.fmt.numChannels,
544
            this.fmt.sampleRate,
545
            "16",
546
            alawmulaw_.mulaw.decode(this.data.samples),
547
            {"container": this.correctContainer_()});
548
    }
549
550
    /**
551
     * Return the closest greater number of bits for a number of bits that
552
     * do not fill a full sequence of bytes.
553
     * @param {!string} bitDepth The bit depth.
554
     * @return {!string}
555
     */
556
    realBitDepth_(bitDepth) {
557
        if (bitDepth != "32f") {
558
            bitDepth = (((parseInt(bitDepth, 10) - 1) | 7) + 1).toString();
559
        }
560
        return bitDepth;
561
    }
562
563
    /**
564
     * Validate the input for wav writing.
565
     * @throws {Error} If any property of the object appears invalid.
566
     * @private
567
     */
568
    checkWriteInput_() {
569
        this.validateBitDepth_();
570
        this.validateNumChannels_();
571
        this.validateSampleRate_();
572
    }
573
574
    /**
575
     * Validate the bit depth.
576
     * @return {!boolean} True is the bit depth is valid.
577
     * @throws {Error} If bit depth is invalid.
578
     * @private
579
     */
580
    validateBitDepth_() {
581
        if (!this.audioFormats_[this.bitDepth]) {
582
            if (parseInt(this.bitDepth, 10) > 8 &&
583
                    parseInt(this.bitDepth, 10) < 54) {
584
                return true;
585
            }
586
            throw new Error("Invalid bit depth.");
587
        }
588
        return true;
589
    }
590
591
    /**
592
     * Validate the number of channels.
593
     * @return {!boolean} True is the number of channels is valid.
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
     * @return {!boolean} True is the sample rate is valid.
608
     * @throws {Error} If the sample rate is invalid.
609
     * @private
610
     */
611
    validateSampleRate_() {
612
        let byteRate = this.fmt.numChannels *
613
            (this.fmt.bitsPerSample / 8) * this.fmt.sampleRate;
614
        if (this.fmt.sampleRate < 1 || byteRate > 4294967295) {
615
            throw new Error("Invalid sample rate.");
616
        }
617
        return true;
618
    }
619
620
    /**
621
     * Reset attributes that should emptied when a file is
622
     * created with the fromScratch() method.
623
     * @private
624
     */
625
    clearHeader_() {
626
        this.fmt.cbSize = 0;
627
        this.fmt.validBitsPerSample = 0;
628
        this.fact.chunkId = "";
629
        this.ds64.chunkId = "";
630
    }
631
632
    /**
633
     * Make the file 16-bit if it is not.
634
     * @private
635
     */
636
    assure16Bit_() {
637
        this.assureUncompressed_();
638
        if (this.bitDepth != "16") {
639
            this.toBitDepth("16");
640
        }
641
    }
642
643
    /**
644
     * Uncompress the samples in case of a compressed file.
645
     * @private
646
     */
647
    assureUncompressed_() {
648
        if (this.bitDepth == "8a") {
649
            this.fromALaw();
650
        } else if(this.bitDepth == "8m") {
651
            this.fromMuLaw();
652
        } else if (this.bitDepth == "4") {
653
            this.fromIMAADPCM();
654
        }
655
    }
656
657
    /**
658
     * Interleave the samples in case they are de-Interleaved.
659
     * @private
660
     */
661
    assureInterleaved_() {
662
        if (!this.isInterleaved) {
663
            this.interleave();
664
        }
665
    }
666
667
    /**
668
     * Set up to work wih big-endian or little-endian files.
669
     * The types used are changed to LE or BE. If the
670
     * the file is big-endian (RIFX), true is returned.
671
     * @return {!boolean} True if the file is RIFX.
672
     * @private
673
     */
674
    LEorBE_() {
675
        let bigEndian = this.container === "RIFX";
676
        uInt8_["be"] = bigEndian;
677
        uInt16_["be"] = bigEndian;
678
        uInt32_["be"] = bigEndian;
679
        return bigEndian;
680
    }
681
682
    /**
683
     * Find a chunk by its fourCC_ in a array of RIFF chunks.
684
     * @param {!Array<!Object>} chunks The wav file chunks.
685
     * @param {!string} chunkId The chunk fourCC_.
686
     * @return {Object|null}
687
     * @private
688
     */
689
    findChunk_(chunks, chunkId) {
690
        for (let i = 0; i<chunks.length; i++) {
691
            if (chunks[i]["chunkId"] == chunkId) {
692
                return chunks[i];
693
            }
694
        }
695
        return null;
696
    }
697
698
    /**
699
     * Read the RIFF chunk a wave file.
700
     * @param {!Uint8Array|!Array<number>} bytes A wav buffer.
701
     * @throws {Error} If no "RIFF" chunk is found.
702
     * @private
703
     */
704
    readRIFFChunk_(bytes) {
705
        this.container = byteData_.unpack(
706
            bytes.slice(0, 4), fourCC_);
707
        if (["RIFF", "RIFX", "RF64"].indexOf(this.container) === -1) {
708
            throw Error("Not a supported format.");
709
        }
710
        this.LEorBE_();
711
        this.chunkSize = byteData_.unpack(bytes.slice(4, 8), uInt32_);
712
        this.format = byteData_.unpack(
713
            bytes.slice(8, 12), fourCC_);
714
        if (this.format != "WAVE") {
715
            throw Error("Could not find the 'WAVE' format identifier");
716
        }
717
    }
718
719
    /**
720
     * Read the "fmt " chunk of a wave file.
721
     * @param {!Array<!Object>} chunks The wav file chunks.
722
     * @throws {Error} If no "fmt " chunk is found.
723
     * @private
724
     */
725
    readFmtChunk_(chunks) {
726
        let chunk = this.findChunk_(chunks, "fmt ");
727
        if (chunk) {
728
            let chunkData = chunk["chunkData"];
729
            this.fmt.chunkId = "fmt ";
730
            this.fmt.chunkSize = chunk["chunkSize"];
731
            this.fmt.audioFormat = byteData_.unpack(
732
                chunkData.slice(0, 2), uInt16_);
733
            this.fmt.numChannels = byteData_.unpack(
734
                chunkData.slice(2, 4), uInt16_);
735
            this.fmt.sampleRate = byteData_.unpack(
736
                chunkData.slice(4, 8), uInt32_);
737
            this.fmt.byteRate = byteData_.unpack(
738
                chunkData.slice(8, 12), uInt32_);
739
            this.fmt.blockAlign = byteData_.unpack(
740
                chunkData.slice(12, 14), uInt16_);
741
            this.fmt.bitsPerSample = byteData_.unpack(
742
                    chunkData.slice(14, 16), uInt16_);
743
            this.readFmtExtension_(chunk);
744
        } else {
745
            throw Error("Could not find the 'fmt ' chunk");
746
        }
747
    }
748
749
    /**
750
     * Read the "fmt " chunk extension.
751
     * @param {!Object<string, *>} chunk The "fmt " chunk.
752
     * @private
753
     */
754
    readFmtExtension_(chunk) {
755
        let chunkData = chunk["chunkData"];
756
        if (this.fmt.chunkSize > 16) {
757
            this.fmt.cbSize = byteData_.unpack(
758
                chunkData.slice(16, 18), uInt16_);
759
            if (this.fmt.chunkSize > 18) {
760
                this.fmt.validBitsPerSample = byteData_.unpack(
761
                    chunkData.slice(18, 20), uInt16_);
762
                if (this.fmt.chunkSize > 20) {
763
                    this.fmt.dwChannelMask = byteData_.unpack(
764
                        chunkData.slice(20, 24), uInt32_);
765
                    this.fmt.subformat = [
766
                        byteData_.unpack(
767
                            chunkData.slice(24, 28), uInt32_),
768
                        byteData_.unpack(
769
                            chunkData.slice(28, 32), uInt32_),
770
                        byteData_.unpack(
771
                            chunkData.slice(32, 36), uInt32_),
772
                        byteData_.unpack(
773
                            chunkData.slice(36, 40), uInt32_)];
774
                }
775
            }
776
        }
777
    }
778
779
    /**
780
     * Read the "fact" chunk of a wav file.
781
     * @param {!Array<Object>} chunks The wav file chunks.
782
     * @throws {Error} If no "fact" chunk is found.
783
     * @private
784
     */
785
    readFactChunk_(chunks) {
786
        let chunk = this.findChunk_(chunks, "fact");
787
        if (chunk) {
788
            this.fact.chunkId = "fact";
789
            this.fact.chunkSize = chunk["chunkSize"];
790
            this.fact.dwSampleLength = byteData_.unpack(
791
                chunk["chunkData"].slice(0, 4), uInt32_);
792
        } else if (this.enforceFact_) {
793
            throw Error("Could not find the 'fact' chunk");
794
        }
795
    }
796
797
    /**
798
     * Read the "cue " chunk of a wave file.
799
     * @param {!Array<Object>} chunks The RIFF file chunks.
800
     * @private
801
     */
802
    readCueChunk_(chunks) {
803
        let chunk = this.findChunk_(chunks, "cue ");
804
        if (chunk) {
805
            let chunkData = chunk["chunkData"];
806
            this.cue.chunkId = "cue ";
807
            this.cue.chunkSize = chunk["chunkSize"];
808
            this.cue.dwCuePoints = byteData_.unpack(
809
                chunkData.slice(0, 4), uInt32_);
810
            for (let i=0; i<this.cue.dwCuePoints; i++) {
811
                let offset = 4 + (i * 24);
812
                this.cue.points.push({
813
                    "dwName": byteData_.unpack(
814
                        chunkData.slice(offset, offset + 4), uInt32_),
815
                    "dwPosition": byteData_.unpack(
816
                        chunkData.slice(offset + 4, offset + 8), uInt32_),
817
                    "fccChunk": byteData_.unpack(
818
                        chunkData.slice(offset + 8, offset + 12), fourCC_),
819
                    "dwChunkStart": byteData_.unpack(
820
                        chunkData.slice(offset + 12, offset + 16), uInt32_),
821
                    "dwBlockStart": byteData_.unpack(
822
                        chunkData.slice(offset + 16, offset + 20), uInt32_),
823
                    "dwSampleOffset": byteData_.unpack(
824
                        chunkData.slice(offset + 20, offset + 24), uInt32_),
825
                });
826
            }
827
        }
828
    }
829
830
    /**
831
     * Read the "data" chunk of a wave file.
832
     * @param {!Array<Object>} chunks The RIFF file chunks.
833
     * @param {!Object<string, *>} options The type definition.
834
     * @throws {Error} If no "data" chunk is found.
835
     * @private
836
     */
837
    readDataChunk_(chunks, options) {
838
        let chunk = this.findChunk_(chunks, "data");
839
        if (chunk) {
840
            this.data.chunkId = "data";
841
            this.data.chunkSize = chunk["chunkSize"];
842
            this.samplesFromBytes_(chunk["chunkData"], options);
843
        } else {
844
            throw Error("Could not find the 'data' chunk");
845
        }
846
    }
847
848
    /**
849
     * Read the "bext" chunk of a wav file.
850
     * @param {!Array<Object>} chunks The wav file chunks.
851
     * @private
852
     */
853
    readBextChunk_(chunks) {
854
        let chunk = this.findChunk_(chunks, "bext");
855
        if (chunk) {
856
            this.head_ = 0;
857
            let chunkData = chunk["chunkData"];
858
            this.bext = {
859
                "chunkId": "bext",
860
                "chunkSize": chunkData.length,
861
                "description": this.readString_(chunkData, 256),
862
                "originator": this.readString_(chunkData, 32),
863
                "originatorReference": this.readString_(chunkData, 32),
864
                "originationDate": this.readString_(chunkData, 10),
865
                "originationTime": this.readString_(chunkData, 8),
866
                "timeReference": [
867
                        this.readFromChunk_(chunkData, uInt32_),
868
                        this.readFromChunk_(chunkData, uInt32_)], 
869
                "version": this.readFromChunk_(
870
                    chunkData, uInt16_),
871
                "UMID": this.readString_(chunkData, 64),
872
                "loudnessValue": this.readFromChunk_(
873
                    chunkData, uInt16_),
874
                "loudnessRange": this.readFromChunk_(
875
                    chunkData, uInt16_),
876
                "maxTruePeakLevel": this.readFromChunk_(
877
                    chunkData, uInt16_),
878
                "maxMomentaryLoudness": this.readFromChunk_(
879
                    chunkData, uInt16_),
880
                "maxShortTermLoudness": this.readFromChunk_(
881
                    chunkData, uInt16_),
882
                "reserved": this.readString_(chunkData, 180),
883
                "codingHistory": this.readString_(
884
                    chunkData, chunkData.length - 602),
885
            };
886
        }
887
    }
888
889
    /**
890
     * Read the "fmt " chunk of a wave file.
891
     * @param {!Array<Object>} chunks The wav file chunks.
892
     * @throws {Error} If no "fmt " chunk is found.
893
     * @private
894
     */
895
    readDs64Chunk_(chunks) {
896
        let chunk = this.findChunk_(chunks, "ds64");
897
        if (chunk) {
898
            let chunkData = chunk["chunkData"];
899
            this.ds64.chunkId = chunk["chunkId"];
900
            this.ds64.chunkSize = chunk["chunkSize"];
901
            this.ds64.riffSizeHigh = byteData_.unpack(
902
                chunkData.slice(0, 4), uInt32_);
903
            this.ds64.riffSizeLow = byteData_.unpack(
904
                chunkData.slice(4, 8), uInt32_);
905
            this.ds64.dataSizeHigh = byteData_.unpack(
906
                chunkData.slice(8, 12), uInt32_);
907
            this.ds64.dataSizeLow = byteData_.unpack(
908
                chunkData.slice(12, 16), uInt32_);
909
            this.ds64.originationTime = byteData_.unpack(
910
                chunkData.slice(16, 20), uInt32_);
911
            this.ds64.sampleCountHigh = byteData_.unpack(
912
                chunkData.slice(20, 24), uInt32_);
913
            this.ds64.sampleCountLow = byteData_.unpack(
914
                chunkData.slice(24, 28), uInt32_);
915
            //if (this.ds64.chunkSize > 28) {
916
            //    this.ds64.tableLength = byteData_.unpack(
917
            //        chunkData.slice(28, 32), uInt32_);
918
            //    this.ds64.table = chunkData.slice(32, 32 + this.ds64.tableLength);    
919
            //}
920
        } else {
921
            if (this.container == "RF64") {
922
                throw Error("Could not find the 'ds64' chunk");    
923
            }
924
        }
925
    }
926
927
    /**
928
     * Read bytes as a string from a RIFF chunk.
929
     * @param {!Array<number>|!Uint8Array} bytes The bytes.
930
     * @param {!number} maxSize the max size of the string.
931
     * @return {!string} The string.
932
     * @private
933
     */
934
    readString_(bytes, maxSize) {
935
        let str = "";
936
        for (let i=0; i<maxSize; i++) {
937
            str += byteData_.unpack([bytes[this.head_]], chr_);
938
            this.head_++;
939
        }
940
        return str;
941
    }
942
943
    /**
944
     * Read a number from a chunk.
945
     * @param {!Array<number>|!Uint8Array} bytes The chunk bytes.
946
     * @param {!Object} bdType The type definition.
947
     * @return {!number} The number.
948
     * @private
949
     */
950
    readFromChunk_(bytes, bdType) {
951
        let size = bdType["bits"] / 8;
952
        let value = byteData_.unpack(
953
            bytes.slice(this.head_, this.head_ + size), bdType);
954
        this.head_ += size;
955
        return value;
956
    }
957
958
    /**
959
     * Write a variable size string as bytes. If the string is smaller
960
     * than the max size the output array is filled with 0s.
961
     * @param {!string} str The string to be written as bytes.
962
     * @param {!number} maxSize the max size of the string.
963
     * @return {!Array<number>} The bytes.
964
     * @private
965
     */
966
    writeString_(str, maxSize) {
967
        let bytes = byteData_.packArray(str, chr_);
968
        for (let i=bytes.length; i<maxSize; i++) {
969
            bytes.push(0);
970
        }
971
        return bytes;
972
    }
973
974
    /**
975
     * Turn the samples to bytes.
976
     * @param {!Object<string, *>} options Type options.
977
     * @return {!Array<number>} The bytes.
978
     * @private
979
     */
980
    samplesToBytes_(options) {
981
        options["bits"] = this.fmt.bitsPerSample == 4 ?
982
            8 : this.fmt.bitsPerSample;
983
        options["signed"] = options["bits"] == 8 ? false : true;
984
        options["float"] = (this.fmt.audioFormat == 3  ||
985
                this.fmt.bitsPerSample == 64) ? true : false;
986
        let bytes = byteData_.packArray(
987
            this.data.samples, options);
988
        if (bytes.length % 2) {
989
            bytes.push(0);
990
        }
991
        return bytes;
992
    }
993
994
    /**
995
     * Turn bytes to samples and load them in the data.samples property.
996
     * @param {!Array<number>|!Uint8Array} bytes The bytes.
997
     * @param {!Object<string, *>} options The type definition.
998
     * @private
999
     */
1000
    samplesFromBytes_(bytes, options) {
1001
        options["bits"] = this.fmt.bitsPerSample == 4 ?
1002
            8 : this.fmt.bitsPerSample;
1003
        options["signed"] = options["bits"] == 8 ? false : true;
1004
        options["float"] = (this.fmt.audioFormat == 3 || 
1005
            this.fmt.bitsPerSample == 64) ? true : false;
1006
        this.data.samples = byteData_.unpackArray(
1007
            bytes, options);
1008
    }
1009
1010
    /**
1011
     * Return the bytes of the "bext" chunk.
1012
     * @return {!Array<number>} The "bext" chunk bytes.
1013
     * @private
1014
     */
1015
    getBextBytes_() {
1016
        if (this.bext.chunkId) {
1017
            return [].concat(
1018
                byteData_.pack(this.bext.chunkId, fourCC_),
1019
                byteData_.pack(this.bext.chunkSize, uInt32_),
1020
                this.writeString_(this.bext.description, 256),
1021
                this.writeString_(this.bext.originator, 32),
1022
                this.writeString_(this.bext.originatorReference, 32),
1023
                this.writeString_(this.bext.originationDate, 10),
1024
                this.writeString_(this.bext.originationTime, 8),
1025
                byteData_.pack(this.bext.timeReference[0], uInt32_),
1026
                byteData_.pack(this.bext.timeReference[1], uInt32_),
1027
                byteData_.pack(this.bext.version, uInt16_),
1028
                this.writeString_(this.bext.UMID, 64),
1029
                byteData_.pack(this.bext.loudnessValue, uInt16_),
1030
                byteData_.pack(this.bext.loudnessRange, uInt16_),
1031
                byteData_.pack(this.bext.maxTruePeakLevel, uInt16_),
1032
                byteData_.pack(this.bext.maxMomentaryLoudness, uInt16_),
1033
                byteData_.pack(this.bext.maxShortTermLoudness, uInt16_),
1034
                this.writeString_(this.bext.reserved, 180),
1035
                this.writeString_(
1036
                    this.bext.codingHistory,
1037
                    this.bext.chunkSize - 602));
1038
        }
1039
        return [];
1040
    }
1041
1042
    /**
1043
     * Return the bytes of the "ds64" chunk.
1044
     * @return {!Array<number>} The "ds64" chunk bytes.
1045
     * @private
1046
     */
1047
    getDs64Bytes_() {
1048
        let ds64Bytes = [];
1049
        if (this.ds64["chunkId"]) {
1050
            ds64Bytes = ds64Bytes.concat(
1051
                byteData_.pack(this.ds64.chunkId, fourCC_),
1052
                byteData_.pack(this.ds64.chunkSize, uInt32_),
1053
                byteData_.pack(this.ds64.riffSizeHigh, uInt32_),
1054
                byteData_.pack(this.ds64.riffSizeLow, uInt32_),
1055
                byteData_.pack(this.ds64.dataSizeHigh, uInt32_),
1056
                byteData_.pack(this.ds64.dataSizeLow, uInt32_),
1057
                byteData_.pack(this.ds64.originationTime, uInt32_),
1058
                byteData_.pack(this.ds64.sampleCountHigh, uInt32_),
1059
                byteData_.pack(this.ds64.sampleCountLow, uInt32_));          
1060
        }
1061
        //if (this.ds64.tableLength) {
1062
        //    ds64Bytes = ds64Bytes.concat(
1063
        //        byteData_.pack(this.ds64.tableLength, uInt32_),
1064
        //        this.ds64.table);
1065
        //}
1066
        return ds64Bytes;
1067
    }
1068
1069
    /**
1070
     * Return the bytes of the "cue " chunk.
1071
     * @return {!Array<number>} The "cue " chunk bytes.
1072
     * @private
1073
     */
1074
    getCueBytes_() {
1075
        if (this.cue.chunkId) {
1076
            return [].concat(
1077
                byteData_.pack(this.cue.chunkId, fourCC_),
1078
                byteData_.pack(this.cue.chunkSize, uInt32_),
1079
                byteData_.pack(this.cue.dwCuePoints, uInt32_),
1080
                this.getCuePointsBytes_());
1081
        }
1082
        return [];
1083
    }
1084
1085
    /**
1086
     * Return the bytes of the "cue " points.
1087
     * @return {!Array<number>} The "cue " points as an array of bytes.
1088
     * @private
1089
     */
1090
    getCuePointsBytes_() {
1091
        let points = [];
1092
        for (let i=0; i<this.cue.dwCuePoints; i++) {
1093
            points = points.concat(
1094
                    byteData_.pack(this.cue.points[i]["dwName"], uInt32_),
1095
                    byteData_.pack(this.cue.points[i]["dwPosition"], uInt32_),
1096
                    byteData_.pack(this.cue.points[i]["fccChunk"], fourCC_),
1097
                    byteData_.pack(this.cue.points[i]["dwChunkStart"], uInt32_),
1098
                    byteData_.pack(this.cue.points[i]["dwBlockStart"], uInt32_),
1099
                    byteData_.pack(this.cue.points[i]["dwSampleOffset"], uInt32_)
1100
                );
1101
        }
1102
        return points;
1103
    }
1104
1105
    /**
1106
     * Return the bytes of the "fact" chunk.
1107
     * @return {!Array<number>} The "fact" chunk bytes.
1108
     * @private
1109
     */
1110
    getFactBytes_() {
1111
        if (this.fact.chunkId) {
1112
            return [].concat(
1113
                byteData_.pack(this.fact.chunkId, fourCC_),
1114
                byteData_.pack(this.fact.chunkSize, uInt32_),
1115
                byteData_.pack(this.fact.dwSampleLength, uInt32_));
1116
        }
1117
        return [];
1118
    }
1119
1120
    /**
1121
     * Return the bytes of the "fmt " chunk.
1122
     * @return {!Array<number>} The "fmt" chunk bytes.
1123
     * @private
1124
     * @throws {Error} if no "fmt " chunk is present.
1125
     */
1126
    getFmtBytes_() {
1127
        if (this.fmt.chunkId) {
1128
            return [].concat(
1129
                byteData_.pack(this.fmt.chunkId, fourCC_),
1130
                byteData_.pack(this.fmt.chunkSize, uInt32_),
1131
                byteData_.pack(this.fmt.audioFormat, uInt16_),
1132
                byteData_.pack(this.fmt.numChannels, uInt16_),
1133
                byteData_.pack(this.fmt.sampleRate, uInt32_),
1134
                byteData_.pack(this.fmt.byteRate, uInt32_),
1135
                byteData_.pack(this.fmt.blockAlign, uInt16_),
1136
                byteData_.pack(this.fmt.bitsPerSample, uInt16_),
1137
                this.getFmtExtensionBytes_()
1138
            );
1139
        } 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...
1140
            throw Error("Could not find the 'fmt ' chunk");
1141
        }
1142
    }
1143
1144
    /**
1145
     * Return the bytes of the fmt extension fields.
1146
     * @return {!Array<number>} The fmt extension bytes.
1147
     * @private
1148
     */
1149
    getFmtExtensionBytes_() {
1150
        let extension = [];
1151
        if (this.fmt.chunkSize > 16) {
1152
            extension = extension.concat(
1153
                byteData_.pack(this.fmt.cbSize, uInt16_));
1154
        }
1155
        if (this.fmt.chunkSize > 18) {
1156
            extension = extension.concat(
1157
                byteData_.pack(this.fmt.validBitsPerSample, uInt16_));
1158
        }
1159
        if (this.fmt.chunkSize > 20) {
1160
            extension = extension.concat(
1161
                byteData_.pack(this.fmt.dwChannelMask, uInt32_));
1162
        }
1163
        if (this.fmt.chunkSize > 24) {
1164
            extension = extension.concat(
1165
                byteData_.pack(this.fmt.subformat[0], uInt32_),
1166
                byteData_.pack(this.fmt.subformat[1], uInt32_),
1167
                byteData_.pack(this.fmt.subformat[2], uInt32_),
1168
                byteData_.pack(this.fmt.subformat[3], uInt32_));
1169
        }
1170
        return extension;
1171
    }
1172
1173
    /**
1174
     * Return "RIFF" if the container is "RF64", the current
1175
     * container name otherwise.
1176
     * Used to enforce "RIFF" as the container in operations where
1177
     * RF64 is not allowed.
1178
     * @private
1179
     */
1180
    correctContainer_() {
1181
        return this.container == "RF64" ? "RIFF" : this.container;
1182
    }
1183
1184
    /**
1185
     * Set the string code of the bit depth based on the "fmt " chunk.
1186
     * @private
1187
     */
1188
     bitDepthFromFmt_() {
1189
        if (this.fmt.audioFormat == 3 && this.fmt.bitsPerSample == 32) {
1190
            this.bitDepth = "32f";
1191
        } else if (this.fmt.audioFormat == 6) {
1192
            this.bitDepth = "8a";
1193
        } else if (this.fmt.audioFormat == 7) {
1194
            this.bitDepth = "8m";
1195
        } else {
1196
            this.bitDepth = this.fmt.bitsPerSample.toString();
1197
        }
1198
     }
1199
    
1200
    /**
1201
     * Return a .wav file byte buffer with the data from the WaveFile object.
1202
     * The return value of this method can be written straight to disk.
1203
     * @return {!Uint8Array} The wav file bytes.
1204
     * @private
1205
     */
1206
    createWaveFile_() {
1207
        let options = {"be": this.LEorBE_()};
1208
        return new Uint8Array([].concat(
1209
            byteData_.pack(this.container, fourCC_),
1210
            byteData_.pack(this.chunkSize, uInt32_),
1211
            byteData_.pack(this.format, fourCC_),
1212
            this.getDs64Bytes_(),
1213
            this.getBextBytes_(),
1214
            this.getFmtBytes_(),
1215
            this.getFactBytes_(),
1216
            byteData_.pack(this.data.chunkId, fourCC_),
1217
            byteData_.pack(this.data.chunkSize, uInt32_),
1218
            this.samplesToBytes_(options),
1219
            this.getCueBytes_()));
1220
    }
1221
}
1222
1223
module.exports = WaveFile;
1224