X-Git-Url: https://git.donarmstrong.com/?a=blobdiff_plain;f=scripts%2Fmidi2ly.py;h=29bb128f45713bd0d3daf82413d4aff043daeaa1;hb=8bdf915821fa94a60c47a1933251337164b9d877;hp=fbd9596eba41bf05366e74ad89b9d973f78338fb;hpb=a994a203e10019895fd0fa2cc40be9b64726c64f;p=lilypond.git diff --git a/scripts/midi2ly.py b/scripts/midi2ly.py index fbd9596eba..29bb128f45 100644 --- a/scripts/midi2ly.py +++ b/scripts/midi2ly.py @@ -1,17 +1,16 @@ #!@PYTHON@ # -# midi2ly.py -- LilyPond midi import script +# msdi2ly.py -- LilyPond midi import script # # source file of the GNU LilyPond music typesetter # -# convert MIDI to LilyPond source -# +# (c) 1998--2005 Han-Wen Nienhuys +# Jan Nieuwenhuizen ''' TODO: * test on weird and unquantised midi input (lily-devel) - * drop c++ midi2ly * update doc and manpage * simply insert clef changes whenever too many ledger lines @@ -22,25 +21,39 @@ TODO: other converters, while leaving midi specific stuff here ''' -import os -import sys import getopt -import sys +import os import string +import sys + +################################################################ +# Users of python modules should include this snippet. +# +libdir = '@local_lilypond_libdir@' +if not os.path.isdir (libdir): + libdir = '@lilypond_libdir@' -# do fuddling: we must load the midi module from the right directory. -datadir = '@datadir@' +# ugh if os.environ.has_key ('LILYPONDPREFIX'): datadir = os.environ['LILYPONDPREFIX'] -else: - datadir = '@datadir@' + while datadir[-1] == os.sep: + datadir= datadir[:-1] + libdir = datadir.replace ('/share/', '/lib/') + +if os.path.exists (os.path.join (datadir, 'lib/lilypond/@TOPLEVEL_VERSION@/')): + libdir = os.path.join (libdir, 'lib/lilypond/@TOPLEVEL_VERSION@/') + +if os.path.exists (os.path.join (datadir, 'lib/lilypond/current/')): + libdir = os.path.join (libdir, 'lib/lilypond/current/') + +sys.path.insert (0, os.path.join (libdir, 'python')) -sys.path.append (os.path.join (datadir, 'python')) -sys.path.append (os.path.join (datadir, 'python/out')) +################################################################ import midi + ################################################################ ################ CONSTANTS @@ -78,7 +91,7 @@ except: def _ (s): return s -program_name = 'midi2ly' +program_name = sys.argv[0] program_version = '@TOPLEVEL_VERSION@' errorport = sys.stderr @@ -89,18 +102,18 @@ verbose_p = 0 # keep_temp_dir_p = 0 -help_summary = _ ("Convert MIDI to LilyPond source") +help_summary = _ ("Convert MIDI to LilyPond source.") option_definitions = [ ('', 'a', 'absolute-pitches', _ ("print absolute pitches")), (_ ("DUR"), 'd', 'duration-quant', _ ("quantise note durations on DUR")), ('', 'e', 'explicit-durations', _ ("print explicit durations")), - ('', 'h', 'help', _ ("this help")), + ('', 'h', 'help', _ ("print this help")), (_ ("ALT[:MINOR]"), 'k', 'key', _ ("set key: ALT=+sharps|-flats; MINOR=1")), - (_ ("FILE"), 'o', 'output', _ ("write ouput to FILE")), + (_ ("FILE"), 'o', 'output', _ ("write output to FILE")), (_ ("DUR"), 's', 'start-quant', _ ("quantise note starts on DUR")), (_ ("DUR*NUM/DEN"), 't', 'allow-tuplet', _ ("allow tuplet durations DUR*NUM/DEN")), - ('', 'V', 'verbose', _ ("verbose")), + ('', 'V', 'verbose', _ ("be verbose")), ('', 'v', 'version', _ ("print version number")), ('', 'w', 'warranty', _ ("show warranty and copyright")), ('', 'x', 'text-lyrics', _ ("treat every text as a lyric")), @@ -131,14 +144,13 @@ def identify (): def warranty (): identify () sys.stdout.write ('\n') - sys.stdout.write (_ ('Copyright (c) %s by' % ' 2001--2002')) + sys.stdout.write (_ ('Copyright (c) %s by') % ' 2001--2005') sys.stdout.write ('\n') sys.stdout.write (' Han-Wen Nienhuys') sys.stdout.write (' Jan Nieuwenhuizen') - sys.stdout.write ('\n') - sys.stdout.write (_ (r''' -Distributed under terms of the GNU General Public License. It comes with -NO WARRANTY.''')) + sys.stdout.write ('\n\n') + sys.stdout.write (_ ("Distributed under terms of the GNU General Public License.")) + sys.stdout.write (_ ("It comes with NO WARRANTY.")) sys.stdout.write ('\n') def progress (s): @@ -185,9 +197,9 @@ def option_help_str (o): if o[1]: sh = '-%s' % o[1] - sep = ' ' + sep = ' ' if o[1] and o[2]: - sep = ',' + sep = ', ' long = '' if o[2]: @@ -219,7 +231,7 @@ def options_help_str (opts): return str def help (): - ls = [(_ ("Usage: %s [OPTION]... FILE") % program_name), + ls = [(_ ("Usage: %s [OPTIONS]... FILE") % program_name), ('\n\n'), (help_summary), ('\n\n'), @@ -227,7 +239,8 @@ def help (): ('\n'), (options_help_str (option_definitions)), ('\n\n'), - (_ ("Report bugs to %s") % 'bug-lilypond@gnu.org'), + (_ ("Report bugs via %s.") % + "http://post.gmane.org/post.php?group=gmane.comp.gnu.lilypond.bugs"), ('\n')] map (sys.stdout.write, ls) @@ -338,7 +351,6 @@ class Note: names = (0, 0, 1, 1, 2, 3, 3, 4, 4, 5, 5, 6) alterations = (0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 1, 0) alteration_names = ('eses', 'es', '', 'is' , 'isis') - def __init__ (self, clocks, pitch, velocity): self.pitch = pitch self.velocity = velocity @@ -423,7 +435,12 @@ class Note: return (o, n, a) - def dump (self): + def __repr__ (self): + s = chr ((self.notename + 2) % 7 + ord ('a')) + return 'Note(%s %s)' % (s, self.duration.dump()) + + def dump (self, dump_dur = 1): + global reference_note s = chr ((self.notename + 2) % 7 + ord ('a')) s = s + self.alteration_names[self.alteration + 2] if absolute_p: @@ -443,11 +460,12 @@ class Note: elif commas < 0: s = s + "," * -commas - if explicit_durations_p \ - or Duration.compare (self.duration, reference_note.duration): + ## FIXME: compile fix --jcn + if dump_dur and (explicit_durations_p \ + or Duration.compare (self.duration, + reference_note.duration)): s = s + self.duration.dump () - global reference_note reference_note = self # TODO: move space @@ -462,7 +480,10 @@ class Time: def bar_clocks (self): return clocks_per_1 * self.num / self.den - + + def __repr__ (self): + return 'Time(%d/%d)' % (self.num, self.den) + def dump (self): global time time = self @@ -473,14 +494,23 @@ class Tempo: self.clocks = 0 self.seconds_per_1 = seconds_per_1 + def __repr__ (self): + return 'Tempo(%d)' % self.bpm () + + def bpm (self): + return 4 * 60 / self.seconds_per_1 + def dump (self): - return '\n ' + '\\tempo 4 = %d ' % (4 * 60 / self.seconds_per_1) + '\n ' + return '\n ' + '\\tempo 4 = %d ' % (self.bpm()) + '\n ' class Clef: clefs = ('"bass_8"', 'bass', 'violin', '"violin^8"') def __init__ (self, type): self.type = type - + + def __repr__ (self): + return 'Clef(%s)' % self.clefs[self.type] + def dump (self): return '\n \\clef %s\n ' % self.clefs[self.type] @@ -500,10 +530,9 @@ class Key: s = '' if self.sharps and self.flats: - s = '\\keysignature %s ' % 'TODO' + pass else: - - if self.flats: + if self.flats: k = (ord ('cfbeadg'[self.flats % 7]) - ord ('a') - 2 -2 * self.minor + 7) % 7 else: k = (ord ('cgdaebf'[self.sharps % 7]) - ord ('a') - 2 -2 * self.minor + 7) % 7 @@ -567,6 +596,10 @@ class Text: s = '\n % [' + self.text_types[self.type] + '] ' + self.text + '\n ' return s + def __repr__ (self): + return 'Text(%d=%s)' % (self.type, self.text) + + def split_track (track): chs = {} @@ -782,11 +815,13 @@ def dump_chord (ch): elif len (notes) > 1: global reference_note s = s + '<' - s = s + notes[0].dump () + s = s + notes[0].dump (dump_dur = 0) r = reference_note for i in notes[1:]: - s = s + i.dump () + s = s + i.dump (dump_dur = 0 ) s = s + '>' + + s = s + notes[0].duration.dump() + ' ' reference_note = r return s @@ -886,21 +921,20 @@ def dump_track (channels, n): if item and item.__class__ == Note: skip = 's' - s = s + '%s = \\notes' % (track + channel) + s = s + '%s = ' % (track + channel) if not absolute_p: s = s + '\\relative c ' elif item and item.__class__ == Text: skip = '" "' - s = s + '%s = \\lyrics ' % (track + channel) + s = s + '%s = \\lyricmode ' % (track + channel) else: skip = '\\skip ' - # must be in \notes mode for parsing \skip - s = s + '%s = \\notes ' % (track + channel) + s = s + '%s = ' % (track + channel) s = s + '{\n' s = s + ' ' + dump_channel (channels[i][0], skip) s = s + '}\n\n' - s = s + '%s = <\n' % track + s = s + '%s = <<\n' % track if clef.type != 2: s = s + clef.dump () + '\n' @@ -914,21 +948,25 @@ def dump_track (channels, n): else: s = s + ' \\context Voice = %s \\%s\n' % (channel, track + channel) - s = s + '>\n\n' + s = s + '>>\n\n' return s def thread_first_item (thread): for chord in thread: for event in chord: - if event[1].__class__ == Note \ - or (event[1].__class__ == Text \ - and event[1].type == midi.LYRIC): - return event[1] - return 0 + if (event[1].__class__ == Note + or (event[1].__class__ == Text + and event[1].type == midi.LYRIC)): + + return event[1] + return None def track_first_item (track): for thread in track: - return thread_first_item (thread) + first = thread_first_item (thread) + if first: + return first + return None def guess_clef (track): i = 0 @@ -949,24 +987,24 @@ def guess_clef (track): return Clef (2) -def convert_midi (f, o): +def convert_midi (in_file, out_file): global clocks_per_1, clocks_per_4, key + global start_quant, start_quant_clocks + global duration_quant, duration_quant_clocks + global allowed_tuplet_clocks - str = open (f).read () + str = open (in_file).read () midi_dump = midi.parse (str) - + clocks_per_1 = midi_dump[0][1] clocks_per_4 = clocks_per_1 / 4 - global start_quant, start_quant_clocks if start_quant: start_quant_clocks = clocks_per_1 / start_quant - global duration_quant, duration_quant_clocks if duration_quant: duration_quant_clocks = clocks_per_1 / duration_quant - global allowed_tuplet_clocks allowed_tuplet_clocks = [] for (dur, num, den) in allowed_tuplets: allowed_tuplet_clocks.append (clocks_per_1 * num / (dur * den)) @@ -978,30 +1016,36 @@ def convert_midi (f, o): tag = '%% Lily was here -- automatically converted by %s from %s' % ( program_name, f) + s = '' - s = tag + '\n\n' + s = tag + '\n\\version "2.7.18"\n\n' for i in range (len (tracks)): s = s + dump_track (tracks[i], i) - s = s + '\n\\score {\n <\n' - for i in range (len (tracks)): + s = s + '\n\\score {\n <<\n' + + i = 0 + for t in tracks: track = track_name (i) - item = track_first_item (tracks[i]) + item = track_first_item (t) + if item and item.__class__ == Note: s = s + ' \\context Staff=%s \\%s\n' % (track, track) elif item and item.__class__ == Text: s = s + ' \\context Lyrics=%s \\%s\n' % (track, track) - s = s + ' >\n}\n' + + i += 1 + s = s + ' >>\n}\n' progress (_ ("%s output to `%s'...") % ('LY', o)) if o == '-': - h = sys.stdout + handle = sys.stdout else: - h = open (o, 'w') + handle = open (out_file, 'w') - h.write (s) - h.close () + handle.write (s) + handle.close () (sh, long) = getopt_args (option_definitions) @@ -1045,13 +1089,10 @@ for opt in options: elif o == '--absolute-pitches' or o == '-a': - global absolute_p absolute_p = 1 elif o == '--duration-quant' or o == '-d': - global duration_quant duration_quant = string.atoi (a) elif o == '--explicit-durations' or o == '-e': - global explicit_durations_p explicit_durations_p = 1 elif o == '--key' or o == '-k': (alterations, minor) = map (string.atoi, string.split (a + ':0', ':'))[0:2] @@ -1061,7 +1102,6 @@ for opt in options: sharps = alterations else: flats = - alterations - global key key = Key (sharps, flats, minor) elif o == '--start-quant' or o == '-s': start_quant = string.atoi (a) @@ -1083,7 +1123,6 @@ if not files or files[0] == '-': for f in files: - g = f g = strip_extension (g, '.midi') g = strip_extension (g, '.mid')