From ca051373e952c6a8907022c3fc9edd340e342adc Mon Sep 17 00:00:00 2001 From: Felippe Roza <4269853+FelippeRoza@users.noreply.github.com> Date: Tue, 18 Aug 2026 15:45:35 +0200 Subject: [PATCH] Fix crash on \rest when the note completes the bar note2rest() popped the note off self.bar, but \rest is parsed after the note it applies to, so when that note completed the bar self.bar had already moved to the next one. The note stayed a sounding note, the following bar lost its BarAttr to the pop and gained a stray rest, and merging that bar crashed with IndexError in Bar.inject_voice(). add_to_bar() now records which bar it put an object in, and note2rest() converts the note there. Fixes #147 --- ly/musicxml/ly2xml_mediator.py | 7 ++- tests/test_xml.py | 4 ++ tests/test_xml_files/rest_command.ly | 12 ++++ tests/test_xml_files/rest_command.xml | 79 +++++++++++++++++++++++++++ 4 files changed, 100 insertions(+), 2 deletions(-) create mode 100644 tests/test_xml_files/rest_command.ly create mode 100644 tests/test_xml_files/rest_command.xml diff --git a/ly/musicxml/ly2xml_mediator.py b/ly/musicxml/ly2xml_mediator.py index e8c7d72..6913d37 100644 --- a/ly/musicxml/ly2xml_mediator.py +++ b/ly/musicxml/ly2xml_mediator.py @@ -83,6 +83,7 @@ def __init__(self): self.multiple_rest_bar = None self.current_mark = 1 self.bar_is_pickup = False + self.obj_bar = None self.stem_dir = None def new_header_assignment(self, name, value): @@ -335,6 +336,7 @@ def new_bar(self, fill_prev=True): def add_to_bar(self, obj): if self.bar is None: self.new_bar() + self.obj_bar = self.bar self.bar.add(obj) def create_barline(self, bl): @@ -636,8 +638,9 @@ def note2rest(self): pos = [self.current_note.base_note, self.current_note.octave] self.current_note = xml_objs.BarRest(dur, voice, pos=pos) self.check_duration(rest=True) - self.bar.obj_list.pop() - self.bar.add(self.current_note) + # The note may have completed its bar, leaving self.bar on the next one. + self.obj_bar.obj_list.pop() + self.obj_bar.add(self.current_note) def set_mult_rest(self): self.multiple_rest = True diff --git a/tests/test_xml.py b/tests/test_xml.py index e5ca26b..3352056 100644 --- a/tests/test_xml.py +++ b/tests/test_xml.py @@ -80,6 +80,10 @@ def test_chord_duration(): compare_output('chord_duration') +def test_rest_command(): + compare_output('rest_command') + + def ly_to_xml(filename): """Read Lilypond file and return XML string.""" writer = ly.musicxml.writer() diff --git a/tests/test_xml_files/rest_command.ly b/tests/test_xml_files/rest_command.ly new file mode 100644 index 0000000..e4d9f62 --- /dev/null +++ b/tests/test_xml_files/rest_command.ly @@ -0,0 +1,12 @@ +\version "2.22.2" + +% \rest converts the note it follows where that note stands, even when the note +% completed the bar (issue #147) + +\score { + \relative b' { + \time 2/4 + b4. b8 \rest | + b8 \rest b8 b4 + } +} diff --git a/tests/test_xml_files/rest_command.xml b/tests/test_xml_files/rest_command.xml new file mode 100644 index 0000000..69ad659 --- /dev/null +++ b/tests/test_xml_files/rest_command.xml @@ -0,0 +1,79 @@ + + + + + + python-ly 0.9.10 + 2026-08-18 + + + + + + + + + + + 2 + + + G + 2 + + + + + B + 4 + + 3 + 1 + quarter + + + + + B + 4 + + 1 + 1 + eighth + + + + + + B + 4 + + 1 + 1 + eighth + + + + B + 4 + + 1 + 1 + eighth + + + + B + 4 + + 2 + 1 + quarter + + + +