Part.hold() — polyphonic overlap on a single part

hold() adds a note without advancing the beat position, so the
next note starts at the same time. Enables: piano sustain (bass
rings while melody plays), drone notes under melody, held chords
with moving lines.

Two lines in the renderer: skip beat_pos advance when _hold is set.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-03-28 11:38:41 -04:00
parent 4edf1d983d
commit 11e4417c62
2 changed files with 35 additions and 2 deletions
+5 -2
View File
@@ -4200,7 +4200,9 @@ def _render_notes_to_buf(notes, buf, samples_per_beat, total_samples,
# Right channel gets up-detuned, left gets down-detuned
stereo_buf[start:end, 1] += up_env * gain * spread_amt
stereo_buf[start:end, 0] += down_env * gain * spread_amt
beat_pos += note.beats
# hold() notes don't advance the beat position
if not getattr(note, '_hold', False):
beat_pos += note.beats
def _render_legato_to_buf(notes, buf, samples_per_beat, total_samples,
@@ -4242,7 +4244,8 @@ def _render_legato_to_buf(notes, buf, samples_per_beat, total_samples,
events.append((start, end, hz, vel))
else:
events.append((start, end, 0, vel)) # rest
beat_pos += note.beats
if not getattr(note, '_hold', False):
beat_pos += note.beats
if not events:
return
+30
View File
@@ -426,6 +426,7 @@ class Note:
bend: float = 0.0
bend_type: str = "smooth" # "smooth" (log), "linear", "late"
lyric: str = "" # syllable for vocal synth
_hold: bool = False # if True, don't advance beat position
@property
def beats(self) -> float:
@@ -2223,6 +2224,35 @@ class Part:
bend_type=bend_type, lyric=lyric))
return self
def hold(self, tone_or_string, duration=Duration.QUARTER, *, velocity: int = 100,
bend: float = 0.0, bend_type: str = "smooth", lyric: str = "") -> "Part":
"""Add a note without advancing the beat position.
The note plays at the current position but the next note
starts at the *same* time — enabling polyphonic overlap
on a single part.
Use this for: piano sustain pedal (bass note rings while
melody plays above), guitar strumming with individual
string timing, held drone notes under a melody.
Example::
>>> piano = score.part("piano", instrument="piano")
>>> piano.hold("C3", Duration.WHOLE) # bass rings for 4 beats
>>> piano.add("E4", Duration.HALF) # starts at same time as C3
>>> piano.add("G4", Duration.HALF) # starts at beat 2
"""
if isinstance(tone_or_string, str):
from .tones import Tone
tone_or_string = Tone.from_string(tone_or_string, system=self._system)
if isinstance(duration, (int, float)):
duration = _RawDuration(duration)
self.notes.append(Note(tone=tone_or_string, duration=duration,
velocity=velocity, bend=bend,
bend_type=bend_type, lyric=lyric, _hold=True))
return self
def set(self, **params) -> "Part":
"""Change effect parameters at the current beat position.