#!@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 """ @relocate-preamble@ """ import lilylib as ly _ = ly._ import utilities import musicxml2ly_conversion import musicxml import musicexp from rational import Rational lilypond_version = "@TOPLEVEL_VERSION@" # Store command-line options in a global variable, so we can access them everywhere options = None class Conversion_Settings: def __init__(self): self.ignore_beaming = False self.convert_stem_directions = False self.convert_rest_positions = True conversion_settings = Conversion_Settings() # Use a global variable to store the setting needed inside a \layout block. # whenever we need to change a setting or add/remove an engraver, we can access # this layout and add the corresponding settings layout_information = musicexp.Layout() # Use a global variable to store the setting needed inside a \paper block. paper = musicexp.Paper() needed_additional_definitions = [] additional_definitions = { "tuplet-note-wrapper": """ % a formatter function, which is simply a wrapper around an existing % tuplet formatter function. It takes the value returned by the given % function and appends a note of given length. #(define-public ((tuplet-number::append-note-wrapper function note) grob) (let* ((txt (if function (function grob) #f))) (if txt (markup txt #:fontsize -5 #:note note UP) (markup #:fontsize -5 #:note note UP) ) ) )""", "tuplet-non-default-denominator": """#(define ((tuplet-number::non-default-tuplet-denominator-text denominator) grob) (number->string (if denominator denominator (ly:event-property (event-cause grob) 'denominator)))) """, "tuplet-non-default-fraction": """#(define ((tuplet-number::non-default-tuplet-fraction-text denominator numerator) grob) (let* ((ev (event-cause grob)) (den (if denominator denominator (ly:event-property ev 'denominator))) (num (if numerator numerator (ly:event-property ev 'numerator)))) (format #f "~a:~a" den num))) """, } def round_to_two_digits(val): return round(val * 100) / 100 def extract_paper_information(score_partwise): defaults = score_partwise.get_maybe_exist_named_child('defaults') if not defaults: return None tenths = -1 scaling = defaults.get_maybe_exist_named_child('scaling') default_tenths_to_millimeters_ratio = 0.175 default_staff_size = 20 if scaling: mm = scaling.get_named_child('millimeters') mm = float(mm.get_text()) tn = scaling.get_maybe_exist_named_child('tenths') tn = float(tn.get_text()) # The variable 'tenths' is actually a ratio, NOT the value of . # 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()) # ... becomes # \header { texidoc = ... set_if_exists('texidoc', ids.get_file_description()); # Finally, apply the required compatibility modes # Some applications created wrong MusicXML files, so we need to # apply some compatibility mode, e.g. ignoring some features/tags # in those files software = ids.get_encoding_software_list() # Case 1: "Sibelius 5.1" with the "Dolet 3.4 for Sibelius" plugin # is missing all beam ends => ignore all beaming information ignore_beaming_software = { "Dolet 4 for Sibelius, Beta 2": "Dolet 4 for Sibelius, Beta 2", "Dolet 3.5 for Sibelius": "Dolet 3.5 for Sibelius", "Dolet 3.4 for Sibelius": "Dolet 3.4 for Sibelius", "Dolet 3.3 for Sibelius": "Dolet 3.3 for Sibelius", "Dolet 3.2 for Sibelius": "Dolet 3.2 for Sibelius", "Dolet 3.1 for Sibelius": "Dolet 3.1 for Sibelius", "Dolet for Sibelius 1.3": "Dolet for Sibelius 1.3", "Noteworthy Composer": "Noteworthy Composer's nwc2xm[", } for s in software: app_description = ignore_beaming_software.get(s, False); if app_description: conversion_settings.ignore_beaming = True ly.warning(_("Encountered file created by %s, containing " "wrong beaming information. All beaming " "information in the MusicXML file will be " "ignored") % app_description) credits = tree.get_named_children('credit') has_composer = False for cred in credits: type = credit_dict.get(cred.get_type()) if(type == 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 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 = musicxml2ly_conversion.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 musicxml_partial_to_lily(partial_len): if partial_len > 0: p = musicexp.Partial() p.partial = musicxml2ly_conversion.rational_to_lily_duration(partial_len) return p else: return None # Detect repeats and alternative endings in the chord event list(music_list) # and convert them to the corresponding musicexp objects, containing nested # music def group_repeats(music_list): repeat_replaced = True music_start = 0 i = 0 # Walk through the list of expressions, looking for repeat structure #(repeat start/end, corresponding endings). If we find one, try to find the # last event of the repeat, replace the whole structure and start over again. # For nested repeats, as soon as we encounter another starting repeat bar, # treat that one first, and start over for the outer repeat. while repeat_replaced and i < 100: i += 1 repeat_start = -1 # position of repeat start / end repeat_end = -1 # position of repeat start / end repeat_times = 0 ending_start = -1 # position of current ending start endings = [] # list of already finished endings pos = 0 last = len(music_list) - 1 repeat_replaced = False final_marker = 0 while pos < len(music_list) and not repeat_replaced: e = music_list[pos] repeat_finished = False if isinstance(e, musicxml2ly_conversion.RepeatMarker): if not repeat_times and e.times: repeat_times = e.times if e.direction == -1: if repeat_end >= 0: repeat_finished = True else: repeat_start = pos repeat_end = -1 ending_start = -1 endings = [] elif e.direction == 1: if repeat_start < 0: repeat_start = 0 if repeat_end < 0: repeat_end = pos final_marker = pos elif isinstance(e, musicxml2ly_conversion.EndingMarker): if e.direction == -1: if repeat_start < 0: repeat_start = 0 if repeat_end < 0: repeat_end = pos ending_start = pos elif e.direction == 1: if ending_start < 0: ending_start = 0 endings.append([ending_start, pos]) ending_start = -1 final_marker = pos elif not isinstance(e, musicexp.BarLine): # As soon as we encounter an element when repeat start and end # is set and we are not inside an alternative ending, # this whole repeat structure is finished => replace it if repeat_start >= 0 and repeat_end > 0 and ending_start < 0: repeat_finished = True # Finish off all repeats without explicit ending bar(e.g. when # we convert only one page of a multi-page score with repeats) if pos == last and repeat_start >= 0: repeat_finished = True final_marker = pos if repeat_end < 0: repeat_end = pos if ending_start >= 0: endings.append([ending_start, pos]) ending_start = -1 if repeat_finished: # We found the whole structure replace it! r = musicexp.RepeatedMusic() if repeat_times <= 0: repeat_times = 2 r.repeat_count = repeat_times # don't erase the first element for "implicit" repeats(i.e. no # starting repeat bars at the very beginning) start = repeat_start + 1 if repeat_start == music_start: start = music_start r.set_music(music_list[start:repeat_end]) for(start, end) in endings: s = musicexp.SequentialMusic() s.elements = music_list[start + 1:end] r.add_ending(s) del music_list[repeat_start:final_marker + 1] music_list.insert(repeat_start, r) repeat_replaced = True pos += 1 # TODO: Implement repeats until the end without explicit ending bar return music_list # Extract the settings for tuplets from the 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 # At this point music_list[i1:last] encompasses all the notes of the # tuplet. There might be dynamics following this range, however, which # apply to the last note of the tuplet. Advance last to include them # in the range. while last < len(music_list) and isinstance(music_list[last], musicexp.DynamicsEvent): last += 1 seq.elements = music_list[i1:last] tsm.element = seq new_list.append(tsm) #TODO: Handle nested tuplets!!!! new_list.extend(music_list[last:]) return new_list def musicxml_clef_to_lily(attributes): change = musicexp.ClefChange() (change.type, change.position, change.octave) = attributes.get_clef_information() return change def musicxml_time_to_lily(attributes): change = musicexp.TimeSignatureChange() # time signature function if hasattr(options, 'shift_meter') and options.shift_meter: tmp_meter = options.shift_meter.split("/",1) sig = [int(tmp_meter[0]), int(tmp_meter[1])] change.originalFractions = attributes.get_time_signature() else: sig = attributes.get_time_signature() if not sig: return None change.fractions = sig time_elm = attributes.get_maybe_exist_named_child('time') if time_elm and hasattr(time_elm, 'symbol'): change.style = { 'single-number': "'single-digit", 'cut': None, 'common': None, 'normal': "'()"}.get(time_elm.symbol, "'()") else: change.style = "'()" # 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] = musicxml2ly_conversion.musicxml_step_to_lily(k[0]) alterations.append(k) change.non_standard_alterations = alterations return change def musicxml_transpose_to_lily(attributes): transpose = attributes.get_transposition() if not transpose: return None shift = musicexp.Pitch() octave_change = transpose.get_maybe_exist_named_child('octave-change') if octave_change: shift.octave = 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 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 = musicxml2ly_conversion.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 = musicxml2ly_conversion.musicxml_step_to_lily(((("E","A","D","G","B")*(lines/5+1))[0:lines])[i]) p.octave = (([-2+int(x%5>1)+2*(x/5) for x in range(0,lines)][0:lines])[i]) p.alteration = 0 p._force_absolute_pitch = True string_tunings[i] = p string_tunings = string_tunings[::-1] return string_tunings[0:lines] def musicxml_frame_to_lily_event(frame): ev = musicexp.FretEvent() ev.strings = frame.get_strings() ev.frets = frame.get_frets() #offset = frame.get_first_fret() - 1 #offset = frame.get_first_fret() barre = [] open_strings = 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 = musicxml2ly_conversion.rational_to_lily_duration(length) if duration: res.set_duration(duration) if hasattr(n, 'parentheses') and n.parentheses == "yes": res.set_parentheses(True) return res 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 = musicxml2ly_conversion.rational_to_lily_duration(lenfrac) r.duration.factor *= self.pending_multibar / lenfrac self.elements.append(r) self.begin_moment = self.end_moment self.end_moment = self.begin_moment + self.pending_multibar self.pending_multibar = 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, musicxml2ly_conversion.RepeatMarker) or isinstance(a, musicxml2ly_conversion.EndingMarker): voice_builder.add_command(a) figured_bass_builder.add_barline(a, False) chordnames_builder.add_barline(a, False) fretboards_builder.add_barline(a, False) continue if isinstance(n, musicxml.Print): for a in musicxml_print_to_lily(n): voice_builder.add_command(a, False) continue # Continue any multimeasure-rests before trying to add bar checks! # Don't handle new MM rests yet, because for them we want bar checks! rest = n.get_maybe_exist_typed_child(musicxml.Rest) if (rest and rest.is_whole_measure() and voice_builder.pending_multibar > 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) ' + lilypond_version + '\n\n' + _ ("""Copyright (c) 2005--2016 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(lilypond_version) printer.print_verbatim('% automatically converted by musicxml2ly from ' + filename) printer.newline() printer.dump(r'\pointAndClickOff') printer.newline() if options.midi: printer.newline() printer.dump(r'\include "articulate.ly"') printer.newline() def print_ly_additional_definitions(printer, filename=None): if needed_additional_definitions: printer.newline() printer.print_verbatim('%% additional definitions required by the score:') printer.newline() for a in set(needed_additional_definitions): printer.print_verbatim(additional_definitions.get(a, '')) printer.newline() printer.newline() # Read in the tree from the given I/O object (either file or string) and # demarshall it using the classes from the musicxml.py file def read_xml(io_object, use_lxml): if use_lxml: import lxml.etree tree = lxml.etree.parse(io_object) mxl_tree = musicxml.lxml_demarshal_node(tree.getroot()) return mxl_tree else: from xml.dom import minidom, Node doc = minidom.parse(io_object) node = doc.documentElement return musicxml.minidom_demarshal_node(node) return None def read_musicxml(filename, compressed, use_lxml): raw_string = None if compressed: if filename == "-": ly.progress(_("Input is compressed, extracting raw MusicXML data from stdin"), True) # unfortunately, zipfile.ZipFile can't read directly from # stdin, so copy everything from stdin to a temp file and read # that. TemporaryFile() will remove the file when it is closed. tmp = tempfile.TemporaryFile() 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()