From e0a1ce9d18e89850ab610ec45f64af2b3721cb87 Mon Sep 17 00:00:00 2001 From: Kenneth Reitz Date: Wed, 1 Apr 2026 13:27:08 -0400 Subject: [PATCH] =?UTF-8?q?Fix=20hold()=20inflating=20Part.total=5Fbeats?= =?UTF-8?q?=20and=20Score.duration=5Fms=20=E2=80=94=20v0.40.8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Note.beats now returns 0.0 for held notes (_hold=True), matching the renderer which already skipped advancing the beat position. Previously every hold() call added its full duration to the part's total, causing duration reports to be 2-3x too long on tracks with drone notes. Co-Authored-By: Claude Opus 4.6 (1M context) --- CHANGELOG.md | 9 +++++++++ pyproject.toml | 2 +- pytheory/rhythm.py | 2 ++ 3 files changed, 12 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index feba24a..0f780b6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,15 @@ All notable changes to PyTheory are documented here. +## 0.40.8 + +- **Fix hold() inflating duration** — `Note.beats` was returning the full + duration for held notes (`_hold=True`), causing `Part.total_beats` and + `Score.duration_ms` to overcount. A part with `hold(Sa, WHOLE * 4)` followed + by `add(Pa, QUARTER)` would report 17 beats instead of 1. Now held notes + return 0 beats, matching the renderer which already skipped advancing the + timeline for held notes. + ## 0.40.7 - **Expose missing Synth enum entries** — rhodes, wurlitzer, vibraphone, diff --git a/pyproject.toml b/pyproject.toml index 5b92723..90c4f0a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "pytheory" -version = "0.40.7" +version = "0.40.8" description = "Music Theory for Humans" readme = "README.md" license = "MIT" diff --git a/pytheory/rhythm.py b/pytheory/rhythm.py index ef8700b..ecae19e 100644 --- a/pytheory/rhythm.py +++ b/pytheory/rhythm.py @@ -475,6 +475,8 @@ class Note: @property def beats(self) -> float: + if self._hold: + return 0.0 return self.duration.value