Problem
ParakeetEngine._capture_session reads the microphone with PortAudio's blocking API on the same thread that decodes. Any decode therefore pauses capture:
- MLX engine: take decodes run inline (
_enqueue_take -> _deliver_take) because MLX streams are thread-local, and the new keepalive_minutes tick decodes there too.
- PortAudio's input ring buffer at default latency holds well under a second, so a decode longer than that drops samples.
_read_samples discards read()'s overflow flag, so the loss is silent.
Consequence: speech arriving during a decode can be clipped rather than merely delayed. Raised by Codex review on #168 against the keepalive, but it is pre-existing — an ordinary take decode has the same exposure (toggle off, then immediately toggle on and speak).
Fix
Decouple capture from decoding: drive the mic with a sounddevice callback that pushes chunks into a bounded queue, and have the processing loop consume that queue. Capture then continues during any decode, and the queue makes overflow explicit and measurable instead of silent.
Points to settle during design:
- Bounded queue + an explicit drop policy, reported through
_report rather than swallowed.
- Surface PortAudio's overflow flag (
status in the callback) as a warning.
- Keep the ordering guarantees the command queue relies on (hold_stop delivering before a following hold_start).
- Both engines and all three modes (toggle/wake/vad) share this loop; hold-mode buffering and the VAD path must behave identically after the change.
Not in scope for #168
#168 documents this as a known limitation (docstring, config sample, docs page, release note) and narrows the keepalive's window with guards. The architectural fix belongs here.
Problem
ParakeetEngine._capture_sessionreads the microphone with PortAudio's blocking API on the same thread that decodes. Any decode therefore pauses capture:_enqueue_take->_deliver_take) because MLX streams are thread-local, and the newkeepalive_minutestick decodes there too._read_samplesdiscardsread()'s overflow flag, so the loss is silent.Consequence: speech arriving during a decode can be clipped rather than merely delayed. Raised by Codex review on #168 against the keepalive, but it is pre-existing — an ordinary take decode has the same exposure (toggle off, then immediately toggle on and speak).
Fix
Decouple capture from decoding: drive the mic with a
sounddevicecallback that pushes chunks into a bounded queue, and have the processing loop consume that queue. Capture then continues during any decode, and the queue makes overflow explicit and measurable instead of silent.Points to settle during design:
_reportrather than swallowed.statusin the callback) as a warning.Not in scope for #168
#168 documents this as a known limitation (docstring, config sample, docs page, release note) and narrows the keepalive's window with guards. The architectural fix belongs here.