An audio tool for decoding chord progressions
Dechord takes music files and recognizes the chords with madmom, then stores the result so you can replay the track, re-run recognition, or revisit the chords later. Play the track back and the recognized chords sweep by in sync, so you can see where each chord falls in the song and improvise over it.
Where traditional DAWs lay tracks out in one long horizontal strip, Dechord wraps chords and the master waveform across multiple lines like sheet music for easy reading.
Two services, run side by side in dev:
backend/— FastAPI + Pydantic + stdlibsqlite3. Stores uploads content-hash addressed underbackend/library/, runs chord recognition in a FastAPIBackgroundTask, and writeschords.jsonback to the library.frontend/— Next.js + React + shadcn/ui. Proxies API calls to the backend through a Next.js rewrite (/api/:path*→http://localhost:8000), so both share one origin and the backend ships without CORS.
Recognition is asynchronous: POST /media returns immediately with an id, the
frontend polls GET /jobs/{id} until the chord stage completes.
See ravel/docs/design-v1.md for the full design and
ravel/docs/recognizer-benchmark.md for
the madmom-variant comparison that drove the recognizer choice.
- Python >= 3.13, managed with uv.
- Node.js + pnpm.
ffmpegonPATH— used to normalize uploads to mono 44.1 kHz before recognition. Install via Homebrew:brew install ffmpeg.
Backend (http://localhost:8000):
uv sync # from backend/
uv run fastapi devFrontend (http://localhost:3000):
pnpm install # from frontend/
pnpm devOpen http://localhost:3000, upload a track, and wait for the row to flip to
done — then click through to the item view for the chord + waveform tracks.
Backend, from backend/:
make lint # ruff
make typecheck # mypy (app + tests)
make test # pytestFrontend, from frontend/:
pnpm lint
pnpm typecheckPer-package notes live in backend/README.md and
frontend/README.md.
| Method | Path | Purpose |
|---|---|---|
| POST | /media |
Multipart upload; queues a recognition job. |
| GET | /media |
List media items (powers the library list). |
| GET | /media/{id} |
Full record incl. chords (the output contract). |
| GET | /media/{id}/audio/source |
Streams the original upload. |
| GET | /media/{id}/audio/peaks |
Precomputed max-abs peaks (float32 blob) for the master waveform. Computed once at upload alongside the ffmpeg WAV normalization; frontend renders the waveform from this without decoding audio client-side. |
| POST | /media/{id}/chords |
Re-run recognition on an existing item. |
| GET | /jobs/{id} |
Poll job status (queued|recognizing|done|failed). |
Chord labels are JAMS-style (C:maj, A:min, N) over the maj/min/N
vocabulary that madmom emits; the frontend renders them as C, Am.