Layer ambient sounds for better sleep, focus & calm
02 — The Problem
Sleep deprivation is a public health crisis — and existing apps make it worse by forcing listeners into rigid, unskippable playlists with no creative control.
03 — Overview
A cross-platform ambient sound mixer built for real people — not to upsell a subscription. One app, three audiences, zero ads.
04 — Core Features
Every feature exists to solve a real friction point — not to fill a feature list.
Play any combination of sounds simultaneously with per-layer volume sliders. Rain + thunder + ocean at exactly your balance.
Presets at 15, 20, 30, 45, 60 min. A smooth cross-fade gradually drops all layers to silence so you drift off naturally.
Pick any audio file from your device library. It's saved permanently — import once, use forever across any session.
Audio continues when you lock the screen or switch apps. Registered in the OS audio session — no interruption, no silence.
Play, pause, and stop directly from Control Center on iOS or the Android media notification — no need to unlock your phone.
Detects when the device enters low-power mode or a phone call interrupts and handles audio gracefully — no loud resume bursts.
05 — Sound Library
Every track is user-owned audio — no licensing fees, no royalty exposure. Additional sounds can be imported from any device file at any time.
06 — App Sections
Each section has its own visual identity, sound set, and timer presets — but they share a single audio engine so sounds from different sections never conflict.
Dark immersive layout, background scene image, all 4 ambient tracks with independent volume. Home of the sleep timer.
Manages custom-imported audio. Pick from device, rename, categorise, remove. Persisted across sessions via AsyncStorage.
Minimal interface, 2 focus-friendly tracks. Teal accent, timer presets tuned for Pomodoro-style work sessions.
Soft violet theme, child-safe sounds, shorter timer presets (15–45 min), import flow for custom lullabies.
07 — Tech Stack
One JavaScript codebase targets iOS, Android, and browser — with native platform APIs accessed through carefully maintained stubs where necessary.
| Layer | Technology | Why this choice |
|---|---|---|
| Framework | React Native + Expo SDK 51 | Single JS codebase for iOS, Android, and web; managed workflow keeps native config declarative |
| Audio engine | expo-av | Independent Sound objects per layer enable true simultaneous multi-channel playback with per-channel volume |
| Lock screen | react-native-track-player + navigator.mediaSession | RNTP owns the native media session on iOS/Android; MediaSession API covers browsers — same JS interface, zero duplication |
| Persistence | AsyncStorage | Offline-first key-value store; library tracks, preferences, and onboarding state survive app restarts without a server |
| Navigation | React Navigation (tab + stack) | Bottom-tab navigator with stack overlays — familiar pattern, works identically on all three platforms |
| Audio import | expo-document-picker | Cross-platform file picker with MIME filtering; picked URIs are stored and re-validated on each session start |
| Build + deploy | EAS Build + EAS Update | Cloud builds for App Store / Play Store binaries; OTA JS updates ship without a review cycle |
| Typography | expo-font (custom fonts) | Fonts loaded before first render with a splash-screen hold; fallback font shown if load fails (no FOUT) |
| Accessibility | Dynamic Type + allowFontScaling | All text components respect iOS Dynamic Type and Android font scale up to maximum — tested at every preset size |
08 — Engineering Highlights
Building a cross-platform audio app exposes edge cases that single-platform apps never face. Here are the ones that required the most care.
expo-av Sound objects all run on the native audio thread, but React Native's JS bridge is single-threaded. Each layer's volume, play/pause, and fade state is maintained independently in JS and applied to its Sound object without coordination locks — possible because expo-av operations are fire-and-forget promises that the OS serialises natively. This means 4 simultaneous layers with independent cross-fades never block the UI thread.
react-native-track-player's ESM source files crash Node 20's CJS→ESM interop before webpack even starts. The fix: a CJS no-op stub pre-populated into Node's require cache before @expo/webpack-config loads, plus a webpack resolve alias so the bundle never touches the native package. The same pattern is applied to any native-only import a task agent adds — a single stub file is cheaper than a fork.
RNTP needs to be in a "playing" state to show iOS Control Center and Android media notifications. Loading it with a real sound would create a second audio session that fights expo-av's mixer. The solution: a 1-second silent WAV in RepeatMode.Track gives RNTP a perpetually "playing" track that emits no audio — expo-av exclusively owns the real output. Remote command handlers (play/pause/stop) are wired directly to the AudioEngine singleton in the app JS context, not in RNTP's background service.
The fade engine steps volume across all active layers in parallel using a shared interval, not per-layer timeouts. Phone calls, Bluetooth reconnects, and manual pause/resume during a fade are all handled by state flags that pause the interval and resume from the last achieved volume rather than restarting — preventing both volume spikes on resume and silent failures when the OS interrupts mid-fade.
Imported tracks are stored as file-system URIs in AsyncStorage. URIs become stale when the OS moves or sandboxes the file (common after iOS updates). On session start, each stored URI is validated; stale entries surface a non-blocking re-import prompt on whichever screen is showing the affected track — the rest of the library continues to work normally.
09 — Progress
The app has gone through a full development and QA cycle. Remaining items are pre-launch polish, not core functionality.
10 — Closing
A production-quality ambient sound mixer built from scratch — cross-platform, offline-first, and engineered to handle the edge cases that ship-and-pray apps miss. Built by Tynell Sene's.