-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathwave.js
123 lines (109 loc) · 2.95 KB
/
wave.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
const {
BufferStruct,
BufferStructUnion,
BufferStructBase,
} = require('./bufferstruct');
const DEBUG = false;
// http://soundfile.sapp.org/doc/WaveFormat/
const RIFFChunkStruct = new BufferStruct({
name: 'RIFFChunk',
endian: 'little',
fields: {
ckID: {type: 'utf8', size: 4},
ckSize: {type: 'int', size: 4},
chunkData: {type: 'bytes', align: 2, size: (fields) => fields.ckSize},
},
});
function makeRIFFChunk(ckID, chunkData) {
return RIFFChunkStruct.serialize({
ckID,
ckSize: chunkData.length,
chunkData,
});
}
const WaveFmtStruct = new BufferStruct({
name: 'WaveFmt',
endian: 'little',
fields: {
// PCM = 1 (i.e. Linear quantization)
// Values other than 1 indicate some form of compression.
audioFormat: {type: 'int', size: 2},
// Mono = 1, Stereo = 2, etc.
numChannels: {type: 'int', size: 2},
// 8000, 44100, etc.
sampleRate: {type: 'int', size: 4},
// sampleRate * numChannels * sampleSize/8
byteRate: {type: 'int', size: 4},
// numChannels * BitsPerSample/8
sampleAlignment: {type: 'int', size: 2},
// Bits Per Sample. 8 bits = 8, 16 bits = 16, etc.
sampleSize: {type: 'int', size: 2},
},
});
function serializeWave({
audioFormat,
numChannels,
sampleRate,
sampleSize,
soundData,
}) {
return makeRIFFChunk(
'RIFF',
Buffer.concat([
Buffer.from('WAVE', 'utf8'), // Format
makeRIFFChunk(
'fmt ',
WaveFmtStruct.serialize({
audioFormat,
numChannels,
sampleRate,
byteRate: (sampleRate * numChannels * sampleSize) / 8,
sampleAlignment: (numChannels * sampleSize) / 8,
sampleSize,
})
),
makeRIFFChunk('data', soundData),
])
);
}
function parseWave(buffer) {
const waveChunk = RIFFChunkStruct.parse(buffer);
DEBUG && console.log(waveChunk);
let pos = 0;
const format = waveChunk.chunkData.slice(0, 4).toString('utf8');
pos += 4; // skip over format bytes
const chunks = [];
while (pos < waveChunk.chunkData.length) {
DEBUG &&
console.log(
'reading chunk',
waveChunk.chunkData.slice(pos, pos + 8),
waveChunk.chunkData.slice(pos, pos + 4).toString('utf8'),
waveChunk.chunkData.readInt32LE(pos + 4)
);
const chunk = RIFFChunkStruct.parse(waveChunk.chunkData, pos);
DEBUG && console.log(chunk);
chunks.push(chunk);
pos = RIFFChunkStruct.lastOffset;
}
const fmtChunk = chunks.find((chunk) => chunk.ckID === 'fmt ');
if (!fmtChunk) throw new Error(`fmt chunk not found`);
const fmt = WaveFmtStruct.parse(fmtChunk.chunkData);
const dataChunk = chunks.find((chunk) => chunk.ckID === 'data');
if (!dataChunk) throw new Error(`data chunk not found`);
const soundData = dataChunk.chunkData;
DEBUG &&
console.log({
waveChunk,
fmt,
soundData,
});
return {
...fmt,
soundData,
};
}
module.exports = {
serialize: serializeWave,
parse: parseWave,
};