From b86e30b384aa0a445dd575b7b95c6cefb245954e Mon Sep 17 00:00:00 2001 From: Endre Oma Date: Tue, 14 Feb 2017 22:51:15 +0100 Subject: [PATCH 1/6] Add start_node to Slur, BarNote.set_slur() --- ly/musicxml/xml_objs.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/ly/musicxml/xml_objs.py b/ly/musicxml/xml_objs.py index bad1e057..369d7b6c 100644 --- a/ly/musicxml/xml_objs.py +++ b/ly/musicxml/xml_objs.py @@ -595,11 +595,13 @@ def __init__(self, fraction, ttype, nr, acttype, normtype): self.normtype = normtype class Slur(): - """Stores information about slur.""" - def __init__(self, nr, slurtype): + """Stores information about slur. start_node is only interesting if slurtype is 'stop'. + start_node must be None or a Slur instance.""" + def __init__(self, nr, slurtype, start_node=None): self.nr = nr self.slurtype = slurtype + self.start_node = start_node ## # Subclasses of BarMus @@ -641,8 +643,8 @@ def set_octave(self, octave): def set_tie(self, tie_type): self.tie.append(tie_type) - def set_slur(self, nr, slur_type): - self.slur.append(Slur(nr, slur_type)) + def set_slur(self, nr, slur_type, slur_start_node=None): + self.slur.append(Slur(nr, slur_type, slur_start_node)) def add_articulation(self, art_name): self.artic.append(art_name) From d5bd287203530a9cfdf6aa3b8e7c4be5f692c993 Mon Sep 17 00:00:00 2001 From: Endre Oma Date: Tue, 14 Feb 2017 23:00:40 +0100 Subject: [PATCH 2/6] Make ScoreSection.merge_voice() more robust Handles the merging of slurs from multiple voices and slurs over bars much better. --- ly/musicxml/xml_objs.py | 45 +++++++++++++++++++++++++++++++++++++++-- 1 file changed, 43 insertions(+), 2 deletions(-) diff --git a/ly/musicxml/xml_objs.py b/ly/musicxml/xml_objs.py index 369d7b6c..1e6b6c1e 100644 --- a/ly/musicxml/xml_objs.py +++ b/ly/musicxml/xml_objs.py @@ -294,6 +294,17 @@ def merge_voice(self, voice, override=False): part.merge_voice(voice, override) +class SlurCount: + """Utility class meant for keeping count of started slurs in a section""" + def __init__(self): + self.count = 0 + + def inc(self): + self.count += 1 + + def dec(self): + self.count -= 1 + class ScoreSection(): """ object to keep track of music section """ def __init__(self, name, glob=False): @@ -301,13 +312,16 @@ def __init__(self, name, glob=False): self.barlist = [] self.glob = glob + # Keeps track of the number of started slurs in the section + self.active_slur_count = SlurCount() + def __repr__(self): return '<{0} {1}>'.format(self.__class__.__name__, self.name) def merge_voice(self, voice, override=False): """Merge in other ScoreSection.""" for org_v, add_v in zip(self.barlist, voice.barlist): - org_v.inject_voice(add_v, override) + org_v.inject_voice(add_v, override, self.active_slur_count) bl_len = len(self.barlist) if len(voice.barlist) > bl_len: self.barlist += voice.barlist[bl_len:] @@ -474,7 +488,7 @@ def is_skip(self, obj_list=None): return False return True - def inject_voice(self, new_voice, override=False): + def inject_voice(self, new_voice, override=False, active_slur_count=None): """ Adding new voice to bar. Omitting double or conflicting bar attributes as long as override is false. Omitting also bars with only skips.""" @@ -493,9 +507,36 @@ def inject_voice(self, new_voice, override=False): pass if not self.is_skip(backup_list): self.create_backup() + + if active_slur_count: + # Update active_slur_count wrt to already existing slur starts + # and slur ends in the bar, before we add backup_list + + for n in self.obj_list: + if isinstance(n, BarNote): + for slur in n.slur: + if slur.slurtype == 'start': + active_slur_count.inc() + elif slur.slurtype == 'stop': + active_slur_count.dec() + for bl in backup_list: self.add(bl) + if active_slur_count and isinstance(bl, BarNote): + # If the slur is starting: increase active_slur_count and set slur number + # to that value. + # If the slur is ending: set slur number to be the same as the origin slur number. + + for slur in bl.slur: + if slur.slurtype == 'start': + active_slur_count.inc() + slur.nr = active_slur_count.count + elif slur.slurtype == 'stop': + active_slur_count.dec() + + if slur.start_node: + slur.nr = slur.start_node.nr class BarMus(): """ Common class for notes and rests. """ From 92ba95a698fcfbef94fd1b867a405f8f5754d695 Mon Sep 17 00:00:00 2001 From: Endre Oma Date: Tue, 14 Feb 2017 23:06:39 +0100 Subject: [PATCH 3/6] Make Mediator keep track of slurs along a section --- ly/musicxml/ly2xml_mediator.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/ly/musicxml/ly2xml_mediator.py b/ly/musicxml/ly2xml_mediator.py index a9ec6951..ce8f9e81 100644 --- a/ly/musicxml/ly2xml_mediator.py +++ b/ly/musicxml/ly2xml_mediator.py @@ -76,6 +76,7 @@ def __init__(self): self.prev_tremolo = 8 self.tupl_dur = 0 self.tupl_sum = 0 + self.slur_stack = [] def new_header_assignment(self, name, value): """Distributing header information.""" @@ -625,7 +626,16 @@ def tie_to_next(self): def set_slur(self, nr, slur_type): """ Set the slur start or stop for the current note. """ - self.current_note.set_slur(nr, slur_type) + + slur_start = None + + if slur_type == 'stop': + slur_start = self.slur_stack.pop() + + self.current_note.set_slur(nr, slur_type, slur_start) + + if slur_type == 'start': + self.slur_stack.append(self.current_note.slur[-1]) def new_articulation(self, art_token): """ From cd5847723cedcdefc66db291063a9c0fe7e6b143 Mon Sep 17 00:00:00 2001 From: Endre Oma Date: Tue, 14 Feb 2017 23:22:54 +0100 Subject: [PATCH 4/6] Add tests for the slur enhancement --- tests/test_xml.py | 2 + tests/test_xml_files/merge_voice_slurs.ly | 37 ++ tests/test_xml_files/merge_voice_slurs.xml | 440 +++++++++++++++++++++ 3 files changed, 479 insertions(+) create mode 100644 tests/test_xml_files/merge_voice_slurs.ly create mode 100644 tests/test_xml_files/merge_voice_slurs.xml diff --git a/tests/test_xml.py b/tests/test_xml.py index e25f6ae5..1d8fa122 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_merge_voice_slurs(): + compare_output('merge_voice_slurs') def ly_to_xml(filename): """Read Lilypond file and return XML string.""" diff --git a/tests/test_xml_files/merge_voice_slurs.ly b/tests/test_xml_files/merge_voice_slurs.ly new file mode 100644 index 00000000..bae6d0f4 --- /dev/null +++ b/tests/test_xml_files/merge_voice_slurs.ly @@ -0,0 +1,37 @@ +\version "2.19.55" + +\header { + title = "merge voices with slurs" +} + +sopranoOne = \relative c'' { + % Music follows here. + c1( | c) | + c( | c) | + c( | c) | + + c2( d) | + + c4\( r2 d4( | e) r2 c4\) | +} + +sopranoTwo = \relative c'' { + % Music follows here. + c1( | c) | + r2 c2( | c2) r2 | + r2 c2( | r2 c2) | + + r4 e( f) r4 | + + r4 c4\( d( r4 | r4 d) c4\) r4 | +} + +\score { + \new ChoirStaff << + \new Staff << + \new Voice = "soprano1" { \voiceOne \sopranoOne } + \new Voice = "soprano2" { \voiceTwo \sopranoTwo } + >> + >> + \layout { } +} diff --git a/tests/test_xml_files/merge_voice_slurs.xml b/tests/test_xml_files/merge_voice_slurs.xml new file mode 100644 index 00000000..72026d93 --- /dev/null +++ b/tests/test_xml_files/merge_voice_slurs.xml @@ -0,0 +1,440 @@ + + + + merge voices with slurs + + + python-ly 0.9.4 + 2016-03-28 + + + + + bracket + + + + + + + + + + 1 + + + G + 2 + + + + + C + 5 + + 4 + 1 + whole + + + + + + 4 + + + + C + 5 + + 4 + 2 + whole + + + + + + + + + C + 5 + + 4 + 1 + whole + + + + + + 4 + + + + C + 5 + + 4 + 2 + whole + + + + + + + + + C + 5 + + 4 + 1 + whole + + + + + + 4 + + + + 2 + 2 + half + + + + C + 5 + + 2 + 2 + half + + + + + + + + + C + 5 + + 4 + 1 + whole + + + + + + 4 + + + + C + 5 + + 2 + 2 + half + + + + + + + 2 + 2 + half + + + + + + C + 5 + + 4 + 1 + whole + + + + + + 4 + + + + 2 + 2 + half + + + + C + 5 + + 2 + 2 + half + + + + + + + + + C + 5 + + 4 + 1 + whole + + + + + + 4 + + + + 2 + 2 + half + + + + C + 5 + + 2 + 2 + half + + + + + + + + + C + 5 + + 2 + 1 + half + + + + + + + D + 5 + + 2 + 1 + half + + + + + + 4 + + + + 1 + 2 + quarter + + + + E + 5 + + 1 + 2 + quarter + + + + + + + F + 5 + + 1 + 2 + quarter + + + + + + + 1 + 2 + quarter + + + + + + C + 5 + + 1 + 1 + quarter + + + + + + + 2 + 1 + half + + + + D + 5 + + 1 + 1 + quarter + + + + + + 4 + + + + 1 + 2 + quarter + + + + C + 5 + + 1 + 2 + quarter + + + + + + + D + 5 + + 1 + 2 + quarter + + + + + + + 1 + 2 + quarter + + + + + + E + 5 + + 1 + 1 + quarter + + + + + + + 2 + 1 + half + + + + C + 5 + + 1 + 1 + quarter + + + + + + 4 + + + + 1 + 2 + quarter + + + + D + 5 + + 1 + 2 + quarter + + + + + + + C + 5 + + 1 + 2 + quarter + + + + + + + 1 + 2 + quarter + + + + + From 6e040128d6518d946894271a50dcbd6e4f3aea3e Mon Sep 17 00:00:00 2001 From: Endre Oma Date: Mon, 20 Feb 2017 18:45:39 +0100 Subject: [PATCH 5/6] Add phrasing indication to the Slur object --- ly/musicxml/ly2xml_mediator.py | 8 +++++--- ly/musicxml/lymus2musxml.py | 4 ++-- ly/musicxml/xml_objs.py | 8 ++++---- 3 files changed, 11 insertions(+), 9 deletions(-) diff --git a/ly/musicxml/ly2xml_mediator.py b/ly/musicxml/ly2xml_mediator.py index ce8f9e81..7deca588 100644 --- a/ly/musicxml/ly2xml_mediator.py +++ b/ly/musicxml/ly2xml_mediator.py @@ -623,16 +623,18 @@ def tie_to_next(self): self.tied = True self.current_note.set_tie(tie_type) - def set_slur(self, nr, slur_type): + def set_slur(self, nr, slur_type, phrasing=False): + """ + Set the slur start or stop for the current note. + phrasing should be set to True if the slur is meant to be a phrasing mark. """ - Set the slur start or stop for the current note. """ slur_start = None if slur_type == 'stop': slur_start = self.slur_stack.pop() - self.current_note.set_slur(nr, slur_type, slur_start) + self.current_note.set_slur(nr, slur_type, phrasing, slur_start) if slur_type == 'start': self.slur_stack.append(self.current_note.slur[-1]) diff --git a/ly/musicxml/lymus2musxml.py b/ly/musicxml/lymus2musxml.py index 13dfb733..37f86953 100644 --- a/ly/musicxml/lymus2musxml.py +++ b/ly/musicxml/lymus2musxml.py @@ -421,9 +421,9 @@ def PhrasingSlur(self, phrslur): if phrslur.token == '\(': self.slurcount += 1 self.phrslurnr = self.slurcount - self.mediator.set_slur(self.phrslurnr, "start") + self.mediator.set_slur(self.phrslurnr, "start", phrasing=True) elif phrslur.token == '\)': - self.mediator.set_slur(self.phrslurnr, "stop") + self.mediator.set_slur(self.phrslurnr, "stop", phrasing=True) self.slurcount -= 1 def Dynamic(self, dynamic): diff --git a/ly/musicxml/xml_objs.py b/ly/musicxml/xml_objs.py index 1e6b6c1e..e246efc7 100644 --- a/ly/musicxml/xml_objs.py +++ b/ly/musicxml/xml_objs.py @@ -638,10 +638,10 @@ def __init__(self, fraction, ttype, nr, acttype, normtype): class Slur(): """Stores information about slur. start_node is only interesting if slurtype is 'stop'. start_node must be None or a Slur instance.""" - def __init__(self, nr, slurtype, start_node=None): + def __init__(self, nr, slurtype, phrasing=False, start_node=None): self.nr = nr self.slurtype = slurtype - + self.phrasing = phrasing self.start_node = start_node ## @@ -684,8 +684,8 @@ def set_octave(self, octave): def set_tie(self, tie_type): self.tie.append(tie_type) - def set_slur(self, nr, slur_type, slur_start_node=None): - self.slur.append(Slur(nr, slur_type, slur_start_node)) + def set_slur(self, nr, slur_type, phrasing=False, slur_start_node=None): + self.slur.append(Slur(nr, slur_type, phrasing, slur_start_node)) def add_articulation(self, art_name): self.artic.append(art_name) From 901f923c842e9aef3e879490f3df8b3103af7fe9 Mon Sep 17 00:00:00 2001 From: Urs Liska Date: Tue, 8 May 2018 00:39:56 +0200 Subject: [PATCH 6/6] XML: fix merge_voice_slurs.xml --- tests/test_xml_files/merge_voice_slurs.xml | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/test_xml_files/merge_voice_slurs.xml b/tests/test_xml_files/merge_voice_slurs.xml index 72026d93..f55aaeb6 100644 --- a/tests/test_xml_files/merge_voice_slurs.xml +++ b/tests/test_xml_files/merge_voice_slurs.xml @@ -435,6 +435,5 @@ quarter -