]> git.donarmstrong.com Git - lilypond.git/blobdiff - python/musicxml.py
Imported Upstream version 2.14.2
[lilypond.git] / python / musicxml.py
index 588a469f970ea537749a3c27bbc4942a9fe65616..be3c61ca8f99a9bc4cf0ba8d7d0c992826359eba 100644 (file)
@@ -15,7 +15,7 @@ def error (str):
 
 def escape_ly_output_string (input_string):
     return_string = input_string
 
 def escape_ly_output_string (input_string):
     return_string = input_string
-    needs_quotes = not re.match (u"^[a-zA-ZäöüÜÄÖßñ]*$", return_string);
+    needs_quotes = not re.match (u"^[a-zA-ZäöüÜÄÖß,\.!:ñ]*$", return_string);
     if needs_quotes:
         return_string = "\"" + string.replace (return_string, "\"", "\\\"") + "\""
     return return_string
     if needs_quotes:
         return_string = "\"" + string.replace (return_string, "\"", "\\\"") + "\""
     return return_string
@@ -37,6 +37,15 @@ 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 = []
 class Xml_node:
     def __init__ (self):
        self._children = []
@@ -246,16 +255,12 @@ class Pitch (Music_xml_node):
        return step
     def get_octave (self):
        ch = self.get_unique_typed_child (get_class (u'octave'))
        return step
     def get_octave (self):
        ch = self.get_unique_typed_child (get_class (u'octave'))
-
-       step = ch.get_text ().strip ()
-       return int (step)
+       octave = ch.get_text ().strip ()
+       return int (octave)
 
     def get_alteration (self):
        ch = self.get_maybe_exist_typed_child (get_class (u'alter'))
 
     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
+       return interpret_alter_element (ch)
 
 class Unpitched (Music_xml_node):
     def get_step (self):
 
 class Unpitched (Music_xml_node):
     def get_step (self):
@@ -294,6 +299,7 @@ class Attributes (Measure_element):
        Measure_element.__init__ (self)
        self._dict = {}
         self._original_tag = None
        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__)
 
     def is_first (self):
        cn = self._parent.get_typed_children (self.__class__)
@@ -311,25 +317,63 @@ class Attributes (Measure_element):
 
     def get_named_attribute (self, name):
        return self._dict.get (name)
 
     def get_named_attribute (self, 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):
 
     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):
         
     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')
 
         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!
+                error (_ ("Senza-misura time signatures are not yet supported!"))
                 return (4, 4)
                 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)
             return (4, 4)
 
     # returns clef information in the form ("cleftype", position, octave-shift)
@@ -350,22 +394,54 @@ class Attributes (Measure_element):
         return clefinfo
 
     def get_key_signature (self):
         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')
 
         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'
-
-        fifths = int (key.get_maybe_exist_named_child ('fifths').get_text ())
-        return (fifths, mode)
-        
+        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')
     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
 
 
 class Barline (Measure_element):
 
 
 class Barline (Measure_element):
@@ -401,6 +477,14 @@ class Note (Measure_element):
            return 3
         else:
             return None
            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
 
     def get_factor (self):
         return 1
 
     def get_factor (self):
         return 1
@@ -454,6 +538,10 @@ class Syllabic (Music_xml_node):
     def continued (self):
         text = self.get_text()
         return (text == "begin") or (text == "middle")
     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
 
 class Text (Music_xml_node):
     pass
 
@@ -464,32 +552,6 @@ class Lyric (Music_xml_node):
         else:
             return -1
 
         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 = []
 class Musicxml_voice:
     def __init__ (self):
        self._elements = []
@@ -528,6 +590,13 @@ class Musicxml_voice:
             return self._lyrics
 
 
             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)
 class Part (Music_xml_node):
     def __init__ (self):
         Music_xml_node.__init__ (self)
@@ -540,7 +609,7 @@ class Part (Music_xml_node):
             n = n._parent
 
         return n.get_named_child ('part-list')
             n = n._parent
 
         return n.get_named_child ('part-list')
-        
+       
     def interpret (self):
        """Set durations and starting points."""
         """The starting point of the very first note is 0!"""
     def interpret (self):
        """Set durations and starting points."""
         """The starting point of the very first note is 0!"""
@@ -615,10 +684,7 @@ class Part (Music_xml_node):
                    if n.get_name() == 'backup':
                        dur = - dur
                         # reset all graces before the backup to after-graces:
                    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)
                         pending_graces = []
                    if n.get_maybe_exist_typed_child (Grace):
                        dur = Rational (0)
@@ -630,7 +696,7 @@ class Part (Music_xml_node):
 
                         rest._is_whole_measure = True
 
 
                         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
                     and n.get_maybe_exist_typed_child (Chord)):
                     now = last_moment
                     measure_position = last_measure_position
@@ -676,10 +742,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:
                         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:
             pending_graces = []
             # Incomplete first measures are not padded, but registered as partial
             if is_first_measure:
@@ -766,7 +829,7 @@ class Part (Music_xml_node):
            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
            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) ):
+                    isinstance (n, FiguredBass) or isinstance (n, Print)):
                continue
 
            if isinstance (n, Attributes) and not start_attr:
                continue
 
            if isinstance (n, Attributes) and not start_attr:
@@ -782,7 +845,7 @@ class Part (Music_xml_node):
                             voices[v].add_element (staff_attributes)
                 continue
 
                             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
                 for v in voices.keys ():
                     voices[v].add_element (n)
                 continue
@@ -854,6 +917,16 @@ class Time_modification(Music_xml_node):
        a = self.get_maybe_exist_named_child ('normal-notes')
        return (int(a.get_text ()), int (b.get_text ()))
 
        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)
 class Accidental (Music_xml_node):
     def __init__ (self):
        Music_xml_node.__init__ (self)
@@ -876,7 +949,40 @@ class Wedge (Music_xml_spanner):
     pass
 
 class Tuplet (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
 
 class Bracket (Music_xml_spanner):
     pass
@@ -892,7 +998,10 @@ class Beam (Music_xml_spanner):
     def get_type (self):
        return self.get_text ()
     def is_primary (self):
     def get_type (self):
        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 Wavy_line (Music_xml_spanner):
     pass
@@ -931,15 +1040,14 @@ class Rest (Music_xml_node):
     def get_step (self):
         ch = self.get_maybe_exist_typed_child (get_class (u'display-step'))
         if ch:
     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:
         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
 
         else:
             return None
 
@@ -958,10 +1066,7 @@ class DirType (Music_xml_node):
 class Bend (Music_xml_node):
     def bend_alter (self):
         alter = self.get_maybe_exist_named_child ('bend-alter')
 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
 
 class Words (Music_xml_node):
     pass
@@ -979,10 +1084,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 ()))
         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
 
 class Root (ChordPitch):
     pass
@@ -1005,10 +1107,7 @@ class ChordModification (Music_xml_node):
         return value
     def get_alter (self):
         ch = self.get_maybe_exist_typed_child (get_class (u'degree-alter'))
         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):
 
 
 class Frame (Music_xml_node):
@@ -1036,6 +1135,12 @@ class Frame_Note (Music_xml_node):
 class FiguredBass (Music_xml_node):
     pass
 
 class FiguredBass (Music_xml_node):
     pass
 
+class Beats (Music_xml_node):
+    pass
+
+class BeatType (Music_xml_node):
+    pass
+
 class BeatUnit (Music_xml_node):
     pass
 
 class BeatUnit (Music_xml_node):
     pass
 
@@ -1045,6 +1150,9 @@ class BeatUnitDot (Music_xml_node):
 class PerMinute (Music_xml_node):
     pass
 
 class PerMinute (Music_xml_node):
     pass
 
+class Print (Music_xml_node):
+    pass
+
 
 
 ## need this, not all classes are instantiated
 
 
 ## need this, not all classes are instantiated
@@ -1059,6 +1167,8 @@ class_dict = {
         'bar-style': BarStyle,
         'bass': Bass,
        'beam' : Beam,
         'bar-style': BarStyle,
         'bass': Bass,
        'beam' : Beam,
+        'beats': Beats,
+        'beat-type': BeatType,
         'beat-unit': BeatUnit,
         'beat-unit-dot': BeatUnitDot,
         'bend' : Bend,
         'beat-unit': BeatUnit,
         'beat-unit-dot': BeatUnitDot,
         'bend' : Bend,
@@ -1070,6 +1180,8 @@ class_dict = {
        'direction': Direction,
         'direction-type': DirType,
        'duration': Duration,
        'direction': Direction,
         'direction-type': DirType,
        'duration': Duration,
+        'elision': Elision,
+        'extend': Extend,
         'frame': Frame,
         'frame-note': Frame_Note,
         'figured-bass': FiguredBass,
         'frame': Frame,
         'frame-note': Frame_Note,
         'figured-bass': FiguredBass,
@@ -1077,6 +1189,9 @@ class_dict = {
        'grace': Grace,
         'harmony': Harmony,
         'identification': Identification,
        'grace': Grace,
         'harmony': Harmony,
         'identification': Identification,
+        'key-alter': KeyAlter,
+        'key-octave': KeyOctave,
+        'key-step': KeyStep,
         'lyric': Lyric,
        'measure': Measure,
        'notations': Notations,
         'lyric': Lyric,
        'measure': Measure,
        'notations': Notations,
@@ -1088,6 +1203,7 @@ class_dict = {
         'pedal': Pedal,
         'per-minute': PerMinute,
        'pitch': Pitch,
         'pedal': Pedal,
         'per-minute': PerMinute,
        'pitch': Pitch,
+        'print': Print,
        'rest': Rest,
         'root': Root,
         'score-part': Score_part,
        'rest': Rest,
         'root': Root,
         'score-part': Score_part,