A violin tuner that tells you how close you are — not just which note.
And a notebook for the pieces you are learning.
Open the app · How it works · Run it
Perfect Pitch is a tuner and a practice notebook for violinists, in one browser tab. It shows how far off you are in cents, remembers that across a whole passage, and keeps the pieces you are learning where you tune them. It installs to a home screen and works with no network.
01 THE PROBLEM
Thirty cents flat is still recognisably C4.
A tuner that only names the note will call a bad note correct.
and a reference tone under every key.
A passage leaves a map of its own intonation.
a note at a time, or a timed run that scores the whole thing.
and never again. Each one hands back the control it asks you to press.
06 ANYWHERE
Nine octaves will not fit a portrait column, so the page turns itself.
Installed, it opens with no network at all.
Bun is what the lockfile is for. Google Chrome is needed only for the end-to-end tests, which drive a real browser.
bun install
bun run devOpen the app, press listen, and allow the microphone. That is the whole tuner — it needs no account and no configuration.
The notebook needs Firebase, because pieces are saved to an account and follow you
between devices. Copy .env.example to .env.local and fill it in from
firebase apps:sdkconfig WEB <appId> --project <projectId>:
VITE_FIREBASE_API_KEY=
VITE_FIREBASE_AUTH_DOMAIN=
VITE_FIREBASE_PROJECT_ID=
VITE_FIREBASE_STORAGE_BUCKET=
VITE_FIREBASE_MESSAGING_SENDER_ID=
VITE_FIREBASE_APP_ID=These ship to the browser and are not secrets — access is controlled by the security rules below, not by hiding them. Without the file the tuner still runs; only signing in is unavailable.
| Command | What it does |
|---|---|
bun run dev |
dev server |
bun run build |
typecheck, then build |
bun run test |
unit tests — 325, in 26 files |
bun run e2e |
Playwright, real Chrome — 84 |
bun run lint |
ESLint |
bun run typecheck |
TypeScript, no emit |
bun run format |
Prettier |
The notebook's end-to-end tests sign in for real, so they need
firebase emulators:start first.
Pitch — one equal-temperament formula, anchored at A4 = 440 Hz.
frequency(midi) = 440 * 2 ** ((midi - 69) / 12)
Detection — the McLeod Pitch Method: the first strong peak of a Normalised Square Difference Function, not the tallest. That is what stops a bowed string's harmonics reading an octave high. Echo cancellation, noise suppression and gain control are all off; each one mangles a sustained tone.
One note at a time. Double stops waver between the two.
Offline — precache the build, hashed assets cache-first, navigations network-first with the cached shell behind them. A Vite plugin stamps the file list in at build from what Rollup actually produced, so the worker's list cannot drift from the files that exist.
Storage — a piece belongs to its owner structurally, nested under their user id rather than tagged with it. The rules check the shape of every write too, so a compromised client cannot store arbitrary documents or exhaust the quota:
match /users/{uid}/compositions/{compositionId} {
allow read, delete: if isOwner(uid);
allow create, update: if isOwner(uid) && isWellFormed();
}
| Layer | Technology |
|---|---|
| Interface | React 19 · React Router 8 · Tailwind 4 |
| Build | Vite 8 · TypeScript 6 |
| Sound | Web Audio API, directly |
| Account | Firebase Auth · Firestore |
| Offline | A service worker generated at build |
| Tests | Vitest · Playwright |
No audio, charting or PWA library. Detection, synthesis, metronome scheduling and the service worker are all first-party.
src/
├─ lib/ pitch, notation, practice, scoring
├─ hooks/ microphone, preferences, Firestore, install
├─ components/ key bed, readout, meter, editor
├─ routes/ tuner · notes
└─ sw.js offline
e2e/ Playwright, against a real browser
docs/
├─ screenshots/ what is above
└─ superpowers/ a design doc per feature
Every feature here starts as a short design doc in
docs/superpowers/specs/ and ends with tests that would
fail without it. Both are worth more than the diff.
git checkout -b your-branch
bun run test && bun run typecheck && bun run lint
bun run e2e # firebase emulators:start, for the notes testsBuilt by Harshul Rathod · Open the app · Design specs





