Skip to content

Commit 6f32c4e

Browse files
committed
musicxml: keep postfix markup in the measure of its note
The postfix events after a note (-., ^\markup, ...) follow the note in the music tree, so the note was completed before them. When the note completed a measure, its markup was put in the next measure, together with the markup of that one. The postfix events are now part of the note they follow, as for the \rest command. This makes test_markup pass; the case with a \mark is from #193, which addresses the same problem. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CMyxTwm2w6VtbxBdt5dL1T
1 parent 8e67f56 commit 6f32c4e

4 files changed

Lines changed: 31 additions & 5 deletions

File tree

‎ly/musicxml/lymus2musxml.py‎

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -157,14 +157,16 @@ def parse_nodes(self, nodes):
157157

158158
def leave_note(self, node=None):
159159
"""Complete the current note (or rest or chord) when the node,
160-
if given, is not part of it."""
160+
if given, is not part of it. The postfix events after a note
161+
(-., ^\\markup, ...) are part of it, so that e.g. its markup is in
162+
the measure of the note, also when the note completes the measure."""
161163
if self.note_node:
162164
n = node.node if isinstance(node, End) else node
163165
if isinstance(n, ly.music.items.Command) and n.token == '\\rest':
164166
# makes the note before it a rest
165167
return
166168
while n:
167-
if n is self.note_node:
169+
if n is self.note_node or isinstance(n, ly.music.items.Postfix):
168170
return
169171
n = n.parent()
170172
self.mediator.end_note()

‎tests/test_musicxml_measures.py‎

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -170,3 +170,29 @@ def test_voices_with_tuplets():
170170
def test_passing_bar_checks_after_time_change(capsys):
171171
convert(r"{ \time 3/4 c'2. | \time 2/4 c'2 | c'4 c' | }")
172172
assert 'bar check' not in capsys.readouterr().out.lower()
173+
174+
175+
def words(root, part=0):
176+
return [[w.text.strip() for w in m.iter('words')] for m in measures(root, part)]
177+
178+
179+
def test_markup_of_note_completing_measure():
180+
root = convert(r"""\relative {
181+
a'1-\markup intenso |
182+
a1^\markup dolce |
183+
a2_\markup { poco più forte } a2
184+
}""")
185+
assert words(root) == [['intenso'], ['dolce'], ['poco più forte']]
186+
validate(root)
187+
188+
189+
def test_markup_of_note_and_mark_in_next_measure():
190+
# from #193
191+
root = convert(r"""\relative {
192+
a'1^\markup intenso |
193+
\mark \markup Coda
194+
a1 |
195+
a1
196+
}""")
197+
assert words(root) == [['intenso'], ['Coda'], []]
198+
validate(root)

‎tests/test_xml.py‎

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77
import os
88
import os.path
99
import io
10-
import pytest
1110
import re
1211
import sys
1312

@@ -62,7 +61,6 @@ def test_church():
6261
compare_output('church_modes')
6362

6463

65-
@pytest.mark.xfail
6664
def test_markup():
6765
compare_output('markup')
6866

‎tests/test_xml_files/markup.xml‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
<?xml version="1.0" encoding="UTF-8"?>
1+
<?xml version="1.0" encoding="utf-8"?>
22
<!DOCTYPE score-partwise PUBLIC "-//Recordare//DTD MusicXML 4.0 Partwise//EN"
33
"http://www.musicxml.org/dtds/partwise.dtd">
44
<score-partwise version="4.0">

0 commit comments

Comments
 (0)