X-Git-Url: https://git.donarmstrong.com/?a=blobdiff_plain;f=scripts%2Fmusicxml2ly.py;h=dda374d84e9f33562721b31dd70e0133e13f03cd;hb=af1b0da3853670ee559e148cea70535d5f6ac716;hp=113552939cabb870da23c87dfde21ea652563148;hpb=3c7ed0acd8e3cd7005ae3341012fef483b7b6cb7;p=lilypond.git diff --git a/scripts/musicxml2ly.py b/scripts/musicxml2ly.py index 113552939c..dda374d84e 100644 --- a/scripts/musicxml2ly.py +++ b/scripts/musicxml2ly.py @@ -1,5 +1,5 @@ #!@TARGET_PYTHON@ - +# -*- coding: utf-8 -*- import optparse import sys import re @@ -7,6 +7,7 @@ import os import string import codecs import zipfile +import tempfile import StringIO """ @@ -21,7 +22,7 @@ import musicexp from rational import Rational -# Store command-line options in a global variable, so we can access them everythwere +# Store command-line options in a global variable, so we can access them everywhere options = None class Conversion_Settings: @@ -30,49 +31,37 @@ class Conversion_Settings: 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 +# 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 () -def progress (str): - ly.stderr_write (str + '\n') - sys.stderr.flush () - -def error_message (str): - ly.stderr_write (str + '\n') - sys.stderr.flush () - needed_additional_definitions = [] additional_definitions = { - "snappizzicato": """#(define-markup-command (snappizzicato layout props) () - (interpret-markup layout props - (markup #:stencil - (ly:stencil-translate-axis - (ly:stencil-add - (make-circle-stencil 0.7 0.1 #f) - (ly:make-stencil - (list 'draw-line 0.1 0 0.1 0 1) - '(-0.1 . 0.1) '(0.1 . 1))) - 0.7 X))))""", - "eyeglasses": """eyeglassesps = #"0.15 setlinewidth - -0.9 0 translate - 1.1 1.1 scale - 1.2 0.7 moveto - 0.7 0.7 0.5 0 361 arc - stroke - 2.20 0.70 0.50 0 361 arc - stroke - 1.45 0.85 0.30 0 180 arc - stroke - 0.20 0.70 moveto - 0.80 2.00 lineto - 0.92 2.26 1.30 2.40 1.15 1.70 curveto - stroke - 2.70 0.70 moveto - 3.30 2.00 lineto - 3.42 2.26 3.80 2.40 3.65 1.70 curveto - stroke" -eyeglasses = \markup { \with-dimensions #'(0 . 4.4) #'(0 . 2.5) \postscript #eyeglassesps }""" + + "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 ((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): @@ -178,14 +167,24 @@ def extract_score_information (tree): work = tree.get_maybe_exist_named_child ('work') if work: - set_if_exists ('title', work.get_work_title ()) set_if_exists ('worknumber', work.get_work_number ()) set_if_exists ('opus', work.get_opus ()) - else: - movement_title = tree.get_maybe_exist_named_child ('movement-title') - if movement_title: + + movement_title = tree.get_maybe_exist_named_child ('movement-title') + + # use either work-title or movement-title as title. + # if both exist use movement-title as subtitle. + # if there is only a movement-title (or work-title is empty or missing) the movement-title should be typeset as a title + if work: + work_title = work.get_work_title () + set_if_exists ('title', work_title) + if work_title == '': set_if_exists ('title', movement_title.get_text ()) - + elif movement_title: + set_if_exists ('subtitle', movement_title.get_text ()) + elif movement_title: + set_if_exists ('title', movement_title.get_text ()) + identifications = tree.get_named_children ('identification') for ids in identifications: set_if_exists ('copyright', ids.get_rights ()) @@ -193,26 +192,45 @@ def extract_score_information (tree): set_if_exists ('arranger', ids.get_arranger ()) set_if_exists ('editor', ids.get_editor ()) set_if_exists ('poet', ids.get_poet ()) - - set_if_exists ('tagline', ids.get_encoding_software ()) + set_if_exists ('encodingsoftware', ids.get_encoding_software ()) set_if_exists ('encodingdate', ids.get_encoding_date ()) set_if_exists ('encoder', ids.get_encoding_person ()) set_if_exists ('encodingdescription', ids.get_encoding_description ()) + set_if_exists ('source', ids.get_source ()) + + # miscellaneous --> 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 + # 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 - if "Dolet 3.4 for Sibelius" in software: - conversion_settings.ignore_beaming = True - progress (_ ("Encountered file created by Dolet 3.4 for Sibelius, containing wrong beaming information. All beaming information in the MusicXML file will be ignored")) - # TODO: Check for other unsupported features - + 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) + + # TODO: Check for other unsupported features return header class PartGroupInfo: @@ -226,11 +244,18 @@ class PartGroupInfo: def add_end (self, g): self.end[getattr (g, 'number', "1")] = g def print_ly (self, printer): - error_message (_ ("Unprocessed PartGroupInfo %s encountered") % self) + ly.warning (_ ("Unprocessed PartGroupInfo %s encountered") % self) def ly_expression (self): - error_message (_ ("Unprocessed PartGroupInfo %s encountered") % self) + ly.warning (_ ("Unprocessed PartGroupInfo %s encountered") % self) return '' +def musicxml_step_to_lily (step): + if step: + return (ord (step) - ord ('A') + 7 - 2) % 7 + else: + return None + + def staff_attributes_to_string_tunings (mxl_attr): details = mxl_attr.get_maybe_exist_named_child ('staff-details') if not details: @@ -240,7 +265,7 @@ def staff_attributes_to_string_tunings (mxl_attr): if staff_lines: lines = string.atoi (staff_lines.get_text ()) - tunings = [0]*lines + tunings = [musicexp.Pitch()] * lines staff_tunings = details.get_named_children ('staff-tuning') for i in staff_tunings: p = musicexp.Pitch() @@ -290,6 +315,8 @@ def staff_attributes_to_lily_staff (mxl_attr): 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 () @@ -301,8 +328,11 @@ def staff_attributes_to_lily_staff (mxl_attr): staff.string_tunings = staff_attributes_to_string_tunings (attributes) # staff.tablature_format = ??? else: - # TODO: Handle case with lines <> 5! staff = musicexp.Staff () + # TODO: Handle case with lines <> 5! + if (lines != 5): + staff.add_context_modification ("\\override StaffSymbol #'line-count = #%s" % lines) + return staff @@ -311,7 +341,7 @@ def extract_score_structure (part_list, staffinfo): score = musicexp.Score () structure = musicexp.StaffGroup (None) score.set_contents (structure) - + if not part_list: return structure @@ -328,9 +358,20 @@ def extract_score_structure (part_list, staffinfo): # Finale gives unnamed parts the name "MusicXML Part" automatically! if partname and partname.get_text() != "MusicXML Part": staff.instrument_name = partname.get_text () - if el.get_maybe_exist_named_child ('part-abbreviation'): - staff.short_instrument_name = el.get_maybe_exist_named_child ('part-abbreviation').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) + + 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 + return staff def read_score_group (el): @@ -419,8 +460,6 @@ def extract_score_structure (part_list, staffinfo): del staves[pos] # replace the staves with the whole group for j in staves[(prev_start + 1):pos]: - if j.is_group: - j.stafftype = "InnerStaffGroup" group.append_staff (j) del staves[(prev_start + 1):pos] staves.insert (prev_start + 1, group) @@ -435,26 +474,32 @@ def extract_score_structure (part_list, staffinfo): group_starts.append (pos) pos += 1 - if len (staves) == 1: - return staves[0] for i in staves: structure.append_staff (i) return score def musicxml_duration_to_lily (mxl_note): - d = musicexp.Duration () - # if the note has no Type child, then that method spits out a warning and - # returns 0, i.e. a whole note - d.duration_log = mxl_note.get_duration_log () + # if the note has no Type child, then that method returns None. In that case, + # use the tag instead. If that doesn't exist, either -> Error + dur = mxl_note.get_duration_info () + if dur: + d = musicexp.Duration () + d.duration_log = dur[0] + d.dots = dur[1] + # Grace notes by specification have duration 0, so no time modification + # factor is possible. It even messes up the output with *0/1 + if not mxl_note.get_maybe_exist_typed_child (musicxml.Grace): + d.factor = mxl_note._duration / d.get_length () + return d - d.dots = len (mxl_note.get_typed_children (musicxml.Dot)) - # Grace notes by specification have duration 0, so no time modification - # factor is possible. It even messes up the output with *0/1 - if not mxl_note.get_maybe_exist_typed_child (musicxml.Grace): - d.factor = mxl_note._duration / d.get_length () + else: + if mxl_note._duration > 0: + return rational_to_lily_duration (mxl_note._duration) + else: + mxl_note.message (_ ("Encountered note at %s without type and duration (=%s)") % (mxl_note.start, mxl_note._duration) ) + return None - return d def rational_to_lily_duration (rational_len): d = musicexp.Duration () @@ -463,17 +508,18 @@ def rational_to_lily_duration (rational_len): d_log = {1: 0, 2: 1, 4:2, 8:3, 16:4, 32:5, 64:6, 128:7, 256:8, 512:9}.get (rational_len.denominator (), -1) # Duration of the form 1/2^n or 3/2^n can be converted to a simple lilypond duration - if (d_log >= 0 and rational_len.numerator() in (1,3,5,7) ): + dots = {1: 0, 3: 1, 7: 2, 15: 3, 31: 4, 63: 5, 127: 6}.get (rational_len.numerator(), -1) + if ( d_log >= dots >= 0 ): # account for the dots! - d.dots = (rational_len.numerator()-1)/2 - d.duration_log = d_log - d.dots + d.duration_log = d_log - dots + d.dots = dots elif (d_log >= 0): d.duration_log = d_log d.factor = Rational (rational_len.numerator ()) else: - error_message (_ ("Encountered rational duration with denominator %s, " + ly.warning (_ ("Encountered rational duration with denominator %s, " "unable to convert to lilypond duration") % - rational_len.denominator ()) + rational_len.denominator ()) # TODO: Test the above error message return None @@ -485,7 +531,7 @@ def musicxml_partial_to_lily (partial_len): p.partial = rational_to_lily_duration (partial_len) return p else: - return Null + return None # Detect repeats and alternative endings in the chord event list (music_list) # and convert them to the corresponding musicexp objects, containing nested @@ -569,15 +615,15 @@ def group_repeats (music_list): 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 + 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] + s.elements = music_list[start + 1:end] r.add_ending (s) - del music_list[repeat_start:final_marker+1] + del music_list[repeat_start:final_marker + 1] music_list.insert (repeat_start, r) repeat_replaced = True pos += 1 @@ -585,6 +631,56 @@ def group_repeats (music_list): 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): @@ -593,24 +689,34 @@ def group_tuplets (music_list, events): MUSIC_LIST demarcated by EVENTS_LIST in TimeScaledMusic objects. """ - + indices = [] + brackets = {} j = 0 - for (ev_chord, tuplet_elt, fraction) in events: + 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': - indices.append ((j, None, fraction)) + 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': - indices[-1] = (indices[-1][0], j, indices[-1][2]) + 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, frac) in indices: - if i1 >= i2: + for (i1, i2, tsm) in indices: + if i1 > i2: continue new_list.extend (music_list[last:i1]) @@ -618,13 +724,10 @@ def group_tuplets (music_list, events): last = i2 + 1 seq.elements = music_list[i1:last] - tsm = musicexp.TimeScaledMusic () tsm.element = seq - tsm.numerator = frac[0] - tsm.denominator = frac[1] - new_list.append (tsm) + #TODO: Handle nested tuplets!!!! new_list.extend (music_list[last:]) return new_list @@ -634,65 +737,199 @@ 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): - (beats, type) = attributes.get_time_signature () +def musicxml_time_to_lily (attributes): + sig = attributes.get_time_signature () + if not sig: + return None change = musicexp.TimeSignatureChange() - change.fraction = (beats, type) - + 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): - start_pitch = musicexp.Pitch () - (fifths, mode) = attributes.get_key_signature () - try: - (n,a) = { - 'major' : (0,0), - 'minor' : (5,0), - }[mode] - start_pitch.step = n - start_pitch.alteration = a - except KeyError: - error_message (_ ("unknown mode %s, expecting 'major' or 'minor'") % 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) - - start_pitch.octave = 0 + 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() - change.mode = mode - change.tonic = start_pitch + + if len (key_sig) == 2 and not isinstance (key_sig[0], list): + # standard key signature, (fifths, mode) + (fifths, mode) = key_sig + change.mode = mode + + start_pitch = musicexp.Pitch () + start_pitch.octave = 0 + try: + (n, a) = { + 'major' : (0, 0), + 'minor' : (5, 0), + 'ionian' : (0, 0), + 'dorian' : (1, 0), + 'phrygian' : (2, 0), + 'lydian' : (3, 0), + 'mixolydian': (4, 0), + 'aeolian' : (5, 0), + 'locrian' : (6, 0), + }[mode] + start_pitch.step = n + start_pitch.alteration = a + except KeyError: + ly.warning (_ ("unknown mode %s, expecting 'major' or 'minor' " + "or a church mode!") % mode) + + fifth = musicexp.Pitch() + fifth.step = 4 + if fifths < 0: + fifths *= -1 + fifth.step *= -1 + fifth.normalize () + for x in range (fifths): + start_pitch = start_pitch.transposed (fifth) + change.tonic = start_pitch + + else: + # Non-standard key signature of the form [[step,alter<,octave>],...] + # MusicXML contains C,D,E,F,G,A,B as steps, lily uses 0-7, so convert + alterations = [] + for k in key_sig: + k[0] = musicxml_step_to_lily (k[0]) + alterations.append (k) + change.non_standard_alterations = alterations return change - + +def musicxml_transpose_to_lily (attributes): + transpose = attributes.get_transposition () + if not transpose: + return None + + shift = musicexp.Pitch () + octave_change = transpose.get_maybe_exist_named_child ('octave-change') + if octave_change: + shift.octave = string.atoi (octave_change.get_text ()) + chromatic_shift = string.atoi (transpose.get_named_child ('chromatic').get_text ()) + chromatic_shift_normalized = chromatic_shift % 12; + (shift.step, shift.alteration) = [ + (0, 0), (0, 1), (1, 0), (2, -1), (2, 0), + (3, 0), (3, 1), (4, 0), (5, -1), (5, 0), + (6, -1), (6, 0)][chromatic_shift_normalized]; + + shift.octave += (chromatic_shift - chromatic_shift_normalized) / 12 + + diatonic = transpose.get_maybe_exist_named_child ('diatonic') + if diatonic: + diatonic_step = string.atoi (diatonic.get_text ()) % 7 + if diatonic_step != shift.step: + # We got the alter incorrect! + old_semitones = shift.semitones () + shift.step = diatonic_step + new_semitones = shift.semitones () + shift.alteration += old_semitones - new_semitones + + transposition = musicexp.Transposition () + transposition.pitch = musicexp.Pitch ().transposed (shift) + return transposition + +def musicxml_staff_details_to_lily (attributes): + details = attributes.get_maybe_exist_named_child ('staff-details') + if not details: + return None + + ## TODO: Handle staff-type, staff-lines, staff-tuning, capo, staff-size + ret = [] + + stafflines = details.get_maybe_exist_named_child ('staff-lines') + if stafflines: + lines = string.atoi (stafflines.get_text ()); + lines_event = musicexp.StaffLinesEvent (lines); + ret.append (lines_event); + + return ret; + + def musicxml_attributes_to_lily (attrs): elts = [] - attr_dispatch = { + attr_dispatch = { 'clef': musicxml_clef_to_lily, 'time': musicxml_time_to_lily, - 'key': musicxml_key_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: - elts.append (func (attrs)) - + 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_page_layout): + val = getattr (el, "new-system") + if (val == "yes"): + elts.append (musicexp.Break ("break")) + if (hasattr (el, "new-page") and conversion_settings.convert_page_layout): + val = getattr (el, "new-page") + if (val == "yes"): + elts.append (musicexp.Break ("pageBreak")) + child = el.get_maybe_exist_named_child ("part-name-display") + if child: + elts.append (musicexp.SetEvent ("Staff.instrumentName", + "\"%s\"" % extract_display_text (child))) + child = el.get_maybe_exist_named_child ("part-abbreviation-display") + if child: + elts.append (musicexp.SetEvent ("Staff.shortInstrumentName", + "\"%s\"" % extract_display_text (child))) + return elts + + class Marker (musicexp.Music): def __init__ (self): self.direction = 0 self.event = None def print_ly (self, printer): - ly.stderr_write (_ ("Encountered unprocessed marker %s\n") % self) + ly.warning (_ ("Encountered unprocessed marker %s\n") % self) pass def ly_expression (self): return "" @@ -780,13 +1017,13 @@ spanner_type_dict = { 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: - error_message (_ ('unknown span event %s') % mxl_event) + ly.warning (_ ('unknown span event %s') % mxl_event) type = mxl_event.get_type () @@ -796,7 +1033,7 @@ def musicxml_spanner_to_lily_event (mxl_event): if span_direction != None: ev.span_direction = span_direction else: - error_message (_ ('unknown span type %s for %s') % (type, name)) + ly.warning (_ ('unknown span type %s for %s') % (type, name)) ev.set_span_type (type) ev.line_type = getattr (mxl_event, 'line-type', 'solid') @@ -867,12 +1104,6 @@ def musicxml_fingering_event (mxl_event): ev.type = mxl_event.get_text () return ev -def musicxml_snappizzicato_event (mxl_event): - needed_additional_definitions.append ("snappizzicato") - ev = musicexp.MarkupEvent () - ev.contents = "\\snappizzicato" - return ev - def musicxml_string_event (mxl_event): ev = musicexp.NoDirectionArticulationEvent () ev.type = mxl_event.get_text () @@ -934,9 +1165,9 @@ articulations_dict = { #"schleifer": "?", #"scoop": "?", #"shake": "?", - "snap-pizzicato": musicxml_snappizzicato_event, + "snap-pizzicato": "snappizzicato", #"spiccato": "?", - "staccatissimo": (musicexp.ShortArticulationEvent, "|"), # or "staccatissimo" + "staccatissimo": (musicexp.ShortArticulationEvent, "!"), # or "staccatissimo" "staccato": (musicexp.ShortArticulationEvent, "."), # or "staccato" "stopped": (musicexp.ShortArticulationEvent, "+"), # or "stopped" #"stress": "?", @@ -988,12 +1219,12 @@ def musicxml_articulation_to_lily_event (mxl_event): def musicxml_dynamics_to_lily_event (dynentry): dynamics_available = ( - "ppppp", "pppp", "ppp", "pp", "p", "mp", "mf", + "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": + if not dynamicsname or dynamicsname == "#text": return if not dynamicsname in dynamics_available: @@ -1020,7 +1251,7 @@ def hexcolorval_to_nr (hex_val): 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)) + return map (lambda x: hexcolorval_to_nr (x), res.group (2, 3, 4)) else: return None @@ -1075,7 +1306,7 @@ def musicxml_words_to_lily_event (words): # 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 + # attributes in LilyPond? Values of these attributes indicate # the number of lines return event @@ -1084,7 +1315,7 @@ def musicxml_words_to_lily_event (words): # 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 +# Idea was taken from: http://lsr.di.unimi.it/LSR/Item?id=194 def musicxml_accordion_to_markup (mxl_event): commandname = "accReg" command = "" @@ -1093,11 +1324,11 @@ def musicxml_accordion_to_markup (mxl_event): if high: commandname += "H" command += """\\combine - \\raise #2.5 \\musicglyph #\"accordion.accDot\" + \\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 + # By default, use one dot (when no or invalid content is given). The # MusicXML spec is quiet about this case... txt = 1 try: @@ -1107,32 +1338,32 @@ def musicxml_accordion_to_markup (mxl_event): if txt == 3: commandname += "MMM" command += """\\combine - \\raise #1.5 \\musicglyph #\"accordion.accDot\" + \\raise #1.5 \\musicglyph #\"accordion.dot\" \\combine - \\raise #1.5 \\translate #(cons 1 0) \\musicglyph #\"accordion.accDot\" + \\raise #1.5 \\translate #(cons 1 0) \\musicglyph #\"accordion.dot\" \\combine - \\raise #1.5 \\translate #(cons -1 0) \\musicglyph #\"accordion.accDot\" + \\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.accDot\" + \\raise #1.5 \\translate #(cons 0.5 0) \\musicglyph #\"accordion.dot\" \\combine - \\raise #1.5 \\translate #(cons -0.5 0) \\musicglyph #\"accordion.accDot\" + \\raise #1.5 \\translate #(cons -0.5 0) \\musicglyph #\"accordion.dot\" """ elif not txt <= 0: commandname += "M" command += """\\combine - \\raise #1.5 \\musicglyph #\"accordion.accDot\" + \\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.accDot\" + \\raise #0.5 \musicglyph #\"accordion.dot\" """ - command += "\musicglyph #\"accordion.accDiscant\"" + 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) @@ -1177,7 +1408,7 @@ def musicxml_harp_pedals_to_ly (mxl_event): def musicxml_eyeglasses_to_ly (mxl_event): needed_additional_definitions.append ("eyeglasses") - return musicexp.MarkEvent ("\\eyeglasses") + return musicexp.MarkEvent ("\\markup { \\eyeglasses }") def next_non_hash_index (lst, pos): pos += 1 @@ -1192,7 +1423,7 @@ def musicxml_metronome_to_ly (mxl_event): index = -1 index = next_non_hash_index (children, index) - if isinstance (children[index], musicxml.BeatUnit): + 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'): @@ -1222,12 +1453,12 @@ def musicxml_metronome_to_ly (mxl_event): except ValueError: pass else: - error_message (_ ("Unknown metronome mark, ignoring")) + ly.warning (_ ("Unknown metronome mark, ignoring")) return return ev else: #TODO: Implement the other (more complex) way for tempo marks! - error_message (_ ("Metronome marks with complex relations ( in MusicXML) are not yet implemented.")) + ly.warning (_ ("Metronome marks with complex relations ( in MusicXML) are not yet implemented.")) return # translate directions into Events, possible values: @@ -1258,7 +1489,7 @@ def musicxml_direction_to_lily (n): if hasattr (n, 'placement') 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), + # 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 () @@ -1283,6 +1514,7 @@ def musicxml_direction_to_lily (n): 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 @@ -1328,6 +1560,50 @@ def musicxml_harmony_to_lily (n): 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_notehead_to_lily (nh): + styles = [] + + # Notehead style + style = notehead_styles_dict.get (nh.get_text ().strip (), None) + style_elm = musicexp.NotestyleEvent () + if style: + style_elm.style = style + if hasattr (nh, 'filled'): + style_elm.filled = (getattr (nh, 'filled') == "yes") + if style_elm.style or (style_elm.filled != None): + styles.append (style_elm) + + # parentheses + if hasattr (nh, 'parentheses') and (nh.parentheses == "yes"): + styles.append (musicexp.ParenthesizeEvent ()) + + return styles + def musicxml_chordpitch_to_lily (mxl_cpitch): r = musicexp.ChordPitch () r.alteration = mxl_cpitch.get_alteration () @@ -1341,6 +1617,7 @@ chordkind_dict = { 'diminished': 'dim5', # Sevenths: 'dominant': '7', + 'dominant-seventh': '7', 'major-seventh': 'maj7', 'minor-seventh': 'm7', 'diminished-seventh': 'dim7', @@ -1383,7 +1660,7 @@ 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: - error_message (_ ("Unable to convert chord type %s to lilypond.") % kind) + ly.warning (_ ("Unable to convert chord type %s to lilypond.") % kind) return res def musicxml_harmony_to_lily_chordname (n): @@ -1402,15 +1679,15 @@ def musicxml_harmony_to_lily_chordname (n): 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? + # 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 + # 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: @@ -1423,7 +1700,7 @@ def musicxml_harmony_to_lily_chordname (n): d.step = deg.get_value () d.alteration = deg.get_alter () ev.add_modification (d) - #TODO: convert the user-symbols attribute: + #TODO: convert the user-symbols attribute: #major: a triangle, like Unicode 25B3 #minor: -, like Unicode 002D #augmented: +, like Unicode 002B @@ -1436,12 +1713,12 @@ def musicxml_harmony_to_lily_chordname (n): def musicxml_figured_bass_note_to_lily (n): res = musicexp.FiguredBassNote () - suffix_dict = { 'sharp' : "+", - 'flat' : "-", - 'natural' : "!", - 'double-sharp' : "++", - 'flat-flat' : "--", - 'sharp-sharp' : "++", + suffix_dict = { 'sharp' : "+", + 'flat' : "-", + 'natural' : "!", + 'double-sharp' : "++", + 'flat-flat' : "--", + 'sharp-sharp' : "++", 'slash' : "/" } prefix = n.get_maybe_exist_named_child ('prefix') if prefix: @@ -1453,7 +1730,7 @@ def musicxml_figured_bass_note_to_lily (n): 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 + # 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 @@ -1474,7 +1751,7 @@ def musicxml_figured_bass_to_lily (n): 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) + length = Rational(int(dur.get_text()), n._divisions) * Rational(1, 4) res.set_real_duration (length) duration = rational_to_lily_duration (length) if duration: @@ -1493,7 +1770,7 @@ instrument_drumtype_dict = { } def musicxml_note_to_lily_main_event (n): - pitch = None + pitch = None duration = None event = None @@ -1505,23 +1782,30 @@ def musicxml_note_to_lily_main_event (n): acc = n.get_maybe_exist_named_child ('accidental') if acc: - # let's not force accs everywhere. - event.cautionary = acc.editorial + # AccidentalCautionary in lily has parentheses + # so treat accidental explicitly in parentheses as cautionary + if hasattr(acc, 'parentheses') and acc.parentheses == "yes": + event.cautionary = True + else: + event.cautionary = acc.cautionary + # TODO: Handle editorial accidentals + # TODO: Handle the level-display setting for displaying brackets/parentheses elif n.get_maybe_exist_typed_child (musicxml.Unpitched): - # Unpitched elements have display-step and can also have - # display-octave. - unpitched = n.get_maybe_exist_typed_child (musicxml.Unpitched) - event = musicexp.NoteEvent () - event.pitch = musicxml_unpitched_to_lily (unpitched) - + # Unpitched elements have display-step and can also have + # display-octave. + unpitched = n.get_maybe_exist_typed_child (musicxml.Unpitched) + event = musicexp.NoteEvent () + event.pitch = musicxml_unpitched_to_lily (unpitched) + elif n.get_maybe_exist_typed_child (musicxml.Rest): # rests can have display-octave and display-step, which are # treated like an ordinary note pitch rest = n.get_maybe_exist_typed_child (musicxml.Rest) event = musicexp.RestEvent () - pitch = musicxml_restdisplay_to_lily (rest) - event.pitch = pitch + if options.convert_rest_positions: + pitch = musicxml_restdisplay_to_lily (rest) + event.pitch = pitch elif n.instrument_name: event = musicexp.NoteEvent () @@ -1536,10 +1820,54 @@ def musicxml_note_to_lily_main_event (n): n.message (_ ("cannot find suitable event")) if event: - event.duration = musicxml_duration_to_lily (n) + event.duration = musicxml_duration_to_lily (n) + + noteheads = n.get_named_children ('notehead') + for nh in noteheads: + styles = musicxml_notehead_to_lily (nh) + for s in styles: + event.add_associated_event (s) return event +def musicxml_lyrics_to_text (lyrics): + # 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: + return musicxml.escape_ly_output_string (text) + " --" + elif continued: + return "--" + elif extended and text: + return musicxml.escape_ly_output_string (text) + " __" + elif extended: + return "__" + elif text: + return musicxml.escape_ly_output_string (text) + else: + return "" ## TODO class NegativeSkip: @@ -1556,17 +1884,24 @@ class LilyPondVoiceBuilder: self.pending_multibar = Rational (0) self.ignore_skips = False self.has_relevant_elements = False + self.measure_length = Rational (4, 4) def _insert_multibar (self): + layout_information.set_context_item ('Score', 'skipBars = ##t') r = musicexp.MultiMeasureRest () - r.duration = musicexp.Duration() - r.duration.duration_log = 0 - r.duration.factor = self.pending_multibar + lenfrac = self.measure_length + r.duration = rational_to_lily_duration (lenfrac) + r.duration.factor *= self.pending_multibar / lenfrac self.elements.append (r) self.begin_moment = self.end_moment self.end_moment = self.begin_moment + self.pending_multibar self.pending_multibar = Rational (0) - + + def set_measure_length (self, mlen): + if (mlen != self.measure_length) and self.pending_multibar: + self._insert_multibar () + self.measure_length = mlen + def add_multibar_rest (self, duration): self.pending_multibar += duration @@ -1574,17 +1909,17 @@ class LilyPondVoiceBuilder: self.end_moment = self.begin_moment + duration def current_duration (self): return self.end_moment - self.begin_moment - - def add_music (self, music, duration): + + 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 = True + self.has_relevant_elements = self.has_relevant_elements or relevant 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: for d in self.pending_dynamics: @@ -1592,18 +1927,27 @@ class LilyPondVoiceBuilder: self.pending_dynamics = [] # Insert some music command that does not affect the position in the measure - def add_command (self, command): + 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 = True + self.has_relevant_elements = self.has_relevant_elements or relevant self.elements.append (command) - def add_barline (self, barline): - # TODO: Implement merging of default barline and custom bar line - self.add_music (barline, Rational (0)) + 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: @@ -1612,18 +1956,17 @@ class LilyPondVoiceBuilder: 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 - has_relevant = self.has_relevant_elements b = musicexp.BarLine () b.bar_number = number self.add_barline (b) - self.has_relevant_elements = has_relevant def jumpto (self, moment): current_end = self.end_moment + self.pending_multibar diff = moment - current_end - + if diff < Rational (0): - error_message (_ ('Negative skip %s') % diff) + 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): @@ -1631,6 +1974,8 @@ class LilyPondVoiceBuilder: 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 @@ -1647,7 +1992,7 @@ class LilyPondVoiceBuilder: evc = musicexp.ChordEvent () evc.elements.append (skip) - self.add_music (evc, diff) + self.add_music (evc, diff, False) if diff > Rational (0) and moment == 0: self.ignore_skips = False @@ -1657,7 +2002,7 @@ class LilyPondVoiceBuilder: value = None # if the position matches, find the last ChordEvent, do not cross a bar line! - at = len( self.elements ) - 1 + at = len(self.elements) - 1 while (at >= 0 and not isinstance (self.elements[at], musicexp.ChordEvent) and not isinstance (self.elements[at], musicexp.BarLine)): @@ -1672,7 +2017,7 @@ class LilyPondVoiceBuilder: self.jumpto (starting_at) value = None return value - + def correct_negative_skip (self, goto): self.end_moment = goto self.begin_moment = goto @@ -1690,11 +2035,11 @@ class VoiceData: self.lyrics_dict = {} self.lyrics_order = [] -def musicxml_step_to_lily (step): - if step: - return (ord (step) - ord ('A') + 7 - 2) % 7 - else: - return None +def measure_length_from_attributes (attr, current_measure_length): + len = attr.get_measure_length () + if not len: + len = current_measure_length + return len def musicxml_voice_to_lily_voice (voice): tuplet_events = [] @@ -1702,7 +2047,7 @@ def musicxml_voice_to_lily_voice (voice): lyrics = {} return_value = VoiceData () return_value.voicedata = voice - + # First pitch needed for relative mode (if selected in command-line options) first_pitch = None @@ -1714,7 +2059,7 @@ def musicxml_voice_to_lily_voice (voice): ignore_lyrics = False current_staff = None - + pending_figured_bass = [] pending_chordnames = [] @@ -1728,8 +2073,11 @@ def musicxml_voice_to_lily_voice (voice): voice_builder = LilyPondVoiceBuilder () figured_bass_builder = LilyPondVoiceBuilder () chordnames_builder = LilyPondVoiceBuilder () + current_measure_length = Rational (4, 4) + voice_builder.set_measure_length (current_measure_length) for n in voice._elements: + tie_started = False if n.get_name () == 'forward': continue staff = n.get_maybe_exist_named_child ('staff') @@ -1743,8 +2091,68 @@ def musicxml_voice_to_lily_voice (voice): 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) 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) + 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) + 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 = musicxml_barline_to_lily (n) + 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) + elif isinstance (a, RepeatMarker) or isinstance (a, EndingMarker): + voice_builder.add_command (a) + figured_bass_builder.add_barline (a, False) + chordnames_builder.add_barline (a, False) + 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) + + # Start any new multimeasure rests + if (rest and rest.is_whole_measure ()): + voice_builder.add_multibar_rest (n._duration) + continue + + if isinstance (n, musicxml.Direction): for a in musicxml_direction_to_lily (n): if a.wait_for_note (): @@ -1769,59 +2177,19 @@ def musicxml_voice_to_lily_voice (voice): pending_figured_bass.append (a) continue - is_chord = n.get_maybe_exist_named_child ('chord') - if not is_chord: - try: - voice_builder.jumpto (n._when) - except NegativeSkip, neg: - voice_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.Attributes): - if n.is_first () and n._measure_position == Rational (0): - try: - number = int (n.get_parent ().number) - except ValueError: - number = 0 - if number > 0: - voice_builder.add_bar_check (number) - figured_bass_builder.add_bar_check (number) - chordnames_builder.add_bar_check (number) - for a in musicxml_attributes_to_lily (n): voice_builder.add_command (a) - continue - - if isinstance (n, musicxml.Barline): - barlines = musicxml_barline_to_lily (n) - for a in barlines: - if isinstance (a, musicexp.BarLine): - voice_builder.add_barline (a) - elif isinstance (a, RepeatMarker) or isinstance (a, EndingMarker): - 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': - error_message (_ ('unexpected %s; expected %s or %s or %s') % (n, 'Note', 'Attributes', 'Barline')) + n.message (_ ('unexpected %s; expected %s or %s or %s') % (n, 'Note', 'Attributes', 'Barline')) continue - rest = n.get_maybe_exist_typed_child (musicxml.Rest) - if (rest - and rest.is_whole_measure ()): - - voice_builder.add_multibar_rest (n._duration) - continue - - if n.is_first () and n._measure_position == Rational (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) - main_event = musicxml_note_to_lily_main_event (n) if main_event and not first_pitch: first_pitch = main_event.pitch @@ -1832,19 +2200,35 @@ def musicxml_voice_to_lily_voice (voice): modes_found['drummode'] = True ev_chord = voice_builder.last_event_chord (n._when) - if not ev_chord: + 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 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 - if n.get_maybe_exist_typed_child (musicxml.Chord) and ev_chord.grace_elements: - 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'): + + # 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" @@ -1860,7 +2244,7 @@ def musicxml_voice_to_lily_voice (voice): # 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: @@ -1877,7 +2261,7 @@ def musicxml_voice_to_lily_voice (voice): 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) @@ -1889,51 +2273,61 @@ def musicxml_voice_to_lily_voice (voice): chordnames_builder.add_music (cn, ev_chord.get_length ()) pending_chordnames = [] - 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 | + # +tied | +slur | +tuplet | glissando | slide | # ornaments | technical | articulations | dynamics | - # +fermata | arpeggiate | non-arpeggiate | + # +fermata | arpeggiate | non-arpeggiate | # accidental-mark | other-notation for notations in notations_children: for tuplet_event in notations.get_tuplets(): - mod = n.get_maybe_exist_typed_child (musicxml.Time_modification) - frac = (1,1) - if mod: - frac = mod.get_fraction () - - tuplet_events.append ((ev_chord, tuplet_event, frac)) - - slurs = [s for s in notations.get_named_children ('slur') - if s.get_type () in ('start','stop')] - if slurs: - if len (slurs) > 1: - error_message (_ ('cannot have two simultaneous slurs')) + 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 - if not grace: - if slurs[0].get_type () == 'start': - inside_slur = True - elif slurs[0].get_type () == 'stop': - inside_slur = False - lily_ev = musicxml_spanner_to_lily_event (slurs[0]) + 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: + if ev: ev_chord.append (ev) arpeggiate = notations.get_named_children ('arpeggiate') @@ -1964,7 +2358,7 @@ def musicxml_voice_to_lily_voice (voice): # Articulations can contain the following child elements: # accent | strong-accent | staccato | tenuto | # detached-legato | staccatissimo | spiccato | - # scoop | plop | doit | falloff | breath-mark | + # scoop | plop | doit | falloff | breath-mark | # caesura | stress | unstress # Technical can contain the following child elements: # up-bow | down-bow | harmonic | open-string | @@ -1974,7 +2368,7 @@ def musicxml_voice_to_lily_voice (voice): # toe | fingernails | other-technical # Ornaments can contain the following child elements: # trill-mark | turn | delayed-turn | inverted-turn | - # shake | wavy-line | mordent | inverted-mordent | + # shake | wavy-line | mordent | inverted-mordent | # schleifer | tremolo | other-ornament, accidental-mark ornaments = notations.get_named_children ('ornaments') ornaments += notations.get_named_children ('articulations') @@ -1983,7 +2377,7 @@ def musicxml_voice_to_lily_voice (voice): for a in ornaments: for ch in a.get_all_children (): ev = musicxml_articulation_to_lily_event (ch) - if ev: + if ev: ev_chord.append (ev) dynamics = notations.get_named_children ('dynamics') @@ -1996,7 +2390,7 @@ def musicxml_voice_to_lily_voice (voice): mxl_beams = [b for b in n.get_named_children ('beam') if (b.get_type () in ('begin', 'end') - and b.is_primary ())] + 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: @@ -2005,14 +2399,6 @@ def musicxml_voice_to_lily_voice (voice): is_beamed = True elif beam_ev.span_direction == 1: # beam and thus melisma ends here is_beamed = False - - if tuplet_event: - mod = n.get_maybe_exist_typed_child (musicxml.Time_modification) - frac = (1,1) - if mod: - frac = mod.get_fraction () - - tuplet_events.append ((ev_chord, tuplet_event, frac)) # Extract the lyrics if not rest and not ignore_lyrics: @@ -2021,18 +2407,24 @@ def musicxml_voice_to_lily_voice (voice): for l in note_lyrics_elements: if l.get_number () < 0: for k in lyrics.keys (): - lyrics[k].append (l.lyric_to_text ()) + lyrics[k].append (musicxml_lyrics_to_text (l)) note_lyrics_processed.append (k) else: - lyrics[l.number].append(l.lyric_to_text ()) + lyrics[l.number].append(musicxml_lyrics_to_text (l)) note_lyrics_processed.append (l.number) for lnr in lyrics.keys (): if not lnr in note_lyrics_processed: lyrics[lnr].append ("\skip4") - ## force trailing mm rests to be written out. + # 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)) - + ly_voice = group_tuplets (voice_builder.elements, tuplet_events) ly_voice = group_repeats (ly_voice) @@ -2042,16 +2434,16 @@ def musicxml_voice_to_lily_voice (voice): ## \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: - error_message (_ ('cannot simultaneously have more than one mode: %s') % modes_found.keys ()) - + ly.warning (_ ('cannot simultaneously have more than one mode: %s') % modes_found.keys ()) + if options.relative: v = musicexp.RelativeMusic () v.element = seq_music @@ -2064,31 +2456,31 @@ def musicxml_voice_to_lily_voice (voice): 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 = figured_bass_builder.elements + fbass_music.elements = group_repeats (figured_bass_builder.elements) v = musicexp.ModeChangingMusicWrapper() v.mode = 'figuremode' v.element = fbass_music return_value.figured_bass = v - + # create \chordmode { chords } if chordnames_builder.has_relevant_elements: cname_music = musicexp.SequentialMusic () - cname_music.elements = chordnames_builder.elements + cname_music.elements = group_repeats (chordnames_builder.elements) v = musicexp.ModeChangingMusicWrapper() v.mode = 'chordmode' v.element = cname_music return_value.chordnames = v - + 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) @@ -2107,11 +2499,11 @@ def musicxml_unpitched_to_lily (mxl_unpitched): p = None step = mxl_unpitched.get_step () if step: - p = musicexp.Pitch () - p.step = musicxml_step_to_lily (step) + p = musicexp.Pitch () + p.step = musicxml_step_to_lily (step) octave = mxl_unpitched.get_octave () if octave and p: - p.octave = octave - 4 + p.octave = octave - 4 return p def musicxml_restdisplay_to_lily (mxl_rest): @@ -2136,7 +2528,17 @@ def voices_in_part (part): def voices_in_part_in_parts (parts): """return a Part -> Name -> Voice dictionary""" - return dict([(p.id, voices_in_part (p)) for p in parts]) + # don't crash if p 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): @@ -2148,7 +2550,7 @@ def get_all_voices (parts): part_ly_voices = {} for n, v in name_voice.items (): - progress (_ ("Converting to LilyPond expressions...")) + ly.progress (_ ("Converting to LilyPond expressions..."), True) # musicxml_voice_to_lily_voice returns (lily_voice, {nr->lyrics, nr->lyrics}) part_ly_voices[n] = musicxml_voice_to_lily_voice (v) @@ -2171,7 +2573,7 @@ If the given filename is -, musicxml2ly reads from the command line. p.version = ('''%prog (LilyPond) @TOPLEVEL_VERSION@\n\n''' + -_ ("""Copyright (c) 2005--2008 by +_ ("""Copyright (c) 2005--2015 by Han-Wen Nienhuys , Jan Nieuwenhuizen and Reinhold Kainhofer @@ -2188,8 +2590,9 @@ information.""") % 'lilypond') help=_ ("show version number and exit")) p.add_option ('-v', '--verbose', - action = "store_true", - dest = 'verbose', + action="callback", + callback=ly.handle_loglevel_option, + callback_args=("DEBUG",), help = _ ("be verbose")) p.add_option ('', '--lxml', @@ -2218,15 +2621,35 @@ information.""") % 'lilypond') p.add_option ('-l', '--language', metavar = _ ("LANG"), action = "store", - help = _ ("use a different language file 'LANG.ly' and corresponding pitch names, e.g. 'deutsch' for deutsch.ly")) + 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', + 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 ('--no-beaming', + 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 ('--npl', '--no-page-layout', + action = "store_false", + default = True, + dest = "convert_page_layout", + help = _ ("do not convert the exact page layout and breaks")) + + p.add_option ('--no-beaming', action = "store_false", default = True, dest = "convert_beaming", @@ -2239,27 +2662,35 @@ information.""") % 'lilypond') 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")) + p.add_option_group ('', - description = (_ ("Report bugs via") - + ''' http://post.gmane.org/post.php''' - '''?group=gmane.comp.gnu.lilypond.bugs\n''')) + description = ( + _ ("Report bugs via %s") + % 'http://post.gmane.org/post.php' + '?group=gmane.comp.gnu.lilypond.bugs') + '\n') return p def music_xml_voice_name_to_lily_name (part_id, name): str = "Part%sVoice%s" % (part_id, name) - return musicxml_id_to_lily (str) + return musicxml_id_to_lily (str) def music_xml_lyrics_name_to_lily_name (part_id, name, lyricsnr): str = "Part%sVoice%sLyrics%s" % (part_id, name, lyricsnr) - return musicxml_id_to_lily (str) + return musicxml_id_to_lily (str) def music_xml_figuredbass_name_to_lily_name (part_id, voicename): str = "Part%sVoice%sFiguredBass" % (part_id, voicename) - return musicxml_id_to_lily (str) + return musicxml_id_to_lily (str) def music_xml_chordnames_name_to_lily_name (part_id, voicename): str = "Part%sVoice%sChords" % (part_id, voicename) - return musicxml_id_to_lily (str) + return musicxml_id_to_lily (str) def print_voice_definitions (printer, part_list, voices): for part in part_list: @@ -2288,9 +2719,9 @@ def print_voice_definitions (printer, part_list, voices): def uniq_list (l): - return dict ([(elt,1) for elt in l]).keys () + return dict ([(elt, 1) for elt in l]).keys () -# format the information about the staff in the form +# format the information about the staff in the form # [staffid, # [ # [voiceid1, [lyricsid11, lyricsid12,...], figuredbassid1], @@ -2320,7 +2751,7 @@ def update_score_setup (score_structure, part_list, voices): part_id = part_definition.id nv_dict = voices.get (part_id) if not nv_dict: - error_message (_ ('unknown part in part-list: %s') % part_id) + ly.warning (_ ('unknown part in part-list: %s') % part_id) continue staves = reduce (lambda x,y: x+ y, @@ -2333,12 +2764,12 @@ def update_score_setup (score_structure, part_list, voices): staves = uniq_list (staves) staves.sort () for s in staves: - thisstaff_raw_voices = [(voice_name, voice.lyrics_order, voice.figured_bass, voice.chordnames) + thisstaff_raw_voices = [(voice_name, voice.lyrics_order, voice.figured_bass, voice.chordnames) 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) + thisstaff_raw_voices = [(voice_name, voice.lyrics_order, voice.figured_bass, voice.chordnames) 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) @@ -2350,7 +2781,7 @@ def update_layout_information (): def print_ly_preamble (printer, filename): printer.dump_version () - printer.print_verbatim ('%% automatically converted from %s\n' % filename) + printer.print_verbatim ('%% automatically converted by musicxml2ly from %s\n' % filename) def print_ly_additional_definitions (printer, filename): if needed_additional_definitions: @@ -2362,7 +2793,7 @@ def print_ly_additional_definitions (printer, filename): printer.newline () printer.newline () -# Read in the tree from the given I/O object (either file or string) and +# 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: @@ -2382,10 +2813,20 @@ def read_musicxml (filename, compressed, use_lxml): raw_string = None if compressed: if filename == "-": - progress (_ ("Input is compressed, extracting raw MusicXML data from stdin") ) - z = zipfile.ZipFile (sys.stdin) + 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: - progress (_ ("Input file %s is compressed, extracting raw MusicXML data") % filename) + 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: @@ -2415,9 +2856,9 @@ def read_musicxml (filename, compressed, use_lxml): def convert (filename, options): if filename == "-": - progress (_ ("Reading MusicXML from Standard input ...") ) + ly.progress (_ ("Reading MusicXML from Standard input ..."), True) else: - progress (_ ("Reading MusicXML from %s ...") % filename) + ly.progress (_ ("Reading MusicXML from %s ...") % filename, True) tree = read_musicxml (filename, options.compressed, options.use_lxml) score_information = extract_score_information (tree) @@ -2438,7 +2879,7 @@ def convert (filename, options): update_layout_information () if not options.output_name: - options.output_name = os.path.basename (filename) + 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] @@ -2450,9 +2891,9 @@ def convert (filename, options): else: output_ly_name = options.output_name + '.ly' - progress (_ ("Output to `%s'") % output_ly_name) + ly.progress (_ ("Output to `%s'") % output_ly_name, True) printer = musicexp.Output_printer() - #progress (_ ("Output to `%s'") % defs_ly_name) + #ly.progress (_ ("Output to `%s'") % defs_ly_name, True) if (options.output_name == "-"): printer.set_file (codecs.getwriter ("utf-8")(sys.stdout)) else: @@ -2461,12 +2902,12 @@ def convert (filename, options): print_ly_additional_definitions (printer, filename) if score_information: score_information.print_ly (printer) - if paper_information: + 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 () @@ -2495,24 +2936,32 @@ def main (): opt_parser.print_usage() sys.exit (2) + if options.midi: + musicexp.set_create_midi (options.midi) + if options.language: musicexp.set_pitch_language (options.language) needed_additional_definitions.append (options.language) - additional_definitions[options.language] = "\\include \"%s.ly\"\n" % 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 # Allow the user to leave out the .xml or xml on the filename - if args[0]=="-": # Read from stdin - filename="-" + basefilename = args[0].decode('utf-8') + if basefilename == "-": # Read from stdin + filename = "-" else: - filename = get_existing_filename_with_extension (args[0], "xml") + filename = get_existing_filename_with_extension (basefilename, "xml") if not filename: - filename = get_existing_filename_with_extension (args[0], "mxl") + 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: - progress (_ ("Unable to find input file %s") % args[0]) + ly.error (_ ("Unable to find input file %s") % basefilename) if __name__ == '__main__': main()