X-Git-Url: https://git.donarmstrong.com/?a=blobdiff_plain;f=scripts%2Fmusicxml2ly.py;h=12e9ee29ab62f0bd59b38475f2c4c621d18183a3;hb=05b7a17f1b19c4d978d340483b3c7b9985710232;hp=886479a8936cfaaaa08f2117e2e6c177fcb7e037;hpb=c7555d70732969277c5e906285ec04e5b561c38e;p=lilypond.git diff --git a/scripts/musicxml2ly.py b/scripts/musicxml2ly.py index 886479a893..12e9ee29ab 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 @@ -308,7 +313,10 @@ def staff_attributes_to_lily_staff (mxl_attr): def extract_score_structure (part_list, staffinfo): + score = musicexp.Score () structure = musicexp.StaffGroup (None) + score.set_contents (structure) + if not part_list: return structure @@ -436,7 +444,7 @@ def extract_score_structure (part_list, staffinfo): return staves[0] for i in staves: structure.append_staff (i) - return structure + return score def musicxml_duration_to_lily (mxl_note): @@ -645,8 +653,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 @@ -1053,7 +1068,7 @@ def musicxml_words_to_lily_event (words): "medium": '', "large": '\\large', "x-large": '\\huge', - "xx-large": '\\bigger\\huge' + "xx-large": '\\larger\\huge' }.get (size, '') if font_size: event.markup += font_size @@ -1157,6 +1172,21 @@ def musicxml_rehearsal_to_ly_mark (mxl_event): ev = musicexp.MarkEvent ("\\markup { %s }" % text) return ev +def musicxml_harp_pedals_to_ly (mxl_event): + count = 0 + result = "\\harp-pedal #\"" + for t in mxl_event.get_named_children ('pedal-tuning'): + alter = t.get_named_child ('pedal-alter') + if alter: + val = int (alter.get_text ().strip ()) + result += {1: "v", 0: "-", -1: "^"}.get (val, "") + count += 1 + if count == 3: + result += "|" + ev = musicexp.MarkupEvent () + ev.contents = result + "\"" + return ev + def musicxml_eyeglasses_to_ly (mxl_event): needed_additional_definitions.append ("eyeglasses") return musicexp.MarkEvent ("\\eyeglasses") @@ -1222,7 +1252,7 @@ directions_dict = { # 'damp' : ??? # 'damp-all' : ??? 'eyeglasses': musicxml_eyeglasses_to_ly, -# 'harp-pedals' : ??? + 'harp-pedals' : musicxml_harp_pedals_to_ly, # 'image' : ??? 'metronome' : musicxml_metronome_to_ly, 'rehearsal' : musicxml_rehearsal_to_ly_mark, @@ -1538,17 +1568,24 @@ class LilyPondVoiceBuilder: self.pending_multibar = Rational (0) self.ignore_skips = False self.has_relevant_elements = False + self.measure_length = (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 = Rational (self.measure_length[0], self.measure_length[1]) + 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 @@ -1605,7 +1642,8 @@ class LilyPondVoiceBuilder: diff = moment - current_end if diff < Rational (0): - error_message (_ ('Negative skip %s') % diff) + error_message (_ ('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): @@ -1613,6 +1651,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 @@ -1678,6 +1718,13 @@ def musicxml_step_to_lily (step): else: 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 + def musicxml_voice_to_lily_voice (voice): tuplet_events = [] modes_found = {} @@ -1710,6 +1757,8 @@ def musicxml_voice_to_lily_voice (voice): voice_builder = LilyPondVoiceBuilder () figured_bass_builder = LilyPondVoiceBuilder () chordnames_builder = LilyPondVoiceBuilder () + current_measure_length = (4, 4) + voice_builder.set_measure_length (current_measure_length) for n in voice._elements: if n.get_name () == 'forward': @@ -1727,6 +1776,50 @@ def musicxml_voice_to_lily_voice (voice): voice_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) + 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.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) + 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 (): @@ -1751,59 +1844,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 @@ -1818,15 +1871,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" @@ -1887,23 +1956,39 @@ def musicxml_voice_to_lily_voice (voice): 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')) + # 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 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 = 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: + 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': @@ -1965,7 +2050,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') @@ -2408,14 +2493,14 @@ def convert (filename, options): parts = tree.get_typed_children (musicxml.Part) (voices, staff_info) = get_all_voices (parts) - score_structure = None + score = None mxl_pl = tree.get_maybe_exist_typed_child (musicxml.Part_list) if mxl_pl: - score_structure = extract_score_structure (mxl_pl, staff_info) + score = extract_score_structure (mxl_pl, staff_info) part_list = mxl_pl.get_named_children ("score-part") # score information is contained in the , or tags - update_score_setup (score_structure, part_list, voices) + update_score_setup (score, part_list, voices) # After the conversion, update the list of settings for the \layout block update_layout_information () @@ -2452,7 +2537,7 @@ def convert (filename, options): printer.newline () printer.dump ("% The score definition") printer.newline () - score_structure.print_ly (printer) + score.print_ly (printer) printer.newline () return voices @@ -2484,17 +2569,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()