/
reader.js
172 lines (149 loc) · 5.1 KB
/
reader.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
/**
* Module dependencies.
*/
var util = require('util');
var assert = require('assert');
var Parser = require('stream-parser');
var Transform = require('readable-stream/transform');
var debug = require('debug')('wave:reader');
var inherits = util.inherits;
var f = util.format;
var formats = {
WAVE_FORMAT_PCM: 0x0001, // PCM
WAVE_FORMAT_IEEE_FLOAT: 0x0003, // IEEE float
WAVE_FORMAT_ALAW: 0x0006, // 8-bit ITU-T G.711 A-law
WAVE_FORMAT_MULAW: 0x0007, // 8-bit ITU-T G.711 µ-law
WAVE_FORMAT_EXTENSIBLE: 0xFFFE // Determined by SubFormat
};
/**
* Module exports.
*/
module.exports = Reader;
/**
* The `Reader` class accepts a WAV audio file written to it and outputs the raw
* audio data with the WAV header stripped (most of the time, PCM audio data will
* be output, depending on the `audioFormat` property).
*
* A `"format"` event gets emitted after the WAV header has been parsed.
*
* @param {Object} opts optional options object
* @api public
*/
function Reader (opts) {
if (!(this instanceof Reader)) {
return new Reader(opts);
}
Transform.call(this, opts);
this._bytes(4, this._onRiffID);
}
inherits(Reader, Transform);
/**
* Mixin `Parser`.
*/
Parser(Reader.prototype);
// the beginning of the WAV file
Reader.prototype._onRiffID = function (chunk) {
debug('onRiffID: %o', chunk);
var id = this.riffId = chunk.toString('ascii');
if ('RIFF' === id) {
debug('detected little-endian WAVE file');
this.endianness = 'LE';
this._bytes(4, this._onChunkSize);
} else if ('RIFX' === id) {
debug('detected big-endian WAVE file');
this.endianness = 'BE';
this._bytes(4, this._onChunkSize);
} else {
this.emit('error', new Error(f('bad "chunk id": expected "RIFF" or "RIFX", got %j', id)));
}
};
// size of the WAV
Reader.prototype._onChunkSize = function (chunk) {
debug('onChunkSize: %o', chunk);
this.chunkSize = chunk['readUInt32' + this.endianness](0);
this._bytes(4, this._onFormat);
};
// the RIFF "format", should always be "WAVE"
Reader.prototype._onFormat = function (chunk) {
debug('onFormat: %o', chunk);
this.waveId = chunk.toString('ascii');
if ('WAVE' === this.waveId) {
this._bytes(4, this._onSubchunk1ID);
} else {
this.emit('error', new Error(f('bad "format": expected "WAVE", got %j', this.waveId)));
}
};
// size of the "subchunk1" (the header)
Reader.prototype._onSubchunk1ID = function (chunk) {
debug('onSubchunk1ID: %o', chunk);
var subchunk1ID = chunk.toString('ascii');
this.chunkId = subchunk1ID;
if ('fmt ' === subchunk1ID) {
this._bytes(4, this._onSubchunk1Size);
} else {
this.emit('error', new Error(f('bad "fmt id": expected "fmt ", got %j', subchunk1ID)));
}
};
Reader.prototype._onSubchunk1Size = function (chunk) {
debug('onSubchunk1Size: %o', chunk);
this.subchunk1Size = chunk['readUInt32' + this.endianness](0);
// TODO: assert should be 16 for PCM
this._bytes(this.subchunk1Size, this._onSubchunk1);
};
Reader.prototype._onSubchunk1 = function (chunk) {
debug('onSubchunk1: %o', chunk);
this.audioFormat = chunk['readUInt16' + this.endianness](0);
this.channels = chunk['readUInt16' + this.endianness](2);
this.sampleRate = chunk['readUInt32' + this.endianness](4);
this.byteRate = chunk['readUInt32' + this.endianness](8); // useless...
this.blockAlign = chunk['readUInt16' + this.endianness](12); // useless...
this.bitDepth = chunk['readUInt16' + this.endianness](14);
this.signed = this.bitDepth != 8;
var format = {
audioFormat: this.audioFormat,
endianness: this.endianness,
channels: this.channels,
sampleRate: this.sampleRate,
byteRate: this.byteRate,
blockAlign: this.blockAlign,
bitDepth: this.bitDepth,
signed: this.signed
};
switch (format.audioFormat) {
case formats.WAVE_FORMAT_PCM:
// default, common case. don't need to do anything.
break;
case formats.WAVE_FORMAT_IEEE_FLOAT:
format.float = true;
break;
case formats.WAVE_FORMAT_ALAW:
format.alaw = true;
break;
case formats.WAVE_FORMAT_MULAW:
format.ulaw = true;
break;
}
this.emit('format', format);
this._bytes(4, this._onSubchunk2ID);
};
Reader.prototype._onSubchunk2ID = function (chunk) {
debug('onSubchunk2ID: %o', chunk);
var subchunk2ID = chunk.toString('ascii');
if ('data' === subchunk2ID) {
this._bytes(4, this._onSubchunk2Size);
} else {
this.emit('error', new Error(f('bad "data" chunk: expected "data", got %j', subchunk2ID)));
}
};
// size of the remaining data in this WAV file
Reader.prototype._onSubchunk2Size = function (chunk) {
debug('onSubchunk2Size: %o', chunk);
this.subchunk2Size = chunk['readUInt32' + this.endianness](0);
// even though the WAV file reports a remaining byte length, in practice it
// can't really be trusted since in streaming situations where the WAV file is
// being generated on-the-fly, the number of remaining bytes would be impossible
// to know beforehand. For this reason, some encoders write `0` for the byte
// length here... In any case, we are just gonna pass through the rest of the
// stream until EOF.
this._passthrough(Infinity, this._onEnd);
};