From c324267c0a839824186533ab46507caf7834f449 Mon Sep 17 00:00:00 2001 From: Endre Oma Date: Sun, 19 Feb 2017 19:32:42 +0100 Subject: [PATCH 01/15] Support _ (underscore) as a skip in lyricmode --- ly/musicxml/ly2xml_mediator.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ly/musicxml/ly2xml_mediator.py b/ly/musicxml/ly2xml_mediator.py index a9ec6951..94b2071a 100644 --- a/ly/musicxml/ly2xml_mediator.py +++ b/ly/musicxml/ly2xml_mediator.py @@ -826,7 +826,7 @@ def new_lyrics_item(self, item): self.lyric_syll = True elif item == '__': self.lyric.append("extend") - elif item == '\\skip': + elif item == '\\skip' or item == '_': self.insert_into.barlist.append("skip") def duration_from_tokens(self, tokens): From 97bcab37deb493be4e6d760a27092dd545e9697c Mon Sep 17 00:00:00 2001 From: Endre Oma Date: Sun, 19 Feb 2017 19:48:02 +0100 Subject: [PATCH 02/15] BarMus: Introduce voice_context identificator This is useful when assigning lyrics to notes after the (possibly) named voice has been merged with other voices --- ly/musicxml/ly2xml_mediator.py | 6 ++++++ ly/musicxml/xml_objs.py | 5 +++++ 2 files changed, 11 insertions(+) diff --git a/ly/musicxml/ly2xml_mediator.py b/ly/musicxml/ly2xml_mediator.py index 94b2071a..a2107cbe 100644 --- a/ly/musicxml/ly2xml_mediator.py +++ b/ly/musicxml/ly2xml_mediator.py @@ -320,6 +320,12 @@ def new_bar(self, fill_prev=True): def add_to_bar(self, obj): if self.bar is None: self.new_bar() + + if isinstance(obj, xml_objs.BarMus): + # assign the voice context the obj belongs to, useful when we must modify the note + # after section merging, eg. set lyrics to a named voice + obj.voice_context = self.insert_into.name + self.bar.add(obj) def create_barline(self, bl): diff --git a/ly/musicxml/xml_objs.py b/ly/musicxml/xml_objs.py index bad1e057..734d4735 100644 --- a/ly/musicxml/xml_objs.py +++ b/ly/musicxml/xml_objs.py @@ -511,6 +511,11 @@ def __init__(self, duration, voice=1): self.dynamic = [] self.oct_shift = None + # set to some object identifying the context this belongs to, eg. string. + # It is useful to set this before merging a voice into another section + # This helps when adding lyrics to named voices outside a score context + self.voice_context = None + def __repr__(self): return '<{0} {1}>'.format(self.__class__.__name__, self.duration) From f3243860ab73accb1fb0e21e4169d030f5ba6d0e Mon Sep 17 00:00:00 2001 From: Endre Oma Date: Sun, 19 Feb 2017 21:05:35 +0100 Subject: [PATCH 03/15] ParseSource.Duration: skip the duration if in lyric mode --- ly/musicxml/lymus2musxml.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/ly/musicxml/lymus2musxml.py b/ly/musicxml/lymus2musxml.py index 13dfb733..ffa1efa8 100644 --- a/ly/musicxml/lymus2musxml.py +++ b/ly/musicxml/lymus2musxml.py @@ -331,6 +331,11 @@ def check_tuplet(self): def Duration(self, duration): """A written duration""" + + if 'lyrics' in self.sims_and_seqs: + # Skip the duration of the skip when typing lyrics + return + if self.tempo: self.mediator.new_tempo(duration.token, duration.tokens, *self.tempo) self.tempo = () From 3acdc03dd8c85d840bf93e7595c7d5cdd8570ee1 Mon Sep 17 00:00:00 2001 From: Endre Oma Date: Sun, 19 Feb 2017 22:00:09 +0100 Subject: [PATCH 04/15] ScoreSection.merge_lyrics: add parameter voice_context This makes it possible to only merge lyrics with objects having the same voice_context --- ly/musicxml/xml_objs.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/ly/musicxml/xml_objs.py b/ly/musicxml/xml_objs.py index 734d4735..73dc4e12 100644 --- a/ly/musicxml/xml_objs.py +++ b/ly/musicxml/xml_objs.py @@ -312,13 +312,18 @@ def merge_voice(self, voice, override=False): if len(voice.barlist) > bl_len: self.barlist += voice.barlist[bl_len:] - def merge_lyrics(self, lyrics): - """Merge in lyrics in music section.""" + def merge_lyrics(self, lyrics, voice_context=None): + """ + Merge in lyrics in music section. + If voice_context is set, it will only merge with notes that has the same voice_context + """ i = 0 ext = False for bar in self.barlist: for obj in bar.obj_list: - if isinstance(obj, BarNote): + if isinstance(obj, BarNote) and \ + not obj.chord and \ + (not voice_context or obj.voice_context == voice_context): if ext: if obj.slur: ext = False From 3c360ed17e7acfbeb08a63c0911884846224a166 Mon Sep 17 00:00:00 2001 From: Endre Oma Date: Sun, 19 Feb 2017 22:20:30 +0100 Subject: [PATCH 05/15] ParseSource.check_context: add Lyrics --- ly/musicxml/lymus2musxml.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ly/musicxml/lymus2musxml.py b/ly/musicxml/lymus2musxml.py index ffa1efa8..776ac68f 100644 --- a/ly/musicxml/lymus2musxml.py +++ b/ly/musicxml/lymus2musxml.py @@ -239,6 +239,8 @@ def check_context(self, context, context_id=None, token=""): self.mediator.new_section('voice') elif context == 'Devnull': self.mediator.new_section('devnull', True) + elif context == 'Lyrics': + pass else: print("Context not implemented:", context) From ab7700cb5728882d78b3bbc0561221c140b4d62c Mon Sep 17 00:00:00 2001 From: Endre Oma Date: Sun, 19 Feb 2017 22:38:43 +0100 Subject: [PATCH 06/15] If merging lyrics with named voices, search the whole score. --- ly/musicxml/ly2xml_mediator.py | 10 ++++++++-- ly/musicxml/xml_objs.py | 19 +++++++++++++++++++ 2 files changed, 27 insertions(+), 2 deletions(-) diff --git a/ly/musicxml/ly2xml_mediator.py b/ly/musicxml/ly2xml_mediator.py index a2107cbe..04eb1976 100644 --- a/ly/musicxml/ly2xml_mediator.py +++ b/ly/musicxml/ly2xml_mediator.py @@ -263,9 +263,15 @@ def check_lyrics(self, voice_id): lyrics_section = self.lyric_sections['lyricsto'+voice_id] voice_section = self.get_var_byname(lyrics_section.voice_id) if voice_section: - voice_section.merge_lyrics(lyrics_section) + voice_section.merge_lyrics(lyrics_section, voice_id) else: - print("Warning can't merge in lyrics!", voice_section) + # A potentially slow path + voice_section = self.score.find_section_for_voice(voice_id) + if voice_section: + # Must explicitly only merge with notes with the same voice_id + voice_section.merge_lyrics(lyrics_section, voice_id) + else: + print("Warning can't merge in lyrics!", voice_section) def check_part(self): """Adds the latest active section to the part.""" diff --git a/ly/musicxml/xml_objs.py b/ly/musicxml/xml_objs.py index 73dc4e12..f7d87c92 100644 --- a/ly/musicxml/xml_objs.py +++ b/ly/musicxml/xml_objs.py @@ -274,6 +274,25 @@ def debug_group(g): for i in self.partlist: debug_group(i) + def find_section_for_voice(self, voice_context, parent = None): + partlist = self.partlist + + if parent: + partlist = parent.partlist + + for section in partlist[::-1]: + # Iterate over sections in partlist, in reverser order (newest to oldest) + if isinstance(section, ScorePartGroup): + section_candidate = self.find_section_for_voice(voice_context, section) + if section_candidate: + return section_candidate + elif isinstance(section, ScoreSection): + for bar in section.barlist: + for obj in bar.obj_list: + if isinstance(obj, BarMus) and obj.voice_context == voice_context: + return section + return None + class ScorePartGroup(): """Object to keep track of part group.""" From ecd64f1b9624bf6ceeeeaa542abe7d3c288589d4 Mon Sep 17 00:00:00 2001 From: Endre Oma Date: Sun, 19 Feb 2017 22:47:35 +0100 Subject: [PATCH 07/15] ParseSource.Set(): allow setting stanza if in lyricmode --- ly/musicxml/lymus2musxml.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/ly/musicxml/lymus2musxml.py b/ly/musicxml/lymus2musxml.py index 776ac68f..270f6338 100644 --- a/ly/musicxml/lymus2musxml.py +++ b/ly/musicxml/lymus2musxml.py @@ -477,6 +477,9 @@ def Set(self, cont_set): self.mediator.set_by_property(cont_set.property(), val) elif cont_set.context() in group_contexts: self.mediator.set_by_property(cont_set.property(), val, group=True) + elif cont_set.property() == 'stanza' and self.alt_mode == 'lyric': + self.mediator.set_by_property(cont_set.property(), val) + def Command(self, command): r""" \bar, \rest etc """ From e28147700994509957fc72095094f2a83c2e7ebf Mon Sep 17 00:00:00 2001 From: Endre Oma Date: Sun, 19 Feb 2017 22:56:35 +0100 Subject: [PATCH 08/15] CreateMusicXML.add_lyric(): if lyric nr is string assign it to name --- ly/musicxml/create_musicxml.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/ly/musicxml/create_musicxml.py b/ly/musicxml/create_musicxml.py index 8fb82b7b..85cb7299 100644 --- a/ly/musicxml/create_musicxml.py +++ b/ly/musicxml/create_musicxml.py @@ -619,7 +619,13 @@ def add_sound_dir(self, midi_tempo): def add_lyric(self, txt, syll, nr, ext=False): """ Add lyric element. """ - lyricnode = etree.SubElement(self.current_note, "lyric", number=str(nr)) + lyricnode = etree.SubElement(self.current_note, "lyric") + + if isinstance(nr, int) or nr.isnumeric(): + lyricnode.attrib['number'] = str(nr) + else: + lyricnode.attrib['name'] = str(nr) + syllnode = etree.SubElement(lyricnode, "syllabic") syllnode.text = syll txtnode = etree.SubElement(lyricnode, "text") From da6a2b07f9425c3e086ef9dc7ed69f42229f1138 Mon Sep 17 00:00:00 2001 From: Endre Oma Date: Sun, 19 Feb 2017 23:55:53 +0100 Subject: [PATCH 09/15] Add test for \lyricsto --- tests/test_xml.py | 2 + .../test_xml_files/lyrics_simple_lyricsto.ly | 42 ++++ .../test_xml_files/lyrics_simple_lyricsto.xml | 231 ++++++++++++++++++ 3 files changed, 275 insertions(+) create mode 100644 tests/test_xml_files/lyrics_simple_lyricsto.ly create mode 100644 tests/test_xml_files/lyrics_simple_lyricsto.xml diff --git a/tests/test_xml.py b/tests/test_xml.py index e25f6ae5..022046de 100644 --- a/tests/test_xml.py +++ b/tests/test_xml.py @@ -30,6 +30,8 @@ def test_dynamics(): def test_tuplet(): compare_output('tuplet') +def test_lyrics_simple_lyricsto(): + compare_output('lyrics_simple_lyricsto') def ly_to_xml(filename): """Read Lilypond file and return XML string.""" diff --git a/tests/test_xml_files/lyrics_simple_lyricsto.ly b/tests/test_xml_files/lyrics_simple_lyricsto.ly new file mode 100644 index 00000000..4cbd6cc6 --- /dev/null +++ b/tests/test_xml_files/lyrics_simple_lyricsto.ly @@ -0,0 +1,42 @@ +\version "2.19.55" + +\header { + title = "lyrics simple lyricsto" +} + +verse = \lyricmode { + \set stanza = "1" + Do -- re -- mi fa + Hel -- lo __ the -- re +} + +chorus = \lyricmode { + \set stanza = "chor" + This is the cho -- rus, la __ di -- da +} + +\score { + \new Staff { + << + \new Voice = "main" { + \voiceOne + \clef treble + \relative c' { + c4 d e f | + g16 a4( f8.) b4 c4 | + } + } + \new Voice = "mainTwo" { + \voiceTwo + \clef treble + \relative c' { + c2 c | g' g | + } + } + \new Lyrics \lyricsto "main" \verse + \new Lyrics \lyricsto "main" \chorus + >> + } + \layout { } +} +\voiceTwo \ No newline at end of file diff --git a/tests/test_xml_files/lyrics_simple_lyricsto.xml b/tests/test_xml_files/lyrics_simple_lyricsto.xml new file mode 100644 index 00000000..ca61b217 --- /dev/null +++ b/tests/test_xml_files/lyrics_simple_lyricsto.xml @@ -0,0 +1,231 @@ + + + + lyrics simple lyricsto + + + python-ly 0.9.5 + 2016-03-28 + + + + + + + + + + + 4 + + + G + 2 + + + + + C + 4 + + 4 + 1 + quarter + + begin + Do + + + single + This + + + + + D + 4 + + 4 + 1 + quarter + + middle + re + + + single + is + + + + + E + 4 + + 4 + 1 + quarter + + end + mi + + + single + the + + + + + F + 4 + + 4 + 1 + quarter + + single + fa + + + begin + cho + + + + 16 + + + + C + 4 + + 8 + 2 + half + + + + C + 4 + + 8 + 2 + half + + + + + + G + 4 + + 1 + 1 + 16th + + begin + Hel + + + end + rus, + + + + + A + 4 + + 4 + 1 + quarter + + + + + end + lo + + + + single + la + + + + + + F + 4 + + 3 + 1 + eighth + + + + + + + + B + 4 + + 4 + 1 + quarter + + begin + the + + + begin + di + + + + + C + 5 + + 4 + 1 + quarter + + end + re + + + end + da + + + + 16 + + + + G + 4 + + 8 + 2 + half + + + + G + 4 + + 8 + 2 + half + + + + + From e26bd6ea38c744d9d312f39d438dc00fb85ef99f Mon Sep 17 00:00:00 2001 From: Endre Oma Date: Mon, 20 Feb 2017 13:59:19 +0100 Subject: [PATCH 10/15] Mediator: add member current_music_section --- ly/musicxml/ly2xml_mediator.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/ly/musicxml/ly2xml_mediator.py b/ly/musicxml/ly2xml_mediator.py index 04eb1976..a7ea6dcb 100644 --- a/ly/musicxml/ly2xml_mediator.py +++ b/ly/musicxml/ly2xml_mediator.py @@ -44,6 +44,10 @@ def __init__(self): self.sections = [] """ default and initial values """ self.insert_into = None + + # Current music section are useful when using "\addlyrics" + self.current_music_section = None + self.current_note = None self.current_lynote = None self.current_is_rest = False @@ -92,6 +96,8 @@ def new_header_assignment(self, name, value): def new_section(self, name, glob=False): name = self.check_name(name) section = xml_objs.ScoreSection(name, glob) + + self.current_music_section = section self.insert_into = section self.sections.append(section) self.bar = None @@ -150,6 +156,8 @@ def new_part(self, pid=None, to_part=None, piano=False): self.group.partlist.append(self.part) else: self.score.partlist.append(self.part) + + self.current_music_section = self.part self.insert_into = self.part self.bar = None From 8784a96ae9c2cb0820a81b29df7b7ff8373cddae Mon Sep 17 00:00:00 2001 From: Endre Oma Date: Mon, 20 Feb 2017 14:02:01 +0100 Subject: [PATCH 11/15] ParseSource.LyricMode(): add suport for \addlyrics --- ly/musicxml/lymus2musxml.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/ly/musicxml/lymus2musxml.py b/ly/musicxml/lymus2musxml.py index 270f6338..03fd9927 100644 --- a/ly/musicxml/lymus2musxml.py +++ b/ly/musicxml/lymus2musxml.py @@ -560,6 +560,11 @@ def LyricMode(self, lyricmode): r"""A \lyricmode, \lyrics or \addlyrics expression.""" self.alt_mode = 'lyric' + if lyricmode.token == '\\addlyrics': + section = self.mediator.current_music_section + self.mediator.new_lyric_section('lyricsto' + section.name, section.name) + self.sims_and_seqs.append('lyrics') + def Override(self, override): r"""An \override command.""" self.override_key = '' @@ -649,6 +654,9 @@ def End(self, end): elif isinstance(end.node, ly.music.items.Relative): self.relative = False self.rel_pitch_isset = False + elif isinstance(end.node, ly.music.items.LyricMode) and end.node.token == '\\addlyrics': + self.mediator.check_lyrics(self.mediator.insert_into.voice_id) + self.sims_and_seqs.pop() else: # print("end:", end.node.token) pass From 4e98f92a5e6c97f507eea3516ff613e52eceec2b Mon Sep 17 00:00:00 2001 From: Endre Oma Date: Mon, 20 Feb 2017 14:09:32 +0100 Subject: [PATCH 12/15] Add test for \addlyrics --- tests/test_xml.py | 3 + .../test_xml_files/lyrics_simple_addlyrics.ly | 27 +++ .../lyrics_simple_addlyrics.xml | 154 ++++++++++++++++++ 3 files changed, 184 insertions(+) create mode 100644 tests/test_xml_files/lyrics_simple_addlyrics.ly create mode 100644 tests/test_xml_files/lyrics_simple_addlyrics.xml diff --git a/tests/test_xml.py b/tests/test_xml.py index 022046de..189a9828 100644 --- a/tests/test_xml.py +++ b/tests/test_xml.py @@ -33,6 +33,9 @@ def test_tuplet(): def test_lyrics_simple_lyricsto(): compare_output('lyrics_simple_lyricsto') +def test_lyrics_simple_addlyrics(): + compare_output('lyrics_simple_addlyrics') + def ly_to_xml(filename): """Read Lilypond file and return XML string.""" writer = ly.musicxml.writer() diff --git a/tests/test_xml_files/lyrics_simple_addlyrics.ly b/tests/test_xml_files/lyrics_simple_addlyrics.ly new file mode 100644 index 00000000..d9880f30 --- /dev/null +++ b/tests/test_xml_files/lyrics_simple_addlyrics.ly @@ -0,0 +1,27 @@ +\version "2.19.55" + +\header { + title = "lyrics simple addlyrics" +} + +verseOne = \lyricmode { + \set stanza = "v1" + My long sing song for voice one +} +verseTwo = \lyricmode { + \set stanza = "v2" + This is verse two, the last verse +} + +\score { + \new Staff { + \relative c' { + \clef treble + c'32 c c16 c8 c4 c2 | + c1 | + } + } + \addlyrics \verseOne + \addlyrics \verseTwo + \layout { } +} \ No newline at end of file diff --git a/tests/test_xml_files/lyrics_simple_addlyrics.xml b/tests/test_xml_files/lyrics_simple_addlyrics.xml new file mode 100644 index 00000000..30414e16 --- /dev/null +++ b/tests/test_xml_files/lyrics_simple_addlyrics.xml @@ -0,0 +1,154 @@ + + + + lyrics simple addlyrics + + + python-ly 0.9.5 + 2016-03-28 + + + + + + + + + + + 8 + + + G + 2 + + + + + C + 5 + + 1 + 1 + 32nd + + single + My + + + single + This + + + + + C + 5 + + 1 + 1 + 32nd + + single + long + + + single + is + + + + + C + 5 + + 2 + 1 + 16th + + single + sing + + + single + verse + + + + + C + 5 + + 4 + 1 + eighth + + single + song + + + single + two, + + + + + C + 5 + + 8 + 1 + quarter + + single + for + + + single + the + + + + + C + 5 + + 16 + 1 + half + + single + voice + + + single + last + + + + + + + C + 5 + + 32 + 1 + whole + + single + one + + + single + verse + + + + + + From 9f36669d567ecd1cfb398ae4859fcccce1e9c4c9 Mon Sep 17 00:00:00 2001 From: Endre Oma Date: Mon, 20 Feb 2017 18:57:30 +0100 Subject: [PATCH 13/15] Rewrite ScoreSection.merge_lyrics() wrt to slurs --- ly/musicxml/xml_objs.py | 33 +++++++++++++++++++++++---------- 1 file changed, 23 insertions(+), 10 deletions(-) diff --git a/ly/musicxml/xml_objs.py b/ly/musicxml/xml_objs.py index f7d87c92..f7bc555c 100644 --- a/ly/musicxml/xml_objs.py +++ b/ly/musicxml/xml_objs.py @@ -337,29 +337,42 @@ def merge_lyrics(self, lyrics, voice_context=None): If voice_context is set, it will only merge with notes that has the same voice_context """ i = 0 - ext = False + + # If we are at the end or inside the slur, but not at the start + inside_slur = False + for bar in self.barlist: for obj in bar.obj_list: if isinstance(obj, BarNote) and \ not obj.chord and \ (not voice_context or obj.voice_context == voice_context): - if ext: - if obj.slur: - ext = False - else: + + slur_started = False + slur_stopped = False + + for slur in obj.slur: + #if slur.phrasing: + # # ignore slur object if it is a phrasing mark + # continue + if slur.slurtype == 'start': + slur_started = True + elif slur.slurtype == 'stop': + slur_stopped = True + + if not inside_slur: try: l = lyrics.barlist[i] except IndexError: break if l != 'skip': - try: - if l[3] == "extend" and obj.slur: - ext = True - except IndexError: - pass obj.add_lyric(l) i += 1 + if slur_started: + inside_slur = True + elif slur_stopped: + inside_slur = False + class Snippet(ScoreSection): """ Short section intended to be merged. From 4f5d1bdfd1e5a72130d1973a4ab2e608b1e63b46 Mon Sep 17 00:00:00 2001 From: Endre Oma Date: Mon, 20 Feb 2017 23:32:23 +0100 Subject: [PATCH 14/15] Search thrugh all sections for possible voice contexts --- ly/musicxml/ly2xml_mediator.py | 6 ++++-- ly/musicxml/xml_objs.py | 10 ++++------ 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/ly/musicxml/ly2xml_mediator.py b/ly/musicxml/ly2xml_mediator.py index a7ea6dcb..d1dbde0c 100644 --- a/ly/musicxml/ly2xml_mediator.py +++ b/ly/musicxml/ly2xml_mediator.py @@ -273,8 +273,10 @@ def check_lyrics(self, voice_id): if voice_section: voice_section.merge_lyrics(lyrics_section, voice_id) else: - # A potentially slow path - voice_section = self.score.find_section_for_voice(voice_id) + voice_section = self.score.find_section_for_voice(voice_id, self.sections) + if not voice_section: + # A potentially slow path, search the whole score + voice_section = self.score.find_section_for_voice(voice_id) if voice_section: # Must explicitly only merge with notes with the same voice_id voice_section.merge_lyrics(lyrics_section, voice_id) diff --git a/ly/musicxml/xml_objs.py b/ly/musicxml/xml_objs.py index f7bc555c..c15cfc4c 100644 --- a/ly/musicxml/xml_objs.py +++ b/ly/musicxml/xml_objs.py @@ -274,16 +274,14 @@ def debug_group(g): for i in self.partlist: debug_group(i) - def find_section_for_voice(self, voice_context, parent = None): - partlist = self.partlist - - if parent: - partlist = parent.partlist + def find_section_for_voice(self, voice_context, partlist = None): + if not partlist: + partlist = self.partlist for section in partlist[::-1]: # Iterate over sections in partlist, in reverser order (newest to oldest) if isinstance(section, ScorePartGroup): - section_candidate = self.find_section_for_voice(voice_context, section) + section_candidate = self.find_section_for_voice(voice_context, section.partlist) if section_candidate: return section_candidate elif isinstance(section, ScoreSection): From 0f30b25e78c223bb86f1408dcdb85fb21fbc5cd4 Mon Sep 17 00:00:00 2001 From: Endre Oma Date: Mon, 20 Feb 2017 23:33:30 +0100 Subject: [PATCH 15/15] Make ScoreSection.merge_lyrics() also care about ties --- ly/musicxml/xml_objs.py | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/ly/musicxml/xml_objs.py b/ly/musicxml/xml_objs.py index c15cfc4c..897ad350 100644 --- a/ly/musicxml/xml_objs.py +++ b/ly/musicxml/xml_objs.py @@ -338,6 +338,7 @@ def merge_lyrics(self, lyrics, voice_context=None): # If we are at the end or inside the slur, but not at the start inside_slur = False + inside_tie = False for bar in self.barlist: for obj in bar.obj_list: @@ -345,6 +346,15 @@ def merge_lyrics(self, lyrics, voice_context=None): not obj.chord and \ (not voice_context or obj.voice_context == voice_context): + tie_started = False + tie_stopped = False + + # Ties can both start and stop at the same note, prefer starts + if 'start' in obj.tie: + tie_started = True + elif 'stop' in obj.tie: + tie_stopped = True + slur_started = False slur_stopped = False @@ -357,7 +367,7 @@ def merge_lyrics(self, lyrics, voice_context=None): elif slur.slurtype == 'stop': slur_stopped = True - if not inside_slur: + if not inside_tie and not inside_slur: try: l = lyrics.barlist[i] except IndexError: @@ -371,6 +381,10 @@ def merge_lyrics(self, lyrics, voice_context=None): elif slur_stopped: inside_slur = False + if tie_started: + inside_tie = True + elif tie_stopped: + inside_tie = False class Snippet(ScoreSection): """ Short section intended to be merged.