A small convolutional-recurrent network that labels guitar audio with one of 73 classes per frame: no chord, or a major, minor, dominant-7th, major-7th, minor-7th or half-diminished-7th chord on any of the 12 roots. It is the neural recognizer of alberto, a guitar songwriting helper that listens to a progression, estimates the key, and suggests what to play next.
config.json has every value)config.json lists them in order)transitions, estimated from the training labels); plain argmax
works too, at a small accuracy costTrained on GuitarSet (Xi, Bittner, Ye, Bello, ISMIR 2018; CC BY 4.0), mono-mic recordings, using the performed chord annotation mapped to the 73-class vocabulary (references with other qualities — 9ths, 6ths, sus — reduce to the triad their third implies). Players 00–04 (300 excerpts, strummed and solo) were used for training with random pitch shifts of up to ±5 semitones applied by rolling CQT bins; player 05 (60 excerpts) was held out. 30 epochs, Adam 1e-3 with cosine decay, 8-second chunks, cross-entropy.
| metric | this model, viterbi | this model, argmax | binary-template baseline |
|---|---|---|---|
| root | 0.729 | 0.724 | 0.541 |
| majmin | 0.689 | 0.675 | 0.516 |
| triads | 0.525 | 0.516 | 0.399 |
| sevenths | 0.575 | 0.566 | 0.400 |
| mirex | 0.721 | 0.721 | 0.506 |
Scored against the performed annotation. The previous 25-class version scored majmin 0.684 and sevenths 0.450, so the seventh vocabulary lifted sevenths by 12.5 points without costing majmin.
The model definition lives in the alberto repository (alberto/chords/model.py). With it
on your path:
from huggingface_hub import hf_hub_download
from alberto.chords.model import recognize
weights = hf_hub_download("shawon/alberto-chord-crnn", "crnn_majmin.pt")
segments = recognize(y, sr, weights=weights) # y: mono float32 numpy array
for seg in segments:
print(f"{seg.start:6.2f} {seg.end:6.2f} {seg.chord}")
Or load the raw checkpoint into your own copy of the architecture:
import torch
ckpt = torch.load("crnn_majmin.pt", map_location="cpu", weights_only=True)
model = ChordCRNN(n_bins=ckpt["n_bins"], n_classes=ckpt.get("n_classes", 25)).eval()
model.load_state_dict(ckpt["state_dict"])
logits = model(torch.from_numpy(features).unsqueeze(0)) # (1, n_frames, 73)
Frames whose RMS is below -60 dBFS should be treated as no-chord regardless of the
prediction; alberto decodes the posteriors with Viterbi (see transitions above).