X-Git-Url: https://git.donarmstrong.com/?a=blobdiff_plain;f=python%2Fmusicxml.py;h=a061b6ea6eee0ba2deec281554413a061e978242;hb=c7358ca08d062adbc9710f158fe0f965c79fc638;hp=7ba771fa8d6c77f72a27d1e47abd89ca91fcfa2c;hpb=ea4fdf1afa3e6bb9a7c586f5a533be93ff3312d3;p=lilypond.git diff --git a/python/musicxml.py b/python/musicxml.py index 7ba771fa8d..a061b6ea6e 100644 --- a/python/musicxml.py +++ b/python/musicxml.py @@ -9,13 +9,10 @@ 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 - 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 @@ -54,10 +51,10 @@ class Xml_node: 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 @@ -76,25 +73,25 @@ class Xml_node: 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 = ''): - sys.stderr.write ('%s<%s%s>' % (indent, self._name, ''.join ([' %s=%s' % item for item in self._attribute_dict.items ()]))) + 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: - sys.stderr.write ('\n') + ly.debug_output ('\n') for c in self._children: c.dump (indent + " ") if non_text_children: - sys.stderr.write (indent) - sys.stderr.write ('\n' % self._name) + ly.debug_output (indent) + ly.debug_output ('\n' % self._name) + - def get_typed_children (self, klass): if not klass: return [] @@ -128,7 +125,7 @@ class Xml_node: def get_unique_typed_child (self, klass): cn = self.get_typed_children(klass) if len (cn) <> 1: - sys.stderr.write (self.__dict__ + '\n') + ly.error (self.__dict__) raise 'Child is not unique for', (klass, 'found', cn) return cn[0] @@ -146,6 +143,7 @@ class Music_xml_node (Xml_node): Xml_node.__init__ (self) self.duration = Rational (0) self.start = Rational (0) + self.voice_id = None; class Work (Xml_node): def get_work_information (self, tag): @@ -154,7 +152,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): @@ -170,6 +168,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 @@ -198,7 +204,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: @@ -207,7 +213,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): @@ -216,7 +222,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 = [] @@ -232,11 +238,9 @@ 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) @@ -246,7 +250,7 @@ class Hash_comment (Music_xml_node): pass class Hash_text (Music_xml_node): def dump (self, indent = ''): - sys.stderr.write ('%s' % string.strip (self._data)) + ly.debug_output ('%s' % string.strip (self._data)) class Pitch (Music_xml_node): def get_step (self): @@ -279,11 +283,11 @@ class Unpitched (Music_xml_node): 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 () + voice = self.get_maybe_exist_named_child ('voice') + if voice: + return voice.get_text () else: - return None + return self.voice_id; def is_first (self): # Look at all measure elements (previously we had self.__class__, which @@ -307,17 +311,17 @@ class Attributes (Measure_element): return cn[0] == self._original_tag else: return cn[0] == self - + 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 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 @@ -340,7 +344,7 @@ class Attributes (Measure_element): # 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. For compound signatures," "return either (beat, beat,..., beat-type) or ((beat,..., type), " @@ -355,7 +359,7 @@ class Attributes (Measure_element): 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!")) + ly.warning (_ ("Senza-misura time signatures are not yet supported!")) return (4, 4) else: signature = [] @@ -477,7 +481,7 @@ class Note (Measure_element): return 3 else: return None - + def get_duration_info (self): log = self.get_duration_log () if log != None: @@ -496,7 +500,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. @@ -517,14 +521,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) @@ -609,13 +613,13 @@ class Part (Music_xml_node): 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!""" - + part_list = self.get_part_list () - + now = Rational (0) factor = Rational (1) attributes_dict = {} @@ -654,7 +658,25 @@ class Part (Music_xml_node): measure_start_moment = now measure_position = Rational (0) + voice_id = None; + assign_to_next_voice = [] for n in m.get_all_children (): + # assign a voice to all measure elements + if (n.get_name() == 'backup'): + voice_id = None; + + if isinstance(n, Measure_element): + if n.get_voice_id (): + voice_id = n.get_voice_id () + for i in assign_to_next_voice: + i.voice_id = voice_id + assign_to_next_voice = [] + else: + if voice_id: + n.voice_id = voice_id + else: + assign_to_next_voice.append (n) + # figured bass has a duration, but applies to the next note # and should not change the current measure position! if isinstance (n, FiguredBass): @@ -672,15 +694,15 @@ class Part (Music_xml_node): 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 # reset all graces before the backup to after-graces: @@ -711,7 +733,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 @@ -831,8 +853,8 @@ class Part (Music_xml_node): 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 isinstance (n, Direction) or isinstance (n, Partial) or @@ -859,25 +881,20 @@ class Part (Music_xml_node): continue if isinstance (n, Direction): - staff_id = n.get_maybe_exist_named_child (u'staff') - if staff_id: - staff_id = staff_id.get_text () - if staff_id: - dir_voices = staff_to_voice_dict.get (staff_id, voices.keys ()) + if (n.voice_id): + voices[n.voice_id].add_element (n) else: - dir_voices = voices.keys () - for v in dir_voices: - voices[v].add_element (n) + assign_to_next_note.append (n) 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: @@ -984,7 +1001,7 @@ class Tuplet (Music_xml_spanner): 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: + if tuplet_nr: return int (tuplet_nr.get_text ()) return None def get_normal_nr (self): @@ -1013,7 +1030,7 @@ class Beam (Music_xml_spanner): class Wavy_line (Music_xml_spanner): pass - + class Pedal (Music_xml_spanner): pass @@ -1066,7 +1083,7 @@ class Grace (Music_xml_node): class Staff (Music_xml_node): pass -class Direction (Music_xml_node): +class Direction (Measure_element): pass class DirType (Music_xml_node): pass @@ -1246,7 +1263,7 @@ def get_class (name): klass = new.classobj (class_name, (Music_xml_node,) , {}) class_dict[name] = klass return klass - + def lxml_demarshal_node (node): name = node.tag @@ -1255,13 +1272,13 @@ 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 @@ -1285,7 +1302,7 @@ def minidom_demarshal_node (node): 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 @@ -1296,7 +1313,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 ()