diff --git a/CHANGELOG.md b/CHANGELOG.md index ae80f2f..8cb6778 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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, diff --git a/pyproject.toml b/pyproject.toml index 1cf4d1a..c9e1e63 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -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" diff --git a/pytheory/__init__.py b/pytheory/__init__.py index 3b6178a..c7bbae6 100644 --- a/pytheory/__init__.py +++ b/pytheory/__init__.py @@ -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 diff --git a/pytheory/rhythm.py b/pytheory/rhythm.py index e42c585..646b6ed 100644 --- a/pytheory/rhythm.py +++ b/pytheory/rhythm.py @@ -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 diff --git a/uv.lock b/uv.lock index 6b2a609..78d3162 100644 --- a/uv.lock +++ b/uv.lock @@ -690,7 +690,7 @@ wheels = [ [[package]] name = "pytheory" -version = "0.40.9" +version = "0.41.0" source = { editable = "." } dependencies = [ { name = "rich" },