ChordCNN (BetterChord): guitar chord recognition from a single strum
All inference code can be found in the BetterChord repo.
What it does
chord_cnn.pth is a small PyTorch CNN that identifies the chord in a
single recorded guitar strum. It takes an 84x119 CQT spectrogram of ~2.75
seconds of audio and outputs three 12-way predictions over the chromatic
scale: which notes are sounding (multi-label), which note is the root, and
which note is the bass. It does not output a chord name directly --
those three raw predictions are turned into a name (e.g. Cmaj7, A6/9,
D/F#) by a separate rule-based module downstream, so the model itself
isn't limited to a fixed list of chord types. Weights also ship as
chord_cnn.onnx (same weights, converted for lightweight inference
without PyTorch).
How to use it
import torch
from cnn_model import ChordCNN # betterchord/training_scripts/
from audio_processing import load_audio, create_spectrogram
from music_theory import identify_chord_smart # betterchord/config/
model = ChordCNN()
model.load_state_dict(torch.load("chord_cnn.pth", map_location="cpu"))
model.eval()
y, sr = load_audio("strum.wav")
spec = create_spectrogram(y, sr) # (84, 119)
x = torch.from_numpy(spec).float()[None, None, :, :] # (1, 1, 84, 119)
with torch.no_grad():
note_logits, root_logits, bass_logits = model(x)
result = identify_chord_smart(
note_logits[0].numpy(), root_logits[0].numpy(), bass_logits[0].numpy()
)
print(result["chord"]) # e.g. "Cmaj7"
identify_chord_smart expects raw logits and handles the sigmoid/softmax,
root/bass reconciliation, and chord naming itself. The .onnx file can be
run the same way through onnxruntime.InferenceSession instead (input
name "input", outputs "note_out" / "root_out" / "bass_out") -- see
export_onnx.py in the repo for that path.
Training data
- IDMT-SMT-Chords (Zenodo) -- CC BY-NC-ND 4.0. Non-Commercial, No-Derivatives. This is a real, restrictive term: ND is commonly read to disallow distributing adapted/derived material, and a model trained on it may be argued to be a derivative. NC forbids commercial use. Do not assume redistributing these weights is permitted because of this.
- severyn-k/isolated-guitar-chords (Hugging Face) -- CC-BY-4.0.
- GADA dataset -- no formal license stated by the source.
- My own recorded/collected audio.
Semitone-shift (-2 / +2) pitch-transposition augmentation was applied during dataset preparation where the source audio allowed it (Ie, E major with voicing 0-2-2-1-0-0 was shifted up to F, with one semitone, and F#, with two semitones).
Intended use
This model is built to identify the chord in a single, isolated, reasonably clean guitar strum (electric or acoustic, standard tuning, root-position or common voicings) -- roughly a 1-3 second clip, which is exactly how the BetterChord app uses it: record or upload a strum, get a chord name back. It is not built for full-song chord transcription over time, chord sequences, multi-instrument mixes, non-guitar sources, alternate tunings, or melodic/single-note input -- it consumes one fixed window and returns one prediction.
Architecture & training
A small CNN (~30M parameters) with a shared convolutional trunk and three parallel output heads (note / root / bass), trained end-to-end on roughly 50,000 samples across ~696 root-position chord classes. See the GitHub repo for the full architecture, preprocessing constants, and training hyperparameters.
Headline eval numbers (project-recorded, not re-measured for this card):
~92.96% test-set accuracy, 98%+ rule-based root accuracy once
identify_chord_smart's reconciliation is applied, and 16-17 of 19 on
a small held-out set of real-world recordings.
Limitations
The model is noticeably less reliable on noisy recordings. A strum with significant background noise can degrade detection meaningfully, so a clean-ish take (or a quiet-ish room) gets much better results than a noisy one.
Beyond noise, three patterns show up consistently in my own testing:
- Very high fret voicings -- chords played high up the neck are identified less reliably than open or low-position voicings.
- Complicated chords -- extensions (9ths, 11ths, 13ths), alterations, and note clusters (several adjacent notes played close together) are harder for the model than plain triads and simple sevenths.
- Inversions -- chords with a bass note other than the root are the least reliable case overall.