k-l-lambda's picture
added node-addon-lilypond
f65fe85
#!@TARGET_PYTHON@
# -*- coding: utf-8 -*-
#
# This file is part of LilyPond, the GNU music typesetter.
#
# Copyright (C) 2005--2020 Han-Wen Nienhuys <[email protected]>,
# Jan Nieuwenhuizen <[email protected]>,
# Reinhold Kainhofer <[email protected]>,
# Patrick L. Schmidt <[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 codecs
from fractions import Fraction
from functools import reduce
import gettext
import io
import optparse
import os
import re
import sys
import tempfile
import warnings
import zipfile
"""
@relocate-preamble@
"""
import musicexp
import musicxml
import musicxml2ly_conversion
import utilities
# Load translation and install _() into Python's builtins namespace.
gettext.install('lilypond', '@localedir@')
import lilylib as ly
lilypond_version = "@TOPLEVEL_VERSION@"
# Store command-line options in a global variable, so we can access them everywhere
options = None
class Conversion_Settings:
def __init__(self):
self.ignore_beaming = False
self.convert_stem_directions = False
self.convert_rest_positions = True
conversion_settings = Conversion_Settings()
# Use a global variable to store the setting needed inside a \layout block.
# whenever we need to change a setting or add/remove an engraver, we can access
# this layout and add the corresponding settings
layout_information = musicexp.Layout()
# Use a global variable to store the setting needed inside a \paper block.
paper = musicexp.Paper()
needed_additional_definitions = []
additional_definitions = {
"tuplet-note-wrapper": """ % a formatter function, which is simply a wrapper around an existing
% tuplet formatter function. It takes the value returned by the given
% function and appends a note of given length.
#(define-public ((tuplet-number::append-note-wrapper function note) grob)
(let* ((txt (if function (function grob) #f)))
(if txt
(markup txt #:fontsize -5 #:note note UP)
(markup #:fontsize -5 #:note note UP)
)
)
)""",
"tuplet-non-default-denominator": """#(define ((tuplet-number::non-default-tuplet-denominator-text denominator) grob)
(number->string (if denominator
denominator
(ly:event-property (event-cause grob) 'denominator))))
""",
"tuplet-non-default-fraction": """#(define ((tuplet-number::non-default-tuplet-fraction-text denominator numerator) grob)
(let* ((ev (event-cause grob))
(den (if denominator denominator (ly:event-property ev 'denominator)))
(num (if numerator numerator (ly:event-property ev 'numerator))))
(format #f "~a:~a" den num)))
""",
}
def round_to_two_digits(val):
return round(val * 100) / 100
def extract_paper_information(score_partwise):
defaults = score_partwise.get_maybe_exist_named_child('defaults')
if not defaults:
return None
tenths = -1
scaling = defaults.get_maybe_exist_named_child('scaling')
default_tenths_to_millimeters_ratio = 0.175
default_staff_size = 20
if scaling:
mm = scaling.get_named_child('millimeters')
mm = float(mm.get_text())
tn = scaling.get_maybe_exist_named_child('tenths')
tn = float(tn.get_text())
# The variable 'tenths' is actually a ratio, NOT the value of <tenths>.
# TODO: rename and replace.
tenths = mm / tn
ratio = tenths / default_tenths_to_millimeters_ratio
staff_size = default_staff_size * ratio
if 1 < staff_size < 100:
paper.global_staff_size = staff_size
else:
msg = "paper.global_staff_size %s is too large, using defaults=20" % staff_size
warnings.warn(msg)
paper.global_staff_size = 20
# We need the scaling(i.e. the size of staff tenths for everything!
if tenths < 0:
return None
def from_tenths(txt):
return round_to_two_digits(float(txt) * tenths / 10)
def set_paper_variable(varname, parent, element_name):
el = parent.get_maybe_exist_named_child(element_name)
if el: # Convert to cm from tenths
setattr(paper, varname, from_tenths(el.get_text()))
pagelayout = defaults.get_maybe_exist_named_child('page-layout')
if pagelayout:
# TODO: How can one have different margins for even and odd pages???
set_paper_variable("page_height", pagelayout, 'page-height')
set_paper_variable("page_width", pagelayout, 'page-width')
if conversion_settings.convert_page_margins:
pmargins = pagelayout.get_named_children('page-margins')
for pm in pmargins:
set_paper_variable("left_margin", pm, 'left-margin')
set_paper_variable("right_margin", pm, 'right-margin')
set_paper_variable("bottom_margin", pm, 'bottom-margin')
set_paper_variable("top_margin", pm, 'top-margin')
systemlayout = defaults.get_maybe_exist_named_child('system-layout')
if systemlayout:
sl = systemlayout.get_maybe_exist_named_child('system-margins')
if sl:
set_paper_variable("system_left_margin", sl, 'left-margin')
set_paper_variable("system_right_margin", sl, 'right-margin')
set_paper_variable("system_distance", systemlayout, 'system-distance')
set_paper_variable("top_system_distance",
systemlayout, 'top-system-distance')
stafflayout = defaults.get_named_children('staff-layout')
for sl in stafflayout:
nr = getattr(sl, 'number', 1)
dist = sl.get_named_child('staff-distance')
# TODO: the staff distance needs to be set in the Staff context!!!
# TODO: Finish appearance?, music-font?, word-font?, lyric-font*, lyric-language*
appearance = defaults.get_named_child('appearance')
if appearance:
lws = appearance.get_named_children('line-width')
for lw in lws:
# Possible types are: beam, bracket, dashes,
# enclosure, ending, extend, heavy barline, leger,
# light barline, octave shift, pedal, slur middle, slur tip,
# staff, stem, tie middle, tie tip, tuplet bracket, and wedge
tp = lw.type
w = from_tenths(lw.get_text())
# TODO: Do something with these values!
nss = appearance.get_named_children('note-size')
for ns in nss:
# Possible types are: cue, grace and large
tp = ns.type
sz = from_tenths(ns.get_text())
# TODO: Do something with these values!
# <other-appearance> elements have no specified meaning
rawmusicfont = defaults.get_named_child('music-font')
if rawmusicfont:
# TODO: Convert the font
pass
rawwordfont = defaults.get_named_child('word-font')
if rawwordfont:
# TODO: Convert the font
pass
rawlyricsfonts = defaults.get_named_children('lyric-font')
for lyricsfont in rawlyricsfonts:
# TODO: Convert the font
pass
return paper
credit_dict = {
None: None,
'': None,
'page number': None, # TODO: what is it used for ?
'title': 'title',
'subtitle': 'subtitle',
'composer': 'composer',
'arranger': 'arranger',
'lyricist': 'poet',
'rights': 'copyright'
}
# score information is contained in the <work>, <identification> or <movement-title> tags
# extract those into a hash, indexed by proper lilypond header attributes
def extract_score_information(tree):
header = musicexp.Header()
def set_if_exists(field, value):
if value:
header.set_field(field, utilities.escape_ly_output_string(value))
movement_title = tree.get_maybe_exist_named_child('movement-title')
movement_number = tree.get_maybe_exist_named_child('movement-number')
if movement_title:
set_if_exists('title', movement_title.get_text())
if movement_number:
set_if_exists('movementnumber', movement_number.get_text())
# set_if_exists('piece', movement_number.get_text()) # the movement number should be visible in the score.
work = tree.get_maybe_exist_named_child('work')
if work:
work_number = work.get_work_number()
work_title = work.get_work_title()
# Overwrite the title from movement-title with work->title
set_if_exists('title', work.get_work_title())
set_if_exists('opus', work.get_work_number())
# Use movement-title as subtitle
if movement_title:
set_if_exists('subtitle', movement_title.get_text())
# TODO: Translation of opus element. Not to be confused with opus in LilyPond. MusicXML opus is a document element for opus DTD
identifications = tree.get_named_children('identification')
for ids in identifications:
set_if_exists('copyright', ids.get_rights())
set_if_exists('composer', ids.get_composer())
set_if_exists('arranger', ids.get_arranger())
set_if_exists('editor', ids.get_editor())
set_if_exists('poet', ids.get_poet())
set_if_exists('encodingsoftware', ids.get_encoding_software())
set_if_exists('encodingdate', ids.get_encoding_date())
set_if_exists('encoder', ids.get_encoding_person())
set_if_exists('encodingdescription', ids.get_encoding_description())
set_if_exists('source', ids.get_source())
# <miscellaneous><miscellaneous-field name="description"> ... becomes
# \header { texidoc = ...
set_if_exists('texidoc', ids.get_file_description())
# Finally, apply the required compatibility modes
# Some applications created wrong MusicXML files, so we need to
# apply some compatibility mode, e.g. ignoring some features/tags
# in those files
software = ids.get_encoding_software_list()
# Case 1: "Sibelius 5.1" with the "Dolet 3.4 for Sibelius" plugin
# is missing all beam ends => ignore all beaming information
ignore_beaming_software = {
"Dolet 4 for Sibelius, Beta 2": "Dolet 4 for Sibelius, Beta 2",
"Dolet 3.5 for Sibelius": "Dolet 3.5 for Sibelius",
"Dolet 3.4 for Sibelius": "Dolet 3.4 for Sibelius",
"Dolet 3.3 for Sibelius": "Dolet 3.3 for Sibelius",
"Dolet 3.2 for Sibelius": "Dolet 3.2 for Sibelius",
"Dolet 3.1 for Sibelius": "Dolet 3.1 for Sibelius",
"Dolet for Sibelius 1.3": "Dolet for Sibelius 1.3",
"Noteworthy Composer": "Noteworthy Composer's nwc2xm[",
}
for s in software:
app_description = ignore_beaming_software.get(s, False)
if app_description:
conversion_settings.ignore_beaming = True
ly.warning(_("Encountered file created by %s, containing "
"wrong beaming information. All beaming "
"information in the MusicXML file will be "
"ignored") % app_description)
credits = tree.get_named_children('credit')
has_composer = False
for cred in credits:
type = credit_dict.get(cred.get_type())
if type is None:
type = credit_dict.get(cred.find_type(credits))
if type == 'composer':
if has_composer:
type = 'poet'
else:
has_composer = True
set_if_exists(type, cred.get_text())
elif type == 'title':
if not work and not movement_title:
set_if_exists('title', cred.get_text())
# elif(not(movement_title)): #bullshit!
# set_if_exists('subtitle', cred.get_text()) #bullshit! otherwise both title and subtitle show the work-title.
elif type is None:
pass
else:
set_if_exists(type, cred.get_text())
# TODO: Check for other unsupported features
return header
class PartGroupInfo:
def __init__(self):
self.start = {}
self.end = {}
def is_empty(self):
return len(self.start) + len(self.end) == 0
def add_start(self, g):
self.start[getattr(g, 'number', "1")] = g
def add_end(self, g):
self.end[getattr(g, 'number', "1")] = g
def print_ly(self, printer):
ly.warning(_("Unprocessed PartGroupInfo %s encountered") % self)
def ly_expression(self):
ly.warning(_("Unprocessed PartGroupInfo %s encountered") % self)
return ''
def staff_attributes_to_string_tunings(mxl_attr):
details = mxl_attr.get_maybe_exist_named_child('staff-details')
if not details:
return []
lines = 6
staff_lines = details.get_maybe_exist_named_child('staff-lines')
if staff_lines:
lines = int(staff_lines.get_text())
tunings = [musicexp.Pitch()] * lines
staff_tunings = details.get_named_children('staff-tuning')
for i in staff_tunings:
p = musicexp.Pitch()
line = 0
try:
line = int(i.line) - 1
except ValueError:
pass
tunings[line] = p
step = i.get_named_child('tuning-step')
step = step.get_text().strip()
p.step = musicxml2ly_conversion.musicxml_step_to_lily(step)
octave = i.get_named_child('tuning-octave')
octave = octave.get_text().strip()
p.octave = int(octave) - 4
alter = i.get_named_child('tuning-alter')
if alter:
p.alteration = int(alter.get_text().strip())
# lilypond seems to use the opposite ordering than MusicXML...
tunings.reverse()
return tunings
def staff_attributes_to_lily_staff(mxl_attr):
if not mxl_attr:
return musicexp.Staff()
(staff_id, attributes) = list(mxl_attr.items())[0]
# distinguish by clef:
# percussion(percussion and rhythmic), tab, and everything else
clef_sign = None
clef = attributes.get_maybe_exist_named_child('clef')
if clef:
sign = clef.get_maybe_exist_named_child('sign')
if sign:
clef_sign = {"percussion": "percussion",
"TAB": "tab"}.get(sign.get_text(), None)
lines = 5
details = attributes.get_named_children('staff-details')
for d in details:
staff_lines = d.get_maybe_exist_named_child('staff-lines')
if staff_lines:
lines = int(staff_lines.get_text())
# TODO: Handle other staff attributes like staff-space, etc.
staff = None
if clef_sign == "percussion" and lines == 1:
staff = musicexp.RhythmicStaff()
elif clef_sign == "percussion":
staff = musicexp.DrumStaff()
# staff.drum_style_table = ???
elif clef_sign == "tab":
staff = musicexp.TabStaff()
staff.string_tunings = staff_attributes_to_string_tunings(attributes)
# staff.tablature_format = ???
else:
staff = musicexp.Staff()
# TODO: Handle case with lines != 5!
if lines != 5:
staff.add_context_modification(
"\\override StaffSymbol #'line-count = #%s" % lines)
return staff
def extract_instrument_sound(score_part):
score_instrument = score_part.get_maybe_exist_named_child(
'score-instrument')
if not score_instrument:
return None
sound = score_instrument.get_maybe_exist_named_child('instrument-sound')
if sound:
return utilities.musicxml_sound_to_lilypond_midi_instrument(sound.get_text())
def extract_score_structure(part_list, staffinfo):
score = musicexp.Score()
structure = musicexp.StaffGroup(None)
score.set_contents(structure)
if not part_list:
return structure
def read_score_part(el):
if not isinstance(el, musicxml.Score_part):
return
# Depending on the attributes of the first measure, we create different
# types of staves(Staff, RhythmicStaff, DrumStaff, TabStaff, etc.)
staff = staff_attributes_to_lily_staff(staffinfo.get(el.id, None))
if not staff:
return None
staff.id = el.id
partname = el.get_maybe_exist_named_child('part-name')
# Finale gives unnamed parts the name "MusicXML Part" automatically!
if partname and partname.get_text() != "MusicXML Part":
staff.instrument_name = partname.get_text()
# part-name-display overrides part-name!
partname = el.get_maybe_exist_named_child("part-name-display")
if partname:
staff.instrument_name = extract_display_text(partname)
if hasattr(options, 'midi') and options.midi:
staff.sound = extract_instrument_sound(el)
if staff.instrument_name:
paper.indent = max(paper.indent, len(staff.instrument_name))
paper.instrument_names.append(staff.instrument_name)
partdisplay = el.get_maybe_exist_named_child('part-abbreviation')
if partdisplay:
staff.short_instrument_name = partdisplay.get_text()
# part-abbreviation-display overrides part-abbreviation!
partdisplay = el.get_maybe_exist_named_child(
"part-abbreviation-display")
if partdisplay:
staff.short_instrument_name = extract_display_text(partdisplay)
# TODO: Read in the MIDI device / instrument
if staff.short_instrument_name:
paper.short_indent = max(
paper.short_indent, len(staff.short_instrument_name))
return staff
def read_score_group(el):
if not isinstance(el, musicxml.Part_group):
return
group = musicexp.StaffGroup()
if hasattr(el, 'number'):
id = el.number
group.id = id
#currentgroups_dict[id] = group
# currentgroups.append(id)
if el.get_maybe_exist_named_child('group-name'):
group.instrument_name = el.get_maybe_exist_named_child(
'group-name').get_text()
if el.get_maybe_exist_named_child('group-abbreviation'):
group.short_instrument_name = el.get_maybe_exist_named_child(
'group-abbreviation').get_text()
if el.get_maybe_exist_named_child('group-symbol'):
group.symbol = el.get_maybe_exist_named_child(
'group-symbol').get_text()
if el.get_maybe_exist_named_child('group-barline'):
group.spanbar = el.get_maybe_exist_named_child(
'group-barline').get_text()
return group
parts_groups = part_list.get_all_children()
# the start/end group tags are not necessarily ordered correctly and groups
# might even overlap, so we can't go through the children sequentially!
# 1) Replace all Score_part objects by their corresponding Staff objects,
# also collect all group start/stop points into one PartGroupInfo object
staves = []
group_info = PartGroupInfo()
for el in parts_groups:
if isinstance(el, musicxml.Score_part):
if not group_info.is_empty():
staves.append(group_info)
group_info = PartGroupInfo()
staff = read_score_part(el)
if staff:
staves.append(staff)
elif isinstance(el, musicxml.Part_group):
if el.type == "start":
group_info.add_start(el)
elif el.type == "stop":
group_info.add_end(el)
if not group_info.is_empty():
staves.append(group_info)
# 2) Now, detect the groups:
group_starts = []
pos = 0
while pos < len(staves):
el = staves[pos]
if isinstance(el, PartGroupInfo):
prev_start = 0
if len(group_starts) > 0:
prev_start = group_starts[-1]
elif len(el.end) > 0: # no group to end here
el.end = {}
if len(el.end) > 0: # closes an existing group
ends = list(el.end.keys())
prev_started = list(staves[prev_start].start.keys())
grpid = None
intersection = [x for x in prev_started if x in ends]
if len(intersection) > 0:
grpid = intersection[0]
else:
# Close the last started group
grpid = list(staves[prev_start].start.keys())[0]
# Find the corresponding closing tag and remove it!
j = pos + 1
foundclosing = False
while j < len(staves) and not foundclosing:
if isinstance(staves[j], PartGroupInfo) and grpid in staves[j].end:
foundclosing = True
del staves[j].end[grpid]
if staves[j].is_empty():
del staves[j]
j += 1
grpobj = staves[prev_start].start[grpid]
group = read_score_group(grpobj)
# remove the id from both the start and end
if grpid in el.end:
del el.end[grpid]
del staves[prev_start].start[grpid]
if el.is_empty():
del staves[pos]
# replace the staves with the whole group
for j in staves[(prev_start + 1):pos]:
group.append_staff(j)
del staves[(prev_start + 1):pos]
staves.insert(prev_start + 1, group)
# reset pos so that we continue at the correct position
pos = prev_start
# remove an empty start group
if staves[prev_start].is_empty():
del staves[prev_start]
group_starts.remove(prev_start)
pos -= 1
elif len(el.start) > 0: # starts new part groups
group_starts.append(pos)
pos += 1
for i in staves:
structure.append_staff(i)
return score
def musicxml_partial_to_lily(partial_len):
if partial_len > 0:
p = musicexp.Partial()
p.partial = musicxml2ly_conversion.rational_to_lily_duration(
partial_len)
return p
else:
return None
# Detect repeats and alternative endings in the chord event list(music_list)
# and convert them to the corresponding musicexp objects, containing nested
# music
def group_repeats(music_list):
repeat_replaced = True
music_start = 0
i = 0
# Walk through the list of expressions, looking for repeat structure
# (repeat start/end, corresponding endings). If we find one, try to find the
# last event of the repeat, replace the whole structure and start over again.
# For nested repeats, as soon as we encounter another starting repeat bar,
# treat that one first, and start over for the outer repeat.
while repeat_replaced and i < 100:
i += 1
repeat_start = -1 # position of repeat start / end
repeat_end = -1 # position of repeat start / end
repeat_times = 0
ending_start = -1 # position of current ending start
endings = [] # list of already finished endings
pos = 0
last = len(music_list) - 1
repeat_replaced = False
final_marker = 0
while pos < len(music_list) and not repeat_replaced:
e = music_list[pos]
repeat_finished = False
if isinstance(e, musicxml2ly_conversion.RepeatMarker):
if not repeat_times and e.times:
repeat_times = e.times
if e.direction == -1:
if repeat_end >= 0:
repeat_finished = True
else:
repeat_start = pos
repeat_end = -1
ending_start = -1
endings = []
elif e.direction == 1:
if repeat_start < 0:
repeat_start = 0
if repeat_end < 0:
repeat_end = pos
final_marker = pos
elif isinstance(e, musicxml2ly_conversion.EndingMarker):
if e.direction == -1:
if repeat_start < 0:
repeat_start = 0
if repeat_end < 0:
repeat_end = pos
ending_start = pos
elif e.direction == 1:
if ending_start < 0:
ending_start = 0
endings.append([ending_start, pos])
ending_start = -1
final_marker = pos
elif not isinstance(e, musicexp.BarLine):
# As soon as we encounter an element when repeat start and end
# is set and we are not inside an alternative ending,
# this whole repeat structure is finished => replace it
if repeat_start >= 0 and repeat_end > 0 and ending_start < 0:
repeat_finished = True
# Finish off all repeats without explicit ending bar(e.g. when
# we convert only one page of a multi-page score with repeats)
if pos == last and repeat_start >= 0:
repeat_finished = True
final_marker = pos
if repeat_end < 0:
repeat_end = pos
if ending_start >= 0:
endings.append([ending_start, pos])
ending_start = -1
if repeat_finished:
# We found the whole structure replace it!
r = musicexp.RepeatedMusic()
if repeat_times <= 0:
repeat_times = 2
r.repeat_count = repeat_times
# don't erase the first element for "implicit" repeats(i.e. no
# starting repeat bars at the very beginning)
start = repeat_start + 1
if repeat_start == music_start:
start = music_start
r.set_music(music_list[start:repeat_end])
for(start, end) in endings:
s = musicexp.SequentialMusic()
s.elements = music_list[start + 1:end]
r.add_ending(s)
del music_list[repeat_start:final_marker + 1]
music_list.insert(repeat_start, r)
repeat_replaced = True
pos += 1
# TODO: Implement repeats until the end without explicit ending bar
return music_list
# Extract the settings for tuplets from the <notations><tuplet> and the
# <time-modification> elements of the note:
def musicxml_tuplet_to_lily(tuplet_elt, time_modification):
tsm = musicexp.TimeScaledMusic()
fraction = (1, 1)
if time_modification:
fraction = time_modification.get_fraction()
tsm.numerator = fraction[0]
tsm.denominator = fraction[1]
normal_type = tuplet_elt.get_normal_type()
if not normal_type and time_modification:
normal_type = time_modification.get_normal_type()
if not normal_type and time_modification:
note = time_modification.get_parent()
if note:
normal_type = note.get_duration_info()
if normal_type:
normal_note = musicexp.Duration()
(normal_note.duration_log, normal_note.dots) = normal_type
tsm.normal_type = normal_note
actual_type = tuplet_elt.get_actual_type()
if actual_type:
actual_note = musicexp.Duration()
(actual_note.duration_log, actual_note.dots) = actual_type
tsm.actual_type = actual_note
# Obtain non-default nrs of notes from the tuplet object!
tsm.display_numerator = tuplet_elt.get_normal_nr()
tsm.display_denominator = tuplet_elt.get_actual_nr()
if hasattr(tuplet_elt, 'bracket') and tuplet_elt.bracket == "no":
tsm.display_bracket = None
elif hasattr(tuplet_elt, 'line-shape') and getattr(tuplet_elt, 'line-shape') == "curved":
tsm.display_bracket = "curved"
else:
tsm.display_bracket = "bracket"
display_values = {"none": None, "actual": "actual", "both": "both"}
if hasattr(tuplet_elt, "show-number"):
tsm.display_number = display_values.get(
getattr(tuplet_elt, "show-number"), "actual")
if hasattr(tuplet_elt, "show-type"):
tsm.display_type = display_values.get(
getattr(tuplet_elt, "show-type"), None)
return tsm
def group_tuplets(music_list, events):
"""Collect Musics from
MUSIC_LIST demarcated by EVENTS_LIST in TimeScaledMusic objects.
"""
indices = []
brackets = {}
j = 0
for(ev_chord, tuplet_elt, time_modification) in events:
while j < len(music_list):
if music_list[j] == ev_chord:
break
j += 1
nr = 0
if hasattr(tuplet_elt, 'number'):
nr = getattr(tuplet_elt, 'number')
if tuplet_elt.type == 'start':
tuplet_object = musicxml_tuplet_to_lily(
tuplet_elt, time_modification)
tuplet_info = [j, None, tuplet_object]
indices.append(tuplet_info)
brackets[nr] = tuplet_info
elif tuplet_elt.type == 'stop':
bracket_info = brackets.get(nr, None)
if bracket_info:
bracket_info[1] = j # Set the ending position to j
del brackets[nr]
new_list = []
last = 0
for(i1, i2, tsm) in indices:
if i1 > i2:
continue
new_list.extend(music_list[last:i1])
seq = musicexp.SequentialMusic()
last = i2 + 1
# At this point music_list[i1:last] encompasses all the notes of the
# tuplet. There might be dynamics following this range, however, which
# apply to the last note of the tuplet. Advance last to include them
# in the range.
while last < len(music_list) and isinstance(music_list[last], musicexp.DynamicsEvent):
last += 1
seq.elements = music_list[i1:last]
tsm.element = seq
new_list.append(tsm)
# TODO: Handle nested tuplets!!!!
new_list.extend(music_list[last:])
return new_list
def musicxml_clef_to_lily(attributes):
change = musicexp.ClefChange()
(change.type, change.position, change.octave) = attributes.get_clef_information()
return change
def musicxml_time_to_lily(attributes):
change = musicexp.TimeSignatureChange()
# time signature function
if hasattr(options, 'shift_meter') and options.shift_meter:
tmp_meter = options.shift_meter.split("/", 1)
sig = [int(tmp_meter[0]), int(tmp_meter[1])]
change.originalFractions = attributes.get_time_signature()
else:
sig = attributes.get_time_signature()
if not sig:
return None
change.fractions = sig
time_elm = attributes.get_maybe_exist_named_child('time')
if time_elm and hasattr(time_elm, 'symbol'):
change.style = {'single-number': "'single-digit",
'cut': None,
'common': None,
'normal': "'()"}.get(time_elm.symbol, "'()")
else:
change.style = "'()"
if getattr(time_elm, 'print-object', 'yes') == 'no':
change.visible = False
# TODO: Handle senza-misura measures
# TODO: What shall we do if the symbol clashes with the sig? e.g. "cut"
# with 3/8 or "single-number" with(2+3)/8 or 3/8+2/4?
return change
def musicxml_key_to_lily(attributes):
key_sig = attributes.get_key_signature()
if not key_sig or not(isinstance(key_sig, list) or isinstance(key_sig, tuple)):
ly.warning(_("Unable to extract key signature!"))
return None
change = musicexp.KeySignatureChange()
if len(key_sig) == 2 and not isinstance(key_sig[0], list):
# standard key signature,(fifths, mode)
(fifths, mode) = key_sig
change.mode = mode
start_pitch = musicexp.Pitch()
start_pitch.octave = 0
try:
(n, a) = {
'major': (0, 0),
'minor': (5, 0),
'ionian': (0, 0),
'dorian': (1, 0),
'phrygian': (2, 0),
'lydian': (3, 0),
'mixolydian': (4, 0),
'aeolian': (5, 0),
'locrian': (6, 0),
}[mode]
start_pitch.step = n
start_pitch.alteration = a
except KeyError:
ly.warning(_("unknown mode %s, expecting 'major' or 'minor' "
"or a church mode!") % mode)
fifth = musicexp.Pitch()
fifth.step = 4
if fifths < 0:
fifths *= -1
fifth.step *= -1
fifth.normalize()
for x in range(fifths):
start_pitch = start_pitch.transposed(fifth)
change.tonic = start_pitch
else:
# Non-standard key signature of the form [[step,alter<,octave>],...]
# MusicXML contains C,D,E,F,G,A,B as steps, lily uses 0-7, so convert
alterations = []
for k in key_sig:
k[0] = musicxml2ly_conversion.musicxml_step_to_lily(k[0])
alterations.append(k)
change.non_standard_alterations = alterations
return change
def musicxml_transpose_to_lily(attributes):
transpose = attributes.get_transposition()
if not transpose:
return None
shift = musicexp.Pitch()
octave_change = transpose.get_maybe_exist_named_child('octave-change')
if octave_change:
shift.octave = int(octave_change.get_text())
chromatic_shift = int(transpose.get_named_child('chromatic').get_text())
chromatic_shift_normalized = chromatic_shift % 12
(shift.step, shift.alteration) = [
(0, 0), (0, 1), (1, 0), (2, -1), (2, 0),
(3, 0), (3, 1), (4, 0), (5, -1), (5, 0),
(6, -1), (6, 0)][chromatic_shift_normalized]
shift.octave += (chromatic_shift - chromatic_shift_normalized) // 12
diatonic = transpose.get_maybe_exist_named_child('diatonic')
if diatonic:
diatonic_step = int(diatonic.get_text()) % 7
if diatonic_step != shift.step:
# We got the alter incorrect!
old_semitones = shift.semitones()
shift.step = diatonic_step
new_semitones = shift.semitones()
shift.alteration += old_semitones - new_semitones
transposition = musicexp.Transposition()
transposition.pitch = musicexp.Pitch().transposed(shift)
return transposition
def musicxml_staff_details_to_lily(attributes):
details = attributes.get_maybe_exist_named_child('staff-details')
if not details:
return None
# TODO: Handle staff-type, staff-lines, staff-tuning, capo, staff-size
ret = []
stafflines = details.get_maybe_exist_named_child('staff-lines')
if stafflines:
lines = int(stafflines.get_text())
lines_event = musicexp.StaffLinesEvent(lines)
ret.append(lines_event)
return ret
def musicxml_attributes_to_lily(attrs):
elts = []
attr_dispatch = {
'clef': musicxml_clef_to_lily,
'time': musicxml_time_to_lily,
'key': musicxml_key_to_lily,
'transpose': musicxml_transpose_to_lily,
'staff-details': musicxml_staff_details_to_lily,
}
for (k, func) in list(attr_dispatch.items()):
children = attrs.get_named_children(k)
if children:
ev = func(attrs)
if isinstance(ev, list):
for e in ev:
elts.append(e)
elif ev:
elts.append(ev)
return elts
def extract_display_text(el):
children = el.get_typed_children(musicxml.get_class("display-text"))
if children:
return " ".join([child.get_text() for child in children])
else:
return False
def musicxml_print_to_lily(el):
# TODO: Implement other print attributes
# <!ELEMENT print (page-layout?, system-layout?, staff-layout*,
# measure-layout?, measure-numbering?, part-name-display?,
# part-abbreviation-display?)>
# <!ATTLIST print
# staff-spacing %tenths; #IMPLIED
# new-system %yes-no; #IMPLIED
# new-page %yes-no-number; #IMPLIED
# blank-page NMTOKEN #IMPLIED
# page-number CDATA #IMPLIED
# >
elts = []
if (hasattr(el, "new-system") and conversion_settings.convert_system_breaks):
val = getattr(el, "new-system")
if val == "yes":
elts.append(musicexp.Break("break"))
if hasattr(el, "new-page") and conversion_settings.convert_page_breaks:
val = getattr(el, "new-page")
if val == "yes":
elts.append(musicexp.Break("pageBreak"))
child = el.get_maybe_exist_named_child("part-name-display")
if child:
elts.append(musicexp.SetEvent("Staff.instrumentName",
"\"%s\"" % extract_display_text(child)))
child = el.get_maybe_exist_named_child("part-abbreviation-display")
if child:
elts.append(musicexp.SetEvent("Staff.shortInstrumentName",
"\"%s\"" % extract_display_text(child)))
return elts
spanner_event_dict = {
'beam': musicexp.BeamEvent,
'dashes': musicexp.TextSpannerEvent,
'bracket': musicexp.BracketSpannerEvent,
'glissando': musicexp.GlissandoEvent,
'octave-shift': musicexp.OctaveShiftEvent,
'pedal': musicexp.PedalEvent,
'slide': musicexp.GlissandoEvent,
'slur': musicexp.SlurEvent,
'wavy-line': musicexp.TextSpannerEvent,
'wedge': musicexp.HairpinEvent
}
spanner_type_dict = {
'start': -1,
'begin': -1,
'crescendo': -1,
'decreschendo': -1,
'diminuendo': -1,
'continue': 0,
'change': 0,
'up': -1,
'down': -1,
'stop': 1,
'end': 1
}
def musicxml_spanner_to_lily_event(mxl_event):
ev = None
name = mxl_event.get_name()
func = spanner_event_dict.get(name)
if func:
ev = func()
else:
ly.warning(_('unknown span event %s') % mxl_event)
if name == "wavy-line":
ev.style = OrnamenthasWhat(mxl_event)
type = mxl_event.get_type()
span_direction = spanner_type_dict.get(type)
# really check for None, because some types will be translated to 0, which
# would otherwise also lead to the unknown span warning
if span_direction is not None:
ev.span_direction = span_direction
else:
ly.warning(_('unknown span type %s for %s') % (type, name))
ev.set_span_type(type)
ev.line_type = getattr(mxl_event, 'line-type', 'solid')
# assign the size, which is used for octave-shift, etc.
ev.size = mxl_event.get_size()
return ev
def musicxml_direction_to_indicator(direction):
return {"above": 1, "upright": 1, "up": 1, "below": -1, "downright": -1, "down": -1, "inverted": -1}.get(direction, 0)
def musicxml_fermata_to_lily_event(mxl_event):
ev = musicexp.ArticulationEvent()
txt = mxl_event.get_text()
# The contents of the element defined the shape, possible are normal, angled and square
ev.type = {"angled": "shortfermata",
"square": "longfermata"}.get(txt, "fermata")
fermata_types = {"angled": "shortfermata",
"square": "longfermata"}
# MusicXML fermata types can be specified in two different ways:
# 1. <fermata>angled</fermata> and
# 2. <fermata type="angled"/> -- both need to be handled.
if hasattr(mxl_event, 'type'):
fermata_type = fermata_types.get(mxl_event.type, 'fermata')
else:
fermata_type = fermata_types.get(mxl_event.get_text(), 'fermata')
ev.type = fermata_type
if hasattr(mxl_event, 'type'):
dir = musicxml_direction_to_indicator(mxl_event.type)
if dir and options.convert_directions:
ev.force_direction = dir
return ev
def musicxml_arpeggiate_to_lily_event(mxl_event):
ev = musicexp.ArpeggioEvent()
ev.direction = musicxml_direction_to_indicator(
getattr(mxl_event, 'direction', None))
return ev
def musicxml_nonarpeggiate_to_lily_event(mxl_event):
ev = musicexp.ArpeggioEvent()
ev.non_arpeggiate = True
ev.direction = musicxml_direction_to_indicator(
getattr(mxl_event, 'direction', None))
return ev
def musicxml_tremolo_to_lily_event(mxl_event):
ev = musicexp.TremoloEvent()
txt = mxl_event.get_text()
if txt:
ev.strokes = txt
else:
# This is supposed to be a default for empty tremolo elements
# TODO: Add empty tremolo element to test cases in tremolo.xml
# TODO: Test empty tremolo element
# TODO: Consideration: Is 3 really a reasonable default?
ev.strokes = "3"
return ev
def musicxml_falloff_to_lily_event(mxl_event):
ev = musicexp.BendEvent()
ev.alter = -4
return ev
def musicxml_doit_to_lily_event(mxl_event):
ev = musicexp.BendEvent()
ev.alter = 4
return ev
def musicxml_bend_to_lily_event(mxl_event):
ev = musicexp.BendEvent()
ev.alter = mxl_event.bend_alter()
return ev
def musicxml_caesura_to_lily_event(mxl_event):
ev = musicexp.MarkupEvent()
# FIXME: default to straight or curved caesura?
ev.contents = "\\musicglyph #\"scripts.caesura.straight\""
ev.force_direction = 1
return ev
def musicxml_fingering_event(mxl_event):
ev = musicexp.ShortArticulationEvent()
ev.type = mxl_event.get_text()
return ev
def musicxml_string_event(mxl_event):
ev = musicexp.NoDirectionArticulationEvent()
ev.type = mxl_event.get_text()
return ev
def musicxml_accidental_mark(mxl_event):
ev = musicexp.MarkupEvent()
contents = {"sharp": "\\sharp",
"natural": "\\natural",
"flat": "\\flat",
"double-sharp": "\\doublesharp",
"sharp-sharp": "\\sharp\\sharp",
"flat-flat": "\\flat\\flat",
"flat-flat": "\\doubleflat",
"natural-sharp": "\\natural\\sharp",
"natural-flat": "\\natural\\flat",
"quarter-flat": "\\semiflat",
"quarter-sharp": "\\semisharp",
"three-quarters-flat": "\\sesquiflat",
"three-quarters-sharp": "\\sesquisharp",
}.get(mxl_event.get_text())
if contents:
ev.contents = contents
return ev
else:
return None
# translate articulations, ornaments and other notations into ArticulationEvents
# possible values:
# -) string (ArticulationEvent with that name)
# -) function (function(mxl_event) needs to return a full ArticulationEvent-derived object
# -) (class, name) (like string, only that a different class than ArticulationEvent is used)
# TODO: Some translations are missing!
articulations_dict = {
"accent": (musicexp.ShortArticulationEvent, ">"), # or "accent"
"accidental-mark": musicxml_accidental_mark,
"bend": musicxml_bend_to_lily_event,
"breath-mark": (musicexp.NoDirectionArticulationEvent, "breathe"),
"caesura": musicxml_caesura_to_lily_event,
# "delayed-turn": "?",
"detached-legato": (musicexp.ShortArticulationEvent, "_"), # or "portato"
"doit": musicxml_doit_to_lily_event,
# "double-tongue": "?",
"down-bow": "downbow",
"falloff": musicxml_falloff_to_lily_event,
"fingering": musicxml_fingering_event,
# "fingernails": "?",
# "fret": "?",
# "hammer-on": "?",
"harmonic": "flageolet",
# "heel": "?",
"inverted-mordent": "prall",
"inverted-turn": "reverseturn",
"mordent": "mordent",
"open-string": "open",
# "plop": "?",
# "pluck": "?",
# "pull-off": "?",
# "schleifer": "?",
# "scoop": "?",
# "shake": "?",
"snap-pizzicato": "snappizzicato",
# "spiccato": "?",
# or "staccatissimo"
"staccatissimo": (musicexp.ShortArticulationEvent, "!"),
"staccato": (musicexp.ShortArticulationEvent, "."), # or "staccato"
"stopped": (musicexp.ShortArticulationEvent, "+"), # or "stopped"
# "stress": "?",
"string": musicxml_string_event,
"strong-accent": (musicexp.ShortArticulationEvent, "^"), # or "marcato"
# "tap": "?",
"tenuto": (musicexp.ShortArticulationEvent, "-"), # or "tenuto"
"thumb-position": "thumb",
# "toe": "?",
"turn": "turn",
"tremolo": musicxml_tremolo_to_lily_event,
"trill-mark": "trill",
# "triple-tongue": "?",
# "unstress": "?"
"up-bow": "upbow",
# "wavy-line": "?",
}
articulation_spanners = ["wavy-line"]
def OrnamenthasWhat(mxl_event):
wavy = trilly = ignore = start = stop = False
for i in mxl_event._parent._children:
if i._name == "wavy-line":
wavy = True
elif i._name == "trill-mark":
trilly = True
try:
if i.type == "continue":
ignore = True
elif i.type == "start":
start = True
elif i.type == "stop":
stop = True
except Exception: ## TODO: find out what to except.
pass
if start == True:
if wavy == True and trilly == False:
musicexp.whatOrnament = "wave"
else:
musicexp.whatOrnament = "trill"
if ignore == True:
return "ignore"
elif stop == True:
return "stop"
elif wavy == True and trilly == True:
return "trill and wave"
elif wavy == True:
return "wave"
elif trilly == True:
return "trill"
def OrnamenthasWavyline(mxl_event):
for i in mxl_event._parent._children:
if i._name == "wavy-line":
return True
return False
def musicxml_articulation_to_lily_event(mxl_event):
# wavy-line elements are treated as trill spanners, not as articulation ornaments
if mxl_event.get_name() in articulation_spanners:
return musicxml_spanner_to_lily_event(mxl_event)
tmp_tp = articulations_dict.get(mxl_event.get_name())
if OrnamenthasWavyline(mxl_event):
return
if not tmp_tp:
return
if isinstance(tmp_tp, str):
ev = musicexp.ArticulationEvent()
ev.type = tmp_tp
elif isinstance(tmp_tp, tuple):
ev = tmp_tp[0]()
ev.type = tmp_tp[1]
else:
ev = tmp_tp(mxl_event)
# Some articulations use the type attribute, other the placement...
dir = None
if hasattr(mxl_event, 'type') and hasattr(options, 'convert_directions') and options.convert_directions:
dir = musicxml_direction_to_indicator(mxl_event.type)
if hasattr(mxl_event, 'placement') and hasattr(options, 'convert_directions') and options.convert_directions:
dir = musicxml_direction_to_indicator(mxl_event.placement)
if dir:
ev.force_direction = dir
return ev
def musicxml_dynamics_to_lily_event(dynentry):
dynamics_available = (
"ppppp", "pppp", "ppp", "pp", "p", "mp", "mf",
"f", "ff", "fff", "ffff", "fp", "sf", "sff", "sp", "spp", "sfz", "rfz")
dynamicsname = dynentry.get_name()
if dynamicsname == "other-dynamics":
dynamicsname = dynentry.get_text()
if not dynamicsname or dynamicsname == "#text":
return None
if not dynamicsname in dynamics_available:
# Get rid of - in tag names (illegal in ly tags!)
dynamicstext = dynamicsname
dynamicsname = dynamicsname.replace("-", "")
additional_definitions[dynamicsname] = dynamicsname + \
" = #(make-dynamic-script \"" + dynamicstext + "\")"
needed_additional_definitions.append(dynamicsname)
event = musicexp.DynamicsEvent()
event.type = dynamicsname
return event
# Convert single-color two-byte strings to numbers 0.0 - 1.0
def hexcolorval_to_nr(hex_val):
try:
v = int(hex_val, 16)
if v == 255:
v = 256
return v / 256.
except ValueError:
return 0.
def hex_to_color(hex_val):
res = re.match(
r'#([0-9a-f][0-9a-f]|)([0-9a-f][0-9a-f])([0-9a-f][0-9a-f])([0-9a-f][0-9a-f])$', hex_val, re.IGNORECASE)
if res:
return [hexcolorval_to_nr(x) for x in res.group(2, 3, 4)]
else:
return None
def font_size_number_to_lily_command(size):
d = {
(0, 8): r'\teeny',
(8, 10): r'\tiny',
(10, 12): r'\small',
(12, 16): r'',
(16, 24): r'\large',
(24, float('inf')): r'\huge',
}
result = None
for r in list(d.keys()):
if r[0] <= size < r[1]:
result = d[r]
break
return result
def font_size_word_to_lily_command(size):
font_size_dict = {
"xx-small": '\\teeny',
"x-small": '\\tiny',
"small": '\\small',
"medium": '',
"large": '\\large',
"x-large": '\\huge',
"xx-large": '\\larger\\huge'
}
return font_size_dict.get(size, '')
def get_font_size(size):
try:
size = float(size)
return font_size_number_to_lily_command(size)
except ValueError:
return font_size_word_to_lily_command(size)
def musicxml_words_to_lily_event(words):
event = musicexp.TextEvent()
text = words.get_text()
# remove white spaces and line breaks before text
text = re.sub('^ *\n? *', '', text)
# remove white spaces and line breaks before text
text = re.sub(' *\n? *$', '', text)
event.text = text
if hasattr(words, 'default-y') and hasattr(options, 'convert_directions') and options.convert_directions:
offset = getattr(words, 'default-y')
try:
off = int(offset)
if off > 0:
event.force_direction = 1
else:
event.force_direction = -1
except ValueError:
event.force_direction = 0
if hasattr(words, 'font-weight'):
font_weight = {"normal": '', "bold": '\\bold'}.get(
getattr(words, 'font-weight'), '')
if font_weight:
event.markup += font_weight
if hasattr(words, 'font-size'):
size = getattr(words, 'font-size')
# font_size = font_size_dict.get(size, '')
font_size = get_font_size(size)
if font_size:
event.markup += font_size
if hasattr(words, 'color'):
color = getattr(words, 'color')
rgb = hex_to_color(color)
if rgb:
event.markup += "\\with-color #(rgb-color %s %s %s)" % (
rgb[0], rgb[1], rgb[2])
if hasattr(words, 'font-style'):
font_style = {"italic": '\\italic'}.get(
getattr(words, 'font-style'), '')
if font_style:
event.markup += font_style
# TODO: How should I best convert the font-family attribute?
# TODO: How can I represent the underline, overline and line-through
# attributes in LilyPond? Values of these attributes indicate
# the number of lines
return event
# convert accordion-registration to lilypond.
# Since lilypond does not have any built-in commands, we need to create
# the markup commands manually and define our own variables.
# Idea was taken from: http://lsr.dsi.unimi.it/LSR/Item?id=194
def musicxml_accordion_to_markup(mxl_event):
commandname = "accReg"
command = ""
high = mxl_event.get_maybe_exist_named_child('accordion-high')
if high:
commandname += "H"
command += """\\combine
\\raise #2.5 \\musicglyph #\"accordion.dot\"
"""
middle = mxl_event.get_maybe_exist_named_child('accordion-middle')
if middle:
# By default, use one dot (when no or invalid content is given). The
# MusicXML spec is quiet about this case...
txt = 1
try:
txt = int(middle.get_text())
except ValueError:
pass
if txt == 3:
commandname += "MMM"
command += r"""\combine
\raise #1.5 \musicglyph #"accordion.dot"
\combine
\raise #1.5 \translate #(cons 1 0) \musicglyph #"accordion.dot"
\combine
\raise #1.5 \translate #(cons -1 0) \musicglyph #"accordion.dot"
"""
elif txt == 2:
commandname += "MM"
command += r"""\combine
\raise #1.5 \translate #(cons 0.5 0) \musicglyph #"accordion.dot"
\combine
\raise #1.5 \translate #(cons -0.5 0) \musicglyph #"accordion.dot"
"""
elif not txt <= 0:
commandname += "M"
command += r"""\combine
\raise #1.5 \musicglyph #"accordion.dot"
"""
low = mxl_event.get_maybe_exist_named_child('accordion-low')
if low:
commandname += "L"
command += r"""\combine
\raise #0.5 \musicglyph #"accordion.dot"
"""
command += r'\musicglyph #"accordion.discant"'
command = r"\markup { \normalsize %s }" % command
# Define the newly built command \accReg[H][MMM][L]
additional_definitions[commandname] = "%s = %s" % (commandname, command)
needed_additional_definitions.append(commandname)
return "\\%s" % commandname
def musicxml_accordion_to_ly(mxl_event):
txt = musicxml_accordion_to_markup(mxl_event)
if txt:
ev = musicexp.MarkEvent(txt)
return ev
return
def musicxml_rehearsal_to_ly_mark(mxl_event):
text = mxl_event.get_text()
if not text:
return
# default is boxed rehearsal marks!
encl = "box"
if hasattr(mxl_event, 'enclosure'):
encl = {"none": None, "square": "box", "circle": "circle"}.get(
mxl_event.enclosure, None)
if encl:
text = "\\%s { %s }" % (encl, text)
ev = musicexp.MarkEvent("\\markup { %s }" % text)
return ev
def musicxml_harp_pedals_to_ly(mxl_event):
count = 0
result = "\\harp-pedal #\""
for t in mxl_event.get_named_children('pedal-tuning'):
alter = t.get_named_child('pedal-alter')
if alter:
val = int(alter.get_text().strip())
result += {1: "v", 0: "-", -1: "^"}.get(val, "")
count += 1
if count == 3:
result += "|"
ev = musicexp.MarkupEvent()
ev.contents = result + "\""
return ev
def musicxml_eyeglasses_to_ly(mxl_event):
needed_additional_definitions.append("eyeglasses")
return musicexp.MarkEvent("\\markup { \\eyeglasses }")
def next_non_hash_index(lst, pos):
pos += 1
while pos < len(lst) and isinstance(lst[pos], musicxml.Hash_text):
pos += 1
return pos
def musicxml_metronome_to_ly(mxl_event, text_event=None):
children = mxl_event.get_all_children()
if not children:
return
index = -1
index = next_non_hash_index(children, index)
if isinstance(children[index], musicxml.BeatUnit):
# first form of metronome-mark, using unit and beats/min or other unit
ev = musicexp.TempoMark()
if text_event:
ev.set_text(text_event.get_text().strip())
if hasattr(mxl_event, 'parentheses'):
ev.set_parentheses(mxl_event.parentheses == "yes")
d = musicexp.Duration()
d.duration_log = utilities.musicxml_duration_to_log(
children[index].get_text())
index = next_non_hash_index(children, index)
if isinstance(children[index], musicxml.BeatUnitDot):
d.dots = 1
index = next_non_hash_index(children, index)
ev.set_base_duration(d)
if isinstance(children[index], musicxml.BeatUnit):
# Form "note = newnote"
newd = musicexp.Duration()
newd.duration_log = utilities.musicxml_duration_to_log(
children[index].get_text())
index = next_non_hash_index(children, index)
if isinstance(children[index], musicxml.BeatUnitDot):
newd.dots = 1
index = next_non_hash_index(children, index)
ev.set_new_duration(newd)
elif isinstance(children[index], musicxml.PerMinute):
# Form "note = bpm"
try:
beats = int(children[index].get_text())
ev.set_beats_per_minute(beats)
except ValueError:
pass
else:
ly.warning(_("Unknown metronome mark, ignoring"))
return
return ev
else:
# TODO: Implement the other (more complex) way for tempo marks!
ly.warning(
_("Metronome marks with complex relations (<metronome-note> in MusicXML) are not yet implemented."))
return
# translate directions into Events, possible values:
# -) string (MarkEvent with that command)
# -) function (function(mxl_event) needs to return a full Event-derived object
# -) (class, name) (like string, only that a different class than MarkEvent is used)
directions_dict = {
'accordion-registration': musicxml_accordion_to_ly,
'coda': (musicexp.MusicGlyphMarkEvent, "coda"),
# 'damp' : ???
# 'damp-all' : ???
'eyeglasses': musicxml_eyeglasses_to_ly,
'harp-pedals': musicxml_harp_pedals_to_ly,
# 'image' : ???
'metronome': musicxml_metronome_to_ly,
'rehearsal': musicxml_rehearsal_to_ly_mark,
# 'scordatura' : ???
'segno': (musicexp.MusicGlyphMarkEvent, "segno"),
'words': musicxml_words_to_lily_event,
}
directions_spanners = ['octave-shift', 'pedal', 'wedge', 'dashes', 'bracket']
def musicxml_direction_to_lily(n):
# TODO: Handle the <staff> element!
res = []
# placement applies to all children!
dir = None
if hasattr(n, 'placement') and hasattr(options, 'convert_directions') and options.convert_directions:
dir = musicxml_direction_to_indicator(n.placement)
dirtype_children = []
# TODO: The direction-type is used for grouping (e.g. dynamics with text),
# so we can't simply flatten them out!
for dt in n.get_typed_children(musicxml.DirType):
dirtype_children += dt.get_all_children()
dirtype_children = [d for d in dirtype_children if d.get_name() != "#text"]
for i, entry in enumerate(dirtype_children):
if not entry:
continue
# brackets, dashes, octave shifts. pedal marks, hairpins etc. are spanners:
if entry.get_name() in directions_spanners:
event = musicxml_spanner_to_lily_event(entry)
if event:
event.force_direction = dir
res.append(event)
continue
# handle text+bpm marks like "Allegro moderato (♩ = 144)"
if entry.get_name() == 'words' and i < len(dirtype_children) - 1:
next_entry = dirtype_children[i+1]
if next_entry.get_name() == 'metronome':
event = musicxml_metronome_to_ly(next_entry, entry)
if event:
res.append(event)
dirtype_children[i+1] = None
continue
# now treat all the "simple" ones, that can be translated using the dict
ev = None
tmp_tp = directions_dict.get(entry.get_name(), None)
if isinstance(tmp_tp, str): # string means MarkEvent
ev = musicexp.MarkEvent(tmp_tp)
elif isinstance(tmp_tp, tuple): # tuple means (EventClass, "text")
ev = tmp_tp[0](tmp_tp[1])
elif tmp_tp:
ev = tmp_tp(entry)
if ev:
# TODO: set the correct direction! Unfortunately, \mark in ly does
# not seem to support directions!
ev.force_direction = dir
res.append(ev)
continue
if entry.get_name() == "dynamics":
for dynentry in entry.get_all_children():
ev = musicxml_dynamics_to_lily_event(dynentry)
if ev:
ev.force_direction = dir
res.append(ev)
return res
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 musicxml_chordpitch_to_lily(mxl_cpitch):
r = musicexp.ChordPitch()
r.alteration = mxl_cpitch.get_alteration()
r.step = musicxml2ly_conversion.musicxml_step_to_lily(
mxl_cpitch.get_step())
return r
chordkind_dict = {
'major': ':5',
'minor': ':m5',
'augmented': ':aug5',
'diminished': ':dim5',
# Sevenths:
'dominant': ':7',
'dominant-seventh': ':7',
'major-seventh': ':maj7',
'minor-seventh': ':m7',
'diminished-seventh': ':dim7',
'augmented-seventh': ':aug7',
'half-diminished': ':dim5m7',
'major-minor': ':maj7m5',
# Sixths:
'major-sixth': ':6',
'minor-sixth': ':m6',
# Ninths:
'dominant-ninth': ':9',
'major-ninth': ':maj9',
'minor-ninth': ':m9',
# 11ths (usually as the basis for alteration):
'dominant-11th': ':11',
'major-11th': ':maj11',
'minor-11th': ':m11',
# 13ths (usually as the basis for alteration):
'dominant-13th': ':13.11',
'major-13th': ':maj13.11',
'minor-13th': ':m13',
# Suspended:
'suspended-second': ':sus2',
'suspended-fourth': ':sus4',
# Functional sixths:
# TODO
# 'Neapolitan': '???',
# 'Italian': '???',
# 'French': '???',
# 'German': '???',
# Other:
# 'pedal': '???',(pedal-point bass)
'power': ':1.5',
# 'Tristan': '???',
'other': ':1',
'none': None,
}
def musicxml_chordkind_to_lily(kind):
res = chordkind_dict.get(kind, None)
# Check for None, since a major chord is converted to ''
if res is None:
ly.warning(_("Unable to convert chord type %s to lilypond.") % kind)
return res
# Global variable for guitar string tunings
string_tunings = None
def musicxml_get_string_tunings(lines):
global string_tunings
if string_tunings is None:
if not lines:
lines = 6
string_tunings = [musicexp.Pitch()] * lines
for i in range(0, lines):
p = musicexp.Pitch()
p.step = musicxml2ly_conversion.musicxml_step_to_lily(
((("E", "A", "D", "G", "B")*(lines/5+1))[0:lines])[i])
p.octave = (([-2+int(x % 5 > 1)+2*(x/5)
for x in range(0, lines)][0:lines])[i])
p.alteration = 0
p._force_absolute_pitch = True
string_tunings[i] = p
string_tunings = string_tunings[::-1]
return string_tunings[0:lines]
def musicxml_frame_to_lily_event(frame):
ev = musicexp.FretEvent()
ev.strings = frame.get_strings()
ev.frets = frame.get_frets()
#offset = frame.get_first_fret() - 1
#offset = frame.get_first_fret()
barre = []
open_strings = list(range(1, ev.strings+1))
for fn in frame.get_named_children('frame-note'):
fret = fn.get_fret()
if fret <= 0:
fret = "o"
el = [fn.get_string(), fret]
fingering = fn.get_fingering()
if fingering >= 0:
el.append(fingering)
ev.elements.append(el)
open_strings.remove(fn.get_string())
b = fn.get_barre()
if b == 'start':
barre.append(el[0]) # start string
barre.append(el[1]) # fret
elif b == 'stop':
barre.insert(1, el[0]) # end string
for string in open_strings:
ev.elements.append([string, 'x'])
ev.elements.sort()
ev.elements.reverse()
if barre:
ev.barre = barre
return ev
def musicxml_harmony_to_lily(n):
res = []
for f in n.get_named_children('frame'):
ev = musicxml_frame_to_lily_event(f)
if ev:
res.append(ev)
return res
def musicxml_harmony_to_lily_fretboards(n):
res = []
frame = n.get_maybe_exist_named_child('frame')
if frame:
strings = frame.get_strings()
if not strings:
strings = 6
tunings = musicxml_get_string_tunings(strings)
ev = musicexp.FretBoardEvent()
#barre = []
for fn in frame.get_named_children('frame-note'):
fbn = musicexp.FretBoardNote()
string = fn.get_string()
fbn.string = string
fingering = fn.get_fingering()
if fingering >= 0:
fbn.fingering = fingering
p = tunings[string-1].copy()
p.add_semitones(fn.get_fret())
fbn.pitch = p
ev.append(fbn)
res.append(ev)
return res
def musicxml_harmony_to_lily_chordname(n):
res = []
root = n.get_maybe_exist_named_child('root')
if root:
ev = musicexp.ChordNameEvent()
ev.root = musicxml_chordpitch_to_lily(root)
kind = n.get_maybe_exist_named_child('kind')
if kind:
ev.kind = musicxml_chordkind_to_lily(kind.get_text())
if not ev.kind:
return res
bass = n.get_maybe_exist_named_child('bass')
if bass:
ev.bass = musicxml_chordpitch_to_lily(bass)
inversion = n.get_maybe_exist_named_child('inversion')
if inversion:
# TODO: LilyPond does not support inversions, does it?
# Mail from Carl Sorensen on lilypond-devel, June 11, 2008:
# 4. LilyPond supports the first inversion in the form of added
# bass notes. So the first inversion of C major would be c:/g.
# To get the second inversion of C major, you would need to do
# e:6-3-^5 or e:m6-^5. However, both of these techniques
# require you to know the chord and calculate either the fifth
# pitch (for the first inversion) or the third pitch (for the
# second inversion) so they may not be helpful for musicxml2ly.
inversion_count = int(inversion.get_text())
if inversion_count == 1:
# TODO: Calculate the bass note for the inversion...
pass
pass
for deg in n.get_named_children('degree'):
d = musicexp.ChordModification()
d.type = deg.get_type()
d.step = deg.get_value()
d.alteration = deg.get_alter()
ev.add_modification(d)
# TODO: convert the user-symbols attribute:
# major: a triangle, like Unicode 25B3
# minor: -, like Unicode 002D
# augmented: +, like Unicode 002B
# diminished: (degree), like Unicode 00B0
# half-diminished: (o with slash), like Unicode 00F8
if ev and ev.root:
res.append(ev)
return res
def musicxml_figured_bass_note_to_lily(n):
res = musicexp.FiguredBassNote()
suffix_dict = {'sharp': "+",
'flat': "-",
'natural': "!",
'double-sharp': "++",
'flat-flat': "--",
'sharp-sharp': "++",
'slash': "/"}
prefix = n.get_maybe_exist_named_child('prefix')
if prefix:
res.set_prefix(suffix_dict.get(prefix.get_text(), ""))
fnumber = n.get_maybe_exist_named_child('figure-number')
if fnumber:
res.set_number(fnumber.get_text())
suffix = n.get_maybe_exist_named_child('suffix')
if suffix:
res.set_suffix(suffix_dict.get(suffix.get_text(), ""))
if n.get_maybe_exist_named_child('extend'):
# TODO: Implement extender lines (unfortunately, in lilypond you have
# to use \set useBassFigureExtenders = ##t, which turns them on
# globally, while MusicXML has a property for each note...
# I'm not sure there is a proper way to implement this cleanly
# n.extend
pass
return res
def musicxml_figured_bass_to_lily(n):
if not isinstance(n, musicxml.FiguredBass):
return
res = musicexp.FiguredBassEvent()
for i in n.get_named_children('figure'):
note = musicxml_figured_bass_note_to_lily(i)
if note:
res.append(note)
dur = n.get_maybe_exist_named_child('duration')
if dur:
# apply the duration to res
length = Fraction(int(dur.get_text()), n._divisions) * Fraction(1, 4)
res.set_real_duration(length)
duration = musicxml2ly_conversion.rational_to_lily_duration(length)
if duration:
res.set_duration(duration)
if hasattr(n, 'parentheses') and n.parentheses == "yes":
res.set_parentheses(True)
return res
def musicxml_lyrics_to_text(lyrics, ignoremelismata):
# TODO: Implement text styles for lyrics syllables
continued = False
extended = False
text = ''
for e in lyrics.get_all_children():
if isinstance(e, musicxml.Syllabic):
continued = e.continued()
elif isinstance(e, musicxml.Text):
# We need to convert soft hyphens to -, otherwise the ascii codec as well
# as lilypond will barf on that character
text += e.get_text().replace('\xad', '-')
elif isinstance(e, musicxml.Elision):
if text:
text += " "
continued = False
extended = False
elif isinstance(e, musicxml.Extend):
if text:
text += " "
extended = True
if text == "-" and continued:
return "--"
elif text == "_" and extended:
return "__"
elif continued and text:
if hasattr(options, 'convert_beaming') and options.convert_beaming:
if ignoremelismata == "on":
return r" \set ignoreMelismata = ##t " + utilities.escape_ly_output_string(text)
elif ignoremelismata == "off":
return " " + utilities.escape_ly_output_string(text) + " -- \\unset ignoreMelismata"
else:
return " " + utilities.escape_ly_output_string(text) + " --"
else:
return " " + utilities.escape_ly_output_string(text) + " -- "
elif continued:
return "--"
elif extended and text:
return " " + utilities.escape_ly_output_string(text) + " __"
elif extended:
return "__"
elif text:
return " " + utilities.escape_ly_output_string(text)
else:
return ""
# TODO
class NegativeSkip:
def __init__(self, here, dest):
self.here = here
self.dest = dest
class LilyPondVoiceBuilder:
def __init__(self):
self.elements = []
self.pending_dynamics = []
self.end_moment = Fraction(0)
self.begin_moment = Fraction(0)
self.pending_multibar = Fraction(0)
self.ignore_skips = False
self.has_relevant_elements = False
self.measure_length = Fraction(4, 4)
self.stay_here = False
def _insert_multibar(self):
layout_information.set_context_item('Score', 'skipBars = ##t')
r = musicexp.MultiMeasureRest()
lenfrac = self.measure_length
r.duration = musicxml2ly_conversion.rational_to_lily_duration(lenfrac)
r.duration.factor *= self.pending_multibar / lenfrac
self.elements.append(r)
self.begin_moment = self.end_moment
self.end_moment = self.begin_moment + self.pending_multibar
self.pending_multibar = Fraction(0)
def set_measure_length(self, mlen):
if (mlen != self.measure_length) and self.pending_multibar:
self._insert_multibar()
self.measure_length = mlen
def add_multibar_rest(self, duration):
self.pending_multibar += duration
def set_duration(self, duration):
self.end_moment = self.begin_moment + duration
def current_duration(self):
return self.end_moment - self.begin_moment
def add_pending_dynamics(self):
for d in self.pending_dynamics:
self.elements.append(d)
self.pending_dynamics = []
def add_music(self, music, duration, relevant=True):
assert isinstance(music, musicexp.Music)
if self.pending_multibar > Fraction(0):
self._insert_multibar()
self.has_relevant_elements = self.has_relevant_elements or relevant
if isinstance(music, musicexp.BarLine):
if self.pending_dynamics:
for d in self.pending_dynamics:
if not isinstance(d, (musicexp.SpanEvent, musicexp.DynamicsEvent)):
index = self.pending_dynamics.index(d)
dyn = self.pending_dynamics.pop(index)
self.elements.append(dyn)
self.elements.append(music)
self.begin_moment = self.end_moment
self.set_duration(duration)
# Insert all pending dynamics right after the note/rest:
if isinstance(music, musicexp.ChordEvent) and self.pending_dynamics:
self.add_pending_dynamics()
# Insert some music command that does not affect the position in the measure
def add_command(self, command, relevant=True):
assert isinstance(command, musicexp.Music)
if self.pending_multibar > Fraction(0):
self._insert_multibar()
self.has_relevant_elements = self.has_relevant_elements or relevant
self.elements.append(command)
def add_barline(self, barline, relevant=False):
# Insert only if we don't have a barline already
# TODO: Implement proper merging of default barline and custom bar line
has_relevant = self.has_relevant_elements
if (not (self.elements) or
not (isinstance(self.elements[-1], musicexp.BarLine)) or
(self.pending_multibar > Fraction(0))):
self.add_music(barline, Fraction(0))
self.has_relevant_elements = has_relevant or relevant
def add_partial(self, command):
self.ignore_skips = True
# insert the partial, but restore relevant_elements (partial is not relevant)
relevant = self.has_relevant_elements
self.add_command(command)
self.has_relevant_elements = relevant
def add_dynamics(self, dynamic):
# store the dynamic item(s) until we encounter the next note/rest:
self.pending_dynamics.append(dynamic)
def add_bar_check(self, number):
# re/store has_relevant_elements, so that a barline alone does not
# trigger output for figured bass, chord names
b = musicexp.BarLine()
b.bar_number = number
self.add_barline(b)
def jumpto(self, moment):
if not self.stay_here:
current_end = self.end_moment + self.pending_multibar
diff = moment - current_end
if diff < Fraction(0):
ly.warning(_('Negative skip %s (from position %s to %s)') %
(diff, current_end, moment))
diff = Fraction(0)
if diff > Fraction(0) and not(self.ignore_skips and moment == 0):
skip = musicexp.SkipEvent()
duration_factor = 1
duration_log = {1: 0, 2: 1, 4: 2, 8: 3, 16: 4, 32: 5,
64: 6, 128: 7, 256: 8, 512: 9}.get(diff.denominator, -1)
duration_dots = 0
# TODO: Use the time signature for skips, too. Problem: The skip
# might not start at a measure boundary!
if duration_log > 0: # denominator is a power of 2...
if diff.numerator == 3:
duration_log -= 1
duration_dots = 1
else:
duration_factor = Fraction(diff.numerator)
else:
# for skips of a whole or more, simply use s1*factor
duration_log = 0
duration_factor = diff
skip.duration.duration_log = duration_log
skip.duration.factor = duration_factor
skip.duration.dots = duration_dots
evc = musicexp.ChordEvent()
evc.elements.append(skip)
self.add_music(evc, diff, False)
if diff > Fraction(0) and moment == 0:
self.ignore_skips = False
def last_event_chord(self, starting_at):
value = None
# if the position matches, find the last ChordEvent, do not cross a bar line!
at = len(self.elements) - 1
while (at >= 0 and
not isinstance(self.elements[at], musicexp.ChordEvent) and
not isinstance(self.elements[at], musicexp.BarLine)):
at -= 1
if (self.elements
and at >= 0
and isinstance(self.elements[at], musicexp.ChordEvent)
and self.begin_moment == starting_at):
value = self.elements[at]
else:
self.jumpto(starting_at)
value = None
return value
def correct_negative_skip(self, goto):
self.end_moment = goto
self.begin_moment = goto
evc = musicexp.ChordEvent()
self.elements.append(evc)
class VoiceData:
def __init__(self):
self.voicename = None
self.voicedata = None
self.ly_voice = None
self.figured_bass = None
self.chordnames = None
self.fretboards = None
self.lyrics_dict = {}
self.lyrics_order = []
def measure_length_from_attributes(attr, current_measure_length):
len = attr.get_measure_length()
if not len:
len = current_measure_length
return len
def music_xml_voice_name_to_lily_name(part_id, name):
s = "Part%sVoice%s" % (part_id, name)
return musicxml_id_to_lily(s)
def music_xml_lyrics_name_to_lily_name(part_id, name, lyricsnr):
s = music_xml_voice_name_to_lily_name(
part_id, name)+("Lyrics%s" % lyricsnr)
return musicxml_id_to_lily(s)
def music_xml_figuredbass_name_to_lily_name(part_id, voicename):
s = music_xml_voice_name_to_lily_name(part_id, voicename)+"FiguredBass"
return musicxml_id_to_lily(s)
def music_xml_chordnames_name_to_lily_name(part_id, voicename):
s = music_xml_voice_name_to_lily_name(part_id, voicename)+"Chords"
return musicxml_id_to_lily(s)
def music_xml_fretboards_name_to_lily_name(part_id, voicename):
s = music_xml_voice_name_to_lily_name(part_id, voicename)+"FretBoards"
return musicxml_id_to_lily(s)
def get_all_lyric_parts_in_voice(voice):
r'''
Collect the indexes of all lyric parts in this voice.
In case not all of the current lyric parts are active (a typical case would be
a refrain/chorus), the current implementation inserts \skip-commands in the
inactive parts to keep them in sync.
'''
all_lyric_parts = []
for elem in voice._elements:
lyrics = elem.get_typed_children(musicxml.Lyric)
if lyrics:
for lyric in lyrics:
index = lyric.get_number()
if not index in all_lyric_parts:
all_lyric_parts.append(index)
return all_lyric_parts
def extract_lyrics(voice, lyric_key, lyrics_dict):
curr_number = None
result = []
def is_note(elem):
return isinstance(elem, musicxml.Note)
def is_rest(elem):
return elem.get_typed_children(musicxml.Rest)
def is_chord(elem):
return elem.get_typed_children(musicxml.Chord)
def is_note_and_not_rest(elem):
return is_note(elem) and not is_rest(elem)
def get_lyric_elements(note):
return note.get_typed_children(musicxml.Lyric)
def has_lyric_belonging_to_lyric_part(note, lyric_part_id):
lyric_elements = get_lyric_elements(note)
lyric_numbers = [lyric.get_number() for lyric in lyric_elements]
return any([lyric_number == lyric_part_id for lyric_number in lyric_numbers])
for idx, elem in enumerate(voice._elements):
lyrics = get_lyric_elements(elem)
lyric_keys = [lyric.get_number() for lyric in lyrics]
note_has_lyric_belonging_to_lyric_part = lyric_key in lyric_keys
# Current note has lyric with 'number' matching 'lyric_key'.
if note_has_lyric_belonging_to_lyric_part:
for lyric in lyrics:
if lyric.get_number() == lyric_key:
text = musicxml_lyrics_to_text(lyric, None)
result.append(text)
# Note has any lyric.
elif get_lyric_elements(elem) and \
not note_has_lyric_belonging_to_lyric_part:
result.append(r'\skip1 ')
# Note does not have any lyric attached to it.
elif is_chord(elem):
# note without lyrics part of a chord. MusicXML format is
# unclear if a chord element could contain a lyric, lets
# asume that we do not want to put a skip here.
continue
elif is_note_and_not_rest(elem):
result.append(r'\skip1 ')
lyrics_dict[lyric_key].extend(result)
def musicxml_voice_to_lily_voice(voice):
tuplet_events = []
lyrics = {}
return_value = VoiceData()
return_value.voicedata = voice
# First pitch needed for relative mode (if selected in command-line options)
first_pitch = None
# Needed for melismata detection (ignore lyrics on those notes!):
inside_slur = False
is_tied = False
is_chord = False
is_beamed = False
ignore_lyrics = False
current_staff = None
pending_figured_bass = []
pending_chordnames = []
pending_fretboards = []
# Make sure that the keys in the dict don't get reordered, since
# we need the correct ordering of the lyrics stanzas! By default,
# a dict will reorder its keys
return_value.lyrics_order = voice.get_lyrics_numbers()
for k in return_value.lyrics_order:
lyrics[k] = []
voice_builder = LilyPondVoiceBuilder()
figured_bass_builder = LilyPondVoiceBuilder()
chordnames_builder = LilyPondVoiceBuilder()
fretboards_builder = LilyPondVoiceBuilder()
current_measure_length = Fraction(4, 4)
voice_builder.set_measure_length(current_measure_length)
in_slur = False
all_lyric_parts = set(get_all_lyric_parts_in_voice(voice))
if list(lyrics.keys()):
for number in list(lyrics.keys()):
extracted_lyrics = extract_lyrics(voice, number, lyrics)
last_bar_check = -1
for idx, n in enumerate(voice._elements):
tie_started = False
if n.get_name() == 'forward':
continue
staff = n.get_maybe_exist_named_child('staff')
if staff:
staff = staff.get_text()
if current_staff and staff != current_staff and not n.get_maybe_exist_named_child('chord'):
voice_builder.add_command(musicexp.StaffChange(staff))
current_staff = staff
if isinstance(n, musicxml.Partial) and n.partial > 0:
a = musicxml_partial_to_lily(n.partial)
if a:
voice_builder.add_partial(a)
figured_bass_builder.add_partial(a)
chordnames_builder.add_partial(a)
fretboards_builder.add_partial(a)
continue
is_chord = n.get_maybe_exist_named_child('chord')
is_after_grace = (isinstance(n, musicxml.Note) and n.is_after_grace())
if not is_chord and not is_after_grace:
try:
voice_builder.jumpto(n._when)
figured_bass_builder.jumpto(n._when)
chordnames_builder.jumpto(n._when)
fretboards_builder.jumpto(n._when)
except NegativeSkip as neg:
voice_builder.correct_negative_skip(n._when)
figured_bass_builder.correct_negative_skip(n._when)
chordnames_builder.correct_negative_skip(n._when)
fretboards_builder.correct_negative_skip(n._when)
n.message(_("Negative skip found: from %s to %s, difference is %s") % (
neg.here, neg.dest, neg.dest - neg.here))
if isinstance(n, musicxml.Barline):
barlines = n.to_lily_object()
for a in barlines:
if isinstance(a, musicexp.BarLine):
voice_builder.add_barline(a)
figured_bass_builder.add_barline(a, False)
chordnames_builder.add_barline(a, False)
fretboards_builder.add_barline(a, False)
elif isinstance(a, musicxml2ly_conversion.RepeatMarker) or isinstance(a, musicxml2ly_conversion.EndingMarker):
voice_builder.add_command(a)
figured_bass_builder.add_barline(a, False)
chordnames_builder.add_barline(a, False)
fretboards_builder.add_barline(a, False)
continue
if isinstance(n, musicxml.Print):
for a in musicxml_print_to_lily(n):
voice_builder.add_command(a, False)
continue
# Continue any multimeasure-rests before trying to add bar checks!
# Don't handle new MM rests yet, because for them we want bar checks!
rest = n.get_maybe_exist_typed_child(musicxml.Rest)
if (rest and rest.is_whole_measure()
and voice_builder.pending_multibar > Fraction(0)):
voice_builder.add_multibar_rest(n._duration)
continue
# Print bar checks between measures.
if n._measure_position == Fraction(0) and n != voice._elements[0]:
try:
num = int(n.get_parent().number)
except ValueError:
num = 0
if num > 0 and num > last_bar_check:
voice_builder.add_bar_check(num)
figured_bass_builder.add_bar_check(num)
chordnames_builder.add_bar_check(num)
fretboards_builder.add_bar_check(num)
last_bar_check = num
if isinstance(n, musicxml.Direction):
# check if Direction already has been converted in another voice.
if n.converted:
continue
else:
n.converted = True
for direction in musicxml_direction_to_lily(n):
if direction.wait_for_note():
voice_builder.add_dynamics(direction)
else:
voice_builder.add_command(direction)
continue
# Start any new multimeasure rests
if (rest and rest.is_whole_measure()):
if pending_chordnames:
chordnames_builder.jumpto(n._when)
chordnames_builder.stay_here = True
if pending_figured_bass:
figured_bass_builder.jumpto(n._when)
figured_bass_builder.stay_here = True
if pending_fretboards:
fretboards_builder.jumpto(n._when)
fretboards_builder.stay_here = True
voice_builder.add_multibar_rest(n._duration)
continue
if isinstance(n, musicxml.Harmony):
if options.fretboards:
# Makes fretboard diagrams in a separate FretBoards voice
for a in musicxml_harmony_to_lily_fretboards(n):
pending_fretboards.append(a)
else:
# Makes markup fretboard-diagrams inside the voice
for a in musicxml_harmony_to_lily(n):
if a.wait_for_note():
voice_builder.add_dynamics(a)
else:
voice_builder.add_command(a)
for a in musicxml_harmony_to_lily_chordname(n):
pending_chordnames.append(a)
continue
if isinstance(n, musicxml.FiguredBass):
a = musicxml_figured_bass_to_lily(n)
if a:
pending_figured_bass.append(a)
continue
if isinstance(n, musicxml.Attributes):
for a in musicxml_attributes_to_lily(n):
voice_builder.add_command(a)
measure_length = measure_length_from_attributes(
n, current_measure_length)
if current_measure_length != measure_length:
current_measure_length = measure_length
voice_builder.set_measure_length(current_measure_length)
continue
if not n.__class__.__name__ == 'Note':
n.message(_('unexpected %s; expected %s or %s or %s') %
(n, 'Note', 'Attributes', 'Barline'))
continue
# if not hasattr(conversion_settings, 'convert_rest_positions'):
# conversion_settings.convert_rest_positions = True
main_event = n.to_lily_object(
convert_stem_directions=conversion_settings.convert_stem_directions,
convert_rest_positions=conversion_settings.convert_rest_positions)
if main_event and not first_pitch:
first_pitch = main_event.pitch
# ignore lyrics for notes inside a slur, tie, chord or beam
ignore_lyrics = is_tied or is_chord # or is_beamed or inside_slur
ev_chord = voice_builder.last_event_chord(n._when)
if not ev_chord:
ev_chord = musicexp.ChordEvent()
voice_builder.add_music(ev_chord, n._duration)
# For grace notes:
grace = n.get_maybe_exist_typed_child(musicxml.Grace)
if n.is_grace():
is_after_grace = ev_chord.has_elements() or n.is_after_grace()
is_chord = n.get_maybe_exist_typed_child(musicxml.Chord)
grace_chord = None
# after-graces and other graces use different lists; Depending on
# whether we have a chord or not, obtain either a new ChordEvent or
# the previous one to create a chord
if is_after_grace:
if ev_chord.after_grace_elements and n.get_maybe_exist_typed_child(musicxml.Chord):
grace_chord = ev_chord.after_grace_elements.get_last_event_chord()
if not grace_chord:
grace_chord = musicexp.ChordEvent()
ev_chord.append_after_grace(grace_chord)
elif n.is_grace():
if ev_chord.grace_elements and n.get_maybe_exist_typed_child(musicxml.Chord):
grace_chord = ev_chord.grace_elements.get_last_event_chord()
if not grace_chord:
grace_chord = musicexp.ChordEvent()
ev_chord.append_grace(grace_chord)
if hasattr(grace, 'slash') and not is_after_grace:
# TODO: use grace_type = "appoggiatura" for slurred grace notes
if grace.slash == "yes":
ev_chord.grace_type = "acciaccatura"
# now that we have inserted the chord into the grace music, insert
# everything into that chord instead of the ev_chord
ev_chord = grace_chord
ev_chord.append(main_event)
ignore_lyrics = True
else:
ev_chord.append(main_event)
# When a note/chord has grace notes (duration==0), the duration of the
# event chord is not yet known, but the event chord was already added
# with duration 0. The following correct this when we hit the real note!
if voice_builder.current_duration() == 0 and n._duration > 0:
voice_builder.set_duration(n._duration)
# if we have a figured bass, set its voice builder to the correct position
# and insert the pending figures
if pending_figured_bass:
try:
figured_bass_builder.jumpto(n._when)
if figured_bass_builder.stay_here:
figured_bass_builder.stay_here = False
except NegativeSkip as neg:
pass
for fb in pending_figured_bass:
# if a duration is given, use that, otherwise the one of the note
dur = fb.real_duration
if not dur:
dur = ev_chord.get_length()
if not fb.duration:
fb.duration = ev_chord.get_duration()
figured_bass_builder.add_music(fb, dur)
pending_figured_bass = []
if pending_chordnames:
try:
chordnames_builder.jumpto(n._when)
if chordnames_builder.stay_here:
chordnames_builder.stay_here = False
except NegativeSkip as neg:
pass
for cn in pending_chordnames:
# Assign the duration of the EventChord
cn.duration = ev_chord.get_duration()
chordnames_builder.add_music(cn, ev_chord.get_length())
pending_chordnames = []
if pending_fretboards:
try:
fretboards_builder.jumpto(n._when)
if fretboards_builder.stay_here:
fretboards_builder.stay_here = False
except NegativeSkip as neg:
pass
for fb in pending_fretboards:
# Assign the duration of the EventChord
fb.duration = ev_chord.get_duration()
fretboards_builder.add_music(fb, ev_chord.get_length())
pending_fretboards = []
notations_children = n.get_typed_children(musicxml.Notations)
tuplet_event = None
span_events = []
# The <notation> element can have the following children (+ means implemented, ~ partially, - not):
# +tied | +slur | +tuplet | glissando | slide |
# ornaments | technical | articulations | dynamics |
# +fermata | arpeggiate | non-arpeggiate |
# accidental-mark | other-notation
for notations in notations_children:
for tuplet_event in notations.get_tuplets():
time_mod = n.get_maybe_exist_typed_child(
musicxml.Time_modification)
tuplet_events.append((ev_chord, tuplet_event, time_mod))
# First, close all open slurs, only then start any new slur
# TODO: Record the number of the open slur to dtermine the correct
# closing slur!
endslurs = [s for s in notations.get_named_children('slur')
if s.get_type() in ('stop')]
if endslurs and not inside_slur:
endslurs[0].message(
_('Encountered closing slur, but no slur is open'))
elif endslurs:
if len(endslurs) > 1:
endslurs[0].message(
_('Cannot have two simultaneous (closing) slurs'))
# record the slur status for the next note in the loop
inside_slur = False
lily_ev = musicxml_spanner_to_lily_event(endslurs[0])
ev_chord.append(lily_ev)
startslurs = [s for s in notations.get_named_children('slur')
if s.get_type() in ('start')]
if startslurs and inside_slur:
startslurs[0].message(
_('Cannot have a slur inside another slur'))
elif startslurs:
if len(startslurs) > 1:
startslurs[0].message(
_('Cannot have two simultaneous slurs'))
# record the slur status for the next note in the loop
inside_slur = True
lily_ev = musicxml_spanner_to_lily_event(startslurs[0])
ev_chord.append(lily_ev)
if not grace:
mxl_tie = notations.get_tie()
if mxl_tie and mxl_tie.type == 'start':
ev_chord.append(musicexp.TieEvent())
is_tied = True
tie_started = True
else:
is_tied = False
fermatas = notations.get_named_children('fermata')
for a in fermatas:
ev = musicxml_fermata_to_lily_event(a)
if ev:
ev_chord.append(ev)
arpeggiate = notations.get_named_children('arpeggiate')
for a in arpeggiate:
ev = musicxml_arpeggiate_to_lily_event(a)
if ev:
ev_chord.append(ev)
arpeggiate = notations.get_named_children('non-arpeggiate')
for a in arpeggiate:
ev = musicxml_nonarpeggiate_to_lily_event(a)
if ev:
ev_chord.append(ev)
glissandos = notations.get_named_children('glissando')
glissandos += notations.get_named_children('slide')
for a in glissandos:
ev = musicxml_spanner_to_lily_event(a)
if ev:
ev_chord.append(ev)
# accidental-marks are direct children of <notation>!
for a in notations.get_named_children('accidental-mark'):
ev = musicxml_articulation_to_lily_event(a)
if ev:
ev_chord.append(ev)
# Articulations can contain the following child elements:
# accent | strong-accent | staccato | tenuto |
# detached-legato | staccatissimo | spiccato |
# scoop | plop | doit | falloff | breath-mark |
# caesura | stress | unstress
# Technical can contain the following child elements:
# up-bow | down-bow | harmonic | open-string |
# thumb-position | fingering | pluck | double-tongue |
# triple-tongue | stopped | snap-pizzicato | fret |
# string | hammer-on | pull-off | bend | tap | heel |
# toe | fingernails | other-technical
# Ornaments can contain the following child elements:
# trill-mark | turn | delayed-turn | inverted-turn |
# shake | wavy-line | mordent | inverted-mordent |
# schleifer | tremolo | other-ornament, accidental-mark
ornaments = notations.get_named_children('ornaments')
ornaments += notations.get_named_children('articulations')
ornaments += notations.get_named_children('technical')
for a in ornaments:
for ch in a.get_all_children():
ev = musicxml_articulation_to_lily_event(ch)
if ev:
ev_chord.append(ev)
dynamics = notations.get_named_children('dynamics')
for a in dynamics:
for ch in a.get_all_children():
ev = musicxml_dynamics_to_lily_event(ch)
if ev:
ev_chord.append(ev)
mxl_beams = [b for b in n.get_named_children('beam')
if (b.get_type() in ('begin', 'end')
and b.is_primary())]
if mxl_beams and not conversion_settings.ignore_beaming:
beam_ev = musicxml_spanner_to_lily_event(mxl_beams[0])
if beam_ev:
ev_chord.append(beam_ev)
if beam_ev.span_direction == -1: # beam and thus melisma starts here
is_beamed = True
elif beam_ev.span_direction == 1: # beam and thus melisma ends here
is_beamed = False
# Assume that a <tie> element only lasts for one note.
# This might not be correct MusicXML interpretation, but works for
# most cases and fixes broken files, which have the end tag missing
if is_tied and not tie_started:
is_tied = False
# force trailing mm rests to be written out.
# voice_builder.add_music (musicexp.ChordEvent(), Fraction(0))
if hasattr(options, 'shift_meter') and options.shift_meter:
for event in voice_builder.elements:
if isinstance(event, musicexp.TimeSignatureChange):
sd = []
for i in range(0, 5):
sd.append(musicexp.ShiftDurations())
sd[i].set_shift_durations_parameters(event)
break
ly_voice = group_tuplets(voice_builder.elements, tuplet_events)
ly_voice = group_repeats(ly_voice)
seq_music = musicexp.SequentialMusic()
seq_music.elements = ly_voice
for k in list(lyrics.keys()):
return_value.lyrics_dict[k] = musicexp.Lyrics()
return_value.lyrics_dict[k].lyrics_syllables = lyrics[k]
if hasattr(options, 'shift_meter') and options.shift_meter:
sd[-1].element = seq_music
seq_music = sd[-1]
sd.pop()
if hasattr(options, 'relative') and options.relative:
v = musicexp.RelativeMusic()
v.element = seq_music
v.basepitch = first_pitch
seq_music = v
return_value.ly_voice = seq_music
# create \figuremode { figured bass elements }
if figured_bass_builder.has_relevant_elements:
fbass_music = musicexp.SequentialMusic()
fbass_music.elements = group_repeats(figured_bass_builder.elements)
v = musicexp.ModeChangingMusicWrapper()
v.mode = 'figuremode'
v.element = fbass_music
if hasattr(options, 'shift_meter') and options.shift_meter:
sd[-1].element = v
v = sd[-1]
sd.pop()
return_value.figured_bass = v
# create \chordmode { chords }
if chordnames_builder.has_relevant_elements:
cname_music = musicexp.SequentialMusic()
cname_music.elements = group_repeats(chordnames_builder.elements)
v = musicexp.ModeChangingMusicWrapper()
v.mode = 'chordmode'
v.element = cname_music
if hasattr(options, 'shift_meter') and options.shift_meter:
sd[-1].element = v
v = sd[-1]
sd.pop()
return_value.chordnames = v
# create diagrams for FretBoards engraver
if fretboards_builder.has_relevant_elements:
fboard_music = musicexp.SequentialMusic()
fboard_music.elements = group_repeats(fretboards_builder.elements)
v = musicexp.MusicWrapper()
v.element = fboard_music
if hasattr(options, 'shift_meter') and options.shift_meter:
sd[-1].element = v
v = sd[-1]
sd.pop()
return_value.fretboards = v
# coll = []
# pending = []
# for elt in return_value.ly_voice.element.elements:
# if isinstance(elt, musicexp.TimeScaledMusic):
# print elt.element.elements
# pending.append(elt)
# else:
# coll.append(elt)
# if pending:
# coll.extend(pending)
# return_value.ly_voice.element.elements = coll
return return_value
def musicxml_id_to_lily(id):
digits = ['Zero', 'One', 'Two', 'Three', 'Four', 'Five',
'Six', 'Seven', 'Eight', 'Nine', 'Ten']
for digit in digits:
d = digits.index(digit)
id = re.sub('%d' % d, digit, id)
id = re.sub('[^a-zA-Z]', 'X', id)
return id
def voices_in_part(part):
"""Return a Name -> Voice dictionary for PART"""
part.interpret()
part.extract_voices()
voices = part.get_voices()
part_info = part.get_staff_attributes()
return (voices, part_info)
def voices_in_part_in_parts(parts):
"""return a Part -> Name -> Voice dictionary"""
# don't crash if Part doesn't have an id (that's invalid MusicXML,
# but such files are out in the wild!)
dictionary = {}
for p in parts:
voices = voices_in_part(p)
if hasattr(p, "id"):
dictionary[p.id] = voices
else:
# TODO: extract correct part id from other sources
dictionary[None] = voices
return dictionary
def get_all_voices(parts):
all_voices = voices_in_part_in_parts(parts)
all_ly_voices = {}
all_ly_staffinfo = {}
for p, (name_voice, staff_info) in list(all_voices.items()):
part_ly_voices = {}
for n, v in list(name_voice.items()):
ly.progress(_("Converting to LilyPond expressions..."), True)
# musicxml_voice_to_lily_voice returns (lily_voice, {nr->lyrics, nr->lyrics})
voice = musicxml_voice_to_lily_voice(v)
part_ly_voices[n] = voice
all_ly_voices[p] = part_ly_voices
all_ly_staffinfo[p] = staff_info
return (all_ly_voices, all_ly_staffinfo)
def option_parser():
p = ly.get_option_parser(usage=_("musicxml2ly [OPTION]... FILE.xml"),
description=_("""Convert MusicXML from FILE.xml to LilyPond input.
If the given filename is -, musicxml2ly reads from the command line.
"""), add_help_option=False)
p.add_option("-h", "--help",
action="help",
help=_("show this help and exit"))
p.version = ('%prog (LilyPond) ' + lilypond_version + '\n\n'
+
_("""Copyright (c) 2005--2020 by
Han-Wen Nienhuys <[email protected]>,
Jan Nieuwenhuizen <[email protected]> and
Reinhold Kainhofer <[email protected]>
Patrick L. Schmidt <[email protected]>
"""
+
"""
This program is free software. It is covered by the GNU General Public
License and you are welcome to change it and/or distribute copies of it
under certain conditions. Invoke as `%s --warranty' for more
information.""") % 'lilypond')
p.add_option("--version",
action="version",
help=_("show version number and exit"))
p.add_option('-v', '--verbose',
action="callback",
callback=ly.handle_loglevel_option,
callback_args=("DEBUG",),
help=_("be verbose"))
p.add_option('', '--lxml',
action="store_true",
default=False,
dest="use_lxml",
help=_("use lxml.etree; uses less memory and cpu time"))
p.add_option('-z', '--compressed',
action="store_true",
dest='compressed',
default=False,
help=_("input file is a compressed MusicXML file "
"(by default, activate if file extension is .mxl)"))
p.add_option('-r', '--relative',
action="store_true",
default=True,
dest="relative",
help=_("convert pitches in relative mode (default)"))
p.add_option('-a', '--absolute',
action="store_false",
dest="relative",
help=_("convert pitches in absolute mode"))
p.add_option('-l', '--language',
metavar=_("LANG"),
action="store",
help=_("use LANG for pitch names, e.g. 'deutsch' for note names in German"))
p.add_option("--loglevel",
help=_("Print log messages according to LOGLEVEL "
"(NONE, ERROR, WARNING, PROGRESS (default), DEBUG)"),
metavar=_("LOGLEVEL"),
action='callback',
callback=ly.handle_loglevel_option,
type='string')
p.add_option('--nd', '--no-articulation-directions',
action="store_false",
default=True,
dest="convert_directions",
help=_("do not convert directions (^, _ or -) for articulations, dynamics, etc."))
p.add_option('--nrp', '--no-rest-positions',
action="store_false",
default=True,
dest="convert_rest_positions",
help=_("do not convert exact vertical positions of rests"))
p.add_option('--nsb', '--no-system-breaks',
action="store_false",
default=True,
dest="convert_system_breaks",
help=_("ignore system breaks"))
p.add_option('--npb', '--no-page-breaks',
action="store_false",
default=True,
dest="convert_page_breaks",
help=_("ignore page breaks"))
p.add_option('--npm', '--no-page-margins',
action="store_false",
default=True,
dest="convert_page_margins",
help=_("ignore page margins"))
p.add_option('--npl', '--no-page-layout',
action="store_false",
default=True,
dest="convert_page_layout",
help=_("do not convert the exact page layout and breaks (shortcut for \"--nsb --npb --npm\" options)"))
p.add_option('--nsd', '--no-stem-directions',
action="store_false",
default=True,
dest="convert_stem_directions",
help=_("ignore stem directions from MusicXML, use lilypond's automatic stemming instead"))
p.add_option('--nb', '--no-beaming',
action="store_false",
default=True,
dest="convert_beaming",
help=_("do not convert beaming information, use lilypond's automatic beaming instead"))
p.add_option('-o', '--output',
metavar=_("FILE"),
action="store",
default=None,
type='string',
dest='output_name',
help=_("set output filename to FILE, stdout if -"))
p.add_option('-m', '--midi',
action="store_true",
default=False,
dest="midi",
help=_("activate midi-block in .ly file"))
# transpose function
p.add_option('--transpose',
metavar=_("TOPITCH"),
action="store",
dest="transpose",
help=_("set pitch to transpose by the interval between pitch 'c' and TOPITCH"))
# time signature changing function
p.add_option('--sm', '--shift-meter',
metavar=_("BEATS/BEATTYPE"),
action="store",
dest="shift_meter",
help=_("change the length|duration of notes as a function of a given time signature to make the score look faster or slower, (eg. '4/4' or '2/2')"))
# switch tabstaff clef
p.add_option('--tc', '--tab-clef',
metavar=_("TABCLEFNAME"),
action="store",
dest="tab_clef",
help=_("switch between two versions of tab clefs (\"tab\" and \"moderntab\")"))
# StringNumber stencil on/off
p.add_option('--sn', '--string-numbers',
metavar=_("t[rue]/f[alse]"),
action="store",
dest="string_numbers",
help=_("deactivate string number stencil with --string-numbers f[alse]. Default is t[rue]"))
# StringNumber stencil on/off
p.add_option('--fb', '--fretboards',
action="store_true",
default=False,
dest="fretboards",
help=_("converts '<frame>' events to a separate FretBoards voice instead of markups"))
p.add_option_group('',
description=(
_("Report bugs via %s")
% '[email protected]') + '\n')
return p
def print_voice_definitions(printer, part_list, voices):
for part in part_list:
part_id = part.id
nv_dict = voices.get(part_id, {})
for (name, voice) in list(nv_dict.items()):
k = music_xml_voice_name_to_lily_name(part_id, name)
printer.dump('%s = ' % k)
voice.ly_voice.print_ly(printer)
printer.newline()
if voice.chordnames:
cnname = music_xml_chordnames_name_to_lily_name(part_id, name)
printer.dump('%s = ' % cnname)
voice.chordnames.print_ly(printer)
printer.newline()
for l in voice.lyrics_order:
lname = music_xml_lyrics_name_to_lily_name(part_id, name, l)
printer.dump('%s = ' % lname)
voice.lyrics_dict[l].print_ly(printer)
printer.newline()
if voice.figured_bass:
fbname = music_xml_figuredbass_name_to_lily_name(part_id, name)
printer.dump('%s = ' % fbname)
voice.figured_bass.print_ly(printer)
printer.newline()
if voice.fretboards:
fbdname = music_xml_fretboards_name_to_lily_name(part_id, name)
printer.dump('%s = ' % fbdname)
voice.fretboards.print_ly(printer)
printer.newline()
# format the information about the staff in the form
# [staffid,
# [
# [voiceid1, [lyricsid11, lyricsid12,...], figuredbassid1],
# [voiceid2, [lyricsid21, lyricsid22,...], figuredbassid2],
# ...
# ]
# ]
# raw_voices is of the form [(voicename, lyricsids, havefiguredbass)*]
def format_staff_info(part_id, staff_id, raw_voices):
voices = []
for (v, lyricsids, figured_bass, chordnames, fretboards) in raw_voices:
voice_name = music_xml_voice_name_to_lily_name(part_id, v)
voice_lyrics = [music_xml_lyrics_name_to_lily_name(part_id, v, l)
for l in lyricsids]
figured_bass_name = ''
if figured_bass:
figured_bass_name = music_xml_figuredbass_name_to_lily_name(
part_id, v)
chordnames_name = ''
if chordnames:
chordnames_name = music_xml_chordnames_name_to_lily_name(
part_id, v)
fretboards_name = ''
if fretboards:
fretboards_name = music_xml_fretboards_name_to_lily_name(
part_id, v)
voices.append([voice_name, voice_lyrics, figured_bass_name,
chordnames_name, fretboards_name])
return [staff_id, voices]
def update_score_setup(score_structure, part_list, voices, parts):
for part_definition in part_list:
part_id = part_definition.id
nv_dict = voices.get(part_id)
if not nv_dict:
ly.warning(_('unknown part in part-list: %s') % part_id)
continue
staves = reduce(lambda x, y: x + y,
[list(voice.voicedata._staves.keys())
for voice in list(nv_dict.values())],
[])
staves_info = []
if len(staves) > 1:
staves_info = []
staves = sorted(set(staves))
for s in staves:
thisstaff_raw_voices = [(voice_name, voice.lyrics_order, voice.figured_bass, voice.chordnames, voice.fretboards)
for (voice_name, voice) in list(nv_dict.items())
if voice.voicedata._start_staff == s]
staves_info.append(format_staff_info(
part_id, s, thisstaff_raw_voices))
else:
thisstaff_raw_voices = [(voice_name, voice.lyrics_order, voice.figured_bass, voice.chordnames, voice.fretboards)
for (voice_name, voice) in list(nv_dict.items())]
staves_info.append(format_staff_info(
part_id, None, thisstaff_raw_voices))
score_structure.set_part_information(part_id, staves_info)
sounds = []
for part in parts:
for measure in part.get_typed_children(musicxml.Measure):
for sound in measure.get_typed_children(musicxml.Sound):
sounds.append(sound)
for direction in measure.get_typed_children(musicxml.Direction):
for sound in direction.get_typed_children(musicxml.Sound):
sounds.append(sound)
score_structure.set_tempo('100')
if len(sounds) != 0:
for sound in sounds:
if (sound.get_tempo() is not None and sound.get_tempo() != ""):
score_structure.set_tempo(sound.get_tempo())
break
# Set global values in the \layout block, like auto-beaming etc.
def update_layout_information():
if not conversion_settings.ignore_beaming and layout_information:
layout_information.set_context_item('Score', 'autoBeaming = ##f')
if musicexp.get_string_numbers() == "f":
layout_information.set_context_item(
'Score', '\\override StringNumber #\'stencil = ##f')
# \n\t\t\t\t\\override StringNumber #\'stencil = ##f
def print_ly_preamble(printer, filename):
printer.dump_version(lilypond_version)
printer.print_verbatim(
'% automatically converted by musicxml2ly from ' + filename)
printer.newline()
printer.dump(r'\pointAndClickOff')
printer.newline()
if options.midi:
printer.newline()
printer.dump(r'\include "articulate.ly"')
printer.newline()
def print_ly_additional_definitions(printer, filename=None):
if needed_additional_definitions:
printer.newline()
printer.print_verbatim(
'%% additional definitions required by the score:')
printer.newline()
for a in set(needed_additional_definitions):
printer.print_verbatim(additional_definitions.get(a, ''))
printer.newline()
printer.newline()
# Read in the tree from the given I/O object (either file or string) and
# demarshall it using the classes from the musicxml.py file
def read_xml(io_object, use_lxml):
if use_lxml:
import lxml.etree
tree = lxml.etree.parse(io_object)
mxl_tree = musicxml.lxml_demarshal_node(tree.getroot())
return mxl_tree
else:
from xml.dom import minidom, Node
doc = minidom.parse(io_object)
node = doc.documentElement
return musicxml.minidom_demarshal_node(node)
return None
def read_musicxml(filename, compressed, use_lxml):
raw_string = None
if compressed:
if filename == "-":
ly.progress(
_("Input is compressed, extracting raw MusicXML data from stdin"), True)
# unfortunately, zipfile.ZipFile can't read directly from
# stdin, so copy everything from stdin to a temp file and read
# that. TemporaryFile() will remove the file when it is closed.
tmp = tempfile.TemporaryFile()
# Make sys.stdin binary
sys.stdin = os.fdopen(sys.stdin.fileno(), 'rb', 0)
bytes_read = sys.stdin.read(8192)
while bytes_read:
tmp.write(bytes_read)
bytes_read = sys.stdin.read(8192)
z = zipfile.ZipFile(tmp, "r")
else:
ly.progress(
_("Input file %s is compressed, extracting raw MusicXML data") % filename, True)
z = zipfile.ZipFile(filename, "r")
container_xml = z.read("META-INF/container.xml").decode("utf-8")
if not container_xml:
return None
container = read_xml(io.StringIO(container_xml), use_lxml)
if not container:
return None
rootfiles = container.get_maybe_exist_named_child('rootfiles')
if not rootfiles:
return None
rootfile_list = rootfiles.get_named_children('rootfile')
mxml_file = None
if len(rootfile_list) > 0:
mxml_file = getattr(rootfile_list[0], 'full-path', None)
if mxml_file:
raw_string = z.read(mxml_file).decode('utf-8')
if raw_string:
io_object = io.StringIO(raw_string)
elif filename == "-":
io_object = sys.stdin
else:
io_object = filename
return read_xml(io_object, use_lxml)
def convert(filename, options):
if filename == "-":
ly.progress(_("Reading MusicXML from Standard input ..."), True)
else:
ly.progress(_("Reading MusicXML from %s ...") % filename, True)
tree = read_musicxml(filename, options.compressed, options.use_lxml)
score_information = extract_score_information(tree)
paper_information = extract_paper_information(tree)
parts = tree.get_typed_children(musicxml.Part)
(voices, staff_info) = get_all_voices(parts)
score = None
mxl_pl = tree.get_maybe_exist_typed_child(musicxml.Part_list)
if mxl_pl:
score = extract_score_structure(mxl_pl, staff_info)
part_list = mxl_pl.get_named_children("score-part")
# score information is contained in the <work>, <identification> or <movement-title> tags
update_score_setup(score, part_list, voices, parts)
# After the conversion, update the list of settings for the \layout block
update_layout_information()
if not options.output_name:
options.output_name = os.path.basename(filename)
options.output_name = os.path.splitext(options.output_name)[0]
elif re.match(r".*\.ly", options.output_name):
options.output_name = os.path.splitext(options.output_name)[0]
#defs_ly_name = options.output_name + '-defs.ly'
if options.output_name == "-":
output_ly_name = 'Standard output'
else:
output_ly_name = options.output_name + '.ly'
ly.progress(_("Output to `%s'") % output_ly_name, True)
printer = musicexp.Output_printer()
#ly.progress(_("Output to `%s'") % defs_ly_name, True)
if options.output_name == "-":
printer.set_file(sys.stdout)
else:
printer.set_file(codecs.open(output_ly_name, 'w', encoding='utf-8'))
print_ly_preamble(printer, filename)
print_ly_additional_definitions(printer, filename)
if score_information:
score_information.print_ly(printer)
if paper_information and conversion_settings.convert_page_layout:
paper_information.print_ly(printer)
if layout_information:
layout_information.print_ly(printer)
print_voice_definitions(printer, part_list, voices)
printer.newline()
printer.dump("% The score definition")
printer.newline()
score.print_ly(printer)
printer.newline()
# Syntax update to current version
if options.output_name != "-":
version = os.popen(
"lilypond --version | head -1 | cut -d' ' -f3").read().strip()
ly.progress(
_("Converting to current version (%s) notations ..." % version), True)
os.system("convert-ly -e %s 2> /dev/null" %
utilities.escape_ly_output_string(output_ly_name))
return voices
def get_existing_filename_with_extension(filename, ext):
if os.path.exists(filename):
return filename
newfilename = filename + "." + ext
if os.path.exists(newfilename):
return newfilename
newfilename = filename + ext
if os.path.exists(newfilename):
return newfilename
return ''
def main():
opt_parser = option_parser()
global options
(options, args) = opt_parser.parse_args()
# in case of shell entry w/o special characters
if options.language == 'catalan' or options.language == 'catala':
options.language = 'català'
if options.language == 'espanol':
options.language = 'español'
if options.language == 'francais':
options.language = 'français'
if options.language == 'portugues':
options.language = 'português'
if not args:
opt_parser.print_usage()
sys.exit(2)
# midi-block option
if options.midi:
musicexp.set_create_midi(options.midi)
# transpose function
if options.transpose:
musicexp.set_transpose(options.transpose)
# tab clef option
if options.tab_clef:
musicexp.set_tab_clef(options.tab_clef)
# string numbers option
if options.string_numbers:
musicexp.set_string_numbers(options.string_numbers)
if options.language:
musicexp.set_pitch_language(options.language)
needed_additional_definitions.append(options.language)
additional_definitions[options.language] = "\\language \"%s\"\n" % options.language
conversion_settings.ignore_beaming = not options.convert_beaming
conversion_settings.convert_page_layout = options.convert_page_layout
if conversion_settings.convert_page_layout:
conversion_settings.convert_system_breaks = options.convert_system_breaks
conversion_settings.convert_page_breaks = options.convert_page_breaks
conversion_settings.convert_page_margins = options.convert_page_margins
else:
conversion_settings.convert_system_breaks = False
conversion_settings.convert_page_breaks = False
conversion_settings.convert_page_margins = False
conversion_settings.convert_stem_directions = options.convert_stem_directions
conversion_settings.convert_rest_positions = options.convert_rest_positions
# Allow the user to leave out the .xml or xml on the filename
basefilename = args[0]
if basefilename == "-": # Read from stdin
filename = "-"
else:
filename = get_existing_filename_with_extension(basefilename, "xml")
if not filename:
filename = get_existing_filename_with_extension(
basefilename, "mxl")
options.compressed = True
if filename and filename.endswith("mxl"):
options.compressed = True
if filename and (filename == "-" or os.path.exists(filename)):
voices = convert(filename, options)
else:
ly.error(_("Unable to find input file %s") % basefilename)
sys.exit(1)
if __name__ == '__main__':
main()