Skip to content

Commit 90de766

Browse files
committed
Refactor waveWorker to typescript
1 parent d9e6f13 commit 90de766

File tree

3 files changed

+116
-103
lines changed

3 files changed

+116
-103
lines changed

src/audio.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import WaveWorker from './waveWorker.js';
1+
import WaveWorker from './waveWorker';
22

33
/**
44
* Process javascript code to audio buffer

src/waveWorker.js

Lines changed: 0 additions & 102 deletions
This file was deleted.

src/waveWorker.ts

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
// Adapted from https://stackoverflow.com/a/42632646
2+
3+
/* eslint-disable no-restricted-globals */
4+
/* eslint-disable no-bitwise */
5+
6+
class WavePCM
7+
{
8+
sampleRate: number;
9+
bitDepth: number;
10+
recordedBuffers: Uint8Array[];
11+
bytesPerSample: number;
12+
numberOfChannels: number|null = null;
13+
14+
constructor(config: {sampleRate: number, bitDepth: number} = {
15+
sampleRate: 48000,
16+
bitDepth: 16,
17+
}) {
18+
this.sampleRate = config.sampleRate;
19+
this.bitDepth = config.bitDepth;
20+
this.recordedBuffers = [];
21+
this.bytesPerSample = this.bitDepth / 8;
22+
}
23+
24+
record(buffers: number[][]) {
25+
this.numberOfChannels = this.numberOfChannels ?? buffers.length;
26+
const bufferLength = buffers[0].length;
27+
const reducedData = new Uint8Array(bufferLength * this.numberOfChannels * this.bytesPerSample);
28+
29+
// Interleave
30+
for (let i = 0; i < bufferLength; i++) {
31+
for (let channel = 0; channel < this.numberOfChannels; channel++) {
32+
const outputIndex = (i * this.numberOfChannels + channel) * this.bytesPerSample;
33+
let sample = buffers[channel][i];
34+
35+
// Check for clipping
36+
if (sample > 1) {
37+
sample = 1;
38+
} else if (sample < -1) {
39+
sample = -1;
40+
}
41+
42+
// bit reduce and convert to uInt
43+
switch (this.bytesPerSample) {
44+
case 4:
45+
sample *= 2147483648;
46+
reducedData[outputIndex] = sample;
47+
reducedData[outputIndex + 1] = sample >> 8;
48+
reducedData[outputIndex + 2] = sample >> 16;
49+
reducedData[outputIndex + 3] = sample >> 24;
50+
break;
51+
52+
case 3:
53+
sample *= 8388608;
54+
reducedData[outputIndex] = sample;
55+
reducedData[outputIndex + 1] = sample >> 8;
56+
reducedData[outputIndex + 2] = sample >> 16;
57+
break;
58+
59+
case 2:
60+
sample *= 32768;
61+
reducedData[outputIndex] = sample;
62+
reducedData[outputIndex + 1] = sample >> 8;
63+
break;
64+
65+
case 1:
66+
reducedData[outputIndex] = (sample + 1) * 128;
67+
break;
68+
69+
default:
70+
throw Error('Only 8, 16, 24 and 32 bits per sample are supported');
71+
}
72+
}
73+
}
74+
75+
this.recordedBuffers.push(reducedData);
76+
}
77+
78+
requestData () {
79+
const bufferLength = this.recordedBuffers[0].length;
80+
const dataLength = this.recordedBuffers.length * bufferLength;
81+
const headerLength = 44;
82+
const wav = new Uint8Array(headerLength + dataLength);
83+
const view = new DataView(wav.buffer);
84+
85+
view.setUint32(0, 1380533830, false); // RIFF identifier 'RIFF'
86+
view.setUint32(4, 36 + dataLength, true); // file length minus RIFF identifier length and file description length
87+
view.setUint32(8, 1463899717, false); // RIFF type 'WAVE'
88+
view.setUint32(12, 1718449184, false); // format chunk identifier 'fmt '
89+
view.setUint32(16, 16, true); // format chunk length
90+
view.setUint16(20, 1, true); // sample format (raw)
91+
view.setUint16(22, (this.numberOfChannels ?? 2), true); // channel count
92+
view.setUint32(24, this.sampleRate, true); // sample rate
93+
view.setUint32(28, this.sampleRate * this.bytesPerSample * (this.numberOfChannels ?? 2), true); // byte rate (sample rate * block align)
94+
view.setUint16(32, this.bytesPerSample * (this.numberOfChannels ?? 2), true); // block align (channel count * bytes per sample)
95+
view.setUint16(34, this.bitDepth, true); // bits per sample
96+
view.setUint32(36, 1684108385, false); // data chunk identifier 'data'
97+
view.setUint32(40, dataLength, true); // data chunk length
98+
99+
for (let i = 0; i < this.recordedBuffers.length; i++) {
100+
wav.set(this.recordedBuffers[i], i * bufferLength + headerLength);
101+
}
102+
103+
// @ts-ignore
104+
self.postMessage(wav, [wav.buffer]);
105+
self.close();
106+
}
107+
}
108+
109+
self.onmessage = function (e) {
110+
const wavPCM = new WavePCM(e.data.config);
111+
wavPCM.record(e.data.pcmArrays ?? [[]]);
112+
wavPCM.requestData();
113+
};
114+
115+
export default WavePCM;

0 commit comments

Comments
 (0)