The overnight audio ring buffer design that keeps 8 hours without spilling memory
Recording audio all night sounds like a memory problem waiting to happen: 12.8 kHz mono at 8 hours is roughly 2.3 GB of raw PCM if you hold it all. No overnight app keeps 2.3 GB resident — it gets terminated. The design that works is a fixed-size ring buffer that holds only what the feature extractor needs, and a disciplined policy that overwrites the rest.
This is the exact ring buffer architecture SleepTrace runs in the background every night, and the one mistake that silently loses nights when it gets the timestamp anchoring wrong.
The ring, and why it must be fixed
The buffer is allocated once at session start: 4 seconds of 12.8 kHz mono int16 = 102,400 samples, ~200 KB. Each 2-second frame read out advances the read pointer by two seconds and the write pointer by two seconds. Because reads and writes move at the same rate, the buffer never grows.
If the frame rate ever exceeds the capture rate (the model stalls, the CPU is throttled), the write pointer laps the read pointer and you get buffer underrun — a tell-tale zero-gain region in the feature stream. That is the detectable signature of OS throttling, exactly the signal-quality flag you want to surface rather than hide. The full field notes on detecting those throttling gaps are here.
The two-clock trap
The bug that took the longest to fix: treating the audio clock (CMTime from AVAudioInputNode) and wall-clock time (Date) as synchronized. They are not. Over eight hours, the drift between them is enough that "the snoring cluster at 02:17" lands at 02:09 in local time, and suddenly the event-correlation pipeline that ties snoring hours to user-entered alcohol intake stops working.
The fix is an anchor: at session start, you capture a single paired sample (audioTime, Date.now()), and after every interruption you re-anchor. The feature log stores everything as offsets from the most recent anchor, converted to wall-clock only at export. The conversion code is the unglamorous core of the blog post on acoustic detection.
Discarding raw audio safely
At morning flush, the ring buffer is simply overwritten. No write-to-disk path exists for raw audio. The event log — timestamps, classes, confidence flags — is the only persistence, and it is a few hundred kilobytes. That discipline is what lets the app live in the background all night without the OS killing it for memory, and it is what makes the "audio never leaves your phone" promise provable in code, not just copy.
Top comments (0)