diff --git a/ly/musicxml/create_musicxml.py b/ly/musicxml/create_musicxml.py index 8fb82b7b..a1879ec5 100644 --- a/ly/musicxml/create_musicxml.py +++ b/ly/musicxml/create_musicxml.py @@ -596,6 +596,11 @@ def add_octave_shift(self, plac, octdir, size): dirtypenode = etree.SubElement(direction, "direction-type") dyn_node = etree.SubElement(dirtypenode, "octave-shift", oct_dict) + def add_sustain(self, type, line, sign): + direction = etree.SubElement(self.current_bar, "direction", placement='below') + dirtypenode = etree.SubElement(direction, "direction-type") + pedal = etree.SubElement(dirtypenode, "pedal", type=type, line=line, sign=sign) + def add_dirwords(self, words): """Add words in direction, e. g. a tempo mark.""" dirtypenode = etree.SubElement(self.direction, "direction-type") diff --git a/ly/musicxml/ly2xml_mediator.py b/ly/musicxml/ly2xml_mediator.py index a9ec6951..c870e8b5 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.sustain_style = 'text' def new_header_assignment(self, name, value): """Distributing header information.""" @@ -717,6 +718,24 @@ def new_trill_spanner(self, end=None): end = "start" self.current_note.add_adv_ornament('wavy-line', end) + def new_sustain(self, type=None): + if not type: + type = 'start' + if self.current_note.sustain and \ + self.current_note.sustain.type == 'stop' and type == 'start': + type = 'change' + + # implied that self.sustain_style == 'text' + line = 'no' + sign = 'yes' + if self.sustain_style == 'mixed': + line = 'yes' + elif self.sustain_style == 'bracket': + line = 'yes' + sign = 'no' + + self.current_note.set_sustain(type, line, sign) + def new_ottava(self, octdiff): octdiff = int(octdiff) if self.octdiff == octdiff: @@ -777,6 +796,8 @@ def set_by_property(self, prprty, value, group=False): self.new_lyric_nr(value) elif prprty == 'systemStartDelimiter': self.change_group_bracket(value) + elif prprty == 'pedalSustainStyle': + self.set_sustain_style(value) def set_partname(self, name): if self.score.is_empty(): @@ -801,6 +822,9 @@ def set_partmidi(self, midi): self.new_part() self.part.midi = midi + def set_sustain_style(self, style): + self.sustain_style = style + def new_lyric_nr(self, num): self.lyric_nr = num diff --git a/ly/musicxml/lymus2musxml.py b/ly/musicxml/lymus2musxml.py index 13dfb733..d9282dec 100644 --- a/ly/musicxml/lymus2musxml.py +++ b/ly/musicxml/lymus2musxml.py @@ -464,6 +464,11 @@ def Set(self, cont_set): self.mediator.unset_tuplspan_dur() return val = cont_set.value().get_string() + + if cont_set.property() == 'pedalSustainStyle': + # access #'value + # FIXME: find a safer way to get a string + val = cont_set.value()[0][0].token else: val = cont_set.value().value() if cont_set.context() in part_contexts: @@ -497,6 +502,10 @@ def Command(self, command): if self.tupl_span: self.mediator.unset_tuplspan_dur() self.tupl_span = False + elif command.token == '\\sustainOn': + self.mediator.new_sustain('start') + elif command.token == '\\sustainOff': + self.mediator.new_sustain('stop') else: if command.token not in excls: print("Unknown command:", command.token) diff --git a/ly/musicxml/xml_objs.py b/ly/musicxml/xml_objs.py index bad1e057..3b95cb78 100644 --- a/ly/musicxml/xml_objs.py +++ b/ly/musicxml/xml_objs.py @@ -140,6 +140,8 @@ def before_note(self, obj): self._add_dynamics([d for d in obj.dynamic if d.before]) if obj.oct_shift and not obj.oct_shift.octdir == 'stop': self.musxml.add_octave_shift(obj.oct_shift.plac, obj.oct_shift.octdir, obj.oct_shift.size) + if obj.sustain: + self.musxml.add_sustain(obj.sustain.type, obj.sustain.line, obj.sustain.sign) def after_note(self, obj): """Xml-nodes after note.""" @@ -510,6 +512,7 @@ def __init__(self, duration, voice=1): self.other_notation = None self.dynamic = [] self.oct_shift = None + self.sustain = None def __repr__(self): return '<{0} {1}>'.format(self.__class__.__name__, self.duration) @@ -541,6 +544,9 @@ def set_dynamics_dashes(self, sign, before=True): def set_oct_shift(self, plac, octdir, size): self.oct_shift = OctaveShift(plac, octdir, size) + def set_sustain(self, type, line='no', sign='yes'): + self.sustain = Sustain(type, line, sign) + def has_attr(self): return False @@ -600,6 +606,12 @@ def __init__(self, nr, slurtype): self.nr = nr self.slurtype = slurtype +class Sustain(): + """Stores information about sustain.""" + def __init__(self, type, line='no', sign='yes'): + self.type = type + self.line = line + self.sign = sign ## # Subclasses of BarMus diff --git a/tests/test_xml.py b/tests/test_xml.py index e25f6ae5..9b5bf696 100644 --- a/tests/test_xml.py +++ b/tests/test_xml.py @@ -31,6 +31,9 @@ def test_tuplet(): compare_output('tuplet') +def test_sustain(): + compare_output('sustain') + def ly_to_xml(filename): """Read Lilypond file and return XML string.""" writer = ly.musicxml.writer() diff --git a/tests/test_xml_files/sustain.ly b/tests/test_xml_files/sustain.ly new file mode 100644 index 00000000..4169c6e4 --- /dev/null +++ b/tests/test_xml_files/sustain.ly @@ -0,0 +1,19 @@ +\score { + \relative c' { + c4\sustainOn d e\sustainOff f | + c8\sustainOn c d d\sustainOff\sustainOn e e f f \sustainOff | + + \set Staff.pedalSustainStyle = #'mixed + c4\sustainOn d e\sustainOff f | + c8\sustainOn c d d\sustainOff\sustainOn e e f f\sustainOff | + + \set Staff.pedalSustainStyle = #'bracket + c4\sustainOn d e\sustainOff f | + c8\sustainOn c d d\sustainOff\sustainOn e e f f\sustainOff | + + \set Staff.pedalSustainStyle = #'text + c4\sustainOn d e\sustainOff f | + c8\sustainOn c d d\sustainOff\sustainOn e e f f\sustainOff | + } + \layout{} +} diff --git a/tests/test_xml_files/sustain.xml b/tests/test_xml_files/sustain.xml new file mode 100644 index 00000000..f884bf15 --- /dev/null +++ b/tests/test_xml_files/sustain.xml @@ -0,0 +1,578 @@ + + + + + + python-ly 0.9.5 + 2017-03-29 + + + + + + + + + + + 2 + + + G + 2 + + + + + + + + + + C + 4 + + 2 + 1 + quarter + + + + D + 4 + + 2 + 1 + quarter + + + + + + + + + E + 4 + + 2 + 1 + quarter + + + + F + 4 + + 2 + 1 + quarter + + + + + + + + + + + C + 4 + + 1 + 1 + eighth + + + + C + 4 + + 1 + 1 + eighth + + + + D + 4 + + 1 + 1 + eighth + + + + + + + + + D + 4 + + 1 + 1 + eighth + + + + E + 4 + + 1 + 1 + eighth + + + + E + 4 + + 1 + 1 + eighth + + + + F + 4 + + 1 + 1 + eighth + + + + + + + + + F + 4 + + 1 + 1 + eighth + + + + + + + + + + + C + 4 + + 2 + 1 + quarter + + + + D + 4 + + 2 + 1 + quarter + + + + + + + + + E + 4 + + 2 + 1 + quarter + + + + F + 4 + + 2 + 1 + quarter + + + + + + + + + + + C + 4 + + 1 + 1 + eighth + + + + C + 4 + + 1 + 1 + eighth + + + + D + 4 + + 1 + 1 + eighth + + + + + + + + + D + 4 + + 1 + 1 + eighth + + + + E + 4 + + 1 + 1 + eighth + + + + E + 4 + + 1 + 1 + eighth + + + + F + 4 + + 1 + 1 + eighth + + + + + + + + + F + 4 + + 1 + 1 + eighth + + + + + + + + + + + C + 4 + + 2 + 1 + quarter + + + + D + 4 + + 2 + 1 + quarter + + + + + + + + + E + 4 + + 2 + 1 + quarter + + + + F + 4 + + 2 + 1 + quarter + + + + + + + + + + + C + 4 + + 1 + 1 + eighth + + + + C + 4 + + 1 + 1 + eighth + + + + D + 4 + + 1 + 1 + eighth + + + + + + + + + D + 4 + + 1 + 1 + eighth + + + + E + 4 + + 1 + 1 + eighth + + + + E + 4 + + 1 + 1 + eighth + + + + F + 4 + + 1 + 1 + eighth + + + + + + + + + F + 4 + + 1 + 1 + eighth + + + + + + + + + + + C + 4 + + 2 + 1 + quarter + + + + D + 4 + + 2 + 1 + quarter + + + + + + + + + E + 4 + + 2 + 1 + quarter + + + + F + 4 + + 2 + 1 + quarter + + + + + + + + + + + C + 4 + + 1 + 1 + eighth + + + + C + 4 + + 1 + 1 + eighth + + + + D + 4 + + 1 + 1 + eighth + + + + + + + + + D + 4 + + 1 + 1 + eighth + + + + E + 4 + + 1 + 1 + eighth + + + + E + 4 + + 1 + 1 + eighth + + + + F + 4 + + 1 + 1 + eighth + + + + + + + + + F + 4 + + 1 + 1 + eighth + + + + +