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:

  1. Very high fret voicings -- chords played high up the neck are identified less reliably than open or low-position voicings.
  2. 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.
  3. Inversions -- chords with a bass note other than the root are the least reliable case overall.
Downloads last month

-

Downloads are not tracked for this model. How to track
Inference Providers NEW
This model isn't deployed by any Inference Provider. 🙋 Ask for provider support

Dataset used to train rodriler/betterchord-chord-cnn