Completed
Branch master (9c6b57)
by Rafael S.
01:32
created

index.js (8 issues)

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