From 330f0c0b84b0d1a5fbc87da52f1d906a8fddbf65 Mon Sep 17 00:00:00 2001 From: Reinhold Kainhofer Date: Sat, 1 Sep 2007 12:38:02 +0200 Subject: [PATCH] MusicXML: Conversion of various types of spanners (octave, pedals, trills) -) Implement Octave-shift (8va, 8vb, 15ma, 15mb) and pedal spanners -) Implement trill spanners. It seems that in MusicXML, a trill span does not include the trill sign, so they are duplicated in many cases. -) Attempt to implement dashed slurs. Does not work yet, because the corresponding \slurDashed needs to be before the note, which the current code structure does not allow. -) Implement Glissando. Explicitly setting the type to wavy does not work yet, because that command needs to be before the note in lilypond which is not yet supported by the structure of the musicxml2ly application. -) Implement bends ("-\bendAfter #nr" in lilypond) -) Use short notation where appropriate for articulations (e.g. "-." instead of "\staccato" or "->" instead of "\accent") Open problem: In MusicXML, a trill span consists of a trill sign plus a wavy-line spanner, while in Lilypond \startTrillSpan will create both. This means that in the future musicxml2ly needs to be changed to combine various kinds of Events. Also, MusicXML allows a trill spanner to start and end at the same note, which is not possible in lilypond. --- python/musicexp.py | 93 ++++++++++++++++++++++++++++++++++++++++-- python/musicxml.py | 49 +++++++++++++++++++++- scripts/musicxml2ly.py | 105 ++++++++++++++++++++++++++++++++++++++++------- 3 files changed, 224 insertions(+), 23 deletions(-) diff --git a/python/musicexp.py b/python/musicexp.py index abe3125..6d7bc94 100644 --- a/python/musicexp.py +++ b/python/musicexp.py @@ -492,12 +492,28 @@ class SpanEvent (Event): def __init__(self): Event.__init__ (self) self.span_direction = 0 + self.line_type = 0 + self.size = 0 + def wait_for_note (self): + return True def get_properties(self): return "'span-direction %d" % self.span_direction class SlurEvent (SpanEvent): def ly_expression (self): - return {-1: '(', + before = '' + after = '' + # TODO: setting dashed/dotted line style does not work, because that + # command needs to be written before the note, not when the + # event is observed after the note! + #if self.line_type == 1: + #before = '\\slurDotted' + #elif self.line_type == 2: + #before = '\\slurDashed' + #if before: + #after = '\\slurSolid' + + return {-1: before + '(' + after, 0:'', 1:')'}.get (self.span_direction, '') @@ -507,7 +523,58 @@ class BeamEvent (SpanEvent): 0:'', 1:']'}.get (self.span_direction, '') +class PedalEvent (SpanEvent): + def ly_expression (self): + return {-1: '\\sustainDown', + 0:'', + 1:'\\sustainUp'}.get (self.span_direction, '') + +# type==-1 means octave up, type==-2 means octave down +class OctaveShiftEvent (SpanEvent): + def wait_for_note (self): + return False; + def ly_octave_shift_indicator (self): + if self.size == 8: + value = 1 + elif self.size == 15: + value = 2 + else: + value = 0 + # -2 means up + if self.span_direction == -2: + value = -value + return value + def ly_expression (self): + dir = self.ly_octave_shift_indicator () + value = '' + if dir: + value = '#(set-octavation %s)' % dir + return {-2: value, + -1: value, + 0: '', + 1: '#(set-octavation 0)'}.get (self.span_direction, '') + +class TrillSpanEvent (SpanEvent): + def ly_expression (self): + return {-1: '\\startTrillSpan', + 0:'', + 1:'\\stopTrillSpan'}.get (self.span_direction, '') + +class GlissandoEvent (SpanEvent): + def ly_expression (self): + style = '' + # TODO: wavy-line glissandos don't work, becasue the style has to be + # set before the note, at the \glissando it's already too late! + #if self.line_type == 3: # wavy-line: + #style = "\once\override Glissando #'style = #'zigzag" + # In lilypond, glissando is NOT a spanner, unlike MusicXML. + return {-1: style + '\\glissando', + 0:'', + 1:''}.get (self.span_direction, '') + class ArpeggioEvent(Event): + def wait_for_note (self): + return True; def ly_expression (self): return ('\\arpeggio') @@ -517,7 +584,7 @@ class TieEvent(Event): return '~' -class HairpinEvent (Event): +class HairpinEvent (SpanEvent): def __init__ (self, type): self.type = type def hairpin_to_ly (self): @@ -540,6 +607,8 @@ class DynamicsEvent (Event): "mp", "mf", "f", "ff", "fff", "ffff", "fp", "sf", "sff", "sp", "spp", "sfz", "rfz" ]; + def wait_for_note (self): + return True; def ly_expression (self): if self.type == None: return; @@ -568,17 +637,33 @@ class ArticulationEvent (Event): def ly_expression (self): return '%s\\%s' % (self.direction_mod (), self.type) +class ShortArticulationEvent (ArticulationEvent): + def direction_mod (self): + # default is - + return { 1: '^', -1: '_', 0: '-' }.get (self.force_direction, '-') + def ly_expression (self): + return '%s%s' % (self.direction_mod (), self.type) class TremoloEvent (Event): def __init__ (self): - self.bars = 0; + Event.__init__ (self) + self.bars = 0 def ly_expression (self): str='' if self.bars > 0: str += ':%s' % (2 ** (2 + string.atoi (self.bars))) return str - + +class BendEvent (Event): + def __init__ (self): + Event.__init__ (self) + self.alter = 0 + def ly_expression (self): + if self.alter: + return "-\\bendAfter #%s" % self.alter + else: + return '' class RhythmicEvent(Event): def __init__ (self): diff --git a/python/musicxml.py b/python/musicxml.py index 0430661..fe8dd60 100644 --- a/python/musicxml.py +++ b/python/musicxml.py @@ -536,20 +536,48 @@ class Accidental (Music_xml_node): self.editorial = False self.cautionary = False +class Music_xml_spanner (Music_xml_node): + def get_type (self): + if hasattr (self, 'type'): + return self.type + else: + return 0 + def get_size (self): + if hasattr (self, 'size'): + return string.atoi (self.size) + else: + return 0 -class Tuplet(Music_xml_node): +class Tuplet(Music_xml_spanner): pass -class Slur (Music_xml_node): +class Slur (Music_xml_spanner): def get_type (self): return self.type -class Beam (Music_xml_node): +class Beam (Music_xml_spanner): def get_type (self): return self.get_text () def is_primary (self): return self.number == "1" + +class Wavy_line (Music_xml_spanner): + pass +class Pedal (Music_xml_spanner): + pass + +class Glissando (Music_xml_spanner): + pass + +class Octave_shift (Music_xml_spanner): + # default is 8 for the octave-shift! + def get_size (self): + if hasattr (self, 'size'): + return string.atoi (self.size) + else: + return 8 + class Chord (Music_xml_node): pass @@ -575,6 +603,15 @@ class Direction (Music_xml_node): class DirType (Music_xml_node): pass +class Bend (Music_xml_node): + def bend_alter (self): + alter = self.get_maybe_exist_named_child ('bend-alter') + if alter: + return alter.get_text() + else: + return 0 + + ## need this, not all classes are instantiated ## for every input file. Only add those classes, that are either directly @@ -584,26 +621,32 @@ class_dict = { 'accidental': Accidental, 'attributes': Attributes, 'beam' : Beam, + 'bend' : Bend, 'chord': Chord, 'dot': Dot, 'direction': Direction, 'direction-type': DirType, 'duration': Duration, + 'glissando': Glissando, 'grace': Grace, 'identification': Identification, 'lyric': Lyric, 'measure': Measure, 'notations': Notations, 'note': Note, + 'octave-shift': Octave_shift, 'part': Part, 'part-list': Part_list, + 'pedal': Pedal, 'pitch': Pitch, 'rest': Rest, 'slur': Slur, 'syllabic': Syllabic, 'text': Text, 'time-modification': Time_modification, + 'tuplet': Tuplet, 'type': Type, + 'wavy-line': Wavy_line, 'work': Work, } diff --git a/scripts/musicxml2ly.py b/scripts/musicxml2ly.py index ed57e29..46f3da3 100644 --- a/scripts/musicxml2ly.py +++ b/scripts/musicxml2ly.py @@ -195,13 +195,25 @@ def musicxml_attributes_to_lily (attrs): spanner_event_dict = { 'slur' : musicexp.SlurEvent, 'beam' : musicexp.BeamEvent, -} + 'glissando' : musicexp.GlissandoEvent, + 'pedal' : musicexp.PedalEvent, + 'wavy-line' : musicexp.TrillSpanEvent, + 'octave-shift' : musicexp.OctaveShiftEvent +} spanner_type_dict = { 'start': -1, 'begin': -1, + 'up': -2, + 'down': -1, 'stop': 1, 'end' : 1 } +spanner_line_type_dict = { + 'solid': 0, + 'dashed': 1, + 'dotted': 2, + 'wavy': 3 +} def musicxml_spanner_to_lily_event (mxl_event): ev = None @@ -212,6 +224,7 @@ def musicxml_spanner_to_lily_event (mxl_event): ev = func() else: print 'unknown span event ', mxl_event + key = mxl_event.get_type () span_direction = spanner_type_dict.get (key) @@ -219,6 +232,13 @@ def musicxml_spanner_to_lily_event (mxl_event): ev.span_direction = span_direction else: print 'unknown span type', key, 'for', name + + if hasattr (mxl_event, 'line-type'): + span_line_type = spanner_line_type_dict.get (getattr (mxl_event, 'line-type')) + if span_line_type: + ev.line_type = span_line_type + # assign the size, which is used for octave-shift, etc. + ev.size = mxl_event.get_size () return ev @@ -234,14 +254,32 @@ def musicxml_fermata_to_lily_event (mxl_event): ev.force_direction = dir return ev -def musicxml_tremolo_to_lily_event(mxl_event): +def musicxml_tremolo_to_lily_event (mxl_event): if mxl_event.get_name () != "tremolo": return ev = musicexp.TremoloEvent () ev.bars = mxl_event.get_text () return ev +def musicxml_bend_to_lily_event (mxl_event): + if mxl_event.get_name () != "bend": + return + ev = musicexp.BendEvent () + ev.alter = mxl_event.bend_alter () + return ev + + # TODO: Some translations are missing! +short_articulations_dict = { + "staccato": ".", + "tenuto": "-", + "stopped": "+", + "staccatissimo": "|", + "accent": ">", + "strong-accent": "^", + #"portato": "_", # does not exist in MusicXML + #"fingering": "", # fingering is special cased, as get_text() will be the event's name +} articulations_dict = { ##### ORNAMENTS "trill-mark": "trill", @@ -251,7 +289,7 @@ articulations_dict = { #"shake": "?", #"wavy-line": "?", "mordent": "mordent", - "inverted-mordent": "downmordent", + "inverted-mordent": "prall", #"schleifer": "?" ##### TECHNICALS "up-bow": "upbow", @@ -259,28 +297,21 @@ articulations_dict = { "harmonic": "flageolet", #"open-string": "", #"thumb-position": "", - #"fingering": "", #"pluck": "", #"double-tongue": "", #"triple-tongue": "", - #"stopped": "", #"snap-pizzicato": "", #"fret": "", #"string": "", #"hammer-on": "", #"pull-off": "", - #"bend": "", + #"bend": "bendAfter #%s", # bend is special-cased, as we need to process the bend-alter subelement! #"tap": "", #"heel": "", #"toe": "", #"fingernails": "" ##### ARTICULATIONS - "accent": "accent", - "strong-accent": "marcato", - "staccato": "staccato", - "tenuto": "tenuto", #"detached-legato": "", - "staccatissimo": "staccatissimo", #"spiccato": "", #"scoop": "", #"plop": "", @@ -291,10 +322,29 @@ articulations_dict = { #"stress": "", #"unstress": "" } +articulation_spanners = [ "wavy-line" ] -def musicxml_articulation_to_lily_event(mxl_event): - ev = musicexp.ArticulationEvent () - tp = articulations_dict.get (mxl_event.get_name ()) +def musicxml_articulation_to_lily_event (mxl_event): + # wavy-line elements are treated as trill spanners, not as articulation ornaments + if mxl_event.get_name () in articulation_spanners: + return musicxml_spanner_to_lily_event (mxl_event) + + # special case, because of the bend-alter subelement + if mxl_event.get_name() == "bend": + return musicxml_bend_to_lily_event (mxl_event) + + # If we can write a shorthand, use them! + if mxl_event.get_name() == "fingering": + ev = musicexp.ShortArticulationEvent () + tp = mxl_event.get_text() + # In all other cases, use the dicts to translate the xml tag name to a proper lilypond command + elif short_articulations_dict.get (mxl_event.get_name ()): + ev = musicexp.ShortArticulationEvent () + tp = short_articulations_dict.get (mxl_event.get_name ()) + else: + ev = musicexp.ArticulationEvent () + tp = articulations_dict.get (mxl_event.get_name ()) + if not tp: return @@ -318,6 +368,8 @@ def musicxml_direction_to_lily( n ): dirtype = n.get_maybe_exist_typed_child (musicxml.DirType) if not dirtype: return res + + direction_spanners = [ 'octave-shift', 'pedal' ] for entry in dirtype.get_all_children (): if entry.get_name () == "dynamics": @@ -342,6 +394,14 @@ def musicxml_direction_to_lily( n ): if wedgetypeval != None: event = musicexp.HairpinEvent (wedgetypeval) res.append (event) + + + # octave shifts. pedal marks etc. are spanners: + if entry.get_name() in direction_spanners: + event = musicxml_spanner_to_lily_event (entry) + if event: + res.append (event) + return res @@ -491,7 +551,10 @@ def musicxml_voice_to_lily_voice (voice): if isinstance (n, musicxml.Direction): for a in musicxml_direction_to_lily (n): - voice_builder.add_dynamics (a) + if a.wait_for_note (): + voice_builder.add_dynamics (a) + else: + voice_builder.add_music (a, 0) continue if not n.get_maybe_exist_named_child ('chord'): @@ -578,9 +641,19 @@ def musicxml_voice_to_lily_voice (voice): fermatas = notations.get_named_children ('fermata') for a in fermatas: - ev = musicxml_fermata_to_lily_event (a); + ev = musicxml_fermata_to_lily_event (a) if ev: ev_chord.append (ev) + + arpeggiate = notations.get_named_children ('arpeggiate') + for a in arpeggiate: + ev_chord.append (musicexp.ArpeggioEvent ()) + + glissandos = notations.get_named_children ('glissando') + for a in glissandos: + ev = musicxml_spanner_to_lily_event (a) + if ev: + ev_chord.append (ev) # Articulations can contain the following child elements: # accent | strong-accent | staccato | tenuto | -- 1.5.2.3