X-Git-Url: https://git.donarmstrong.com/?a=blobdiff_plain;f=scripts%2Fmusicxml2ly.py;h=ba7be37a65f025d71dd24164803af20af35aab52;hb=39c0e8adb498996f5e414b1d0bc3a20ac81aa619;hp=e2a54008e321eef92b447d57d51f77a525eb710b;hpb=edbaa4793402ff05a4d3c2823b0cd36862e591c2;p=lilypond.git diff --git a/scripts/musicxml2ly.py b/scripts/musicxml2ly.py index e2a54008e3..ba7be37a65 100644 --- a/scripts/musicxml2ly.py +++ b/scripts/musicxml2ly.py @@ -44,35 +44,132 @@ def error_message (str): 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-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 "~a:~a" den num))) +""", + + "compound-time-signature": """%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% Formatting of (possibly complex) compound time signatures +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +#(define-public (insert-markups l m) + (let* ((ll (reverse l))) + (let join-markups ((markups (list (car ll))) + (remaining (cdr ll))) + (if (pair? remaining) + (join-markups (cons (car remaining) (cons m markups)) (cdr remaining)) + markups)))) + +% Use a centered-column inside a left-column, because the centered column +% moves its reference point to the center, which the left-column undoes. +% The center-column also aligns its contented centered, which is not undone... +#(define-public (format-time-fraction time-sig-fraction) + (let* ((revargs (reverse (map number->string time-sig-fraction))) + (den (car revargs)) + (nums (reverse (cdr revargs)))) + (make-override-markup '(baseline-skip . 0) + (make-number-markup + (make-left-column-markup (list + (make-center-column-markup (list + (make-line-markup (insert-markups nums "+")) + den)))))))) + +#(define-public (format-complex-compound-time time-sig) + (let* ((sigs (map format-time-fraction time-sig))) + (make-override-markup '(baseline-skip . 0) + (make-number-markup + (make-line-markup + (insert-markups sigs (make-vcenter-markup "+"))))))) + +#(define-public (format-compound-time time-sig) + (cond + ((not (pair? time-sig)) (null-markup)) + ((pair? (car time-sig)) (format-complex-compound-time time-sig)) + (else (format-time-fraction time-sig)))) + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% Measure length calculation of (possibly complex) compound time signatures +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +#(define-public (calculate-time-fraction time-sig-fraction) + (let* ((revargs (reverse time-sig-fraction)) + (den (car revargs)) + (nums (cdr revargs))) + (ly:make-moment (apply + nums) den))) + +#(define-public (calculate-complex-compound-time time-sig) + (let* ((sigs (map calculate-time-fraction time-sig))) + (let add-moment ((moment ZERO-MOMENT) + (remaining sigs)) + (if (pair? remaining) + (add-moment (ly:moment-add moment (car remaining)) (cdr remaining)) + moment)))) + +#(define-public (calculate-compound-measure-length time-sig) + (cond + ((not (pair? time-sig)) (ly:make-moment 4 4)) + ((pair? (car time-sig)) (calculate-complex-compound-time time-sig)) + (else (calculate-time-fraction time-sig)))) + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% Base beat lenth +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +#(define-public (calculate-compound-base-beat-full time-sig) + (let* ((den (map last time-sig))) + (apply max den))) + +#(define-public (calculate-compound-base-beat time-sig) + (ly:make-moment 1 (cond + ((not (pair? time-sig)) 4) + ((pair? (car time-sig)) (calculate-compound-base-beat-full time-sig)) + (else (calculate-compound-base-beat-full (list time-sig)))))) + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% The music function to set the complex time signature +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +compoundMeter = +#(define-music-function (parser location args) (pair?) + (let ((mlen (calculate-compound-measure-length args)) + (beat (calculate-compound-base-beat args))) + #{ +\once \override Staff.TimeSignature #'stencil = #ly:text-interface::print +\once \override Staff.TimeSignature #'text = #(format-compound-time $args) +% \set Staff.beatGrouping = #(reverse (cdr (reverse $args))) +\set Timing.measureLength = $mlen +\set Timing.timeSignatureFraction = #(cons (ly:moment-main-numerator $mlen) + (ly:moment-main-denominator $mlen)) +\set Timing.beatLength = $beat + +% TODO: Implement beatGrouping and auto-beam-settings!!! +#} )) +""" } def round_to_two_digits (val): @@ -176,15 +273,15 @@ def extract_score_information (tree): if value: header.set_field (field, musicxml.escape_ly_output_string (value)) + movement_title = tree.get_maybe_exist_named_child ('movement-title') + if movement_title: + set_if_exists ('title', movement_title.get_text ()) work = tree.get_maybe_exist_named_child ('work') if work: + # Overwrite the title from movement-title with work->title 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: - set_if_exists ('title', movement_title.get_text ()) identifications = tree.get_named_children ('identification') for ids in identifications: @@ -210,14 +307,23 @@ def extract_score_information (tree): # 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")) - if "Noteworthy Composer" in software: - conversion_settings.ignore_beaming = True - progress (_ ("Encountered file created by Noteworthy Composer's nwc2xml, 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 + progress (_ ("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: @@ -446,24 +552,27 @@ def extract_score_structure (part_list, staffinfo): def musicxml_duration_to_lily (mxl_note): - d = musicexp.Duration () # 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 - d.duration_log = mxl_note.get_duration_log () - if d.duration_log == None: - 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 - else: - d.dots = len (mxl_note.get_typed_children (musicxml.Dot)) + 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 + 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 + + def rational_to_lily_duration (rational_len): d = musicexp.Duration () @@ -593,6 +702,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): @@ -606,25 +765,28 @@ def group_tuplets (music_list, events): 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 = tuplet_elt.number + nr = 0 + if hasattr (tuplet_elt, 'number'): + nr = getattr (tuplet_elt, 'number') if tuplet_elt.type == 'start': - tuplet_info = [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': bracket_info = brackets.get (nr, None) if bracket_info: - bracket_info[1] = j + bracket_info[1] = j # Set the ending position to j del brackets[nr] new_list = [] last = 0 - for (i1, i2, frac) in indices: + for (i1, i2, tsm) in indices: if i1 > i2: continue @@ -633,13 +795,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 @@ -651,48 +810,76 @@ def musicxml_clef_to_lily (attributes): return change def musicxml_time_to_lily (attributes): - (beats, type) = attributes.get_time_signature () - + sig = attributes.get_time_signature () + if not sig: + return None change = musicexp.TimeSignatureChange() - change.fraction = (beats, type) - + change.fractions = sig + if (len(sig) != 2) or isinstance (sig[0], list): + needed_additional_definitions.append ("compound-time-signature") + + 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), - '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: - 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 () + key_sig = attributes.get_key_signature () + if not key_sig or not (isinstance (key_sig, list) or isinstance (key_sig, tuple)): + error_message (_ ("Unable to extract key signature!")) + return None + + change = musicexp.KeySignatureChange() - for x in range (fifths): - start_pitch = start_pitch.transposed (fifth) + 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.octave = 0 + 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: + error_message (_ ("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 - change = musicexp.KeySignatureChange() - change.mode = mode - change.tonic = start_pitch + else: + # Non-standard key signature of the form [[step,alter<,octave>],...] + change.non_standard_alterations = key_sig return change def musicxml_transpose_to_lily (attributes): @@ -739,10 +926,36 @@ def musicxml_attributes_to_lily (attrs): for (k, func) in attr_dispatch.items (): children = attrs.get_named_children (k) if children: - elts.append (func (attrs)) + ev = func (attrs) + if ev: + elts.append (ev) + + return elts +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")) return elts + class Marker (musicexp.Music): def __init__ (self): self.direction = 0 @@ -923,12 +1136,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 () @@ -990,7 +1197,7 @@ articulations_dict = { #"schleifer": "?", #"scoop": "?", #"shake": "?", - "snap-pizzicato": musicxml_snappizzicato_event, + "snap-pizzicato": "snappizzicato", #"spiccato": "?", "staccatissimo": (musicexp.ShortArticulationEvent, "|"), # or "staccatissimo" "staccato": (musicexp.ShortArticulationEvent, "."), # or "staccato" @@ -1131,7 +1338,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 @@ -1233,7 +1440,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 @@ -1384,6 +1591,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 () @@ -1397,6 +1648,7 @@ chordkind_dict = { 'diminished': 'dim5', # Sevenths: 'dominant': '7', + 'dominant-seventh': '7', 'major-seventh': 'maj7', 'minor-seventh': 'm7', 'diminished-seventh': 'dim7', @@ -1458,7 +1710,7 @@ 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 @@ -1576,8 +1828,9 @@ def musicxml_note_to_lily_main_event (n): # 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 () @@ -1592,10 +1845,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: @@ -1612,12 +1909,12 @@ class LilyPondVoiceBuilder: self.pending_multibar = Rational (0) self.ignore_skips = False self.has_relevant_elements = False - self.measure_length = (4, 4) + self.measure_length = Rational (4, 4) def _insert_multibar (self): layout_information.set_context_item ('Score', 'skipBars = ##t') r = musicexp.MultiMeasureRest () - lenfrac = Rational (self.measure_length[0], self.measure_length[1]) + lenfrac = self.measure_length r.duration = rational_to_lily_duration (lenfrac) r.duration.factor *= self.pending_multibar / lenfrac self.elements.append (r) @@ -1638,12 +1935,12 @@ class LilyPondVoiceBuilder: 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) @@ -1655,18 +1952,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: @@ -1675,11 +1981,9 @@ 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 @@ -1713,7 +2017,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 @@ -1763,11 +2067,10 @@ def musicxml_step_to_lily (step): return None def measure_length_from_attributes (attr, current_measure_length): - mxl = attr.get_named_attribute ('time') - if mxl: - return attr.get_time_signature () - else: - return 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 = [] @@ -1801,10 +2104,11 @@ def musicxml_voice_to_lily_voice (voice): voice_builder = LilyPondVoiceBuilder () figured_bass_builder = LilyPondVoiceBuilder () chordnames_builder = LilyPondVoiceBuilder () - current_measure_length = (4, 4) + 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') @@ -1818,6 +2122,8 @@ 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') @@ -1825,8 +2131,12 @@ def musicxml_voice_to_lily_voice (voice): 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): @@ -1834,8 +2144,18 @@ def musicxml_voice_to_lily_voice (voice): 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! @@ -1984,7 +2304,6 @@ 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 = [] @@ -1996,12 +2315,8 @@ def musicxml_voice_to_lily_voice (voice): # 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)) + 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 @@ -2014,8 +2329,7 @@ def musicxml_voice_to_lily_voice (voice): if len (endslurs) > 1: endslurs[0].message (_ ('Cannot have two simultaneous (closing) slurs')) # record the slur status for the next note in the loop - if not grace: - inside_slur = False + inside_slur = False lily_ev = musicxml_spanner_to_lily_event (endslurs[0]) ev_chord.append (lily_ev) @@ -2027,8 +2341,7 @@ def musicxml_voice_to_lily_voice (voice): 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: - inside_slur = True + inside_slur = True lily_ev = musicxml_spanner_to_lily_event (startslurs[0]) ev_chord.append (lily_ev) @@ -2038,6 +2351,7 @@ def musicxml_voice_to_lily_voice (voice): if mxl_tie and mxl_tie.type == 'start': ev_chord.append (musicexp.TieEvent ()) is_tied = True + tie_started = True else: is_tied = False @@ -2124,16 +2438,22 @@ 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) @@ -2171,7 +2491,7 @@ def musicxml_voice_to_lily_voice (voice): # 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 @@ -2180,7 +2500,7 @@ def musicxml_voice_to_lily_voice (voice): # 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 @@ -2239,7 +2559,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): @@ -2274,7 +2604,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--2009 by Han-Wen Nienhuys , Jan Nieuwenhuizen and Reinhold Kainhofer @@ -2329,6 +2659,18 @@ information.""") % 'lilypond') 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 ('--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, @@ -2343,9 +2685,10 @@ information.""") % 'lilypond') dest = 'output_name', help = _ ("set output filename to FILE, stdout if -")) 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): @@ -2564,7 +2907,7 @@ 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) @@ -2603,6 +2946,7 @@ def main (): needed_additional_definitions.append (options.language) additional_definitions[options.language] = "\\include \"%s.ly\"\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 basefilename = args[0].decode('utf-8')