Engineering · How I built it

Hundreds of hours in Audacity: building Night & Air’s snore detector

Twelve volunteers. Forty-eight nights. What it took to turn manually labeled bedroom audio into a detector shared by two native apps.

12 min readNative audio ML

Engineering case study · Founder account

Volunteers
12
Recorded nights
48
Model features
51
Exported trees
280

The most labor-intensive part of building Night & Air was spending hundreds of hours in Audacity, listening to bedroom recordings and manually marking snore clusters. Twelve volunteers supplied recordings spanning 48 nights. That work became the foundation for a proprietary detector running locally inside a Swift iOS app and a Kotlin/Jetpack Compose Android app, through a shared C engine. The interesting engineering problem was teaching a detector what to count when the microphone also heard the room.

I am Sree Kumar, the independent founder and builder of Night & Air. I wanted to make a snoring app that helped people work through a practical question: when I change something about my routine, does the pattern of my nights change?

That question makes false positives expensive. If a fan becomes part of the snoring measurement, a change in fan speed can look like a change in the person. A polished chart cannot rescue a measurement that has drifted away from the thing it claims to describe.

This is an account of the implementation I have today: an ExtraTrees classifier exported to C, a shared signal-processing frontend, and native applications responsible for capture and lifecycle. The dataset, representation and decisions after inference all matter to the result.

The High-Intent Paradigm Shift

Recording a night is easy to explain. Deciding what to do with the result is harder. Night & Air starts with seven qualifying, reviewed baseline nights, then guides the user through one Tune-up at a time: a deliberate change followed by another period of recording and comparison.

The purpose of that structure is to make the question smaller. If someone changes their sleep position, dinner timing and evening routine together, a better week cannot identify which change helped. Keeping one change in focus makes the result more interpretable and the nightly action easier to follow.

This is a sequential, within-person comparison. It is not a randomized controlled experiment, and seven nights do not eliminate variation, confounding or regression to the mean. The app borrows the discipline of establishing a baseline and changing one thing; it does not turn a bedroom into a clinical trial.

That distinction shaped the engineering. Consistency across nights matters because the product asks users to compare them. The detector, preprocessing and threshold policy are part of the measuring instrument. Changing any of them can change the result even when the underlying audio has not changed.

It also sets the boundary for the product. Night & Air is a snoring wellness tool. Its recordings and trends can support observation and discussion; they do not diagnose sleep apnea or establish whether someone needs treatment. A quieter recording is an acoustic observation, not a medical clearance.

The Grind: Building the Dataset in Audacity

The starting material was recordings supplied by 12 volunteers over 48 nights. I spent hundreds of hours listening and visually inspecting the audio in Audacity, manually marking snore clusters. I did not use a public audio dataset for this model.

The dataset came from conversations with volunteers who supplied their overnight recordings. Twelve people and 48 nights gave me material I could inspect closely. They do not establish broad population coverage, and this project does not benchmark public datasets.

The work demanded attention to both sound and time. A label has to say where an event belongs in the recording. The waveform gives one view of that boundary; the spectrogram gives another; listening supplies the context that neither picture can settle alone. A large excursion in a waveform only establishes that something happened near the microphone.

Working through overnight material also changes what you notice. A detector has to survive long stretches of background sound between the events you care about. Those stretches are part of the task. The system must decide when to remain quiet as well as when to mark something.

I would not describe this as having created infallible ground truth. Manual labels contain judgment, especially around ambiguous boundaries and overlapping sounds. The value of doing the work was having a concrete reference I could return to while developing and inspecting the detector.

Preserve the recording you intend to measure

One important rule is to preserve the original audio and its timeline. A processed listening copy can make inspection easier, but production training and inference expect unfiltered source audio. Applying Audacity Noise Reduction or a low-pass filter to the input would create a different signal from the one the mobile pipeline expects.

This matters because “noise reduction” can describe very different operations. In this detector, noise-floor normalization is part of feature extraction. It does not mean replacing the original recording with a cleaned waveform. Keeping those meanings separate prevents a helpful annotation workflow from quietly becoming an incompatible inference workflow.

The evaluation limit I need to be explicit about

The evaluation split was by audio clip, not by volunteer. Clips from the same participant, room or recording can therefore appear on both sides of the split. Shared acoustics and neighboring material can make that evaluation more forgiving than a test on completely unseen people and bedrooms.

I cannot use it to claim unseen-person accuracy. False positives remain; I am not presenting a validated false-positive rate here. A stronger evaluation needs held-out participants and nights, with results broken out by background conditions. That is a specific limitation of the evidence, regardless of how much effort went into labeling.

Overcoming the Real-Bedroom Noise Problem: AC, Fans and Speech

A bedroom microphone receives a mixture. Air conditioning, fans, running appliances and speech can coexist with snoring. Frequency overlap makes a simple “keep this band, discard everything else” rule insufficient. A sound can be loud, low-pitched or repetitive without being the event the app intends to measure.

The current solution combines spectral features, a learned classifier and temporal evidence. It is sound-event detection in a noisy mixture. It is not source separation: the pipeline does not reconstruct an isolated snore track or erase another person's speech from the recording.

Make the representation aware of the room

For each physical recording part, the frontend downmixes the decoded channels, measures and subtracts the part's mean, and resamples to 16 kHz. It then applies a causal second-order Butterworth high-pass filter with a 50 Hz cutoff. This establishes the input expected by the deployed model.

Analysis uses a 2,048-sample symmetric Hann window and a 1,600-sample hop. At 16 kHz, those are 128-millisecond frames advancing every 100 milliseconds. Each frame produces spectral statistics and power measurements across ten bands.

The current feature representation. On small screens, swipe the table to read both columns.

Signal processing in the deployed detector
Part of the representationWhat the implementation uses
Frequency-band edges50, 100, 200, 400, 800, 1,200, 2,000, 3,000, 4,000, 6,000 and 7,900 Hz
Background referenceEach band's 20th-percentile level in decibels across the physical recording part
Spectral shapeCentroid, bandwidth, 85% rolloff, peak frequency, flatness and peak-to-mean ratio
Short temporal contextCentered differences and edge-padded three-frame and seven-frame means

For each band, the frontend subtracts its estimated background level from the current power in decibels, with the relative value bounded below at −6 dB. Ratios and fractions derived from normalized band powers add information about how energy is distributed.

The useful idea is to give the classifier information about a sound relative to the surrounding recording. A persistent background component should not be interpreted solely through its absolute level. That is the role of the representation; it is not proof that every fan or air conditioner will be rejected.

The background estimate belongs to the whole physical recording part. Recomputing it independently on an arbitrary playback excerpt can change the feature values. That dependency is one reason preprocessing is part of the model contract, rather than a collection of interchangeable audio utilities.

A tree ensemble, followed by a timing rule

The deployed scikit-learn ExtraTrees classifier contains 280 trees and receives 51 features per frame: 23 base features, 12 temporal differences and 16 context features. Its exported C implementation produces a scalar score. The score is not calibrated: 0.50 is a model threshold, not an established 50% probability of a snore.

The current bundled policy uses 0.50 for both score inclusion and playback eligibility. These are separate settings with separate roles. Local model hops qualify against the scoring threshold; playback also checks the peak of the complete candidate event. Raising a playback threshold can narrow the listening selection without redefining the scoring threshold.

There is then a periodicity requirement: accepted evidence must belong to a stable sequence of at least four qualifying pulses. The current interval limits are two to eight seconds, with a coefficient of variation (the standard deviation of the intervals divided by their mean) no greater than 0.25. This asks whether the timing resembles a repeating breathing-like pattern, rather than accepting isolated positive frames indiscriminately.

That rule introduces a trade-off. It can reject isolated false detections, but real irregular or isolated snores may also fail it. Periodicity cannot identify a person, and repetitive background sound is still a challenge. The system is deliberately narrower than a claim to recognize every snore in every bedroom.

Amplitude and signal-to-noise measurements remain useful descriptions. In the current production contract, they do not independently decide which evidence counts. A displayed loudness category, a candidate event's span and a selected 30-second playback clip must not quietly expand the measured snoring duration.

Bring the model back into Audacity

The repository includes a desktop audit tool that runs the same generated-C model and frontend as the apps. It exports raw model-hop scores, complete candidate-event labels and derived score-evidence tracks that can be inspected alongside the audio in Audacity.

Crucially, raw-hop labels are emitted upstream of the final thresholds and periodicity rules. That makes it possible to distinguish a model response from a policy decision: did the forest respond to this region, did the local score qualify, and did the timing gate retain it?

This closes a useful loop. Audacity label tracks were where I placed manual labels; it is also where I can inspect what the deployed detector actually did. A final nightly number alone would hide too much of that chain.

Architecting a Cloudless, On-Device C Pipeline

The overnight audio inference path is local. Python belongs to model development; the mobile apps run an exported C forest and its matching frontend. Swift handles the iOS application, and Kotlin with Jetpack Compose handles Android, calling the native library through JNI.

This gives the two applications a common detector while leaving platform audio capture, lifecycle, storage and presentation in native code. It also makes the numerical work inspectable outside either phone: the desktop audit tool compiles against the same core.

iPhoneSwift · AVFoundationNative capture and lifecycle
AndroidKotlin · Compose · JNINative capture and lifecycle
Shared local C engine51 features · 280 treesMatching preprocessing, model and evidence rules
Nightly score evidenceQualified time on the night timeline
Playback selectionClips chosen from eligible events
Both native apps call the shared detector. Scoring and playback retain separate eligibility rules; a listening clip’s length is not measured snoring duration.

The frontend ships with the model

Exporting the tree weights is only part of deployment. A discrepancy in resampling, window shape, feature order, percentile calculation or edge handling can feed a valid model different inputs from those it saw during training.

The implementation therefore treats the forest and its 51-feature frontend as a coupled artifact. The native host analyzers feed finalized physical recording parts through a two-pass process. The first establishes the part mean; the subsequent processing builds the features and model evidence. Complete-part statistics and centered context mean this is not accurately described as a purely causal classifier scoring each microphone sample immediately.

The repository includes Python/C feature and score reference fixtures for 16 kHz, 44.1 kHz and 48 kHz inputs. Those checks address numerical agreement. Other tests cover boundaries, interrupted recording parts and how evidence is joined on the night timeline. None of these implementation tests substitutes for a held-out bedroom accuracy study.

There is also a provenance gap to close. The deployed generated arrays are tied to a specific model-bundle hash, but that bundle lacks an immutable link to its training dataset and evaluation run. A separately retained lab model is a different forest. I should not borrow its results to describe the deployed one; the complete training-to-release chain still needs to be recovered.

Local inference is a concrete privacy choice

The detector does not need to upload an overnight recording to classify it. That avoids making remote audio processing a requirement for the core measurement. Bedroom audio is particularly personal, so this boundary matters to me independently of the engineering convenience.

“Cloudless” here describes audio inference. Profile, check-in and summary synchronization, separately controlled analysis-file sharing, and optional audio inclusion in standard iPhone backup have their own controls. Exports can also create copies outside the app. These choices are described in the privacy notice.

Compiling the model into the native core also avoids putting a Python runtime on the phone. It gives me direct control over the representation and execution path. Whether that produces acceptable overnight energy use still needs measurement on actual devices.

I have tested on an iPhone 13 mini and a Samsung A12. Those are device-test facts, not battery benchmarks. I am not attaching an energy or runtime benchmark to those device names. The iOS implementation limits live preview work, uses sparse status updates and decodes bounded audio windows. Microphone capture, storage and analysis still consume energy; a reproducible benchmark must measure that whole workload.

Why I keep building native

An overnight app has to keep working through screen locks, background transitions and interruptions. That responsibility reaches below the chart. Native Swift and Kotlin give me access to the platform behavior I need to understand, while shared C keeps the measurement logic common.

For an independent developer, that is a workable division of responsibilities: build the interface and lifecycle around each operating system, and share the detector where numerical consistency matters. It demands care in more than one stack, but it keeps the connection between a recording and its result visible.

The hours in Audacity gave me the starting evidence. The harder continuing obligation is to preserve that connection through preprocessing, model export, thresholds and the interface, while being clear about what has actually been validated.

If you build native software, work on audio ML, or want to try a more deliberate approach to tracking snoring, take a look at Night & Air and join the beta. I would welcome specific observations about the recordings and results. Those are the materials this app was built from, and they remain the most useful way to improve it.

Technical references

Implementation details reflect the October 18, 2026 source snapshot. Dataset scale, labeling effort and test devices are the founder’s account. These references explain the tools and methods; they do not validate Night & Air’s detection accuracy.

Try Night & Air

Start with your own nights.

Build a baseline, test one habit and review what changed. Explore the iPhone beta through TestFlight or the Android internal test with an approved tester account.