]> git.donarmstrong.com Git - lilypond.git/commitdiff
MusicXML: Conversion of various types of spanners (octave, pedals, trills)
authorReinhold Kainhofer <reinhold@kainhofer.com>
Sun, 2 Sep 2007 23:03:14 +0000 (01:03 +0200)
committerReinhold Kainhofer <reinhold@kainhofer.com>
Thu, 6 Sep 2007 14:14:36 +0000 (16:14 +0200)
-) 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.

Signed-off-by: Reinhold Kainhofer <reinhold@kainhofer.com>
python/musicexp.py
python/musicxml.py
scripts/musicxml2ly.py

index abe3125955437bd3e7352372c3a5f518aac9c95e..79fcf95ec76384f5465a2c6e3b57ac31169eb623 100644 (file)
@@ -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,10 +637,17 @@ 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=''
@@ -579,6 +655,15 @@ class TremoloEvent (Event):
             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):
index 0430661da98cb7a0fe52e58580d198b7ccb4e021..fe8dd60537d59ea42bd384ed5eb1df1ffead08f5 100644 (file)
@@ -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,
 }
 
index ed57e29edb80f47b0bba66c229c42c55ce013613..12d4747bf9dc01f8c3cf82cf785a9bd9dfcae012 100644 (file)
@@ -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
@@ -213,6 +225,7 @@ def musicxml_spanner_to_lily_event (mxl_event):
     else:
         print 'unknown span event ', mxl_event
 
+
     key = mxl_event.get_type ()
     span_direction = spanner_type_dict.get (key)
     if span_direction:
@@ -220,6 +233,13 @@ def musicxml_spanner_to_lily_event (mxl_event):
     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
 
 def musicxml_direction_to_indicator (direction):
@@ -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):
+    # 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 ())
 
-def musicxml_articulation_to_lily_event(mxl_event):
-    ev = musicexp.ArticulationEvent ()
-    tp = articulations_dict.get (mxl_event.get_name ())
     if not tp:
         return
     
@@ -319,6 +369,8 @@ def musicxml_direction_to_lily( n ):
     if not dirtype: 
       return res
 
+    direction_spanners = [ 'octave-shift', 'pedal' ]
+
     for entry in dirtype.get_all_children ():
         if entry.get_name () == "dynamics":
             for dynentry in entry.get_all_children ():
@@ -343,6 +395,14 @@ def musicxml_direction_to_lily( n ):
                     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
 
 instrument_drumtype_dict = {
@@ -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 |