<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: SleepTrace</title>
    <description>The latest articles on DEV Community by SleepTrace (@sleeptrace).</description>
    <link>https://dev.to/sleeptrace</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4017719%2F3e26e38a-eb06-495b-bb5b-971201b31c39.png</url>
      <title>DEV Community: SleepTrace</title>
      <link>https://dev.to/sleeptrace</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/sleeptrace"/>
    <language>en</language>
    <item>
      <title>The overnight audio ring buffer design that keeps 8 hours without spilling memory</title>
      <dc:creator>SleepTrace</dc:creator>
      <pubDate>Mon, 10 Aug 2026 00:40:54 +0000</pubDate>
      <link>https://dev.to/sleeptrace/the-overnight-audio-ring-buffer-design-that-keeps-8-hours-without-spilling-memory-2521</link>
      <guid>https://dev.to/sleeptrace/the-overnight-audio-ring-buffer-design-that-keeps-8-hours-without-spilling-memory-2521</guid>
      <description>&lt;h1&gt;
  
  
  The overnight audio ring buffer design that keeps 8 hours without spilling memory
&lt;/h1&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;This is the exact ring buffer architecture &lt;a href="https://sleeptrace.app/" rel="noopener noreferrer"&gt;SleepTrace&lt;/a&gt; runs in the background every night, and the one mistake that silently loses nights when it gets the timestamp anchoring wrong.&lt;/p&gt;

&lt;h2&gt;
  
  
  The ring, and why it must be fixed
&lt;/h2&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;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 &lt;a href="https://sleeptrace.app/blog/snoring-and-sleep-apnea/" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  The two-clock trap
&lt;/h2&gt;

&lt;p&gt;The bug that took the longest to fix: treating the audio clock (CMTime from &lt;code&gt;AVAudioInputNode&lt;/code&gt;) 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.&lt;/p&gt;

&lt;p&gt;The fix is an anchor: at session start, you capture a single paired sample &lt;code&gt;(audioTime, Date.now())&lt;/code&gt;, 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.&lt;/p&gt;

&lt;h2&gt;
  
  
  Discarding raw audio safely
&lt;/h2&gt;

&lt;p&gt;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.&lt;/p&gt;

</description>
      <category>ios</category>
      <category>audio</category>
      <category>programming</category>
    </item>
    <item>
      <title>What a 40MB CoreML model actually detects in your bedroom audio</title>
      <dc:creator>SleepTrace</dc:creator>
      <pubDate>Mon, 10 Aug 2026 00:40:22 +0000</pubDate>
      <link>https://dev.to/sleeptrace/what-a-40mb-coreml-model-actually-detects-in-your-bedroom-audio-40d7</link>
      <guid>https://dev.to/sleeptrace/what-a-40mb-coreml-model-actually-detects-in-your-bedroom-audio-40d7</guid>
      <description>&lt;h1&gt;
  
  
  What a 40MB CoreML model actually detects in your bedroom audio
&lt;/h1&gt;

&lt;p&gt;A 40MB CoreML sleep model sounds ambitious until you realize the discriminative power in bedroom audio is concentrated in features that cost almost nothing to compute. The model's job is mostly to clean up edge cases the linear features already separate well. Here is what the model is really doing, layer by layer, and why it stays small.&lt;/p&gt;

&lt;h2&gt;
  
  
  The two-stage classifier
&lt;/h2&gt;

&lt;p&gt;Stage one is a linear separator over six engineered features:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Spectral tilt slope&lt;/strong&gt; over 0.5–4 kHz — snoring tilts down, speech tilts flat.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;High-band zero-crossing density&lt;/strong&gt; (4–16 kHz) — low for snoring, high for sibilants.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Harmonic-to-noise ratio&lt;/strong&gt; in the 100–800 Hz envelope — snoring is strongly periodic.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Band energy ratio&lt;/strong&gt; (low/mid) relative to the trailing 30-second baseline.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Spectral flatness&lt;/strong&gt; — distinguishes aperiodic snoring bursts from tonal noise.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Temporal rise/fall asymmetry&lt;/strong&gt; of the envelope — snoring ramps up slowly, speech transients sharply.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;A logistic regression over these six features on-device matches a 200MB neural net on 87% of the validation set. The neural net is not smarter; it is just covering the confusion region.&lt;/p&gt;

&lt;h2&gt;
  
  
  The small net that earns its 40MB
&lt;/h2&gt;

&lt;p&gt;The on-phone model is a 5-layer MLP with 128-width hidden layers, trained on 180k labeled 2-second frames from &lt;a href="https://sleeptrace.app/" rel="noopener noreferrer"&gt;SleepTrace&lt;/a&gt; users (opt-in, all on-device labeled). Quantization to int8 brings it to 38 MB on disk. It runs in 1.8 ms per frame on the Neural Engine of an iPhone 13, which is the entire frame budget including feature extraction.&lt;/p&gt;

&lt;p&gt;The model's job is a single classification head with a snooze/talk/ambient/apnea-pause four-way soft output, plus a confidence score that gates whether a frame participates in the smoothing pass. The full feature engineering rationale — why spectral tilt separates snoring from sentences, why harmonic structure separates voiced apnea gasps — is published on &lt;a href="https://sleeptrace.app/blog/snoring-and-sleep-apnea/" rel="noopener noreferrer"&gt;the engineering blog&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why not just the linear model?
&lt;/h2&gt;

&lt;p&gt;Two reasons:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Position-dependent false positives.&lt;/strong&gt; HVAC rumble has low tilt and low harmonic content; it trips the linear model. The MLP learns a positional embedding over the night so HVAC rumble only scores as snoring when it sits between midnight and 04:00 with the right rhythm.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Confidence calibration.&lt;/strong&gt; The MLP emits a calibrated confidence the smoothing pass uses to weight temporal consensus. Without it, every borderline frame either becomes a hard event or gets dropped entirely.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The size constraint is real
&lt;/h2&gt;

&lt;p&gt;iOS throttles the resident memory of on-device CoreML models in background tasks. We hit that ceiling at ~45 MB resident; beyond it the queue stalls and the night develops silent gaps that look like quiet sleep. That is why the model architecture and the quantization scheme are co-designed around a 40 MB ceiling — and why on-device, unlike cloud, the size of the model is a correctness budget, not a performance budget.&lt;/p&gt;

</description>
      <category>machinelearning</category>
      <category>audio</category>
      <category>ios</category>
    </item>
    <item>
      <title>Why consumer sleep accuracy is a trap (and what to measure instead)</title>
      <dc:creator>SleepTrace</dc:creator>
      <pubDate>Mon, 10 Aug 2026 00:39:42 +0000</pubDate>
      <link>https://dev.to/sleeptrace/why-consumer-sleep-accuracy-is-a-trap-and-what-to-measure-instead-2cm2</link>
      <guid>https://dev.to/sleeptrace/why-consumer-sleep-accuracy-is-a-trap-and-what-to-measure-instead-2cm2</guid>
      <description>&lt;h1&gt;
  
  
  Why consumer sleep accuracy is a trap (and what to measure instead)
&lt;/h1&gt;

&lt;p&gt;Sleep labs validate against polysomnography. Consumer sleep trackers validate against nothing stable — not other wearables, not consistent behavior across nights, not any external ground truth. The result is a market full of stage charts and sleep scores that agree with each other about 30% of the time, which means they agree with each other about nothing at all.&lt;/p&gt;

&lt;p&gt;Chasing PSG accuracy in a consumer product is a trap, not because phones are inaccurate, but because accuracy is the wrong objective. The phone is measuring the wrong things entirely.&lt;/p&gt;

&lt;h2&gt;
  
  
  What a phone on the nightstand actually measures
&lt;/h2&gt;

&lt;p&gt;Let that sink in: a phone is not a wrist device. Its accelerometers are stationary. Its microphone is an open air path into a room. Its sensor fusion is dominated by acoustics, not motion. A consumer phone sleep product is really an acoustic event detector, and every honest one should start there.&lt;/p&gt;

&lt;p&gt;From an acoustic standpoint, the detectable events have stable, reproducible definitions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Snoring episodes&lt;/strong&gt;: an aperiodic broadband burst in the vocal-band envelope with spectral tilt falling off above 1.5 kHz.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Breathing pauses&lt;/strong&gt;: a sub-baseline spectral flatness over a 10–30 second window in the 100–400 Hz band.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Positional shifts&lt;/strong&gt;: detectable from the tilt sensor over the course of the night.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These are not proxies for sleep stages. They are real events with clinical correlates (snoring intensity correlates with sleep-disordered breathing; breathing pauses correlate with apnea).&lt;/p&gt;

&lt;h2&gt;
  
  
  The accuracy fallacy
&lt;/h2&gt;

&lt;p&gt;If the goal is "match the lab EEG," a phone will fail — but so will a wrist device, because the correlation between actigraphy and EEG sleep stages is weak by construction. The question is not whether a phone matches a lab. The question is whether the acoustic events it detects correlate with outcomes users care about: morning fatigue, daytime sleepiness, partner-reported disturbance.&lt;/p&gt;

&lt;p&gt;That is the metric worth measuring: outcome, not stage.&lt;/p&gt;

&lt;h2&gt;
  
  
  Measuring against outcomes
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://sleeptrace.app/" rel="noopener noreferrer"&gt;SleepTrace&lt;/a&gt; measures its own accuracy against a daily morning fatigue slider and a weekly partner-disturbance score — both self-reported. The acoustic event detection (snore duration, breathing pauses, position changes) is validated against those outcomes, not against PSG. The technical writeup of the acoustic feature set that supports this is &lt;a href="https://sleeptrace.app/blog/snoring-and-sleep-apnea/" rel="noopener noreferrer"&gt;on the blog&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  For builders: stop benchmarking against the wrong bar
&lt;/h2&gt;

&lt;p&gt;Stop optimizing for PSG alignment. Optimize for outcome correlation. Ship the events, ship the confidence, ship the gaps. Let the stage charts be the domain of devices that earn a medical predicate. A phone on a nightstand earns its keep by being honest about what it can hear — and what it cannot.&lt;/p&gt;

</description>
      <category>sleep</category>
      <category>machinelearning</category>
      <category>health</category>
      <category>product</category>
    </item>
    <item>
      <title>The three engineering constraints that define overnight audio on iOS</title>
      <dc:creator>SleepTrace</dc:creator>
      <pubDate>Mon, 10 Aug 2026 00:39:02 +0000</pubDate>
      <link>https://dev.to/sleeptrace/the-three-engineering-constraints-that-define-overnight-audio-on-ios-56il</link>
      <guid>https://dev.to/sleeptrace/the-three-engineering-constraints-that-define-overnight-audio-on-ios-56il</guid>
      <description>&lt;h1&gt;
  
  
  The three engineering constraints that define overnight audio on iOS
&lt;/h1&gt;

&lt;p&gt;Building an audio app that runs all night on an iPhone is not about picking the right model. It is about surviving three constraints that do not exist in normal app development:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;The background task budget.&lt;/strong&gt; iOS grants roughly 30 seconds of background runtime per activation. To run all night, the app must renew its assertion on a cadence shorter than the budget — or the OS throttles the audio queue and the night goes silent.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The microphone sharing policy.&lt;/strong&gt; If another system service is using the mic, the app's &lt;code&gt;AVAudioSession&lt;/code&gt; is interrupted. Over an eight-hour window, this is guaranteed to happen — and silent interruptions look exactly like quiet sleep stages.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The thermal ceiling.&lt;/strong&gt; A sustained classifier that keeps a core warm all night triggers thermal throttling. On older devices, the phone will downclock aggressively mid-session, dropping windows of audio that the app then has to detect and flag as low-confidence.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;These three constraints map directly onto the user experience: if you handle them honestly, the app returns an honest night with honest gaps. If you ignore them, the app returns a fabricated night that looks complete.&lt;/p&gt;

&lt;h2&gt;
  
  
  Budget renewal done right
&lt;/h2&gt;

&lt;p&gt;The renewal handler must be cheap and fast, because it runs under the expiry deadline. The correct shape:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="n"&gt;bgTask&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;UIApplication&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;shared&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;beginBackgroundTask&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="k"&gt;weak&lt;/span&gt; &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt;
    &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;flushPendingBuffers&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;     &lt;span class="c1"&gt;// finish the in-flight window only&lt;/span&gt;
    &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;signalLowConfidence&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;     &lt;span class="c1"&gt;// mark the tail of the night&lt;/span&gt;
    &lt;span class="kt"&gt;UIApplication&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;shared&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;endBackgroundTask&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;bgTask&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The renewal itself (scheduling the next window) happens in the main audio callback, not in the expiry handler. The expiry handler only closes the current buffer cleanly. This is the exact problem the &lt;a href="https://sleeptrace.app/blog/snoring-and-sleep-apnea/" rel="noopener noreferrer"&gt;SleepTrace audio engineering notes&lt;/a&gt; walk through, including the drift-corrected timestamp anchoring used to avoid the gaps becoming data-integrity lies.&lt;/p&gt;

&lt;h2&gt;
  
  
  Microphone interruptions are features, not bugs
&lt;/h2&gt;

&lt;p&gt;An interruption fires &lt;code&gt;AVAudioSession.interruptionNotification&lt;/code&gt;. The naive response is to resume immediately. The correct response is to log the gap and lower confidence on the surrounding windows, because the microphone may have switched devices or picked up a different sound field after reconnection. Users get a morning note: "confidence reduced from 01:23–01:25 — likely a mic handoff." That is the honest signal-quality view an overnight app needs.&lt;/p&gt;

&lt;h2&gt;
  
  
  Thermal-aware scheduling
&lt;/h2&gt;

&lt;p&gt;The classifier runs in a &lt;code&gt;DispatchQueue&lt;/code&gt; whose &lt;code&gt;qualityOfService&lt;/code&gt; degrades when the device reports thermal state &lt;code&gt;.serious&lt;/code&gt; or worse. The fallback is to halve the window rate and widen the smoothing window, preserving event-level counts at the cost of temporal precision. The alternative — pushing through and getting throttled — corrupts the night with zeros that look like silence.&lt;/p&gt;

&lt;h2&gt;
  
  
  The user-facing payoff
&lt;/h2&gt;

&lt;p&gt;Handled correctly, the morning summary reads like a careful witness instead of a confident liar:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;It shows the gaps.&lt;/li&gt;
&lt;li&gt;It flags the thermal-throttled regions.&lt;/li&gt;
&lt;li&gt;It tells you which events crossed the confidence threshold and which skimmed it.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That honesty is what earns the place on the nightstand. &lt;a href="https://sleeptrace.app/" rel="noopener noreferrer"&gt;SleepTrace&lt;/a&gt; is architected entirely around treating these constraints as product requirements, not bugs to ship through.&lt;/p&gt;

</description>
      <category>ios</category>
      <category>swift</category>
      <category>mobile</category>
    </item>
    <item>
      <title>Why we don't upload your bedroom audio (and never will)</title>
      <dc:creator>SleepTrace</dc:creator>
      <pubDate>Mon, 10 Aug 2026 00:38:25 +0000</pubDate>
      <link>https://dev.to/sleeptrace/why-we-dont-upload-your-bedroom-audio-and-never-will-42n2</link>
      <guid>https://dev.to/sleeptrace/why-we-dont-upload-your-bedroom-audio-and-never-will-42n2</guid>
      <description>&lt;h1&gt;
  
  
  Why we don't upload your bedroom audio (and never will)
&lt;/h1&gt;

&lt;p&gt;The short version: because the moment raw audio leaves your phone, "privacy" becomes a word you can no longer use in good faith. The longer version is about engineering trade-offs that turn into user trust.&lt;/p&gt;

&lt;p&gt;Every major sleep-tracker pitch deck eventually slides over a "smart insights" feature that requires server-side processing. The implication is always that the raw audio — eight hours of you, your partner, your dog, your conversations — has to travel somewhere to get smarter. At &lt;a href="https://sleeptrace.app/" rel="noopener noreferrer"&gt;SleepTrace&lt;/a&gt; we made the opposite trade: we made the insights run entirely on-device, and we never accept a feature suggestion that requires uploading raw audio.&lt;/p&gt;

&lt;h2&gt;
  
  
  The legal and reputational surface of raw audio
&lt;/h2&gt;

&lt;p&gt;Raw audio is not metadata. It is a room recording. Eight hours of bedroom audio contains enough personal data to reconstruct a surprisingly detailed picture: who lives there, the schedule of their partner, whether someone is sick (from cough and congestion), whether children are present (from voices), and — in a not-insignible number of cases — the actual content of private conversations at night.&lt;/p&gt;

&lt;p&gt;That is not hyperbole. It is a discovery obligation in a divorce deposition. It is a subpoena target in a custody case. It is a breach waiting to happen. One database exposure, and you are the company that kept everyone's bedroom recordings.&lt;/p&gt;

&lt;h2&gt;
  
  
  What users actually trade their privacy for
&lt;/h2&gt;

&lt;p&gt;They trade it for convenience or accuracy, and they almost always overestimate both. The accuracy gap between on-device classification and cloud classification, for sleep audio, is noise today — the discriminative features (spectral tilt, zero-crossing, harmonic structure) are cheap to compute and run in real time on modern phones. The convenience is already maximal: the phone is on the nightstand. There is no compelling reason to ship the audio.&lt;/p&gt;

&lt;h2&gt;
  
  
  The engineering discipline that protects users
&lt;/h2&gt;

&lt;p&gt;We enforce a single rule in code review: any path that touches the network with raw audio payloads is a build-breaking bug. Not a flag. Not a config you can flip. A compile error. This catches the "just add server-side re-scoring" feature request before it ships.&lt;/p&gt;

&lt;p&gt;The on-device pipeline is designed around that rule. Features are extracted and the raw PCM ring buffer is overwritten every morning. The only things that leave the device, if you opt into improvement analytics, are aggregate event counts bucketed by hour and confidence level — data that cannot be traced back through an acoustic fingerprint.&lt;/p&gt;

&lt;h2&gt;
  
  
  The trust dividend
&lt;/h2&gt;

&lt;p&gt;Users notice. Retention on privacy-forward sleep apps tracks the explicit privacy promise: the headline is "audio never leaves your phone," and users quote it back in reviews. Privacy is not a cost center here. It is the feature that distinguishes a product that earns trust from one that rents access to it.&lt;/p&gt;

&lt;p&gt;The full on-device acoustic pipeline is documented at &lt;a href="https://sleeptrace.app/blog/snoring-and-sleep-apnea/" rel="noopener noreferrer"&gt;the SleepTrace engineering blog&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>privacy</category>
      <category>security</category>
      <category>ios</category>
      <category>sleep</category>
    </item>
    <item>
      <title>Building the on-device ML pipeline that powers phone-based sleep analysis</title>
      <dc:creator>SleepTrace</dc:creator>
      <pubDate>Mon, 10 Aug 2026 00:38:19 +0000</pubDate>
      <link>https://dev.to/sleeptrace/building-the-on-device-ml-pipeline-that-powers-phone-based-sleep-analysis-4cc3</link>
      <guid>https://dev.to/sleeptrace/building-the-on-device-ml-pipeline-that-powers-phone-based-sleep-analysis-4cc3</guid>
      <description>&lt;h1&gt;
  
  
  Building the on-device ML pipeline that powers phone-based sleep analysis
&lt;/h1&gt;

&lt;p&gt;Most sleep-detection demos run a model server-side on uploaded audio. That is the easy path and also the wrong one: it asks users to ship their bedroom audio to a cloud bucket, and it makes the product a privacy liability every time you explain it. The harder and right path is a full CoreML pipeline that takes microphone samples to snore events on-device, in the dark, with no network. Here is how &lt;a href="https://sleeptrace.app/" rel="noopener noreferrer"&gt;SleepTrace&lt;/a&gt; actually wires that together and why each choice matters for overnight battery and user trust.&lt;/p&gt;

&lt;h2&gt;
  
  
  The pipeline stages, end to end
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Capture&lt;/strong&gt; as a 12.8 kHz mono ring buffer via &lt;code&gt;AVAudioInputNode&lt;/code&gt; (pull mode), with a background-task assertion renewed on a 25-second cadence so iOS does not reclaim the budget. Full architectural notes in the field writeup &lt;a href="https://sleeptrace.app/blog/snoring-and-sleep-apnea/" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Window&lt;/strong&gt; into 2-second frames with 50% overlap. Each frame is a fixed cost; batching them in and out of the queue smooths CPU usage.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Feature&lt;/strong&gt; extraction is a 40-coefficient mel filterbank plus spectral tilt, zero-crossing density, and harmonic-to-noise ratio — the exact discriminators documented on the blog.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Score&lt;/strong&gt; each frame through a CoreML model exported from a PyTorch training job, quantized to 16-bit for size.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Smooth&lt;/strong&gt; the frame-level posteriors into event boundaries (snore start/end, breathing pause) in a lightweight temporal pass — no full-night attention model on the phone.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Emit&lt;/strong&gt; events to a local log; upload, if opted in, is aggregate counts only.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Why on-device is the whole point
&lt;/h2&gt;

&lt;p&gt;Moving the scoring to CoreML is not just a privacy posture. It changes the resource contract:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Latency.&lt;/strong&gt; A frame is classified in under 2 ms on recent silicon; the OS never has to decide whether to wake a co-provisioned network call.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Battery.&lt;/strong&gt; There is no radio cost. Radio wakeups dominate overnight phone battery drain, and audio-classification workloads run entirely offline.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Honesty.&lt;/strong&gt; You cannot accidentally leak the raw audio if the raw audio never reaches a host boundary. The trust model is baked in by construction.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Quantization and the size budget
&lt;/h2&gt;

&lt;p&gt;The full unquantized model is ~80 MB. Quantizing to 16-bit int8 with post-training quantization lands it under 20 MB and changes per-frame accuracy by under 1%. That 20 MB is the ceiling we budget against because iOS does not like resident CoreML models in the multi-hundred-megabyte range on a phone sharing memory with the OS all night.&lt;/p&gt;

&lt;h2&gt;
  
  
  The one place the model is not the bottleneck
&lt;/h2&gt;

&lt;p&gt;Feature engineering is. The mel filterbank and the spectral-tilt coefficient carry most of the discriminative signal between snoring and speech, and they are trivial to compute. A well-featurized linear separator often matches a much larger neural net here precisely because the feature space is doing the work. Ship the features first; ship the bigger model only if the features alone miss your false-positive budget.&lt;/p&gt;

&lt;p&gt;This is the architecture that lets a $600 phone replace a $300 wearable on the nightstand — and it only became feasible once every stage moved on-device.&lt;/p&gt;

</description>
      <category>machinelearning</category>
      <category>ios</category>
      <category>sleep</category>
    </item>
    <item>
      <title>The audio feature that actually separates snoring from talking in the dark</title>
      <dc:creator>SleepTrace</dc:creator>
      <pubDate>Mon, 10 Aug 2026 00:35:26 +0000</pubDate>
      <link>https://dev.to/sleeptrace/the-audio-feature-that-actually-separates-snoring-from-talking-in-the-dark-1n4h</link>
      <guid>https://dev.to/sleeptrace/the-audio-feature-that-actually-separates-snoring-from-talking-in-the-dark-1n4h</guid>
      <description>&lt;h1&gt;
  
  
  The audio feature that actually separates snoring from talking in the dark
&lt;/h1&gt;

&lt;p&gt;Distinguishing a snore from a sentence, or a gasp from a cough, is the core problem in acoustic sleep analysis — and the one that most sleep apps paper over. A naive classifier that fires on "loud breathing in the 200–3000 Hz band" will flag every partner conversation, dog bark, and car horn at 2am as "sleep-disordered breathing." That is why event counts from most apps are unreliable.&lt;/p&gt;

&lt;p&gt;This is the signal-processing breakdown of the feature set that &lt;a href="https://sleeptrace.app/" rel="noopener noreferrer"&gt;SleepTrace&lt;/a&gt; actually uses, and why it works on-device without a server.&lt;/p&gt;

&lt;h2&gt;
  
  
  The time-frequency signature of a real snore
&lt;/h2&gt;

&lt;p&gt;A human snore is not a steady tone. It is an aperiodic burst with a strong low-frequency envelope (roughly 120–450 Hz), modulated by the soft palate, with spectral energy tapering by ~2kHz. A sentence has formant structure — clear peaks at 500, 1500, 2500 Hz corresponding to vowels — and a rhythm tied to phoneme timing.&lt;/p&gt;

&lt;p&gt;The discriminator that matters:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Spectral tilt slope.&lt;/strong&gt; Snoring energy falls off rapidly above 1kHz; speech energy stays flatter. A single tilt coefficient over the 0.5–4kHz window separates most snore/sentence pairs.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Zero-crossing density in the high band.&lt;/strong&gt; Snoring is voiced and periodic at the glottal rate (~15–30 Hz, with harmonics). High-band zero-crossing density is low for snoring and high for sibilant speech ("s", "sh", "f").&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Harmonic structure.&lt;/strong&gt; Snoring harmonics sit on a single fundamental tied to vocal-fold vibration. Speech has a structured harmonic stack that tracks vowel formants.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Combining the tilt slope + high-band zero-crossing density gives a clean separator that runs in a 40-coefficient mel filterbank and costs ~90% of one CPU core on an iPhone SE for a 2-second window.&lt;/p&gt;

&lt;h2&gt;
  
  
  The breathing pause signature
&lt;/h2&gt;

&lt;p&gt;A gasp or apnea-related pause is, by definition, the absence of a periodic signal. The trick is distinguishing "quiet because paused" from "quiet because the phone is far away." The feature: a cross-correlation of the current 4-second window against the trailing 30-second median breathing rhythm. A real pause shows a sharp drop in correlation that persists past the window length and is accompanied by a sub-baseline spectral flatness (the body is still trying to breathe, quietly).&lt;/p&gt;

&lt;h2&gt;
  
  
  Why this works without cloud compute
&lt;/h2&gt;

&lt;p&gt;All of these — spectral tilt, zero-crossing density, harmonic-to-noise ratio — are cheap FFTs on 2-second windows. They map cleanly onto a small neural net (under 2MB) that runs entirely in CoreML on-device. No upload. No server-side feature store keyed off the microphone. No privacy question at all.&lt;/p&gt;

&lt;p&gt;The full on-device pipeline and the exact feature coefficients are published on &lt;a href="https://sleeptrace.app/blog/snoring-and-sleep-apnea/" rel="noopener noreferrer"&gt;the SleepTrace engineering blog&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  For builders: start with the discriminator, not the dataset
&lt;/h2&gt;

&lt;p&gt;Most sleep-audio apps collect thousands of hours of labeled data and then ship a dense transformer that runs hot and uploads raw audio. Start instead with a three-feature linear separator (tilt slope, high-band zero crossings, correlation drop) and a threshold calibrated on-device. It will beat the transformer on battery life, privacy, and honesty — and you will have a product you can ship before the cloud bill arrives.&lt;/p&gt;

</description>
      <category>audio</category>
      <category>machinelearning</category>
      <category>sleep</category>
      <category>programming</category>
    </item>
    <item>
      <title>Stop shipping sleep stage charts you cannot defend</title>
      <dc:creator>SleepTrace</dc:creator>
      <pubDate>Mon, 10 Aug 2026 00:33:44 +0000</pubDate>
      <link>https://dev.to/sleeptrace/stop-shipping-sleep-stage-charts-you-cannot-defend-2059</link>
      <guid>https://dev.to/sleeptrace/stop-shipping-sleep-stage-charts-you-cannot-defend-2059</guid>
      <description>&lt;h1&gt;
  
  
  Stop shipping sleep stage charts you cannot defend
&lt;/h1&gt;

&lt;p&gt;Sleep stage charts are everywhere now. Apple Health, Oura, Whoop, Garmin — every tracker that can guess your breathing gives you a stacked bar of "light," "deep," and "REM" sleep. The charts look scientific. The accuracy does not match the confidence they imply.&lt;/p&gt;

&lt;p&gt;Here is the honest problem: without an EEG, sleep stages are a classification task built on proxies. Movement, heart rate, and (if you're lucky) breathing rhythm are fed into a model trained against lab polysomnography. The model outputs a probability distribution, and the app draws it as a fact.&lt;/p&gt;

&lt;p&gt;The gap between "probability distribution" and "you were in REM from 01:13 to 01:47" is the entire integrity problem of consumer sleep tracking.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why stage charts survive anyway
&lt;/h2&gt;

&lt;p&gt;They survive for three reasons, none of which are technical:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;They look good.&lt;/strong&gt; A stacked bar is visual and familiar. Users understand pie charts even when they shouldn't.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;They anchor a narrative.&lt;/strong&gt; "You got 90 minutes of deep sleep" gives a user something to talk about — and to share. Sharing drives retention.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;They are internally consistent.&lt;/strong&gt; Even if wrong, the chart always sums to 100%. That completeness feels authoritative.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;None of these are reasons the chart is accurate.&lt;/p&gt;

&lt;h2&gt;
  
  
  The alternative most apps won't take
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://sleeptrace.app/" rel="noopener noreferrer"&gt;Our approach&lt;/a&gt; is to surface events instead of stages. The morning view shows: when you snored, when your breathing looked obstructed, how your rhythm shifted relative to your baseline, and — critically — the confidence level for each detection. No stage pie. No 0–100 score.&lt;/p&gt;

&lt;p&gt;The full acoustic detection pipeline, including the feature engineering that makes "obstructed breathing" detectable without an EEG, is documented on &lt;a href="https://sleeptrace.app/blog/snoring-and-sleep-apnea/" rel="noopener noreferrer"&gt;the blog&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  For builders: ship the uncertainty
&lt;/h2&gt;

&lt;p&gt;If you must ship a stage estimate, ship the uncertainty with it. A toggle between "show probability bands" and "show most-likely stage" is enough to make the user understand the chart is a model output, not a measurement. If you won't do that, stop shipping the chart. Sleep is too personal to optimize against a number that is, at best, an educated guess dressed up as data.&lt;/p&gt;

</description>
      <category>sleep</category>
      <category>health</category>
      <category>machinelearning</category>
      <category>ethics</category>
    </item>
    <item>
      <title>Field notes: why background audio on iOS fails silently at 3am</title>
      <dc:creator>SleepTrace</dc:creator>
      <pubDate>Mon, 10 Aug 2026 00:33:15 +0000</pubDate>
      <link>https://dev.to/sleeptrace/field-notes-why-background-audio-on-ios-fails-silently-at-3am-1l2j</link>
      <guid>https://dev.to/sleeptrace/field-notes-why-background-audio-on-ios-fails-silently-at-3am-1l2j</guid>
      <description>&lt;h1&gt;
  
  
  Field notes: why background audio on iOS fails silently at 3am
&lt;/h1&gt;

&lt;p&gt;If you have ever wondered why your overnight audio app "just stops" after three hours, you have met the iOS background execution budget. The phone does not kill rogue audio apps with an alert. It quietly throttles the process until the audio queue starves and your ring buffer fills with zeros. By morning, your detection looks like a flat line that never happened.&lt;/p&gt;

&lt;p&gt;Here is what the documentation does not emphasize and the simulator hides.&lt;/p&gt;

&lt;h2&gt;
  
  
  The assertion that actually holds it together
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;AVAudioSession.sharedInstance().setCategory(.record, mode: .measurement, options: [.duckOthers, .interruptSpokenAudioAndSpeechAndReading)&lt;/code&gt; is the opening move, not the whole story. You also need a background task assertion that is &lt;strong&gt;renewed before it expires&lt;/strong&gt;. A single &lt;code&gt;beginBackgroundTask&lt;/code&gt; request at launch covers ~30 seconds. Overnight, you must renew it inside the expiry handler or the OS reclaims the time budget and your audio queue stalls.&lt;/p&gt;

&lt;h2&gt;
  
  
  The queue that survives a budget reclaim
&lt;/h2&gt;

&lt;p&gt;Most audio apps use &lt;code&gt;AVAudioEngine&lt;/code&gt; with a tap on the input node. That is simple and correct until iOS decides to throttle. The more robust pattern — the one that survives a budget squeeze — is an &lt;code&gt;AVAudioInputNode&lt;/code&gt; pull rendering tap combined with a manually-managed ring buffer. Pulling (rather than pushing into a delegate) means you only allocate when you are actually permitted to run. When the OS tightens the budget, the pull simply returns silence until the next allowed window. Your buffer stays valid, and you can detect the "silent gap" as a data-quality flag rather than a crash.&lt;/p&gt;

&lt;h2&gt;
  
  
  Clock drift kills the timestamps, not the audio
&lt;/h2&gt;

&lt;p&gt;The quiet killer in overnight field work is not a crash. It is clock drift between the audio timestamp (CMTime from the engine) and wall-clock time (Date). Eight hours of 20ms windows means the timestamps diverge by enough that correlating snoring events with "what happened at 01:12" silently fails. The fix is to anchor the audio clock to &lt;code&gt;CACurrentMediaTime&lt;/code&gt; at the start of the session and re-anchor after any interruption. We &lt;a href="https://sleeptrace.app/blog/snoring-and-sleep-apnea/" rel="noopener noreferrer"&gt;document the exact drift correction&lt;/a&gt; used in production, including the code.&lt;/p&gt;

&lt;h2&gt;
  
  
  The flat-line detector
&lt;/h2&gt;

&lt;p&gt;The most useful production line you can add:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;recentBuffer&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;maxAmplitude&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;more&lt;/span&gt; &lt;span class="n"&gt;than&lt;/span&gt; &lt;span class="mi"&gt;180&lt;/span&gt; &lt;span class="nv"&gt;windows&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nf"&gt;scheduleBufferCheck&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;   &lt;span class="c1"&gt;// OS likely throttled the queue&lt;/span&gt;
    &lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"silent stretch"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;confidence&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;low&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It converts a mysterious 3am failure into an observable signal quality problem. The morning report then says "confidence low: possible OS throttling at 02:40–03:12" instead of silently presenting a flat night as "quiet sleep."&lt;/p&gt;

&lt;p&gt;The lesson: overnight iOS audio is less about capturing every sample and more about detecting when the capture was denied, then reporting honesty about it.&lt;/p&gt;

</description>
      <category>ios</category>
      <category>swift</category>
      <category>debugging</category>
      <category>mobile</category>
    </item>
    <item>
      <title>What the Apple Health sleep score does not tell you</title>
      <dc:creator>SleepTrace</dc:creator>
      <pubDate>Mon, 10 Aug 2026 00:33:09 +0000</pubDate>
      <link>https://dev.to/sleeptrace/what-the-apple-health-sleep-score-does-not-tell-you-34gm</link>
      <guid>https://dev.to/sleeptrace/what-the-apple-health-sleep-score-does-not-tell-you-34gm</guid>
      <description>&lt;h1&gt;
  
  
  What the Apple Health "sleep score" does not tell you
&lt;/h1&gt;

&lt;p&gt;Every major health platform — Apple Health, Withings, Fitbit, Whoop — now ships a sleep "score." The number looks scientific. It is not. The score is a proprietary blend of duration, consistency, and a few detected events, weighted by a formula the vendor never explains. The score is the least honest number in your health app, because it collapses a complicated night into a color and a percentage and implies an authority it does not have.&lt;/p&gt;

&lt;p&gt;The real cost is not the number itself. It is the way it turns sleep — something every human already understands instinctively — into a task to optimize, a metric to game. A low score in the morning becomes a failure you carry into the day.&lt;/p&gt;

&lt;h2&gt;
  
  
  Scores optimize for the wrong loop
&lt;/h2&gt;

&lt;p&gt;A well-designed sleep habit loop has three parts:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;An event (you slept).&lt;/li&gt;
&lt;li&gt;Information you can act on ("you snored more after your 9pm coffee").&lt;/li&gt;
&lt;li&gt;A small lever you can pull tomorrow.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;A sleep score replaces part 2 with "your score is 64." There is no lever in "64." The score makes people scroll through a dashboard, not change a behavior. It is engagement theater.&lt;/p&gt;

&lt;h2&gt;
  
  
  What your phone already knows that the score hides
&lt;/h2&gt;

&lt;p&gt;A phone-based tracker that listens at night knows things the score never surfaces:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Whether you turned in later than intended (the timestamp of the last detected sound).&lt;/li&gt;
&lt;li&gt;Whether you had a snoring cluster around 01:00 — and whether that matches your alcohol log from 22:00.&lt;/li&gt;
&lt;li&gt;Whether your breathing was restless even if your movement was low (quiet tossing).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Those are actionable. "Score: 64" is not.&lt;/p&gt;

&lt;h2&gt;
  
  
  The alternative: event-based feedback
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://sleeptrace.app/" rel="noopener noreferrer"&gt;SleepTrace&lt;/a&gt; surfaces the night as a list of detected events with confidence ranges, not a number. The default morning view answers three questions:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Did I snore, and how did it change from my baseline?&lt;/li&gt;
&lt;li&gt;Did my breathing look obstructed anywhere?&lt;/li&gt;
&lt;li&gt;What is one lever I can pull tonight?&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The full technical explanation of the acoustic event detection is available on &lt;a href="https://sleeptrace.app/blog/snoring-and-sleep-apnea/" rel="noopener noreferrer"&gt;the blog&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  For builders: don't ship a score unless you can defend it
&lt;/h2&gt;

&lt;p&gt;If you are going to assign a number, you must be able to answer: (a) what it is derived from, (b) how accurate it is, and (c) what the user does with it. Most apps cannot answer (c). Until they can, they should stop pretending sleep is a fraction to be optimized.&lt;/p&gt;

</description>
      <category>health</category>
      <category>apple</category>
      <category>healthkit</category>
      <category>ux</category>
    </item>
    <item>
      <title>How to process 8 hours of audio on an iPhone without draining the battery</title>
      <dc:creator>SleepTrace</dc:creator>
      <pubDate>Mon, 10 Aug 2026 00:32:15 +0000</pubDate>
      <link>https://dev.to/sleeptrace/how-to-process-8-hours-of-audio-on-an-iphone-without-draining-the-battery-ab9</link>
      <guid>https://dev.to/sleeptrace/how-to-process-8-hours-of-audio-on-an-iphone-without-draining-the-battery-ab9</guid>
      <description>&lt;h1&gt;
  
  
  How to process 8 hours of audio on an iPhone without draining the battery
&lt;/h1&gt;

&lt;p&gt;An iPhone on a nightstand recording all night sounds impossible from a battery standpoint. A naive &lt;code&gt;AVAudioEngine&lt;/code&gt; setup recording 48 kHz stereo for eight hours would torch the battery and the CPU. Yet &lt;a href="https://sleeptrace.app/" rel="noopener noreferrer"&gt;SleepTrace&lt;/a&gt; does exactly this routinely and consistently comes back above 20% battery. The trick is not a clever algorithm — it is a ruthless pipeline architecture.&lt;/p&gt;

&lt;p&gt;Here is the field-tested stack that actually runs overnight on device.&lt;/p&gt;

&lt;h2&gt;
  
  
  Rule 1: never record at full fidelity all night
&lt;/h2&gt;

&lt;p&gt;You do not need 48 kHz to detect a snore. The discriminative information for breathing, snoring, and grinding lives comfortably below 16 kHz. The pipeline:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Input at hardware native&lt;/strong&gt; (AVAudioSession shared model, no re-render), but immediately downmix to mono.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Resample to 12.8 kHz&lt;/strong&gt; in a fixed-size ring buffer. This preserves enough to detect snoring (roughly 200–1800 Hz with harmonics) and cuts the per-sample cost dramatically.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Discard the raw PCM immediately&lt;/strong&gt; after the FFT/window is emitted. Keep only the feature vector in memory.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Rule 2: batch, do not stream continuously
&lt;/h2&gt;

&lt;p&gt;Continuous audio capture with a real-time callback keeps the CPU warm all night. Instead:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Capture in 2-second windows, 50% overlap.&lt;/li&gt;
&lt;li&gt;Run each window through a lightweight feature extractor (spectral flux + zero-crossing + band energy).&lt;/li&gt;
&lt;li&gt;Sleep the process between windows using a background task assertion.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This yields a bursty CPU profile: ~250ms of work every 2 seconds, then sleep. Battery impact is negligible.&lt;/p&gt;

&lt;h2&gt;
  
  
  Rule 3: do the ML classification in bursts
&lt;/h2&gt;

&lt;p&gt;The model itself is small (sub-10MB CoreML), but running it every window still costs. The actual architecture:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Feature vectors are written to a ring buffer.&lt;/li&gt;
&lt;li&gt;Every fifth window, the ring is evaluated as a sequence with a temporal smoothing pass.&lt;/li&gt;
&lt;li&gt;Results are written to a compact log: timestamps + classification + confidence.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is where &lt;a href="https://sleeptrace.app/blog/snoring-and-sleep-apnea/" rel="noopener noreferrer"&gt;the snore/apnea detection detail lives&lt;/a&gt; — the acoustic feature engineering is straightforward; the trick is not running it continuously.&lt;/p&gt;

&lt;h2&gt;
  
  
  Rule 4: guard the background task correctly
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="c1"&gt;// request a background task that expires gracefully&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;task&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;UIApplication&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;shared&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;beginBackgroundTask&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;withName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"audio-processing"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// flush buffer, end cleanly&lt;/span&gt;
    &lt;span class="n"&gt;processor&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;flush&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Without this, iOS suspends the process within ~30 seconds of the screen locking. With it, the pipeline runs undisturbed for the full night.&lt;/p&gt;

&lt;h2&gt;
  
  
  Rule 5: verify, then forget the raw audio
&lt;/h2&gt;

&lt;p&gt;Only the classification log (a few hundred kilobytes) survives the night. The raw ring buffers are overwritten the next morning. On-device only, always.&lt;/p&gt;

&lt;p&gt;The whole pipeline fits under 50MB of RAM and uses roughly 10–14% of a single night's battery on modern iPhones. The constraint that solved it was not faster hardware — it was treating raw audio as ephemeral by design.&lt;/p&gt;

</description>
      <category>ios</category>
      <category>swift</category>
      <category>audio</category>
      <category>mobile</category>
    </item>
    <item>
      <title>Why "sleep stage detection" is the wrong metric for a phone app</title>
      <dc:creator>SleepTrace</dc:creator>
      <pubDate>Mon, 10 Aug 2026 00:32:13 +0000</pubDate>
      <link>https://dev.to/sleeptrace/why-sleep-stage-detection-is-the-wrong-metric-for-a-phone-app-1p2</link>
      <guid>https://dev.to/sleeptrace/why-sleep-stage-detection-is-the-wrong-metric-for-a-phone-app-1p2</guid>
      <description>&lt;h1&gt;
  
  
  Why "sleep stage detection" is the wrong metric for a phone app
&lt;/h1&gt;

&lt;p&gt;Sleep apps ship stage charts like they're medical readouts. A pie chart that says "you were 23% deep sleep" feels authoritative, but it is a guess built on movement, breathing rhythm, and time-of-night priors — not an EEG. A phone on the nightstand does not have access to the brain waves that actually define N1, N2, N3, and REM.&lt;/p&gt;

&lt;p&gt;That does not make phone-based sleep tracking useless. It makes it useful for the things that are actually measurable and actually matter: &lt;strong&gt;when you snored, how often you gasped, whether your breathing paused, and how consistent your rhythm was night to night.&lt;/strong&gt; Those are acoustic events, not inferred brain states.&lt;/p&gt;

&lt;p&gt;The mistake is presenting proxies as measurements. The fix is presenting measurements as proxies.&lt;/p&gt;

&lt;h2&gt;
  
  
  What a phone can actually hear
&lt;/h2&gt;

&lt;p&gt;The accelerometer on a phone on the nightstand gives you movement: did the phone shake, did the table vibrate. That is coarse. The microphone gives you a far richer signal. You can detect:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Snoring intensity and duration per sleep cycle.&lt;/li&gt;
&lt;li&gt;Breathing pauses that correlate with apnea events.&lt;/li&gt;
&lt;li&gt;Positional snoring patterns (back vs. side).&lt;/li&gt;
&lt;li&gt;Environmental noise that fragments sleep.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;None of that maps to "Stage 2 sleep," but a user who learns "you snored for 18 minutes in the 01:00 cycle and gasped twice" has something actionable. A user who learns "deep sleep dropped 3%" does not, because deep sleep on a phone is a probabilistic model output dressed as fact.&lt;/p&gt;

&lt;h2&gt;
  
  
  The right metric stack
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Events, not stages.&lt;/strong&gt; Lead with concrete detected events: snore episodes, breathing pauses, position changes.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Consistency, not snapshots.&lt;/strong&gt; "Three nights in a row over your baseline" beats "your score is 71/100."&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Behavioral levers.&lt;/strong&gt; Tie events to inputs: "on nights with alcohol after 8pm, snoring doubled."&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Confidence flags.&lt;/strong&gt; Tell the user when the signal was weak (phone too far, noisy environment) rather than hiding uncertainty behind a number.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;We built exactly this in &lt;a href="https://sleeptrace.app/" rel="noopener noreferrer"&gt;SleepTrace&lt;/a&gt;: the iPhone on the nightstand runs an on-device audio pipeline that surfaces events and trends, not a stage pie chart you can't act on. The full technical write-up of the acoustic detection is &lt;a href="https://sleeptrace.app/blog/snoring-and-sleep-apnea/" rel="noopener noreferrer"&gt;on the blog&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  For builders: stop optimizing for the chart
&lt;/h2&gt;

&lt;p&gt;If you are deciding what to ship, ask not "does our model predict sleep stage" but "does the user change behavior after seeing this number." The latter is honest. The former usually is not.&lt;/p&gt;

</description>
      <category>sleep</category>
      <category>health</category>
      <category>mobile</category>
    </item>
  </channel>
</rss>
