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