Spaces:
Sleeping
Sleeping
| # musicxml.py | |
| # -*- coding: utf-8 -*- | |
| # | |
| # This file is part of LilyPond, the GNU music typesetter. | |
| # | |
| # Copyright (C) 2005--2020 Han-Wen Nienhuys <hanwen@xs4all.nl>, | |
| # 2007-2011 Reinhold Kainhofer <reinhold@kainhofer.com> | |
| # | |
| # LilyPond is free software: you can redistribute it and/or modify | |
| # it under the terms of the GNU General Public License as published by | |
| # the Free Software Foundation, either version 3 of the License, or | |
| # (at your option) any later version. | |
| # | |
| # LilyPond is distributed in the hope that it will be useful, | |
| # but WITHOUT ANY WARRANTY; without even the implied warranty of | |
| # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
| # GNU General Public License for more details. | |
| # | |
| # You should have received a copy of the GNU General Public License | |
| # along with LilyPond. If not, see <http://www.gnu.org/licenses/>. | |
| import copy | |
| from fractions import Fraction | |
| import re | |
| import sys | |
| import warnings | |
| import lilylib as ly | |
| import musicexp | |
| import musicxml2ly_conversion | |
| import utilities | |
| class Xml_node(object): | |
| 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): | |
| 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 get_text(self): | |
| if self._data: | |
| return self._data | |
| if not self._children: | |
| return '' | |
| return ''.join([c.get_text() for c in self._children]) | |
| def message(self, msg): | |
| ly.warning(msg) | |
| p = self | |
| while p: | |
| ly.progress(' In: <%s %s>\n' % (p._name, ' '.join( | |
| ['%s=%s' % item for item in list(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 list(self._attribute_dict.items())]))) | |
| non_text_children = [ | |
| c for c in self._children if not isinstance(c, Hash_text)] | |
| if non_text_children: | |
| ly.debug_output('\n') | |
| for c in self._children: | |
| c.dump(indent + " ") | |
| if non_text_children: | |
| ly.debug_output(indent) | |
| ly.debug_output('</%s>\n' % self._name) | |
| def get_typed_children(self, klass): | |
| if not klass: | |
| return [] | |
| else: | |
| return [c for c in self._children if isinstance(c, klass)] | |
| def get_named_children(self, nm): | |
| return self.get_typed_children(get_class(nm)) | |
| 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_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_typed_child(self, klass): | |
| cn = self.get_typed_children(klass) | |
| if len(cn) == 0: | |
| return None | |
| else: | |
| if len(cn) > 1: | |
| warnings.warn(_('more than one child of class %s, all but' | |
| 'the first will be ignored') % klass.__name__) | |
| 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 RuntimeError( | |
| 'Child is not unique for %s found %d' % (klass, 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 | |
| class Music_xml_node(Xml_node): | |
| def __init__(self): | |
| Xml_node.__init__(self) | |
| self.duration = Fraction(0) | |
| self.start = Fraction(0) | |
| self.converted = False | |
| self.voice_id = None | |
| class Music_xml_spanner(Music_xml_node): | |
| def get_type(self): | |
| if hasattr(self, 'type'): | |
| return self.type | |
| else: | |
| return 0 | |
| def get_size(self): | |
| if hasattr(self, 'size'): | |
| return int(self.size) | |
| else: | |
| return 0 | |
| 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 "\n".join(ret) | |
| # 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 "\n".join(ret) | |
| 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 is not 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 is not 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([i for i, item in enumerate(ys) if 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 is not None) and hasattr(words, 'font-size')): | |
| sizes.append(getattr(words, 'font-size')) | |
| return list(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 is not None) and hasattr(words, 'default-x')): | |
| default_xs.append(getattr(words, 'default-x')) | |
| return list(map(round, list(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 is not None and hasattr(words, 'default-y'): | |
| default_ys.append(getattr(words, 'default-y')) | |
| return list(map(round, list(map(float, default_ys)))) | |
| def get_text(self): | |
| words = self.get_maybe_exist_named_child('credit-words') | |
| if words is not None: | |
| return words.get_text() | |
| else: | |
| return '' | |
| class Duration(Music_xml_node): | |
| def get_length(self): | |
| dur = int(self.get_text()) * Fraction(1, 4) | |
| return dur | |
| class Hash_text(Music_xml_node): | |
| def dump(self, indent=''): | |
| ly.debug_output(self._data.strip()) | |
| class Pitch(Music_xml_node): | |
| def get_step(self): | |
| ch = self.get_unique_typed_child(get_class('step')) | |
| step = ch.get_text().strip() | |
| return step | |
| def get_octave(self): | |
| ch = self.get_unique_typed_child(get_class('octave')) | |
| octave = ch.get_text().strip() | |
| return int(octave) | |
| def get_alteration(self): | |
| ch = self.get_maybe_exist_typed_child(get_class('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 | |
| class Unpitched(Music_xml_node): | |
| def get_step(self): | |
| ch = self.get_unique_typed_child(get_class('display-step')) | |
| step = ch.get_text().strip() | |
| return step | |
| def get_octave(self): | |
| ch = self.get_unique_typed_child(get_class('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 = 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 Fraction(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') | |
| 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 = i.get_text().strip().split("+") | |
| 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: | |
| 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: | |
| 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 list(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 is not 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 is not None) or (event.color is not None): | |
| styles.append(event) | |
| # parentheses | |
| if hasattr(self, 'parentheses') and (self.parentheses == "yes"): | |
| styles.append(musicexp.ParenthesizeEvent()) | |
| return styles | |
| class Note(Measure_element): | |
| 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('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('type') | |
| if ch: | |
| log = ch.get_text().strip() | |
| return utilities.musicxml_duration_to_log(log) | |
| elif self.get_maybe_exist_named_child('grace'): | |
| # FIXME: is it ok to default to eight note for grace notes? | |
| return 3 | |
| else: | |
| return None | |
| def get_duration_info(self): | |
| log = self.get_duration_log() | |
| if log is not 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('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 <duration> 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 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) | |
| else: | |
| self.message(_("cannot find suitable event")) | |
| if event: | |
| event.duration = self.initialize_duration() | |
| self.set_notehead_style(event) | |
| self.set_stem_style(event) | |
| if convert_stem_directions: | |
| self.set_stem_directions(event) | |
| 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): | |
| # not empty to make sure this happens only once. | |
| mapping = {1: 1} | |
| for score_part in self.get_named_children('score-part'): | |
| for instr in score_part.get_named_children('score-instrument'): | |
| id = instr.id | |
| name = instr.get_named_child("instrument-name") | |
| mapping[id] = name.get_text() | |
| self._id_instrument_name_dict = mapping | |
| def get_instrument(self, id): | |
| if not self._id_instrument_name_dict: | |
| self.generate_id_instrument_dict() | |
| 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 __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('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 get_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 int(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 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 <note> blocks with a <rest> inside. This class is only | |
| # for the inner <rest> 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('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('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) | |
| class ChordPitch(Music_xml_node): | |
| def step_class_name(self): | |
| return 'root-step' | |
| def alter_class_name(self): | |
| return '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 'bass-step' | |
| def alter_class_name(self): | |
| return 'bass-alter' | |
| class ChordModification(Music_xml_node): | |
| def get_type(self): | |
| ch = self.get_maybe_exist_typed_child(get_class('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('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('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.get_number() | |
| if nr > 0 and nr not 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 <lyric> 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 = {} | |
| 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 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 = Fraction(0) | |
| factor = Fraction(1) | |
| attributes_dict = {} | |
| attributes_object = None | |
| measures = self.get_typed_children(Measure) | |
| last_moment = Fraction(-1) | |
| last_measure_position = Fraction(-1) | |
| measure_position = Fraction(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 = Fraction(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 = Fraction(0) | |
| if n.__class__ == Attributes: | |
| n.set_attributes_from_previous(attributes_dict) | |
| n.read_self() | |
| attributes_dict = n._dict.copy() | |
| attributes_object = n | |
| factor = Fraction(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 = Fraction(0) | |
| rest = n.get_maybe_exist_typed_child(Rest) | |
| if(rest | |
| and attributes_object | |
| and attributes_object.get_measure_length() == dur): | |
| rest._is_whole_measure = True | |
| if(dur > Fraction(0) | |
| and n.get_maybe_exist_typed_child(Chord)): | |
| now = last_moment | |
| measure_position = last_measure_position | |
| n._when = now | |
| n._measure_position = measure_position | |
| # 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 > Fraction(0): | |
| pending_graces = [] | |
| n._duration = dur | |
| if dur > Fraction(0): | |
| last_moment = now | |
| last_measure_position = measure_position | |
| now += dur | |
| measure_position += dur | |
| elif dur < Fraction(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') | |
| if instrument: | |
| 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') or 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('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('chord'): | |
| vid = last_voice | |
| else: | |
| vid = "1" | |
| if vid is not None: | |
| last_voice = vid | |
| staff_id = n.get_maybe_exist_named_child('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 vid not in voices: | |
| voices[vid] = Musicxml_voice() | |
| if vid and sid and not n.get_maybe_exist_typed_child(Grace): | |
| if vid not in voice_to_staff_dict: | |
| 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 list(voice_to_staff_dict.items()): | |
| if s not in staff_to_voice_dict: | |
| 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 list(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 list(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 list(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 | |
| class BeatType(Music_xml_node): | |
| pass | |
| class BeatUnit(Music_xml_node): | |
| pass | |
| class BeatUnitDot(Music_xml_node): | |
| pass | |
| class Beats(Music_xml_node): | |
| pass | |
| class Bracket(Music_xml_spanner): | |
| pass | |
| class Chord(Music_xml_node): | |
| pass | |
| class Dashes(Music_xml_spanner): | |
| pass | |
| class DirType(Music_xml_node): | |
| pass | |
| class Direction(Measure_element): | |
| pass | |
| class Dot(Music_xml_node): | |
| pass | |
| class Elision(Music_xml_node): | |
| pass | |
| class Extend(Music_xml_node): | |
| pass | |
| class FiguredBass(Music_xml_node): | |
| pass | |
| class Glissando(Music_xml_spanner): | |
| pass | |
| class Grace(Music_xml_node): | |
| pass | |
| class Harmony(Music_xml_node): | |
| pass | |
| class Hash_comment(Music_xml_node): | |
| pass | |
| class KeyAlter(Music_xml_node): | |
| pass | |
| class Direction (Measure_element): | |
| pass | |
| class KeyOctave(Music_xml_node): | |
| pass | |
| class KeyStep(Music_xml_node): | |
| pass | |
| class Part_group(Music_xml_node): | |
| pass | |
| class Pedal(Music_xml_spanner): | |
| pass | |
| class PerMinute(Music_xml_node): | |
| pass | |
| class Print(Music_xml_node): | |
| pass | |
| class Root(ChordPitch): | |
| pass | |
| class Score_part(Music_xml_node): | |
| pass | |
| class Slide(Music_xml_spanner): | |
| pass | |
| class Staff(Music_xml_node): | |
| pass | |
| class Text(Music_xml_node): | |
| pass | |
| class Type(Music_xml_node): | |
| pass | |
| class Wavy_line(Music_xml_spanner): | |
| pass | |
| class Wedge(Music_xml_spanner): | |
| pass | |
| class Words(Music_xml_node): | |
| pass | |
| # need this, not all classes are instantiated | |
| # for every input file. Only add those classes, that are either directly | |
| # used by class name or extend Music_xml_node in some way! | |
| class_dict = { | |
| '#comment': Hash_comment, | |
| '#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, | |
| '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_') | |
| name = name[0].upper() + name[1:].lower() | |
| return str(name) | |
| def get_class(name): | |
| classname = class_dict.get(name) | |
| if classname: | |
| return classname | |
| else: | |
| class_name = name2class_name(name) | |
| klass = type(class_name, (Music_xml_node,), {}) | |
| class_dict[name] = klass | |
| return klass | |
| def lxml_demarshal_node(node): | |
| name = node.tag | |
| # 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) | |
| 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 = [x for x in py_node._children if x] | |
| for c in py_node._children: | |
| c._parent = py_node | |
| for(k, v) in list(node.items()): | |
| py_node.__dict__[k] = v | |
| py_node._attribute_dict[k] = v | |
| return py_node | |
| def minidom_demarshal_node(node): | |
| name = node.nodeName | |
| klass = get_class(name) | |
| py_node = klass() | |
| py_node._name = name | |
| py_node._children = [minidom_demarshal_node(cn) for cn in node.childNodes] | |
| for c in py_node._children: | |
| c._parent = py_node | |
| if node.attributes: | |
| for(nm, value) in list(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._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 = sorted(class_dict.keys()) | |
| print('\n'.join(ks)) | |