The problem
Every year, ~350,000 Americans suffer out-of-hospital cardiac arrest. Fewer than 10% survive. The single biggest predictor of survival is whether a bystander starts CPR in the first two minutes — every minute of delay drops survival by roughly (7\text{–}10%).
Most bystanders freeze. Not because they don't care — because they've never done it, they can't remember the rhythm, and they're terrified of doing it wrong.
What Pulse does
Pulse turns any phone into a real-time CPR coach. Prop the phone up, start compressions, and the app watches you through the rear camera while a calm voice guides you through every step — from the 911 call to the last compression before the paramedics arrive.
- Clara, a voice agent, walks you through the pre-CPR checks (responsive, breathing, age, AED, position) hands-free.
- The camera detects each compression from the vertical motion of your clasped hands.
- A metronome ticks at the clinically-recommended 110 BPM — the tempo of Stayin' Alive.
- Every 5th compression is counted out loud.
- If your elbows bend, the app tells you to straighten your arms — bent arms lose ~40% of transmitted force.
How it actually works
On-device pose estimation. MediaPipe's PoseLandmarker + HandLandmarker run in a Web Worker at 30 fps. Nothing leaves the phone during compressions — no network, no latency, no privacy risk.
Peak detection. Compressions are found from the smoothed vertical position (y_t) of the clasped hands. In MediaPipe's coordinate system, down is positive, so the bottom of a compression is a local maximum. We track a running max (M_t) and fire when the signal has risen far enough off it:
$$ \text{peak}t = \left( M_t - y_t \geq \alpha \right) \wedge \left( t - t{\text{last}} \geq \tau_{\text{ref}} \right) $$
with amplitude threshold (\alpha = 0.02) (normalized image coords) and refractory window (\tau_{\text{ref}} = 350\text{ ms}). After each peak the detector disarms until it sees a fresh downstroke — this is what kills double-counting at slow tempo.
Smoothing. Raw landmarks jitter. We apply the OneEuro filter with cutoff frequency
$$ f_c(t) = f_{c_{\min}} + \beta \cdot |\dot{y}_t| $$
tuned to (\beta = 0.07). Higher (\beta) means the filter opens up when motion is fast, so peaks don't lag; lower (\beta) means quieter idle. CPR runs at ~2 Hz, well above the rehab defaults, hence the higher (\beta).
Audio scheduling. Web Audio uses a lookahead scheduler with a 25 ms tick and a 100 ms horizon. The target inter-click interval at 110 BPM is
$$ \Delta t = \frac{60}{110} \approx 0.5455 \text{ s} $$
Clicks are queued against AudioContext.currentTime, not setInterval, so tab throttling and re-render pressure can't drift the beat. React state is banned from the 30fps loop — landmarks, peaks, and the audio clock live in refs; only the count and phase are setState, throttled to ~10 Hz.
Audio priority ladder. Two voices talking over each other is worse than silence, so cues are strictly ordered: phase instructions interrupt everything; metronome is the continuous baseline; spoken counts fire on detected peaks; form corrections only slot in between numbers, at most one per five compressions. All voice lines are pre-rendered ElevenLabs v3 mp3s decoded up front — no live TTS in the hot loop.
What we're honest about
A single camera cannot measure compression depth in centimetres. Two cameras and a known baseline could triangulate it, but one can't — and pretending otherwise would be dangerous. Pulse only compares your compressions to your own: if you get shallower, it notices. It never claims you're at 5 cm.
Built with5 cm.
Built with
Next.js 16 · React 19 · TypeScript · MediaPipe Tasks Vision · Web Audio API · ElevenLabs (Clara agent + pre-rendered cues) · Tailwind 4 · PWA (installable, works offline)
The pitch
$$ \text{lives saved} = \text{bystanders willing to try} \times \text{quality of what they do} $$
Pulse pushes both terms up. It's already in your pocket. It never froze in a real emergency because it can't.
Built With
- elevenlabs
- mediapipe
- next.js
- pwa
- react
- tailwind-css
- web-audio-api
Log in or sign up for Devpost to join the conversation.