ABC notation export via Score.to_abc() — v0.41.0

New method converts scores to ABC notation with support for multi-voice,
chords, rests, accidentals, and all durations. Pass html=True for a
self-contained HTML page with abcjs sheet music rendering.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-07 07:37:56 -04:00
parent fcc5db8e3d
commit 6ecef688e1
5 changed files with 22 additions and 9 deletions
+7
View File
@@ -2,6 +2,13 @@
All notable changes to PyTheory are documented here.
## 0.41.0
- **ABC notation export** — `Score.to_abc()` converts scores to ABC notation
strings. Supports multi-voice scores (via `V:` directives), chords, rests,
accidentals, and all standard durations. Pass `html=True` to get a
self-contained HTML page that renders sheet music in the browser via abcjs.
## 0.40.9
- **Mellotron synth** — tape-replay keyboard with wow/flutter, tape saturation,
+1 -1
View File
@@ -1,6 +1,6 @@
[project]
name = "pytheory"
version = "0.40.9"
version = "0.41.0"
description = "Music Theory for Humans"
readme = "README.md"
license = "MIT"
+1 -1
View File
@@ -1,6 +1,6 @@
"""PyTheory: Music Theory for Humans."""
__version__ = "0.40.9"
__version__ = "0.41.0"
from .tones import Tone, Interval
from .systems import System, SYSTEMS, TET
+12 -6
View File
@@ -4463,11 +4463,13 @@ class Score:
return f"{abc_acc}{note_char}{oct_str}"
def _notes_to_abc(self, notes, default_unit, ts):
def _notes_to_abc(self, notes, default_unit, ts,
bars_per_line=4):
"""Convert a list of Note objects to an ABC body string."""
beats_per_measure = ts.beats_per_measure
parts = []
tokens = []
beat_in_measure = 0.0
measure_count = 0
for note in notes:
beats = note.duration.value
@@ -4506,16 +4508,20 @@ class Score:
frac = Fraction(multiplier).limit_denominator(16)
dur_str = f"{frac.numerator}/{frac.denominator}"
parts.append(f"{abc_note}{dur_str}")
tokens.append(f"{abc_note}{dur_str}")
beat_in_measure += beats
if beat_in_measure >= beats_per_measure - 0.001:
parts.append("|")
measure_count += 1
if measure_count % bars_per_line == 0:
tokens.append("|\n")
else:
tokens.append("|")
beat_in_measure -= beats_per_measure
body = " ".join(parts)
body = " ".join(tokens)
# Clean up trailing/double barlines
body = body.replace("| |", "|").rstrip("| ").rstrip()
body = body.replace("| |", "|").rstrip("| \n").rstrip()
if not body.endswith("|"):
body += " |"
return body
Generated
+1 -1
View File
@@ -690,7 +690,7 @@ wheels = [
[[package]]
name = "pytheory"
version = "0.40.9"
version = "0.41.0"
source = { editable = "." }
dependencies = [
{ name = "rich" },