X-Git-Url: https://git.donarmstrong.com/?a=blobdiff_plain;f=python%2Fmusicxml.py;h=ae5ca80a59647dca423d6b374bf6b595b53ee315;hb=b872748c6aa8bb721ced458691b38ac2fac5dfc8;hp=e7fe77d69977de9fb6606964c3805f192a0a27ea;hpb=dffba2a49f12b6f204be1056b8e81b275f0d162f;p=lilypond.git diff --git a/python/musicxml.py b/python/musicxml.py index e7fe77d699..ae5ca80a59 100644 --- a/python/musicxml.py +++ b/python/musicxml.py @@ -1,590 +1,1855 @@ +# -*- coding: utf-8 -*- import new +import string from rational import * +import re +import sys +import copy +import lilylib as ly +import warnings + +_ = ly._ + +import musicexp +import musicxml2ly_conversion +import utilities + class Xml_node: - def __init__ (self): - self._children = [] - self._data = None - self._original = None - self._name = 'xml_node' - self._parent = None + + def __init__(self): + self._children = [] + self._data = None + self._original = None + self._name = 'xml_node' + self._parent = None self._attribute_dict = {} - - def get_parent (self): + + def get_parent(self): return self._parent - - def is_first (self): - return self._parent.get_typed_children (self.__class__)[0] == self - def original (self): - return self._original - def get_name (self): - return self._name + def is_first(self): + return self._parent.get_typed_children(self.__class__)[0] == self + + def original(self): + return self._original + def get_name(self): + return self._name - def get_text (self): - if self._data: - return self._data + def get_text(self): + 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): - print msg + def message(self, msg): + ly.warning(msg) p = self while p: - print ' In: <%s %s>' % (p._name, ' '.join (['%s=%s' % item for item in p._attribute_dict.items()])) - p = p.get_parent () - - def get_typed_children (self, klass): + 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('\n' % self._name) + + + def get_typed_children(self, klass): if not klass: return [] else: return [c for c in self._children if isinstance(c, klass)] - def get_named_children (self, nm): - return self.get_typed_children (class_dict.get (nm)) + def get_named_children(self, nm): + return self.get_typed_children(get_class(nm)) + + def get_named_child(self, nm): + return self.get_maybe_exist_named_child(nm) - def get_named_child (self, nm): - return self.get_maybe_exist_named_child (nm) + def get_children(self, predicate): + return [c for c in self._children if predicate(c)] - def get_children (self, predicate): - return [c for c in self._children if predicate(c)] + def get_all_children(self): + return self._children - def get_all_children (self): - return self._children + def get_maybe_exist_named_child(self, name): + return self.get_maybe_exist_typed_child(get_class(name)) - def get_maybe_exist_named_child (self, name): - return self.get_maybe_exist_typed_child (class_dict[name]) + def get_maybe_exist_typed_child(self, klass): + cn = self.get_typed_children(klass) + if len(cn) == 0: + return None + else: + try: + assert len(cn) == 1 + return cn[0] + except: + msg = ' '.join( + ['more than one child of class ', + klass.__name__, + '...all but the first will be ignored!']) + warnings.warn(msg) + return cn[0] + + def get_unique_typed_child(self, klass): + 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] + + def get_named_child_value_number(self, name, default): + n = self.get_maybe_exist_named_child(name) + if n: + return int(n.get_text()) + else: + return default - 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 - def get_unique_typed_child (self, klass): - cn = self.get_typed_children(klass) - if len (cn) <> 1: - print self.__dict__ - raise 'Child is not unique for', (klass, 'found', cn) +class Music_xml_node(Xml_node): + def __init__(self): + Xml_node.__init__(self) + self.duration = Rational(0) + self.start = Rational(0) + self.converted = False + self.voice_id = None; - return cn[0] -class Music_xml_node (Xml_node): - def __init__ (self): - Xml_node.__init__ (self) - self.duration = Rational (0) - self.start = Rational (0) +class Music_xml_spanner(Music_xml_node): + def get_type(self): + if hasattr(self, 'type'): + return self.type + else: + return 0 -class Duration (Music_xml_node): - def get_length (self): - dur = int (self.get_text ()) * Rational (1,4) - return dur + def get_size(self): + if hasattr(self, 'size'): + return int(self.size) + else: + return 0 -class Hash_comment (Music_xml_node): - pass -class Pitch (Music_xml_node): - def get_step (self): - ch = self.get_unique_typed_child (class_dict[u'step']) - step = ch.get_text ().strip () - return step - def get_octave (self): - ch = self.get_unique_typed_child (class_dict[u'octave']) +class Measure_element(Music_xml_node): + + def get_voice_id(self): + voice = self.get_maybe_exist_named_child('voice') + if voice: + return voice.get_text() + else: + return self.voice_id + + 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) + # 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 + + +class Work(Xml_node): + + def get_work_information(self, tag): + wt = self.get_maybe_exist_named_child(tag) + if wt: + return wt.get_text() + else: + return '' + + def get_work_title(self): + return self.get_work_information('work-title') + + def get_work_number(self): + return self.get_work_information('work-number') + + # def get_opus(self): + # return self.get_work_information('opus') + + +class Identification(Xml_node): + + def get_rights(self): + rights = self.get_named_children('rights') + ret = [] + for r in rights: + text = r.get_text() + # if this Xml_node has an attribute, such as 'type="words"', + # include it in the header. Otherwise, it is assumed that + # the text contents of this node looks something like this: + # 'Copyright: X.Y.' and thus already contains the relevant + # information. + if hasattr(r, 'type'): + rights_type = r.type.title() # capitalize first letter + result = ''.join([rights_type, ': ', text]) + ret.append(result) + else: + ret.append(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 + for i in creators: + if hasattr(i, 'type') and i.type == type: + return i.get_text() + return None + + def get_composer(self): + c = self.get_creator('composer') + if c: + return c + creators = self.get_named_children('creator') + # return the first creator tag that has no type at all + for i in creators: + if not hasattr(i, 'type'): + return i.get_text() + return None + + def get_arranger(self): + return self.get_creator('arranger') + + def get_editor(self): + return self.get_creator('editor') + + def get_poet(self): + v = self.get_creator('lyricist') + if v: + return v + v = self.get_creator('poet') + return v + + def get_encoding_information(self, type): + enc = self.get_named_children('encoding') + if enc: + children = enc[0].get_named_children(type) + if children: + return children[0].get_text() + else: + return None + + def get_encoding_software(self): + return self.get_encoding_information('software') + + def get_encoding_date(self): + return self.get_encoding_information('encoding-date') + + def get_encoding_person(self): + 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 = [] + for e in enc: + softwares = e.get_named_children('software') + for s in softwares: + software.append(s.get_text()) + return software + + def get_file_description(self): + misc = self.get_named_children('miscellaneous') + for m in misc: + 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 None + + +class Credit(Xml_node): + + def get_type(self): + type = self.get_maybe_exist_named_child('credit-type') + if(type != None): + return type.get_text() + else: + return None + + def find_type(self, credits): + sizes = self.get_font_sizes(credits) + sizes.sort(reverse=True) + ys = self.get_default_ys(credits) + ys.sort(reverse=True) + xs = self.get_default_xs(credits) + xs.sort(reverse=True) + + # Words child of the self credit-element + words = self.get_maybe_exist_named_child('credit-words') + size = None + x = None + y = None + halign = None + valign = None + justify = None + if(words != None): + if hasattr(words, 'font-size'): + size = utilities.string_to_integer(getattr(words, 'font-size')) + if hasattr(words, 'default-x'): + x = round(float(getattr(words, 'default-x'))) + if hasattr(words, 'default-y'): + y = round(float(getattr(words, 'default-y'))) + if hasattr(words, 'halign'): + halign = getattr(words, 'halign') + if hasattr(words, 'valign'): + valign = getattr(words, 'valign') + if hasattr(words, 'justify'): + justify = getattr(words, 'justify') + if(size and size == max(sizes) and y and y == max(ys) and(justify or halign) and(justify == 'center' or halign == 'center')): + return 'title' + elif((y and y > min(ys) and y < max(ys)) and((justify or halign) and(justify == 'center' or halign == 'center'))): + return 'subtitle' + elif((justify or halign) and(justify == 'left' or halign == 'left') and(not(x) or x == min(xs))): + return 'lyricist' + elif((justify or halign) and(justify == 'right' or halign == 'right') and(not(x) or x == max(xs))): + return 'composer' + elif(size and size == min(sizes) and y == min(ys)): + return 'rights' + # Special cases for Finale NotePad + elif((valign and(valign == 'top')) and(y and y == ys[1])): + return 'subtitle' + elif((valign and(valign == 'top')) and(x and x == min(xs))): + return 'lyricist' + elif((valign and(valign == 'top')) and(y and y == min(ys))): + return 'rights' + # Other special cases + elif((valign and(valign == 'bottom'))): + return 'rights' + elif(len([item for item in range(len(ys)) if ys[item] == y]) == 2): + # The first one is the composer, the second one is the lyricist + return 'composer' + + return None # no type recognized + + def get_font_sizes(self, credits): + sizes = [] + for cred in credits: + words = cred.get_maybe_exist_named_child('credit-words') + if((words != None) and hasattr(words, 'font-size')): + sizes.append(getattr(words, 'font-size')) + return map(utilities.string_to_integer, sizes) + + def get_default_xs(self, credits): + default_xs = [] + for cred in credits: + words = cred.get_maybe_exist_named_child('credit-words') + if((words != None) and hasattr(words, 'default-x')): + default_xs.append(getattr(words, 'default-x')) + return map(round, map(float, default_xs)) + + def get_default_ys(self, credits): + default_ys = [] + for cred in credits: + words = cred.get_maybe_exist_named_child('credit-words') + if((words != None) and hasattr(words, 'default-y')): + default_ys.append(getattr(words, 'default-y')) + return map(round, map(float, default_ys)) + + def get_text(self): + words = self.get_maybe_exist_named_child('credit-words') + if(words != None): + return words.get_text() + else: + return '' + + +class Duration(Music_xml_node): + + def get_length(self): + dur = int(self.get_text()) * Rational(1, 4) + return dur + + +class Hash_text(Music_xml_node): + + 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 + + def get_octave(self): + 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')) + return utilities.interpret_alter_element(ch) + + def to_lily_object(self): + p = musicexp.Pitch() + p.alteration = self.get_alteration() + p.step = musicxml2ly_conversion.musicxml_step_to_lily(self.get_step()) + p.octave = self.get_octave() - 4 + return p - step = ch.get_text ().strip () - return int (step) - def get_alteration (self): - ch = self.get_maybe_exist_typed_child (class_dict[u'alter']) - alter = 0 - if ch: - alter = int (ch.get_text ().strip ()) - return alter +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 + + def get_octave(self): + ch = self.get_unique_typed_child(get_class(u'display-octave')) + + if ch: + octave = ch.get_text().strip() + return int(octave) + else: + return None + + def to_lily_object(self): + p = None + step = self.get_step() + if step: + p = musicexp.Pitch() + p.step = musicxml2ly_conversion.musicxml_step_to_lily(step) + octave = self.get_octave() + if octave and p: + p.octave = octave - 4 + return p + 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 - - def is_first (self): - cn = self._parent.get_typed_children (self.__class__) - cn = [c for c in cn if c.get_voice_id () == self.get_voice_id ()] - return cn[0] == self - -class Attributes (Measure_element): - def __init__ (self): - Measure_element.__init__ (self) - self._dict = {} - - 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[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" + voice = self.get_maybe_exist_named_child ('voice') + if voice: + return voice.get_text () + else: + return self.voice_id; + + +class Attributes(Measure_element): + + def __init__(self): + 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__) + 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) + + 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 + n = 0 + for i in sig[0:-1]: + n += i + return Rational(n, sig[-1]) + + def get_measure_length(self): + 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. 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') - - 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') + mxl = self.get_named_attribute('time') + 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) + 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) + def get_clef_information(self): + clefinfo = ['G', 2, 0] + mxl = self.get_named_attribute('clef') + if not mxl: + return clefinfo + sign = mxl.get_maybe_exist_named_child('sign') if sign: - return sign.get_text () + clefinfo[0] = sign.get_text() + line = mxl.get_maybe_exist_named_child('line') + if line: + clefinfo[1] = int(line.get_text()) + octave = mxl.get_maybe_exist_named_child('clef-octave-change') + if octave: + clefinfo[2] = int(octave.get_text()) + return clefinfo + + def get_key_signature(self): + "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') + 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: - print 'clef requested, but unknow' - return 'G' + 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, utilities.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 Barline(Measure_element): + + def to_lily_object(self): + # retval contains all possible markers in the order: + # 0..bw_ending, 1..bw_repeat, 2..barline, 3..fw_repeat, 4..fw_ending + retval = {} + bartype_element = self.get_maybe_exist_named_child("bar-style") + repeat_element = self.get_maybe_exist_named_child("repeat") + ending_element = self.get_maybe_exist_named_child("ending") + + bartype = None + if bartype_element: + bartype = bartype_element.get_text() + + if repeat_element and hasattr(repeat_element, 'direction'): + repeat = musicxml2ly_conversion.RepeatMarker() + repeat.direction = {"forward":-1, "backward": 1}.get( + repeat_element.direction, 0) + + if((repeat_element.direction == "forward" and bartype == "heavy-light") or + (repeat_element.direction == "backward" and bartype == "light-heavy")): + bartype = None + if hasattr(repeat_element, 'times'): + try: + repeat.times = int(repeat_element.times) + except ValueError: + repeat.times = 2 + repeat.event = self + if repeat.direction == -1: + retval[3] = repeat + else: + retval[1] = repeat + + if ending_element and hasattr(ending_element, 'type'): + ending = musicxml2ly_conversion.EndingMarker() + ending.direction = {"start":-1, "stop": 1, "discontinue": 1}.get( + ending_element.type, 0) + ending.event = self + if ending.direction == -1: + retval[4] = ending + else: + retval[0] = ending + # TODO. ending number="" + + if bartype: + b = musicexp.BarLine() + b.type = bartype + retval[2] = b + + return retval.values() + + +class Partial(Measure_element): + def __init__(self, partial): + Measure_element.__init__(self) + self.partial = partial + + +class Stem(Music_xml_node): + + stem_value_dict = { + 'down': 'stemDown', + 'up': 'stemUp', + 'double': None, # TODO: Implement + 'none': 'stemNeutral' + } + + def to_stem_event(self): + values = [] + value = self.stem_value_dict.get(self.get_text(), None) + stem_value = musicexp.StemEvent() + if value: + stem_value.value = value + values.append(stem_value) + return values + + def to_stem_style_event(self): + styles = [] + style_elm = musicexp.StemstyleEvent() + if hasattr(self, 'color'): + style_elm.color = utilities.hex_to_color(getattr(self, 'color')) + if(style_elm.color != None): + styles.append(style_elm) + return styles + + +class Notehead(Music_xml_node): + + notehead_styles_dict = { + 'slash': '\'slash', + 'triangle': '\'triangle', + 'diamond': '\'diamond', + 'square': '\'la', # TODO: Proper squared note head + 'cross': None, # TODO: + shaped note head + 'x': '\'cross', + 'circle-x': '\'xcircle', + 'inverted triangle': None, # TODO: Implement + 'arrow down': None, # TODO: Implement + 'arrow up': None, # TODO: Implement + 'slashed': None, # TODO: Implement + 'back slashed': None, # TODO: Implement + 'normal': None, + 'cluster': None, # TODO: Implement + 'none': '#f', + 'do': '\'do', + 're': '\'re', + 'mi': '\'mi', + 'fa': '\'fa', + 'so': None, + 'la': '\'la', + 'ti': '\'ti', + } + + def to_lily_object(self): #function changed: additionally processcolor attribute + styles = [] + + # Notehead style + key = self.get_text().strip() + style = self.notehead_styles_dict.get(key, None) + event = musicexp.NotestyleEvent() + if style: + event.style = style + if hasattr(self, 'filled'): + event.filled =(getattr(self, 'filled') == "yes") + if hasattr(self, 'color'): + event.color = utilities.hex_to_color(getattr(self, 'color')) + if event.style or(event.filled != None) or(event.color != None): + styles.append(event) + # parentheses + if hasattr(self, 'parentheses') and(self.parentheses == "yes"): + styles.append(musicexp.ParenthesizeEvent()) + + return styles + + +class Note(Measure_element): + + drumtype_dict = { + 'Acoustic Snare Drum': 'acousticsnare', + 'Side Stick': 'sidestick', + 'Open Triangle': 'opentriangle', + 'Mute Triangle': 'mutetriangle', + 'Tambourine': 'tambourine', + 'Bass Drum': 'bassdrum', + } + + def __init__(self): + Measure_element.__init__(self) + self.instrument_name = '' + self._after_grace = False + self._duration = 1 + + def is_grace(self): + return self.get_maybe_exist_named_child(u'grace') + + def is_after_grace(self): + if not self.is_grace(): + return False; + gr = self.get_maybe_exist_typed_child(Grace) + return self._after_grace or hasattr(gr, 'steal-time-previous'); + + def get_duration_log(self): + ch = self.get_maybe_exist_named_child(u'type') + + if ch: + log = ch.get_text().strip() + return utilities.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 + else: + return None - def get_key_signature (self): - "return (fifths, mode) tuple" + 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_pitches(self): + return self.get_typed_children(get_class(u'pitch')) + + def set_notehead_style(self, event): + noteheads = self.get_named_children('notehead') + for nh in noteheads: + styles = nh.to_lily_object() + for style in styles: + event.add_associated_event(style) + + def set_stem_directions(self, event): + stems = self.get_named_children('stem') + for stem in stems: + values = stem.to_stem_event() + for v in values: + event.add_associated_event(v) + + def set_stem_style(self, event): + stems = self.get_named_children('stem') + for stem in stems: + styles = stem.to_stem_style_event() + for style in styles: + event.add_associated_event(style) + + def initialize_duration(self): + from musicxml2ly_conversion import rational_to_lily_duration + from musicexp import Duration + # if the note has no Type child, then that method returns None. In that case, + # use the tag instead. If that doesn't exist, either -> Error + dur = self.get_duration_info() + if dur: + d = Duration() + d.duration_log = dur[0] + d.dots = dur[1] + # Grace notes by specification have duration 0, so no time modification + # factor is possible. It even messes up the output with *0/1 + if not self.get_maybe_exist_typed_child(Grace): + d.factor = self._duration / d.get_length() + return d + else: + if self._duration > 0: + return rational_to_lily_duration(self._duration) + else: + self.message( + _("Encountered note at %s without type and duration(=%s)") \ + %(mxl_note.start, mxl_note._duration)) + return None + + def initialize_pitched_event(self): + mxl_pitch = self.get_maybe_exist_typed_child(Pitch) + pitch = mxl_pitch.to_lily_object() + event = musicexp.NoteEvent() + event.pitch = pitch + acc = self.get_maybe_exist_named_child('accidental') + if acc: + # let's not force accs everywhere. + event.cautionary = acc.cautionary + # TODO: Handle editorial accidentals + # TODO: Handle the level-display setting for displaying brackets/parentheses + return event + + def initialize_unpitched_event(self): + # Unpitched elements have display-step and can also have + # display-octave. + unpitched = self.get_maybe_exist_typed_child(Unpitched) + event = musicexp.NoteEvent() + event.pitch = unpitched.to_lily_object() + return event + + def initialize_rest_event(self, convert_rest_positions=True): + # rests can have display-octave and display-step, which are + # treated like an ordinary note pitch + rest = self.get_maybe_exist_typed_child(Rest) + event = musicexp.RestEvent() + if convert_rest_positions: + pitch = rest.to_lily_object() + event.pitch = pitch + return event + + def initialize_drum_event(self): + event = musicexp.NoteEvent() + drum_type = self.drumtype_dict.get(self.instrument_name) + if drum_type: + event.drum_type = drum_type + else: + self.message( + _("drum %s type unknown, please add to instrument_drumtype_dict") + % n.instrument_name) + event.drum_type = 'acousticsnare' + return event + + def to_lily_object(self, + convert_stem_directions=True, + convert_rest_positions=True): + pitch = None + duration = None + event = None + + if self.get_maybe_exist_typed_child(Pitch): + event = self.initialize_pitched_event() + elif self.get_maybe_exist_typed_child(Unpitched): + event = self.initialize_unpitched_event() + elif self.get_maybe_exist_typed_child(Rest): + event = self.initialize_rest_event(convert_rest_positions) + elif self.instrument_name: + event = self.initialize_drum_event() + else: + self.message(_("cannot find suitable event")) - key = self.get_named_attribute ('key') - mode_node = key.get_maybe_exist_named_child ('mode') - mode = 'major' - if mode_node: - mode = mode_node.get_text () + if event: + event.duration = self.initialize_duration() - fifths = int (key.get_maybe_exist_named_child ('fifths').get_text ()) - return (fifths, mode) - + self.set_notehead_style(event) + self.set_stem_style(event) + if convert_stem_directions: + self.set_stem_directions(event) -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, - 'breve': -1, - 'long': -2, - 'whole': 0} [log] - else: - return 0 - - def get_factor (self): - return 1 - - def get_pitches (self): - return self.get_typed_children (class_dict[u'pitch']) - -class Part_list (Music_xml_node): - def __init__ (self): - Music_xml_node.__init__ (self) + return event + + +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): + + 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'): + 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 () + name = instr.get_named_child("instrument-name") + mapping[id] = name.get_text() self._id_instrument_name_dict = mapping - def get_instrument (self, id): + 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 + instrument_name = self._id_instrument_name_dict.get(id) + if instrument_name: + return instrument_name + else: + ly.warning(_("Unable to find instrument for ID=%s\n") % 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 = [] - self._staves = {} - self._start_staff = None + def __init__(self): + Music_xml_node.__init__(self) + self.partial = 0 + + 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')) + + +class Syllabic(Music_xml_node): + + def continued(self): + text = self.get_text() + return(text == "begin") or(text == "middle") + + def begin(self): + return(text == "begin") + + def middle(self): + return(text == "middle") + + def end(self): + return(text == "end") + + +class Lyric(Music_xml_node): + + def number(self): + """ + Return the number attribute(if it exists) of the lyric element. + + @rtype: number + @return: The value of the number attribute + """ + if hasattr(self, 'number'): + return self.number + else: + return -1 + + +class Sound(Music_xml_node): + + def get_tempo(self): + """ + Return the tempo attribute(if it exists) of the sound element. + This attribute can be used by musicxml2ly for the midi output(see L{musicexp.Score}). + + @rtype: string + @return: The value of the tempo attribute + """ + if hasattr(self, 'tempo'): + return self.tempo + else: + return None + + +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 + + def get_tuplets(self): + 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())) + + 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 = utilities.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 + + +class Tuplet(Music_xml_spanner): + + 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 = utilities.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 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 () + 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 Slur(Music_xml_spanner): + + def get_type(self): + return self.type + + +class Tied(Music_xml_spanner): + + def get_type(self): + return self.type + + +class Beam(Music_xml_spanner): + def get_type(self): + return self.get_text() + def is_primary(self): + if hasattr(self, 'number'): + return self.number == "1" + else: + return True + +class Octave_shift(Music_xml_spanner): + # default is 8 for the octave-shift! + def get_size(self): + if hasattr(self, 'size'): + return int(self.size) + else: + return 8 + + +# Rests in MusicXML are blocks with a inside. This class is only +# for the inner element, not the whole rest block. +class Rest(Music_xml_node): + + def __init__(self): + Music_xml_node.__init__(self) + self._is_whole_measure = False + + def is_whole_measure(self): + return self._is_whole_measure + + def get_step(self): + ch = self.get_maybe_exist_typed_child(get_class(u'display-step')) + if ch: + 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: + oct = ch.get_text().strip() + return int(oct) + else: + return None + + def to_lily_object(self): + p = None + step = self.get_step() + if step: + p = musicexp.Pitch() + p.step = musicxml2ly_conversion.musicxml_step_to_lily(step) + octave = self.get_octave() + if octave and p: + p.octave = octave - 4 + return p + + +class Bend(Music_xml_node): + + def bend_alter(self): + alter = self.get_maybe_exist_named_child('bend-alter') + return utilities.interpret_alter_element(alter) - if not self._start_staff: - self._start_staff = name - self._staves[name] = True - def insert (self, idx, e): - self._elements.insert (idx, e) +class ChordPitch(Music_xml_node): + def step_class_name(self): + return u'root-step' + def alter_class_name(self): + return u'root-alter' + + def get_step(self): + ch = self.get_unique_typed_child(get_class(self.step_class_name())) + return ch.get_text().strip() + + def get_alteration(self): + ch = self.get_maybe_exist_typed_child(get_class(self.alter_class_name())) + return utilities.interpret_alter_element(ch) + + +class Bass(ChordPitch): + + def step_class_name(self): + return u'bass-step' + + def alter_class_name(self): + return u'bass-alter' + + +class ChordModification(Music_xml_node): + + def get_type(self): + ch = self.get_maybe_exist_typed_child(get_class(u'degree-type')) + return {'add': 1, 'alter': 1, 'subtract':-1}.get(ch.get_text().strip(), 0) + + def get_value(self): + ch = self.get_maybe_exist_typed_child(get_class(u'degree-value')) + value = 0 + if ch: + value = int(ch.get_text().strip()) + return value + + def get_alter(self): + ch = self.get_maybe_exist_typed_child(get_class(u'degree-alter')) + return utilities.interpret_alter_element(ch) + + +class Frame(Music_xml_node): + + def get_frets(self): + return self.get_named_child_value_number('frame-frets', 4) + + def get_strings(self): + return self.get_named_child_value_number('frame-strings', 6) + + def get_first_fret(self): + return self.get_named_child_value_number('first-fret', 1) + + +class Frame_Note(Music_xml_node): + + def get_string(self): + return self.get_named_child_value_number('string', 1) + + def get_fret(self): + return self.get_named_child_value_number('fret', 0) + + def get_fingering(self): + return self.get_named_child_value_number('fingering', -1) + + def get_barre(self): + n = self.get_maybe_exist_named_child('barre') + if n: + return getattr(n, 'type', '') + else: + return '' + + +class Musicxml_voice: + + def __init__(self): + 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() + + 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: + self.has_lyrics = len(lyrics) > 0 + + for l in lyrics: + nr = l.number + if(nr > 0) and not(nr in self._lyrics): + self._lyrics.append(nr) + + def insert(self, idx, e): + self._elements.insert(idx, e) + + def get_lyrics_numbers(self): + if(len(self._lyrics) == 0) and self._has_lyrics: + #only happens if none of the tags has a number attribute + return [1] + else: + return self._lyrics -class Part (Music_xml_node): - def __init__ (self): - Music_xml_node.__init__ (self) - self._voices = [] - def get_part_list (self): +class Part(Music_xml_node): + + def __init__(self): + Music_xml_node.__init__(self) + self._voices = {} + self._staff_attributes_dict = {} + + 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) - attributes_dict = {} + return n.get_named_child('part-list') + + def graces_to_aftergraces(self, pending_graces): + for gr in pending_graces: + gr._when = gr._prev_when + gr._measure_position = gr._prev_measure_position + gr._after_grace = True + + 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 = {} attributes_object = None - measures = self.get_typed_children (Measure) - last_moment = Rational (-1) - last_measure_position = Rational (-1) - for m in measures: - measure_start_moment = now - measure_position = Rational (0) - for n in m.get_all_children (): - dur = Rational (0) + measures = self.get_typed_children(Measure) + last_moment = Rational(-1) + last_measure_position = Rational(-1) + measure_position = Rational(0) + measure_start_moment = now + is_first_measure = True + previous_measure = None + # Graces at the end of a measure need to have their position set to the + # previous number! + pending_graces = [] + 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 + # upbeats(initial value of 0 fits these, too). + # Also, don't reset the measure position at the end of the loop, + # but rather when starting the next measure(since only then do we + # know if the next measure is implicit and continues that measure) + if not m.is_implicit(): + # Warn about possibly overfull measures and reset the position + if attributes_object and previous_measure and previous_measure.partial == 0: + length = attributes_object.get_measure_length() + new_now = measure_start_moment + length + if now <> new_now: + problem = 'incomplete' + if now > new_now: + problem = 'overfull' + ## only for verbose operation. + if problem <> 'incomplete' and previous_measure: + previous_measure.message('%s measure? Expected: %s, Difference: %s' %(problem, now, new_now - now)) + now = new_now + 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): + n._divisions = factor.denominator() + n._when = now + n._measure_position = measure_position + continue + + if isinstance(n, Hash_text): + continue + 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['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 - if n.get_maybe_exist_typed_child (Grace): - dur = Rational (0) - - rest = n.get_maybe_exist_typed_child (Rest) - if (rest + + 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: + self.graces_to_aftergraces(pending_graces) + pending_graces = [] + 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): + and attributes_object.get_measure_length() == dur): rest._is_whole_measure = True - if (dur > Rational (0) - and n.get_maybe_exist_typed_child (Chord)): + if(dur > Rational(0) + and n.get_maybe_exist_typed_child(Chord)): now = last_moment measure_position = last_measure_position - - last_moment = now - last_measure_position = measure_position - n._when = now + n._when = now n._measure_position = measure_position - n._duration = dur - now += dur - measure_position += dur + + # For all grace notes, store the previous note, in case need + # to turn the grace note into an after-grace later on! + if isinstance(n, Note) and n.is_grace(): + n._prev_when = last_moment + 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 + # a measure with no following note!!! + n._when = last_moment + n._measure_position = last_measure_position + elif isinstance(n, Note) and n.is_grace(): + pending_graces.append(n) + elif(dur > Rational(0)): + pending_graces = []; + + n._duration = dur + if dur > Rational(0): + last_moment = now + last_measure_position = measure_position + now += dur + measure_position += dur + elif dur < Rational(0): + # backup element, reset measure position + now += dur + measure_position += dur + if measure_position < 0: + # backup went beyond the measure start => reset to 0 + now -= measure_position + measure_position = 0 + last_moment = now + last_measure_position = measure_position if n._name == 'note': - instrument = n.get_maybe_exist_named_child ('instrument') + 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' - - ## only for verbose operation. - if problem <> 'incomplete': - m.message ('%s measure? Expected: %s, Difference: %s' % (problem, now, new_now - now)) - - now = new_now - - def extract_voices (part): - voices = {} - measures = part.get_typed_children (Measure) - elements = [] - for m in measures: - elements.extend (m.get_all_children ()) - - start_attr = None - for n in elements: - voice_id = n.get_maybe_exist_typed_child (class_dict['voice']) - - if not (voice_id or isinstance (n, Attributes)): - continue - - if isinstance (n, Attributes) and not start_attr: - start_attr = n - continue - - if isinstance (n, Attributes): - for v in voices.values (): - v.add_element (n) - continue + n.instrument_name = part_list.get_instrument(instrument.id) + + # reset all graces at the end of the measure to after-graces: + self.graces_to_aftergraces(pending_graces) + pending_graces = [] + # Incomplete first measures are not padded, but registered as partial + if is_first_measure: + is_first_measure = False + # upbeats are marked as implicit measures + if attributes_object and m.is_implicit(): + length = attributes_object.get_measure_length() + measure_end = measure_start_moment + length + if measure_end <> now: + m.partial = now + previous_measure = m + + # modify attributes so that only those applying to the given staff remain + def extract_attributes_for_staff(part, attr, staff): + attributes = copy.copy(attr) + attributes._children = []; + 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))): + attributes._children.append(c) + if not attributes._children: + return None + else: + return attributes + + def extract_voices(part): + # The last indentified voice + last_voice = None + + 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()) + # make sure we know all voices already so that dynamics, clefs, etc. + # can be assigned to the correct voices + voice_to_staff_dict = {} + for n in elements: + voice_id = n.get_maybe_exist_named_child(u'voice') + vid = None + 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? + if n.get_maybe_exist_named_child(u'chord'): + vid = last_voice + else: + vid = "1" + + if(vid != None): + last_voice = vid + + staff_id = n.get_maybe_exist_named_child(u'staff') + sid = None + 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() + if vid and sid and not n.get_maybe_exist_typed_child(Grace): + if not voice_to_staff_dict.has_key(vid): + voice_to_staff_dict[vid] = sid + + # invert the voice_to_staff_dict into a staff_to_voice_dict(since we + # need to assign staff-assigned objects like clefs, times, etc. to + # all the correct voices. This will never work entirely correct due + # to staff-switches, but that's the best we can do! + staff_to_voice_dict = {} + for(v, s) in voice_to_staff_dict.items(): + if not staff_to_voice_dict.has_key(s): + staff_to_voice_dict[s] = [v] + else: + staff_to_voice_dict[s].append(v) + + start_attr = None + assign_to_next_note = [] + id = None + for n in elements: + voice_id = n.get_maybe_exist_typed_child(get_class('voice')) + if voice_id: + id = voice_id.get_text() + else: + if n.get_maybe_exist_typed_child(get_class('chord')): + id = last_voice + else: + id = "1" + + if(id != "None"): + last_voice = id + + # 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 + isinstance(n, Barline) or isinstance(n, Harmony) or + isinstance(n, FiguredBass) or isinstance(n, Print)): + 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 belong to! + for(s, vids) in staff_to_voice_dict.items(): + staff_attributes = part.extract_attributes_for_staff(n, s) + if staff_attributes: + for v in vids: + voices[v].add_element(staff_attributes) + continue + + if isinstance(n, Partial) or isinstance(n, Barline) or isinstance(n, Print): + for v in voices.keys(): + voices[v].add_element(n) + continue + + if isinstance(n, Direction): + if (n.voice_id): + voices[n.voice_id].add_element (n) + else: + 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 + # 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. + pass + else: + for i in assign_to_next_note: + voices[id].add_element(i) + assign_to_next_note = [] + voices[id].add_element(n) + + # Assign all remaining elements from assign_to_next_note to the voice + # of the previous note: + for i in assign_to_next_note: + voices[id].add_element(i) + assign_to_next_note = [] + + 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() + part._staff_attributes_dict[s] = staff_attributes + for v in vids: + voices[v].insert(0, staff_attributes) + voices[v]._elements[0].read_self() + + part._voices = voices + + def get_voices(self): + return self._voices + + def get_staff_attributes(self): + return self._staff_attributes_dict + + +class BarStyle(Music_xml_node): + pass - id = voice_id.get_text () - if not voices.has_key (id): - voices[id] = Musicxml_voice() +class BeatType(Music_xml_node): + pass - voices[id].add_element (n) +class BeatUnit(Music_xml_node): + pass - if start_attr: - for (k,v) in voices.items (): - v.insert (0, start_attr) +class BeatUnitDot(Music_xml_node): + pass - part._voices = voices +class Beats(Music_xml_node): + pass - def get_voices (self): - return self._voices +class Bracket(Music_xml_spanner): + pass -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 +class Chord(Music_xml_node): + pass - def get_tuplet (self): - return self.get_maybe_exist_typed_child (Tuplet) +class Dashes(Music_xml_spanner): + pass -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 (int(a.get_text ()), int (b.get_text ())) +class DirType(Music_xml_node): + pass -class Accidental (Music_xml_node): - def __init__ (self): - Music_xml_node.__init__ (self) - self.editorial = False - self.cautionary = False +class Direction(Measure_element): + pass +class Dot(Music_xml_node): + pass -class Tuplet(Music_xml_node): +class Elision(Music_xml_node): pass -class Slur (Music_xml_node): - def get_type (self): - return self.type +class Extend(Music_xml_node): + pass -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): +class FiguredBass(Music_xml_node): pass -class Dot (Music_xml_node): +class Glissando(Music_xml_spanner): pass -class Alter (Music_xml_node): +class Grace(Music_xml_node): pass -class Rest (Music_xml_node): - def __init__ (self): - Music_xml_node.__init__ (self) - self._is_whole_measure = False - def is_whole_measure (self): - return self._is_whole_measure +class Harmony(Music_xml_node): + pass -class Mode (Music_xml_node): +class Hash_comment(Music_xml_node): pass -class Tied (Music_xml_node): + +class KeyAlter(Music_xml_node): pass -class Type (Music_xml_node): +class Direction (Measure_element): pass -class Grace (Music_xml_node): + +class KeyOctave(Music_xml_node): pass -class Staff (Music_xml_node): + +class KeyStep(Music_xml_node): pass -class Instrument (Music_xml_node): +class Part_group(Music_xml_node): pass -class Fermata (Music_xml_node): +class Pedal(Music_xml_spanner): pass -class Dynamics (Music_xml_node): + +class PerMinute(Music_xml_node): pass -class Articulations (Music_xml_node): + +class Print(Music_xml_node): pass -class Accent (Music_xml_node): + +class Root(ChordPitch): pass -class Staccato (Music_xml_node): + +class Score_part(Music_xml_node): pass -class Tenuto (Music_xml_node): + +class Slide(Music_xml_spanner): pass -class Tremolo (Music_xml_node): + +class Staff(Music_xml_node): pass -class Technical (Music_xml_node): + +class Text(Music_xml_node): pass -class Ornaments (Music_xml_node): + +class Type(Music_xml_node): pass +class Wavy_line(Music_xml_spanner): + pass -class Direction (Music_xml_node): +class Wedge(Music_xml_spanner): pass -class DirType (Music_xml_node): + +class Words(Music_xml_node): pass ## need this, not all classes are instantiated -## for every input file. +## 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, - 'accidental': Accidental, - 'alter': Alter, - 'attributes': Attributes, - 'beam' : Beam, - 'chord': Chord, - 'dot': Dot, - 'duration': Duration, - 'grace': Grace, - 'instrument': Instrument, - 'mode' : Mode, - 'measure': Measure, - 'notations': Notations, - 'note': Note, - 'part': Part, - 'pitch': Pitch, - 'rest':Rest, - 'slur': Slur, - 'tied': Tied, - 'time-modification': Time_modification, - 'tuplet': Tuplet, - 'type': Type, - 'part-list': Part_list, - 'staff': Staff, - 'fermata': Fermata, - 'articulations': Articulations, - 'accent': Accent, - 'staccato': Staccato, - 'tenuto': Tenuto, - 'tremolo': Tremolo, - 'technical': Technical, - 'ornaments': Ornaments, + '#comment': Hash_comment, + '#text': Hash_text, + 'accidental': Accidental, + 'attributes': Attributes, + 'barline': Barline, + 'bar-style': BarStyle, + 'bass': Bass, + 'beam' : Beam, + 'beats': Beats, + 'beat-type': BeatType, + 'beat-unit': BeatUnit, + 'beat-unit-dot': BeatUnitDot, + 'bend' : Bend, + 'bracket' : Bracket, + 'chord': Chord, + 'credit': Credit, + 'dashes' : Dashes, + 'degree' : ChordModification, + 'dot': Dot, 'direction': Direction, - 'direction-type': DirType + 'direction-type': DirType, + 'duration': Duration, + 'elision': Elision, + 'extend': Extend, + 'frame': Frame, + 'frame-note': Frame_Note, + 'figured-bass': FiguredBass, + 'glissando': Glissando, + 'grace': Grace, + 'harmony': Harmony, + 'identification': Identification, + 'key-alter': KeyAlter, + 'key-octave': KeyOctave, + 'key-step': KeyStep, + 'lyric': Lyric, + 'measure': Measure, + 'notations': Notations, + 'note': Note, + 'notehead': Notehead, + 'octave-shift': Octave_shift, + 'part': Part, + 'part-group': Part_group, + 'part-list': Part_list, + 'pedal': Pedal, + 'per-minute': PerMinute, + 'pitch': Pitch, + 'print': Print, + 'rest': Rest, + 'root': Root, + 'score-part': Score_part, + 'slide': Slide, + 'slur': Slur, + 'sound': Sound, + 'staff': Staff, + 'stem': Stem, + 'syllabic': Syllabic, + 'text': Text, + 'time-modification': Time_modification, + 'tied': Tied, + 'tuplet': Tuplet, + 'type': Type, + 'unpitched': Unpitched, + 'wavy-line': Wavy_line, + 'wedge': Wedge, + 'words': Words, + 'work': Work, } -def name2class_name (name): - name = name.replace ('-', '_') - name = name.replace ('#', 'hash_') +def name2class_name(name): + name = name.replace('-', '_') + name = name.replace('#', 'hash_') name = name[0].upper() + name[1:].lower() - return str (name) + return str(name) -def get_class (name): - try: - return class_dict[name] - except KeyError: - class_name = name2class_name (name) - klass = new.classobj (class_name, (Music_xml_node,) , {}) - class_dict[name] = klass +def get_class(name): + classname = class_dict.get(name) + if classname: + return classname + else: + class_name = name2class_name(name) + klass = new.classobj(class_name,(Music_xml_node,) , {}) + class_dict[name] = klass return klass - -def lxml_demarshal_node (node): + +def lxml_demarshal_node(node): name = node.tag - if name is None: + # Ignore comment nodes, which are also returned by the etree parser! + if name is None or node.__class__.__name__ == "_Comment": return None - klass = get_class (name) + 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) - + 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 (): + for(k, v) in node.items(): py_node.__dict__[k] = v py_node._attribute_dict[k] = v return py_node -def minidom_demarshal_node (node): +def minidom_demarshal_node(node): name = node.nodeName - klass = get_class (name) + klass = get_class(name) py_node = klass() py_node._name = name - py_node._children = [minidom_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 + 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 -if __name__ == '__main__': - 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) +if __name__ == '__main__': + 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)