]> git.donarmstrong.com Git - lilypond.git/blobdiff - scripts/musicxml2ly.py
Merge branch 'master' into lilypond/translation
[lilypond.git] / scripts / musicxml2ly.py
index a2e09e87e29b4beeeffee04556ed34c7ae0d3d7c..e2a54008e321eef92b447d57d51f77a525eb710b 100644 (file)
@@ -1,5 +1,5 @@
 #!@TARGET_PYTHON@
-
+# -*- coding: utf-8 -*-
 import optparse
 import sys
 import re
@@ -199,6 +199,8 @@ def extract_score_information (tree):
         set_if_exists ('encodingdate', ids.get_encoding_date ())
         set_if_exists ('encoder', ids.get_encoding_person ())
         set_if_exists ('encodingdescription', ids.get_encoding_description ())
+        
+        set_if_exists ('texidoc', ids.get_file_description ());
 
         # Finally, apply the required compatibility modes
         # Some applications created wrong MusicXML files, so we need to 
@@ -211,6 +213,9 @@ def extract_score_information (tree):
         if "Dolet 3.4 for Sibelius" in software:
             conversion_settings.ignore_beaming = True
             progress (_ ("Encountered file created by Dolet 3.4 for Sibelius, containing wrong beaming information. All beaming information in the MusicXML file will be ignored"))
+        if "Noteworthy Composer" in software:
+            conversion_settings.ignore_beaming = True
+            progress (_ ("Encountered file created by Noteworthy Composer's nwc2xml, containing wrong beaming information. All beaming information in the MusicXML file will be ignored"))
         # TODO: Check for other unsupported features
 
     return header
@@ -419,8 +424,6 @@ def extract_score_structure (part_list, staffinfo):
                     del staves[pos]
                 # replace the staves with the whole group
                 for j in staves[(prev_start + 1):pos]:
-                    if j.is_group:
-                        j.stafftype = "InnerStaffGroup"
                     group.append_staff (j)
                 del staves[(prev_start + 1):pos]
                 staves.insert (prev_start + 1, group)
@@ -444,17 +447,22 @@ def extract_score_structure (part_list, staffinfo):
 
 def musicxml_duration_to_lily (mxl_note):
     d = musicexp.Duration ()
-    # if the note has no Type child, then that method spits out a warning and 
-    # returns 0, i.e. a whole note
+    # if the note has no Type child, then that method returns None. In that case,
+    # use the <duration> tag instead. If that doesn't exist, either -> Error
     d.duration_log = mxl_note.get_duration_log ()
-
-    d.dots = len (mxl_note.get_typed_children (musicxml.Dot))
-    # Grace notes by specification have duration 0, so no time modification 
-    # factor is possible. It even messes up the output with *0/1
-    if not mxl_note.get_maybe_exist_typed_child (musicxml.Grace):
-        d.factor = mxl_note._duration / d.get_length ()
-
-    return d
+    if d.duration_log == None:
+        if mxl_note._duration > 0:
+            return rational_to_lily_duration (mxl_note._duration)
+        else:
+            mxl_note.message (_ ("Encountered note at %s without type and duration (=%s)") % (mxl_note.start, mxl_note._duration) )
+            return None
+    else:
+        d.dots = len (mxl_note.get_typed_children (musicxml.Dot))
+        # Grace notes by specification have duration 0, so no time modification 
+        # factor is possible. It even messes up the output with *0/1
+        if not mxl_note.get_maybe_exist_typed_child (musicxml.Grace):
+            d.factor = mxl_note._duration / d.get_length ()
+        return d
 
 def rational_to_lily_duration (rational_len):
     d = musicexp.Duration ()
@@ -595,6 +603,7 @@ def group_tuplets (music_list, events):
 
     
     indices = []
+    brackets = {}
 
     j = 0
     for (ev_chord, tuplet_elt, fraction) in events:
@@ -602,15 +611,21 @@ def group_tuplets (music_list, events):
             if music_list[j] == ev_chord:
                 break
             j += 1
+        nr = tuplet_elt.number
         if tuplet_elt.type == 'start':
-            indices.append ((j, None, fraction))
+            tuplet_info = [j, None, fraction]
+            indices.append (tuplet_info)
+            brackets[nr] = tuplet_info
         elif tuplet_elt.type == 'stop':
-            indices[-1] = (indices[-1][0], j, indices[-1][2])
+            bracket_info = brackets.get (nr, None)
+            if bracket_info:
+                bracket_info[1] = j
+                del brackets[nr]
 
     new_list = []
     last = 0
     for (i1, i2, frac) in indices:
-        if i1 >= i2:
+        if i1 > i2:
             continue
 
         new_list.extend (music_list[last:i1])
@@ -648,8 +663,15 @@ def musicxml_key_to_lily (attributes):
     (fifths, mode) = attributes.get_key_signature () 
     try:
         (n,a) = {
-            'major' : (0,0),
-            'minor' : (5,0),
+            'major'     : (0,0),
+            'minor'     : (5,0),
+            'ionian'    : (0,0),
+            'dorian'    : (1,0),
+            'phrygian'  : (2,0),
+            'lydian'    : (3,0),
+            'mixolydian': (4,0),
+            'aeolian'   : (5,0),
+            'locrian'   : (6,0),
             }[mode]
         start_pitch.step = n
         start_pitch.alteration = a
@@ -672,13 +694,47 @@ def musicxml_key_to_lily (attributes):
     change.mode = mode
     change.tonic = start_pitch
     return change
+
+def musicxml_transpose_to_lily (attributes):
+    transpose = attributes.get_transposition ()
+    if not transpose:
+        return None
+
+    shift = musicexp.Pitch ()
+    octave_change = transpose.get_maybe_exist_named_child ('octave-change')
+    if octave_change:
+        shift.octave = string.atoi (octave_change.get_text ())
+    chromatic_shift = string.atoi (transpose.get_named_child ('chromatic').get_text ())
+    chromatic_shift_normalized = chromatic_shift % 12;
+    (shift.step, shift.alteration) = [
+        (0,0), (0,1), (1,0), (2,-1), (2,0), 
+        (3,0), (3,1), (4,0), (5,-1), (5,0), 
+        (6,-1), (6,0)][chromatic_shift_normalized];
     
+    shift.octave += (chromatic_shift - chromatic_shift_normalized) / 12
+
+    diatonic = transpose.get_maybe_exist_named_child ('diatonic')
+    if diatonic:
+        diatonic_step = string.atoi (diatonic.get_text ()) % 7
+        if diatonic_step != shift.step:
+            # We got the alter incorrect!
+            old_semitones = shift.semitones ()
+            shift.step = diatonic_step
+            new_semitones = shift.semitones ()
+            shift.alteration += old_semitones - new_semitones
+
+    transposition = musicexp.Transposition ()
+    transposition.pitch = musicexp.Pitch ().transposed (shift)
+    return transposition
+
+
 def musicxml_attributes_to_lily (attrs):
     elts = []
     attr_dispatch =  {
         'clef': musicxml_clef_to_lily,
         'time': musicxml_time_to_lily,
-        'key': musicxml_key_to_lily
+        'key': musicxml_key_to_lily,
+        'transpose': musicxml_transpose_to_lily,
     }
     for (k, func) in attr_dispatch.items ():
         children = attrs.get_named_children (k)
@@ -1639,6 +1695,8 @@ class LilyPondVoiceBuilder:
             duration_factor = 1
             duration_log = {1: 0, 2: 1, 4:2, 8:3, 16:4, 32:5, 64:6, 128:7, 256:8, 512:9}.get (diff.denominator (), -1)
             duration_dots = 0
+            # TODO: Use the time signature for skips, too. Problem: The skip 
+            #       might not start at a measure boundary!
             if duration_log > 0: # denominator is a power of 2...
                 if diff.numerator () == 3:
                     duration_log -= 1
@@ -1762,32 +1820,9 @@ def musicxml_voice_to_lily_voice (voice):
                 voice_builder.add_partial (a)
             continue
 
-        if isinstance (n, musicxml.Direction):
-            for a in musicxml_direction_to_lily (n):
-                if a.wait_for_note ():
-                    voice_builder.add_dynamics (a)
-                else:
-                    voice_builder.add_command (a)
-            continue
-
-        if isinstance (n, musicxml.Harmony):
-            for a in musicxml_harmony_to_lily (n):
-                if a.wait_for_note ():
-                    voice_builder.add_dynamics (a)
-                else:
-                    voice_builder.add_command (a)
-            for a in musicxml_harmony_to_lily_chordname (n):
-                pending_chordnames.append (a)
-            continue
-
-        if isinstance (n, musicxml.FiguredBass):
-            a = musicxml_figured_bass_to_lily (n)
-            if a:
-                pending_figured_bass.append (a)
-            continue
-
         is_chord = n.get_maybe_exist_named_child ('chord')
-        if not is_chord:
+        is_after_grace = (isinstance (n, musicxml.Note) and n.is_after_grace ());
+        if not is_chord and not is_after_grace:
             try:
                 voice_builder.jumpto (n._when)
             except NegativeSkip, neg:
@@ -1828,6 +1863,31 @@ def musicxml_voice_to_lily_voice (voice):
             voice_builder.add_multibar_rest (n._duration)
             continue
 
+
+        if isinstance (n, musicxml.Direction):
+            for a in musicxml_direction_to_lily (n):
+                if a.wait_for_note ():
+                    voice_builder.add_dynamics (a)
+                else:
+                    voice_builder.add_command (a)
+            continue
+
+        if isinstance (n, musicxml.Harmony):
+            for a in musicxml_harmony_to_lily (n):
+                if a.wait_for_note ():
+                    voice_builder.add_dynamics (a)
+                else:
+                    voice_builder.add_command (a)
+            for a in musicxml_harmony_to_lily_chordname (n):
+                pending_chordnames.append (a)
+            continue
+
+        if isinstance (n, musicxml.FiguredBass):
+            a = musicxml_figured_bass_to_lily (n)
+            if a:
+                pending_figured_bass.append (a)
+            continue
+
         if isinstance (n, musicxml.Attributes):
             for a in musicxml_attributes_to_lily (n):
                 voice_builder.add_command (a)
@@ -1855,15 +1915,31 @@ def musicxml_voice_to_lily_voice (voice):
             ev_chord = musicexp.ChordEvent()
             voice_builder.add_music (ev_chord, n._duration)
 
+        # For grace notes:
         grace = n.get_maybe_exist_typed_child (musicxml.Grace)
-        if grace:
+        if n.is_grace ():
+            is_after_grace = ev_chord.has_elements () or n.is_after_grace ();
+            is_chord = n.get_maybe_exist_typed_child (musicxml.Chord)
+
             grace_chord = None
-            if n.get_maybe_exist_typed_child (musicxml.Chord) and ev_chord.grace_elements:
-                grace_chord = ev_chord.grace_elements.get_last_event_chord ()
-            if not grace_chord:
-                grace_chord = musicexp.ChordEvent ()
-                ev_chord.append_grace (grace_chord)
-            if hasattr (grace, 'slash'):
+
+            # after-graces and other graces use different lists; Depending on
+            # whether we have a chord or not, obtain either a new ChordEvent or 
+            # the previous one to create a chord
+            if is_after_grace:
+                if ev_chord.after_grace_elements and n.get_maybe_exist_typed_child (musicxml.Chord):
+                    grace_chord = ev_chord.after_grace_elements.get_last_event_chord ()
+                if not grace_chord:
+                    grace_chord = musicexp.ChordEvent ()
+                    ev_chord.append_after_grace (grace_chord)
+            elif n.is_grace ():
+                if ev_chord.grace_elements and n.get_maybe_exist_typed_child (musicxml.Chord):
+                    grace_chord = ev_chord.grace_elements.get_last_event_chord ()
+                if not grace_chord:
+                    grace_chord = musicexp.ChordEvent ()
+                    ev_chord.append_grace (grace_chord)
+
+            if hasattr (grace, 'slash') and not is_after_grace:
                 # TODO: use grace_type = "appoggiatura" for slurred grace notes
                 if grace.slash == "yes":
                     ev_chord.grace_type = "acciaccatura"
@@ -1924,7 +2000,7 @@ def musicxml_voice_to_lily_voice (voice):
                 frac = (1,1)
                 if mod:
                     frac = mod.get_fraction ()
-                
+
                 tuplet_events.append ((ev_chord, tuplet_event, frac))
 
             # First, close all open slurs, only then start any new slur
@@ -2018,7 +2094,7 @@ def musicxml_voice_to_lily_voice (voice):
             for a in ornaments:
                 for ch in a.get_all_children ():
                     ev = musicxml_articulation_to_lily_event (ch)
-                    if ev: 
+                    if ev:
                         ev_chord.append (ev)
 
             dynamics = notations.get_named_children ('dynamics')
@@ -2040,14 +2116,6 @@ def musicxml_voice_to_lily_voice (voice):
                     is_beamed = True
                 elif beam_ev.span_direction == 1: # beam and thus melisma ends here
                     is_beamed = False
-            
-        if tuplet_event:
-            mod = n.get_maybe_exist_typed_child (musicxml.Time_modification)
-            frac = (1,1)
-            if mod:
-                frac = mod.get_fraction ()
-                
-            tuplet_events.append ((ev_chord, tuplet_event, frac))
 
         # Extract the lyrics
         if not rest and not ignore_lyrics:
@@ -2537,17 +2605,21 @@ def main ():
     conversion_settings.ignore_beaming = not options.convert_beaming
 
     # Allow the user to leave out the .xml or xml on the filename
-    if args[0]=="-": # Read from stdin
-        filename="-"
+    basefilename = args[0].decode('utf-8')
+    if basefilename == "-": # Read from stdin
+        basefilename = "-"
     else:
-        filename = get_existing_filename_with_extension (args[0], "xml")
+        filename = get_existing_filename_with_extension (basefilename, "xml")
         if not filename:
-            filename = get_existing_filename_with_extension (args[0], "mxl")
+            filename = get_existing_filename_with_extension (basefilename, "mxl")
             options.compressed = True
+    if filename and filename.endswith ("mxl"):
+        options.compressed = True
+
     if filename and (filename == "-" or os.path.exists (filename)):
         voices = convert (filename, options)
     else:
-        progress (_ ("Unable to find input file %s") % args[0])
+        progress (_ ("Unable to find input file %s") % basefilename)
 
 if __name__ == '__main__':
     main()