Source code for ly.musicxml.create_musicxml

# This file is part of python-ly, https://pypi.python.org/pypi/python-ly
#
# Copyright (c) 2008 - 2015 by Wilbert Berendsen
#
# This program 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 2
# of the License, or (at your option) any later version.
#
# This program 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 this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
# See http://www.gnu.org/licenses/ for more information.

"""
Uses xml.etree to create a Music XML document.

Example::

    musxml = create_musicxml.CreateMusicXML()
    musxml.create_part()
    musxml.create_measure(divs=1)
    musxml.new_note('C', 4, 'whole', 4)
    xml = musxml.musicxml()
    xml.write(filename)

"""

from __future__ import unicode_literals
from __future__ import division

import sys
try:
    import xml.etree.cElementTree as etree
except ImportError:
    import xml.etree.ElementTree as etree


import ly.pkginfo

from .midi_sound_map import midi_sound_map

[docs]class CreateMusicXML(): """ Creates the XML nodes according to the Music XML standard.""" def __init__(self): """Creates the basic structure of the XML without any music.""" self.root = etree.Element("score-partwise", version="3.0") self.tree = etree.ElementTree(self.root) self.score_info = etree.SubElement(self.root, "identification") encoding = etree.SubElement(self.score_info, "encoding") software = etree.SubElement(encoding, "software") software.text = ly.pkginfo.name + " " + ly.pkginfo.version encoding_date = etree.SubElement(encoding, "encoding-date") import datetime encoding_date.text = str(datetime.date.today()) self.partlist = etree.SubElement(self.root, "part-list") self.part_count = 1 ## # Building the basic Elements ##
[docs] def create_title(self, title): """Create score title.""" mov_title = etree.Element("movement-title") mov_title.text = title self.root.insert(0, mov_title)
[docs] def create_score_info(self, tag, info, attr={}): """Create score info.""" info_node = etree.SubElement(self.score_info, tag, attr) info_node.text = info
[docs] def create_partgroup(self, gr_type, num, name=None, abbr=None, symbol=None): """Create a new part group.""" attr_dict = {"type": gr_type, "number": str(num)} partgroup = etree.SubElement(self.partlist, "part-group", attr_dict) if name: group_name = etree.SubElement(partgroup, "group-name") group_name.text = name if abbr: group_abbr = etree.SubElement(partgroup, "group-abbreviation") group_abbr.text = abbr if symbol: group_symbol = etree.SubElement(partgroup, "group-symbol") group_symbol.text = symbol
[docs] def create_part(self, name="unnamed", abbr=False, midi=False): """Create a new part """ strnr = str(self.part_count) part = etree.SubElement(self.partlist, "score-part", id="P"+strnr) partname = etree.SubElement(part, "part-name") partname.text = name if abbr: partabbr = etree.SubElement(part, "part-abbreviation") partabbr.text = abbr if midi: scoreinstr = etree.SubElement(part, "score-instrument", id="P"+strnr+"-I"+strnr) instrname = etree.SubElement(scoreinstr, "instrument-name") instrname.text = midi if midi in midi_sound_map and midi_sound_map[midi]: instrsound = etree.SubElement(scoreinstr, "instrument-sound") instrsound.text = midi_sound_map[midi] midiinstr = etree.SubElement(part, "midi-instrument", id="P"+strnr+"-I"+strnr) midich = etree.SubElement(midiinstr, "midi-channel") midich.text = strnr midiname = etree.SubElement(midiinstr, "midi-name") midiname.text = midi self.current_part = etree.SubElement(self.root, "part", id="P"+strnr) self.part_count += 1 self.bar_nr = 1
[docs] def create_measure(self, pickup = False, **bar_attrs): """Create new measure """ if pickup and self.bar_nr == 1: self.bar_nr = 0 self.current_bar = etree.SubElement(self.current_part, "measure", number=str(self.bar_nr)) self.bar_nr +=1 if bar_attrs: self.new_bar_attr(**bar_attrs)
## # High-level node creation ##
[docs] def new_note(self, step, octave, durtype, divdur, alter=0, acc_token=0, voice=1, dot=0, chord=0, grace=(0, 0), stem_dir=0): """Create all nodes needed for a normal note. """ self.create_note() if grace[0]: self.add_grace(grace[1]) if chord: self.add_chord() self.add_pitch(step, alter, octave) if not grace[0]: self.add_div_duration(divdur) self.add_voice(voice) self.add_duration_type(durtype) if dot: for i in range(dot): self.add_dot() if alter or acc_token: if acc_token == '!': # cautionary self.add_accidental(alter, caut=True) elif acc_token == '?': # parentheses self.add_accidental(alter, parenth=True) else: self.add_accidental(alter) if stem_dir: self.set_stem_dir(stem_dir)
[docs] def new_unpitched_note(self, step, octave, durtype, divdur, voice=1, dot=0, chord=0, grace=(0, 0)): """Create all nodes needed for an unpitched note. """ self.create_note() if grace[0]: self.add_grace(grace[1]) if chord: self.add_chord() self.add_unpitched(step, octave) if not grace[0]: self.add_div_duration(divdur) self.add_duration_type(durtype) self.add_voice(voice) if dot: for i in range(dot): self.add_dot()
[docs] def tuplet_note(self, fraction, bs, ttype, nr, divs, atyp='', ntyp=''): """Convert current note to tuplet """ base = self.mult * bs[0] scaling = bs[1] a = divs*4*fraction[1] b = (1/base)*fraction[0] duration = (a/b)*scaling self.change_div_duration(duration) from fractions import Fraction self.mult = Fraction(fraction[1], fraction[0]) timemod_node = self.get_time_modify() if timemod_node: self.adjust_time_modify(timemod_node, fraction) else: self.add_time_modify(fraction) if ttype: self.add_notations() if atyp and ttype != "stop": self.add_tuplet_type(nr, ttype, fraction[0], atyp, fraction[1], ntyp) else: self.add_tuplet_type(nr, ttype)
[docs] def tie_note(self, tie_type): self.add_tie(tie_type) self.add_notations() self.add_tied(tie_type)
[docs] def new_rest(self, duration, durtype, pos, dot, voice): """Create all nodes needed for a rest. """ self.create_note() if pos: self.add_rest_w_pos(pos[0], pos[1]) else: self.add_rest() self.add_div_duration(duration) self.add_voice(voice) if durtype: self.add_duration_type(durtype) if dot: for i in range(dot): self.add_dot()
[docs] def new_articulation(self, artic): """Add specified articulation. """ self.add_notations() self.add_articulations() self.add_named_artic(artic)
[docs] def new_simple_ornament(self, ornament): """Add specified ornament. """ self.add_notations() self.add_ornaments() func_call = getattr(self, 'add_'+ornament) func_call()
[docs] def new_adv_ornament(self, ornament, args): """Add more complex ornament.""" self.add_notations() self.add_ornaments() if ornament == "wavy-line": self.add_wavyline(args['type'])
[docs] def new_bar_attr(self, clef=0, mustime=0, key=None, mode=0, divs=0, multirest=0): """Create all bar attributes set. """ self.create_bar_attr() if divs: self.add_divisions(divs) if key is not None: self.add_key(key, mode) if mustime: self.add_time(mustime) if clef: sign, line, octch = clef self.add_clef(sign, line, oct_ch=octch) if multirest: self.add_bar_style(multirest)
[docs] def create_tempo(self, words, metronome, sound, dots): self.add_direction() if words: self.add_dirwords(words) if metronome: self.add_metron_dir(metronome[0], metronome[1], dots) self.add_sound_dir(sound)
[docs] def create_new_node(self, parentnode, nodename, txt): """ The Music XML language is extensive. This function can be used to create a non basic node not covered elsewhere in this script. TODO: add attributes """ new_node = etree.SubElement(parentnode, nodename) new_node.text = str(txt)
## # Low-level node creation ##
[docs] def add_creator(self, creator, name): """Add creator to score info.""" attr = {'type': creator } self.create_score_info("creator", name, attr)
[docs] def add_rights(self, rights, type=None): """Add rights to score info.""" attr = {'type': type} if type else {} self.create_score_info("rights", rights, attr)
[docs] def create_note(self): """Create new note.""" self.current_note = etree.SubElement(self.current_bar, "note") self.current_notation = None self.current_artic = None self.current_ornament = None self.current_tech = None
[docs] def add_pitch(self, step, alter, octave): """Create new pitch.""" pitch = etree.SubElement(self.current_note, "pitch") stepnode = etree.SubElement(pitch, "step") stepnode.text = str(step) if alter: altnode = etree.SubElement(pitch, "alter") altnode.text = str(alter) octnode = etree.SubElement(pitch, "octave") octnode.text = str(octave)
[docs] def add_unpitched(self, step, octave): """Create unpitched.""" unpitched = etree.SubElement(self.current_note, "unpitched") stepnode = etree.SubElement(unpitched, "display-step") stepnode.text = str(step) octnode = etree.SubElement(unpitched, "display-octave") octnode.text = str(octave)
[docs] def add_accidental(self, alter, caut=False, parenth=False): """Create accidental.""" attrib = {} if caut: attrib['cautionary'] = 'yes' if parenth: attrib['parentheses'] = 'yes' acc = etree.SubElement(self.current_note, "accidental", attrib) acc_dict = { 0: 'natural', 1: 'sharp', -1: 'flat', 2: 'sharp-sharp', -2: 'flat-flat', 0.5: 'natural-up', -0.5: 'natural-down', 1.5: 'sharp-up', -1.5: 'flat-down' } acc.text = acc_dict[alter]
[docs] def set_stem_dir(self, dir): stem_dir = etree.SubElement(self.current_note, "stem") stem_dir.text = dir
[docs] def add_rest(self): """Create rest.""" etree.SubElement(self.current_note, "rest")
[docs] def add_rest_w_pos(self, step, octave): """Create rest with display position.""" restnode = etree.SubElement(self.current_note, "rest") stepnode = etree.SubElement(restnode, "display-step") octnode = etree.SubElement(restnode, "display-octave") stepnode.text = str(step) octnode.text = str(octave)
[docs] def add_skip(self, duration, forward=True): if forward: skip = etree.SubElement(self.current_bar, "forward") else: skip = etree.SubElement(self.current_bar, "backward") dura_node = etree.SubElement(skip, "duration") dura_node.text = str(duration)
[docs] def add_div_duration(self, divdur): """Create new duration """ self.duration = etree.SubElement(self.current_note, "duration") self.duration.text = str(divdur) self.mult = 1
[docs] def change_div_duration(self, newdura): """Set new duration when tuplet """ self.duration.text = str(newdura)
[docs] def add_duration_type(self, durtype): """Create new type """ typenode = etree.SubElement(self.current_note, "type") typenode.text = str(durtype)
[docs] def add_dot(self): """Create a dot """ etree.SubElement(self.current_note, "dot")
[docs] def add_beam(self, nr, beam_type): """Add beam. """ beam_node = etree.SubElement(self.current_notation, "beam", number=str(nr)) beam_node.text = beam_type
[docs] def add_tie(self, tie_type): """Create node tie (used for sound of tie) """ # A tie must be directly after a duration insert_at = get_tag_index(self.current_note, "duration") + 1 tie_element = etree.Element("tie", type=tie_type) self.current_note.insert(insert_at, tie_element)
[docs] def add_grace(self, slash): """Create grace node """ if slash: etree.SubElement(self.current_note, "grace", slash="yes") else: etree.SubElement(self.current_note, "grace")
[docs] def add_notations(self): if not self.current_notation: self.current_notation = etree.SubElement(self.current_note, "notations")
[docs] def add_tied(self, tie_type): """Create node tied (used for notation of tie) """ etree.SubElement(self.current_notation, "tied", type=tie_type)
[docs] def add_time_modify(self, fraction): """Create time modification """ index = get_tag_index(self.current_note, "accidental") if index == -1: index = get_tag_index(self.current_note, "dot") if index == -1: index = get_tag_index(self.current_note, "type") timemod_node = etree.Element("time-modification") actual_notes = etree.SubElement(timemod_node, "actual-notes") actual_notes.text = str(fraction[0]) norm_notes = etree.SubElement(timemod_node, "normal-notes") norm_notes.text = str(fraction[1]) self.current_note.insert(index + 1, timemod_node)
[docs] def get_time_modify(self): """Check if time-modification node already exists.""" return self.current_note.find("time-modification")
[docs] def adjust_time_modify(self, timemod_node, fraction): """Adjust existing time-modification node.""" actual_notes = timemod_node.find("actual-notes") an = int(actual_notes.text) * fraction[0] actual_notes.text = str(an) norm_notes = timemod_node.find("normal-notes") nn = int(norm_notes.text) * fraction[1] norm_notes.text = str(nn)
[docs] def add_tuplet_type(self, nr, ttype, actnr=0, acttype='', normnr=0, normtype=''): """Create tuplet with type attribute """ tuplnode = etree.SubElement(self.current_notation, "tuplet", {'number': str(nr), 'type': ttype }) if actnr: actnode = etree.SubElement(tuplnode, "tuplet-actual") atn = etree.SubElement(actnode, "tuplet-number") atn.text = str(actnr) att = etree.SubElement(actnode, "tuplet-type") if not acttype: acttype = self.current_note.find("type").text att.text = acttype if normnr: normnode = etree.SubElement(tuplnode, "tuplet-normal") ntn = etree.SubElement(normnode, "tuplet-number") ntn.text = str(normnr) ntt = etree.SubElement(normnode, "tuplet-type") if not normtype: normtype = self.current_note.find("type").text ntt.text = normtype
[docs] def add_slur(self, nr, sl_type): """Add slur. """ self.add_notations() etree.SubElement(self.current_notation, "slur", {'number': str(nr), 'type': sl_type })
[docs] def add_named_notation(self, notate): """Fermata, etc. """ self.add_notations() etree.SubElement(self.current_notation, notate)
[docs] def add_articulations(self): """Common function for creating all types of articulations. """ if not self.current_artic: self.current_artic = etree.SubElement(self.current_notation, "articulations")
[docs] def add_named_artic(self, artic): """Add articulation with specified name. """ etree.SubElement(self.current_artic, artic)
[docs] def add_ornaments(self): if not self.current_ornament: self.add_notations() self.current_ornament = etree.SubElement(self.current_notation, "ornaments")
[docs] def add_tremolo(self, trem_type, lines): self.add_ornaments() trem_node = etree.SubElement(self.current_ornament, "tremolo", type=trem_type) trem_node.text = str(lines)
[docs] def add_trill(self): etree.SubElement(self.current_ornament, "trill-mark")
[docs] def add_turn(self): etree.SubElement(self.current_ornament, "turn")
[docs] def add_mordent(self): etree.SubElement(self.current_ornament, "mordent")
[docs] def add_prall(self): etree.SubElement(self.current_ornament, "inverted-mordent")
[docs] def add_wavyline(self, end_type): self.add_ornaments etree.SubElement(self.current_ornament, "wavy-line", type=end_type)
[docs] def add_gliss(self, linetype, endtype, nr): nodedict = { "line-type": linetype, "number": str(nr), "type": endtype } self.add_notations() etree.SubElement(self.current_notation, "glissando", nodedict)
[docs] def add_technical(self): if not self.current_tech: self.add_notations() self.current_tech = etree.SubElement(self.current_notation, "technical")
[docs] def add_fingering(self, finger_nr): self.add_technical() fing_node = etree.SubElement(self.current_tech, "fingering") fing_node.text = str(finger_nr)
[docs] def create_bar_attr(self): """Create node attributes """ self.bar_attr = etree.SubElement(self.current_bar, "attributes")
[docs] def add_divisions(self, div): division = etree.SubElement(self.bar_attr, "divisions") division.text = str(div)
[docs] def add_key(self, key, mode): keynode = etree.SubElement(self.bar_attr, "key") fifths = etree.SubElement(keynode, "fifths") fifths.text = str(key) modenode = etree.SubElement(keynode, "mode") modenode.text = str(mode)
[docs] def add_time(self, timesign): if len(timesign)==3: timenode = etree.SubElement(self.bar_attr, "time", symbol=timesign[2]) else: timenode = etree.SubElement(self.bar_attr, "time") beatnode = etree.SubElement(timenode, "beats") beatnode.text = str(timesign[0]) typenode = etree.SubElement(timenode, "beat-type") typenode.text = str(timesign[1])
[docs] def add_clef(self, sign, line, nr=0, oct_ch=0): if nr: clefnode = etree.SubElement(self.bar_attr, "clef", number=str(nr)) else: clefnode = etree.SubElement(self.bar_attr, "clef") signnode = etree.SubElement(clefnode, "sign") signnode.text = str(sign) if line: linenode = etree.SubElement(clefnode, "line") linenode.text = str(line) if oct_ch: octchnode = etree.SubElement(clefnode, "clef-octave-change") octchnode.text = str(oct_ch)
[docs] def add_bar_style(self, multirest=None): bar_style = etree.SubElement(self.bar_attr, "measure-style") if multirest: multirestnode = etree.SubElement(bar_style, "multiple-rest") multirestnode.text = str(multirest)
[docs] def new_system(self, force_break): etree.SubElement(self.current_bar, "print", {'new-system':force_break})
[docs] def add_barline(self, bl_type, repeat=None): barnode = etree.SubElement(self.current_bar, "barline", location="right") barstyle = etree.SubElement(barnode, "bar-style") barstyle.text = bl_type if repeat: repeatnode = etree.SubElement(barnode, "repeat", direction=repeat)
[docs] def add_backup(self, duration): if duration <= 0: return backupnode = etree.SubElement(self.current_bar, "backup") durnode = etree.SubElement(backupnode, "duration") durnode.text = str(duration)
[docs] def add_voice(self, voice): voicenode = etree.SubElement(self.current_note, "voice") voicenode.text = str(voice)
[docs] def add_staff(self, staff): staffnode = etree.SubElement(self.current_note, "staff") staffnode.text = str(staff)
[docs] def add_staves(self, staves): index = get_tag_index(self.bar_attr, "time") stavesnode = etree.Element("staves") stavesnode.text = str(staves) self.bar_attr.insert(index + 1, stavesnode)
[docs] def add_chord(self): etree.SubElement(self.current_note, "chord")
[docs] def add_direction(self, pos="above"): self.direction = etree.SubElement(self.current_bar, "direction", placement=pos)
[docs] def add_dynamic_mark(self, dyn): """Add specified dynamic mark.""" direction = etree.SubElement(self.current_bar, "direction", placement='below') dirtypenode = etree.SubElement(direction, "direction-type") dyn_node = etree.SubElement(dirtypenode, "dynamics") dynexpr_node = etree.SubElement(dyn_node, dyn)
[docs] def add_dynamic_wedge(self, wedge_type): """Add dynamic wedge/hairpin.""" direction = etree.SubElement(self.current_bar, "direction", placement='below') dirtypenode = etree.SubElement(direction, "direction-type") dyn_node = etree.SubElement(dirtypenode, "wedge", type=wedge_type)
[docs] def add_dynamic_text(self, text): """Add dynamic text.""" direction = etree.SubElement(self.current_bar, "direction", placement='below') dirtypenode = etree.SubElement(direction, "direction-type") dyn_node = etree.SubElement(dirtypenode, "words") dyn_node.attrib['font-style'] = 'italic' dyn_node.text = text
[docs] def add_dynamic_dashes(self, text): """Add dynamics dashes.""" direction = etree.SubElement(self.current_bar, "direction", placement='below') dirtypenode = etree.SubElement(direction, "direction-type") dyn_node = etree.SubElement(dirtypenode, "dashes", type=text)
[docs] def add_octave_shift(self, plac, octdir, size): """Add octave shift.""" oct_dict = {"type": octdir, "size": str(size) } direction = etree.SubElement(self.current_bar, "direction", placement=plac) dirtypenode = etree.SubElement(direction, "direction-type") dyn_node = etree.SubElement(dirtypenode, "octave-shift", oct_dict)
[docs] def add_dirwords(self, words): """Add words in direction, e. g. a tempo mark.""" if self.current_bar.find('direction') == None: self.add_direction() dirtypenode = etree.SubElement(self.direction, "direction-type") wordsnode = etree.SubElement(dirtypenode, "words") wordsnode.text = words
[docs] def add_mark(self, mark): """Add rehearsal mark in direction""" if self.current_bar.find('direction') == None: self.add_direction() dirtypenode = etree.SubElement(self.direction, "direction-type") rehearsalnode = etree.SubElement(dirtypenode, "rehearsal") rehearsalnode.text = mark
[docs] def add_metron_dir(self, unit, beats, dots): dirtypenode = etree.SubElement(self.direction, "direction-type") metrnode = etree.SubElement(dirtypenode, "metronome") bunode = etree.SubElement(metrnode, "beat-unit") bunode.text = unit if dots: for d in range(dots): etree.SubElement(metrnode, "beat-unit-dot") pmnode = etree.SubElement(metrnode, "per-minute") pmnode.text = str(beats)
[docs] def add_sound_dir(self, midi_tempo): # FIXME: remove the int conversion once LilyPond accepts decimal tempo soundnode = etree.SubElement(self.direction, "sound", tempo=str(int(midi_tempo)))
[docs] def add_lyric(self, txt, syll, nr, ext=False): """ Add lyric element. """ lyricnode = etree.SubElement(self.current_note, "lyric", number=str(nr)) syllnode = etree.SubElement(lyricnode, "syllabic") syllnode.text = syll txtnode = etree.SubElement(lyricnode, "text") txtnode.text = txt if ext: etree.SubElement(lyricnode, "extend")
## # Create the XML document ##
[docs] def musicxml(self, prettyprint=True): xml = MusicXML(self.tree) if prettyprint: xml.indent(" ") return xml
[docs]class MusicXML(object): """Represent a generated MusicXML tree.""" def __init__(self, tree): self.tree = tree self.root = tree.getroot()
[docs] def indent(self, indent=" "): """Add indent and linebreaks to the created XML tree.""" import ly.etreeutil ly.etreeutil.indent(self.root, indent)
[docs] def tostring(self, encoding='UTF-8'): """Output etree as a XML document.""" return etree.tostring(self.root, encoding=encoding, method="xml")
[docs] def write(self, file, encoding='UTF-8', doctype=True): """Write XML to a file (file obj or filename).""" def write(f): if doctype: f.write((xml_decl_txt + "\n").format(encoding=encoding).encode(encoding)) f.write((doctype_txt + "\n").encode(encoding)) self.tree.write(f, encoding=encoding, xml_declaration=False) else: self.tree.write(f, encoding=encoding, xml_declaration=True, method="xml") if hasattr(file, 'write'): write(file) # do not close if it already was a file object else: # it is not a file object with open(file, 'wb') as f: write(f)
[docs]def get_tag_index(node, tag): """Return the (first) index of tag in node. If tag is not found, -1 is returned. """ for i, elem in enumerate(list(node)): if elem.tag == tag: return i return -1
xml_decl_txt = """<?xml version="1.0" encoding="{encoding}"?>""" doctype_txt = """<!DOCTYPE score-partwise PUBLIC "-//Recordare//DTD MusicXML 2.0 Partwise//EN" "http://www.musicxml.org/dtds/partwise.dtd">"""