X-Git-Url: https://git.donarmstrong.com/?a=blobdiff_plain;f=scripts%2Fmusicxml2ly.py;h=7a8e8c407165cc310fe7f54aaa3065b92537b02b;hb=9a6392ad41c7cd1d811971b20b613129c65e10a6;hp=71b41d9dc35e9359d1a1c333a750a20ccafddcbc;hpb=3761f50a69bd34d6ff6da9c6a18d25c006d44362;p=lilypond.git diff --git a/scripts/musicxml2ly.py b/scripts/musicxml2ly.py index 71b41d9dc3..7a8e8c4071 100644 --- a/scripts/musicxml2ly.py +++ b/scripts/musicxml2ly.py @@ -1,4 +1,4 @@ -#!@PYTHON@ +#!@TARGET_PYTHON@ import optparse import sys @@ -7,34 +7,14 @@ import os import string from gettext import gettext as _ +""" +@relocate-preamble@ +""" -datadir = '@local_lilypond_datadir@' -if not os.path.isdir (datadir): - datadir = '@lilypond_datadir@' -if os.environ.has_key ('LILYPONDPREFIX'): - datadir = os.environ['LILYPONDPREFIX'] - while datadir[-1] == os.sep: - datadir = datadir[:-1] - -if os.path.exists (os.path.join (datadir, 'share/lilypond/@TOPLEVEL_VERSION@/')): - datadir = os.path.join (datadir, 'share/lilypond/@TOPLEVEL_VERSION@/') -elif os.path.exists (os.path.join (datadir, 'share/lilypond/current/')): - datadir = os.path.join (datadir, 'share/lilypond/current/') - -sys.path.insert (0, os.path.join (datadir, 'python')) - -# dynamic relocation, for GUB binaries. -bindir = os.path.split (sys.argv[0])[0] - -for prefix_component in ['share', 'lib']: - datadir = os.path.abspath (bindir + '/../%s/lilypond/current/python/' % prefix_component) - sys.path.insert (0, datadir) - - +import lilylib as ly import musicxml import musicexp -import lilylib as ly from rational import Rational @@ -100,41 +80,31 @@ def group_tuplets (music_list, events): return new_list -def musicxml_clef_to_lily (mxl): - sign = mxl.get_maybe_exist_named_child ('sign') +def musicxml_clef_to_lily (attributes): change = musicexp.ClefChange () - if sign: - change.type = sign.get_text () + change.type = attributes.get_clef_sign () return change - -def musicxml_time_to_lily (mxl): - beats = mxl.get_maybe_exist_named_child ('beats') - type = mxl.get_maybe_exist_named_child ('beat-type') +def musicxml_time_to_lily (attributes): + (beats, type) = attributes.get_time_signature () + change = musicexp.TimeSignatureChange() - change.fraction = (string.atoi(beats.get_text ()), - string.atoi(type.get_text ())) + change.fraction = (beats, type) return change -def musicxml_key_to_lily (mxl): +def musicxml_key_to_lily (attributes): start_pitch = musicexp.Pitch () + (fifths, mode) = attributes.get_key_signature () try: - mode = mxl.get_maybe_exist_named_child ('mode') - if mode: - mode = mode.get_text () - else: - mode = 'major' - - (n,a) = { 'major' : (0,0), - 'minor' : (6,0), + (n,a) = { + 'major' : (0,0), + 'minor' : (5,0), }[mode] start_pitch.step = n start_pitch.alteration = a except KeyError: print 'unknown mode', mode - - fifths = string.atoi (mxl.get_maybe_exist_named_child ('fifths').get_text ()) fifth = musicexp.Pitch() fifth.step = 4 @@ -143,7 +113,6 @@ def musicxml_key_to_lily (mxl): fifth.step *= -1 fifth.normalize () - start_pitch = musicexp.Pitch() for x in range (fifths): start_pitch = start_pitch.transposed (fifth) @@ -166,19 +135,10 @@ def musicxml_attributes_to_lily (attrs): ## ugh: you get clefs spread over staves for piano if childs: - elts.append (func (childs[0])) + elts.append (func (attrs)) return elts -def create_skip_music (duration): - skip = musicexp.SkipEvent() - skip.duration.duration_log = 0 - skip.duration.factor = duration - - evc = musicexp.EventChord () - evc.append (skip) - return evc - spanner_event_dict = { 'slur' : musicexp.SlurEvent, 'beam' : musicexp.BeamEvent, @@ -208,6 +168,15 @@ def musicxml_spanner_to_lily_event (mxl_event): return ev +instrument_drumtype_dict = { + 'Acoustic Snare Drum': 'acousticsnare', + 'Side Stick': 'sidestick', + 'Open Triangle': 'opentriangle', + 'Mute Triangle': 'mutetriangle', + 'Tambourine': 'tambourine', + +} + def musicxml_note_to_lily_main_event (n): pitch = None duration = None @@ -226,58 +195,159 @@ def musicxml_note_to_lily_main_event (n): elif n.get_maybe_exist_typed_child (musicxml.Rest): event = musicexp.RestEvent() + elif n.instrument_name: + event = musicexp.NoteEvent () + try: + event.drum_type = instrument_drumtype_dict[n.instrument_name] + except KeyError: + n.message ("drum %s type unknow, please add to instrument_drumtype_dict" % n.instrument_name) + event.drum_type = 'acousticsnare' + + if not event: + n.message ("cannot find suitable event") event.duration = musicxml_duration_to_lily (n) return event -def musicxml_voice_to_lily_voice (voice): - ly_voice = [] - ly_now = Rational (0) - pending_skip = Rational (0) +## todo +class NegativeSkip: + def __init__ (self, here, dest): + self.here = here + self.dest = dest + +class LilyPondVoiceBuilder: + def __init__ (self): + self.elements = [] + self.end_moment = Rational (0) + self.begin_moment = Rational (0) + self.pending_multibar = Rational (0) + + def _insert_multibar (self): + r = musicexp.MultiMeasureRest () + r.duration = musicexp.Duration() + r.duration.duration_log = 0 + r.duration.factor = self.pending_multibar + 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 add_multibar_rest (self, duration): + self.pending_multibar += duration + + + def add_music (self, music, duration): + assert isinstance (music, musicexp.Music) + if self.pending_multibar > Rational (0): + self._insert_multibar () + + self.elements.append (music) + self.begin_moment = self.end_moment + self.end_moment = self.begin_moment + duration + + def add_bar_check (self, number): + b = musicexp.BarCheck () + b.bar_number = number + self.add_music (b, Rational (0)) + + def jumpto (self, moment): + current_end = self.end_moment + self.pending_multibar + diff = moment - current_end + + if diff < Rational (0): + print 'Negative skip', diff + diff = Rational (0) + + if diff > Rational (0): + skip = musicexp.SkipEvent() + skip.duration.duration_log = 0 + skip.duration.factor = diff + + evc = musicexp.EventChord () + evc.elements.append (skip) + self.add_music (evc, diff) + + def last_event_chord (self, starting_at): + + value = None + if (self.elements + and isinstance (self.elements[-1], musicexp.EventChord) + and self.begin_moment == starting_at): + value = self.elements[-1] + else: + self.jumpto (starting_at) + value = None + + return value + + def correct_negative_skip (self, goto): + self.end_moment = goto + self.begin_moment = goto + evc = musicexp.EventChord () + self.elements.append (evc) + +def musicxml_voice_to_lily_voice (voice): tuplet_events = [] + modes_found = {} + + voice_builder = LilyPondVoiceBuilder() for n in voice._elements: if n.get_name () == 'forward': continue - - if isinstance (n, musicxml.Attributes): - ly_now += pending_skip - pending_skip = Rational (0) + + if not n.get_maybe_exist_named_child ('chord'): + try: + voice_builder.jumpto (n._when) + except NegativeSkip, neg: + voice_builder.correct_negative_skip (n._when) + n.message ("Negative skip? from %s to %s, diff %s" % (neg.here, neg.dest, neg.dest - neg.here)) - ly_voice.extend (musicxml_attributes_to_lily (n)) + 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 + + voice_builder.add_bar_check (number) + for a in musicxml_attributes_to_lily (n): + voice_builder.add_music (a, Rational (0)) continue - + if not n.__class__.__name__ == 'Note': print 'not a Note or Attributes?', n continue - if n.is_first () and ly_voice: - ly_voice[-1].comment += '\n' - + 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 + voice_builder.add_bar_check (num) + main_event = musicxml_note_to_lily_main_event (n) - ev_chord = None - if None == n.get_maybe_exist_typed_child (musicxml.Chord): - ly_now += pending_skip - pending_skip = main_event.get_length () + try: + if main_event.drum_type: + modes_found['drummode'] = True + except AttributeError: + pass - if ly_now <> n._when: - diff = n._when - ly_now - if diff < Rational (0): - print 'huh: negative skip', n._when, ly_now, n._duration - diff = Rational (1,314159265) + ev_chord = voice_builder.last_event_chord (n._when) + if not ev_chord: + ev_chord = musicexp.EventChord() + voice_builder.add_music (ev_chord, n._duration) - ly_voice.append (create_skip_music (diff)) - ly_now = n._when - - ly_voice.append (musicexp.EventChord()) - else: - pass - - ev_chord = ly_voice[-1] ev_chord.append (main_event) notations = n.get_maybe_exist_typed_child (musicxml.Notations) @@ -307,7 +377,8 @@ def musicxml_voice_to_lily_voice (voice): ev_chord.append (musicexp.TieEvent ()) mxl_beams = [b for b in n.get_named_children ('beam') - if b.get_type () in ('begin', 'end')] + if (b.get_type () in ('begin', 'end') + and b.is_primary ())] if mxl_beams: beam_ev = musicxml_spanner_to_lily_event (mxl_beams[0]) if beam_ev: @@ -321,17 +392,38 @@ def musicxml_voice_to_lily_voice (voice): tuplet_events.append ((ev_chord, tuplet_event, frac)) - ly_voice = group_tuplets (ly_voice, tuplet_events) + ## force trailing mm rests to be written out. + voice_builder.add_music (musicexp.EventChord (), Rational (0)) + + ly_voice = group_tuplets (voice_builder.elements, tuplet_events) seq_music = musicexp.SequentialMusic() + + if 'drummode' in modes_found.keys (): + ## \key barfs in drummode. + ly_voice = [e for e in ly_voice + if not isinstance(e, musicexp.KeySignatureChange)] seq_music.elements = ly_voice - return seq_music + + + + if len (modes_found) > 1: + print 'Too many modes found', modes_found.keys () + + return_value = seq_music + for mode in modes_found.keys (): + v = musicexp.ModeChangingMusicWrapper() + v.element = return_value + v.mode = mode + return_value = v + + return return_value def musicxml_id_to_lily (id): digits = ['one', 'two', 'three', 'four', 'five', 'six', 'seven', 'eight', - 'nine', 'ten'] + 'nine', 'ten'] for dig in digits: d = digits.index (dig) + 1 @@ -349,12 +441,10 @@ def musicxml_pitch_to_lily (mxl_pitch): p.octave = mxl_pitch.get_octave () - 4 return p - - def voices_in_part (part): """Return a Name -> Voice dictionary for PART""" part.interpret () - part.extract_voices () + part.extract_voices () voice_dict = part.get_voices () return voice_dict @@ -381,37 +471,41 @@ def get_all_voices (parts): def option_parser (): - p = ly.get_option_parser(usage='musicxml2ly FILE.xml', - version = """%prog (LilyPond) @TOPLEVEL_VERSION@ - -This program is free software. It is covered by the GNU General Public + p = ly.get_option_parser(usage=_ ("musicxml2ly FILE.xml"), + version=('''%prog (LilyPond) @TOPLEVEL_VERSION@\n\n''' + + +_ ("""This program is free software. It is covered by the GNU General Public License and you are welcome to change it and/or distribute copies of it -under certain conditions. Invoke as `lilypond --warranty' for more -information. - +under certain conditions. Invoke as `%s --warranty' for more +information.""") % 'lilypond' ++ """ Copyright (c) 2005--2006 by - Han-Wen Nienhuys and - Jan Nieuwenhuizen -""", - - description = - """Convert MusicXML file to LilyPond input. -""" - ) + Han-Wen Nienhuys and + Jan Nieuwenhuizen +"""), + description=_ ("Convert %s to LilyPond input.") % 'MusicXML' + "\n") p.add_option ('-v', '--verbose', - action = "store_true", - dest = 'verbose', - help = 'be verbose') + action="store_true", + dest='verbose', + help=_ ("be verbose")) + + p.add_option ('', '--lxml', + action="store_true", + default=False, + dest="use_lxml", + help=_ ("Use lxml.etree; uses less memory and cpu time.")) + p.add_option ('-o', '--output', - metavar = 'FILE', - action = "store", - default = None, - type = 'string', - dest = 'output', - help = 'set output file') - - p.add_option_group ('', description = '''Report bugs via http://post.gmane.org/post.php?group=gmane.comp.gnu.lilypond.bugs -''') + metavar=_ ("FILE"), + action="store", + default=None, + type='string', + dest='output_name', + help=_ ("set output filename to FILE")) + p.add_option_group ('bugs', + description=(_ ("Report bugs via") + + ''' http://post.gmane.org/post.php''' + '''?group=gmane.comp.gnu.lilypond.bugs\n''')) return p def music_xml_voice_name_to_lily_name (part, name): @@ -420,11 +514,14 @@ def music_xml_voice_name_to_lily_name (part, name): def print_voice_definitions (printer, voices): for (part, nv_dict) in voices.items(): + for (name, (voice, mxlvoice)) in nv_dict.items (): k = music_xml_voice_name_to_lily_name (part, name) printer.dump ('%s = ' % k) voice.print_ly (printer) printer.newline() + + def uniq_list (l): return dict ([(elt,1) for elt in l]).keys () @@ -479,44 +576,70 @@ def print_score_setup (printer, part_list, voices): printer ('>>') printer.newline () - printer ('>>') printer.newline () - - def print_ly_preamble (printer, filename): printer.dump_version () printer.print_verbatim ('%% converted from %s\n' % filename) -def convert (filename, output_name): - printer = musicexp.Output_printer() - progress ("Reading MusicXML...") +def read_musicxml (filename, use_lxml): + if use_lxml: + import lxml.etree + + tree = lxml.etree.parse (filename) + mxl_tree = musicxml.lxml_demarshal_node (tree.getroot ()) + return mxl_tree + else: + from xml.dom import minidom, Node + + doc = minidom.parse(filename) + node = doc.documentElement + return musicxml.minidom_demarshal_node (node) + + return None + + +def convert (filename, options): + progress ("Reading MusicXML from %s ..." % filename) - tree = musicxml.read_musicxml (filename) - parts = tree.get_typed_children (musicxml.Part) - voices = get_all_voices (parts) + tree = read_musicxml (filename, options.use_lxml) part_list = [] + id_instrument_map = {} if tree.get_maybe_exist_typed_child (musicxml.Part_list): - pl = tree.get_maybe_exist_typed_child (musicxml.Part_list) - part_list = pl.get_named_children ("score-part") + mxl_pl = tree.get_maybe_exist_typed_child (musicxml.Part_list) + part_list = mxl_pl.get_named_children ("score-part") - if not output_name: - output_name = os.path.basename (filename) - output_name = os.path.splitext (output_name)[0] + '.ly' + parts = tree.get_typed_children (musicxml.Part) + voices = get_all_voices (parts) - - if output_name: - progress ("Output to `%s'" % output_name) - printer.set_file (open (output_name, 'w')) - - progress ("Printing as .ly...") + if not options.output_name: + options.output_name = os.path.basename (filename) + options.output_name = os.path.splitext (options.output_name)[0] + + + defs_ly_name = options.output_name + '-defs.ly' + driver_ly_name = options.output_name + '.ly' + + printer = musicexp.Output_printer() + progress ("Output to `%s'" % defs_ly_name) + printer.set_file (open (defs_ly_name, 'w')) print_ly_preamble (printer, filename) - print_voice_definitions (printer, voices) + print_voice_definitions (printer, voices) + + printer.close () + + + progress ("Output to `%s'" % driver_ly_name) + printer = musicexp.Output_printer() + printer.set_file (open (driver_ly_name, 'w')) + print_ly_preamble (printer, filename) + printer.dump (r'\include "%s"' % defs_ly_name) print_score_setup (printer, part_list, voices) printer.newline () + return voices @@ -528,7 +651,7 @@ def main (): opt_parser.print_usage() sys.exit (2) - voices = convert (args[0], options.output) + voices = convert (args[0], options) if __name__ == '__main__': main()