mirror of
https://github.com/block/buzz.git
synced 2026-08-18 06:50:31 +02:00
Signed-off-by: Tyler Longwell <tlongwell@block.xyz> Co-authored-by: npub1qyvc0c5kl4gqv2fd97fsk46tu378sqgy35vc83rvgfwne90sel7s0ed67d <011987e296fd5006292d2f930b574be47c7801048d1983c46c425d3c95f0cffd@sprout-oss.stage.blox.sqprod.co> Co-authored-by: Max <d8473ee32b973aa31a21a65adddcc4b69cc2a8a4dee8121ecd51926e0cddbc02@sprout-oss.stage.blox.sqprod.co> Co-authored-by: Mari <95cae996907d7cab9f5dbf43c0f53edeac6ab0b032a6feae4abfd784e467b3f5@sprout-oss.stage.blox.sqprod.co> Co-authored-by: Quinn <96f056ad5f2305c8ddf637dc65d048aa4c12d7daeb8867690e34fca46b0ef64c@sprout-oss.stage.blox.sqprod.co> Co-authored-by: Sami <f4a42a97e594b77bdbd8ee35191c8b28a94a4cb871d96f32921558275421fb68@sprout-oss.stage.blox.sqprod.co> Co-authored-by: Perci <5a968df9a7494b4e019b9ecf739e088ba61097b4312124e9a88ae5b42e3f5f3e@sprout-oss.stage.blox.sqprod.co> Co-authored-by: Tyler Longwell <tlongwell@block.xyz>
68 lines
2.5 KiB
JavaScript
68 lines
2.5 KiB
JavaScript
// AudioWorklet processor — runs in the AudioWorklet thread.
|
|
// Accumulates PCM Float32 samples and sends 20ms batches to the main thread.
|
|
//
|
|
// ASSUMPTION: AudioContext runs at 48 kHz (Tauri WebView default on desktop).
|
|
// The 960-sample buffer = exactly one 20ms Opus frame at 48 kHz. If the
|
|
// sample rate differs (e.g. 44.1 kHz), frames will be slightly misaligned
|
|
// with the Opus encoder. getUserMedia should request sampleRate: 48000.
|
|
//
|
|
// Supports push-to-talk (PTT) gating: when `this.transmitting` is false,
|
|
// incoming audio frames are discarded and the buffer is reset. The main thread
|
|
// sends `{ type: 'ptt', active: boolean }` messages to toggle transmission.
|
|
// Default: transmitting=true (open mic for VAD mode compatibility).
|
|
//
|
|
// Note: when the worklet is disconnected, any partial buffer (< 960 samples)
|
|
// is silently dropped. The last ~20ms of speech may be lost on huddle leave.
|
|
// This is acceptable for voice — losing a partial syllable at disconnect is
|
|
// imperceptible compared to the natural end-of-conversation flow.
|
|
class SttTapProcessor extends AudioWorkletProcessor {
|
|
constructor() {
|
|
super();
|
|
this.buffer = new Float32Array(960); // ~20ms at 48kHz
|
|
this.offset = 0;
|
|
this.transmitting = true; // default: open (VAD mode). PTT mode sets false on init.
|
|
|
|
// Direction: main→worklet (receives). The worklet→main direction uses
|
|
// this.port.postMessage for PCM data — these don't conflict.
|
|
this.port.onmessage = (e) => {
|
|
if (e.data && e.data.type === "ptt") {
|
|
this.transmitting = e.data.active;
|
|
}
|
|
};
|
|
}
|
|
|
|
process(inputs) {
|
|
const input = inputs[0]?.[0]; // mono channel
|
|
if (!input) return true;
|
|
|
|
// PTT gating: discard frames when not transmitting.
|
|
// Reset buffer offset so we don't send stale audio when PTT activates.
|
|
if (!this.transmitting) {
|
|
this.offset = 0;
|
|
return true;
|
|
}
|
|
|
|
const remaining = this.buffer.length - this.offset;
|
|
const toCopy = Math.min(input.length, remaining);
|
|
this.buffer.set(input.subarray(0, toCopy), this.offset);
|
|
this.offset += toCopy;
|
|
|
|
if (this.offset >= this.buffer.length) {
|
|
// Transfer ownership for zero-copy
|
|
this.port.postMessage(this.buffer, [this.buffer.buffer]);
|
|
this.buffer = new Float32Array(960);
|
|
this.offset = 0;
|
|
|
|
if (toCopy < input.length) {
|
|
const leftover = input.subarray(toCopy);
|
|
this.buffer.set(leftover);
|
|
this.offset = leftover.length;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
}
|
|
|
|
registerProcessor("stt-tap-processor", SttTapProcessor);
|