Passed
Push — master ( c8c54a...b3668a )
by Rafael S.
01:20
created

index.js (2 issues)

1
/*!
2
 * wavefile
3
 * Read & write wave files with 8, 16, 24, 32 PCM, 32 IEEE & 64-bit data.
4
 * Copyright (c) 2017 Rafael da Silva Rocha. MIT License.
5
 * https://github.com/rochars/wavefile
6
 *
7
 */
8
9
const byteData = require("byte-data");
10
const wavefileheader = require("./src/wavefileheader");
11
12
/**
13
 * A wave file.
14
 */
15
class WaveFile extends wavefileheader.WaveFileHeader {
16
17
    /**
18
     * @param {Uint8Array} bytes The file bytes.
19
     * @param {boolean} enforceFact True if it should throw a error
20
     *      if no "fact" chunk is found.
21
     * @param {boolean} enforceBext True if it should throw a error
22
     *      if no "bext" chunk is found.
23
     */
24
    constructor(bytes, enforceFact=false, enforceBext=false) {
25
        super();
26
        /** @type {boolean} */
27
        this.isFromScratch_ = false;
28
        /** @type {boolean} */
29
        this.enforceFact = enforceFact;
30
        /** @type {boolean} */
31
        this.enforceBext = enforceBext;
32
        /**
33
         * Error messages.
34
         * @enum {string}
35
         */
36
        this.WaveErrors = {
37
            "format": "Not a supported format.",
38
            "wave": "Could not find the 'WAVE' chunk",
39
            "fmt ": "Could not find the 'fmt ' chunk",
40
            "data": "Could not find the 'data' chunk",
41
            "fact": "Could not find the 'fact' chunk",
42
            "bext": "Could not find the 'bext' chunk",
43
            "bitDepth": "Invalid bit depth.",
44
            "numChannels": "Invalid number of channels.",
45
            "sampleRate": "Invalid sample rate."
46
        };
47
        this.samples_ = [];
48
        this.bytes_ = [];
49
        if(bytes) {
50
            this.fromBuffer(bytes);
51
        }
52
    }
53
54
    /**
55
     * Create a WaveFile object based on the arguments passed.
56
     * @param {number} numChannels The number of channels
57
     *     (Ints like 1 for mono, 2 stereo and so on).
58
     * @param {number} sampleRate The sample rate.
59
     *     Integer numbers like 8000, 44100, 48000, 96000, 192000.
60
     * @param {string} bitDepth The audio bit depth.
61
     *     One of "8", "16", "24", "32", "32f", "64".
62
     * @param {!Array<number>} samples Array of samples to be written.
63
     *     Samples must be in the correct range according to the bit depth.
64
     *     Samples of multi-channel data .
65
     */
66
    fromScratch(numChannels, sampleRate, bitDepth, samples, options={}) {
67
        if (!options.container) {
68
            options.container = "RIFF";
69
        }
70
        this.isFromScratch_ = true;
71
        let bytes = parseInt(bitDepth, 10) / 8;
72
        this.chunkSize = 36 + samples.length * bytes;
73
        this.subChunk1Size = 16;
74
        this.byteRate = (numChannels * bytes) * sampleRate;
75
        this.blockAlign = numChannels * bytes;
76
        this.chunkId = options.container;
77
        this.format = "WAVE";
78
        this.subChunk1Id = "fmt ";
79
        this.audioFormat = this.headerFormats_[bitDepth];
80
        this.numChannels = numChannels;
81
        this.sampleRate = sampleRate;
82
        this.bitsPerSample = parseInt(bitDepth, 10);
83
        this.subChunk2Id = "data";
84
        this.subChunk2Size = samples.length * bytes;
85
        this.samples_ = samples;
86
        this.bitDepth_ = bitDepth;
87
    }
88
89
    /**
90
     * Read a wave file from a byte buffer.
91
     * @param {Uint8Array} bytes The buffer.
92
     */
93
    fromBuffer(bytes) {
94
        this.isFromScratch_ = false;
95
        this.readRIFFChunk_(bytes);
96
        this.readWAVEChunk_(bytes);
97
        this.readFmtChunk_(bytes);
98
        this.readFactChunk_(bytes);
99
        this.readBextChunk_(bytes);
100
        this.readDataChunk_(bytes);
101
    }
102
103
    /**
104
     * Turn the WaveFile object into a byte buffer.
105
     * @return {Uint8Array}
106
     */
107
    toBuffer() {
108
        this.checkWriteInput_(this.numChannels, this.sampleRate, this.bitDepth_);
109
        this.samplesToBytes_();
110
        return new Uint8Array(this.createWaveFile_());
111
    }
112
113
    /**
114
     * Turn the file to RIFF.
115
     * All values will be little-endian when writing.
116
     */
117
    toRIFF() {
118
        this.chunkId = "RIFF";
119
    }
120
121
    /**
122
     * Turn the file to RIFX.
123
     * All values but FourCCs will be big-endian when writing.
124
     */
125
    toRIFX() {
126
        this.chunkId = "RIFX";
127
    }
128
129
    /**
130
     * Change the bit depth of the data.
131
     * @param {string} bitDepth The new bit depth of the data.
132
     *      One of "8", "16", "24", "32", "32f", "64"
133
     */
134
    toBitDepth(bitDepth) {
135
        if (bitDepth == this.bitDepth_) {
136
            return;
137
        }
138
        let originalBitDepth = this.bitDepth_;
139
        this.bitDepth_ = bitDepth;
140
        try {
141
            this.validateBitDepth_();
142
        } catch(err) {
143
            this.bitDepth_ = originalBitDepth;
144
            throw err;
145
        }
146
        let len = this.samples_.length;
147
        let newSamples = [];
148
149
        // change the bit depth of the samples
150
        let oldMaxValue =
151
            parseInt((byteData.BitDepthMaxValues[parseInt(originalBitDepth, 10)]) / 2, 10);
152
        let newMaxValue =
153
            parseInt((byteData.BitDepthMaxValues[parseInt(this.bitDepth_, 10)] -1) / 2, 10);
154
155
        for (let i=0; i<len;i++) {
156
            if (originalBitDepth == "8") {
157
                this.samples_[i] -= 128;
158
            }
159
            if (this.bitDepth_ == "32f" || this.bitDepth_ == "64") {
160
                if (originalBitDepth == "32f" || originalBitDepth == "64") {
161
                    newSamples.push(this.samples_[i]);
162
                } else {
163
                    newSamples.push(this.samples_[i] / oldMaxValue);
164
                }
165
            }else {
166
                if (originalBitDepth == "32f" || originalBitDepth == "64" ) {
167
                    newSamples.push(this.samples_[i] * newMaxValue);
168
                } else {
169
                    newSamples.push(
170
                        parseInt((this.samples_[i] / oldMaxValue) * newMaxValue, 10)
171
                    );
172
                }
173
                if (newSamples[i] < 0) {
174
                    newSamples[i]--;
175
                }
176
                if (this.bitDepth_ == "8") {
177
                    newSamples[i] += 128;
178
                }
179
            }  
180
        }
181
        // recreate the file with the new samples
182
        this.fromScratch(
183
            this.numChannels,
184
            this.sampleRate,
185
            this.bitDepth_,
186
            newSamples,
187
            {"container": this.chunkId}
188
        );
189
    }
190
191
    /**
192
     * Interleave multi-channel samples.
193
     */
194
    interleave() {
195
        let finalSamples = [];
196
        let i;
197
        let j;
198
        let numChannels = this.samples_[0].length;
199
        for (i = 0; i < numChannels; i++) {
200
            for (j = 0; j < this.samples_.length; j++) {
201
                finalSamples.push(this.samples_[j][i]);
202
            }
203
        }
204
        this.samples_ = finalSamples;
205
    }
206
207
    /**
208
     * De-interleave samples into multiple channels.
209
     */
210
    deInterleave() {
211
        let finalSamples = [];
212
        let i;
213
        for (i = 0; i < this.numChannels; i++) {
214
            finalSamples[i] = [];
215
        }
216
        i = 0;
217
        let j;
218
        while (i < this.samples_.length) {
219
            for (j = 0; j < this.numChannels; j++) {
220
                finalSamples[j].push(this.samples_[i+j]);
221
            }
222
            i += j;
223
        }
224
        this.samples_ = finalSamples;
225
    }
226
227
    /**
228
     * Read the RIFF chunk a wave file.
229
     * @param {Uint8Array} bytes an array representing the wave file.
230
     * @throws {Error} If no "RIFF" chunk is found.
231
     */
232
    readRIFFChunk_(bytes) {
233
        this.chunkId = byteData.fromBytes(bytes.slice(0, 4),
234
            8, {"char": true});
235
        if (this.chunkId != "RIFF" && this.chunkId != "RIFX") {
236
            throw Error(this.WaveErrors.format);
237
        }
238
        this.chunkSize = byteData.fromBytes(
239
            bytes.slice(4, 8), 32, {"be": this.chunkId == "RIFX"})[0];
240
    }
241
242
    /**
243
     * Read the WAVE chunk of a wave file.
244
     * @param {Uint8Array} bytes an array representing the wave file.
245
     * @throws {Error} If no "WAVE" chunk is found.
246
     */
247
    readWAVEChunk_(bytes) {
248
        let start = byteData.findString(bytes, "WAVE");
249
        if (start === -1) {
250
            throw Error(this.WaveErrors.wave);
251
        }
252
        this.format = "WAVE";
253
    }
254
255
    /**
256
     * Read the "fmt " chunk of a wave file.
257
     * @param {Uint8Array} bytes an array representing the wave file.
258
     * @throws {Error} If no "fmt " chunk is found.
259
     */
260
    readFmtChunk_(bytes) {
261
        let start = byteData.findString(bytes, "fmt ");
262
        if (start === -1) {
263
            throw Error(this.WaveErrors["fmt "]);
264
        }
265
        let options = {"be": this.chunkId == "RIFX"};
266
        this.subChunk1Id = "fmt ";
267
        this.subChunk1Size = byteData.fromBytes(
268
            bytes.slice(start + 4, start + 8), 32, options)[0];
269
        this.audioFormat = byteData.fromBytes(
270
            bytes.slice(start + 8, start + 10), 16, options)[0];
271
        this.numChannels = byteData.fromBytes(
272
            bytes.slice(start + 10, start + 12), 16, options)[0];
273
        this.sampleRate = byteData.fromBytes(
274
            bytes.slice(start + 12, start + 16), 32, options)[0];
275
        this.byteRate = byteData.fromBytes(
276
            bytes.slice(start + 16, start + 20), 32, options)[0];
277
        this.blockAlign = byteData.fromBytes(
278
            bytes.slice(start + 20, start + 22), 16, options)[0];
279
        this.bitsPerSample = byteData.fromBytes(
280
            bytes.slice(start + 22, start + 24), 16, options)[0];
281
        if (this.audioFormat == 3 && this.bitsPerSample == 32) {
282
            this.bitDepth_ = "32f";
283
        }else {
284
            this.bitDepth_ = this.bitsPerSample.toString();
285
        }
286
    }
287
288
    /**
289
     * Read the "fact" chunk of a wave file.
290
     * @param {Uint8Array} bytes an array representing the wave file.
291
     * @throws {Error} If no "fact" chunk is found.
292
     */
293
    readFactChunk_(bytes) {
294
        let start = byteData.findString(bytes, "fact");
295
        if (start === -1 && this.enforceFact) {
296
            throw Error(this.WaveErrors.fact);
297
        }else if (start > -1) {
298
            this.factChunkId = "fact";
299
            //this.factChunkSize = byteData.uIntFrom4Bytes(
300
            //    bytes.slice(start + 4, start + 8));
301
            //this.dwSampleLength = byteData.uIntFrom4Bytes(
302
            //    bytes.slice(start + 8, start + 12));
303
        }
304
    }
305
306
    /**
307
     * Read the "bext" chunk of a wave file.
308
     * @param {Uint8Array} bytes an array representing the wave file.
309
     * @throws {Error} If no "bext" chunk is found.
310
     */
311
    readBextChunk_(bytes) {
312
        let start = byteData.findString(bytes, "bext");
313
        if (start === -1 && this.enforceBext) {
314
            throw Error(this.WaveErrors.bext);
315
        }else if (start > -1){
316
            this.bextChunkId = "bext";
317
        }
318
    }
319
320
    /**
321
     * Read the "data" chunk of a wave file.
322
     * @param {Uint8Array} bytes an array representing the wave file.
323
     * @throws {Error} If no "data" chunk is found.
324
     */
325
    readDataChunk_(bytes) {
326
        let start = byteData.findString(bytes, "data");
327
        if (start === -1) {
328
            throw Error(this.WaveErrors.data);
329
        }
330
        this.subChunk2Id = "data";
331
        this.subChunk2Size = byteData.fromBytes(
332
            bytes.slice(start + 4, start + 8),
333
            32,
334
            {"be": this.chunkId == "RIFX"})[0];
335
        this.samplesFromBytes_(bytes, start);
336
    }
337
338
    /**
339
     * Find and return the start offset of the data chunk on a wave file.
340
     * @param {Uint8Array} bytes Array of bytes representing the wave file.
341
     * @param {number} start The offset to start reading.
342
     */
343
    samplesFromBytes_(bytes, start) {
344
        let params = {
345
            "signed": this.bitsPerSample == 8 ? false : true,
346
            "be": this.chunkId == "RIFX"
347
        };
348
        if (this.bitsPerSample == 32 && this.audioFormat == 3) {
349
            params.float = true;
350
        }
351
        let samples = bytes.slice(start + 8, start + 8 + this.subChunk2Size);
352
        if (this.bitsPerSample == 4) {
353
            this.samples_ = byteData.fromBytes(samples, 8, params);
354
        } else {
355
            this.samples_ = byteData.fromBytes(samples, this.bitsPerSample, params);
356
        }
357
    }
358
359
    /**
360
     * Validate the input for wav writing.
361
     * @param {number} numChannels The number of channels
362
     *     Should be a int greater than zero smaller than the
363
     *     channel limit according to the bit depth.
364
     * @param {number} sampleRate The sample rate.
365
     *     Should be a int greater than zero smaller than the
366
     *     channel limit according to the bit depth and number of channels.
367
     * @param {string} bitDepth The audio bit depth.
368
     *     Should be one of "8", "16", "24", "32", "32f", "64".
369
     * @throws {Error} If any argument does not meet the criteria.
370
     */
371
    checkWriteInput_() {
372
        this.validateBitDepth_();
373
        this.validateNumChannels_();
374
        this.validateSampleRate_();
375
    }
376
377
    /**
378
     * Validate the bit depth.
379
     * @param {number} numChannels The number of channels
380
     * @param {string} bitDepth The audio bit depth.
381
     *     Should be one of "8", "16", "24", "32", "32f", "64".
382
     * @throws {Error} If any argument does not meet the criteria.
383
     */
384
    validateBitDepth_() {
385
        if (!this.headerFormats_[this.bitDepth_]) {
386
            throw new Error(this.WaveErrors.bitDepth);
387
        }
388
        return true;
389
    }
390
391
    /**
392
     * Validate the sample rate value.
393
     * @param {number} numChannels The number of channels
0 ignored issues
show
The parameter numChannels does not exist. Did you maybe forget to remove this comment?
Loading history...
394
     * @param {string} bitDepth The audio bit depth.
0 ignored issues
show
The parameter bitDepth does not exist. Did you maybe forget to remove this comment?
Loading history...
395
     *     Should be one of "8", "16", "24", "32", "32f", "64".
396
     * @throws {Error} If any argument does not meet the criteria.
397
     */
398
    validateNumChannels_() {
399
        let blockAlign = this.numChannels * this.bitsPerSample / 8;
400
        if (this.numChannels < 1 || blockAlign > 65535) {
401
            throw new Error(this.WaveErrors.numChannels);
402
        }
403
        return true;
404
    }
405
406
    /**
407
     * Validate the sample rate value.
408
     * @param {number} numChannels The number of channels
409
     *     Should be a int greater than zero smaller than the
410
     *     channel limit according to the bit depth.
411
     * @param {number} sampleRate The sample rate.
412
     * @param {string} bitDepth The audio bit depth.
413
     *     Should be one of "8", "16", "24", "32", "32f", "64".
414
     * @throws {Error} If any argument does not meet the criteria.
415
     */
416
    validateSampleRate_() {
417
        let byteRate = this.numChannels *
418
            (this.bitsPerSample / 8) * this.sampleRate;
419
        if (this.sampleRate < 1 || byteRate > 4294967295) {
420
            throw new Error(this.WaveErrors.sampleRate);
421
        }
422
        return true;
423
    }
424
425
    /**
426
     * Split each sample into bytes.
427
     */
428
    samplesToBytes_() {
429
        let params = {"be": this.chunkId == "RIFX"};
430
        if (this.bitsPerSample == 32 && this.audioFormat == 3) {
431
            params.float = true;
432
        }
433
        let bitDepth = this.bitsPerSample == 4 ? 8 : this.bitsPerSample;
434
        this.bytes_ = byteData.toBytes(this.samples_, bitDepth, params);
435
        if (this.bytes_.length % 2) {
436
            this.bytes_.push(0);
437
        }
438
    }
439
440
    /**
441
     * Turn a WaveFile object into a file.
442
     * @return {Uint8Array} The wav file bytes.
443
     */
444
    createWaveFile_() {
445
        let factVal = [];
446
        if (this.factChunkId) {
447
            factVal = byteData.toBytes(this.factChunkId, 8, {"char": true});
448
        }
449
        let options = {"be": this.chunkId == "RIFX"};
450
        return byteData.toBytes(this.chunkId, 8, {"char": true}).concat(
451
            byteData.toBytes([this.chunkSize], 32, options),
452
            byteData.toBytes(this.format, 8, {"char": true}), 
453
            byteData.toBytes(this.subChunk1Id, 8, {"char": true}),
454
            byteData.toBytes([this.subChunk1Size], 32, options),
455
            byteData.toBytes([this.audioFormat], 16, options),
456
            byteData.toBytes([this.numChannels], 16, options),
457
            byteData.toBytes([this.sampleRate], 32, options),
458
            byteData.toBytes([this.byteRate], 32, options),
459
            byteData.toBytes([this.blockAlign], 16, options),
460
            byteData.toBytes([this.bitsPerSample], 16, options),
461
            factVal,
462
            byteData.toBytes(this.subChunk2Id, 8, {"char": true}),
463
            byteData.toBytes([this.subChunk2Size], 32, options),
464
            this.bytes_);
465
    }
466
}
467
468
module.exports.WaveFile = WaveFile;
469