]> git.donarmstrong.com Git - lilypond.git/commitdiff
*** empty log message ***
authorHan-Wen Nienhuys <hanwen@xs4all.nl>
Tue, 9 May 2006 14:11:11 +0000 (14:11 +0000)
committerHan-Wen Nienhuys <hanwen@xs4all.nl>
Tue, 9 May 2006 14:11:11 +0000 (14:11 +0000)
ChangeLog
python/musicexp.py
python/musicxml.py
scripts/musicxml2ly.py

index a652a2247723bc7ca65dc8ab69fe7ba07550ef33..1897fbd9d1eb5371ec534e7c22dbab8b11a32886 100644 (file)
--- a/ChangeLog
+++ b/ChangeLog
@@ -9,6 +9,40 @@
 
 2006-05-09  Han-Wen Nienhuys  <hanwen@lilypond.org>
 
+       * scripts/musicxml2ly.py (musicxml_clef_to_lily): use new
+       Attributes methods
+       (musicxml_time_to_lily): idem
+       (musicxml_key_to_lily): idem
+       (instrument_drumtype_dict): dict for supported drumtypes.
+       (LilyPondVoiceBuilder.__init__): new class: sanely keep track of
+       moments and pending mm rests
+       (musicxml_voice_to_lily_voice): rewrite to use LilyPondVoiceBuilder
+       (musicxml_voice_to_lily_voice): strip KeyChangeEvents for drums.
+       (musicxml_voice_to_lily_voice): add mode change.
+       (option_parser): lxml.etree (http://codespeak.net/lxml/) for more
+       speed and less memory usage (factor 5 to 10).
+       (convert): write -defs.ly  and driver file separately so people
+       can script their own part extraction.
+
+       * python/musicxml.py (minidom_demarshal_node): new function:
+       separate minidom handling.
+       (lxml_demarshal_node): new function: support lxml.etree too.
+       (Xml_node.message): new function: verbose error message, with XML
+       path to offending node.
+       (Attributes.get_measure_length): sane interface to MusicXML attributes.
+       (Part_list.generate_id_instrument_dict): new method: collect
+       instrument names, to be able to set drum_type.
+       (Part.interpret): handle underfull measures
+       (Part.interpret): assign instrument names.
+
+       * python/musicexp.py (Output_printer.close): new method
+       (MusicWrapper.print_ly): new class: support other modes,
+       eg. \drummode
+       (BarCheck.print_ly): new class. Support bar checks, with comments
+       and fancy barchecks. 
+       (NoteEvent.__init__): also set drum_type for drum notes.
+       (MultiMeasureRest.lisp_expression): dump mm rests.
+
        * lily/paper-column-engraver.cc (stop_translation_timestep): set
        line-break-permission if forbidBreak is not set.
        
index 2f54f36b7bf14ebd969251a1e35f15303814d36e..e7b3870425cabd4344322035742174c9b533c6d5 100644 (file)
@@ -94,7 +94,6 @@ class Output_printer:
         self.dump (arg)
     
     def dump (self, str):
-        
         if self._skipspace:
             self._skipspace = False
             self.unformatted_output (str)
@@ -103,6 +102,13 @@ class Output_printer:
             for w in words:
                 self.add_word (w)
 
+
+    def close (self):
+        self.newline ()
+        self._file.close ()
+        self._file = None
+        
+        
 class Duration:
     def __init__ (self):
         self.duration_log = 0
@@ -143,7 +149,7 @@ class Duration:
 
     def get_length (self):
         dot_fact = Rational( (1 << (1 + self.dots))-1,
-                  1 << self.dots)
+                             1 << self.dots)
 
         log = abs (self.duration_log)
         dur = 1 << log
@@ -182,11 +188,10 @@ class Pitch:
         c.octave += c.step / 7
         c.step = c.step  % 7
 
-    
     def lisp_expression (self):
         return '(ly:make-pitch %d %d %d)' % (self.octave,
-                          self.step,
-                          self.alteration)
+                                             self.step,
+                                             self.alteration)
 
     def copy (self):
         p = Pitch ()
@@ -218,6 +223,7 @@ class Pitch:
             str += "," * (-self.octave - 1) 
             
         return str
+    
     def print_ly (self, outputter):
         outputter (self.ly_expression())
     
@@ -249,7 +255,6 @@ class Music:
         name = self.name()
 
         props = self.get_properties ()
-#                props += 'start %f ' % self.start
         
         return "(make-music '%s %s)" % (name,  props)
 
@@ -267,15 +272,15 @@ class Music:
 
         if not text:
             return
-
             
         if text == '\n':
             printer.newline ()
             return
+        
         lines = string.split (text, '\n')
         for l in lines:
             if l:
-                printer.dump ('% ' + l)
+                printer.unformatted_output ('% ' + l)
             printer.newline ()
             
 
@@ -295,6 +300,15 @@ class MusicWrapper (Music):
     def print_ly (self, func):
         self.element.print_ly (func)
 
+class ModeChangingMusicWrapper (MusicWrapper):
+    def __init__ (self):
+        MusicWrapper.__init__ (self)
+        self.mode = 'notemode'
+
+    def print_ly (self, func):
+        func ('\\%s' % self.mode)
+        MusicWrapper.print_ly (self, func)
+
 class TimeScaledMusic (MusicWrapper):
     def print_ly (self, func):
         func ('\\times %d/%d ' %
@@ -429,12 +443,30 @@ class EventChord(NestedMusic):
         else:
             pass
         
-        #        print  'huh', rest_events, note_events, other_events
         for e in other_events:
             e.print_ly (printer)
 
         self.print_comment (printer)
             
+
+class BarCheck (Music):
+    def __init__ (self):
+        Music.__init__ (self)
+        self.bar_number = 0
+        
+    def print_ly (self, printer):
+        if self.bar_number > 0 and (self.bar_number % 10) == 0:
+            printer.dump ("|  \\barNumberCheck #%d " % self.bar_number)
+            printer.newline ()
+        else:
+            printer.dump ("| ")
+            printer.print_verbatim (' %% %d' % self.bar_number)
+            printer.newline ()
+
+    def ly_expression (self):
+        return " | "
+
 class Event(Music):
     pass
 
@@ -477,7 +509,7 @@ class RhythmicEvent(Event):
         
     def get_properties (self):
         return ("'duration %s"
-            % self.duration.lisp_expression ())
+                % self.duration.lisp_expression ())
     
 class RestEvent (RhythmicEvent):
     def ly_expression (self):
@@ -494,15 +526,21 @@ class SkipEvent (RhythmicEvent):
 class NoteEvent(RhythmicEvent):
     def  __init__ (self):
         RhythmicEvent.__init__ (self)
-        self.pitch = Pitch()
+        self.pitch = None
+        self.drum_type = None
         self.cautionary = False
         self.forced_accidental = False
         
     def get_properties (self):
-        return ("'pitch %s\n 'duration %s"
-            % (self.pitch.lisp_expression (),
-             self.duration.lisp_expression ()))
+        str = RhythmicEvent.get_properties ()
+        
+        if self.pitch:
+            str += self.pitch.lisp_expression ()
+        elif self.drum_type:
+            str += "'drum-type '%s" % self.drum_type
 
+        return str
+    
     def pitch_mods (self):
         excl_question = ''
         if self.cautionary:
@@ -513,13 +551,21 @@ class NoteEvent(RhythmicEvent):
         return excl_question
     
     def ly_expression (self):
-        return '%s%s%s' % (self.pitch.ly_expression (),
-                 self.pitch_mods(),
-                 self.duration.ly_expression ())
+        if self.pitch:
+            return '%s%s%s' % (self.pitch.ly_expression (),
+                               self.pitch_mods(),
+                               self.duration.ly_expression ())
+        elif self.drum_type:
+            return '%s%s' (self.drum_type,
+                           self.duration.ly_expression ())
 
     def print_ly (self, printer):
-        self.pitch.print_ly (printer)
-        printer (self.pitch_mods ())  
+        if self.pitch:
+            self.pitch.print_ly (printer)
+            printer (self.pitch_mods ())
+        else:
+            printer (self.drum_type)
+
         self.duration.print_ly (printer)
 
 class KeySignatureChange (Music):
@@ -576,6 +622,28 @@ class ClefChange (Music):
         return clefsetting
 
 
+class MultiMeasureRest(Music):
+
+    def lisp_expression (self):
+        return """
+(make-music
+  'MultiMeasureRestMusicGroup
+  'elements
+  (list (make-music (quote BarCheck))
+        (make-music
+          'EventChord
+          'elements
+          (list (make-music
+                  'MultiMeasureRestEvent
+                  'duration
+                  %s)))
+        (make-music (quote BarCheck))))
+""" % self.duration.lisp_expression ()
+
+    def ly_expression (self):
+        return 'R%s' % self.duration.ly_expression ()
+
+
 def test_pitch ():
     bflat = Pitch()
     bflat.alteration = -1
index e324f702a62885e2ff8f3f80344b657c47eed00e..45b3523f6e0d595c8e19b853a921f046a3beaa9f 100644 (file)
@@ -1,10 +1,5 @@
-import sys
 import new
-import re
-import string
-from rational import Rational
-
-from xml.dom import minidom, Node
+from rational import *
 
 class Xml_node:
     def __init__ (self):
@@ -14,6 +9,9 @@ class Xml_node:
        self._name = 'xml_node'
        self._parent = None
 
+    def get_parent (self):
+        return self._parent
+    
     def is_first (self):
        return self._parent.get_typed_children (self.__class__)[0] == self
 
@@ -31,6 +29,14 @@ class Xml_node:
 
        return ''.join ([c.get_text () for c in self._children])
 
+    def message (self, msg):
+        print msg
+
+        p = self
+        while p:
+            print '  In: <%s %s>' % (p._name, ' '.join (['%s=%s' % item for item in p._original.attrib.items()]))
+            p = p._parent
+        
     def get_typed_children (self, klass):
        return [c for c in self._children if isinstance(c, klass)]
 
@@ -75,7 +81,7 @@ class Music_xml_node (Xml_node):
 
 class Duration (Music_xml_node):
     def get_length (self):
-       dur = string.atoi (self.get_text ()) * Rational (1,4)
+       dur = int (self.get_text ()) * Rational (1,4)
        return dur
 
 class Hash_comment (Music_xml_node):
@@ -90,13 +96,13 @@ class Pitch (Music_xml_node):
        ch = self.get_unique_typed_child (class_dict[u'octave'])
 
        step = ch.get_text ().strip ()
-       return string.atoi (step)
+       return int (step)
 
     def get_alteration (self):
        ch = self.get_maybe_exist_typed_child (class_dict[u'alter'])
        alter = 0
        if ch:
-           alter = string.atoi (ch.get_text ().strip ())
+           alter = int (ch.get_text ().strip ())
        return alter
 
 class Measure_element (Music_xml_node):
@@ -119,6 +125,7 @@ class Attributes (Measure_element):
 
     def set_attributes_from_previous (self, dict):
        self._dict.update (dict)
+        
     def read_self (self):
        for c in self.get_all_children ():
            self._dict[c.get_name()] = c
@@ -126,20 +133,64 @@ class Attributes (Measure_element):
     def get_named_attribute (self, name):
        return self._dict[name]
 
+    def get_measure_length (self):
+        (n,d) = self.get_time_signature ()
+        return Rational (n,d)
+        
+    def get_time_signature (self):
+        "return time sig as a (beat, beat-type) tuple"
+
+        try:
+            mxl = self.get_named_attribute ('time')
+            
+            beats = mxl.get_maybe_exist_named_child ('beats')
+            type = mxl.get_maybe_exist_named_child ('beat-type')
+            return (int (beats.get_text ()),
+                    int (type.get_text ()))
+        except KeyError:
+            print 'error: requested time signature, but time sig unknown'
+            return (4, 4)
+
+    def get_clef_sign (self):
+        mxl = self.get_named_attribute ('clef')
+        sign = mxl.get_maybe_exist_named_child ('sign')
+        if sign:
+            return sign.get_text ()
+        else:
+            print 'clef requested, but unknow'
+            return 'G'
+
+    def get_key_signature (self):
+        "return (fifths, mode) tuple"
+
+        key = self.get_named_attribute ('key')
+        mode_node = self.get_maybe_exist_named_child ('mode')
+        mode = 'major'
+        if mode_node:
+            mode = mode_node.get_text ()
+
+        fifths = int (key.get_maybe_exist_named_child ('fifths').get_text ())
+        return (fifths, mode)
+                
+
 class Note (Measure_element):
+    def __init__ (self):
+        Measure_element.__init__ (self)
+        self.instrument_name = ''
+        
     def get_duration_log (self):
        ch = self.get_maybe_exist_typed_child (class_dict[u'type'])
 
        if ch:
            log = ch.get_text ().strip()
-           return      {'eighth': 3,
-                    'quarter': 2,
-                    'half': 1,
-                    '16th': 4,
-                    '32nd': 5,
+           return {'eighth': 3,
+                    'quarter': 2,
+                    'half': 1,
+                    '16th': 4,
+                    '32nd': 5,
                     'breve': -1,
-                    'long': -2,
-                    'whole': 0} [log]
+                    'long': -2,
+                    'whole': 0} [log]
        else:
            return 0
 
@@ -150,13 +201,37 @@ class Note (Measure_element):
        return self.get_typed_children (class_dict[u'pitch'])
 
 class Part_list (Music_xml_node):
-    pass
-
+    def __init__ (self):
+        Music_xml_node.__init__ (self)
+        self._id_instrument_name_dict = {}
+        
+    def generate_id_instrument_dict (self):
+
+        ## not empty to make sure this happens only once.
+        mapping = {1: 1}
+        for score_part in self.get_named_children ('score-part'):
+            for instr in score_part.get_named_children ('score-instrument'):
+                id = instr.id
+                name = instr.get_named_child ("instrument-name")
+                mapping[id] = name.get_text ()
+
+        self._id_instrument_name_dict = mapping
+
+    def get_instrument (self, id):
+        if not self._id_instrument_name_dict:
+            self.generate_id_instrument_dict()
+
+        try:
+            return self._id_instrument_name_dict[id]
+        except KeyError:
+            print "Opps, couldn't find instrument for ID=", id
+            return "Grand Piano"
+        
 class Measure(Music_xml_node):
     def get_notes (self):
        return self.get_typed_children (class_dict[u'note'])
 
-
+    
 class Musicxml_voice:
     def __init__ (self):
        self._elements = []
@@ -182,38 +257,79 @@ class Part (Music_xml_node):
     def __init__ (self):
        self._voices = []
 
+    def get_part_list (self):
+        n = self
+        while n and n.get_name() != 'score-partwise':
+            n = n._parent
+
+        return n.get_named_child ('part-list')
+        
     def interpret (self):
        """Set durations and starting points."""
-
+        
+        part_list = self.get_part_list ()
+        
        now = Rational (0)
        factor = Rational (1)
-       attr_dict = {}
+       attributes_dict = {}
+        attributes_object = None
        measures = self.get_typed_children (Measure)
-
+        
        for m in measures:
+            measure_start_moment = now
+            measure_position = Rational (0)
            for n in m.get_all_children ():
                dur = Rational (0)
 
-               if n.__class__ == Attributes:
-                   n.set_attributes_from_previous (attr_dict)
+                if n.__class__ == Attributes:
+                   n.set_attributes_from_previous (attributes_dict)
                    n.read_self ()
-                   attr_dict = n._dict.copy ()
-
+                   attributes_dict = n._dict.copy ()
+                    attributes_object = n
+                    
                    factor = Rational (1,
-                                      string.atoi (attr_dict['divisions']
-                                                   .get_text ()))
+                                      int (attributes_dict['divisions'].get_text ()))
                elif (n.get_maybe_exist_typed_child (Duration)
                      and not n.get_maybe_exist_typed_child (Chord)):
+                    
                    mxl_dur = n.get_maybe_exist_typed_child (Duration)
                    dur = mxl_dur.get_length () * factor
+                    
                    if n.get_name() == 'backup':
                        dur = - dur
                    if n.get_maybe_exist_typed_child (Grace):
                        dur = Rational (0)
 
+                    rest = n.get_maybe_exist_typed_child (Rest)
+                   if (rest
+                        and attributes_object
+                        and attributes_object.get_measure_length () == dur):
+
+                        rest._is_whole_measure = True
+                        
+
                n._when = now
+                n._measure_position = measure_position
                n._duration = dur
                now += dur
+                measure_position += dur
+                if n._name == 'note':
+                    instrument = n.get_maybe_exist_named_child ('instrument')
+                    if instrument:
+                        n.instrument_name = part_list.get_instrument (instrument.id)
+
+            if attributes_object:
+                length = attributes_object.get_measure_length ()
+                new_now = measure_start_moment + length
+                
+                if now <> new_now:
+                    problem = 'incomplete'
+                    if now > new_now:
+                        problem = 'overfull'
+                        
+                    m.message ('%s measure? Expected: %s, Difference: %s' % (problem, now, new_now - now))
+
+                now = new_now
 
     def extract_voices (part):
        voices = {}
@@ -269,7 +385,7 @@ class Time_modification(Music_xml_node):
     def get_fraction (self):
        b = self.get_maybe_exist_typed_child (class_dict['actual-notes'])
        a = self.get_maybe_exist_typed_child (class_dict['normal-notes'])
-       return (string.atoi(a.get_text ()), string.atoi (b.get_text ()))
+       return (int(a.get_text ()), int (b.get_text ()))
 
 class Accidental (Music_xml_node):
     def __init__ (self):
@@ -288,7 +404,9 @@ class Slur (Music_xml_node):
 class Beam (Music_xml_node):
     def get_type (self):
        return self.get_text ()
-
+    def is_primary (self):
+        return self.number == "1"
+    
 class Chord (Music_xml_node):
     pass
 
@@ -299,7 +417,12 @@ class Alter (Music_xml_node):
     pass
 
 class Rest (Music_xml_node):
-    pass
+    def __init__ (self):
+        Music_xml_node.__init__ (self)
+        self._is_whole_measure = False
+    def is_whole_measure (self):
+        return self._is_whole_measure
+
 class Mode (Music_xml_node):
     pass
 class Tied (Music_xml_node):
@@ -312,6 +435,11 @@ class Grace (Music_xml_node):
 class Staff (Music_xml_node):
     pass
 
+class Instrument (Music_xml_node):
+    pass
+
+## need this, not all classes are instantiated
+## for every input file.
 class_dict = {
        '#comment': Hash_comment,
        'accidental': Accidental,
@@ -322,6 +450,7 @@ class_dict = {
        'dot': Dot,
        'duration': Duration,
        'grace': Grace,
+        'instrument': Instrument, 
        'mode' : Mode,
        'measure': Measure,
        'notations': Notations,
@@ -345,32 +474,48 @@ def name2class_name (name):
 
     return str (name)
 
-def create_classes (names, dict):
-    for n in names:
-       if dict.has_key (n):
-           continue
-
-       class_name = name2class_name (n)
+def get_class (name):
+    try:
+        return class_dict[name]
+    except KeyError:
+       class_name = name2class_name (name)
        klass = new.classobj (class_name, (Music_xml_node,) , {})
-       dict[n] = klass
+       class_dict[name] = klass
+        return klass
+        
+def lxml_demarshal_node (node):
+    name = node.tag
+
+    if name is None:
+        return None
+    klass = get_class (name)
+    py_node = klass()
+    
+    py_node._original = node
+    py_node._name = name
+    py_node._data = node.text
+    py_node._children = [lxml_demarshal_node (cn) for cn in node.getchildren()]
+    py_node._children = filter (lambda x: x, py_node._children)
+    
+    for c in py_node._children:
+       c._parent = py_node
 
-def element_names (node, dict):
-    dict[node.nodeName] = 1
-    for cn in node.childNodes:
-       element_names (cn, dict)
-    return dict
+    for (k,v) in node.items ():
+        py_node.__dict__[k] = v
+
+    return py_node
 
-def demarshal_node (node):
+def minidom_demarshal_node (node):
     name = node.nodeName
-    klass = class_dict[name]
+
+    klass = get_class (name)
     py_node = klass()
     py_node._name = name
-    py_node._children = [demarshal_node (cn) for cn in node.childNodes]
+    py_node._children = [minidom_demarshal_node (cn) for cn in node.childNodes]
     for c in py_node._children:
        c._parent = py_node
 
     if node.attributes:
-
        for (nm, value) in node.attributes.items():
            py_node.__dict__[nm] = value
 
@@ -381,34 +526,12 @@ def demarshal_node (node):
     py_node._original = node
     return py_node
 
-def strip_white_space (node):
-    node._children = \
-    [c for c in node._children
-     if not (c._original.nodeType == Node.TEXT_NODE and
-            re.match (r'^\s*$', c._data))]
-
-    for c in node._children:
-       strip_white_space (c)
-
-def create_tree (name):
-    doc = minidom.parse(name)
-    node = doc.documentElement
-    names = element_names (node, {}).keys()
-    create_classes (names, class_dict)
-
-    return demarshal_node (node)
-
-def test_musicxml (tree):
-    m = tree._children[-2]
-    print m
-
-def read_musicxml (name):
-    tree = create_tree (name)
-    strip_white_space (tree)
-    return tree
 
 if __name__  == '__main__':
-    tree = read_musicxml ('BeetAnGeSample.xml')
-    test_musicxml (tree)
-
-
+        import lxml.etree
+        
+        tree = lxml.etree.parse ('beethoven.xml')
+        mxl_tree = lxml_demarshal_node (tree.getroot ())
+        ks = class_dict.keys()
+        ks.sort()
+        print '\n'.join (ks)
index 71b41d9dc35e9359d1a1c333a750a20ccafddcbc..f1125b15d6f1d7c4c4c30a8b96405c97e14513a4 100644 (file)
@@ -6,6 +6,9 @@ import re
 import os
 import string
 from gettext import gettext as _
+import musicxml
+
+
 
 
 datadir = '@local_lilypond_datadir@'
@@ -31,10 +34,10 @@ for prefix_component in ['share', 'lib']:
     sys.path.insert (0, datadir)
 
 
+import lilylib as ly
 
 import musicxml
 import musicexp
-import lilylib as ly
 
 from rational import Rational
 
@@ -100,41 +103,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' : (6,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
@@ -166,19 +159,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 +192,14 @@ def musicxml_spanner_to_lily_event (mxl_event):
 
     return ev
 
+instrument_drumtype_dict = {
+    'Acoustic Snare Drum': 'acousticsnare',
+    'Side Stick': 'sidestick',
+    'Open Triangle': 'opentriangle',
+    'Tambourine': 'tambourine',
+    
+}
+
 def musicxml_note_to_lily_main_event (n):
     pitch  = None
     duration = None
@@ -226,59 +218,142 @@ 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 ()
+        event.drum_type = instrument_drumtype_dict[n.instrument_name]
+        
+    
+    if not event:
+        n.message ("could not 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):
+            raise NegativeSkip(current_end, moment)
+
+        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):
+        if (self.elements
+            and isinstance (self.elements[-1], musicexp.EventChord)
+            and self.begin_moment == starting_at):
+            return self.elements[-1]
+        else:
+            self.jumpto (starting_at)
+            return None
+    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)
+
+        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):
+                voice_builder.add_bar_check (int (n.get_parent ().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):
+            num = int (n.get_parent ().number)
+            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()
 
-                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)
+        voice_builder.add_music (ev_chord, n._duration)
         
         notations = n.get_maybe_exist_typed_child (musicxml.Notations)
         tuplet_event = None
@@ -307,7 +382,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 +397,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 ())
+    
+    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 +446,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
@@ -390,8 +485,8 @@ under certain conditions.  Invoke as `lilypond --warranty' for more
 information.
 
 Copyright (c) 2005--2006 by
- Han-Wen Nienhuys <hanwen@xs4all.nl> and
- Jan Nieuwenhuizen <janneke@gnu.org>
   Han-Wen Nienhuys <hanwen@xs4all.nl> and
   Jan Nieuwenhuizen <janneke@gnu.org>
 """,
 
                  description  =
@@ -399,16 +494,23 @@ Copyright (c) 2005--2006 by
 """
                  )
     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')
+                  metavar = 'FILE',
+                  action="store",
+                  default=None,
+                  type='string',
+                  dest='output_name',
+                  help='set output file')
 
     p.add_option_group  ('', description = '''Report bugs via http://post.gmane.org/post.php?group=gmane.comp.gnu.lilypond.bugs
 ''')
@@ -420,11 +522,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 +584,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)
+    
+    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)
-    print_voice_definitions (printer,  voices)
+    printer.dump (r'\include "%s"' % defs_ly_name)
     print_score_setup (printer, part_list, voices)
     printer.newline ()
+
     return voices
 
 
@@ -528,7 +659,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()