class PCMAudioProcessor extends AudioWorkletProcessor { constructor() { super() this._inRate = sampleRate // z.B. 48000 this._pos = 0 // Phase in Eingangssamples [0, step) this._carry = null // letztes Eingangssample des vorigen Blocks this._outRate = 24000 } process(inputs) { const chs = inputs[0] if (!chs || chs.length === 0) return true const inF32 = chs[0] // mono Float32 const step = this._inRate / this._outRate // src ggf. mit carry präfixieren, damit i+1 existiert let src = inF32 if (this._carry !== null) { const tmp = new Float32Array(1 + inF32.length) tmp[0] = this._carry tmp.set(inF32, 1) src = tmp } // Anzahl ausgebbarer Samples (lineare Interp.: i+1 < src.length) const avail = src.length - 1 - this._pos const outLen = avail > 0 ? Math.ceil(avail / step) : 0 const outI16 = new Int16Array(outLen) let pos = this._pos for (let k = 0; k < outLen; k++) { const i = Math.floor(pos) const frac = pos - i const x0 = src[i] const x1 = src[i + 1] // existiert garantiert durch -1 in avail // Linearinterp. + Clamping let y = x0 + frac * (x1 - x0) if (y > 1) y = 1 else if (y < -1) y = -1 // Float32 -> int16 (runden, saturieren) const s = y <= -1 ? -0x8000 : Math.round(y * 0x7fff) outI16[k] = s pos += step } // Phase für nächsten Block relativ zu dessen src verschieben // (src[0] == letztes Sample des aktuellen Eingangs) this._pos = pos - (src.length - 1) if (this._pos < 0) this._pos = 0 // numerische Sicherheit // letztes echtes Eingangssample als carry behalten this._carry = src[src.length - 1] // zero-copy an die UI (Transfer des Buffers) try { this.port.postMessage(outI16, [outI16.buffer]) } catch { // Fallback ohne Transferliste (z. B. in älteren Browsern) this.port.postMessage(outI16) } return true } } registerProcessor("audio-processor-worklet", PCMAudioProcessor)