Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions ly/musicxml/ly2xml_mediator.py
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,14 @@ def new_lyric_section(self, name, voice_id):
self.insert_into = lyrics
self.lyric_sections[name] = lyrics

def new_addlyrics_section(self):
r"""Collect \addlyrics into a lyrics section that is merged
into the preceding music when the lyrics end."""
lyrics = xml_objs.LyricsSection('addlyrics', None)
lyrics.merge_into = self.insert_into
self.insert_into = lyrics
self.lyric = None

def check_name(self, name, nr=1):
n = self.get_var_byname(name)
if n:
Expand Down Expand Up @@ -275,6 +283,14 @@ def check_lyrics(self, voice_id):
else:
print("Warning can't merge in lyrics!", voice_section)

def check_addlyrics(self):
r"""Merge the finished \addlyrics section into the music it follows."""
lyrics_section = self.insert_into
if self.lyric and self.lyric[1] == 'middle':
self.lyric[1] = 'end'
self.insert_into = lyrics_section.merge_into
self.insert_into.merge_lyrics(lyrics_section)

def check_part(self):
"""Adds the latest active section to the part."""
if len(self.sections)>1:
Expand Down
13 changes: 13 additions & 0 deletions ly/musicxml/lymus2musxml.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ def __init__(self):
self.numericTime = False
self.voice_sep = False
self.sims_and_seqs = []
self.implicit_staff = []
self.override_dict = {}
self.ottava = False
self.with_contxt = None
Expand Down Expand Up @@ -235,6 +236,10 @@ def check_context(self, context, context_id=None, token=""):
self.mediator.add_staff_id(context_id)
elif context == 'Voice':
self.sims_and_seqs.append('voice')
# A Voice outside any Staff gets an implicit Staff (and part)
self.implicit_staff.append(self.mediator.part is None)
if self.implicit_staff[-1]:
self.mediator.new_part()
if context_id:
self.mediator.new_section(context_id)
else:
Expand Down Expand Up @@ -580,6 +585,9 @@ def FigureMode(self, figmode):
def LyricMode(self, lyricmode):
r"""A \lyricmode, \lyrics or \addlyrics expression."""
self.alt_mode = 'lyric'
if lyricmode.token == '\\addlyrics':
self.mediator.new_addlyrics_section()
self.sims_and_seqs.append('lyrics')

def Override(self, override):
r"""An \override command."""
Expand Down Expand Up @@ -636,6 +644,8 @@ def End(self, end):
if end.node.context() == 'Voice':
self.mediator.check_voices()
self.sims_and_seqs.pop()
if self.implicit_staff.pop():
self.mediator.check_part()
elif end.node.context() in group_contexts:
self.mediator.close_group()
elif end.node.context() in staff_contexts:
Expand Down Expand Up @@ -665,6 +675,9 @@ def End(self, end):
elif end.node.token == '\\lyricsto':
self.mediator.check_lyrics(end.node.context_id())
self.sims_and_seqs.pop()
elif end.node.token == '\\addlyrics':
self.mediator.check_addlyrics()
self.sims_and_seqs.pop()
elif end.node.token == '\\with':
self.with_contxt = None
elif end.node.token == '\\drums':
Expand Down
8 changes: 8 additions & 0 deletions tests/test_xml.py
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,14 @@ def test_chord_duration():
compare_output('chord_duration')


def test_addlyrics():
compare_output('addlyrics')


def test_voice_in_group():
compare_output('voice_in_group')


def ly_to_xml(filename):
"""Read Lilypond file and return XML string."""
writer = ly.musicxml.writer()
Expand Down
8 changes: 8 additions & 0 deletions tests/test_xml_files/addlyrics.ly
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
\version "2.18.2"

\score {
\new Staff {
\relative c' { d2 c | bes4 bes c8 bes a4 }
}
\addlyrics { Du tout plon -- giet au de -- ses }
}
130 changes: 130 additions & 0 deletions tests/test_xml_files/addlyrics.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE score-partwise PUBLIC "-//Recordare//DTD MusicXML 2.0 Partwise//EN"
"http://www.musicxml.org/dtds/partwise.dtd">
<score-partwise version="3.0">
<identification>
<encoding>
<software>python-ly 0.9.10</software>
<encoding-date>2026-09-26</encoding-date>
</encoding>
</identification>
<part-list>
<score-part id="P1">
<part-name />
</score-part>
</part-list>
<part id="P1">
<measure number="1">
<attributes>
<divisions>2</divisions>
<time symbol="common">
<beats>4</beats>
<beat-type>4</beat-type>
</time>
<clef>
<sign>G</sign>
<line>2</line>
</clef>
</attributes>
<note>
<pitch>
<step>D</step>
<octave>4</octave>
</pitch>
<duration>4</duration>
<voice>1</voice>
<type>half</type>
<lyric number="1">
<syllabic>single</syllabic>
<text>Du</text>
</lyric>
</note>
<note>
<pitch>
<step>C</step>
<octave>4</octave>
</pitch>
<duration>4</duration>
<voice>1</voice>
<type>half</type>
<lyric number="1">
<syllabic>single</syllabic>
<text>tout</text>
</lyric>
</note>
</measure>
<measure number="2">
<note>
<pitch>
<step>B</step>
<alter>-1</alter>
<octave>3</octave>
</pitch>
<duration>2</duration>
<voice>1</voice>
<type>quarter</type>
<accidental>flat</accidental>
<lyric number="1">
<syllabic>begin</syllabic>
<text>plon</text>
</lyric>
</note>
<note>
<pitch>
<step>B</step>
<alter>-1</alter>
<octave>3</octave>
</pitch>
<duration>2</duration>
<voice>1</voice>
<type>quarter</type>
<accidental>flat</accidental>
<lyric number="1">
<syllabic>end</syllabic>
<text>giet</text>
</lyric>
</note>
<note>
<pitch>
<step>C</step>
<octave>4</octave>
</pitch>
<duration>1</duration>
<voice>1</voice>
<type>eighth</type>
<lyric number="1">
<syllabic>single</syllabic>
<text>au</text>
</lyric>
</note>
<note>
<pitch>
<step>B</step>
<alter>-1</alter>
<octave>3</octave>
</pitch>
<duration>1</duration>
<voice>1</voice>
<type>eighth</type>
<accidental>flat</accidental>
<lyric number="1">
<syllabic>begin</syllabic>
<text>de</text>
</lyric>
</note>
<note>
<pitch>
<step>A</step>
<octave>3</octave>
</pitch>
<duration>2</duration>
<voice>1</voice>
<type>quarter</type>
<lyric number="1">
<syllabic>end</syllabic>
<text>ses</text>
</lyric>
</note>
</measure>
</part>
</score-partwise>
10 changes: 10 additions & 0 deletions tests/test_xml_files/voice_in_group.ly
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
\version "2.18.2"

\score {
\new StaffGroup <<
\new Voice = "S" { \set Staff.instrumentName = #"Cantus" c''2 d'' }
\addlyrics { Du tout }
\new Voice = "B" { \set Staff.instrumentName = #"Bassus" c2 g, }
\addlyrics { plon giet }
>>
}
105 changes: 105 additions & 0 deletions tests/test_xml_files/voice_in_group.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE score-partwise PUBLIC "-//Recordare//DTD MusicXML 2.0 Partwise//EN"
"http://www.musicxml.org/dtds/partwise.dtd">
<score-partwise version="3.0">
<identification>
<encoding>
<software>python-ly 0.9.10</software>
<encoding-date>2026-09-26</encoding-date>
</encoding>
</identification>
<part-list>
<part-group type="start" number="1">
<group-symbol>bracket</group-symbol>
</part-group>
<score-part id="P1">
<part-name>Cantus</part-name>
</score-part>
<score-part id="P2">
<part-name>Bassus</part-name>
</score-part>
<part-group type="stop" number="1" />
</part-list>
<part id="P1">
<measure number="1">
<attributes>
<divisions>1</divisions>
<time symbol="common">
<beats>4</beats>
<beat-type>4</beat-type>
</time>
<clef>
<sign>G</sign>
<line>2</line>
</clef>
</attributes>
<note>
<pitch>
<step>C</step>
<octave>5</octave>
</pitch>
<duration>2</duration>
<voice>1</voice>
<type>half</type>
<lyric number="1">
<syllabic>single</syllabic>
<text>Du</text>
</lyric>
</note>
<note>
<pitch>
<step>D</step>
<octave>5</octave>
</pitch>
<duration>2</duration>
<voice>1</voice>
<type>half</type>
<lyric number="1">
<syllabic>single</syllabic>
<text>tout</text>
</lyric>
</note>
</measure>
</part>
<part id="P2">
<measure number="1">
<attributes>
<divisions>1</divisions>
<time symbol="common">
<beats>4</beats>
<beat-type>4</beat-type>
</time>
<clef>
<sign>G</sign>
<line>2</line>
</clef>
</attributes>
<note>
<pitch>
<step>C</step>
<octave>3</octave>
</pitch>
<duration>2</duration>
<voice>1</voice>
<type>half</type>
<lyric number="1">
<syllabic>single</syllabic>
<text>plon</text>
</lyric>
</note>
<note>
<pitch>
<step>G</step>
<octave>2</octave>
</pitch>
<duration>2</duration>
<voice>1</voice>
<type>half</type>
<lyric number="1">
<syllabic>single</syllabic>
<text>giet</text>
</lyric>
</note>
</measure>
</part>
</score-partwise>