MOYISNORE
ACOUSTIC PIPELINE

How MoyiSnore turns eight hours of sound into a night you can read

Two things run while you sleep: an AAC encoder and a 20 Hz feature loop that keeps a 10 Hz loudness envelope. That is all. No machine learning runs overnight, nothing is uploaded, and no detection decision is made in the dark — decisions made at 3 a.m. against partial data cannot be revisited. In the morning the app walks the recording once: the envelope picks out the active stretches, Apple's sound classifier reads only those, and the surviving windows become events with clips, loudness and severity. Every number on this page is read from the shipping source.

UPDATE · 2026-11-06

The paragraph above describes the original design. Since then there has been a breakthrough, and MoyiSnore now has two modes: overnight — record all night and run the full-night ML in the morning, as described on this page — and real-time, where ML runs while you sleep.

Parameters below are taken from the app source (AudioPipeline, Screeners, EnvelopeGate, BatchProcessor, Theme, CLOUD_SYNC_SPEC). Synthetic data is labelled as such.

01 / THREE-STAGE FUNNEL

Cheap at night, thorough in the morning

The design rule is that the overnight stage must survive eight hours of iOS background scheduling on battery. So the overnight stage does arithmetic only — rectify, filter, accumulate — and every decision that benefits from hindsight waits for morning, when the whole file is on disk and a wrong threshold can simply be run again.

NIGHT

Capture

AVAudioEngine tap, 4800-frame buffers (100 ms at 48 kHz). Session category .playAndRecord with mode .measurement, which turns off the system AGC and EQ so the measurement stays clean. A silent player node keeps the session alive through calls and alarms.

AAC m4a · device sample rate · ≈200 MB/night
NIGHT

Frame features

50 ms frames, 20 per second. Each frame yields four numbers: raw RMS, snore-band RMS, voice-band RMS and zero-crossing rate. Two 4th-order Biquad chains run in parallel — 40–400 Hz for snoring, 250–3000 Hz for speech.

4 floats × 20 fps
NIGHT

Envelope

Every two frames the louder value is kept, giving a 10 Hz loudness track — peaks must not be averaged away. Written to disk every 30 s. Ten hours is about 1.2 MB.

10 Hz · max-of-2 · ~1.2 MB
MORNING

Silence gate

The envelope, already computed, is scanned for active stretches: threshold = 20th-percentile noise floor × 2.5, each stretch padded by 2 s, stretches merged when less than 5 s apart. A relative floor is what makes a hotel with loud air conditioning and a silent bedroom both work.

P20 × 2.5 · pad 2 s · merge 5 s
MORNING

Classification

SNAudioStreamAnalyzer with the built-in Apple classifier, fed only the active stretches at their true file positions. Snoring is {snoring, snort}; sleep-talk is {speech, whispering, shout, singing, babbling}.

SNClassifySoundRequest .version1
MORNING

Events

Same-class windows merge when the gap is 2 s or less; anything under 0.5 s is dropped. Each survivor gets a clip cut with 1 s on either side and normalised to 0.9 peak, plus peak dB, mean level and a severity score.

merge 2 s · min 0.5 s · clip ±1 s
MORNING

Cleanup

The full-night recording has done its job and is deleted by default — the clips are what you keep. Transcription is on demand, per event, when you tap it.

master audio deleted

Three ways the gate stands aside

No envelope, or a floor that makes no sense
Fall back to analysing the whole file. Skipping ML because a threshold misfired would silently lose a night.
Active share ≥ 70%
Fall back to the whole file. There is nothing left to save, and the single-pass path is simpler and more reliable.
Everything below threshold
Genuinely silent night: no ML run at all.
02 / NIGHT-WEAVE

Scrub a night, cell by cell

One row is one hour; one cell is ten seconds. Cell brightness is loudness on a fixed scale, so rows from different nights can be laid side by side — most sleep apps normalise each night to its own loudest moment, which makes every night look identical. Drag across the weave to read what the pipeline sees at that instant. The night is synthetic; the parameters, colours and mapping are the app's.

Drag across the weave · 08:00 h · 10 s / cell
Time —
Raw RMS —
Snore band 40–400 Hz —
Voice band 250–3000 Hz —
Zero-crossing rate —
Gate —
Classifier —
App theme · 29

Loudness here is dBFS, digital full scale. The app also presents an estimated dB SPL, which depends on the device and where the phone sits — consistent night to night, not a calibrated sound-level meter.

03 / THE SILENCE GATE

How much of the night the classifier ever hears

This is the decision that turns a five-minute analysis into seconds. The envelope is cheap and already on disk; the gate uses it to decide what deserves the expensive pass. Move the three controls and watch the active share move. The 20th-percentile floor is fixed by the data, not by you — that is the point of a relative threshold.

2.5 × P20
2.0 s
5.0 s
Active share
—
— segments
P20 floor: — Threshold: — Path: —

Envelope shown at 10 Hz, max-pooled to the canvas width. Highlighted stretches are what SNAudioStreamAnalyzer receives, at their true frame positions, so window timestamps need no conversion.

04 / THE ML THRESHOLD

Sensitivity, and why top-1 is not enough

The sensitivity setting is one number, 1 to 10, and it moves the minimum confidence from 0.70 down to 0.25 — strict to permissive, 0.50 at the default of 5. The subtler decision is how far down the classifier output to look. A hotel air conditioner produces a constant class that holds first place all night, pushing snoring into second. Reading only the top result loses the whole night; the app scans the top five and takes the first target class that clears the threshold.

5 / 10
Minimum confidence
0.50
Events detected
—
—
If only top-1 were read
—
—
snore sleep-talk windows below threshold

Synthetic windows with confidences in the range the real classifier produces. Merge and minimum-duration rules are the shipping ones: same class within 2 s merges, under 0.5 s is dropped.

One window, as the classifier returns it

1 white noise 0.71 —
2 mechanical fan 0.63 —
3 snoring 0.58 ← target ✓
4 breathing 0.31 —
5 speech 0.12 —

A real top-5 from a room with a fan running. The first target class above threshold wins, regardless of rank.

05 / BANDS AND FEATURES

What the overnight loop actually computes

Snoring is low-frequency and speech is mid-frequency, so the overnight loop keeps both bands separately. It also keeps a dominance test: low above mid × 2 for snoring, mid above low × 0.35 for speech. Measured against real zh-TW speech through the pipeline, only about 23% of speech frames reach low > mid × 2 — which is why talking does not register as a snore. These screeners drive the engineering-mode instrument panel and shaped the design; the events they produce overnight are computed and discarded, because the morning classifier is the detector.

Per frame, every 50 ms

rawRMS Unfiltered RMS Live waveform, ribbon, envelope
lowRMS 40–400 Hz RMS Snore screener, dominance test
midRMS 250–3000 Hz RMS Voice screener, dominance test
zcr Zero crossings ÷ frame length Speech-likeness, 0.006–0.35

Screener state machines, as instrumented

Adaptive floor EMA, α = 0.002, updated only while not triggered — updating inside an event chases the threshold upward until the event eats itself.
Trigger max(floor × ratio, 0.0009). The absolute lower bound is about −61 dBFS, so a dead-silent room does not fire on its own noise.
Hysteresis Release at 0.6 × trigger, held for 1.2 s — snoring has a breathing gap between pulses.
Onset 4 consecutive frames (200 ms) for snoring, 6 (300 ms) for speech.
Event bounds Snore 0.5–30 s, sleep-talk 0.4–20 s. The end is the last frame above release, not where the wait ran out.
Constant-noise guard A snore event cannot last 8 s — breathing ends it. Eight seconds without release means a fan or an air conditioner: the floor learns up to 90% of the observed peak and the stretch is discarded.
Sensitivity mapping 1 → 5.0 ×, 10 → 1.5 ×, linear; the default 5 gives 3.4 × above the floor.
06 / SPECIFICATIONS

Numbers

Platform iPhone and iPad, iOS 17.6 or later
Audio session .playAndRecord, mode .measurement — AGC and EQ off
Tap buffer 4800 frames, about 100 ms at 48 kHz
Master recording AAC m4a, device sample rate, quality medium, ≈200 MB per night
Frame rate 50 ms frames, 20 fps
Filter bands Snore 40–400 Hz, voice 250–3000 Hz, 4th-order Biquad each
Envelope 10 Hz, max of two frames, flushed every 30 s, ≈1.2 MB per 10 h
Raw ring buffer 45 s of audio, for clip extraction
Silence gate 20th-percentile floor × 2.5, pad 2 s, merge 5 s, bail at 70% active
Classifier SNClassifySoundRequest, identifier .version1, on-device
Confidence range 0.70 (sensitivity 1) to 0.25 (sensitivity 10), 0.50 default
Class sets snoring, snort / speech, whispering, shout, singing, babbling
Event rules Merge same class within 2 s, drop under 0.5 s
Clip Event ±1 s, normalised to 0.9 peak
Severity 0–100: 0.6 × loudness (−50 → −10 dBFS) + 0.4 × duration (0 → 20 s)
Transcription zh-TW, requiresOnDeviceRecognition = true, 30 s timeout, on demand
New engine iOS 26 SpeechTranscriber preferred when installed, on-device
Sync CloudKit private database, custom SessionsZone, CKSyncEngine + CKOperation
Network calls None anywhere in the project
Themes 29 presets, each with a dark and a light palette
Ribbon cell 10 s per cell, absolute scale by default
Timeline scaling 95th-percentile normalisation, so one loud bang does not flatten the night
07 / DATA ARCHITECTURE

What moves, and what cannot

There is no server of ours and no account system. Sync uses CloudKit's private database under your own Apple ID: metadata travels through CKSyncEngine, assets through direct operations so that clips can be fetched only when you press play. The full-night recording is never uploaded — it is deleted locally after analysis. Offline is not an error state: if iCloud is unavailable the app is the local-only app it started as, and network state can never block a recording from starting.

session.json — events, times, state tens of KB Always in iCloud, always local
envelope.bin — 10 Hz night envelope ~1.2 MB Always in iCloud, always local
clips/evt-* — event audio a few MB In iCloud; local copy can be offloaded and re-fetched on play
audio.m4a — full-night recording ~200 MB Never uploaded, deleted after analysis
Account, email, identifiers — Not collected, nothing to send
Analytics, ads, crash SDKs — Not present

Two devices, one microphone

A single CloudKit record holds a recording lock, taken by compare-and-swap and kept alive by a heartbeat every 5 minutes against a 15-minute lease — a device that runs out of battery never gets to release anything, so a lock without a heartbeat would deadlock forever. And because the lock is a courtesy, not a correctness gate: if the check fails or you are offline, recording starts anyway. Two overlapping sessions are two UUIDs, both kept.

08 / LIMITS

What this cannot tell you

Not a diagnosis

MoyiSnore is not a medical device. It records and measures sound; it does not detect apnea or any condition. An objective, timestamped record is useful to bring to a physician — it is not a substitute for one.

Two sleepers, one microphone

A phone cannot tell whose snore it heard. In a shared bed, read the report as a record of the room.

Placement changes the numbers

Loudness depends on distance and on what the phone rests on. Same place every night is what makes nights comparable.

General classifier, not a snore model

The app uses Apple's built-in sound classifier rather than a model trained on snoring. It is honest about that: every event keeps its class, its confidence and its audio clip, so you can check any of it yourself.

False positives survive the funnel

A fan, a passing vehicle, a partner turning over. The constant-noise guard and the confidence threshold remove most of it, not all.

09 / GLOSSARY

Terms used on this page

RMS
Root mean square — the energy of a frame of audio, the honest way to say loudness for a signal that swings either side of zero.
dBFS
Decibels relative to digital full scale: 0 dBFS is the loudest a sample can be, everything real is negative. Not the same as dB SPL, which is sound pressure in the room.
ZCR
Zero-crossing rate — how often the waveform crosses zero within a frame. Cheap to compute and a surprisingly good hint at whether a sound is voiced, rumbling or hissy.
Biquad
A two-pole, two-zero digital filter section. Four in series give the 24 dB per octave skirts that keep the snore and voice bands apart.
Envelope
The slow outline of a signal's loudness. Here: 10 values per second, each the louder of two 50 ms frames.
Percentile floor
Using the 20th-percentile value as the noise floor instead of a fixed number, because most of a night is quiet and that is where the floor lives.
Hysteresis
Different thresholds for starting and stopping, so a signal hovering at the line does not chatter on and off.
Somniloquy
Sleep-talking. Transient, often quiet, and — unlike snoring — mid-frequency.
SoundAnalysis
Apple's on-device audio classification framework. The built-in .version1 classifier covers several hundred everyday sound classes, snoring among them.
Neural Engine
The dedicated inference silicon in Apple chips; it is why a whole night can be classified in seconds without the battery cost of doing it live.
CKAsset
A file attached to a CloudKit record. Clips and envelopes travel as assets, which is what makes fetch-on-play possible.
Compare-and-swap
Write only if the record has not changed since you read it. CloudKit's optimistic lock, and the whole basis of the recording lock.
10 / REFERENCES

Acoustic foundations

[1]
Perez-Padilla, R., et al. Characteristics of the sounds of snoring. Am Rev Respir Dis (1993)
— Basis for acoustic frequency thresholding below 1000 Hz and fundamental pitch estimation.
[2]
ITU-T Rec. G.729 Annex B A silence compression scheme for terminals. (1996)
— Mathematical model for short-time energy (RMS) and zero-crossing silence pre-filtering.
[3]
Gemmeke, J. F., et al. Audio Set: An ontology and human-labeled dataset for audio events. IEEE ICASSP (2017)
— Taxonomy mapping for ML-based snore and human speech classification.
[4]
Arkin, A. M. Sleeptalking: Psychology and Psychophysiology. Lawrence Erlbaum (1981)
— Temporal segmentation models for transient somniloquy events.

References inform the design; the app makes no clinical claim.

← Back to the overview