Skip to content

Repository files navigation

Perfect Pitch — the tuner reading A4 at 440.0 hz, dead centre on a cents meter, under the words "how close you are, not just which note"


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

A violin has no frets.

Thirty cents flat is still recognisably C4.
A tuner that only names the note will call a bad note correct.

Two readouts side by side, both naming the note A4. The left reads 440.0 hz and "in tune" in green; the right reads 437.0 hz and "-12¢ flat" in red

The name was never the hard part.



02   TUNE

Every octave, all the time.

The tuner: a large A4 readout at 440.0 hz marked in tune, a cents meter beside it, and a bed of 88 keys with A4 lit green

Eighty-eight keys, because scordatura moves the range. Metronome, drone,
and a reference tone under every key.




03   PRACTICE

The bed remembers.

The same key bed after a passage, with C4, E4 and G4 shaded green and A4 shaded red

Green where you were in tune, red where you were not.
A passage leaves a map of its own intonation.




04   NOTES

Write it. Hear it. Play it back.

The notebook: a piece called Vande Mataram written over seven lines of sargam, a list of saved pieces beside it, and a swara keyboard underneath

Sargam or Western letters, movable Sa. Then practise against it —
a note at a time, or a timed run that scores the whole thing.




05   FIRST RUN

It explains itself, once.

The walkthrough running over the tuner: the page dimmed, the listen button ringed in red and left pressable, and a panel reading "2 of 6 — Start here"

Six steps on the tuner, five in the notebook, offered on a first visit
and never again. Each one hands back the control it asks you to press.




06   ANYWHERE

Turns sideways. Installs.


A phone held upright, with the whole tuner turned ninety degrees inside it so the full key bed fits

Nine octaves will not fit a portrait column, so the page turns itself.
Installed, it opens with no network at all.




Run it

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 dev

Open 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.


How it works

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();
}

Built with

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

Contributing

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 tests

Built by Harshul Rathod  ·  Open the app  ·  Design specs

About

A violin tuner that tells you how close you are, not just which note, and a notebook for the pieces you are learning. Installable, works offline.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages