51 lines
2.1 KiB
JavaScript
51 lines
2.1 KiB
JavaScript
// AudioWorklet-Prozessor für die isolierte "Nur Minecraft"-Tonaufnahme.
|
|
//
|
|
// loopback-capture liefert PCM-Häppchen unregelmäßig getaktet über IPC (mal
|
|
// etwas früher, mal etwas später). Würde man sie direkt nacheinander als
|
|
// einzelne AudioBufferSourceNode-Aufrufe abspielen, würde jede kleine
|
|
// Verzögerung als hörbares Knacksen/Aussetzer auffallen. Stattdessen landen
|
|
// ankommende Häppchen hier in einem Ringpuffer; der Audio-Rendering-Thread
|
|
// liest in gleichmäßigem Takt (process() läuft alle 128 Samples) daraus.
|
|
// Kleine Zeitschwankungen bei der Anlieferung werden so unhörbar
|
|
// abgefedert. Läuft der Puffer doch mal leer, gibt es kurz Stille statt
|
|
// eines Knacksens.
|
|
class PcmRingBufferProcessor extends AudioWorkletProcessor {
|
|
constructor() {
|
|
super();
|
|
this.capacity = sampleRate * 2; // 2 Sekunden Puffer je Kanal
|
|
this.ring = [new Float32Array(this.capacity), new Float32Array(this.capacity)];
|
|
this.writeIdx = 0;
|
|
this.readIdx = 0;
|
|
this.available = 0; // gepufferte Frames (nicht Samples)
|
|
this.port.onmessage = (e) => this._enqueue(e.data);
|
|
}
|
|
|
|
_enqueue([left, right]) {
|
|
const n = left.length;
|
|
for (let i = 0; i < n; i++) {
|
|
const idx = (this.writeIdx + i) % this.capacity;
|
|
this.ring[0][idx] = left[i];
|
|
this.ring[1][idx] = right[i];
|
|
}
|
|
this.writeIdx = (this.writeIdx + n) % this.capacity;
|
|
this.available = Math.min(this.capacity, this.available + n);
|
|
}
|
|
|
|
process(_inputs, outputs) {
|
|
const output = outputs[0];
|
|
const frames = output[0].length;
|
|
const playable = Math.min(frames, this.available);
|
|
for (let i = 0; i < playable; i++) {
|
|
const idx = (this.readIdx + i) % this.capacity;
|
|
output[0][i] = this.ring[0][idx];
|
|
output[1][i] = this.ring[1][idx];
|
|
}
|
|
for (let i = playable; i < frames; i++) { output[0][i] = 0; output[1][i] = 0; }
|
|
this.readIdx = (this.readIdx + playable) % this.capacity;
|
|
this.available -= playable;
|
|
return true; // Prozessor am Leben halten, auch wenn gerade nichts ankommt
|
|
}
|
|
}
|
|
|
|
registerProcessor('pcm-player', PcmRingBufferProcessor);
|