Passed
Push — master ( 8d3d43...11cdea )
by Rafael S.
01:29
created

wavefile-reader-writer.js ➔ ???   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 18

Duplication

Lines 0
Ratio 0 %

Importance

Changes 79
Bugs 1 Features 0
Metric Value
cc 1
c 79
b 1
f 0
nc 1
dl 0
loc 18
rs 9.4285
nop 0
1
/*
2
 * WaveFileReaderWriter
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 WaveErrors = require("../src/wave-errors");
10
const uInt8 = byteData.uInt8;
11
const uInt16 = byteData.uInt16;
12
const uInt32 = byteData.uInt32;
13
const char = byteData.char;
14
let WaveFileHeader = require("../src/wavefile-header");
15
16
/**
17
 * Read and write wave files.
18
 */
19
class WaveFileReaderWriter extends WaveFileHeader {
20
21
    constructor() {
22
        super();
23
        /**
24
         * Header formats.
25
         * @enum {number}
26
         */
27
        this.headerFormats_ = {
28
            "4": 17,
29
            "8": 1,
30
            "16": 1,
31
            "24": 1,
32
            "32": 1,
33
            "32f": 3,
34
            "64": 3
35
        };
36
        /** @type {!Array<number>} */
37
        this.samples_ = [];
38
    }
39
40
    /**
41
     * Read the RIFF chunk a wave file.
42
     * @param {Uint8Array} bytes an array representing the wave file.
43
     * @throws {Error} If no "RIFF" chunk is found.
44
     */
45
    readRIFFChunk_(bytes) {
46
        this.chunkId = byteData.unpackSequence(bytes.slice(0, 4), char);
47
        if (this.chunkId != "RIFF" && this.chunkId != "RIFX") {
48
            throw Error(WaveErrors.format);
49
        }
50
        let bigEndian = this.LEorBE();
51
        this.chunkSize = byteData.fromBytes(
52
            bytes.slice(4, 8),
53
            32,
54
            {"be": bigEndian, "single": true});
55
        this.format = byteData.unpackSequence(bytes.slice(8, 12), char);
56
        if (this.format != "WAVE") {
57
            throw Error(WaveErrors.wave);
58
        }
59
    }
60
61
    LEorBE() {
62
        let bigEndian = this.chunkId == "RIFX";
63
        uInt8.be = bigEndian;
64
        uInt16.be = bigEndian;
65
        uInt32.be = bigEndian;
66
        return bigEndian;
67
    }
68
69
    /**
70
     * Read the "fmt " chunk of a wave file.
71
     * @param {Object} chunks The RIFF file chunks.
72
     * @throws {Error} If no "fmt " chunk is found.
73
     */
74
    readFmtChunk_(chunks) {
75
        let chunk = this.findChunk(chunks, "fmt ");
76
        if (chunk) {
77
            this.fmtChunkId = "fmt ";
78
            this.fmtChunkSize = chunk.chunkSize;
79
            this.audioFormat = byteData.unpack(
80
                chunk.chunkData.slice(0, 2), uInt16);
81
            this.numChannels = byteData.unpack(
82
                chunk.chunkData.slice(2, 4), uInt16);
83
            this.sampleRate = byteData.unpack(
84
                chunk.chunkData.slice(4, 8), uInt32);
85
            this.byteRate = byteData.unpack(
86
                chunk.chunkData.slice(8, 12), uInt32);
87
            this.blockAlign = byteData.unpack(
88
                chunk.chunkData.slice(12, 14), uInt16);
89
            this.bitsPerSample = byteData.unpack(
90
                    chunk.chunkData.slice(14, 16), uInt16);
91
            this.readFmtExtension(chunk);
92
        } else {
93
            throw Error(WaveErrors["fmt "]);
94
        }
95
    }
96
97
    /**
98
     * Read the "fmt " chunk extension.
99
     * @param {Object} chunk The "fmt " chunk.
100
     */
101
    readFmtExtension(chunk) {
102
        if (this.fmtChunkSize > 16) {
103
            this.cbSize = byteData.unpack(
104
                chunk.chunkData.slice(16, 18), uInt16);
105
            if (this.fmtChunkSize > 18) {
106
                this.validBitsPerSample = byteData.unpack(
107
                    chunk.chunkData.slice(18, 20), uInt16);
108
            }
109
        }
110
    }
111
112
    /**
113
     * Read the "fact" chunk of a wave file.
114
     * @param {Object} chunks The RIFF file chunks.
115
     * @throws {Error} If no "fact" chunk is found.
116
     */
117
    readFactChunk_(chunks) {
118
        let chunk = this.findChunk(chunks, "fact");
119
        if (chunk) {
120
            this.factChunkId = "fact";
121
            this.factChunkSize = chunk.chunkSize;
122
            this.dwSampleLength = byteData.unpack(
123
                chunk.chunkData.slice(0, 4), uInt32);
124
        } else if (this.enforceFact) {
125
            throw Error(WaveErrors["fact"]);
126
        }
127
    }
128
129
    /**
130
     * Read the "bext" chunk of a wave file.
131
     * @param {Object} chunks The RIFF file chunks.
132
     * @throws {Error} If no "bext" chunk is found.
133
     */
134
    readBextChunk_(chunks) {
135
        let chunk = this.findChunk(chunks, "bext");
136
        if (chunk) {
137
            this.bextChunkId = "bext";
138
            this.bextChunkSize = chunk.chunkSize;
139
            this.bextChunkData = chunk.chunkData;
140
        }
141
    }
142
143
    /**
144
     * Read the "cue " chunk of a wave file.
145
     * @param {Object} chunks The RIFF file chunks.
146
     * @throws {Error} If no "cue" chunk is found.
147
     */
148
    readCueChunk_(chunks) {
149
        let chunk = this.findChunk(chunks, "cue ");
150
        if (chunk) {
151
            this.cueChunkId = "cue ";
152
            this.cueChunkSize = chunk.chunkSize;
153
            this.cueChunkData = chunk.chunkData;
154
        }
155
    }
156
157
    /**
158
     * Read the "data" chunk of a wave file.
159
     * @param {Object} chunks The RIFF file chunks.
160
     * @throws {Error} If no "data" chunk is found.
161
     */
162
    readDataChunk_(chunks, options) {
163
        let chunk = this.findChunk(chunks, "data");
164
        if (chunk) {
165
            this.dataChunkId = "data";
166
            this.dataChunkSize = chunk.chunkSize;
167
            this.samplesFromBytes_(chunk.chunkData, options);
168
        } else {
169
            throw Error(WaveErrors["data"]);
170
        }
171
    }
172
173
    /**
174
     * Find and return the start offset of the data chunk on a wave file.
175
     * @param {Uint8Array} bytes Array of bytes representing the wave file.
176
     */
177
    samplesFromBytes_(bytes, options) {
178
        options.signed = this.bitsPerSample == 8 ? false : true
179
        if (this.bitsPerSample == 32 && this.audioFormat == 3) {
180
            options.float = true;
181
        }
182
        options.single = false;
183
        if (this.bitsPerSample == 4) {
184
            this.samples_ = byteData.pack(bytes, uInt8);
185
        } else {
186
            this.samples_ = byteData.fromBytes(
187
                bytes, this.bitsPerSample, options);
188
        }
189
    }
190
191
    /**
192
     * Find a chunk by its FourCC in a array of RIFF chunks.
193
     * @return {Object|null}
194
     */
195
    findChunk(chunks, fourCC) {
196
        for (let i = 0; i<chunks.length; i++) {
197
            if (chunks[i].chunkId == fourCC) {
198
                return chunks[i];
199
            }
200
        }
201
        return null;
202
    }
203
204
    /**
205
     * Split each sample into bytes.
206
     */
207
    samplesToBytes_(options) {
208
        if (this.bitsPerSample == 32 && this.audioFormat == 3) {
209
            options.float = true;
210
        }
211
        let bitDepth = this.bitsPerSample == 4 ? 8 : this.bitsPerSample;
212
        let bytes = byteData.toBytes(this.samples_, bitDepth, options);
213
        if (bytes.length % 2) {
214
            bytes.push(0);
215
        }
216
        return bytes;
217
    }
218
219
    /**
220
     * Get the bytes of the "bext" chunk.
221
     * @return {!Array<number>} The "bext" chunk bytes.
222
     */
223
    getBextBytes_() {
224
        if (this.bextChunkId) {
225
            return [].concat(
226
                    byteData.packSequence(this.bextChunkId, char),
227
                    byteData.pack(this.bextChunkSize, uInt32),
228
                    this.bextChunkData
229
                );
230
        }
231
        return [];
232
    }
233
234
    /**
235
     * Get the bytes of the "cue " chunk.
236
     * @return {!Array<number>} The "cue " chunk bytes.
237
     */
238
    getCueBytes_() {
239
        if (this.cueChunkId) {
240
            return [].concat(
241
                    byteData.packSequence(this.cueChunkId, char),
242
                    byteData.pack(this.cueChunkSize, uInt32),
243
                    this.cueChunkData
244
                );
245
        }
246
        return [];
247
    }
248
249
    /**
250
     * Get the bytes of the "fact" chunk.
251
     * @return {!Array<number>} The "fact" chunk bytes.
252
     */
253
    getFactBytes_() {
254
        if (this.factChunkId) {
255
            return [].concat(
256
                    byteData.packSequence(this.factChunkId, char),
257
                    byteData.pack(this.factChunkSize, uInt32),
258
                    byteData.pack(this.dwSampleLength, uInt32)
259
                );
260
        }
261
        return [];
262
    }
263
264
    /**
265
     * Get the bytes of the cbSize field.
266
     * @return {!Array<number>} The cbSize bytes.
267
     */
268
    getCbSizeBytes_() {
269
        if (this.fmtChunkSize > 16) {
270
            return byteData.pack(this.cbSize, uInt16);
271
        }
272
        return [];
273
    }
274
275
    /**
276
     * Get the bytes of the validBitsPerSample field.
277
     * @return {!Array<number>} The validBitsPerSample bytes.
278
     */
279
    getValidBitsPerSampleBytes_() {
280
        if (this.fmtChunkSize > 18) {
281
            return byteData.pack(this.validBitsPerSample, uInt16);
282
        }
283
        return [];
284
    }
285
286
    /**
287
     * Turn a WaveFile object into a file.
288
     * @return {Uint8Array} The wav file bytes.
289
     */
290
    createWaveFile_() {
291
        let options = {"be": this.chunkId == "RIFX"};
292
        return byteData.packSequence(this.chunkId, char).concat(
293
                byteData.pack(this.chunkSize, uInt32),
294
                byteData.packSequence(this.format, char),
295
                this.getBextBytes_(),
296
                byteData.packSequence(this.fmtChunkId, char),
297
                byteData.pack(this.fmtChunkSize, uInt32),
298
                byteData.pack(this.audioFormat, uInt16),
299
                byteData.pack(this.numChannels, uInt16),
300
                byteData.pack(this.sampleRate, uInt32),
301
                byteData.pack(this.byteRate, uInt32),
302
                byteData.pack(this.blockAlign, uInt16),
303
                byteData.pack(this.bitsPerSample, uInt16),
304
                this.getCbSizeBytes_(),
305
                this.getValidBitsPerSampleBytes_(),
306
                this.getFactBytes_(),
307
                byteData.packSequence(this.dataChunkId, char),
308
                byteData.pack(this.dataChunkSize, uInt32),
309
                this.samplesToBytes_(options),
310
                this.getCueBytes_()
311
            );
312
    }
313
}
314
315
module.exports = WaveFileReaderWriter;
316