Passed
Push — master ( dc45b4...afe78d )
by Rafael S.
01:28
created

index.js (8 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
     */
116
    toRIFF() {
117
        this.chunkId = "RIFF";
118
    }
119
120
    /**
121
     * Turn the file to RIFX.
122
     */
123
    toRIFX() {
124
        this.chunkId = "RIFX";
125
    }
126
127
    /**
128
     * Interleave multi-channel samples.
129
     */
130
    interleave() {
131
        let finalSamples = [];
132
        let i;
133
        let j;
134
        let numChannels = this.samples_[0].length;
135
        for (i = 0; i < numChannels; i++) {
136
            for (j = 0; j < this.samples_.length; j++) {
137
                finalSamples.push(this.samples_[j][i]);
138
            }
139
        }
140
        this.samples_ = finalSamples;
141
    }
142
143
    /**
144
     * De-interleave samples into multiple channels.
145
     */
146
    deInterleave() {
147
        let finalSamples = [];
148
        let i;
149
        for (i = 0; i < this.numChannels; i++) {
150
            finalSamples[i] = [];
151
        }
152
        i = 0;
153
        let j;
154
        while (i < this.samples_.length) {
155
            for (j = 0; j < this.numChannels; j++) {
156
                finalSamples[j].push(this.samples_[i+j]);
157
            }
158
            i += j;
159
        }
160
        this.samples_ = finalSamples;
161
    }
162
163
    /**
164
     * Read the RIFF chunk a wave file.
165
     * @param {Uint8Array} bytes an array representing the wave file.
166
     * @throws {Error} If no "RIFF" chunk is found.
167
     */
168
    readRIFFChunk_(bytes) {
169
        this.chunkId = byteData.fromBytes(bytes.slice(0, 4),
170
            8, {"char": true});
171
        if (this.chunkId != "RIFF" && this.chunkId != "RIFX") {
172
            throw Error(this.WaveErrors.format);
173
        }
174
        this.chunkSize = byteData.fromBytes(
175
            bytes.slice(4, 8), 32, {"be": this.chunkId == "RIFX"})[0];
176
    }
177
178
    /**
179
     * Read the WAVE chunk of a wave file.
180
     * @param {Uint8Array} bytes an array representing the wave file.
181
     * @throws {Error} If no "WAVE" chunk is found.
182
     */
183
    readWAVEChunk_(bytes) {
184
        let start = byteData.findString(bytes, "WAVE");
185
        if (start === -1) {
186
            throw Error(this.WaveErrors.wave);
187
        }
188
        this.format = "WAVE";
189
    }
190
191
    /**
192
     * Read the "fmt " chunk of a wave file.
193
     * @param {Uint8Array} bytes an array representing the wave file.
194
     * @throws {Error} If no "fmt " chunk is found.
195
     */
196
    readFmtChunk_(bytes) {
197
        let start = byteData.findString(bytes, "fmt ");
198
        if (start === -1) {
199
            throw Error(this.WaveErrors["fmt "]);
200
        }
201
        let options = {"be": this.chunkId == "RIFX"};
202
        this.subChunk1Id = "fmt ";
203
        this.subChunk1Size = byteData.fromBytes(
204
            bytes.slice(start + 4, start + 8), 32, options)[0];
205
        this.audioFormat = byteData.fromBytes(
206
            bytes.slice(start + 8, start + 10), 16, options)[0];
207
        this.numChannels = byteData.fromBytes(
208
            bytes.slice(start + 10, start + 12), 16, options)[0];
209
        this.sampleRate = byteData.fromBytes(
210
            bytes.slice(start + 12, start + 16), 32, options)[0];
211
        this.byteRate = byteData.fromBytes(
212
            bytes.slice(start + 16, start + 20), 32, options)[0];
213
        this.blockAlign = byteData.fromBytes(
214
            bytes.slice(start + 20, start + 22), 16, options)[0];
215
        this.bitsPerSample = byteData.fromBytes(
216
            bytes.slice(start + 22, start + 24), 16, options)[0];
217
        if (this.audioFormat == 3 && this.bitsPerSample == 32) {
218
            this.bitDepth_ = "32f";
219
        }else {
220
            this.bitDepth_ = this.bitsPerSample.toString();
221
        }
222
    }
223
224
    /**
225
     * Read the "fact" chunk of a wave file.
226
     * @param {Uint8Array} bytes an array representing the wave file.
227
     * @throws {Error} If no "fact" chunk is found.
228
     */
229
    readFactChunk_(bytes) {
230
        let start = byteData.findString(bytes, "fact");
231
        if (start === -1 && this.enforceFact) {
232
            throw Error(this.WaveErrors.fact);
233
        }else if (start > -1) {
234
            this.factChunkId = "fact";
235
            //this.factChunkSize = byteData.uIntFrom4Bytes(
236
            //    bytes.slice(start + 4, start + 8));
237
            //this.dwSampleLength = byteData.uIntFrom4Bytes(
238
            //    bytes.slice(start + 8, start + 12));
239
        }
240
    }
241
242
    /**
243
     * Read the "bext" chunk of a wave file.
244
     * @param {Uint8Array} bytes an array representing the wave file.
245
     * @throws {Error} If no "bext" chunk is found.
246
     */
247
    readBextChunk_(bytes) {
248
        let start = byteData.findString(bytes, "bext");
249
        if (start === -1 && this.enforceBext) {
250
            throw Error(this.WaveErrors.bext);
251
        }else if (start > -1){
252
            this.bextChunkId = "bext";
253
        }
254
    }
255
256
    /**
257
     * Read the "data" chunk of a wave file.
258
     * @param {Uint8Array} bytes an array representing the wave file.
259
     * @throws {Error} If no "data" chunk is found.
260
     */
261
    readDataChunk_(bytes) {
262
        let start = byteData.findString(bytes, "data");
263
        if (start === -1) {
264
            throw Error(this.WaveErrors.data);
265
        }
266
        this.subChunk2Id = "data";
267
        this.subChunk2Size = byteData.fromBytes(
268
            bytes.slice(start + 4, start + 8),
269
            32,
270
            {"be": this.chunkId == "RIFX"})[0];
271
        this.samplesFromBytes_(bytes, start);
272
    }
273
274
    /**
275
     * Find and return the start offset of the data chunk on a wave file.
276
     * @param {Uint8Array} bytes Array of bytes representing the wave file.
277
     * @param {number} start The offset to start reading.
278
     */
279
    samplesFromBytes_(bytes, start) {
280
        let params = {
281
            "signed": this.bitsPerSample == 8 ? false : true,
282
            "be": this.chunkId == "RIFX"
283
        };
284
        if (this.bitsPerSample == 32 && this.audioFormat == 3) {
285
            params.float = true;
286
        }
287
        let samples = bytes.slice(start + 8, start + 8 + this.subChunk2Size);
288
        if (this.bitsPerSample == 4) {
289
            this.samples_ = byteData.fromBytes(samples, 8, params);
290
        } else {
291
            this.samples_ = byteData.fromBytes(samples, this.bitsPerSample, params);
292
        }
293
    }
294
295
    /**
296
     * Validate the input for wav writing.
297
     * @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...
298
     *     Should be a int greater than zero smaller than the
299
     *     channel limit according to the bit depth.
300
     * @param {number} sampleRate The sample rate.
0 ignored issues
show
The parameter sampleRate does not exist. Did you maybe forget to remove this comment?
Loading history...
301
     *     Should be a int greater than zero smaller than the
302
     *     channel limit according to the bit depth and number of channels.
303
     * @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...
304
     *     Should be one of "8", "16", "24", "32", "32f", "64".
305
     * @throws {Error} If any argument does not meet the criteria.
306
     */
307
    checkWriteInput_() {
308
        if (!this.headerFormats_[this.bitDepth_]) {
309
            throw new Error(this.WaveErrors.bitDepth);
310
        }
311
        this.validateNumChannels_();
312
        this.validateSampleRate_();
313
    }
314
315
    /**
316
     * Validate the sample rate value.
317
     * @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...
318
     * @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...
319
     *     Should be one of "8", "16", "24", "32", "32f", "64".
320
     * @throws {Error} If any argument does not meet the criteria.
321
     */
322
    validateNumChannels_() {
323
        let blockAlign = this.numChannels * this.bitsPerSample / 8;
324
        if (this.numChannels < 1 || blockAlign > 65535) {
325
            throw new Error(this.WaveErrors.numChannels);
326
        }
327
        return true;
328
    }
329
330
    /**
331
     * Validate the sample rate value.
332
     * @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...
333
     *     Should be a int greater than zero smaller than the
334
     *     channel limit according to the bit depth.
335
     * @param {number} sampleRate The sample rate.
0 ignored issues
show
The parameter sampleRate does not exist. Did you maybe forget to remove this comment?
Loading history...
336
     * @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...
337
     *     Should be one of "8", "16", "24", "32", "32f", "64".
338
     * @throws {Error} If any argument does not meet the criteria.
339
     */
340
    validateSampleRate_() {
341
        let byteRate = this.numChannels *
342
            (this.bitsPerSample / 8) * this.sampleRate;
343
        if (this.sampleRate < 1 || byteRate > 4294967295) {
344
            throw new Error(this.WaveErrors.sampleRate);
345
        }
346
        return true;
347
    }
348
349
    /**
350
     * Split each sample into bytes.
351
     */
352
    samplesToBytes_() {
353
        let params = {"be": this.chunkId == "RIFX"};
354
        if (this.bitsPerSample == 32 && this.audioFormat == 3) {
355
            params.float = true;
356
        }
357
        let bitDepth = this.bitsPerSample == 4 ? 8 : this.bitsPerSample;
358
        this.bytes_ = byteData.toBytes(this.samples_, bitDepth, params);
359
        if (this.bytes_.length % 2) {
360
            this.bytes_.push(0);
361
        }
362
    }
363
364
    /**
365
     * Turn a WaveFile object into a file.
366
     * @return {Uint8Array} The wav file bytes.
367
     */
368
    createWaveFile_() {
369
        let factVal = [];
370
        if (this.factChunkId) {
371
            factVal = byteData.toBytes(this.factChunkId, 8, {"char": true});
372
        }
373
        let options = {"be": this.chunkId == "RIFX"};
374
        return byteData.toBytes(this.chunkId, 8, {"char": true}).concat(
375
            byteData.toBytes([this.chunkSize], 32, options),
376
            byteData.toBytes(this.format, 8, {"char": true}), 
377
            byteData.toBytes(this.subChunk1Id, 8, {"char": true}),
378
            byteData.toBytes([this.subChunk1Size], 32, options),
379
            byteData.toBytes([this.audioFormat], 16, options),
380
            byteData.toBytes([this.numChannels], 16, options),
381
            byteData.toBytes([this.sampleRate], 32, options),
382
            byteData.toBytes([this.byteRate], 32, options),
383
            byteData.toBytes([this.blockAlign], 16, options),
384
            byteData.toBytes([this.bitsPerSample], 16, options),
385
            factVal,
386
            byteData.toBytes(this.subChunk2Id, 8, {"char": true}),
387
            byteData.toBytes([this.subChunk2Size], 32, options),
388
            this.bytes_);
389
    }
390
}
391
392
module.exports.WaveFile = WaveFile;
393