Files
kontextwerk/frontend/assets/audio-processor-worklet.js
2025-10-05 06:34:07 +00:00

73 lines
2.3 KiB
JavaScript

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)