Passed
Push — master ( e4164e...2222ad )
by Rafael S.
01:19
created

src/wavefile-reader-writer.js   A

Size

Lines of Code 356

Duplication

Duplicated Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
nc 1
dl 0
loc 356
rs 10
noi 11

1 Function

Rating   Name   Duplication   Size   Complexity  
B wavefile-reader-writer.js ➔ ??? 0 39 1
1
/*!
2
 * WaveFile
3
 * Copyright (c) 2017 Rafael da Silva Rocha. MIT License.
4
 * https://github.com/rochars/wavefile
5
 *
6
 */
7
8
const byteData = require("byte-data");
9
const waveFileHeader = require("../src/wavefile-header");
10
11
/**
12
 * A wave file.
13
 */
14
class WaveFileReaderWriter extends waveFileHeader.WaveFileHeader {
15
16
    /**
17
     * @param {Uint8Array} bytes The file bytes.
0 ignored issues
show
Documentation introduced by
The parameter bytes does not exist. Did you maybe forget to remove this comment?
Loading history...
18
     * @param {boolean} enforceFact True if it should throw a error
19
     *      if no "fact" chunk is found.
20
     * @param {boolean} enforceBext True if it should throw a error
21
     *      if no "bext" chunk is found.
22
     */
23
    constructor(enforceFact=false, enforceBext=false) {
24
        super();
25
        /** @type {boolean} */
26
        this.isFromScratch_ = false;
27
        /** @type {boolean} */
28
        this.enforceFact = enforceFact;
29
        /** @type {boolean} */
30
        this.enforceBext = enforceBext;
31
        /**
32
         * Error messages.
33
         * @enum {string}
34
         */
35
        this.WaveErrors = {
36
            "format": "Not a supported format.",
37
            "wave": "Could not find the 'WAVE' chunk",
38
            "fmt ": "Could not find the 'fmt ' chunk",
39
            "data": "Could not find the 'data' chunk",
40
            "fact": "Could not find the 'fact' chunk",
41
            "bext": "Could not find the 'bext' chunk",
42
            "bitDepth": "Invalid bit depth.",
43
            "numChannels": "Invalid number of channels.",
44
            "sampleRate": "Invalid sample rate."
45
        };
46
        /**
47
         * Header formats.
48
         * @enum {number}
49
         */
50
        this.headerFormats_ = {
51
            "4": 17,
52
            "8": 1,
53
            "16": 1,
54
            "24": 1,
55
            "32": 1,
56
            "32f": 3,
57
            "64": 3
58
        };
59
        this.samples_ = [];
60
        this.bytes_ = [];
61
    }
62
63
    /**
64
     * Create a WaveFile object based on the arguments passed.
65
     * @param {number} numChannels The number of channels
66
     *     (Ints like 1 for mono, 2 stereo and so on).
67
     * @param {number} sampleRate The sample rate.
68
     *     Integer numbers like 8000, 44100, 48000, 96000, 192000.
69
     * @param {string} bitDepth The audio bit depth.
70
     *     One of "8", "16", "24", "32", "32f", "64".
71
     * @param {!Array<number>} samples Array of samples to be written.
72
     *     Samples must be in the correct range according to the bit depth.
73
     *     Samples of multi-channel data .
74
     */
75
    fromScratch(numChannels, sampleRate, bitDepth, samples, options={}) {
76
        if (!options.container) {
77
            options.container = "RIFF";
78
        }
79
        this.isFromScratch_ = true;
80
        let bytes = parseInt(bitDepth, 10) / 8;
81
        this.chunkSize = 36 + samples.length * bytes;
82
        this.subChunk1Size = 16;
83
        this.byteRate = (numChannels * bytes) * sampleRate;
84
        this.blockAlign = numChannels * bytes;
85
        this.chunkId = options.container;
86
        this.format = "WAVE";
87
        this.subChunk1Id = "fmt ";
88
        this.audioFormat = this.headerFormats_[bitDepth];
89
        this.numChannels = numChannels;
90
        this.sampleRate = sampleRate;
91
        this.bitsPerSample = parseInt(bitDepth, 10);
92
        this.subChunk2Id = "data";
93
        this.subChunk2Size = samples.length * bytes;
94
        this.samples_ = samples;
95
        this.bitDepth_ = bitDepth;
96
    }
97
98
    /**
99
     * Read a wave file from a byte buffer.
100
     * @param {Uint8Array} bytes The buffer.
101
     */
102
    fromBuffer(bytes) {
103
        this.isFromScratch_ = false;
104
        this.readRIFFChunk_(bytes);
105
        this.readWAVEChunk_(bytes);
106
        this.readFmtChunk_(bytes);
107
        this.readFactChunk_(bytes);
108
        this.readBextChunk_(bytes);
109
        this.readDataChunk_(bytes);
110
    }
111
112
    /**
113
     * Turn the WaveFile object into a byte buffer.
114
     * @return {Uint8Array}
115
     */
116
    toBuffer() {
117
        this.checkWriteInput_(this.numChannels, this.sampleRate, this.bitDepth_);
118
        this.samplesToBytes_();
119
        return new Uint8Array(this.createWaveFile_());
120
    }
121
    
122
    /**
123
     * Read the RIFF chunk a wave file.
124
     * @param {Uint8Array} bytes an array representing the wave file.
125
     * @throws {Error} If no "RIFF" chunk is found.
126
     */
127
    readRIFFChunk_(bytes) {
128
        this.chunkId = byteData.fromBytes(bytes.slice(0, 4),
129
            8, {"char": true});
130
        if (this.chunkId != "RIFF" && this.chunkId != "RIFX") {
131
            throw Error(this.WaveErrors.format);
132
        }
133
        this.chunkSize = byteData.fromBytes(
134
            bytes.slice(4, 8), 32, {"be": this.chunkId == "RIFX"})[0];
135
    }
136
137
    /**
138
     * Read the WAVE chunk of a wave file.
139
     * @param {Uint8Array} bytes an array representing the wave file.
140
     * @throws {Error} If no "WAVE" chunk is found.
141
     */
142
    readWAVEChunk_(bytes) {
143
        let start = byteData.findString(bytes, "WAVE");
144
        if (start === -1) {
145
            throw Error(this.WaveErrors.wave);
146
        }
147
        this.format = "WAVE";
148
    }
149
150
    /**
151
     * Read the "fmt " chunk of a wave file.
152
     * @param {Uint8Array} bytes an array representing the wave file.
153
     * @throws {Error} If no "fmt " chunk is found.
154
     */
155
    readFmtChunk_(bytes) {
156
        let start = byteData.findString(bytes, "fmt ");
157
        if (start === -1) {
158
            throw Error(this.WaveErrors["fmt "]);
159
        }
160
        let options = {"be": this.chunkId == "RIFX"};
161
        this.subChunk1Id = "fmt ";
162
        this.subChunk1Size = byteData.fromBytes(
163
            bytes.slice(start + 4, start + 8), 32, options)[0];
164
        this.audioFormat = byteData.fromBytes(
165
            bytes.slice(start + 8, start + 10), 16, options)[0];
166
        this.numChannels = byteData.fromBytes(
167
            bytes.slice(start + 10, start + 12), 16, options)[0];
168
        this.sampleRate = byteData.fromBytes(
169
            bytes.slice(start + 12, start + 16), 32, options)[0];
170
        this.byteRate = byteData.fromBytes(
171
            bytes.slice(start + 16, start + 20), 32, options)[0];
172
        this.blockAlign = byteData.fromBytes(
173
            bytes.slice(start + 20, start + 22), 16, options)[0];
174
        this.bitsPerSample = byteData.fromBytes(
175
            bytes.slice(start + 22, start + 24), 16, options)[0];
176
        if (this.audioFormat == 3 && this.bitsPerSample == 32) {
177
            this.bitDepth_ = "32f";
178
        }else {
179
            this.bitDepth_ = this.bitsPerSample.toString();
180
        }
181
    }
182
183
    /**
184
     * Read the "fact" chunk of a wave file.
185
     * @param {Uint8Array} bytes an array representing the wave file.
186
     * @throws {Error} If no "fact" chunk is found.
187
     */
188
    readFactChunk_(bytes) {
189
        let start = byteData.findString(bytes, "fact");
190
        if (start === -1 && this.enforceFact) {
191
            throw Error(this.WaveErrors.fact);
192
        }else if (start > -1) {
193
            this.factChunkId = "fact";
194
            //this.factChunkSize = byteData.uIntFrom4Bytes(
195
            //    bytes.slice(start + 4, start + 8));
196
            //this.dwSampleLength = byteData.uIntFrom4Bytes(
197
            //    bytes.slice(start + 8, start + 12));
198
        }
199
    }
200
201
    /**
202
     * Read the "bext" chunk of a wave file.
203
     * @param {Uint8Array} bytes an array representing the wave file.
204
     * @throws {Error} If no "bext" chunk is found.
205
     */
206
    readBextChunk_(bytes) {
207
        let start = byteData.findString(bytes, "bext");
208
        if (start === -1 && this.enforceBext) {
209
            throw Error(this.WaveErrors.bext);
210
        }else if (start > -1){
211
            this.bextChunkId = "bext";
212
        }
213
    }
214
215
    /**
216
     * Read the "data" chunk of a wave file.
217
     * @param {Uint8Array} bytes an array representing the wave file.
218
     * @throws {Error} If no "data" chunk is found.
219
     */
220
    readDataChunk_(bytes) {
221
        let start = byteData.findString(bytes, "data");
222
        if (start === -1) {
223
            throw Error(this.WaveErrors.data);
224
        }
225
        this.subChunk2Id = "data";
226
        this.subChunk2Size = byteData.fromBytes(
227
            bytes.slice(start + 4, start + 8),
228
            32,
229
            {"be": this.chunkId == "RIFX"})[0];
230
        this.samplesFromBytes_(bytes, start);
231
    }
232
233
    /**
234
     * Find and return the start offset of the data chunk on a wave file.
235
     * @param {Uint8Array} bytes Array of bytes representing the wave file.
236
     * @param {number} start The offset to start reading.
237
     */
238
    samplesFromBytes_(bytes, start) {
239
        let params = {
240
            "signed": this.bitsPerSample == 8 ? false : true,
241
            "be": this.chunkId == "RIFX"
242
        };
243
        if (this.bitsPerSample == 32 && this.audioFormat == 3) {
244
            params.float = true;
245
        }
246
        let samples = bytes.slice(start + 8, start + 8 + this.subChunk2Size);
247
        if (this.bitsPerSample == 4) {
248
            this.samples_ = byteData.fromBytes(samples, 8, params);
249
        } else {
250
            this.samples_ = byteData.fromBytes(samples, this.bitsPerSample, params);
251
        }
252
    }
253
254
    /**
255
     * Validate the input for wav writing.
256
     * @param {number} numChannels The number of channels
0 ignored issues
show
Documentation introduced by
The parameter numChannels does not exist. Did you maybe forget to remove this comment?
Loading history...
257
     *     Should be a int greater than zero smaller than the
258
     *     channel limit according to the bit depth.
259
     * @param {number} sampleRate The sample rate.
0 ignored issues
show
Documentation introduced by
The parameter sampleRate does not exist. Did you maybe forget to remove this comment?
Loading history...
260
     *     Should be a int greater than zero smaller than the
261
     *     channel limit according to the bit depth and number of channels.
262
     * @param {string} bitDepth The audio bit depth.
0 ignored issues
show
Documentation introduced by
The parameter bitDepth does not exist. Did you maybe forget to remove this comment?
Loading history...
263
     *     Should be one of "8", "16", "24", "32", "32f", "64".
264
     * @throws {Error} If any argument does not meet the criteria.
265
     */
266
    checkWriteInput_() {
267
        this.validateBitDepth_();
268
        this.validateNumChannels_();
269
        this.validateSampleRate_();
270
    }
271
272
    /**
273
     * Validate the bit depth.
274
     * @param {number} numChannels The number of channels
0 ignored issues
show
Documentation introduced by
The parameter numChannels does not exist. Did you maybe forget to remove this comment?
Loading history...
275
     * @param {string} bitDepth The audio bit depth.
0 ignored issues
show
Documentation introduced by
The parameter bitDepth does not exist. Did you maybe forget to remove this comment?
Loading history...
276
     *     Should be one of "8", "16", "24", "32", "32f", "64".
277
     * @throws {Error} If any argument does not meet the criteria.
278
     */
279
    validateBitDepth_() {
280
        if (!this.headerFormats_[this.bitDepth_]) {
281
            throw new Error(this.WaveErrors.bitDepth);
282
        }
283
        return true;
284
    }
285
286
    /**
287
     * Validate the sample rate value.
288
     * @param {number} numChannels The number of channels
0 ignored issues
show
Documentation introduced by
The parameter numChannels does not exist. Did you maybe forget to remove this comment?
Loading history...
289
     * @param {string} bitDepth The audio bit depth.
0 ignored issues
show
Documentation introduced by
The parameter bitDepth does not exist. Did you maybe forget to remove this comment?
Loading history...
290
     *     Should be one of "8", "16", "24", "32", "32f", "64".
291
     * @throws {Error} If any argument does not meet the criteria.
292
     */
293
    validateNumChannels_() {
294
        let blockAlign = this.numChannels * this.bitsPerSample / 8;
295
        if (this.numChannels < 1 || blockAlign > 65535) {
296
            throw new Error(this.WaveErrors.numChannels);
297
        }
298
        return true;
299
    }
300
301
    /**
302
     * Validate the sample rate value.
303
     * @param {number} numChannels The number of channels
0 ignored issues
show
Documentation introduced by
The parameter numChannels does not exist. Did you maybe forget to remove this comment?
Loading history...
304
     *     Should be a int greater than zero smaller than the
305
     *     channel limit according to the bit depth.
306
     * @param {number} sampleRate The sample rate.
0 ignored issues
show
Documentation introduced by
The parameter sampleRate does not exist. Did you maybe forget to remove this comment?
Loading history...
307
     * @param {string} bitDepth The audio bit depth.
0 ignored issues
show
Documentation introduced by
The parameter bitDepth does not exist. Did you maybe forget to remove this comment?
Loading history...
308
     *     Should be one of "8", "16", "24", "32", "32f", "64".
309
     * @throws {Error} If any argument does not meet the criteria.
310
     */
311
    validateSampleRate_() {
312
        let byteRate = this.numChannels *
313
            (this.bitsPerSample / 8) * this.sampleRate;
314
        if (this.sampleRate < 1 || byteRate > 4294967295) {
315
            throw new Error(this.WaveErrors.sampleRate);
316
        }
317
        return true;
318
    }
319
320
    /**
321
     * Split each sample into bytes.
322
     */
323
    samplesToBytes_() {
324
        let params = {"be": this.chunkId == "RIFX"};
325
        if (this.bitsPerSample == 32 && this.audioFormat == 3) {
326
            params.float = true;
327
        }
328
        let bitDepth = this.bitsPerSample == 4 ? 8 : this.bitsPerSample;
329
        this.bytes_ = byteData.toBytes(this.samples_, bitDepth, params);
330
        if (this.bytes_.length % 2) {
331
            this.bytes_.push(0);
332
        }
333
    }
334
335
    /**
336
     * Turn a WaveFile object into a file.
337
     * @return {Uint8Array} The wav file bytes.
338
     */
339
    createWaveFile_() {
340
        let factVal = [];
341
        if (this.factChunkId) {
342
            factVal = byteData.toBytes(this.factChunkId, 8, {"char": true});
343
        }
344
        let options = {"be": this.chunkId == "RIFX"};
345
        return byteData.toBytes(this.chunkId, 8, {"char": true}).concat(
346
            byteData.toBytes([this.chunkSize], 32, options),
347
            byteData.toBytes(this.format, 8, {"char": true}), 
348
            byteData.toBytes(this.subChunk1Id, 8, {"char": true}),
349
            byteData.toBytes([this.subChunk1Size], 32, options),
350
            byteData.toBytes([this.audioFormat], 16, options),
351
            byteData.toBytes([this.numChannels], 16, options),
352
            byteData.toBytes([this.sampleRate], 32, options),
353
            byteData.toBytes([this.byteRate], 32, options),
354
            byteData.toBytes([this.blockAlign], 16, options),
355
            byteData.toBytes([this.bitsPerSample], 16, options),
356
            factVal,
357
            byteData.toBytes(this.subChunk2Id, 8, {"char": true}),
358
            byteData.toBytes([this.subChunk2Size], 32, options),
359
            this.bytes_);
360
    }
361
}
362
363
module.exports.WaveFileReaderWriter = WaveFileReaderWriter;
364