A CLI tool for automatic music transcription. Separates audio stems with Demucs, transcribes the selected stem using Basic Pitch, and outputs a structured JSON representation of notes and rhythm on a 32nd-note grid — suitable for rendering as sheet music.
Primary use case: modern multi-track recordings. Works best with contemporary material; older mono recordings (pre-1960s jazz) separate poorly due to model training data.
Input (.mp3 / .wav)
|
v
[ separate ] ─── Demucs htdemucs_6s (6-stem)
|
stems.json + stem wavs
|
v
[ transcribe ] ─── stems.json + --stem + --time-sig + --bpm
|
+─── Basic Pitch → raw notes (time, pitch, confidence)
|
+─── librosa → BPM detection + beat grid
|
+─── Quantizer → align to 32nd-note grid, detect tuplets/ties
|
v
output.json
# Step 1: separate stems
transcribe separate input.mp3 --output-dir ./stems/
# Step 2: transcribe a stem (auto BPM detection)
transcribe transcribe stems/stems.json --stem other --time-sig 4/4
# Step 2: transcribe with manual BPM
transcribe transcribe stems/stems.json --stem piano --time-sig 4/4 --bpm 132Step 1 produces stems/{drums,bass,guitar,piano,vocals,other}.wav plus a stems.json config. Uses the htdemucs_6s model for dedicated guitar and piano tracks.
Step 2 reads that config, runs Basic Pitch on the chosen stem, and writes output.json.
src/transcribe/
cli.py entry point (Typer)
separator.py Demucs wrapper → stem wavs + stems.json
transcriber.py Basic Pitch wrapper → raw notes (sec, midi, confidence)
quantizer.py beat grid, note snapping, tuplet detection, tie resolution
models.py Pydantic data models
Core logic pipeline:
- BPM detection via
librosa.beat.beat_trackwith half/double-time correction (or manual override via--bpm) - Beat grid — divide each beat into 8 32nd-note slots per the time signature
- Note snapping — snap each onset to nearest grid slot
- Duration quantization — round to nearest valid 32nd-note multiple (1,2,4,8,16,32)
- Tuplet detection — identify 8th-note triplet groups (3 notes in one beat window)
- Tie resolution — notes crossing beat/group boundaries are split and linked with
tiefield
Note
measure int
position int 0-indexed 32nd-note slot within measure
pitch str | None scientific notation e.g. "A4", "Bb3"; None = rest
duration int in 32nd notes (8 = quarter note)
is_rest bool
tuplet Tuplet | None
tie "start" | "middle" | "end" | None
Tuplet
ratio "3:2"
group_id "t1", "t2", ...
index 0 / 1 / 2
tuplet_duration int
Transcription
metadata TranscriptionMetadata
notes list[Note]
| Duration | 32nd units |
|---|---|
| Whole | 32 |
| Half | 16 |
| Quarter | 8 |
| 8th | 4 |
| 16th | 2 |
| 32nd | 1 |
Position range per measure: 0–31 (4/4), 0–23 (3/4).
{
"metadata": {
"source_file": "input.mp3",
"stem": "other",
"bpm": 132.0,
"time_signature": "4/4",
"grid_resolution": "32nd",
"total_measures": 24
},
"notes": [
{ "measure": 1, "position": 0, "pitch": "A4", "duration": 8, "is_rest": false, "tuplet": null, "tie": null },
{ "measure": 1, "position": 24, "pitch": "C5", "duration": 8, "is_rest": false,
"tuplet": { "ratio": "3:2", "group_id": "t1", "index": 2, "tuplet_duration": 1 }, "tie": "start" }
]
}Requires Python 3.11+.
uv sync
uv run transcribe --help- Typer — CLI
- Pydantic v2 — data models
- Demucs — source separation (htdemucs_6s: drums, bass, guitar, piano, vocals, other)
- Basic Pitch — polyphonic pitch detection
- librosa — BPM detection and audio analysis
- HTML sheet music rendering via VexFlow (
transcribe render output.json --output sheet.html)