]> git.donarmstrong.com Git - lilypond.git/blobdiff - python/musicxml.py
Merge branch 'issue4614' into HEAD
[lilypond.git] / python / musicxml.py
index 0e1cb4ae6d087da34db85133daa7d453af274044..6eb3b45aba21ca1aa89a0fc7fdf96373b649d4aa 100644 (file)
@@ -9,9 +9,6 @@ import lilylib as ly
 
 _ = ly._
 
-def error (str):
-    ly.stderr_write ((_ ("error: %s") % str) + "\n")
-
 
 def escape_ly_output_string (input_string):
     return_string = input_string
@@ -37,43 +34,64 @@ def musicxml_duration_to_log (dur):
 
 
 
+def interpret_alter_element (alter_elm):
+    alter = 0
+    if alter_elm:
+        val = eval(alter_elm.get_text ())
+        if type (val) in (int, float):
+            alter = val
+    return alter
+
+
 class Xml_node:
     def __init__ (self):
-       self._children = []
-       self._data = None
-       self._original = None
-       self._name = 'xml_node'
-       self._parent = None
+        self._children = []
+        self._data = None
+        self._original = None
+        self._name = 'xml_node'
+        self._parent = None
         self._attribute_dict = {}
-        
+
     def get_parent (self):
         return self._parent
-    
+
     def is_first (self):
-       return self._parent.get_typed_children (self.__class__)[0] == self
+        return self._parent.get_typed_children (self.__class__)[0] == self
 
     def original (self):
-       return self._original 
+        return self._original
     def get_name (self):
-       return self._name
+        return self._name
 
     def get_text (self):
-       if self._data:
-           return self._data
+        if self._data:
+            return self._data
 
-       if not self._children:
-           return ''
+        if not self._children:
+            return ''
 
-       return ''.join ([c.get_text () for c in self._children])
+        return ''.join ([c.get_text () for c in self._children])
 
     def message (self, msg):
-        ly.stderr_write (msg+'\n')
+        ly.warning (msg)
 
         p = self
         while p:
-            sys.stderr.write ('  In: <%s %s>\n' % (p._name, ' '.join (['%s=%s' % item for item in p._attribute_dict.items ()])))
+            ly.progress ('  In: <%s %s>\n' % (p._name, ' '.join (['%s=%s' % item for item in p._attribute_dict.items ()])))
             p = p.get_parent ()
-        
+
+    def dump (self, indent = ''):
+        ly.debug_output ('%s<%s%s>' % (indent, self._name, ''.join ([' %s=%s' % item for item in self._attribute_dict.items ()])))
+        non_text_children = [c for c in self._children if not isinstance (c, Hash_text)]
+        if non_text_children:
+            ly.debug_output ('\n')
+        for c in self._children:
+            c.dump (indent + "    ")
+        if non_text_children:
+            ly.debug_output (indent)
+        ly.debug_output ('</%s>\n' % self._name)
+
+
     def get_typed_children (self, klass):
         if not klass:
             return []
@@ -81,36 +99,36 @@ class Xml_node:
             return [c for c in self._children if isinstance(c, klass)]
 
     def get_named_children (self, nm):
-       return self.get_typed_children (get_class (nm))
+        return self.get_typed_children (get_class (nm))
 
     def get_named_child (self, nm):
-       return self.get_maybe_exist_named_child (nm)
+        return self.get_maybe_exist_named_child (nm)
 
     def get_children (self, predicate):
-       return [c for c in self._children if predicate(c)]
+        return [c for c in self._children if predicate(c)]
 
     def get_all_children (self):
-       return self._children
+        return self._children
 
     def get_maybe_exist_named_child (self, name):
-       return self.get_maybe_exist_typed_child (get_class (name))
+        return self.get_maybe_exist_typed_child (get_class (name))
 
     def get_maybe_exist_typed_child (self, klass):
-       cn = self.get_typed_children (klass)
-       if len (cn)==0:
-           return None
-       elif len (cn) == 1:
-           return cn[0]
-       else:
-           raise "More than 1 child", klass
+        cn = self.get_typed_children (klass)
+        if len (cn)==0:
+            return None
+        elif len (cn) == 1:
+            return cn[0]
+        else:
+            raise "More than 1 child", klass
 
     def get_unique_typed_child (self, klass):
-       cn = self.get_typed_children(klass)
-       if len (cn) <> 1:
-           sys.stderr.write (self.__dict__ + '\n')
-           raise 'Child is not unique for', (klass, 'found', cn)
+        cn = self.get_typed_children(klass)
+        if len (cn) <> 1:
+            ly.error (self.__dict__)
+            raise 'Child is not unique for', (klass, 'found', cn)
 
-       return cn[0]
+        return cn[0]
 
     def get_named_child_value_number (self, name, default):
         n = self.get_maybe_exist_named_child (name)
@@ -122,9 +140,9 @@ class Xml_node:
 
 class Music_xml_node (Xml_node):
     def __init__ (self):
-       Xml_node.__init__ (self)
-       self.duration = Rational (0)
-       self.start = Rational (0)
+        Xml_node.__init__ (self)
+        self.duration = Rational (0)
+        self.start = Rational (0)
 
 class Work (Xml_node):
     def get_work_information (self, tag):
@@ -133,7 +151,7 @@ class Work (Xml_node):
             return wt.get_text ()
         else:
             return ''
-      
+
     def get_work_title (self):
         return self.get_work_information ('work-title')
     def get_work_number (self):
@@ -149,6 +167,14 @@ class Identification (Xml_node):
           ret.append (r.get_text ())
         return string.join (ret, "\n")
 
+    # get contents of the source-element (usually used for publishing information). (These contents are saved in a custom variable named "source" in the header of the .ly file.)
+    def get_source (self):
+        source = self.get_named_children ('source')
+        ret = []
+        for r in source:
+          ret.append (r.get_text ())
+        return string.join (ret, "\n")
+
     def get_creator (self, type):
         creators = self.get_named_children ('creator')
         # return the first creator tag that has the particular type
@@ -177,7 +203,7 @@ class Identification (Xml_node):
             return v
         v = self.get_creator ('poet')
         return v
-    
+
     def get_encoding_information (self, type):
         enc = self.get_named_children ('encoding')
         if enc:
@@ -186,7 +212,7 @@ class Identification (Xml_node):
                 return children[0].get_text ()
         else:
             return None
-      
+
     def get_encoding_software (self):
         return self.get_encoding_information ('software')
     def get_encoding_date (self):
@@ -195,7 +221,7 @@ class Identification (Xml_node):
         return self.get_encoding_information ('encoder')
     def get_encoding_description (self):
         return self.get_encoding_information ('encoding-description')
-    
+
     def get_encoding_software_list (self):
         enc = self.get_named_children ('encoding')
         software = []
@@ -211,112 +237,146 @@ class Identification (Xml_node):
             misc_fields = m.get_named_children ('miscellaneous-field')
             for mf in misc_fields:
                 if hasattr (mf, 'name') and mf.name == 'description':
-                    return mf.get_text () 
+                    return mf.get_text ()
         return None
 
-
-
 class Duration (Music_xml_node):
     def get_length (self):
-       dur = int (self.get_text ()) * Rational (1,4)
-       return dur
+        dur = int (self.get_text ()) * Rational (1,4)
+        return dur
 
 class Hash_comment (Music_xml_node):
     pass
 class Hash_text (Music_xml_node):
-    pass
+    def dump (self, indent = ''):
+        ly.debug_output ('%s' % string.strip (self._data))
 
 class Pitch (Music_xml_node):
     def get_step (self):
-       ch = self.get_unique_typed_child (get_class (u'step'))
-       step = ch.get_text ().strip ()
-       return step
+        ch = self.get_unique_typed_child (get_class (u'step'))
+        step = ch.get_text ().strip ()
+        return step
     def get_octave (self):
-       ch = self.get_unique_typed_child (get_class (u'octave'))
-
-       step = ch.get_text ().strip ()
-       return int (step)
+        ch = self.get_unique_typed_child (get_class (u'octave'))
+        octave = ch.get_text ().strip ()
+        return int (octave)
 
     def get_alteration (self):
-       ch = self.get_maybe_exist_typed_child (get_class (u'alter'))
-       alter = 0
-       if ch:
-           alter = int (ch.get_text ().strip ())
-       return alter
+        ch = self.get_maybe_exist_typed_child (get_class (u'alter'))
+        return interpret_alter_element (ch)
 
 class Unpitched (Music_xml_node):
     def get_step (self):
-       ch = self.get_unique_typed_child (get_class (u'display-step'))
-       step = ch.get_text ().strip ()
-       return step
+        ch = self.get_unique_typed_child (get_class (u'display-step'))
+        step = ch.get_text ().strip ()
+        return step
 
     def get_octave (self):
-       ch = self.get_unique_typed_child (get_class (u'display-octave'))
+        ch = self.get_unique_typed_child (get_class (u'display-octave'))
 
-       if ch:
-           octave = ch.get_text ().strip ()
-           return int (octave)
-       else:
-           return None
+        if ch:
+            octave = ch.get_text ().strip ()
+            return int (octave)
+        else:
+            return None
 
 class Measure_element (Music_xml_node):
     def get_voice_id (self):
-       voice_id = self.get_maybe_exist_named_child ('voice')
-       if voice_id:
-           return voice_id.get_text ()
-       else:
-           return None
+        voice_id = self.get_maybe_exist_named_child ('voice')
+        if voice_id:
+            return voice_id.get_text ()
+        else:
+            return None
 
     def is_first (self):
         # Look at all measure elements (previously we had self.__class__, which
         # only looked at objects of the same type!
-       cn = self._parent.get_typed_children (Measure_element)
+        cn = self._parent.get_typed_children (Measure_element)
         # But only look at the correct voice; But include Attributes, too, which
         # are not tied to any particular voice
-       cn = [c for c in cn if (c.get_voice_id () == self.get_voice_id ()) or isinstance (c, Attributes)]
-       return cn[0] == self
+        cn = [c for c in cn if (c.get_voice_id () == self.get_voice_id ()) or isinstance (c, Attributes)]
+        return cn[0] == self
 
 class Attributes (Measure_element):
     def __init__ (self):
-       Measure_element.__init__ (self)
-       self._dict = {}
+        Measure_element.__init__ (self)
+        self._dict = {}
         self._original_tag = None
+        self._time_signature_cache = None
 
     def is_first (self):
-       cn = self._parent.get_typed_children (self.__class__)
+        cn = self._parent.get_typed_children (self.__class__)
         if self._original_tag:
             return cn[0] == self._original_tag
         else:
             return cn[0] == self
-    
+
     def set_attributes_from_previous (self, dict):
-       self._dict.update (dict)
-        
+        self._dict.update (dict)
+
     def read_self (self):
-       for c in self.get_all_children ():
-           self._dict[c.get_name()] = c
+        for c in self.get_all_children ():
+            self._dict[c.get_name()] = c
 
     def get_named_attribute (self, name):
-       return self._dict.get (name)
+        return self._dict.get (name)
+
+    def single_time_sig_to_fraction (self, sig):
+        if len (sig) < 2:
+            return 0
+        n = 0
+        for i in sig[0:-1]:
+          n += i
+        return Rational (n, sig[-1])
 
     def get_measure_length (self):
-        (n,d) = self.get_time_signature ()
-        return Rational (n,d)
-        
+        sig = self.get_time_signature ()
+        if not sig or len (sig) == 0:
+            return 1
+        if isinstance (sig[0], list):
+            # Complex compound time signature
+            l = 0
+            for i in sig:
+                l += self.single_time_sig_to_fraction (i)
+            return l
+        else:
+           # Simple (maybe compound) time signature of the form (beat, ..., type)
+            return self.single_time_sig_to_fraction (sig)
+        return 0
+
     def get_time_signature (self):
-        "return time sig as a (beat, beat-type) tuple"
+        "Return time sig as a (beat, beat-type) tuple. For compound signatures,"
+        "return either (beat, beat,..., beat-type) or ((beat,..., type), "
+        "(beat,..., type), ...)."
+        if self._time_signature_cache:
+            return self._time_signature_cache
 
         try:
             mxl = self.get_named_attribute ('time')
-            if mxl:
-                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 ()))
-            else:
+            if not mxl:
+                return None
+
+            if mxl.get_maybe_exist_named_child ('senza-misura'):
+                # TODO: Handle pieces without a time signature!
+                ly.warning (_ ("Senza-misura time signatures are not yet supported!"))
                 return (4, 4)
-        except KeyError:
-            error (_ ("requested time signature, but time sig is unknown"))
+            else:
+                signature = []
+                current_sig = []
+                for i in mxl.get_all_children ():
+                    if isinstance (i, Beats):
+                        beats = string.split (i.get_text ().strip (), "+")
+                        current_sig = [int (j) for j in beats]
+                    elif isinstance (i, BeatType):
+                        current_sig.append (int (i.get_text ()))
+                        signature.append (current_sig)
+                        current_sig = []
+                if isinstance (signature[0], list) and len (signature) == 1:
+                    signature = signature[0]
+                self._time_signature_cache = signature
+                return signature
+        except (KeyError, ValueError):
+            self.message (_ ("Unable to interpret time signature! Falling back to 4/4."))
             return (4, 4)
 
     # returns clef information in the form ("cleftype", position, octave-shift)
@@ -337,18 +397,55 @@ class Attributes (Measure_element):
         return clefinfo
 
     def get_key_signature (self):
-        "return (fifths, mode) tuple"
+        "return (fifths, mode) tuple if the key signatures is given as "
+        "major/minor in the Circle of fifths. Otherwise return an alterations"
+        "list of the form [[step,alter<,octave>], [step,alter<,octave>], ...], "
+        "where the octave values are optional."
 
         key = self.get_named_attribute ('key')
-        mode_node = key.get_maybe_exist_named_child ('mode')
-        mode = None
-        if mode_node:
-            mode = mode_node.get_text ()
-        if not mode or mode == '':
-            mode = 'major'
+        if not key:
+            return None
+        fifths_elm = key.get_maybe_exist_named_child ('fifths')
+        if fifths_elm:
+            mode_node = key.get_maybe_exist_named_child ('mode')
+            mode = None
+            if mode_node:
+                mode = mode_node.get_text ()
+            if not mode or mode == '':
+                mode = 'major'
+            fifths = int (fifths_elm.get_text ())
+            # TODO: Shall we try to convert the key-octave and the cancel, too?
+            return (fifths, mode)
+        else:
+            alterations = []
+            current_step = 0
+            for i in key.get_all_children ():
+                if isinstance (i, KeyStep):
+                    current_step = i.get_text ().strip ()
+                elif isinstance (i, KeyAlter):
+                    alterations.append ([current_step, interpret_alter_element (i)])
+                elif isinstance (i, KeyOctave):
+                    nr = -1
+                    if hasattr (i, 'number'):
+                        nr = int (i.number)
+                    if (nr > 0) and (nr <= len (alterations)):
+                        # MusicXML Octave 4 is middle C -> shift to 0
+                        alterations[nr-1].append (int (i.get_text ())-4)
+                    else:
+                        i.message (_ ("Key alteration octave given for a "
+                            "non-existing alteration nr. %s, available numbers: %s!") % (nr, len(alterations)))
+            return alterations
+
+    def get_transposition (self):
+        return self.get_named_attribute ('transpose')
+
+class KeyAlter (Music_xml_node):
+    pass
+class KeyStep (Music_xml_node):
+    pass
+class KeyOctave (Music_xml_node):
+    pass
 
-        fifths = int (key.get_maybe_exist_named_child ('fifths').get_text ())
-        return (fifths, mode)
 
 class Barline (Measure_element):
     pass
@@ -378,9 +475,17 @@ class Note (Measure_element):
         if ch:
             log = ch.get_text ().strip()
             return musicxml_duration_to_log (log)
-       elif self.get_maybe_exist_named_child (u'grace'):
-           # FIXME: is it ok to default to eight note for grace notes?
-           return 3
+        elif self.get_maybe_exist_named_child (u'grace'):
+            # FIXME: is it ok to default to eight note for grace notes?
+            return 3
+        else:
+            return None
+
+    def get_duration_info (self):
+        log = self.get_duration_log ()
+        if log != None:
+            dots = len (self.get_typed_children (Dot))
+            return (log, dots)
         else:
             return None
 
@@ -394,7 +499,7 @@ class Part_list (Music_xml_node):
     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.
@@ -415,14 +520,14 @@ class Part_list (Music_xml_node):
         if instrument_name:
             return instrument_name
         else:
-            ly.stderr_write (_ ("Unable to find instrument for ID=%s\n") % id)
+            ly.warning (_ ("Unable to find instrument for ID=%s\n") % id)
             return "Grand Piano"
 
 class Part_group (Music_xml_node):
     pass
 class Score_part (Music_xml_node):
     pass
-        
+
 class Measure (Music_xml_node):
     def __init__ (self):
         Music_xml_node.__init__ (self)
@@ -430,12 +535,16 @@ class Measure (Music_xml_node):
     def is_implicit (self):
         return hasattr (self, 'implicit') and self.implicit == 'yes'
     def get_notes (self):
-       return self.get_typed_children (get_class (u'note'))
+        return self.get_typed_children (get_class (u'note'))
 
 class Syllabic (Music_xml_node):
     def continued (self):
         text = self.get_text()
         return (text == "begin") or (text == "middle")
+class Elision (Music_xml_node):
+    pass
+class Extend (Music_xml_node):
+    pass
 class Text (Music_xml_node):
     pass
 
@@ -446,49 +555,23 @@ class Lyric (Music_xml_node):
         else:
             return -1
 
-    def lyric_to_text (self):
-        continued = False
-        syllabic = self.get_maybe_exist_typed_child (Syllabic)
-        if syllabic:
-            continued = syllabic.continued ()
-        text = self.get_maybe_exist_typed_child (Text)
-        
-        if text:
-            text = text.get_text()
-            # We need to convert soft hyphens to -, otherwise the ascii codec as well
-            # as lilypond will barf on that character
-            text = string.replace( text, u'\xad', '-' )
-        
-        if text == "-" and continued:
-            return "--"
-        elif text == "_" and continued:
-            return "__"
-        elif continued and text:
-            return escape_ly_output_string (text) + " --"
-        elif continued:
-            return "--"
-        elif text:
-            return escape_ly_output_string (text)
-        else:
-            return ""
-
 class Musicxml_voice:
     def __init__ (self):
-       self._elements = []
-       self._staves = {}
-       self._start_staff = None
+        self._elements = []
+        self._staves = {}
+        self._start_staff = None
         self._lyrics = []
         self._has_lyrics = False
 
     def add_element (self, e):
-       self._elements.append (e)
-       if (isinstance (e, Note)
-           and e.get_maybe_exist_typed_child (Staff)):
-           name = e.get_maybe_exist_typed_child (Staff).get_text ()
+        self._elements.append (e)
+        if (isinstance (e, Note)
+            and e.get_maybe_exist_typed_child (Staff)):
+            name = e.get_maybe_exist_typed_child (Staff).get_text ()
 
-           if not self._start_staff and not e.get_maybe_exist_typed_child (Grace):
-               self._start_staff = name
-           self._staves[name] = True
+            if not self._start_staff and not e.get_maybe_exist_typed_child (Grace):
+                self._start_staff = name
+            self._staves[name] = True
 
         lyrics = e.get_typed_children (Lyric)
         if not self._has_lyrics:
@@ -500,7 +583,7 @@ class Musicxml_voice:
                 self._lyrics.append (nr)
 
     def insert (self, idx, e):
-       self._elements.insert (idx, e)
+        self._elements.insert (idx, e)
 
     def get_lyrics_numbers (self):
         if (len (self._lyrics) == 0) and self._has_lyrics:
@@ -510,10 +593,17 @@ class Musicxml_voice:
             return self._lyrics
 
 
+def graces_to_aftergraces (pending_graces):
+    for gr in pending_graces:
+        gr._when = gr._prev_when
+        gr._measure_position = gr._prev_measure_position
+        gr._after_grace = True
+
+
 class Part (Music_xml_node):
     def __init__ (self):
         Music_xml_node.__init__ (self)
-       self._voices = {}
+        self._voices = {}
         self._staff_attributes_dict = {}
 
     def get_part_list (self):
@@ -522,18 +612,18 @@ class Part (Music_xml_node):
             n = n._parent
 
         return n.get_named_child ('part-list')
-        
+
     def interpret (self):
-       """Set durations and starting points."""
+        """Set durations and starting points."""
         """The starting point of the very first note is 0!"""
-        
+
         part_list = self.get_part_list ()
-        
-       now = Rational (0)
-       factor = Rational (1)
-       attributes_dict = {}
+
+        now = Rational (0)
+        factor = Rational (1)
+        attributes_dict = {}
         attributes_object = None
-       measures = self.get_typed_children (Measure)
+        measures = self.get_typed_children (Measure)
         last_moment = Rational (-1)
         last_measure_position = Rational (-1)
         measure_position = Rational (0)
@@ -543,7 +633,7 @@ class Part (Music_xml_node):
         # Graces at the end of a measure need to have their position set to the
         # previous number!
         pending_graces = []
-       for m in measures:
+        for m in measures:
             # implicit measures are used for artificial measures, e.g. when
             # a repeat bar line splits a bar into two halves. In this case,
             # don't reset the measure position to 0. They are also used for
@@ -578,41 +668,38 @@ class Part (Music_xml_node):
 
                 if isinstance (n, Hash_text):
                     continue
-               dur = Rational (0)
+                dur = Rational (0)
 
                 if n.__class__ == Attributes:
-                   n.set_attributes_from_previous (attributes_dict)
-                   n.read_self ()
-                   attributes_dict = n._dict.copy ()
+                    n.set_attributes_from_previous (attributes_dict)
+                    n.read_self ()
+                    attributes_dict = n._dict.copy ()
                     attributes_object = n
-                    
-                   factor = Rational (1,
-                                      int (attributes_dict.get ('divisions').get_text ()))
-
-                
-               if (n.get_maybe_exist_typed_child (Duration)):
-                   mxl_dur = n.get_maybe_exist_typed_child (Duration)
-                   dur = mxl_dur.get_length () * factor
-                    
-                   if n.get_name() == 'backup':
-                       dur = - dur
+
+                    factor = Rational (1,
+                                       int (attributes_dict.get ('divisions').get_text ()))
+
+
+                if (n.get_maybe_exist_typed_child (Duration)):
+                    mxl_dur = n.get_maybe_exist_typed_child (Duration)
+                    dur = mxl_dur.get_length () * factor
+
+                    if n.get_name() == 'backup':
+                        dur = - dur
                         # reset all graces before the backup to after-graces:
-                        for n in pending_graces:
-                            n._when = n._prev_when
-                            n._measure_position = n._prev_measure_position
-                            n._after_grace = True
+                        graces_to_aftergraces (pending_graces)
                         pending_graces = []
-                   if n.get_maybe_exist_typed_child (Grace):
-                       dur = Rational (0)
+                    if n.get_maybe_exist_typed_child (Grace):
+                        dur = Rational (0)
 
                     rest = n.get_maybe_exist_typed_child (Rest)
-                   if (rest
+                    if (rest
                         and attributes_object
                         and attributes_object.get_measure_length () == dur):
 
                         rest._is_whole_measure = True
 
-                if (dur > Rational (0) 
+                if (dur > Rational (0)
                     and n.get_maybe_exist_typed_child (Chord)):
                     now = last_moment
                     measure_position = last_measure_position
@@ -627,7 +714,7 @@ class Part (Music_xml_node):
                     n._prev_measure_position = last_measure_position
                 # After-graces are placed at the same position as the previous note
                 if isinstance(n, Note) and  n.is_after_grace ():
-                    # TODO: We should do the same for grace notes at the end of 
+                    # TODO: We should do the same for grace notes at the end of
                     # a measure with no following note!!!
                     n._when = last_moment
                     n._measure_position = last_measure_position
@@ -658,10 +745,7 @@ class Part (Music_xml_node):
                         n.instrument_name = part_list.get_instrument (instrument.id)
 
             # reset all graces at the end of the measure to after-graces:
-            for n in pending_graces:
-                n._when = n._prev_when
-                n._measure_position = n._prev_measure_position
-                n._after_grace = True
+            graces_to_aftergraces (pending_graces)
             pending_graces = []
             # Incomplete first measures are not padded, but registered as partial
             if is_first_measure:
@@ -681,6 +765,8 @@ class Part (Music_xml_node):
         attributes._dict = attr._dict.copy ()
         attributes._original_tag = attr
         # copy only the relevant children over for the given staff
+        if staff == "None":
+            staff = "1"
         for c in attr._children:
             if (not (hasattr (c, 'number') and (c.number != staff)) and
                 not (isinstance (c, Hash_text))):
@@ -691,13 +777,13 @@ class Part (Music_xml_node):
             return attributes
 
     def extract_voices (part):
-       voices = {}
-       measures = part.get_typed_children (Measure)
-       elements = []
-       for m in measures:
+        voices = {}
+        measures = part.get_typed_children (Measure)
+        elements = []
+        for m in measures:
             if m.partial > 0:
                 elements.append (Partial (m.partial))
-           elements.extend (m.get_all_children ())
+            elements.extend (m.get_all_children ())
         # make sure we know all voices already so that dynamics, clefs, etc.
         # can be assigned to the correct voices
         voice_to_staff_dict = {}
@@ -707,6 +793,8 @@ class Part (Music_xml_node):
             if voice_id:
                 vid = voice_id.get_text ()
             elif isinstance (n, Note):
+                # TODO: Check whether we shall really use "None" here, or
+                #       rather use "1" as the default?
                 vid = "None"
 
             staff_id = n.get_maybe_exist_named_child (u'staff')
@@ -714,6 +802,10 @@ class Part (Music_xml_node):
             if staff_id:
                 sid = staff_id.get_text ()
             else:
+                # TODO: Check whether we shall really use "None" here, or
+                #       rather use "1" as the default?
+                #       If this is changed, need to change the corresponding
+                #       check in extract_attributes_for_staff, too.
                 sid = "None"
             if vid and not voices.has_key (vid):
                 voices[vid] = Musicxml_voice()
@@ -732,31 +824,31 @@ class Part (Music_xml_node):
                 staff_to_voice_dict[s].append (v)
 
 
-       start_attr = None
+        start_attr = None
         assign_to_next_note = []
         id = None
-       for n in elements:
-           voice_id = n.get_maybe_exist_typed_child (get_class ('voice'))
+        for n in elements:
+            voice_id = n.get_maybe_exist_typed_child (get_class ('voice'))
             if voice_id:
                 id = voice_id.get_text ()
             else:
                 id = "None"
 
-            # We don't need backup/forward any more, since we have already 
-            # assigned the correct onset times. 
+            # We don't need backup/forward any more, since we have already
+            # assigned the correct onset times.
             # TODO: Let Grouping through. Also: link, print, bokmark sound
-           if not (isinstance (n, Note) or isinstance (n, Attributes) or
+            if not (isinstance (n, Note) or isinstance (n, Attributes) or
                     isinstance (n, Direction) or isinstance (n, Partial) or
                     isinstance (n, Barline) or isinstance (n, Harmony) or
-                    isinstance (n, FiguredBass) ):
-               continue
+                    isinstance (n, FiguredBass) or isinstance (n, Print)):
+                continue
 
-           if isinstance (n, Attributes) and not start_attr:
-               start_attr = n
-               continue
+            if isinstance (n, Attributes) and not start_attr:
+                start_attr = n
+                continue
 
             if isinstance (n, Attributes):
-                # assign these only to the voices they really belongs to!
+                # assign these only to the voices they really belong to!
                 for (s, vids) in staff_to_voice_dict.items ():
                     staff_attributes = part.extract_attributes_for_staff (n, s)
                     if staff_attributes:
@@ -764,7 +856,7 @@ class Part (Music_xml_node):
                             voices[v].add_element (staff_attributes)
                 continue
 
-            if isinstance (n, Partial) or isinstance (n, Barline):
+            if isinstance (n, Partial) or isinstance (n, Barline) or isinstance (n, Print):
                 for v in voices.keys ():
                     voices[v].add_element (n)
                 continue
@@ -782,13 +874,13 @@ class Part (Music_xml_node):
                 continue
 
             if isinstance (n, Harmony) or isinstance (n, FiguredBass):
-                # store the harmony or figured bass element until we encounter 
+                # store the harmony or figured bass element until we encounter
                 # the next note and assign it only to that one voice.
                 assign_to_next_note.append (n)
                 continue
 
             if hasattr (n, 'print-object') and getattr (n, 'print-object') == "no":
-                #Skip this note. 
+                #Skip this note.
                 pass
             else:
                 for i in assign_to_next_note:
@@ -802,7 +894,7 @@ class Part (Music_xml_node):
             voices[id].add_element (i)
         assign_to_next_note = []
 
-       if start_attr:
+        if start_attr:
             for (s, vids) in staff_to_voice_dict.items ():
                 staff_attributes = part.extract_attributes_for_staff (start_attr, s)
                 staff_attributes.read_self ()
@@ -814,33 +906,43 @@ class Part (Music_xml_node):
         part._voices = voices
 
     def get_voices (self):
-       return self._voices
+        return self._voices
     def get_staff_attributes (self):
         return self._staff_attributes_dict
 
 class Notations (Music_xml_node):
     def get_tie (self):
-       ts = self.get_named_children ('tied')
-       starts = [t for t in ts if t.type == 'start']
-       if starts:
-           return starts[0]
-       else:
-           return None
+        ts = self.get_named_children ('tied')
+        starts = [t for t in ts if t.type == 'start']
+        if starts:
+            return starts[0]
+        else:
+            return None
 
     def get_tuplets (self):
-       return self.get_typed_children (Tuplet)
+        return self.get_typed_children (Tuplet)
 
 class Time_modification(Music_xml_node):
     def get_fraction (self):
-       b = self.get_maybe_exist_named_child ('actual-notes')
-       a = self.get_maybe_exist_named_child ('normal-notes')
-       return (int(a.get_text ()), int (b.get_text ()))
+        b = self.get_maybe_exist_named_child ('actual-notes')
+        a = self.get_maybe_exist_named_child ('normal-notes')
+        return (int(a.get_text ()), int (b.get_text ()))
+
+    def get_normal_type (self):
+        tuplet_type = self.get_maybe_exist_named_child ('normal-type')
+        if tuplet_type:
+            dots = self.get_named_children ('normal-dot')
+            log = musicxml_duration_to_log (tuplet_type.get_text ().strip ())
+            return (log , len (dots))
+        else:
+            return None
+
 
 class Accidental (Music_xml_node):
     def __init__ (self):
-       Music_xml_node.__init__ (self)
-       self.editorial = False
-       self.cautionary = False
+        Music_xml_node.__init__ (self)
+        self.editorial = False
+        self.cautionary = False
 
 class Music_xml_spanner (Music_xml_node):
     def get_type (self):
@@ -858,7 +960,40 @@ class Wedge (Music_xml_spanner):
     pass
 
 class Tuplet (Music_xml_spanner):
-    pass
+    def duration_info_from_tuplet_note (self, tuplet_note):
+        tuplet_type = tuplet_note.get_maybe_exist_named_child ('tuplet-type')
+        if tuplet_type:
+            dots = tuplet_note.get_named_children ('tuplet-dot')
+            log = musicxml_duration_to_log (tuplet_type.get_text ().strip ())
+            return (log, len (dots))
+        else:
+            return None
+
+    # Return tuplet note type as (log, dots)
+    def get_normal_type (self):
+        tuplet = self.get_maybe_exist_named_child ('tuplet-normal')
+        if tuplet:
+            return self.duration_info_from_tuplet_note (tuplet)
+        else:
+            return None
+
+    def get_actual_type (self):
+        tuplet = self.get_maybe_exist_named_child ('tuplet-actual')
+        if tuplet:
+            return self.duration_info_from_tuplet_note (tuplet)
+        else:
+            return None
+
+    def get_tuplet_note_count (self, tuplet_note):
+        if tuplet_note:
+            tuplet_nr = tuplet_note.get_maybe_exist_named_child ('tuplet-number')
+            if tuplet_nr:
+                return int (tuplet_nr.get_text ())
+        return None
+    def get_normal_nr (self):
+        return self.get_tuplet_note_count (self.get_maybe_exist_named_child ('tuplet-normal'))
+    def get_actual_nr (self):
+        return self.get_tuplet_note_count (self.get_maybe_exist_named_child ('tuplet-actual'))
 
 class Bracket (Music_xml_spanner):
     pass
@@ -868,17 +1003,20 @@ class Dashes (Music_xml_spanner):
 
 class Slur (Music_xml_spanner):
     def get_type (self):
-       return self.type
+        return self.type
 
 class Beam (Music_xml_spanner):
     def get_type (self):
-       return self.get_text ()
+        return self.get_text ()
     def is_primary (self):
-        return self.number == "1"
+        if hasattr (self, 'number'):
+            return self.number == "1"
+        else:
+            return True
 
 class Wavy_line (Music_xml_spanner):
     pass
-    
+
 class Pedal (Music_xml_spanner):
     pass
 
@@ -913,15 +1051,14 @@ class Rest (Music_xml_node):
     def get_step (self):
         ch = self.get_maybe_exist_typed_child (get_class (u'display-step'))
         if ch:
-            step = ch.get_text ().strip ()
-            return step
+            return ch.get_text ().strip ()
         else:
             return None
     def get_octave (self):
         ch = self.get_maybe_exist_typed_child (get_class (u'display-octave'))
         if ch:
-            step = ch.get_text ().strip ()
-            return int (step)
+            oct = ch.get_text ().strip ()
+            return int (oct)
         else:
             return None
 
@@ -940,10 +1077,7 @@ class DirType (Music_xml_node):
 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
+        return interpret_alter_element (alter)
 
 class Words (Music_xml_node):
     pass
@@ -961,10 +1095,7 @@ class ChordPitch (Music_xml_node):
         return ch.get_text ().strip ()
     def get_alteration (self):
         ch = self.get_maybe_exist_typed_child (get_class (self.alter_class_name ()))
-        alter = 0
-        if ch:
-            alter = int (ch.get_text ().strip ())
-        return alter
+        return interpret_alter_element (ch)
 
 class Root (ChordPitch):
     pass
@@ -987,10 +1118,7 @@ class ChordModification (Music_xml_node):
         return value
     def get_alter (self):
         ch = self.get_maybe_exist_typed_child (get_class (u'degree-alter'))
-        value = 0
-        if ch:
-            value = int (ch.get_text ().strip ())
-        return value
+        return interpret_alter_element (ch)
 
 
 class Frame (Music_xml_node):
@@ -1018,6 +1146,12 @@ class Frame_Note (Music_xml_node):
 class FiguredBass (Music_xml_node):
     pass
 
+class Beats (Music_xml_node):
+    pass
+
+class BeatType (Music_xml_node):
+    pass
+
 class BeatUnit (Music_xml_node):
     pass
 
@@ -1027,61 +1161,72 @@ class BeatUnitDot (Music_xml_node):
 class PerMinute (Music_xml_node):
     pass
 
+class Print (Music_xml_node):
+    pass
+
 
 
 ## need this, not all classes are instantiated
 ## for every input file. Only add those classes, that are either directly
 ## used by class name or extend Music_xml_node in some way!
 class_dict = {
-       '#comment': Hash_comment,
+        '#comment': Hash_comment,
         '#text': Hash_text,
-       'accidental': Accidental,
-       'attributes': Attributes,
+        'accidental': Accidental,
+        'attributes': Attributes,
         'barline': Barline,
         'bar-style': BarStyle,
         'bass': Bass,
-       'beam' : Beam,
+        'beam' : Beam,
+        'beats': Beats,
+        'beat-type': BeatType,
         'beat-unit': BeatUnit,
         'beat-unit-dot': BeatUnitDot,
         'bend' : Bend,
         'bracket' : Bracket,
-       'chord': Chord,
+        'chord': Chord,
         'dashes' : Dashes,
         'degree' : ChordModification,
-       'dot': Dot,
-       'direction': Direction,
+        'dot': Dot,
+        'direction': Direction,
         'direction-type': DirType,
-       'duration': Duration,
+        'duration': Duration,
+        'elision': Elision,
+        'extend': Extend,
         'frame': Frame,
         'frame-note': Frame_Note,
         'figured-bass': FiguredBass,
         'glissando': Glissando,
-       'grace': Grace,
+        'grace': Grace,
         'harmony': Harmony,
         'identification': Identification,
+        'key-alter': KeyAlter,
+        'key-octave': KeyOctave,
+        'key-step': KeyStep,
         'lyric': Lyric,
-       'measure': Measure,
-       'notations': Notations,
-       'note': Note,
+        'measure': Measure,
+        'notations': Notations,
+        'note': Note,
         'octave-shift': Octave_shift,
-       'part': Part,
+        'part': Part,
     'part-group': Part_group,
-       'part-list': Part_list,
+        'part-list': Part_list,
         'pedal': Pedal,
         'per-minute': PerMinute,
-       'pitch': Pitch,
-       'rest': Rest,
+        'pitch': Pitch,
+        'print': Print,
+        'rest': Rest,
         'root': Root,
         'score-part': Score_part,
         'slide': Slide,
-       'slur': Slur,
-       'staff': Staff,
+        'slur': Slur,
+        'staff': Staff,
         'syllabic': Syllabic,
         'text': Text,
-       'time-modification': Time_modification,
+        'time-modification': Time_modification,
         'tuplet': Tuplet,
-       'type': Type,
-       'unpitched': Unpitched,
+        'type': Type,
+        'unpitched': Unpitched,
         'wavy-line': Wavy_line,
         'wedge': Wedge,
         'words': Words,
@@ -1100,11 +1245,11 @@ def get_class (name):
     if classname:
         return classname
     else:
-       class_name = name2class_name (name)
-       klass = new.classobj (class_name, (Music_xml_node,) , {})
-       class_dict[name] = klass
+        class_name = name2class_name (name)
+        klass = new.classobj (class_name, (Music_xml_node,) , {})
+        class_dict[name] = klass
         return klass
-        
+
 def lxml_demarshal_node (node):
     name = node.tag
 
@@ -1113,15 +1258,15 @@ def lxml_demarshal_node (node):
         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
+        c._parent = py_node
 
     for (k, v) in node.items ():
         py_node.__dict__[k] = v
@@ -1137,16 +1282,16 @@ def minidom_demarshal_node (node):
     py_node._name = name
     py_node._children = [minidom_demarshal_node (cn) for cn in node.childNodes]
     for c in py_node._children:
-       c._parent = py_node
+        c._parent = py_node
 
     if node.attributes:
-       for (nm, value) in node.attributes.items ():
-           py_node.__dict__[nm] = value
+        for (nm, value) in node.attributes.items ():
+            py_node.__dict__[nm] = value
             py_node._attribute_dict[nm] = value
-            
+
     py_node._data = None
     if node.nodeType == node.TEXT_NODE and node.data:
-       py_node._data = node.data 
+        py_node._data = node.data
 
     py_node._original = node
     return py_node
@@ -1154,7 +1299,7 @@ def minidom_demarshal_node (node):
 
 if __name__  == '__main__':
     import lxml.etree
-        
+
     tree = lxml.etree.parse ('beethoven.xml')
     mxl_tree = lxml_demarshal_node (tree.getroot ())
     ks = class_dict.keys ()