X-Git-Url: https://git.donarmstrong.com/?a=blobdiff_plain;f=scripts%2Fmusicxml2ly.py;h=e2a54008e321eef92b447d57d51f77a525eb710b;hb=edbaa4793402ff05a4d3c2823b0cd36862e591c2;hp=a2e09e87e29b4beeeffee04556ed34c7ae0d3d7c;hpb=8de0c0e819512b7b2cb3a8cf53ce359cc3256110;p=lilypond.git diff --git a/scripts/musicxml2ly.py b/scripts/musicxml2ly.py index a2e09e87e2..e2a54008e3 100644 --- a/scripts/musicxml2ly.py +++ b/scripts/musicxml2ly.py @@ -1,5 +1,5 @@ #!@TARGET_PYTHON@ - +# -*- coding: utf-8 -*- import optparse import sys import re @@ -199,6 +199,8 @@ def extract_score_information (tree): 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 ('texidoc', ids.get_file_description ()); # Finally, apply the required compatibility modes # Some applications created wrong MusicXML files, so we need to @@ -211,6 +213,9 @@ def extract_score_information (tree): 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 return header @@ -419,8 +424,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) @@ -444,17 +447,22 @@ 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 spits out a warning and - # returns 0, i.e. a whole note + # 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 () - - 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 () - - return d + 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)) + # 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 def rational_to_lily_duration (rational_len): d = musicexp.Duration () @@ -595,6 +603,7 @@ def group_tuplets (music_list, events): indices = [] + brackets = {} j = 0 for (ev_chord, tuplet_elt, fraction) in events: @@ -602,15 +611,21 @@ def group_tuplets (music_list, events): if music_list[j] == ev_chord: break j += 1 + nr = tuplet_elt.number if tuplet_elt.type == 'start': - indices.append ((j, None, fraction)) + tuplet_info = [j, None, fraction] + 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 + del brackets[nr] new_list = [] last = 0 for (i1, i2, frac) in indices: - if i1 >= i2: + if i1 > i2: continue new_list.extend (music_list[last:i1]) @@ -648,8 +663,15 @@ def musicxml_key_to_lily (attributes): (fifths, mode) = attributes.get_key_signature () try: (n,a) = { - 'major' : (0,0), - 'minor' : (5,0), + '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 @@ -672,13 +694,47 @@ def musicxml_key_to_lily (attributes): change.mode = mode change.tonic = start_pitch 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_attributes_to_lily (attrs): elts = [] 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, } for (k, func) in attr_dispatch.items (): children = attrs.get_named_children (k) @@ -1639,6 +1695,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 @@ -1762,32 +1820,9 @@ def musicxml_voice_to_lily_voice (voice): voice_builder.add_partial (a) continue - if isinstance (n, musicxml.Direction): - for a in musicxml_direction_to_lily (n): - if a.wait_for_note (): - voice_builder.add_dynamics (a) - else: - voice_builder.add_command (a) - continue - - if isinstance (n, musicxml.Harmony): - for a in musicxml_harmony_to_lily (n): - if a.wait_for_note (): - voice_builder.add_dynamics (a) - else: - voice_builder.add_command (a) - for a in musicxml_harmony_to_lily_chordname (n): - pending_chordnames.append (a) - continue - - if isinstance (n, musicxml.FiguredBass): - a = musicxml_figured_bass_to_lily (n) - if a: - pending_figured_bass.append (a) - continue - is_chord = n.get_maybe_exist_named_child ('chord') - if not is_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) except NegativeSkip, neg: @@ -1828,6 +1863,31 @@ def musicxml_voice_to_lily_voice (voice): 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 (): + voice_builder.add_dynamics (a) + else: + voice_builder.add_command (a) + continue + + if isinstance (n, musicxml.Harmony): + for a in musicxml_harmony_to_lily (n): + if a.wait_for_note (): + voice_builder.add_dynamics (a) + else: + voice_builder.add_command (a) + for a in musicxml_harmony_to_lily_chordname (n): + pending_chordnames.append (a) + continue + + if isinstance (n, musicxml.FiguredBass): + a = musicxml_figured_bass_to_lily (n) + if a: + pending_figured_bass.append (a) + continue + if isinstance (n, musicxml.Attributes): for a in musicxml_attributes_to_lily (n): voice_builder.add_command (a) @@ -1855,15 +1915,31 @@ def musicxml_voice_to_lily_voice (voice): 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" @@ -1924,7 +2000,7 @@ def musicxml_voice_to_lily_voice (voice): frac = (1,1) if mod: frac = mod.get_fraction () - + tuplet_events.append ((ev_chord, tuplet_event, frac)) # First, close all open slurs, only then start any new slur @@ -2018,7 +2094,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') @@ -2040,14 +2116,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: @@ -2537,17 +2605,21 @@ def main (): conversion_settings.ignore_beaming = not options.convert_beaming # 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 + basefilename = "-" 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]) + progress (_ ("Unable to find input file %s") % basefilename) if __name__ == '__main__': main()