尚未偵測到更新。GitHub 發布版本後會顯示在這裡。
個人 GitHub 帳號下公開儲存庫的擁有者可以登入並驗證所有權,自行管理介紹,無需管理員核准。
登入並產生驗證檔案,在 30 分鐘內將其提交至此儲存庫的預設分支。其他分支或分叉儲存庫中的檔案無效。然後在下方檢查檔案,即可無需管理員核准直接管理專案。請保留檔案以維持管理權限。
如需登入,將前往登入頁面。登入後請回到此處,再次點擊所選的驗證按鈕。
在專案資料夾中執行 /nomorevibe verify,即可驗證所有權並自行管理介紹。
一分鐘完成驗證製作者的介紹僅有上面的一句話。 更多詳情請閱讀儲存庫 README。 儲存庫 README ↗
正在載入完整 README。
檢視完整 README ↗Family video calls that tell Grandma who is talking.
When a family crowds around one phone to call someone with memory loss, she hears a stream of voices with no names attached. She can't see who is speaking, and she may not remember whose voice is whose. Most call apps assume one person per device, so they have nothing to say about a room full of voices.
Caretaker is built for nursing-home residents with dementia and the families who call them. It names each voice as it speaks, says how that person is related to her ("your grandson Kevin"), and keeps the good news from the call on her screen after everyone hangs up.
Built for the Congressional App Challenge 2026.
It knows who is speaking. Every two seconds of voice becomes a 192-number voiceprint. Caretaker compares it with the voiceprints of family members who enrolled by reading six short sentences, and lights up the matching person's tile with their name and relationship to her.
It never guesses. If a voice doesn't clearly match one person, the app says "Someone new". A wrong name is worse than no name for someone with dementia: it teaches her the wrong thing about the people she loves. So a name has to pass two tests, a minimum similarity and a clear lead over the second-best match.
It writes down what was said, with names. Whisper speech recognition runs alongside, and each word is filed under the speaker who said it. The family sees a live, named transcript and can fix a wrong label with one tap.
It keeps the good news. A silent notetaker picks out moments worth remembering ("David passed his driving test") and puts them on her Family News screen. Memories publish automatically and the family can remove them afterwards. New people and family-tree changes wait for a family member to confirm them. Medical notes never reach her screen.
It is built for her, not for us. The resident view runs on an iPad on a stand and follows rules enforced in code, not just in comments:
microphone ─▶ find speech ─▶ take 2 s ─▶ map the pitch ─▶ voiceprint ─▶ match a name ─▶ hold it steady ─▶ show it
(Silero VAD) every 1 s (80-band log-Mel) (ECAPA-TDNN) (cosine, τ + margin) (hysteresis)
└──────▶ write the words (Whisper) ─▶ file each word under its speaker ────────────────────────────────▲
| Step | What it does, plainly | Why it matters |
|---|---|---|
| Find speech | A small neural network marks where speech starts and stops | Silence and room noise never become a voiceprint |
| Take 2 seconds | Re-scores the last 2 s of speech once a second | The name appears about 2 s after someone starts talking |
| Map the pitch | Turns the sound into a map of 80 pitch bands over time | It's what the voice model reads |
| Voiceprint | A pretrained ECAPA-TDNN model (20.8 M parameters) turns the clip into 192 numbers | Clips of the same person land close together |
| Match a name | Compares that voiceprint with each family member's average (cosine similarity) | A name only if it clears τ = 0.45 and leads the runner-up by 0.05 |
| Hold it steady | Keeps the current name until a different one wins twice in a row | A cough or a laugh doesn't flip the name |
I did not train the voice model. I run a model already trained on thousands of public, open-source voices (SpeechBrain's ECAPA-TDNN, trained on VoxCeleb). None of my family's audio was used to train it. My recordings only build each person's voiceprint and test the system. My own work is everything around the model: the enrollment recorder, building voiceprints, the open-set decision rule, the smoothing, choosing the threshold from data, the evaluation, and the app.
The /how-it-works page draws this whole pipeline from real recordings: the
spectrogram, the inside of the voice model, a 3-D map of the voiceprints, and the
decision zone on a sphere. /how-it-works/stats shows the accuracy charts.
Measured on my own recordings plus 40 strangers from LibriSpeech (a free library of audiobook recordings). Every test clip was kept out of the voiceprint it was checked against, so the system never saw the answer ahead of time.
| Result | |
|---|---|
| Whole sentences named correctly | 100 % |
| 2-second live slices named correctly | about 96–98 %. The rest show "Someone new", none get a wrong name |
| Stranger slices given a family name | 0 of 240 (40 LibriSpeech voices) |
| Equal error rate | 0.66 % |
| Separation score (ROC AUC) | 0.998 |
| Speed | about 32 ms of work per second of audio, roughly 30× faster than real time on a laptop |
What I learned the hard way:
Limits: few enrolled people (so mixing up two relatives is under-tested), one microphone, quiet rooms, English only. I don't claim more than that.
Each one was chosen from measurements, not intuition. τ and the margin live in
demo.config.json; the rest are in services/speaker/caretaker_speaker/.
| Setting | Value | What it does |
|---|---|---|
| τ (threshold) | 0.45 | Minimum similarity needed to name anyone |
| Margin | 0.05 | How far the best match must beat the runner-up. Below this it says "Someone new" instead of picking between two relatives |
| Window | 2 s, every 1 s | The shortest slice that still scored 96–98 % |
| End of turn | 500 ms of silence | When one person's turn is over |
| Stickiness | 2 windows | A different name has to win twice in a row before the label changes |
| Enrollment | 6 sentences, ~20 s | Accuracy stops improving at about 20 seconds of speech |
The guiding rule: "Someone new" can be fixed with one tap; a wrong name can't be undone in her head. Where the two mistakes compete, the code chooses "Someone new".
apps/web/styles/tokens.css), two typefaces kept apart:
Inter for the family view, Atkinson Hyperlegible for the resident viewapps/web/ the app: family view, resident view, /how-it-works
services/speaker/ voice ID: recorder, enrollment, scoring, streaming loop, replay
ml/eval/ evaluation: LibriSpeech tests, threshold picking, stats for the site
demo.config.json who is enrolled, which family member they play, τ and margin
The app (no setup beyond Node and pnpm):
cd apps/web
pnpm install
pnpm dev # http://localhost:3000
pnpm test # relationship, resident-feed and timeline checks
The call screens replay a recorded call (three family members, one laptop mic),
cut at speaker changes with pauses added, so the demo works without a microphone or a
second person. The speaker-ID pipeline and its accuracy are real and
shown on /how-it-works and /how-it-works/stats. Pause and scrub it from the header
on /family/call.
The voice pipeline (Python 3.12):
cd services/speaker
uv venv --python 3.12 .venv && uv pip install --python .venv/bin/python -e .
.venv/bin/python -m caretaker_speaker.record # local page that coaches each person through 6 sentences
.venv/bin/python -m caretaker_speaker.enroll # one voiceprint per person -> data/voiceprints.npz
.venv/bin/python -m caretaker_speaker.replay call.wav out.json # recorded call -> named timeline for the app
.venv/bin/python -m pytest
The recorder measures the room's noise floor, shows a live level meter, and rejects takes that are too short, too quiet or clipped. Rejected takes go to a separate folder, so they never reach a voiceprint.
Re-measuring:
cd services/speaker
PYTHONPATH=../..:. .venv/bin/python -m ml.eval.run_eval --set librispeech # enrolled vs. stranger trials
PYTHONPATH=../..:. .venv/bin/python -m ml.eval.voice_report # regenerates the /how-it-works/stats data
PYTHONPATH=../..:. .venv/bin/python -m ml.eval.run_call score out.json labels.csv # scores a call against hand labels
Voice recordings and voiceprints stay on the local machine in data/, which is
git-ignored. This repository contains no one's voice. Everyone recorded gave
consent. Speaker ID and Whisper both run locally; nothing is sent to a cloud service.
services/speaker) and the numbers on /how-it-works/stats are real and measured.
Live streaming uses the same scoring code behind a socket and is next.Built by @DarrenChan99 for the Congressional App Challenge 2026.
AI use: I used Claude Code for scaffolding the stack, implmenting my design vision, brainstorming and researching ways to solve challenges (like diarization).