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

index.js (1 issue)

Severity
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;
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) {
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
                let offset = 4 + (i * 24);
0 ignored issues
show
The variable offset seems to be never used. Consider removing it.
Loading history...
857
                this.cue.points.push({
858
                    "dwName": this.read_(chunkData, uInt32_),
859
                    "dwPosition": this.read_(chunkData, uInt32_),
860
                    "fccChunk": this.readString_(chunkData, 4),
861
                    "dwChunkStart": this.read_(chunkData, uInt32_),
862
                    "dwBlockStart": this.read_(chunkData, uInt32_),
863
                    "dwSampleOffset": this.read_(chunkData, uInt32_),
864
                });
865
            }
866
        }
867
    }
868
869
    /**
870
     * Read the "data" chunk of a wave file.
871
     * @param {!Array<Object>} chunks The RIFF file chunks.
872
     * @param {!Object<string, *>} options The type definition.
873
     * @throws {Error} If no "data" chunk is found.
874
     * @private
875
     */
876
    readDataChunk_(chunks, options) {
877
        let chunk = this.findChunk_(chunks, "data");
878
        if (chunk) {
879
            this.data.chunkId = "data";
880
            this.data.chunkSize = chunk["chunkSize"];
881
            this.samplesFromBytes_(chunk["chunkData"], options);
882
        } else {
883
            throw Error("Could not find the 'data' chunk");
884
        }
885
    }
886
887
    /**
888
     * Read the "bext" chunk of a wav file.
889
     * @param {!Array<Object>} chunks The wav file chunks.
890
     * @private
891
     */
892
    readBextChunk_(chunks) {
893
        let chunk = this.findChunk_(chunks, "bext");
894
        if (chunk) {
895
            this.head_ = 0;
896
            let chunkData = chunk["chunkData"];
897
            this.bext.chunkId = chunk["chunkId"];
898
            this.bext.chunkSize = chunk["chunkSize"];
899
            this.bext.description = this.readString_(chunkData, 256);
900
            this.bext.originator = this.readString_(chunkData, 32);
901
            this.bext.originatorReference = this.readString_(chunkData, 32);
902
            this.bext.originationDate = this.readString_(chunkData, 10);
903
            this.bext.originationTime = this.readString_(chunkData, 8);
904
            this.bext.timeReference = [
905
                    this.read_(chunkData, uInt32_),
906
                    this.read_(chunkData, uInt32_)];
907
            this.bext.version = this.read_(chunkData, uInt16_);
908
            this.bext.UMID = this.readString_(chunkData, 64);
909
            this.bext.loudnessValue = this.read_(chunkData, uInt16_);
910
            this.bext.loudnessRange = this.read_(chunkData, uInt16_);
911
            this.bext.maxTruePeakLevel = this.read_(chunkData, uInt16_);
912
            this.bext.maxMomentaryLoudness = this.read_(chunkData, uInt16_);
913
            this.bext.maxShortTermLoudness = this.read_(chunkData, uInt16_);
914
            this.bext.reserved = this.readString_(chunkData, 180);
915
            this.bext.codingHistory = this.readString_(
916
                chunkData, this.bext.chunkSize - 602);
917
        }
918
    }
919
920
    /**
921
     * Read the "fmt " chunk of a wave file.
922
     * @param {!Array<Object>} chunks The wav file chunks.
923
     * @throws {Error} If no "fmt " chunk is found.
924
     * @private
925
     */
926
    readDs64Chunk_(chunks) {
927
        let chunk = this.findChunk_(chunks, "ds64");
928
        if (chunk) {
929
            this.head_ = 0;
930
            let chunkData = chunk["chunkData"];
931
            this.ds64.chunkId = chunk["chunkId"];
932
            this.ds64.chunkSize = chunk["chunkSize"];
933
            this.ds64.riffSizeHigh = this.read_(chunkData, uInt32_);
934
            this.ds64.riffSizeLow = this.read_(chunkData, uInt32_);
935
            this.ds64.dataSizeHigh = this.read_(chunkData, uInt32_);
936
            this.ds64.dataSizeLow = this.read_(chunkData, uInt32_);
937
            this.ds64.originationTime = this.read_(chunkData, uInt32_);
938
            this.ds64.sampleCountHigh = this.read_(chunkData, uInt32_);
939
            this.ds64.sampleCountLow = this.read_(chunkData, uInt32_);
940
            //if (this.ds64.chunkSize > 28) {
941
            //    this.ds64.tableLength = byteData_.unpack(
942
            //        chunkData.slice(28, 32), uInt32_);
943
            //    this.ds64.table = chunkData.slice(32, 32 + this.ds64.tableLength);    
944
            //}
945
        } else {
946
            if (this.container == "RF64") {
947
                throw Error("Could not find the 'ds64' chunk");    
948
            }
949
        }
950
    }
951
952
    /**
953
     * Read bytes as a string from a RIFF chunk.
954
     * @param {!Array<number>|!Uint8Array} bytes The bytes.
955
     * @param {!number} maxSize the max size of the string.
956
     * @return {!string} The string.
957
     * @private
958
     */
959
    readString_(bytes, maxSize) {
960
        let str = "";
961
        for (let i=0; i<maxSize; i++) {
962
            str += byteData_.unpack([bytes[this.head_]], chr_);
963
            this.head_++;
964
        }
965
        return str;
966
    }
967
968
    /**
969
     * Read a number from a chunk.
970
     * @param {!Array<number>|!Uint8Array} bytes The chunk bytes.
971
     * @param {!Object} bdType The type definition.
972
     * @return {number} The number.
973
     * @private
974
     */
975
    read_(bytes, bdType) {
976
        let size = bdType["bits"] / 8;
977
        let value = byteData_.unpack(
978
            bytes.slice(this.head_, this.head_ + size), bdType);
979
        this.head_ += size;
980
        return value;
981
    }
982
983
    /**
984
     * Write a variable size string as bytes. If the string is smaller
985
     * than the max size the output array is filled with 0s.
986
     * @param {!string} str The string to be written as bytes.
987
     * @param {!number} maxSize the max size of the string.
988
     * @return {!Array<number>} The bytes.
989
     * @private
990
     */
991
    writeString_(str, maxSize) {
992
        let bytes = byteData_.packArray(str, chr_);
993
        for (let i=bytes.length; i<maxSize; i++) {
994
            bytes.push(0);
995
        }
996
        return bytes;
997
    }
998
999
    /**
1000
     * Turn the samples to bytes.
1001
     * @param {!Object<string, *>} options Type options.
1002
     * @return {!Array<number>} The bytes.
1003
     * @private
1004
     */
1005
    samplesToBytes_(options) {
1006
        options["bits"] = this.fmt.bitsPerSample == 4 ?
1007
            8 : this.fmt.bitsPerSample;
1008
        options["signed"] = options["bits"] == 8 ? false : true;
1009
        options["float"] = (this.fmt.audioFormat == 3  ||
1010
                this.fmt.bitsPerSample == 64) ? true : false;
1011
        let bytes = byteData_.packArray(
1012
            this.data.samples, options);
1013
        if (bytes.length % 2) {
1014
            bytes.push(0);
1015
        }
1016
        return bytes;
1017
    }
1018
1019
    /**
1020
     * Turn bytes to samples and load them in the data.samples property.
1021
     * @param {!Array<number>|!Uint8Array} bytes The bytes.
1022
     * @param {!Object<string, *>} options The type definition.
1023
     * @private
1024
     */
1025
    samplesFromBytes_(bytes, options) {
1026
        options["bits"] = this.fmt.bitsPerSample == 4 ?
1027
            8 : this.fmt.bitsPerSample;
1028
        options["signed"] = options["bits"] == 8 ? false : true;
1029
        options["float"] = (this.fmt.audioFormat == 3 || 
1030
            this.fmt.bitsPerSample == 64) ? true : 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_.pack(this.bext.chunkId, fourCC_),
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_.pack(this.ds64.chunkId, fourCC_),
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_.pack(this.cue.chunkId, fourCC_),
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"], 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_.pack(this.fact.chunkId, fourCC_),
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
     * @throws {Error} if no "fmt " chunk is present.
1149
     * @private
1150
     */
1151
    getFmtBytes_() {
1152
        if (this.fmt.chunkId) {
1153
            return [].concat(
1154
                byteData_.pack(this.fmt.chunkId, fourCC_),
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 {
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 "RIFF" if the container is "RF64", the current container name
1200
     * otherwise. Used to enforce "RIFF" when RF64 is not allowed.
1201
     * @return {!string}
1202
     * @private
1203
     */
1204
    correctContainer_() {
1205
        return this.container == "RF64" ? "RIFF" : this.container;
1206
    }
1207
1208
    /**
1209
     * Set the string code of the bit depth based on the "fmt " chunk.
1210
     * @private
1211
     */
1212
     bitDepthFromFmt_() {
1213
        if (this.fmt.audioFormat == 3 && this.fmt.bitsPerSample == 32) {
1214
            this.bitDepth = "32f";
1215
        } else if (this.fmt.audioFormat == 6) {
1216
            this.bitDepth = "8a";
1217
        } else if (this.fmt.audioFormat == 7) {
1218
            this.bitDepth = "8m";
1219
        } else {
1220
            this.bitDepth = this.fmt.bitsPerSample.toString();
1221
        }
1222
     }
1223
    
1224
    /**
1225
     * Return a .wav file byte buffer with the data from the WaveFile object.
1226
     * The return value of this method can be written straight to disk.
1227
     * @return {!Uint8Array} The wav file bytes.
1228
     * @private
1229
     */
1230
    createWaveFile_() {
1231
        let options = {"be": this.LEorBE_()};
1232
        return new Uint8Array([].concat(
1233
            byteData_.pack(this.container, fourCC_),
1234
            byteData_.pack(this.chunkSize, uInt32_),
1235
            byteData_.pack(this.format, fourCC_),
1236
            this.getDs64Bytes_(),
1237
            this.getBextBytes_(),
1238
            this.getFmtBytes_(),
1239
            this.getFactBytes_(),
1240
            byteData_.pack(this.data.chunkId, fourCC_),
1241
            byteData_.pack(this.data.chunkSize, uInt32_),
1242
            this.samplesToBytes_(options),
1243
            this.getCueBytes_()));
1244
    }
1245
}
1246
1247
module.exports = WaveFile;
1248