Add sheet music command — [s] in picker or --sheet flag

Opens ABC notation rendered as sheet music in the browser via abcjs.
Auto-detects key from track's Key object, bass clef for low parts.
Bumps pytheory to >=0.41.2 for to_abc() support.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-07 07:45:02 -04:00
parent 52967c15c2
commit d13dd02f5e
3 changed files with 44 additions and 8 deletions
+39 -3
View File
@@ -613,7 +613,7 @@ def pick_track():
pass
stdscr.addstr(3, max(0, (w - 15) // 2), "" * 15,
curses.color_pair(2))
stdscr.addstr(4, max(0, (w - 56) // 2), "↑/↓ navigate ↵ play r render a play all R render all q quit",
stdscr.addstr(4, max(0, (w - 68) // 2), "↑/↓ navigate ↵ play s sheet r render a play all R render all q quit",
curses.A_DIM)
# Track list
@@ -711,6 +711,10 @@ def pick_track():
result[0] = entries[selected[0]][0]
action[0] = "play"
return
elif key == ord("s"):
result[0] = entries[selected[0]][0]
action[0] = "sheet"
return
elif key == ord("r"):
result[0] = entries[selected[0]][0]
action[0] = "render"
@@ -765,6 +769,8 @@ examples:
help="Show score metadata and stats")
insp.add_argument("--parts", action="store_true",
help="List all parts with details")
insp.add_argument("--sheet", action="store_true",
help="Open sheet music (ABC notation) in browser")
play = p.add_argument_group("playback")
play.add_argument("--from", dest="from_measure", type=int, metavar="N",
@@ -857,6 +863,32 @@ def _render_and_cache(path, args):
return buf, sr, offset_sec, score, mod
def _open_sheet(path):
"""Render a track's score as ABC notation and open sheet music in the browser."""
import tempfile
import webbrowser
score, mod = load_score(path)
title = get_title(mod, Path(path))
# Derive ABC key from the module's Key object (e.g. Key("Eb", "minor"))
abc_key = "C"
if hasattr(mod, "key"):
k = mod.key
root = getattr(k, "tonic_name", "C")
mode = getattr(k, "mode", "major")
abc_key = str(root)
if "minor" in mode.lower():
abc_key += "m"
html = score.to_abc(title=title, key=abc_key, html=True)
out = Path(tempfile.gettempdir()) / f"{Path(path).stem}_sheet.html"
out.write_text(html)
webbrowser.open(f"file://{out}")
print(f" Sheet music -> {out}")
def _play_track(path, args, force_render=False, render_only=False):
"""Load, render, and play a single track. Uses cached WAV if available.
Returns 'next', 'prev', or None."""
@@ -1046,6 +1078,8 @@ def main():
print(f"{f.name}: {e}")
print(f"\n Done! {done[0]} tracks cached.\n")
elif act == "sheet":
_open_sheet(path)
elif act == "render":
_play_track(path, args, force_render=True)
else:
@@ -1070,8 +1104,8 @@ def main():
print(f"File not found: {path}")
sys.exit(1)
# ── Info / parts (no playback) ────────────────────────────────
if args.info or args.parts or args.midi:
# ── Info / parts / sheet (no playback) ──────────────────────────
if args.info or args.parts or args.midi or args.sheet:
score, mod = load_score(path)
if args.bpm:
score.bpm = args.bpm
@@ -1081,6 +1115,8 @@ def main():
show_info(score, mod, path)
elif args.parts:
show_parts(score)
elif args.sheet:
_open_sheet(path)
elif args.midi:
score.save_midi(args.midi)
print(f"Exported MIDI -> {args.midi}")
+1 -1
View File
@@ -8,7 +8,7 @@ authors = [
]
requires-python = ">=3.10"
dependencies = [
"pytheory>=0.40.9",
"pytheory>=0.41.2",
]
[tool.uv]
Generated
+4 -4
View File
@@ -97,7 +97,7 @@ dependencies = [
]
[package.metadata]
requires-dist = [{ name = "pytheory", specifier = ">=0.40.9" }]
requires-dist = [{ name = "pytheory", specifier = ">=0.41.2" }]
[[package]]
name = "markdown-it-py"
@@ -287,7 +287,7 @@ wheels = [
[[package]]
name = "pytheory"
version = "0.40.9"
version = "0.41.2"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "rich" },
@@ -295,9 +295,9 @@ dependencies = [
{ name = "scipy", version = "1.17.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" },
{ name = "sounddevice" },
]
sdist = { url = "https://files.pythonhosted.org/packages/7e/cf/0366e974c27261de5219a032637512b3b7594118dfb5a219700a96e49445/pytheory-0.40.9.tar.gz", hash = "sha256:5a15b9c15c94d81f794edec5dbe03fc0c58a0bd08233777a62209df197755b2b", size = 187008, upload-time = "2026-04-03T03:15:15.939Z" }
sdist = { url = "https://files.pythonhosted.org/packages/d4/55/625d43b77c96bcd7eb0782c34c55dd9c0fba831e47e74af1f14f8d9088ff/pytheory-0.41.2.tar.gz", hash = "sha256:86eb9484921e2be2451215fba293e6daa55a1339593acb96dc061024097577e0", size = 188842, upload-time = "2026-04-07T11:44:13.51Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/db/87/3d0fb3cb84c8d199e2cf4807259524119912335211c2ee2531a9dc5c3527/pytheory-0.40.9-py3-none-any.whl", hash = "sha256:d9bad930c6e38d684fb75519ea989dc93331cb71628fbd47cf3449b9316fb730", size = 191155, upload-time = "2026-04-03T03:15:17.308Z" },
{ url = "https://files.pythonhosted.org/packages/86/69/4fc3bd3580ad45f05f2f3e6a77ac637b0b12f663c5999685b712d521e502/pytheory-0.41.2-py3-none-any.whl", hash = "sha256:90298eb5df74ad187e512faac0856458b4b906905e4d1eeb24c02a972599fc5f", size = 193031, upload-time = "2026-04-07T11:44:14.705Z" },
]
[[package]]