From dc90b895668826a09e06ad1ef94e5e90569a870c Mon Sep 17 00:00:00 2001 From: John Gourlay Date: Thu, 25 Feb 2016 10:58:17 -0500 Subject: [PATCH] Incorporate musicxml2ly_conversion.py into musicxml2ly.py. --- python/musicxml2ly_conversion.py | 3296 ------------------------------ scripts/musicxml2ly.py | 3277 ++++++++++++++++++++++++++++- 2 files changed, 3275 insertions(+), 3298 deletions(-) delete mode 100644 python/musicxml2ly_conversion.py diff --git a/python/musicxml2ly_conversion.py b/python/musicxml2ly_conversion.py deleted file mode 100644 index 9ec2b3b928..0000000000 --- a/python/musicxml2ly_conversion.py +++ /dev/null @@ -1,3296 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- -import optparse -import sys -import re -import os -import string -import codecs -import zipfile -import tempfile -import StringIO -import utilities -import warnings - -""" - -This generic code used for all python scripts. - -The quotes are to ensure that the source .py file can still be -run as a python script, but does not include any sys.path handling. -Otherwise, the lilypond-book calls inside the build -might modify installed .pyc files. - -""" - -for d in ['/usr/share/lilypond/2.19.3', - '/usr/lib/lilypond/2.19.3']: - sys.path.insert(0, os.path.join(d, 'python')) - -# dynamic relocation, for GUB binaries. -bindir = os.path.abspath(os.path.dirname(sys.argv[0])) -for p in ['share', 'lib']: - datadir = os.path.abspath(bindir + '/../%s/lilypond/current/python/' % p) - sys.path.insert(0, datadir) -""" -""" - -import lilylib as ly -_ = ly._ - -import musicxml -import musicexp - -from rational import Rational - -# 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 . - # 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 {} is too large, using defaults=20".format( - 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! - # 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 , or 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()) - set_if_exists('miscellaneous', 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 == 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 == 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 musicxml_step_to_lily(step): - if step: - return (ord(step) - ord('A') + 7 - 2) % 7 - else: - return None - -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 = string.atoi(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 = string.atoi(i.line) - 1 - except ValueError: - pass - tunings[line] = p - - step = i.get_named_child(u'tuning-step') - step = step.get_text().strip() - p.step = musicxml_step_to_lily(step) - - octave = i.get_named_child(u'tuning-octave') - octave = octave.get_text().strip() - p.octave = int(octave) - 4 - - alter = i.get_named_child(u'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) = 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 = string.atoi(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 = el.end.keys() - prev_started = staves[prev_start].start.keys() - grpid = None - intersection = filter(lambda x:x in ends, prev_started) - if len(intersection) > 0: - grpid = intersection[0] - else: - # Close the last started group - grpid = 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 staves[j].end.has_key(grpid): - 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 el.end.has_key(grpid): - 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 rational_to_lily_duration(rational_len): - d = musicexp.Duration() - - rational_len.normalize_self() - d_log = {1: 0, 2: 1, 4:2, 8:3, 16:4, 32:5, 64:6, 128:7, 256:8, 512:9}.get(rational_len.denominator(), -1) - - # Duration of the form 1/2^n or 3/2^n can be converted to a simple lilypond duration - dots = {1: 0, 3: 1, 7: 2, 15: 3, 31: 4, 63: 5, 127: 6}.get(rational_len.numerator(), -1) - if(d_log >= dots >= 0): - # account for the dots! - d.duration_log = d_log - dots - d.dots = dots - elif(d_log >= 0): - d.duration_log = d_log - d.factor = Rational(rational_len.numerator()) - else: - ly.warning(_("Encountered rational duration with denominator %s, " - "unable to convert to lilypond duration") % - rational_len.denominator()) - # TODO: Test the above error message - return None - - return d - -def musicxml_partial_to_lily(partial_len): - if partial_len > 0: - p = musicexp.Partial() - p.partial = 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, 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, 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 and the -# 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 - 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 = "'()" - - # TODO: Handle senza-misura measures - # TODO: Handle hidden time signatures(print-object="no") - # 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] = 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 = string.atoi(octave_change.get_text()) - chromatic_shift = string.atoi(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 = string.atoi(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 = string.atoi(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 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): - child = el.get_maybe_exist_named_child("display-text") - if child: - return child.get_text() - else: - return False - - -def musicxml_print_to_lily(el): - # TODO: Implement other print attributes - # - # - 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 - - -class Marker(musicexp.Music): - def __init__(self): - self.direction = 0 - self.event = None - def print_ly(self, printer): - ly.warning(_("Encountered unprocessed marker %s\n") % self) - pass - def ly_expression(self): - return "" -class RepeatMarker(Marker): - def __init__(self): - Marker.__init__(self) - self.times = 0 -class EndingMarker(Marker): - pass - - -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 != 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. angled and - # 2. -- 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": "?", - "staccatissimo": (musicexp.ShortArticulationEvent, "|"), # or "staccatissimo" - "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: - 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 = string.replace(dynamicsname, "-", "") - 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 map(lambda x: hexcolorval_to_nr(x), 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 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() - text = re.sub('^ *\n? *', '', text) #remove white spaces and line breaks before text - text = re.sub(' *\n? *$', '', text) #remove white spaces and line breaks before 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 = string.atoi(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 = string.atoi(middle.get_text()) - except ValueError: - pass - if txt == 3: - commandname += "MMM" - command += """\\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 += """\\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 += """\\combine - \\raise #1.5 \\musicglyph #\"accordion.dot\" - """ - low = mxl_event.get_maybe_exist_named_child('accordion-low') - if low: - commandname += "L" - command += """\\combine - \\raise #0.5 \musicglyph #\"accordion.dot\" - """ - - command += "\musicglyph #\"accordion.discant\"" - command = "\\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): - 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 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 ( 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 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() - - for entry in dirtype_children: - # 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 - - # 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 = musicxml_step_to_lily(mxl_cpitch.get_step()) - return r - -chordkind_dict = { - 'major': r'{}:5', - 'minor': r'{}:m5', - 'augmented': r'{}:aug5', - 'diminished': r'{}:dim5', - # Sevenths: - 'dominant': r'{}:7', - 'dominant-seventh': r'{}:7', - 'major-seventh': r'{}:maj7', - 'minor-seventh': r'{}:m7', - 'diminished-seventh': r'{}:dim7', - 'augmented-seventh': r'{}:aug7', - 'half-diminished': r'{}:dim5m7', - 'major-minor': r'{}:maj7m5', - # Sixths: - 'major-sixth': r'{}:6', - 'minor-sixth': r'{}:m6', - # Ninths: - 'dominant-ninth': r'{}:9', - 'major-ninth': r'{}:maj9', - 'minor-ninth': r'{}:m9', - # 11ths (usually as the basis for alteration): - 'dominant-11th': r'{}:11', - 'major-11th': r'{}:maj11', - 'minor-11th': r'{}:m11', - # 13ths (usually as the basis for alteration): - 'dominant-13th': r'{}:13.11', - 'major-13th': r'{}:maj13.11', - 'minor-13th': r'{}:m13', - # Suspended: - 'suspended-second': r'{}:sus2', - 'suspended-fourth': r'{}:sus4', - # Functional sixths: - # TODO - #'Neapolitan': '???', - #'Italian': '???', - #'French': '???', - #'German': '???', - # Other: - #'pedal': '???',(pedal-point bass) - 'power': r'\powerChords {}:1.5', - #'Tristan': '???', - 'other': r'{}: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 == 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 == None): - if not lines: - lines = 6 - string_tunings = [musicexp.Pitch()] * lines - for i in range(0, lines): - p = musicexp.Pitch() - p.step = 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 = 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 = string.atoi(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 = Rational(int(dur.get_text()), n._divisions) * Rational(1, 4) - res.set_real_duration(length) - duration = 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 - -instrument_drumtype_dict = { - 'Acoustic Snare Drum': 'acousticsnare', - 'Side Stick': 'sidestick', - 'Open Triangle': 'opentriangle', - 'Mute Triangle': 'mutetriangle', - 'Tambourine': 'tambourine', - 'Bass Drum': 'bassdrum', -} - - - -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 += string.replace(e.get_text(), u'\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 " \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 = Rational(0) - self.begin_moment = Rational(0) - self.pending_multibar = Rational(0) - self.ignore_skips = False - self.has_relevant_elements = False - self.measure_length = Rational(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 = 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 = Rational(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 > Rational(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 > Rational(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 > Rational(0))): - - self.add_music(barline, Rational(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 < Rational(0): - ly.warning(_('Negative skip %s (from position %s to %s)') % - (diff, current_end, moment)) - diff = Rational(0) - - if diff > Rational(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 = Rational(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 > Rational(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): - str = "Part%sVoice%s" % (part_id, name) - return musicxml_id_to_lily(str) - -def music_xml_lyrics_name_to_lily_name(part_id, name, lyricsnr): - str = music_xml_voice_name_to_lily_name(part_id, name)+("Lyrics%s" % lyricsnr) - return musicxml_id_to_lily(str) - -def music_xml_figuredbass_name_to_lily_name(part_id, voicename): - str = music_xml_voice_name_to_lily_name(part_id, voicename)+"FiguredBass" - return musicxml_id_to_lily(str) - -def music_xml_chordnames_name_to_lily_name(part_id, voicename): - str = music_xml_voice_name_to_lily_name(part_id, voicename)+"Chords" - return musicxml_id_to_lily(str) - -def music_xml_fretboards_name_to_lily_name(part_id, voicename): - str = music_xml_voice_name_to_lily_name(part_id, voicename)+"FretBoards" - return musicxml_id_to_lily(str) - -def get_all_lyric_parts_in_voice(voice): - ''' - 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.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_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.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.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.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('\skip1 ') - # Note does not have any lyric attached to it. - elif is_note_and_not_rest(elem): - result.append('\skip1 ') - - lyrics_dict[lyric_key].extend(result) - -def musicxml_voice_to_lily_voice(voice): - tuplet_events = [] - modes_found = {} - 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 = Rational(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 lyrics.keys(): - for number in lyrics.keys(): - extracted_lyrics = extract_lyrics(voice, number, lyrics) - - 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, 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, RepeatMarker) or isinstance(a, 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 > Rational(0)): - voice_builder.add_multibar_rest(n._duration) - continue - - # print a bar check at the beginning of each measure! - if n.is_first() and n._measure_position == Rational(0) and n != voice._elements[0]: - try: - num = int(n.get_parent().number) - except ValueError: - num = 0 - if num > 0: - 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) - - 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 - - if main_event and hasattr(main_event, 'drum_type') and main_event.drum_type: - modes_found['drummode'] = True - - 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, 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, 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, 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 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 ! - 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 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(), Rational(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() - - if 'drummode' in modes_found.keys(): - ## \key barfs in drummode. - ly_voice = [e for e in ly_voice - if not isinstance(e, musicexp.KeySignatureChange)] - - seq_music.elements = ly_voice - for k in lyrics.keys(): - return_value.lyrics_dict[k] = musicexp.Lyrics() - return_value.lyrics_dict[k].lyrics_syllables = lyrics[k] - - - if len(modes_found) > 1: - ly.warning(_('cannot simultaneously have more than one mode: %s') % modes_found.keys()) - - 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 - for mode in modes_found.keys(): - v = musicexp.ModeChangingMusicWrapper() - v.element = seq_music - v.mode = mode - return_value.ly_voice = v - - # 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 all_voices.items(): - - part_ly_voices = {} - for n, v in 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 (dev) v0.2.41\n\n''' -+ -_ ("""Copyright (c) 2005--2015 by - Han-Wen Nienhuys , - Jan Nieuwenhuizen and - Reinhold Kainhofer - Patrick L. Schmidt -""" -+ -""" -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 zip-compressed MusicXML file")) - - 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 '' events to a separate FretBoards voice instead of markups")) - - p.add_option_group('', - description=( - _ ("Report bugs via %s") - % 'http://post.gmane.org/post.php' - '?group=gmane.comp.gnu.lilypond.bugs') + '\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 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() - -def uniq_list(l): - return dict([(elt, 1) for elt in l]).keys() - -# 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, - [voice.voicedata._staves.keys() - for voice in nv_dict.values()], - []) - staves_info = [] - if len(staves) > 1: - staves_info = [] - staves = uniq_list(staves) - staves.sort() - for s in staves: - thisstaff_raw_voices = [(voice_name, voice.lyrics_order, voice.figured_bass, voice.chordnames, voice.fretboards) - for (voice_name, voice) in 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 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() != 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() - printer.print_verbatim('% automatically converted by philomelos musicxml2ly v0.2.41\n') - 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() - sys.stdin = os.fdopen(sys.stdin.fileno(), 'rb', 0) # Make sys.stdin binary - bytes_read = sys.stdin.read(8192) - while bytes_read: - for b in bytes_read: - tmp.write(b) - 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") - if not container_xml: - return None - container = read_xml(StringIO.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) - - if raw_string: - io_object = StringIO.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 , or 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(".*\.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(codecs.getwriter("utf-8")(sys.stdout)) - else: - printer.set_file(codecs.open(output_ly_name, 'wb', 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() - 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 - - # Allow the user to leave out the .xml or xml on the filename - basefilename = args[0].decode('utf-8') - 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) diff --git a/scripts/musicxml2ly.py b/scripts/musicxml2ly.py index eb5d359a2a..fa6e69c0ce 100644 --- a/scripts/musicxml2ly.py +++ b/scripts/musicxml2ly.py @@ -1,7 +1,3280 @@ -#!/usr/bin/env python +#!@TARGET_PYTHON@ # -*- coding: utf-8 -*- +import optparse +import sys +import re +import os +import string +import codecs +import zipfile +import tempfile +import StringIO +import warnings -from musicxml2ly_conversion import main +""" +@relocate-preamble@ +""" + +import lilylib as ly +_ = ly._ + +import utilities +import musicxml +import musicexp + +from rational import Rational + +# 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 . + # 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 {} is too large, using defaults=20".format( + 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! + # 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 , or 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()) + set_if_exists('miscellaneous', 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 == 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 == 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 musicxml_step_to_lily(step): + if step: + return (ord(step) - ord('A') + 7 - 2) % 7 + else: + return None + +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 = string.atoi(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 = string.atoi(i.line) - 1 + except ValueError: + pass + tunings[line] = p + + step = i.get_named_child(u'tuning-step') + step = step.get_text().strip() + p.step = musicxml_step_to_lily(step) + + octave = i.get_named_child(u'tuning-octave') + octave = octave.get_text().strip() + p.octave = int(octave) - 4 + + alter = i.get_named_child(u'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) = 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 = string.atoi(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 = el.end.keys() + prev_started = staves[prev_start].start.keys() + grpid = None + intersection = filter(lambda x:x in ends, prev_started) + if len(intersection) > 0: + grpid = intersection[0] + else: + # Close the last started group + grpid = 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 staves[j].end.has_key(grpid): + 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 el.end.has_key(grpid): + 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 rational_to_lily_duration(rational_len): + d = musicexp.Duration() + + rational_len.normalize_self() + d_log = {1: 0, 2: 1, 4:2, 8:3, 16:4, 32:5, 64:6, 128:7, 256:8, 512:9}.get(rational_len.denominator(), -1) + + # Duration of the form 1/2^n or 3/2^n can be converted to a simple lilypond duration + dots = {1: 0, 3: 1, 7: 2, 15: 3, 31: 4, 63: 5, 127: 6}.get(rational_len.numerator(), -1) + if(d_log >= dots >= 0): + # account for the dots! + d.duration_log = d_log - dots + d.dots = dots + elif(d_log >= 0): + d.duration_log = d_log + d.factor = Rational(rational_len.numerator()) + else: + ly.warning(_("Encountered rational duration with denominator %s, " + "unable to convert to lilypond duration") % + rational_len.denominator()) + # TODO: Test the above error message + return None + + return d + +def musicxml_partial_to_lily(partial_len): + if partial_len > 0: + p = musicexp.Partial() + p.partial = 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, 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, 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 and the +# 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 + 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 = "'()" + + # TODO: Handle senza-misura measures + # TODO: Handle hidden time signatures(print-object="no") + # 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] = 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 = string.atoi(octave_change.get_text()) + chromatic_shift = string.atoi(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 = string.atoi(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 = string.atoi(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 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): + child = el.get_maybe_exist_named_child("display-text") + if child: + return child.get_text() + else: + return False + + +def musicxml_print_to_lily(el): + # TODO: Implement other print attributes + # + # + 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 + + +class Marker(musicexp.Music): + def __init__(self): + self.direction = 0 + self.event = None + def print_ly(self, printer): + ly.warning(_("Encountered unprocessed marker %s\n") % self) + pass + def ly_expression(self): + return "" +class RepeatMarker(Marker): + def __init__(self): + Marker.__init__(self) + self.times = 0 +class EndingMarker(Marker): + pass + + +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 != 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. angled and + # 2. -- 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": "?", + "staccatissimo": (musicexp.ShortArticulationEvent, "|"), # or "staccatissimo" + "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: + 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 = string.replace(dynamicsname, "-", "") + 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 map(lambda x: hexcolorval_to_nr(x), 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 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() + text = re.sub('^ *\n? *', '', text) #remove white spaces and line breaks before text + text = re.sub(' *\n? *$', '', text) #remove white spaces and line breaks before 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 = string.atoi(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 = string.atoi(middle.get_text()) + except ValueError: + pass + if txt == 3: + commandname += "MMM" + command += """\\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 += """\\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 += """\\combine + \\raise #1.5 \\musicglyph #\"accordion.dot\" + """ + low = mxl_event.get_maybe_exist_named_child('accordion-low') + if low: + commandname += "L" + command += """\\combine + \\raise #0.5 \musicglyph #\"accordion.dot\" + """ + + command += "\musicglyph #\"accordion.discant\"" + command = "\\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): + 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 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 ( 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 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() + + for entry in dirtype_children: + # 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 + + # 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 = musicxml_step_to_lily(mxl_cpitch.get_step()) + return r + +chordkind_dict = { + 'major': r'{}:5', + 'minor': r'{}:m5', + 'augmented': r'{}:aug5', + 'diminished': r'{}:dim5', + # Sevenths: + 'dominant': r'{}:7', + 'dominant-seventh': r'{}:7', + 'major-seventh': r'{}:maj7', + 'minor-seventh': r'{}:m7', + 'diminished-seventh': r'{}:dim7', + 'augmented-seventh': r'{}:aug7', + 'half-diminished': r'{}:dim5m7', + 'major-minor': r'{}:maj7m5', + # Sixths: + 'major-sixth': r'{}:6', + 'minor-sixth': r'{}:m6', + # Ninths: + 'dominant-ninth': r'{}:9', + 'major-ninth': r'{}:maj9', + 'minor-ninth': r'{}:m9', + # 11ths (usually as the basis for alteration): + 'dominant-11th': r'{}:11', + 'major-11th': r'{}:maj11', + 'minor-11th': r'{}:m11', + # 13ths (usually as the basis for alteration): + 'dominant-13th': r'{}:13.11', + 'major-13th': r'{}:maj13.11', + 'minor-13th': r'{}:m13', + # Suspended: + 'suspended-second': r'{}:sus2', + 'suspended-fourth': r'{}:sus4', + # Functional sixths: + # TODO + #'Neapolitan': '???', + #'Italian': '???', + #'French': '???', + #'German': '???', + # Other: + #'pedal': '???',(pedal-point bass) + 'power': r'\powerChords {}:1.5', + #'Tristan': '???', + 'other': r'{}: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 == 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 == None): + if not lines: + lines = 6 + string_tunings = [musicexp.Pitch()] * lines + for i in range(0, lines): + p = musicexp.Pitch() + p.step = 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 = 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 = string.atoi(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 = Rational(int(dur.get_text()), n._divisions) * Rational(1, 4) + res.set_real_duration(length) + duration = 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 + +instrument_drumtype_dict = { + 'Acoustic Snare Drum': 'acousticsnare', + 'Side Stick': 'sidestick', + 'Open Triangle': 'opentriangle', + 'Mute Triangle': 'mutetriangle', + 'Tambourine': 'tambourine', + 'Bass Drum': 'bassdrum', +} + + + +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 += string.replace(e.get_text(), u'\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 " \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 = Rational(0) + self.begin_moment = Rational(0) + self.pending_multibar = Rational(0) + self.ignore_skips = False + self.has_relevant_elements = False + self.measure_length = Rational(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 = 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 = Rational(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 > Rational(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 > Rational(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 > Rational(0))): + + self.add_music(barline, Rational(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 < Rational(0): + ly.warning(_('Negative skip %s (from position %s to %s)') % + (diff, current_end, moment)) + diff = Rational(0) + + if diff > Rational(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 = Rational(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 > Rational(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): + str = "Part%sVoice%s" % (part_id, name) + return musicxml_id_to_lily(str) + +def music_xml_lyrics_name_to_lily_name(part_id, name, lyricsnr): + str = music_xml_voice_name_to_lily_name(part_id, name)+("Lyrics%s" % lyricsnr) + return musicxml_id_to_lily(str) + +def music_xml_figuredbass_name_to_lily_name(part_id, voicename): + str = music_xml_voice_name_to_lily_name(part_id, voicename)+"FiguredBass" + return musicxml_id_to_lily(str) + +def music_xml_chordnames_name_to_lily_name(part_id, voicename): + str = music_xml_voice_name_to_lily_name(part_id, voicename)+"Chords" + return musicxml_id_to_lily(str) + +def music_xml_fretboards_name_to_lily_name(part_id, voicename): + str = music_xml_voice_name_to_lily_name(part_id, voicename)+"FretBoards" + return musicxml_id_to_lily(str) + +def get_all_lyric_parts_in_voice(voice): + ''' + 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.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_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.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.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.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('\skip1 ') + # Note does not have any lyric attached to it. + elif is_note_and_not_rest(elem): + result.append('\skip1 ') + + lyrics_dict[lyric_key].extend(result) + +def musicxml_voice_to_lily_voice(voice): + tuplet_events = [] + modes_found = {} + 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 = Rational(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 lyrics.keys(): + for number in lyrics.keys(): + extracted_lyrics = extract_lyrics(voice, number, lyrics) + + 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, 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, RepeatMarker) or isinstance(a, 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 > Rational(0)): + voice_builder.add_multibar_rest(n._duration) + continue + + # print a bar check at the beginning of each measure! + if n.is_first() and n._measure_position == Rational(0) and n != voice._elements[0]: + try: + num = int(n.get_parent().number) + except ValueError: + num = 0 + if num > 0: + 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) + + 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 + + if main_event and hasattr(main_event, 'drum_type') and main_event.drum_type: + modes_found['drummode'] = True + + 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, 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, 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, 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 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 ! + 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 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(), Rational(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() + + if 'drummode' in modes_found.keys(): + ## \key barfs in drummode. + ly_voice = [e for e in ly_voice + if not isinstance(e, musicexp.KeySignatureChange)] + + seq_music.elements = ly_voice + for k in lyrics.keys(): + return_value.lyrics_dict[k] = musicexp.Lyrics() + return_value.lyrics_dict[k].lyrics_syllables = lyrics[k] + + + if len(modes_found) > 1: + ly.warning(_('cannot simultaneously have more than one mode: %s') % modes_found.keys()) + + 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 + for mode in modes_found.keys(): + v = musicexp.ModeChangingMusicWrapper() + v.element = seq_music + v.mode = mode + return_value.ly_voice = v + + # 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 all_voices.items(): + + part_ly_voices = {} + for n, v in 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) @TOPLEVEL_VERSION@\n\n''' ++ +_ ("""Copyright (c) 2005--2015 by + Han-Wen Nienhuys , + Jan Nieuwenhuizen and + Reinhold Kainhofer + Patrick L. Schmidt +""" ++ +""" +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 zip-compressed MusicXML file")) + + 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 '' events to a separate FretBoards voice instead of markups")) + + p.add_option_group('', + description=( + _ ("Report bugs via %s") + % 'http://post.gmane.org/post.php' + '?group=gmane.comp.gnu.lilypond.bugs') + '\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 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() + +def uniq_list(l): + return dict([(elt, 1) for elt in l]).keys() + +# 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, + [voice.voicedata._staves.keys() + for voice in nv_dict.values()], + []) + staves_info = [] + if len(staves) > 1: + staves_info = [] + staves = uniq_list(staves) + staves.sort() + for s in staves: + thisstaff_raw_voices = [(voice_name, voice.lyrics_order, voice.figured_bass, voice.chordnames, voice.fretboards) + for (voice_name, voice) in 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 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() != 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() + printer.print_verbatim('% automatically converted by philomelos musicxml2ly v0.2.41\n') + 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() + sys.stdin = os.fdopen(sys.stdin.fileno(), 'rb', 0) # Make sys.stdin binary + bytes_read = sys.stdin.read(8192) + while bytes_read: + for b in bytes_read: + tmp.write(b) + 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") + if not container_xml: + return None + container = read_xml(StringIO.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) + + if raw_string: + io_object = StringIO.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 , or 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(".*\.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(codecs.getwriter("utf-8")(sys.stdout)) + else: + printer.set_file(codecs.open(output_ly_name, 'wb', 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() + 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 + + # Allow the user to leave out the .xml or xml on the filename + basefilename = args[0].decode('utf-8') + 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() -- 2.39.2