Plan the guitar state of an entire fixed set, not just one song.
CapoRoute is a local, deterministic CLI for singer-guitarists. You declare the concert keys you can sing, the chord-shape keys you want to play, and the tunings available for each song. CapoRoute finds the globally lowest-cost route through the set while keeping your running order untouched.
It produces a stage sheet like this:
1. Paper Lanterns
key=D shape=D capo=0 tuning=drop-d
change: Set tuning to drop-d; Set capo to 0
2. River Signal
key=D shape=D capo=0 tuning=drop-d
change: No setup change
3. Borrowed Light
key=E shape=D capo=2 tuning=drop-d
change: Move capo 0 -> 2
4. Quiet Machinery
key=G shape=G capo=0 tuning=standard
change: Retune drop-d -> standard; Move capo 2 -> 0
The first song may use a slightly less preferred shape if that prevents a much more expensive change before the next song. That whole-set tradeoff is the product.
- Single-song capo calculators answer “what fret gives me this key?” CapoRoute optimizes adjacent physical states across the whole show.
- Setlist managers store songs, lyrics, and gigs. CapoRoute stores no copyrighted song content and has no account or database.
- DJ set optimizers reorder tracks by tempo/key flow. CapoRoute treats your artistic song order as authoritative.
- CapoRoute does not guess vocal range, detect audio keys, or tell you which key is safe. You provide acceptable concert keys explicitly.
The research and scope decision are documented in docs/ideas/caporoute.md.
CapoRoute 0.1.0 supports Python 3.11 or newer and has no runtime dependencies.
From a GitHub Release wheel:
python -m pip install caporoute-0.1.0-py3-none-any.whl
caporoute --versionFrom a source checkout with uv:
uv sync --locked
uv run caporoute --versionuv run caporoute check examples/coffeehouse.json
uv run caporoute plan examples/coffeehouse.json
uv run caporoute plan examples/coffeehouse.json --format markdown --output build/coffeehouse.md
uv run caporoute plan examples/coffeehouse.json --format json --output build/coffeehouse.jsonExpected check result:
OK: Coffeehouse Proof (6 songs, 9 feasible choices)
Committed outputs are available as docs/demo/coffeehouse.md and docs/demo/coffeehouse.json.
{
"set_name": "Small Room",
"max_capo": 7,
"weights": {
"capo_move": 1.5,
"tuning_change": 8.0,
"capo_height": 0.1
},
"songs": [
{
"title": "Northbound",
"performance_keys": [
{ "key": "D", "preference": 0 },
{ "key": "Eb", "preference": 2 }
],
"shapes": [
{ "key": "C", "preference": 0, "tunings": ["standard"] },
{ "key": "D", "preference": 1, "tunings": ["drop-d"] }
]
}
]
}- Keys are
AthroughG, optional#orb, and optionalmfor minor. preferenceis a non-negative penalty; lower is better.- Major performance keys only pair with major shape keys; minor only pairs with minor.
max_capois inclusive and ranges from 0 through 11.- Tuning names are labels you control. Equal labels mean no retune is needed.
The full contract is in docs/spec.md.
For one playable choice:
local = performance preference + shape preference + capo × capo_height
Between adjacent songs:
transition = |next capo - current capo| × capo_move
+ tuning_change when the tuning label changes
CapoRoute minimizes all local and transition costs across the fixed order with dynamic programming. It reports the selected state, the feasible-choice count, and each cost component. Exact ties are deterministic.
Weights are personal workflow preferences, not universal musical truths. Increase tuning_change if retuning on stage is especially disruptive; increase capo_move if fast fret changes are the main problem.
caporoute check SET.json
caporoute plan SET.json [--format terminal|json|markdown|csv] [--output PATH]
0: valid and feasible.1: valid input, but at least one named song has no feasible choice.2: usage, JSON, validation, read, or write error.
--output writes atomically. A failed plan does not leave a partial report. JSON and Markdown preserve source titles; CSV neutralizes formula-like title cells before spreadsheet import.
Run the same release gate used by CI and the tag workflow:
uv sync --locked
uv run python scripts/check.pyThe gate checks formatting, lint, strict types, at least 90% branch coverage, dependency audit, valid and impossible examples, deterministic committed reports, wheel/sdist contents, checksums, and a clean-wheel install.
Focused commands:
uv run ruff format --check src tests scripts examples
uv run ruff check src tests scripts examples
uv run mypy src tests scripts
uv run pytest -p no:cacheprovider --basetemp=.tmp/pytest
uv build --clearDo not skip a test or lower the coverage gate. Start with the error category:
error: ... expected valid JSON: validate commas/quotes, then reruncaporoute check FILE.error: ... max_capo: use an integer from 0 through 11.infeasible: song N ...: add an acceptable concert key, add a same-mode shape, or raisemax_capo; reruncheckbeforeplan.cannot write ...: create the parent directory and verify the target is a file path, not a directory.uv lock --checkfails: runuv lock, inspectuv.lock, then rerun the full gate.- dependency download fails with a socket/network error: restore network access and rerun; do not remove the audit.
More detail is in docs/TROUBLESHOOTING.md.
CapoRoute performs no network requests and collects no telemetry. It reads only the input path you provide and writes only an explicit output path. Report titles are untrusted data: terminal controls are made visible, Markdown delimiters are escaped, and CSV formula prefixes are neutralized.
Report security issues using SECURITY.md.
Project metadata, console entry points, CI, and release automation follow current primary documentation:
- Python Packaging User Guide: writing
pyproject.toml - uv: using uv in GitHub Actions
- GitHub Actions workflow syntax
- GitHub CLI
gh release create
MIT. See LICENSE.