Passed
Push — master ( df656c...214d87 )
by Rafael S.
02:14
created

index.js (3 issues)

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