]> git.donarmstrong.com Git - lilypond.git/blobdiff - scripts/musicxml2ly.py
use sed-atfiles to put relocation handling in python scripts.
[lilypond.git] / scripts / musicxml2ly.py
index 71b41d9dc35e9359d1a1c333a750a20ccafddcbc..7a8e8c407165cc310fe7f54aaa3065b92537b02b 100644 (file)
@@ -1,4 +1,4 @@
-#!@PYTHON@
+#!@TARGET_PYTHON@
 
 import optparse
 import sys
@@ -7,34 +7,14 @@ import os
 import string
 from gettext import gettext as _
 
+"""
+@relocate-preamble@
+"""
 
-datadir = '@local_lilypond_datadir@'
-if not os.path.isdir (datadir):
-    datadir = '@lilypond_datadir@'
-if os.environ.has_key ('LILYPONDPREFIX'):
-    datadir = os.environ['LILYPONDPREFIX']
-    while datadir[-1] == os.sep:
-        datadir = datadir[:-1]
-
-if os.path.exists (os.path.join (datadir, 'share/lilypond/@TOPLEVEL_VERSION@/')):
-    datadir = os.path.join (datadir, 'share/lilypond/@TOPLEVEL_VERSION@/')
-elif os.path.exists (os.path.join (datadir, 'share/lilypond/current/')):
-    datadir = os.path.join (datadir, 'share/lilypond/current/')
-
-sys.path.insert (0, os.path.join (datadir, 'python'))
-
-# dynamic relocation, for GUB binaries.
-bindir = os.path.split (sys.argv[0])[0]
-
-for prefix_component in ['share', 'lib']:
-    datadir = os.path.abspath (bindir + '/../%s/lilypond/current/python/' % prefix_component)
-    sys.path.insert (0, datadir)
-
-
+import lilylib as ly
 
 import musicxml
 import musicexp
-import lilylib as ly
 
 from rational import Rational
 
@@ -100,41 +80,31 @@ def group_tuplets (music_list, events):
     return new_list
 
 
-def musicxml_clef_to_lily (mxl):
-    sign = mxl.get_maybe_exist_named_child ('sign')
+def musicxml_clef_to_lily (attributes):
     change = musicexp.ClefChange ()
-    if sign:
-        change.type = sign.get_text ()
+    change.type = attributes.get_clef_sign ()
     return change
-
     
-def musicxml_time_to_lily (mxl):
-    beats = mxl.get_maybe_exist_named_child ('beats')
-    type = mxl.get_maybe_exist_named_child ('beat-type')
+def musicxml_time_to_lily (attributes):
+    (beats, type) = attributes.get_time_signature ()
+
     change = musicexp.TimeSignatureChange()
-    change.fraction = (string.atoi(beats.get_text ()),
-             string.atoi(type.get_text ()))
+    change.fraction = (beats, type)
     
     return change
 
-def musicxml_key_to_lily (mxl):
+def musicxml_key_to_lily (attributes):
     start_pitch  = musicexp.Pitch ()
+    (fifths, mode) = attributes.get_key_signature () 
     try:
-        mode = mxl.get_maybe_exist_named_child ('mode')
-        if mode:
-            mode = mode.get_text ()
-        else:
-            mode = 'major'
-            
-        (n,a) = { 'major' : (0,0),
-             'minor' : (6,0),
+        (n,a) = {
+            'major' : (0,0),
+            'minor' : (5,0),
             }[mode]
         start_pitch.step = n
         start_pitch.alteration = a
     except  KeyError:
         print 'unknown mode', mode
-        
-    fifths = string.atoi (mxl.get_maybe_exist_named_child ('fifths').get_text ())
 
     fifth = musicexp.Pitch()
     fifth.step = 4
@@ -143,7 +113,6 @@ def musicxml_key_to_lily (mxl):
         fifth.step *= -1
         fifth.normalize ()
     
-    start_pitch = musicexp.Pitch()
     for x in range (fifths):
         start_pitch = start_pitch.transposed (fifth)
 
@@ -166,19 +135,10 @@ def musicxml_attributes_to_lily (attrs):
 
         ## ugh: you get clefs spread over staves for piano
         if childs:
-            elts.append (func (childs[0]))
+            elts.append (func (attrs))
     
     return elts
 
-def create_skip_music (duration):
-    skip = musicexp.SkipEvent()
-    skip.duration.duration_log = 0
-    skip.duration.factor = duration
-
-    evc = musicexp.EventChord ()
-    evc.append (skip)
-    return evc
-
 spanner_event_dict = {
     'slur' : musicexp.SlurEvent,
     'beam' : musicexp.BeamEvent,
@@ -208,6 +168,15 @@ def musicxml_spanner_to_lily_event (mxl_event):
 
     return ev
 
+instrument_drumtype_dict = {
+    'Acoustic Snare Drum': 'acousticsnare',
+    'Side Stick': 'sidestick',
+    'Open Triangle': 'opentriangle',
+    'Mute Triangle': 'mutetriangle',
+    'Tambourine': 'tambourine',
+    
+}
+
 def musicxml_note_to_lily_main_event (n):
     pitch  = None
     duration = None
@@ -226,58 +195,159 @@ def musicxml_note_to_lily_main_event (n):
         
     elif n.get_maybe_exist_typed_child (musicxml.Rest):
         event = musicexp.RestEvent()
+    elif n.instrument_name:
+        event = musicexp.NoteEvent ()
+        try:
+            event.drum_type = instrument_drumtype_dict[n.instrument_name]
+        except KeyError:
+            n.message ("drum %s type unknow, please add to instrument_drumtype_dict" % n.instrument_name)
+            event.drum_type = 'acousticsnare'
+    
+    if not event:
+        n.message ("cannot find suitable event")
 
     event.duration = musicxml_duration_to_lily (n)
     return event
 
-def musicxml_voice_to_lily_voice (voice):
-    ly_voice = []
-    ly_now = Rational (0)
-    pending_skip = Rational (0) 
 
+## todo
+class NegativeSkip:
+    def __init__ (self, here, dest):
+        self.here = here
+        self.dest = dest
+
+class LilyPondVoiceBuilder:
+    def __init__ (self):
+        self.elements = []
+        self.end_moment = Rational (0)
+        self.begin_moment = Rational (0)
+        self.pending_multibar = Rational (0)
+
+    def _insert_multibar (self):
+        r = musicexp.MultiMeasureRest ()
+        r.duration = musicexp.Duration()
+        r.duration.duration_log = 0
+        r.duration.factor = self.pending_multibar
+        self.elements.append (r)
+        self.begin_moment = self.end_moment
+        self.end_moment = self.begin_moment + self.pending_multibar
+        self.pending_multibar = Rational (0)
+        
+    def add_multibar_rest (self, duration):
+        self.pending_multibar += duration
+        
+        
+    def add_music (self, music, duration):
+        assert isinstance (music, musicexp.Music)
+        if self.pending_multibar > Rational (0):
+            self._insert_multibar ()
+
+        self.elements.append (music)
+        self.begin_moment = self.end_moment
+        self.end_moment = self.begin_moment + duration 
+
+    def add_bar_check (self, number):
+        b = musicexp.BarCheck ()
+        b.bar_number = number
+        self.add_music (b, Rational (0))
+
+    def jumpto (self, moment):
+        current_end = self.end_moment + self.pending_multibar
+        diff = moment - current_end
+        
+        if diff < Rational (0):
+            print 'Negative skip', diff
+            diff = Rational (0)
+
+        if diff > Rational (0):
+            skip = musicexp.SkipEvent()
+            skip.duration.duration_log = 0
+            skip.duration.factor = diff
+
+            evc = musicexp.EventChord ()
+            evc.elements.append (skip)
+            self.add_music (evc, diff)
+                
+    def last_event_chord (self, starting_at):
+
+        value = None
+        if (self.elements
+            and isinstance (self.elements[-1], musicexp.EventChord)
+            and self.begin_moment == starting_at):
+            value = self.elements[-1]
+        else:
+            self.jumpto (starting_at)
+            value = None
+
+        return value
+        
+    def correct_negative_skip (self, goto):
+        self.end_moment = goto
+        self.begin_moment = goto
+        evc = musicexp.EventChord ()
+        self.elements.append (evc)
+        
+def musicxml_voice_to_lily_voice (voice):
     tuplet_events = []
+    modes_found = {}
+
+    voice_builder = LilyPondVoiceBuilder()
 
     for n in voice._elements:
         if n.get_name () == 'forward':
             continue
-        
-        if isinstance (n, musicxml.Attributes):
-            ly_now += pending_skip
-            pending_skip = Rational (0)
+
+        if not n.get_maybe_exist_named_child ('chord'):
+            try:
+                voice_builder.jumpto (n._when)
+            except NegativeSkip, neg:
+                voice_builder.correct_negative_skip (n._when)
+                n.message ("Negative skip? from %s to %s, diff %s" % (neg.here, neg.dest, neg.dest - neg.here))
             
-            ly_voice.extend (musicxml_attributes_to_lily (n))
+        if isinstance (n, musicxml.Attributes):
+            if n.is_first () and n._measure_position == Rational (0):
+                try:
+                    number = int (n.get_parent ().number)
+                except ValueError:
+                    number = 0
+                
+                voice_builder.add_bar_check (number)
+            for a in musicxml_attributes_to_lily (n):
+                voice_builder.add_music (a, Rational (0))
             continue
-        
+
         if not n.__class__.__name__ == 'Note':
             print 'not a Note or Attributes?', n
             continue
 
-        if n.is_first () and ly_voice:
-            ly_voice[-1].comment += '\n'
-        
+        rest = n.get_maybe_exist_typed_child (musicxml.Rest)
+        if (rest
+            and rest.is_whole_measure ()):
+
+            voice_builder.add_multibar_rest (n._duration)
+            continue
 
+        if n.is_first () and n._measure_position == Rational (0):
+            try: 
+                num = int (n.get_parent ().number)
+            except ValueError:
+                num = 0
+            voice_builder.add_bar_check (num)
+        
         main_event = musicxml_note_to_lily_main_event (n)
 
-        ev_chord = None
-        if None ==  n.get_maybe_exist_typed_child (musicxml.Chord):
-            ly_now += pending_skip
-            pending_skip = main_event.get_length ()
+        try:
+            if main_event.drum_type:
+                modes_found['drummode'] = True
+        except AttributeError:
+            pass
 
-            if ly_now <> n._when:
-                diff = n._when - ly_now
 
-                if diff < Rational (0):
-                    print 'huh: negative skip', n._when, ly_now, n._duration
-                    diff = Rational (1,314159265)
+        ev_chord = voice_builder.last_event_chord (n._when)
+        if not ev_chord: 
+            ev_chord = musicexp.EventChord()
+            voice_builder.add_music (ev_chord, n._duration)
 
-                ly_voice.append (create_skip_music (diff))
-                ly_now = n._when
-                
-            ly_voice.append (musicexp.EventChord())
-        else:
-            pass
-        
-        ev_chord = ly_voice[-1]
         ev_chord.append (main_event)
         
         notations = n.get_maybe_exist_typed_child (musicxml.Notations)
@@ -307,7 +377,8 @@ def musicxml_voice_to_lily_voice (voice):
                 ev_chord.append (musicexp.TieEvent ())
 
         mxl_beams = [b for b in n.get_named_children ('beam')
-              if b.get_type () in ('begin', 'end')] 
+                     if (b.get_type () in ('begin', 'end')
+                         and b.is_primary ())] 
         if mxl_beams:
             beam_ev = musicxml_spanner_to_lily_event (mxl_beams[0])
             if beam_ev:
@@ -321,17 +392,38 @@ def musicxml_voice_to_lily_voice (voice):
                 
             tuplet_events.append ((ev_chord, tuplet_event, frac))
 
-    ly_voice = group_tuplets (ly_voice, tuplet_events)
+    ## force trailing mm rests to be written out.   
+    voice_builder.add_music (musicexp.EventChord (), Rational (0))
+    
+    ly_voice = group_tuplets (voice_builder.elements, tuplet_events)
 
     seq_music = musicexp.SequentialMusic()
+
+    if 'drummode' in modes_found.keys ():
+        ## \key <pitch> barfs in drummode.
+        ly_voice = [e for e in ly_voice
+                    if not isinstance(e, musicexp.KeySignatureChange)]
     
     seq_music.elements = ly_voice
-    return seq_music
+
+    
+    
+    if len (modes_found) > 1:
+       print 'Too many modes found', modes_found.keys ()
+
+    return_value = seq_music
+    for mode in modes_found.keys ():
+        v = musicexp.ModeChangingMusicWrapper()
+        v.element = return_value
+        v.mode = mode
+        return_value = v
+    
+    return return_value
 
 
 def musicxml_id_to_lily (id):
     digits = ['one', 'two', 'three', 'four', 'five', 'six', 'seven', 'eight',
-         'nine', 'ten']
+              'nine', 'ten']
     
     for dig in digits:
         d = digits.index (dig) + 1
@@ -349,12 +441,10 @@ def musicxml_pitch_to_lily (mxl_pitch):
     p.octave = mxl_pitch.get_octave () - 4
     return p
 
-
-
 def voices_in_part (part):
     """Return a Name -> Voice dictionary for PART"""
     part.interpret ()
-    part.extract_voices ()                
+    part.extract_voices ()
     voice_dict = part.get_voices ()
 
     return voice_dict
@@ -381,37 +471,41 @@ def get_all_voices (parts):
 
 
 def option_parser ():
-    p = ly.get_option_parser(usage='musicxml2ly FILE.xml',
-                 version = """%prog (LilyPond) @TOPLEVEL_VERSION@
-
-This program is free software.  It is covered by the GNU General Public
+    p = ly.get_option_parser(usage=_ ("musicxml2ly FILE.xml"),
+                             version=('''%prog (LilyPond) @TOPLEVEL_VERSION@\n\n'''
+                                      +
+_ ("""This program is free software.  It is covered by the GNU General Public
 License and you are welcome to change it and/or distribute copies of it
-under certain conditions.  Invoke as `lilypond --warranty' for more
-information.
-
+under certain conditions.  Invoke as `%s --warranty' for more
+information.""") % 'lilypond'
++ """
 Copyright (c) 2005--2006 by
- Han-Wen Nienhuys <hanwen@xs4all.nl> and
- Jan Nieuwenhuizen <janneke@gnu.org>
-""",
-
-                 description  =
-                 """Convert MusicXML file to LilyPond input.
-"""
-                 )
+    Han-Wen Nienhuys <hanwen@xs4all.nl> and
+    Jan Nieuwenhuizen <janneke@gnu.org>
+"""),
+                             description=_ ("Convert %s to LilyPond input.") % 'MusicXML' + "\n")
     p.add_option ('-v', '--verbose',
-           action = "store_true",
-           dest = 'verbose',
-           help = 'be verbose')
+                  action="store_true",
+                  dest='verbose',
+                  help=_ ("be verbose"))
+
+    p.add_option ('', '--lxml',
+                  action="store_true",
+                  default=False,
+                  dest="use_lxml",
+                  help=_ ("Use lxml.etree; uses less memory and cpu time."))
+    
     p.add_option ('-o', '--output',
-           metavar = 'FILE',
-           action = "store",
-           default = None,
-           type = 'string',
-           dest = 'output',
-           help = 'set output file')
-
-    p.add_option_group  ('', description = '''Report bugs via http://post.gmane.org/post.php?group=gmane.comp.gnu.lilypond.bugs
-''')
+                  metavar=_ ("FILE"),
+                  action="store",
+                  default=None,
+                  type='string',
+                  dest='output_name',
+                  help=_ ("set output filename to FILE"))
+    p.add_option_group ('bugs',
+                        description=(_ ("Report bugs via")
+                                     + ''' http://post.gmane.org/post.php'''
+                                     '''?group=gmane.comp.gnu.lilypond.bugs\n'''))
     return p
 
 def music_xml_voice_name_to_lily_name (part, name):
@@ -420,11 +514,14 @@ def music_xml_voice_name_to_lily_name (part, name):
 
 def print_voice_definitions (printer, voices):
     for (part, nv_dict) in voices.items():
+        
         for (name, (voice, mxlvoice)) in nv_dict.items ():
             k = music_xml_voice_name_to_lily_name (part, name)
             printer.dump ('%s = ' % k)
             voice.print_ly (printer)
             printer.newline()
+
+            
 def uniq_list (l):
     return dict ([(elt,1) for elt in l]).keys ()
     
@@ -479,44 +576,70 @@ def print_score_setup (printer, part_list, voices):
             printer ('>>')
             printer.newline ()
             
-
     printer ('>>')
     printer.newline ()
 
-                
-
 def print_ly_preamble (printer, filename):
     printer.dump_version ()
     printer.print_verbatim ('%% converted from %s\n' % filename)
 
-def convert (filename, output_name):
-    printer = musicexp.Output_printer()
-    progress ("Reading MusicXML...")
+def read_musicxml (filename, use_lxml):
+    if use_lxml:
+        import lxml.etree
+        
+        tree = lxml.etree.parse (filename)
+        mxl_tree = musicxml.lxml_demarshal_node (tree.getroot ())
+        return mxl_tree
+    else:
+        from xml.dom import minidom, Node
+        
+        doc = minidom.parse(filename)
+        node = doc.documentElement
+        return musicxml.minidom_demarshal_node (node)
+
+    return None
+
+
+def convert (filename, options):
+    progress ("Reading MusicXML from %s ..." % filename)
     
-    tree = musicxml.read_musicxml (filename)
-    parts = tree.get_typed_children (musicxml.Part)
-    voices = get_all_voices (parts)
+    tree = read_musicxml (filename, options.use_lxml)
 
     part_list = []
+    id_instrument_map = {}
     if tree.get_maybe_exist_typed_child (musicxml.Part_list):
-        pl = tree.get_maybe_exist_typed_child (musicxml.Part_list)
-        part_list = pl.get_named_children ("score-part")
+        mxl_pl = tree.get_maybe_exist_typed_child (musicxml.Part_list)
+        part_list = mxl_pl.get_named_children ("score-part")
         
-    if not output_name:
-        output_name = os.path.basename (filename)
-        output_name = os.path.splitext (output_name)[0] + '.ly'
+    parts = tree.get_typed_children (musicxml.Part)
+    voices = get_all_voices (parts)
 
-        
-    if output_name:
-        progress ("Output to `%s'" % output_name)
-        printer.set_file (open (output_name, 'w'))
-    
-    progress ("Printing as .ly...")
+    if not options.output_name:
+        options.output_name = os.path.basename (filename) 
+        options.output_name = os.path.splitext (options.output_name)[0]
+
+
+    defs_ly_name = options.output_name + '-defs.ly'
+    driver_ly_name = options.output_name + '.ly'
+
+    printer = musicexp.Output_printer()
+    progress ("Output to `%s'" % defs_ly_name)
+    printer.set_file (open (defs_ly_name, 'w'))
 
     print_ly_preamble (printer, filename)
-    print_voice_definitions (printer,  voices)
+    print_voice_definitions (printer, voices)
+    
+    printer.close ()
+    
+    
+    progress ("Output to `%s'" % driver_ly_name)
+    printer = musicexp.Output_printer()
+    printer.set_file (open (driver_ly_name, 'w'))
+    print_ly_preamble (printer, filename)
+    printer.dump (r'\include "%s"' % defs_ly_name)
     print_score_setup (printer, part_list, voices)
     printer.newline ()
+
     return voices
 
 
@@ -528,7 +651,7 @@ def main ():
         opt_parser.print_usage()
         sys.exit (2)
 
-    voices = convert (args[0], options.output)
+    voices = convert (args[0], options)
 
 if __name__ == '__main__':
     main()