k-l-lambda's picture
added node-addon-lilypond
f65fe85
# musicxml.py
# -*- coding: utf-8 -*-
#
# This file is part of LilyPond, the GNU music typesetter.
#
# Copyright (C) 2005--2020 Han-Wen Nienhuys <[email protected]>,
# 2007-2011 Reinhold Kainhofer <[email protected]>
#
# 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))