From 7a565f377203ba8e01574952014a23dfa8d94fa3 Mon Sep 17 00:00:00 2001 From: salmonik Date: Thu, 30 Jul 2026 15:09:51 +0200 Subject: fix: auto completion timing out before ready + fix preexisting warns --- docs/adr/0001-pcm-ring-buffer-over-opus-storage.md | 54 ++++++++++++++++++++++ 1 file changed, 54 insertions(+) create mode 100644 docs/adr/0001-pcm-ring-buffer-over-opus-storage.md (limited to 'docs/adr') diff --git a/docs/adr/0001-pcm-ring-buffer-over-opus-storage.md b/docs/adr/0001-pcm-ring-buffer-over-opus-storage.md new file mode 100644 index 0000000..24c9f98 --- /dev/null +++ b/docs/adr/0001-pcm-ring-buffer-over-opus-storage.md @@ -0,0 +1,54 @@ +# ADR 0001 — Mixed PCM ring buffer over per-SSRC Opus storage + +Date: 2026-07-08 +Status: Accepted + +## Context + +The voice-recording feature (`/record`) must keep a rolling, fixed-length +window of everything spoken in the bot's current voice channel, so that a +flush produces a single mixed audio file of (up to) the window length. + +songbird delivers incoming voice as one Opus stream per speaking SSRC. There +were two viable storage strategies: + +- **(A) Mixed PCM ring buffer.** Decode each packet to PCM, sum all speakers + into one pre-allocated ring of mono i16 samples. Flush = encode the ring. +- **(B) Raw Opus packets per SSRC.** Store each SSRC's compressed packets in a + timestamped deque. Flush = decode all, mix by timestamp, then encode. + +## Decision + +Store a single mixed mono-s16 PCM ring buffer (**A**), sized +`sample_rate × window_seconds` (~86 MB at 48 kHz / 15 min), allocated only +while the bot is connected and recording is enabled. + +## Rationale + +- **Matches the stated mental model.** The feature was specified as "a blob in + memory that has the newest 15 minutes, deletes the old, appends the new at + the end." (A) is the literal implementation of that; (B) stores N separate + per-user deques, which contradicts the "single blob, append at end" shape. +- **Trivial flush.** (A) flushes by handing a known-size s16le buffer to one + ffmpeg encode. (B) requires bulk decode + timestamp-aligned mixing at flush + time — heavier and more failure-prone (drift, late packets, anchor resets). +- **songbird already decodes.** With the `receive` feature and + `DecodeMode::Decode(Mono, 48kHz)`, songbird's `CoreEvent::VoiceTick` delivers + already-decoded, jitter-buffered mono i16 every 20 ms — so (A) needs no + per-SSRC Opus decoder, no `opus2` direct dependency, and no f32→s16 step. + (B) would re-derive work songbird already does. +- **The memory cost is opt-in.** `RECORDING_ENABLED` defaults to `true` but a + memory-constrained host sets it `false` and pays 0. A host that enables + recording accepts the ~86 MB. `RECORDING_WINDOW_SECONDS` lets such a host + shrink the ring (e.g. 300 s → ~29 MB) without code changes. + +## Consequences + +- ~86 MB resident while recording (at the default 15 min / 48 kHz window). + Acceptable for a single-guild self-hosted bot; tunable down via the window. +- Multi-speaker overlap is mixed with `i16::saturating_add`, which can clip + slightly on very loud overlaps rather than wrap to garbage. The right trade + for a 50 Hz × N-packet hot path. +- No per-speaker attribution is possible from the stored buffer (the mix is + lossy by design). If attribution is ever needed, (B) would have to be + revisited — this decision is the main thing that makes that reversal costly. -- cgit v1.2.3