Produce and work with ChordPro chord sheets — the
plain-text format where chords live inline in the lyrics as [C]like [G]this.
This skill is a router that degrades gracefully: it picks the most reliable
workflow for whatever the user actually has (a .cho file, a chords-over-lyrics
paste, bare lyrics, an audio file, or just a song title) and every path ends by
validating the output. It is honest about the one hard part — extracting
chords from raw audio is an ~80%-accurate draft, never an oracle — and falls
back to interactive, fill-in-the-gaps assistance instead of pretending otherwise.
When to use
- User names ChordPro /
.cho/.crd/.chopro/.pro, inline[C]lyricchords, or{title:}/{start_of_chorus}/{soc}directives. - Convert an existing sheet: chords-over-lyrics text (Ultimate Guitar, OnSong, a lyric with chord letters on the line above) → ChordPro.
- Render / transpose / validate a
.chowith thechordproora2crdCLI. - "Make a chord sheet for " starting from lyrics only, or from a YouTube / Bilibili / SoundCloud link or an mp3 / wav file.
- Ask about lyrics sources (Mojim/Magic Mirror, KKBOX, Musixmatch, LRCLIB, Genius,
NetEase, QQ Music) — which give time-synced
.lrcvs plain text.
When NOT to use
- Engraving staff notation (notes on a staff, MusicXML / MIDI / LilyPond / MuseScore output) — ChordPro is lyrics+chords, not a score. Hand off to a notation tool. (ChordPro can embed ABC/LilyPond fragments — see the format reference — but it is not an engraver.)
- OMR from a scanned score image (photo/PDF of printed sheet music → notes) is out of scope; there is no reliable open path. Offer manual transcription into ChordPro instead, and say so plainly.
Authoritative sources
- Format + directives + CLI: https://www.chordpro.org/ (link, don't paraphrase from memory — the directive set evolves).
- Source / releases: https://github.com/ChordPro/chordpro.
The router — pick the workflow by what the user has
| User has… | Do this | Reliability |
|---|---|---|
A .cho/.crd/.pro file |
Render / transpose / validate via chordpro CLI |
High |
| Chords-over-lyrics text (UG/OnSong) | a2crd → light manual cleanup → validate |
High |
| A known song (title / lyrics / a link) and no chords yet | Search existing chord charts online → chart-to-cho.py (preserves the chart's own alignment) → sanity-check → validate. See references/chord-tab-sources.md |
Med–High (best first move) |
| Lyrics only, no chart found | Fetch/confirm lyrics → interactively fill chords (propose from key, ask the user to confirm/correct) | Medium (human-in-loop) |
| An audio file or link, want a machine draft | scripts/audio-to-chords.py → draft with AUTO-GENERATED header → human correction |
~80% (last resort) |
Prefer the highest row that fits. Two rules that matter: don't jump to the audio
pipeline when the user already pasted chords (a2crd is faster and correct); and
for a named/popular song, search for an existing human-made chart first — it's
usually more accurate than audio ACR and sidesteps the download-ToS problem
entirely.
Before charting a named song, confirm it exists as described — user-supplied
artist/album is sometimes wrong, and searching a bad artist can grab a mislabeled
chart; a quick Wikipedia/LRCLIB match prevents shipping a misattribution.
Non-interactive/batch runs can't use the "ask the user" rows — instead produce a
best-effort chart from the published chord vocabulary, caption every uncertainty in
a {comment:}, and omit unknown metadata rather than fabricate it.
ChordPro cheat-sheet + output template
- Extensions:
.cho(recommended), also.crd.chopro.chord.pro. Plain UTF-8.#starts a comment line; blank lines separate blocks. - Inline chords:
[C]immediately before the syllable it sits over —Swing [G]low, sweet [C]chari[G]ot. Annotations (non-chords):[*softly]. - Metadata:
{title:}/{t:},{subtitle:}/{st:},{artist:},{composer:},{album:},{year:},{key:},{tempo:},{time:},{capo:}. - Environments (long / short): chorus
{start_of_chorus}/{soc}…{end_of_chorus}/{eoc}; verse{sov}…{eov}; bridge{sob}…{eob}; tab (monospaced, chords NOT parsed){sot}…{eot}. Recall a chorus with{chorus}. Comments:{comment:}/{c:}. - Key vs capo:
{key:}is the written/fingering key of the shapes shown;{capo:}is the capo used. If charts disagree, prefer the one whose chord vocabulary is internally consistent, and caption the sounding pitch. - Chorus recall:
{chorus}reprints the most-recently-defined chorus (showing only a "Chorus" label by default). With 2+ distinct recurring sections, label them —{start_of_chorus: name}…{chorus: name}— or expand the repeat.
Emit this shape — metadata block → environments/comments → verses/choruses:
{title: Swing Low Sweet Chariot}
{artist: Traditional}
{key: G}
{tempo: 90}
{time: 4/4}
{comment: Intro}
[G] [C] [G] [D]
{start_of_chorus: Chorus}
Swing [G]low, sweet [C]chari[G]ot,
Comin' for to carry me [D]home.
{end_of_chorus}
{start_of_verse: Verse 1}
I [G]looked over Jordan, and [C]what did I [G]see,
Comin' for to carry me [D]home.
{end_of_verse}
{chorus}
Full directive set (chord {define} diagrams, {transpose}, markup, ABC/LilyPond
blocks, x_ custom namespace) → read references/chordpro-format.md.
The chordpro / a2crd CLI (essentials)
The official tool is a Perl program (CPAN dist App::Music::ChordPro). Current
6.x releases install the chordpro command; the chords-over-lyrics importer
is chordpro --a2crd (a standalone a2crd binary shipped in older releases and
may be absent — prefer chordpro --a2crd).
# Install (macOS — there is NO official Homebrew formula; use CPAN)
brew install perl cpanminus && cpanm App::Music::ChordPro
# If `chordpro` then errors "Can't locate ChordPro.pm", activate local::lib once:
eval "$(perl -I"$HOME/perl5/lib/perl5" -Mlocal::lib)" # add to ~/.zshrc to persist
# Render
chordpro -o song.pdf song.cho # PDF (format inferred from extension)
chordpro --generate=HTML -o song.html song.cho
# Transpose (N semitones; suffix s/f forces sharp/flat spelling)
chordpro -x 2 -o up.pdf song.cho
chordpro -x -3f -o down.pdf song.cho
# Convert chords-over-lyrics text -> ChordPro (the official importer)
chordpro --a2crd input.txt -o song.cho
Install-per-platform, generators, config JSON, songbook --toc, and a2crd
heuristic tuning → read references/cli-and-rendering.md.
Verify loop — never hand back an unvalidated .cho
Every generated or edited file goes through the parser before you present it:
scripts/validate-cho.sh song.cho # wraps the command below; PASS/FAIL + warnings
# equivalently:
chordpro --strict --generate=Text -o - song.cho # exit 0 + no stderr warnings = valid
To auto-normalize to canonical form (fix spacing, round-trip directives):
chordpro --generate=ChordPro -o song.cho song.cho. If chordpro isn't
installed, the script prints the install one-liner — say so and offer to proceed
without rendering (the format is still human-checkable).
Sanity-check the harmony (non-blocking). After building a chart — especially a
fetched or auto-generated one — run scripts/analyze-progression.py song.cho. It
detects the key, re-expresses every chord as a scale degree (Roman + Nashville),
and flags the ones worth a second look: out-of-key chords reached by an
improbable move (the strongest "likely mis-transcribed" signal), plus duplicate
and floating chords. It's advisory, not a gate — unusual ≠ wrong — but many
low-plausibility flags mean the chart or the detected key needs another look, and
it's a good tie-breaker when two fetched charts disagree. Theory + how to read
the verdict → references/music-theory.md.
Producing a PDF? Verify it renders, not just parses. validate-cho.sh only
checks that the file parses — it can't see a broken PDF. chordpro's default
fonts have no CJK glyphs, so a Chinese/Japanese/Korean song renders as blank
tofu while still exiting 0 and passing validation. Use scripts/render-cho.sh song.cho — it auto-detects a CJK font, renders, and glyph-checks the PDF (fails
loudly on tofu).
Finding existing chord charts (the usual first move)
For a named/popular song, a human-made chart almost always already exists online
and beats audio extraction on accuracy. scripts/chart-to-cho.py fetches and
converts it while preserving the chart's own chord-to-syllable alignment — the
key to accuracy. The source already encodes which syllable each chord sits on
(Chord4's 後來的生(活), UG's monospace columns); the script reproduces that. Never
re-align fetched chords onto separately-fetched lyrics by eye — that lossy
re-merge is what drifts chords and drops them as floaters.
# 1. search: 中文 吉他谱/和弦/弹唱谱 ; EN "<artist> <title>" chords / ultimate guitar
# 2. convert (auto-detects Chord4 vs Ultimate Guitar; keeps alignment + metadata):
uv run scripts/chart-to-cho.py --url "<chart-url>" -o song.cho
# Cloudflare wall? save the page in a browser, then: --html saved.html --site chord4
# 3. LRCLIB is only the .lrc sidecar + a gap-filler for missing lines — NOT the
# alignment source (the chart already aligned the chords):
uv run scripts/fetch-lyrics.py --artist "…" --track "…" -o song.lrc
# 4. sanity-check the harmony (key + degrees + likely mis-transcriptions):
uv run scripts/analyze-progression.py song.cho
# 5. validate + render (CJK-safe):
scripts/validate-cho.sh song.cho && scripts/render-cho.sh song.cho
chart-to-cho.py adds the source {comment:} caption itself. Human-made chart, not
machine ACR — so no AUTO-GENERATED header. Per-site extraction internals, the site
catalog (91pu/Chord4/UG — many are JS-rendered or image-only six-line tablature),
simplified↔traditional (--opencc / URL variant), and legality →
read references/chord-tab-sources.md. Harmony theory behind step 4 →
references/music-theory.md.
Generating from audio / links — the fallback when no chart exists
This is the shakiest path — reach for it only when no existing chart is available.
Set expectations first: open automatic chord recognition tops out around 78–80% on simple major/minor pop and drops sharply on 7ths, extended/jazz chords, key changes, and quiet or dense mixes; inversions/slash chords are unreliable; and it cannot know the capo a guitarist used. Treat the result as a draft to correct, not a transcription.
# Best-effort turnkey: link or local file -> draft ChordPro
uv run scripts/audio-to-chords.py --dry-run "https://youtu.be/..." # show the plan first
uv run scripts/audio-to-chords.py -o draft.cho "https://youtu.be/..."
The script downloads audio with yt-dlp (YouTube/Bilibili/SoundCloud) and runs
chord-extractor/Chordino. Its output must carry the
{comment: AUTO-GENERATED — verify chords/key/timing} header. On macOS the
Chordino Vamp plugin often isn't installed; the script detects that and prints
exact install guidance + manual alternatives instead of crashing — relay that to
the user rather than silently failing. Tool comparison, beat/key detection, and
commercial escape hatches (Moises/Songsterr have APIs; Chordify/Chord AI/Ultimate
Guitar don't) → read references/audio-to-chords.md.
Legal: only process content the user owns or has the right to use, for personal use, locally; downloading copyrighted audio can violate a platform's ToS. Surface this — don't bury it.
Lyrics sources
Default to LRCLIB — free, no API key, returns time-synced .lrc:
uv run scripts/fetch-lyrics.py --artist "Adele" --track "Hello" --duration 295
uv run scripts/fetch-lyrics.py --plain --artist "..." --track "..." # plain text
| Source | Access | Synced .lrc? |
|---|---|---|
| LRCLIB | free, no key | yes |
| Musixmatch | API key; full/synced is paid | yes (paid) |
| NetEase / QQ Music | unofficial community APIs | yes (Chinese catalog) |
Genius (lyricsgenius) |
API token; lyrics scraped from page | no (plain) |
| Mojim / Magic Mirror | scrape-only | no (plain) |
| KKBOX | partner-gated | n/a |
Full comparison, legality, and the LRCLIB API → read references/lyrics-sources.md.
Available scripts
scripts/chart-to-cho.py— fetch an existing chart (Chord4 primary, Ultimate Guitar fallback) and convert it to inline ChordPro, preserving the chart's own chord-to-syllable alignment. Auto-detects the site; pulls title/artist/key/capo. Flags:--url/--html,--site chord4|ug|auto,-o,--opencc s2t|t2s,--dry-run,--help.scripts/analyze-progression.py— sanity-check a progression: detect key, label each chord by scale degree (Roman + Nashville + diatonic/borrowed/secondary/ out-of-key), score each move against a rock-corpus transition matrix, and flag likely mis-transcriptions + duplicate/floating chords. Zero-dep (music21optional). Flags:<file.cho>or--key/--chords,--json,--help.scripts/validate-cho.sh <file.cho>— verify a file parses cleanly (chordpro --strict); PASS/FAIL to stdout, warnings to stderr. Guides install ifchordprois missing. Flags:--help.scripts/render-cho.sh <file.cho>— render to PDF with a CJK-safe font (auto-detected) and glyph-check the result, so a Chinese song can't silently render as tofu. Flags:-o/--output,--font,--keep-config,--help.scripts/fetch-lyrics.py— fetch lyrics from LRCLIB (synced.lrcdefault,--plainfor text). Auto-retries a CJK miss with a romanized/pinyin query. Use it for the.lrcsidecar / to fill missing lines, not to re-align a chart's chords. Self-contained viauv run. Flags:--artist,--track,--duration,--album,--plain,-o/--output,--help,--dry-run.scripts/audio-to-chords.py— best-effort link/audio → draft ChordPro (yt-dlp+chord-extractor). Degrades gracefully when the ACR backend is unavailable. Flags:-o/--output,--lrc,--help,--dry-run.
Bundled assets
Ready-to-open examples + a template under assets/ (all songs are public
domain; every .cho validates clean with chordpro --strict). Use them as
copy-paste starting points and few-shot references:
assets/example-amazing-grace.cho— simplest real song: metadata + inline chords.assets/example-when-the-saints.cho— chorus/verse structure +{chorus}recall.assets/example-greensleeves.cho— feature showcase:{define}diagrams, a{sot}tab block, chorus recall, transpose note.assets/template-song.cho— fill-in-the-blanks scaffold (renders as-is); the starting point for the interactive chord-fill flow.assets/example-synced-lyrics.lrc— sample time-synced.lrc(whatfetch-lyrics.pyreturns; feed it toaudio-to-chords.py --lrc).assets/example-a2crd-input.txt— chords-over-lyrics plain text; convert withchordpro --a2crd assets/example-a2crd-input.txt.
Reference files
references/chordpro-format.md— Read when hand-authoring or you need a directive beyond the cheat-sheet ({define}diagrams,{transpose}, markup, ABC/LilyPond,x_custom namespace).references/chord-tab-sources.md— Read when you need chords for a named song and have none:chart-to-cho.pyusage, where to find existing charts (91pu/Chord4/Ultimate Guitar/…), the per-site markup it parses, and the legality.references/music-theory.md— Read when validating a progression (key detection, Roman/Nashville degrees, diatonic-fit, the rock-corpus transition matrix, how to readanalyze-progression.py's verdict).references/cli-and-rendering.md— Read when installing, rendering, transposing, converting witha2crd, or validating/normalizing.references/audio-to-chords.md— Read when the user wants chords from an audio file or a link (ACR tool choice, beat/key, limits, escape hatches).references/lyrics-sources.md— Read when fetching lyrics (source table, synced-vs-plain, API-vs-scrape, legality, LRCLIB API).
Gotchas
- macOS has no official Homebrew
chordpro— install via CPAN (brew install perl cpanminus && cpanm App::Music::ChordPro). A signed.dmgGUI exists on GitHub Releases but the CLI is the CPAN route. Can't locate ChordPro.pm in @INCafter cpanm — cpanm installed into alocal::lib(~/perl5) that isn't on Perl's path. Activate it:eval "$(perl -I"$HOME/perl5/lib/perl5" -Mlocal::lib)"(persist in~/.zshrc).- No standalone
a2crdin ChordPro 6.x — usechordpro --a2crd input.txt. Its output needs a human pass (heuristic, column-based chord placement); always validate and eyeball before rendering. - Angle brackets
< >are text markup in ChordPro v6. Don't put<...>placeholders in lyrics/chords — the parser reads<b>/<i>-style markup and floods warnings. Use plain-text placeholders (seeassets/template-song.cho). - No official online validator. chordpro.org hosts docs + a desktop GUI, not
a "try it online" box. Validate locally with the CLI (
validate-cho.sh). - CJK renders as tofu by default — and passes validation.
chordpro's default fonts have no Han/Kana/Hangul glyphs, so a Chinese/Japanese/Korean song produces a blank PDF that still exits 0 and passesvalidate-cho.sh(which only checks parsing). Render CJK withscripts/render-cho.sh(auto CJK font + glyph-check), or pass--configpointingpdf.fonts.*at a CJK font. a2crdis Latin-only. Its column heuristic assumes 1 char = 1 column, but full-width CJK occupies 2 display cells, so chords drift. For CJK, align chords to syllables/phrases by musical judgment — don't trust column arithmetic.- Preserve a fetched chart's own alignment — don't re-merge. A published chart
already places each chord on a specific syllable (Chord4's
(字)markup, UG's columns);chart-to-cho.pyreproduces that exactly. Re-fetching "clean" lyrics from LRCLIB and re-aligning the chords by eye discards that ground truth and is what produces drifted, duplicated, and floating chords. LRCLIB is for the.lrcsidecar and for filling lines the chart omits — not for re-alignment. Note that a faithful transcription legitimately includes chords that land on a rest between syllables (instrumental beats), so not every "floating" chord is a bug —analyze-progression.pyflags them for review, not deletion. - A strict "Unknown chord" warning ≠ a parse error.
chordpro --strictwarns "Unknown chord" for a valid chord it has no built-in diagram for (e.g.Em/C#,F#m7b5); it still renders. Add a{define}(see the format reference) or ignore. {chorus}recall is a trap with 2+ recurring sections. It reprints only the most-recently-defined chorus, as a bare "Chorus" tag by default. Label distinct blocks ({start_of_chorus: name}/{chorus: name}) or expand repeats explicitly.WebFetchstrips copyrighted lyrics (returns "[lyrics omitted]"), so the chart-fetch path needs rawcurl+ per-site parsing; and from an image-only chartWebFetchmay fabricate chord placement — trust only an explicit chord list.- Chord accuracy from audio is ~80% at best on simple pop, worse otherwise.
Never present a machine-extracted sheet without the
AUTO-GENERATEDheader and a "verify chords/key/timing" caveat. - Auto-generated chart sites are ACR in disguise. Chordu / Chordify / GuitarTuna charts are machine-derived (~80%, same caveat) — treat them as drafts, unlike human-made charts on 91pu / Chord4 / Ultimate Guitar.
- Detected key is a suggestion. ACR outputs absolute pitches; key detection
confuses relative major/minor and keys a fifth apart, and enharmonic spelling
(Gb vs F#) is ambiguous. Confirm with the user before committing
{key:}. - Chords in
{sot}…{eot}tab blocks are NOT parsed — that's for ASCII tab. Use inline[..]in verses/choruses; reserve tab blocks for fret diagrams. - Lyrics/audio ToS. Prefer LRCLIB (open); most other lyric sources are scrape-only or paid, and downloading audio has copyright/ToS limits. Keep it personal-use and local; surface the caveat rather than hiding it.