|
1 | | -export interface RecorderBuffer { |
2 | | - buffer: Float32Array; |
3 | | - bufferPosition: number; |
4 | | -} |
| 1 | +/* |
| 2 | +v1. allocated Float32Array all the time |
| 3 | +v2. used SharedArrayBuffer, deep rabbithole of cross-origin-isolation |
| 4 | +v3. uses transferable ArrayBuffer pool |
| 5 | +*/ |
5 | 6 |
|
6 | | -const bufferSize = 8192 * 4; // size of the shared ringbuffer |
7 | | -const outputSize = bufferSize / 4; // return quarter chunks of the ringbuffer as they're ready |
| 7 | +const bufferSize = 8192 * 4; // number of samples per buffer in the pool - must be multiple of 128 |
8 | 8 |
|
9 | 9 | export class Recorder extends EventTarget { |
10 | 10 | context: AudioContext; |
11 | 11 | recordNode: AudioWorkletNode; |
12 | | - sharedState: SharedArrayBuffer; |
13 | | - sharedBuffers: SharedArrayBuffer[]; |
14 | 12 | buffers: Float32Array[]; |
15 | | - state: Int32Array; |
16 | | - pollingInterval: number; |
17 | | - lastReadPosition: number = 0; |
18 | | - outBuffers: Float32Array[]; |
19 | 13 |
|
20 | 14 | constructor(context: AudioContext) { |
21 | 15 | super(); |
22 | 16 | this.context = context; |
23 | 17 |
|
24 | | - this.sharedState = new SharedArrayBuffer(16); // write, 3 reserved * sizeof(int32) |
25 | | - this.sharedBuffers = [ |
26 | | - new SharedArrayBuffer(bufferSize * 4), // * sizeof(float) |
27 | | - new SharedArrayBuffer(bufferSize * 4) |
28 | | - ]; |
29 | | - |
30 | | - this.state = new Int32Array(this.sharedState); |
31 | | - this.buffers = [ |
32 | | - new Float32Array(this.sharedBuffers[0]), |
33 | | - new Float32Array(this.sharedBuffers[1]), |
34 | | - ]; |
35 | | - |
36 | | - this.outBuffers = [ |
37 | | - new Float32Array(outputSize), |
38 | | - new Float32Array(outputSize) |
39 | | - ]; |
| 18 | + const bufferPool: ArrayBuffer[] = []; |
| 19 | + for (let i = 0; i < 16; i++) { |
| 20 | + bufferPool.push(new ArrayBuffer(bufferSize * Float32Array.BYTES_PER_ELEMENT)); |
| 21 | + } |
40 | 22 |
|
41 | 23 | this.recordNode = new AudioWorkletNode(this.context, "record-processor"); |
42 | | - this.recordNode.port.postMessage({ type: "init", buffers: this.sharedBuffers, state: this.sharedState }); |
| 24 | + this.recordNode.port.addEventListener("message", this.onMessage); |
43 | 25 | this.recordNode.connect(context.destination); |
44 | 26 |
|
45 | | - // TODO: compute a poll interval such that a quarter of the ringbuffer is always ready |
46 | | - this.pollingInterval = window.setInterval(this.onPoll, 90); |
| 27 | + this.recordNode.port.start(); |
| 28 | + this.recordNode.port.postMessage({ type: "init", buffers: bufferPool }); |
47 | 29 | } |
48 | 30 |
|
49 | | - onPoll = () => { |
50 | | - const writePosition = Atomics.load(this.state, 0); |
51 | | - const available = (writePosition >= this.lastReadPosition) |
52 | | - ? writePosition - this.lastReadPosition |
53 | | - : bufferSize - this.lastReadPosition + writePosition; |
54 | | - |
55 | | - if (available < outputSize) return; // wait until enough data |
56 | | - |
57 | | - const left = this.buffers[0].subarray(this.lastReadPosition, this.lastReadPosition + outputSize); |
58 | | - const right = this.buffers[1].subarray(this.lastReadPosition, this.lastReadPosition + outputSize); |
59 | | - |
60 | | - this.lastReadPosition += outputSize; |
61 | | - |
62 | | - this.dispatchEvent(new CustomEvent("input", { |
63 | | - detail: [left, right] |
64 | | - })); |
65 | | - } |
| 31 | + onMessage = (ev: MessageEvent<{type, buffers}>) => { |
| 32 | + if (ev.data.type === "input") { |
| 33 | + const left = new Float32Array(ev.data.buffers[0]); |
| 34 | + const right = new Float32Array(ev.data.buffers[1]); |
| 35 | + this.dispatchEvent(new CustomEvent("input", { detail: [ left, right ] })); |
| 36 | + this.recordNode.port.postMessage({ type: "recycle", buffers: ev.data.buffers }, ev.data.buffers); |
| 37 | + } |
| 38 | + }; |
66 | 39 |
|
67 | 40 | destroy() { |
68 | 41 | this.recordNode.port.postMessage({ type: "quit" }); |
69 | | - clearInterval(this.pollingInterval); |
70 | | - this.pollingInterval = undefined; |
71 | 42 | } |
72 | 43 | } |
0 commit comments