Skip to content

feat: opt-in microphone snore detection that keeps only snore times - #74

Merged
ng merged 3 commits into
devfrom
feat/mic-snore
Oct 23, 2026
Merged

ng merged 3 commits into
devfrom
feat/mic-snore

Conversation

@ng

@ng ng commented Oct 23, 2026

Copy link
Copy Markdown
Contributor

Summary

Opt-in overnight snore detection with the iPhone microphone. It implements section 2 of sleepypod/training's overnight capture plan. Find it under Settings → Health → Snore detection (Beta).

  • Listening: AVAudioSession in the record category, with the audio background mode added to Info.plist. An AVAudioEngine input tap feeds Apple's built-in classifier (SNClassifySoundRequest(.version1) through SNAudioStreamAnalyzer, 3 s windows, 0.5 overlap). Capture refuses to start when the classifier's knownClassifications has no snoring label.
  • Events: a window opens an event at confidence ≥ 0.6. The event stays open while windows stay ≥ 0.4, closes after two quieter windows, and merges with another event less than 3 s away. Times come from the audio buffers' host time, anchored once to wall-clock time.
  • Knocks: a transient detector finds knock onsets in memory. Only those in the first and last 60 s are kept, for pod clock sync.
  • Interruptions: calls and alarms pause listening, and the gap is recorded. Route changes restart the engine. App termination saves the session. Denied or revoked microphone access stops capture with a link to Settings.
  • Privacy: only event metadata, coverage, interruptions and knock times are stored, in Application Support/Snore/snore-<captureId>.json. Raw audio, transcripts and embeddings are never written or sent. Nothing uploads automatically; the screen exports the session through the share sheet.
  • Format: docs/snore-capture.md documents it, with events in the training contract fields (captureId, startMs, endMs, eventType, confidence, detectorVersion, labelOrigin).
  • Morning summary: time listened, snoring detected, events, longest event, interruptions and knocks. The session can be exported or deleted.

project.yml is unchanged. Info.plist gets only NSMicrophoneUsageDescription and the audio background mode.

Test plan

  • xcodebuild build -scheme Sleepypod -destination generic/platform=iOS SWIFT_TREAT_WARNINGS_AS_ERRORS=YES (CI build)
  • xcodebuild test -only-testing:SleepypodTests on an iOS 26 simulator. The new suites cover:
    • event hysteresis, merging and thresholds;
    • coverage bookkeeping;
    • the knock detector on synthetic buffers (refractory period, warm-up, minimum peak) and the opening/closing knock window;
    • the capture state machine;
    • the host-clock mapping;
    • JSON contract keys and round trip, with no audio fields;
    • the service with a fake audio source: a night end to end, interruption and route-change gaps, termination save, failed restart, denied/refused/granted permission, an unavailable classifier, deletion, and notification parsing and delivery.
  • Coverage of the new code:
    • SnoreDetection.swift 99.4%;
    • SnoreCaptureService.swift 93.3%;
    • SnoreAudioCapture.swift 8%, the hardware-bound engine and classifier glue;
    • SnoreCaptureView.swift UI, untested, as other views here.
  • swiftlint --strict
  • On device:
    1. Open Settings → Health → Snore detection, tap Start listening, and allow the microphone.
    2. Knock three times on the bed frame.
    3. Lock the phone, plugged in, on the nightstand.
    4. Play or imitate snoring for a minute, then talk and run a fan.
    5. Knock again, then tap Stop.
    6. Check the summary (snore events, knocks: 6) and Export for training.
  • On device: start, take a phone call, end it; the summary shows one interruption and listening resumed.
  • On device: deny the microphone; the screen explains how to enable it.
  • Overnight on device: measure battery use and confirm listening continues with the screen locked.

Limitations

  • Overnight background behavior, battery use and detection precision in a real bedroom aren't measured.
  • The plan's consented calibration session (scripted snoring, speech and fan audio for precision and threshold choice) isn't built yet.
  • Classifier confidence isn't a calibrated probability.
  • Audio alone doesn't say who snored.

🤖 Generated with Claude Code

https://claude.ai/code/session_016Wac1uHSpomCEBtwBdw1YA

@coderabbitai

coderabbitai Bot commented Oct 23, 2026 •

Copy link
Copy Markdown

Warning

Review limit reached

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

Next included review available in 18 minutes.

Check out review usage here.

View limit details

Limit details: You’ve used the included review currently available.

Learn how review limits work.

Review configuration:

⚙️ Run configuration
  • Source mode: Auto
  • Repository: Unavailable
  • Branch: Unavailable
  • File: No configuration file from this repository applied
  • Commit: Unavailable
  • Layers: defaults
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 60ff52c3-038c-40cc-82c2-0832f328de85
📥 Commits

Reviewing files that changed from the base of the PR and between 8e0ae43 and de14b1c.

📒 Files selected for processing: 11
  • Sleepypod.xcodeproj/project.pbxproj
  • Sleepypod/Info.plist
  • Sleepypod/Services/Snore/SnoreAudioCapture.swift
  • Sleepypod/Services/Snore/SnoreCaptureService.swift
  • Sleepypod/Services/Snore/SnoreDetection.swift
  • Sleepypod/SleepypodApp.swift
  • Sleepypod/Views/Settings/SettingsSheet.swift
  • Sleepypod/Views/Settings/SnoreCaptureView.swift
  • SleepypodTests/SnoreCaptureTests.swift
  • SleepypodTests/StageModelTests.swift
  • docs/snore-capture.md
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@codecov

codecov Bot commented Oct 23, 2026 •

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ All tests successful. No failed tests found.

📢 Thoughts on this report? Let us know!

ng and others added 2 commits October 22, 2026 20:21
…es, never audio

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016Wac1uHSpomCEBtwBdw1YA
Signed-off-by: Jonathan Ng <jon@jonathanng.com>
…essions, block double starts, keep sessions out of backups, restart only on input changes

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016Wac1uHSpomCEBtwBdw1YA
Signed-off-by: Jonathan Ng <jon@jonathanng.com>
…cking on CI

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016Wac1uHSpomCEBtwBdw1YA
Signed-off-by: Jonathan Ng <jon@jonathanng.com>
@ng
ng added this pull request to the merge queue Oct 23, 2026
@github-merge-queue
github-merge-queue Bot removed this pull request from the merge queue due to failed status checks Oct 23, 2026
@ng
ng added this pull request to the merge queue Oct 23, 2026
Merged via the queue into dev with commit 7a9696b Oct 23, 2026
6 checks passed
@ng
ng deleted the feat/mic-snore branch October 23, 2026 04:14
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant