X-Git-Url: https://git.donarmstrong.com/?a=blobdiff_plain;f=scripts%2Fmusicxml2ly.py;h=6cdd72e430996ba47c1f58fd9ac3db68d941e283;hb=5c14a087ca6cbd665fd631452b7b1283ba0387c3;hp=1f7f076b45d06ddff866e43a40baa81e90f696a6;hpb=1899aa5e1b77587e9dafebb4e3aaeb60a69c53ee;p=lilypond.git diff --git a/scripts/musicxml2ly.py b/scripts/musicxml2ly.py index 1f7f076b45..6cdd72e430 100644 --- a/scripts/musicxml2ly.py +++ b/scripts/musicxml2ly.py @@ -55,24 +55,23 @@ additional_definitions = { '(-0.1 . 0.1) '(0.1 . 1))) 0.7 X))))""", "eyeglasses": """eyeglassesps = #"0.15 setlinewidth -% 255 0 0 setrgbcolor --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" + -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 }""" } @@ -913,42 +912,42 @@ articulations_dict = { #"delayed-turn": "?", "detached-legato": (musicexp.ShortArticulationEvent, "_"), # or "portato" "doit": musicxml_doit_to_lily_event, - #"double-tongue": "", + #"double-tongue": "?", "down-bow": "downbow", "falloff": musicxml_falloff_to_lily_event, "fingering": musicxml_fingering_event, - #"fingernails": "", - #"fret": "", - #"hammer-on": "", + #"fingernails": "?", + #"fret": "?", + #"hammer-on": "?", "harmonic": "flageolet", - #"heel": "", + #"heel": "?", "inverted-mordent": "prall", "inverted-turn": "reverseturn", "mordent": "mordent", "open-string": "open", - #"plop": "", - #"pluck": "", - #"pull-off": "", + #"plop": "?", + #"pluck": "?", + #"pull-off": "?", #"schleifer": "?", - #"scoop": "", + #"scoop": "?", #"shake": "?", "snap-pizzicato": musicxml_snappizzicato_event, - #"spiccato": "", + #"spiccato": "?", "staccatissimo": (musicexp.ShortArticulationEvent, "|"), # or "staccatissimo" "staccato": (musicexp.ShortArticulationEvent, "."), # or "staccato" "stopped": (musicexp.ShortArticulationEvent, "+"), # or "stopped" - #"stress": "", + #"stress": "?", "string": musicxml_string_event, "strong-accent": (musicexp.ShortArticulationEvent, "^"), # or "marcato" - #"tap": "", + #"tap": "?", "tenuto": (musicexp.ShortArticulationEvent, "-"), # or "tenuto" "thumb-position": "thumb", - #"toe": "", + #"toe": "?", "turn": "turn", "tremolo": musicxml_tremolo_to_lily_event, "trill-mark": "trill", - #"triple-tongue": "", - #"unstress": "" + #"triple-tongue": "?", + #"unstress": "?" "up-bow": "upbow", #"wavy-line": "?", } @@ -1054,7 +1053,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 @@ -1158,10 +1157,76 @@ 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") +def next_non_hash_index (lst, pos): + pos += 1 + while pos < len (lst) and isinstance (lst[pos], musicxml.Hash_text): + pos += 1 + return pos + +def musicxml_metronome_to_ly (mxl_event): + children = mxl_event.get_all_children () + if not children: + return + + index = -1 + index = next_non_hash_index (children, index) + 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'): + ev.set_parentheses (mxl_event.parentheses == "yes") + + d = musicexp.Duration () + d.duration_log = musicxml.musicxml_duration_to_log (children[index].get_text ()) + index = next_non_hash_index (children, index) + if isinstance (children[index], musicxml.BeatUnitDot): + d.dots = 1 + index = next_non_hash_index (children, index) + ev.set_base_duration (d) + if isinstance (children[index], musicxml.BeatUnit): + # Form "note = newnote" + newd = musicexp.Duration () + newd.duration_log = musicxml.musicxml_duration_to_log (children[index].get_text ()) + index = next_non_hash_index (children, index) + if isinstance (children[index], musicxml.BeatUnitDot): + newd.dots = 1 + index = next_non_hash_index (children, index) + ev.set_new_duration (newd) + elif isinstance (children[index], musicxml.PerMinute): + # Form "note = bpm" + try: + beats = int (children[index].get_text ()) + ev.set_beats_per_minute (beats) + except ValueError: + pass + else: + error_message (_ ("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.")) + return + # translate directions into Events, possible values: # -) string (MarkEvent with that command) # -) function (function(mxl_event) needs to return a full Event-derived object @@ -1172,11 +1237,11 @@ directions_dict = { # 'damp' : ??? # 'damp-all' : ??? 'eyeglasses': musicxml_eyeglasses_to_ly, -# 'harp-pedals' : -# 'image' : -# 'metronome' : + 'harp-pedals' : musicxml_harp_pedals_to_ly, +# 'image' : ??? + 'metronome' : musicxml_metronome_to_ly, 'rehearsal' : musicxml_rehearsal_to_ly_mark, -# 'scordatura' : +# 'scordatura' : ??? 'segno' : (musicexp.MusicGlyphMarkEvent, "segno"), 'words' : musicxml_words_to_lily_event, } @@ -1257,6 +1322,112 @@ def musicxml_harmony_to_lily (n): ev = musicxml_frame_to_lily_event (f) if ev: res.append (ev) + return res + + +def musicxml_chordpitch_to_lily (mxl_cpitch): + r = musicexp.ChordPitch () + r.alteration = mxl_cpitch.get_alteration () + r.step = musicxml_step_to_lily (mxl_cpitch.get_step ()) + return r + +chordkind_dict = { + 'major': '5', + 'minor': 'm5', + 'augmented': 'aug5', + 'diminished': 'dim5', + # Sevenths: + 'dominant': '7', + 'major-seventh': 'maj7', + 'minor-seventh': 'm7', + 'diminished-seventh': 'dim7', + 'augmented-seventh': 'aug7', + 'half-diminished': 'dim5m7', + 'major-minor': 'maj7m5', + # Sixths: + 'major-sixth': '6', + 'minor-sixth': 'm6', + # Ninths: + 'dominant-ninth': '9', + 'major-ninth': 'maj9', + 'minor-ninth': 'm9', + # 11ths (usually as the basis for alteration): + 'dominant-11th': '11', + 'major-11th': 'maj11', + 'minor-11th': 'm11', + # 13ths (usually as the basis for alteration): + 'dominant-13th': '13.11', + 'major-13th': 'maj13.11', + 'minor-13th': 'm13', + # Suspended: + 'suspended-second': 'sus2', + 'suspended-fourth': 'sus4', + # Functional sixths: + # TODO + #'Neapolitan': '???', + #'Italian': '???', + #'French': '???', + #'German': '???', + # Other: + #'pedal': '???',(pedal-point bass) + 'power': '5^3', + #'Tristan': '???', + 'other': '1', + 'none': None, +} + +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) + return res + +def musicxml_harmony_to_lily_chordname (n): + res = [] + root = n.get_maybe_exist_named_child ('root') + if root: + ev = musicexp.ChordNameEvent () + ev.root = musicxml_chordpitch_to_lily (root) + kind = n.get_maybe_exist_named_child ('kind') + if kind: + ev.kind = musicxml_chordkind_to_lily (kind.get_text ()) + if not ev.kind: + return res + bass = n.get_maybe_exist_named_child ('bass') + if bass: + 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? + + # 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 + # second inversion) so they may not be helpful for musicxml2ly. + inversion_count = string.atoi (inversion.get_text ()) + if inversion_count == 1: + # TODO: Calculate the bass note for the inversion... + pass + pass + for deg in n.get_named_children ('degree'): + d = musicexp.ChordModification () + d.type = deg.get_type () + d.step = deg.get_value () + d.alteration = deg.get_alter () + ev.add_modification (d) + #TODO: convert the user-symbols attribute: + #major: a triangle, like Unicode 25B3 + #minor: -, like Unicode 002D + #augmented: +, like Unicode 002B + #diminished: (degree), like Unicode 00B0 + #half-diminished: (o with slash), like Unicode 00F8 + if ev and ev.root: + res.append (ev) return res @@ -1299,7 +1470,6 @@ def musicxml_figured_bass_to_lily (n): res.append (note) dur = n.get_maybe_exist_named_child ('duration') if dur: - # TODO: implement duration (given in base steps!) # apply the duration to res length = Rational(int(dur.get_text()), n._divisions)*Rational(1,4) res.set_real_duration (length) @@ -1382,6 +1552,7 @@ class LilyPondVoiceBuilder: self.begin_moment = Rational (0) self.pending_multibar = Rational (0) self.ignore_skips = False + self.has_relevant_elements = False def _insert_multibar (self): r = musicexp.MultiMeasureRest () @@ -1406,6 +1577,7 @@ class LilyPondVoiceBuilder: if self.pending_multibar > Rational (0): self._insert_multibar () + self.has_relevant_elements = True self.elements.append (music) self.begin_moment = self.end_moment self.set_duration (duration) @@ -1421,6 +1593,7 @@ class LilyPondVoiceBuilder: assert isinstance (command, musicexp.Music) if self.pending_multibar > Rational (0): self._insert_multibar () + self.has_relevant_elements = True self.elements.append (command) def add_barline (self, barline): # TODO: Implement merging of default barline and custom bar line @@ -1434,9 +1607,13 @@ class LilyPondVoiceBuilder: self.pending_dynamics.append (dynamic) 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 @@ -1506,6 +1683,7 @@ class VoiceData: self.voicedata = None self.ly_voice = None self.figured_bass = None + self.chordnames = None self.lyrics_dict = {} self.lyrics_order = [] @@ -1535,6 +1713,7 @@ def musicxml_voice_to_lily_voice (voice): current_staff = None pending_figured_bass = [] + pending_chordnames = [] # Make sure that the keys in the dict don't get reordered, since # we need the correct ordering of the lyrics stanzas! By default, @@ -1545,6 +1724,7 @@ def musicxml_voice_to_lily_voice (voice): voice_builder = LilyPondVoiceBuilder () figured_bass_builder = LilyPondVoiceBuilder () + chordnames_builder = LilyPondVoiceBuilder () for n in voice._elements: if n.get_name () == 'forward': @@ -1576,8 +1756,10 @@ def musicxml_voice_to_lily_voice (voice): 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: @@ -1600,6 +1782,8 @@ def musicxml_voice_to_lily_voice (voice): 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) @@ -1632,6 +1816,8 @@ def musicxml_voice_to_lily_voice (voice): 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: @@ -1675,13 +1861,30 @@ def musicxml_voice_to_lily_voice (voice): # if we have a figured bass, set its voice builder to the correct position # and insert the pending figures if pending_figured_bass: - try: - figured_bass_builder.jumpto (n._when) - except NegativeSkip, neg: - pass - for fb in pending_figured_bass: - figured_bass_builder.add_music (fb, fb.real_duration) - pending_figured_bass = [] + try: + figured_bass_builder.jumpto (n._when) + except NegativeSkip, neg: + pass + for fb in pending_figured_bass: + # if a duration is given, use that, otherwise the one of the note + dur = fb.real_duration + if not dur: + dur = ev_chord.get_length () + if not fb.duration: + 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) + except NegativeSkip, neg: + pass + for cn in pending_chordnames: + # Assign the duration of the EventChord + cn.duration = ev_chord.get_duration () + chordnames_builder.add_music (cn, ev_chord.get_length ()) + pending_chordnames = [] notations_children = n.get_typed_children (musicxml.Notations) @@ -1860,7 +2063,7 @@ def musicxml_voice_to_lily_voice (voice): return_value.ly_voice = v # create \figuremode { figured bass elements } - if figured_bass_builder.elements: + if figured_bass_builder.has_relevant_elements: fbass_music = musicexp.SequentialMusic () fbass_music.elements = figured_bass_builder.elements v = musicexp.ModeChangingMusicWrapper() @@ -1868,6 +2071,15 @@ def musicxml_voice_to_lily_voice (voice): 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 + v = musicexp.ModeChangingMusicWrapper() + v.mode = 'chordmode' + v.element = cname_music + return_value.chordnames = v + return return_value def musicxml_id_to_lily (id): @@ -1944,26 +2156,30 @@ def get_all_voices (parts): def option_parser (): - p = ly.get_option_parser (usage = _ ("musicxml2ly [options] FILE.xml"), - description = _ ("Convert MusicXML from FILE.xml to LilyPond input. If the given filename is -, musicxml2ly reads from the command line.") + "\n", - add_help_option=False) + p = ly.get_option_parser (usage = _ ("musicxml2ly [OPTION]... FILE.xml"), + description = +_ ("""Convert MusicXML from FILE.xml to LilyPond input. +If the given filename is -, musicxml2ly reads from the command line. +"""), add_help_option=False) p.add_option("-h", "--help", action="help", help=_ ("show this help and exit")) p.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 `%s --warranty' for more -information.""") % 'lilypond' -+ """ -Copyright (c) 2005--2008 by ++ +_ ("""Copyright (c) 2005--2008 by Han-Wen Nienhuys , Jan Nieuwenhuizen and Reinhold Kainhofer -""") +""" ++ +""" +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 `%s --warranty' for more +information.""") % 'lilypond') + p.add_option("--version", action="version", help=_ ("show version number and exit")) @@ -2020,7 +2236,7 @@ Copyright (c) 2005--2008 by type = 'string', dest = 'output_name', help = _ ("set output filename to FILE, stdout if -")) - p.add_option_group (ly.display_encode (_ ('Bugs')), + p.add_option_group ('', description = (_ ("Report bugs via") + ''' http://post.gmane.org/post.php''' '''?group=gmane.comp.gnu.lilypond.bugs\n''')) @@ -2038,6 +2254,10 @@ def music_xml_figuredbass_name_to_lily_name (part_id, voicename): str = "Part%sVoice%sFiguredBass" % (part_id, voicename) 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) + def print_voice_definitions (printer, part_list, voices): for part in part_list: part_id = part.id @@ -2047,6 +2267,11 @@ def print_voice_definitions (printer, part_list, voices): printer.dump ('%s = ' % k) voice.ly_voice.print_ly (printer) printer.newline() + if voice.chordnames: + cnname = music_xml_chordnames_name_to_lily_name (part_id, name) + printer.dump ('%s = ' % cnname ) + voice.chordnames.print_ly (printer) + printer.newline() for l in voice.lyrics_order: lname = music_xml_lyrics_name_to_lily_name (part_id, name, l) printer.dump ('%s = ' % lname ) @@ -2073,14 +2298,17 @@ def uniq_list (l): # raw_voices is of the form [(voicename, lyricsids, havefiguredbass)*] def format_staff_info (part_id, staff_id, raw_voices): voices = [] - for (v, lyricsids, figured_bass) in raw_voices: + for (v, lyricsids, figured_bass, chordnames) in raw_voices: voice_name = music_xml_voice_name_to_lily_name (part_id, v) voice_lyrics = [music_xml_lyrics_name_to_lily_name (part_id, v, l) for l in lyricsids] figured_bass_name = '' if figured_bass: figured_bass_name = music_xml_figuredbass_name_to_lily_name (part_id, v) - voices.append ([voice_name, voice_lyrics, figured_bass_name]) + chordnames_name = '' + if chordnames: + chordnames_name = music_xml_chordnames_name_to_lily_name (part_id, v) + voices.append ([voice_name, voice_lyrics, figured_bass_name, chordnames_name]) return [staff_id, voices] def update_score_setup (score_structure, part_list, voices): @@ -2102,12 +2330,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) + 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) + 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)