Entrain
A React and Capacitor app that uses rhythmic light and sound to guide brainwaves into target states, alpha relaxation, theta meditation, focused attention, while an AI agent monitors the brain's response through a Neurosity Crown EEG headset and adapts the session in real time. Live on web at entrain.still.codes, with iOS and Android builds packaged via Capacitor and planned for a future release, on a tiered Stripe subscription, the only project in this portfolio shipped commercially to the public rather than kept as a research demo.
Visit entrain.still.codesArchitecture
Neurosity Crown (Bluetooth / Firebase, in-browser SDK)
│ powerByBand, ~4 Hz per channel
▼
brain-stream: average to one scalar per band
│
▼
feedback controller
baseline (first ~30s) → rolling median smoothing
→ outlier reject → trend (rising / stable / falling)
│ │
│ data watchdog: 10s silence
│ → open-loop fallback, resumes
│ closed-loop when data returns
▼
trajectory runner (advances on elapsed duration)
│
├─▶ photic flicker (SVG, capped at 25 Hz)
└─▶ isochronic tone (Web Audio)
The feedback controller treats the arrival of powerByBand epochs, not the SDK's own heartbeat, as proof the Crown is on the head and producing data. If ten seconds pass with no epochs, an open-loop timer takes over so the session keeps advancing on schedule instead of stalling; when data resumes, the session returns to closed-loop automatically. The trajectory runner itself advances purely on elapsed duration (a stability signal is logged for the post-session retrospect but never blocks progression), since an earlier design that waited for a stable EEG trend could stall a multi-step session on step zero.
Entrain talks to the Neurosity Crown directly from the client through the Neurosity SDK. An earlier architecture (see Observable, elsewhere in this portfolio) routed EEG through a separate Rust daemon and broadcast WebSocket (five hops between the headset and the app) and made mobile deployment impossible, since the daemon had to run on a desktop. Removing that hop was also what made the iOS and Android builds viable.
Privacy by construction: raw EEG never leaves the deviceOnly the computed band-power time series, controller decisions, and trajectory records are persisted, never raw 256 Hz per-channel samples. Signed-in subscribers sync those same computed session records to private, per-user Supabase Storage for cross-device restore; the account layer never sees raw brainwave data. Storage is local-first: session records write to the browser's IndexedDB before they sync to Supabase, the same local-first pattern Observable used for its own session archive. Signing into a different account on the same device wipes local storage before any sync runs, so one account's session history and AI memory can never leak into another account's cloud data.
AI as autopilot, not a dashboardAn entrainment agent, not a chart, is the interface: a brief pre-session check-in recommends a trajectory or designs a custom one, and a terse post-session retrospect is generated on demand from the session's own band-power data, grounded in what the EEG actually showed rather than generic encouragement. A second, more exploratory therapeutic agent was designed alongside it but deliberately deferred rather than shipped half-built. Practitioners can also run sessions on behalf of separate named client profiles, not just self-guided individual use.
Tiered access with no gate on the hardware or the AI keyThe free tier is not a limited demo: manual entrainment, presets, the session timer, and full Crown closed-loop feedback all work with no account and no subscription. The paywall sits specifically at the AI agent, and even that has an escape hatch: a subscriber can bring their own Anthropic or OpenAI key and bypass the billed proxy entirely.
Safety is load-bearing, not decorativeA hard 25 Hz cap on visual flicker, a strobe-sensitivity check during onboarding that permanently disables the visual channel if triggered, and an optional exit ramp (internally named the "SMR-Bridge") that eases the brain back to a 12 to 15 Hz range before a session ends, specifically to prevent the post-session irritability that ending on a low-frequency state can otherwise cause. Every AI-authored custom trajectory has to pass an explicit guardrail validator (max duration, frequency floor, minimum step length) before a session is allowed to start, so the AI's own output is checked against hard bounds rather than trusted outright.
Stack
React, TypeScript, Vite, Capacitor, Supabase, Stripe, Neurosity SDK, Vercel AI SDK
Status
Live in production on web. Commercially available on a tiered Stripe subscription; the core experience needs no account. iOS and Android are Capacitor-packaged and planned, not yet released. Actively maintained, most recent work is a CORS-hardening pass and a full documentation refresh.
Source is private. The product isn't: try it at entrain.still.codes.
Back to work