chordpro
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]lyric chords, 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
.cho with the chordpro or a2crd CLI.
- "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/魔鏡, KKBOX, Musixmatch, LRCLIB, Genius,
NetEase, QQ Music) — which give time-synced
.lrc vs 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
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 (91譜/Chord4/UG — many are JS-rendered or image-only 六线谱),
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 / 魔鏡 |
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 (music21
optional). 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
if chordpro is 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 .lrc default,
--plain for text). Auto-retries a CJK miss with a romanized/pinyin query. Use it
for the .lrc sidecar / to fill missing lines, not to re-align a chart's chords.
Self-contained via uv 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 (what
fetch-lyrics.py returns; feed it to audio-to-chords.py --lrc).
assets/example-a2crd-input.txt — chords-over-lyrics plain text; convert with
chordpro --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.py usage, where to find existing charts
(91譜/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 read analyze-progression.py's verdict).
references/cli-and-rendering.md — Read when installing, rendering,
transposing, converting with a2crd, 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 .dmg
GUI exists on GitHub Releases but the CLI is the CPAN route.
Can't locate ChordPro.pm in @INC after cpanm — cpanm installed into a
local::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
a2crd in ChordPro 6.x — use chordpro --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 (see assets/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 passes validate-cho.sh (which only checks
parsing). Render CJK with scripts/render-cho.sh (auto CJK font + glyph-check),
or pass --config pointing pdf.fonts.* at a CJK font.
a2crd is 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.py reproduces 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 .lrc
sidecar 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.py flags them for review, not deletion.
- A strict "Unknown chord" warning ≠ a parse error.
chordpro --strict warns
"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.
WebFetch strips copyrighted lyrics (returns "[lyrics omitted]"), so the
chart-fetch path needs raw curl + per-site parsing; and from an image-only chart
WebFetch may 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-GENERATED header 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 91譜 / 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.
1---2name: chordpro3description: Author, convert, validate, render, and transpose ChordPro chord sheets, and build one for a song from an existing online chart, lyrics, or audio. Use when the user mentions ChordPro or .cho/.crd/.pro files, `[C]lyric` inline chords or `{title:}`/`{start_of_chorus}` directives, the `chordpro`/`a2crd` CLI, converting chords-over-lyrics (Ultimate Guitar/OnSong) text, making a guitar chord sheet for a song by name, or extracting chords from a YouTube/Bilibili/SoundCloud link or mp3/wav. References chordpro.org.4---56# chordpro78Produce and work with [ChordPro](https://www.chordpro.org/) chord sheets — the9plain-text format where chords live inline in the lyrics as `[C]like [G]this`.10This skill is a **router that degrades gracefully**: it picks the most reliable11workflow for whatever the user actually has (a `.cho` file, a chords-over-lyrics12paste, bare lyrics, an audio file, or just a song title) and every path ends by13**validating** the output. It is honest about the one hard part — extracting14chords from raw audio is an ~80%-accurate *draft*, never an oracle — and falls15back to interactive, fill-in-the-gaps assistance instead of pretending otherwise.1617## When to use1819- User names **ChordPro** / `.cho` / `.crd` / `.chopro` / `.pro`, inline20 `[C]lyric` chords, or `{title:}` / `{start_of_chorus}` / `{soc}` directives.21- Convert an existing sheet: **chords-over-lyrics** text (Ultimate Guitar, OnSong,22 a lyric with chord letters on the line above) → ChordPro.23- Render / transpose / validate a `.cho` with the **`chordpro`** or **`a2crd`** CLI.24- "Make a chord sheet for <song>" starting from **lyrics only**, or from a25 **YouTube / Bilibili / SoundCloud link** or an **mp3 / wav** file.26- Ask about **lyrics sources** (Mojim/魔鏡, KKBOX, Musixmatch, LRCLIB, Genius,27 NetEase, QQ Music) — which give time-synced `.lrc` vs plain text.2829## When NOT to use3031- **Engraving staff notation** (notes on a staff, MusicXML / MIDI / LilyPond /32 MuseScore output) — ChordPro is lyrics+chords, not a score. Hand off to a33 notation tool. (ChordPro *can* embed ABC/LilyPond fragments — see the format34 reference — but it is not an engraver.)35- **OMR from a scanned score image** (photo/PDF of printed sheet music → notes)36 is out of scope; there is no reliable open path. Offer manual transcription37 into ChordPro instead, and say so plainly.3839## Authoritative sources4041- Format + directives + CLI: **https://www.chordpro.org/** (link, don't42 paraphrase from memory — the directive set evolves).43- Source / releases: **https://github.com/ChordPro/chordpro**.4445## The router — pick the workflow by what the user has4647| User has… | Do this | Reliability |48|---|---|---|49| A `.cho`/`.crd`/`.pro` file | Render / transpose / **validate** via `chordpro` CLI | High |50| **Chords-over-lyrics** text (UG/OnSong) | `a2crd` → light manual cleanup → validate | High |51| 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**) |52| **Lyrics only**, no chart found | Fetch/confirm lyrics → **interactively** fill chords (propose from key, ask the user to confirm/correct) | Medium (human-in-loop) |53| An **audio file or link**, want a machine draft | `scripts/audio-to-chords.py` → *draft* with `AUTO-GENERATED` header → human correction | ~80% (last resort) |5455Prefer the highest row that fits. Two rules that matter: don't jump to the audio56pipeline when the user already pasted chords (`a2crd` is faster and correct); and57for a **named/popular song, search for an existing human-made chart first** — it's58usually more accurate than audio ACR and sidesteps the download-ToS problem59entirely.6061**Before charting a named song, confirm it exists as described** — user-supplied62artist/album is sometimes wrong, and searching a bad artist can grab a mislabeled63chart; a quick Wikipedia/LRCLIB match prevents shipping a misattribution.64**Non-interactive/batch runs** can't use the "ask the user" rows — instead produce a65best-effort chart from the published chord vocabulary, caption every uncertainty in66a `{comment:}`, and omit unknown metadata rather than fabricate it.6768## ChordPro cheat-sheet + output template6970- **Extensions**: `.cho` (recommended), also `.crd` `.chopro` `.chord` `.pro`.71 Plain UTF-8. `#` starts a comment line; blank lines separate blocks.72- **Inline chords**: `[C]` immediately before the syllable it sits over —73 `Swing [G]low, sweet [C]chari[G]ot`. Annotations (non-chords): `[*softly]`.74- **Metadata**: `{title:}`/`{t:}`, `{subtitle:}`/`{st:}`, `{artist:}`,75 `{composer:}`, `{album:}`, `{year:}`, `{key:}`, `{tempo:}`, `{time:}`, `{capo:}`.76- **Environments** (long / short): chorus `{start_of_chorus}`/`{soc}` …77 `{end_of_chorus}`/`{eoc}`; verse `{sov}`…`{eov}`; bridge `{sob}`…`{eob}`;78 tab (monospaced, chords NOT parsed) `{sot}`…`{eot}`. Recall a chorus with79 `{chorus}`. Comments: `{comment:}`/`{c:}`.80- **Key vs capo**: `{key:}` is the *written/fingering* key of the shapes shown;81 `{capo:}` is the capo used. If charts disagree, prefer the one whose chord82 vocabulary is internally consistent, and caption the *sounding* pitch.83- **Chorus recall**: `{chorus}` reprints the most-recently-defined chorus (showing84 only a "Chorus" label by default). With 2+ distinct recurring sections, label85 them — `{start_of_chorus: name}` … `{chorus: name}` — or expand the repeat.8687Emit this shape — metadata block → environments/comments → verses/choruses:8889```90{title: Swing Low Sweet Chariot}91{artist: Traditional}92{key: G}93{tempo: 90}94{time: 4/4}9596{comment: Intro}97[G] [C] [G] [D]9899{start_of_chorus: Chorus}100Swing [G]low, sweet [C]chari[G]ot,101Comin' for to carry me [D]home.102{end_of_chorus}103104{start_of_verse: Verse 1}105I [G]looked over Jordan, and [C]what did I [G]see,106Comin' for to carry me [D]home.107{end_of_verse}108109{chorus}110```111112Full directive set (chord `{define}` diagrams, `{transpose}`, markup, ABC/LilyPond113blocks, `x_` custom namespace) → read `references/chordpro-format.md`.114115## The `chordpro` / `a2crd` CLI (essentials)116117The official tool is a Perl program (CPAN dist **`App::Music::ChordPro`**). Current1186.x releases install the **`chordpro`** command; the chords-over-lyrics importer119is `chordpro --a2crd` (a standalone `a2crd` binary shipped in older releases and120may be absent — prefer `chordpro --a2crd`).121122```bash123# Install (macOS — there is NO official Homebrew formula; use CPAN)124brew install perl cpanminus && cpanm App::Music::ChordPro125# If `chordpro` then errors "Can't locate ChordPro.pm", activate local::lib once:126eval "$(perl -I"$HOME/perl5/lib/perl5" -Mlocal::lib)" # add to ~/.zshrc to persist127128# Render129chordpro -o song.pdf song.cho # PDF (format inferred from extension)130chordpro --generate=HTML -o song.html song.cho131132# Transpose (N semitones; suffix s/f forces sharp/flat spelling)133chordpro -x 2 -o up.pdf song.cho134chordpro -x -3f -o down.pdf song.cho135136# Convert chords-over-lyrics text -> ChordPro (the official importer)137chordpro --a2crd input.txt -o song.cho138```139140Install-per-platform, generators, config JSON, songbook `--toc`, and `a2crd`141heuristic tuning → read `references/cli-and-rendering.md`.142143## Verify loop — never hand back an unvalidated `.cho`144145Every generated or edited file goes through the parser before you present it:146147```bash148scripts/validate-cho.sh song.cho # wraps the command below; PASS/FAIL + warnings149# equivalently:150chordpro --strict --generate=Text -o - song.cho # exit 0 + no stderr warnings = valid151```152153To auto-normalize to canonical form (fix spacing, round-trip directives):154`chordpro --generate=ChordPro -o song.cho song.cho`. If `chordpro` isn't155installed, the script prints the install one-liner — say so and offer to proceed156without rendering (the format is still human-checkable).157158**Sanity-check the harmony (non-blocking).** After building a chart — especially a159fetched or auto-generated one — run `scripts/analyze-progression.py song.cho`. It160detects the key, re-expresses every chord as a scale degree (Roman + Nashville),161and flags the ones worth a second look: **out-of-key chords reached by an162improbable move** (the strongest "likely mis-transcribed" signal), plus duplicate163and floating chords. It's advisory, not a gate — unusual ≠ wrong — but many164low-plausibility flags mean the chart or the detected key needs another look, and165it's a good **tie-breaker** when two fetched charts disagree. Theory + how to read166the verdict → `references/music-theory.md`.167168**Producing a PDF? Verify it *renders*, not just parses.** `validate-cho.sh` only169checks that the file parses — it can't see a broken PDF. `chordpro`'s default170fonts have **no CJK glyphs**, so a Chinese/Japanese/Korean song renders as blank171tofu while still exiting 0 and passing validation. Use `scripts/render-cho.sh172song.cho` — it auto-detects a CJK font, renders, and glyph-checks the PDF (fails173loudly on tofu).174175## Finding existing chord charts (the usual first move)176177For a named/popular song, a human-made chart almost always already exists online178and beats audio extraction on accuracy. **`scripts/chart-to-cho.py` fetches and179converts it while preserving the chart's own chord-to-syllable alignment** — the180key to accuracy. The source already encodes which syllable each chord sits on181(Chord4's `後來的生(活)`, UG's monospace columns); the script reproduces that. **Never182re-align fetched chords onto separately-fetched lyrics by eye** — that lossy183re-merge is what drifts chords and drops them as floaters.184185```bash186# 1. search: 中文 吉他谱/和弦/弹唱谱 ; EN "<artist> <title>" chords / ultimate guitar187# 2. convert (auto-detects Chord4 vs Ultimate Guitar; keeps alignment + metadata):188uv run scripts/chart-to-cho.py --url "<chart-url>" -o song.cho189# Cloudflare wall? save the page in a browser, then: --html saved.html --site chord4190# 3. LRCLIB is only the .lrc sidecar + a gap-filler for missing lines — NOT the191# alignment source (the chart already aligned the chords):192uv run scripts/fetch-lyrics.py --artist "…" --track "…" -o song.lrc193# 4. sanity-check the harmony (key + degrees + likely mis-transcriptions):194uv run scripts/analyze-progression.py song.cho195# 5. validate + render (CJK-safe):196scripts/validate-cho.sh song.cho && scripts/render-cho.sh song.cho197```198199`chart-to-cho.py` adds the source `{comment:}` caption itself. Human-made chart, not200machine ACR — so no `AUTO-GENERATED` header. Per-site extraction internals, the site201catalog (91譜/Chord4/UG — many are JS-rendered or image-only 六线谱),202simplified↔traditional (`--opencc` / URL variant), and legality →203read `references/chord-tab-sources.md`. Harmony theory behind step 4 →204`references/music-theory.md`.205206## Generating from audio / links — the fallback when no chart exists207208This is the shakiest path — reach for it only when no existing chart is available.209210**Set expectations first**: open automatic chord recognition tops out around **78–80% on simple major/minor pop**211and drops sharply on 7ths, extended/jazz chords, key changes, and quiet or dense212mixes; inversions/slash chords are unreliable; and it cannot know the capo a213guitarist used. Treat the result as a **draft to correct**, not a transcription.214215```bash216# Best-effort turnkey: link or local file -> draft ChordPro217uv run scripts/audio-to-chords.py --dry-run "https://youtu.be/..." # show the plan first218uv run scripts/audio-to-chords.py -o draft.cho "https://youtu.be/..."219```220221The script downloads audio with `yt-dlp` (YouTube/Bilibili/SoundCloud) and runs222`chord-extractor`/Chordino. Its output **must** carry the223`{comment: AUTO-GENERATED — verify chords/key/timing}` header. On macOS the224Chordino Vamp plugin often isn't installed; the script detects that and prints225exact install guidance + manual alternatives instead of crashing — relay that to226the user rather than silently failing. Tool comparison, beat/key detection, and227commercial escape hatches (Moises/Songsterr have APIs; Chordify/Chord AI/Ultimate228Guitar don't) → read `references/audio-to-chords.md`.229230**Legal**: only process content the user owns or has the right to use, for231personal use, locally; downloading copyrighted audio can violate a platform's232ToS. Surface this — don't bury it.233234## Lyrics sources235236Default to **LRCLIB** — free, no API key, returns time-synced `.lrc`:237238```bash239uv run scripts/fetch-lyrics.py --artist "Adele" --track "Hello" --duration 295240uv run scripts/fetch-lyrics.py --plain --artist "..." --track "..." # plain text241```242243| Source | Access | Synced `.lrc`? |244|---|---|---|245| **LRCLIB** | free, no key | **yes** |246| Musixmatch | API key; full/synced is paid | yes (paid) |247| NetEase / QQ Music | unofficial community APIs | yes (Chinese catalog) |248| Genius (`lyricsgenius`) | API token; lyrics scraped from page | no (plain) |249| Mojim / 魔鏡 | scrape-only | no (plain) |250| KKBOX | partner-gated | n/a |251252Full comparison, legality, and the LRCLIB API → read `references/lyrics-sources.md`.253254## Available scripts255256- **`scripts/chart-to-cho.py`** — fetch an existing chart (Chord4 primary, Ultimate257 Guitar fallback) and convert it to inline ChordPro, **preserving the chart's own258 chord-to-syllable alignment**. Auto-detects the site; pulls title/artist/key/capo.259 Flags: `--url`/`--html`, `--site chord4|ug|auto`, `-o`, `--opencc s2t|t2s`,260 `--dry-run`, `--help`.261- **`scripts/analyze-progression.py`** — sanity-check a progression: detect key,262 label each chord by scale degree (Roman + Nashville + diatonic/borrowed/secondary/263 out-of-key), score each move against a rock-corpus transition matrix, and flag264 likely mis-transcriptions + duplicate/floating chords. Zero-dep (`music21`265 optional). Flags: `<file.cho>` or `--key`/`--chords`, `--json`, `--help`.266- **`scripts/validate-cho.sh <file.cho>`** — verify a file parses cleanly267 (`chordpro --strict`); PASS/FAIL to stdout, warnings to stderr. Guides install268 if `chordpro` is missing. Flags: `--help`.269- **`scripts/render-cho.sh <file.cho>`** — render to PDF with a **CJK-safe** font270 (auto-detected) and glyph-check the result, so a Chinese song can't silently271 render as tofu. Flags: `-o/--output`, `--font`, `--keep-config`, `--help`.272- **`scripts/fetch-lyrics.py`** — fetch lyrics from LRCLIB (synced `.lrc` default,273 `--plain` for text). Auto-retries a CJK miss with a romanized/pinyin query. Use it274 for the `.lrc` sidecar / to fill missing lines, not to re-align a chart's chords.275 Self-contained via `uv run`. Flags: `--artist`, `--track`, `--duration`,276 `--album`, `--plain`, `-o/--output`, `--help`, `--dry-run`.277- **`scripts/audio-to-chords.py`** — best-effort link/audio → draft ChordPro278 (`yt-dlp` + `chord-extractor`). Degrades gracefully when the ACR backend is279 unavailable. Flags: `-o/--output`, `--lrc`, `--help`, `--dry-run`.280281## Bundled assets282283Ready-to-open examples + a template under `assets/` (all songs are **public284domain**; every `.cho` validates clean with `chordpro --strict`). Use them as285copy-paste starting points and few-shot references:286287- `assets/example-amazing-grace.cho` — simplest real song: metadata + inline chords.288- `assets/example-when-the-saints.cho` — chorus/verse structure + `{chorus}` recall.289- `assets/example-greensleeves.cho` — feature showcase: `{define}` diagrams, a290 `{sot}` tab block, chorus recall, transpose note.291- `assets/template-song.cho` — fill-in-the-blanks scaffold (renders as-is); the292 starting point for the interactive chord-fill flow.293- `assets/example-synced-lyrics.lrc` — sample time-synced `.lrc` (what294 `fetch-lyrics.py` returns; feed it to `audio-to-chords.py --lrc`).295- `assets/example-a2crd-input.txt` — chords-over-lyrics plain text; convert with296 `chordpro --a2crd assets/example-a2crd-input.txt`.297298## Reference files299300- `references/chordpro-format.md` — Read **when** hand-authoring or you need a301 directive beyond the cheat-sheet (`{define}` diagrams, `{transpose}`, markup,302 ABC/LilyPond, `x_` custom namespace).303- `references/chord-tab-sources.md` — Read **when** you need chords for a named304 song and have none: `chart-to-cho.py` usage, where to find existing charts305 (91譜/Chord4/Ultimate Guitar/…), the per-site markup it parses, and the legality.306- `references/music-theory.md` — Read **when** validating a progression (key307 detection, Roman/Nashville degrees, diatonic-fit, the rock-corpus transition308 matrix, how to read `analyze-progression.py`'s verdict).309- `references/cli-and-rendering.md` — Read **when** installing, rendering,310 transposing, converting with `a2crd`, or validating/normalizing.311- `references/audio-to-chords.md` — Read **when** the user wants chords from an312 audio file or a link (ACR tool choice, beat/key, limits, escape hatches).313- `references/lyrics-sources.md` — Read **when** fetching lyrics (source table,314 synced-vs-plain, API-vs-scrape, legality, LRCLIB API).315316## Gotchas317318- **macOS has no official Homebrew `chordpro`** — install via CPAN319 (`brew install perl cpanminus && cpanm App::Music::ChordPro`). A signed `.dmg`320 GUI exists on GitHub Releases but the CLI is the CPAN route.321- **`Can't locate ChordPro.pm in @INC` after cpanm** — cpanm installed into a322 `local::lib` (`~/perl5`) that isn't on Perl's path. Activate it:323 `eval "$(perl -I"$HOME/perl5/lib/perl5" -Mlocal::lib)"` (persist in `~/.zshrc`).324- **No standalone `a2crd` in ChordPro 6.x** — use `chordpro --a2crd input.txt`.325 Its output needs a human pass (heuristic, column-based chord placement); always326 validate and eyeball before rendering.327- **Angle brackets `< >` are text markup in ChordPro v6.** Don't put `<...>`328 placeholders in lyrics/chords — the parser reads `<b>`/`<i>`-style markup and329 floods warnings. Use plain-text placeholders (see `assets/template-song.cho`).330- **No official online validator.** chordpro.org hosts docs + a desktop GUI, not331 a "try it online" box. Validate locally with the CLI (`validate-cho.sh`).332- **CJK renders as tofu by default — and passes validation.** `chordpro`'s default333 fonts have no Han/Kana/Hangul glyphs, so a Chinese/Japanese/Korean song produces334 a blank PDF that still exits 0 and passes `validate-cho.sh` (which only checks335 parsing). Render CJK with `scripts/render-cho.sh` (auto CJK font + glyph-check),336 or pass `--config` pointing `pdf.fonts.*` at a CJK font.337- **`a2crd` is Latin-only.** Its column heuristic assumes 1 char = 1 column, but338 full-width CJK occupies 2 display cells, so chords drift. For CJK, align chords to339 syllables/phrases by musical judgment — don't trust column arithmetic.340- **Preserve a fetched chart's own alignment — don't re-merge.** A published chart341 already places each chord on a specific syllable (Chord4's `(字)` markup, UG's342 columns); `chart-to-cho.py` reproduces that exactly. Re-fetching "clean" lyrics343 from LRCLIB and re-aligning the chords by eye **discards that ground truth** and is344 what produces drifted, duplicated, and floating chords. LRCLIB is for the `.lrc`345 sidecar and for filling lines the chart omits — not for re-alignment. Note that a346 faithful transcription legitimately includes chords that land on a *rest* between347 syllables (instrumental beats), so not every "floating" chord is a bug —348 `analyze-progression.py` flags them for review, not deletion.349- **A strict "Unknown chord" warning ≠ a parse error.** `chordpro --strict` warns350 "Unknown chord" for a *valid* chord it has no built-in diagram for (e.g. `Em/C#`,351 `F#m7b5`); it still renders. Add a `{define}` (see the format reference) or ignore.352- **`{chorus}` recall is a trap with 2+ recurring sections.** It reprints only the353 most-recently-defined chorus, as a bare "Chorus" tag by default. Label distinct354 blocks (`{start_of_chorus: name}` / `{chorus: name}`) or expand repeats explicitly.355- **`WebFetch` strips copyrighted lyrics** (returns "[lyrics omitted]"), so the356 chart-fetch path needs raw `curl` + per-site parsing; and from an image-only chart357 `WebFetch` may *fabricate* chord placement — trust only an explicit chord list.358- **Chord accuracy from audio is ~80% at best** on simple pop, worse otherwise.359 Never present a machine-extracted sheet without the `AUTO-GENERATED` header and360 a "verify chords/key/timing" caveat.361- **Auto-generated chart sites are ACR in disguise.** Chordu / Chordify /362 GuitarTuna charts are machine-derived (~80%, same caveat) — treat them as drafts,363 unlike human-made charts on 91譜 / Chord4 / Ultimate Guitar.364- **Detected key is a suggestion.** ACR outputs absolute pitches; key detection365 confuses relative major/minor and keys a fifth apart, and enharmonic spelling366 (Gb vs F#) is ambiguous. Confirm with the user before committing `{key:}`.367- **Chords in `{sot}`…`{eot}` tab blocks are NOT parsed** — that's for ASCII tab.368 Use inline `[..]` in verses/choruses; reserve tab blocks for fret diagrams.369- **Lyrics/audio ToS.** Prefer LRCLIB (open); most other lyric sources are370 scrape-only or paid, and downloading audio has copyright/ToS limits. Keep it371 personal-use and local; surface the caveat rather than hiding it.