]> git.donarmstrong.com Git - lilypond.git/blob - scripts/midi2ly.py
6161b13ead605ee0d207387b5114a068c63f5111
[lilypond.git] / scripts / midi2ly.py
1 #!@PYTHON@
2 #
3 # msdi2ly.py -- LilyPond midi import script
4
5 # source file of the GNU LilyPond music typesetter
6 #
7 # (c) 1998--2006  Han-Wen Nienhuys <hanwen@cs.uu.nl>
8 #                 Jan Nieuwenhuizen <janneke@gnu.org>
9
10
11 '''
12 TODO:
13    * test on weird and unquantised midi input (lily-devel)
14    * update doc and manpage
15
16    * simply insert clef changes whenever too many ledger lines
17      [to avoid tex capacity exceeded]
18    * do not ever quant skips
19    * better lyrics handling
20    * [see if it is feasible to] move ly-classes to library for use in
21      other converters, while leaving midi specific stuff here
22 '''
23
24 import getopt
25 import os
26 import string
27 import sys
28
29        
30 ################################################################
31 # Users of python modules should include this snippet.
32 #
33 libdir = '@local_lilypond_libdir@'
34 if not os.path.isdir (libdir):
35         libdir = '@lilypond_libdir@'
36
37 # ugh
38 datadir = '@local_lilypond_datadir@'
39 if os.environ.has_key ('LILYPONDPREFIX'):
40         datadir = os.environ['LILYPONDPREFIX']
41         while datadir[-1] == os.sep:
42                 datadir= datadir[:-1]
43         libdir = datadir.replace ('/share/', '/lib/')
44
45 if os.path.exists (os.path.join (datadir, 'lib/lilypond/@TOPLEVEL_VERSION@/')):
46         libdir = os.path.join (libdir, 'lib/lilypond/@TOPLEVEL_VERSION@/')
47         
48 if os.path.exists (os.path.join (datadir, 'lib/lilypond/current/')):
49         libdir = os.path.join (libdir, 'lib/lilypond/current/')
50         
51 sys.path.insert (0, os.path.join (libdir, 'python'))
52
53 ################################################################
54
55 import midi
56
57
58 ################################################################
59 ################ CONSTANTS
60
61
62 output_name = ''
63 LINE_BELL = 60
64 scale_steps = [0,2,4,5,7,9,11]
65
66 clocks_per_1 = 1536
67 clocks_per_4 = 0
68 key = 0
69 time = 0
70 reference_note = 0
71 start_quant = 0
72 start_quant_clocks = 0
73 duration_quant = 0
74 duration_quant_clocks = 0
75 allowed_tuplets = []
76 allowed_tuplet_clocks = []
77 absolute_p = 0
78 explicit_durations_p = 0
79 text_lyrics_p = 0
80
81
82
83 ################################################################
84
85 localedir = '@localedir@'
86 try:
87         import gettext
88         gettext.bindtextdomain ('lilypond', localedir)
89         gettext.textdomain ('lilypond')
90         _ = gettext.gettext
91 except:
92         def _ (s):
93                 return s
94
95 program_name = sys.argv[0]
96 program_version = '@TOPLEVEL_VERSION@'
97
98 errorport = sys.stderr
99 verbose_p = 0
100
101 # temp_dir = os.path.join (original_dir,  '%s.dir' % program_name)
102 # original_dir = os.getcwd ()
103 # keep_temp_dir_p = 0
104
105
106 help_summary = _ ("Convert MIDI to LilyPond source.")
107
108 option_definitions = [
109         ('', 'a', 'absolute-pitches', _ ("print absolute pitches")),
110         (_ ("DUR"), 'd', 'duration-quant', _ ("quantise note durations on DUR")),
111         ('', 'e', 'explicit-durations', _ ("print explicit durations")),
112         ('', 'h', 'help', _ ("print this help")),
113         (_ ("ALT[:MINOR]"), 'k', 'key', _ ("set key: ALT=+sharps|-flats; MINOR=1")),
114         (_ ("FILE"), 'o', 'output', _ ("write output to FILE")),
115         (_ ("DUR"), 's', 'start-quant', _ ("quantise note starts on DUR")),
116         (_ ("DUR*NUM/DEN"), 't', 'allow-tuplet', _ ("allow tuplet durations DUR*NUM/DEN")),
117         ('', 'V', 'verbose', _ ("be verbose")),
118         ('', 'v', 'version', _ ("print version number")),
119         ('', 'w', 'warranty', _ ("show warranty and copyright")),
120         ('', 'x', 'text-lyrics', _ ("treat every text as a lyric")),
121         ]
122
123 ################################################################
124 # lilylib.py -- options and stuff
125
126 # source file of the GNU LilyPond music typesetter
127
128 import os
129
130 try:
131         import gettext
132         gettext.bindtextdomain ('lilypond', localedir)
133         gettext.textdomain ('lilypond')
134         _ = gettext.gettext
135 except:
136         def _ (s):
137                 return s
138
139 if program_version == '@' + 'TOPLEVEL_VERSION' + '@':
140         program_version = '1.5.17'
141
142 def identify ():
143         sys.stdout.write ('%s (GNU LilyPond) %s\n' % (program_name, program_version))
144
145 def warranty ():
146         identify ()
147         sys.stdout.write ('\n')
148         sys.stdout.write (_ ('Copyright (c) %s by') % ' 2001--2006')
149         sys.stdout.write ('\n')
150         sys.stdout.write ('  Han-Wen Nienhuys')
151         sys.stdout.write ('  Jan Nieuwenhuizen')
152         sys.stdout.write ('\n\n')
153         sys.stdout.write (_ ("Distributed under terms of the GNU General Public License."))
154         sys.stdout.write (_ ("It comes with NO WARRANTY."))
155         sys.stdout.write ('\n')
156
157 def progress (s):
158         errorport.write (s + '\n')
159
160 def warning (s):
161         progress (_ ("warning: ") + s)
162                 
163 def error (s):
164
165
166         '''Report the error S.  Exit by raising an exception. Please
167         do not abuse by trying to catch this error. If you do not want
168         a stack trace, write to the output directly.
169
170         RETURN VALUE
171
172         None
173         
174         '''
175         
176         progress (_ ("error: ") + s)
177         raise _ ("Exiting ... ")
178
179 def getopt_args (opts):
180         '''Construct arguments (LONG, SHORT) for getopt from  list of options.'''
181         short = ''
182         long = []
183         for o in opts:
184                 if o[1]:
185                         short = short + o[1]
186                         if o[0]:
187                                 short = short + ':'
188                 if o[2]:
189                         l = o[2]
190                         if o[0]:
191                                 l = l + '='
192                         long.append (l)
193         return (short, long)
194
195 def option_help_str (o):
196         '''Transform one option description (4-tuple ) into neatly formatted string'''
197         sh = '  '       
198         if o[1]:
199                 sh = '-%s' % o[1]
200
201         sep = '  '
202         if o[1] and o[2]:
203                 sep = ', '
204                 
205         long = ''
206         if o[2]:
207                 long= '--%s' % o[2]
208
209         arg = ''
210         if o[0]:
211                 if o[2]:
212                         arg = '='
213                 arg = arg + o[0]
214         return '  ' + sh + sep + long + arg
215
216
217 def options_help_str (opts):
218         '''Convert a list of options into a neatly formatted string'''
219         w = 0
220         strs =[]
221         helps = []
222
223         for o in opts:
224                 s = option_help_str (o)
225                 strs.append ((s, o[3]))
226                 if len (s) > w:
227                         w = len (s)
228
229         str = ''
230         for s in strs:
231                 str = str + '%s%s%s\n' % (s[0], ' ' * (w - len(s[0])  + 3), s[1])
232         return str
233
234 def help ():
235         ls = [(_ ("Usage: %s [OPTIONS]... FILE") % program_name),
236                 ('\n\n'),
237                 (help_summary),
238                 ('\n\n'),
239                 (_ ("Options:")),
240                 ('\n'),
241                 (options_help_str (option_definitions)),
242                 ('\n\n'),
243                 (_ ("Report bugs via %s.") %
244                  "http://post.gmane.org/post.php?group=gmane.comp.gnu.lilypond.bugs"),
245                 ('\n')]
246         map (sys.stdout.write, ls)
247         
248 def setup_temp ():
249         """
250         Create a temporary directory, and return its name. 
251         """
252         global temp_dir
253         if not keep_temp_dir_p:
254                 temp_dir = tempfile.mktemp (program_name)
255         try:
256                 os.mkdir (temp_dir, 0777)
257         except OSError:
258                 pass
259
260         return temp_dir
261
262
263 def system (cmd, ignore_error = 0):
264         """Run CMD. If IGNORE_ERROR is set, don't complain when CMD returns non zero.
265
266         RETURN VALUE
267
268         Exit status of CMD
269         """
270         
271         if verbose_p:
272                 progress (_ ("Invoking `%s\'") % cmd)
273         st = os.system (cmd)
274         if st:
275                 name = re.match ('[ \t]*([^ \t]*)', cmd).group (1)
276                 msg = name + ': ' + _ ("command exited with value %d") % st
277                 if ignore_error:
278                         warning (msg + ' ' + _ ("(ignored)") + ' ')
279                 else:
280                         error (msg)
281
282         return st
283
284
285 def cleanup_temp ():
286         if not keep_temp_dir_p:
287                 if verbose_p:
288                         progress (_ ("Cleaning %s...") % temp_dir)
289                 shutil.rmtree (temp_dir)
290
291
292 def strip_extension (f, ext):
293         (p, e) = os.path.splitext (f)
294         if e == ext:
295                 e = ''
296         return p + e
297
298 ################################################################
299 # END Library
300 ################################################################
301
302 \f
303
304
305 class Duration:
306         allowed_durs = (1, 2, 4, 8, 16, 32, 64, 128)
307         def __init__ (self, clocks):
308                 self.clocks = clocks
309                 if clocks <= 0:
310                         self.clocks = duration_quant_clocks
311                 (self.dur, self.num, self.den) = self.dur_num_den (clocks)
312                 
313         def dur_num_den (self, clocks):
314                 for i in range (len (allowed_tuplet_clocks)):
315                         if clocks == allowed_tuplet_clocks[i]:
316                                 return allowed_tuplets[i]
317
318                 dur = 0; num = 1; den = 1;
319                 g = gcd (clocks, clocks_per_1)
320                 if g:
321                         (dur, num) = (clocks_per_1 / g, clocks / g)
322                 if not dur in self.allowed_durs:
323                         dur = 4; num = clocks; den = clocks_per_4
324                 return (dur, num, den)
325
326         def dump (self):
327                 if self.den == 1:
328                         if self.num == 1:
329                                 s = '%d' % self.dur
330                         elif self.num == 3 and self.dur != 1:
331                                 s = '%d.' % (self.dur / 2)
332                         else:
333                                 s = '%d*%d' % (self.dur, self.num)
334                 else:
335                         s = '%d*%d/%d' % (self.dur, self.num, self.den)
336                         
337                 global reference_note
338                 reference_note.duration = self
339
340                 return s
341
342         def compare (self, other):
343                 return self.clocks - other.clocks
344
345 def sign (x):
346         if x >= 0:
347                 return 1
348         else:
349                 return -1
350
351 class Note:
352         names = (0, 0, 1, 1, 2, 3, 3, 4, 4, 5, 5, 6)
353         alterations = (0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 1, 0)
354         alteration_names = ('eses', 'es', '', 'is' , 'isis')
355         def __init__ (self, clocks, pitch, velocity):
356                 self.pitch = pitch
357                 self.velocity = velocity
358                 # hmm
359                 self.clocks = clocks
360                 self.duration = Duration (clocks)
361                 (self.octave, self.notename, self.alteration) = self.o_n_a ()
362
363         def o_n_a (self):
364                 # major scale: do-do
365                 # minor scale: la-la  (= + 5) '''
366
367                 n = self.names[(self.pitch) % 12]
368                 a = self.alterations[(self.pitch) % 12]
369
370                 if a and key.flats:
371                         a = - self.alterations[(self.pitch) % 12]
372                         n = (n - a) % 7
373
374                 #  By tradition, all scales now consist of a sequence
375                 #  of 7 notes each with a distinct name, from amongst
376                 #  a b c d e f g.  But, minor scales have a wide
377                 #  second interval at the top - the 'leading note' is
378                 #  sharped. (Why? it just works that way! Anything
379                 #  else doesn't sound as good and isn't as flexible at
380                 #  saying things. In medieval times, scales only had 6
381                 #  notes to avoid this problem - the hexachords.)
382
383                 #  So, the d minor scale is d e f g a b-flat c-sharp d
384                 #  - using d-flat for the leading note would skip the
385                 #  name c and duplicate the name d.  Why isn't c-sharp
386                 #  put in the key signature? Tradition. (It's also
387                 #  supposedly based on the Pythagorean theory of the
388                 #  cycle of fifths, but that really only applies to
389                 #  major scales...)  Anyway, g minor is g a b-flat c d
390                 #  e-flat f-sharp g, and all the other flat minor keys
391                 #  end up with a natural leading note. And there you
392                 #  have it.
393
394                 #  John Sankey <bf250@freenet.carleton.ca>
395                 #
396                 #  Let's also do a-minor: a b c d e f gis a
397                 #
398                 #  --jcn
399
400                 o = self.pitch / 12 - 4
401
402                 if key.minor:
403                         # as -> gis
404                         if key.sharps == 0 and key.flats == 0 \
405                            and n == 5 and a == -1:
406                                 n = 4; a = 1
407                         # des -> cis
408                         elif key.flats == 1 and n == 1 and a == -1:
409                                 n = 0; a = 1
410                         # ges -> fis
411                         elif key.flats == 2 and n == 4 and a == -1:
412                                 n = 3; a = 1
413                         # g -> fisis
414                         elif key.sharps == 5 and n == 4 and a == 0:
415                                 n = 3; a = 2
416                         # d -> cisis
417                         elif key.sharps == 6 and n == 1 and a == 0:
418                                 n = 0; a = 2
419                         # a -> gisis
420                         elif key.sharps == 7 and n == 5 and a == 0:
421                                 n = 4; a = 2
422
423                 # b -> ces
424                 if key.flats >= 6 and n == 6 and a == 0:
425                         n = 0; a = -1; o = o + 1
426                 # e -> fes
427                 if key.flats >= 7 and n == 2 and a == 0:
428                         n = 3; a = -1
429
430                 # f -> eis
431                 if key.sharps >= 3 and n == 3 and a == 0:
432                         n = 2; a = 1
433                 # c -> bis
434                 if key.sharps >= 4 and n == 0 and a == 0:
435                         n = 6; a = 1; o = o - 1
436
437                 return (o, n, a)
438                 
439         def __repr__ (self):
440                 s = chr ((self.notename + 2)  % 7 + ord ('a'))
441                 return 'Note(%s %s)' % (s, self.duration.dump())
442
443         def dump (self, dump_dur = 1):
444                 global reference_note
445                 s = chr ((self.notename + 2)  % 7 + ord ('a'))
446                 s = s + self.alteration_names[self.alteration + 2]
447                 if absolute_p:
448                         commas = self.octave
449                 else:
450                         delta = self.pitch - reference_note.pitch
451                         commas = sign (delta) * (abs (delta) / 12)
452                         if ((sign (delta) \
453                              * (self.notename - reference_note.notename) + 7) \
454                             % 7 >= 4) \
455                             or ((self.notename == reference_note.notename) \
456                                 and (abs (delta) > 4) and (abs (delta) < 12)):
457                                 commas = commas + sign (delta)
458                         
459                 if commas > 0:
460                         s = s + "'" * commas
461                 elif commas < 0:
462                         s = s + "," * -commas
463
464                 ## FIXME: compile fix --jcn
465                 if dump_dur and (explicit_durations_p \
466                    or Duration.compare (self.duration,
467                                         reference_note.duration)):
468                         s = s + self.duration.dump ()
469
470                 reference_note = self
471                 
472                 # TODO: move space
473                 return s + ' '
474
475
476 class Time:
477         def __init__ (self, num, den):
478                 self.clocks = 0
479                 self.num = num
480                 self.den = den
481
482         def bar_clocks (self):
483                 return clocks_per_1 * self.num / self.den
484
485         def __repr__ (self):
486                 return 'Time(%d/%d)' % (self.num, self.den)
487         
488         def dump (self):
489                 global time
490                 time = self
491                 return '\n  ' + '\\time %d/%d ' % (self.num, self.den) + '\n  '
492
493 class Tempo:
494         def __init__ (self, seconds_per_1):
495                 self.clocks = 0
496                 self.seconds_per_1 = seconds_per_1
497
498         def __repr__ (self):
499                 return 'Tempo(%d)' % self.bpm ()
500         
501         def bpm (self):
502                 return 4 * 60 / self.seconds_per_1
503         
504         def dump (self):
505                 return '\n  ' + '\\tempo 4 = %d ' % (self.bpm()) + '\n  '
506
507 class Clef:
508         clefs = ('"bass_8"', 'bass', 'violin', '"violin^8"')
509         def __init__ (self, type):
510                 self.type = type
511
512         def __repr__ (self):
513                 return 'Clef(%s)' % self.clefs[self.type]
514         
515         def dump (self):
516                 return '\n  \\clef %s\n  ' % self.clefs[self.type]
517
518 class Key:
519         key_sharps = ('c', 'g', 'd', 'a', 'e', 'b', 'fis')
520         key_flats = ('BUG', 'f', 'bes', 'es', 'as', 'des', 'ges')
521
522         def __init__ (self, sharps, flats, minor):
523                 self.clocks = 0
524                 self.flats = flats
525                 self.sharps = sharps
526                 self.minor = minor
527
528         def dump (self):
529                 global key
530                 key = self
531
532                 s = ''
533                 if self.sharps and self.flats:
534                         pass
535                 else:
536                         if self.flats:
537                                 k = (ord ('cfbeadg'[self.flats % 7]) - ord ('a') - 2 -2 * self.minor + 7) % 7
538                         else:
539                                 k = (ord ('cgdaebf'[self.sharps % 7]) - ord ('a') - 2 -2 * self.minor + 7) % 7
540   
541                         if not self.minor:
542                                 name = chr ((k + 2) % 7 + ord ('a'))
543                         else:
544                                 name = chr ((k + 2) % 7 + ord ('a'))
545
546                         # fis cis gis dis ais eis bis
547                         sharps = (2, 4, 6, 1, 3, 5, 7)
548                         # bes es as des ges ces fes
549                         flats = (6, 4, 2, 7, 5, 3, 1)
550                         a = 0
551                         if self.flats:
552                                 if flats[k] <= self.flats:
553                                         a = -1
554                         else:
555                                 if sharps[k] <= self.sharps:
556                                         a = 1
557
558                         if a:
559                                 name = name + Note.alteration_names[a + 2]
560
561                         s = '\\key ' + name
562                         if self.minor:
563                                 s = s + ' \\minor'
564                         else:
565                                 s = s + ' \\major'
566
567                 return '\n\n  ' + s + '\n  '
568
569
570 class Text:
571         text_types = (
572                 'SEQUENCE_NUMBER',
573                 'TEXT_EVENT',
574                 'COPYRIGHT_NOTICE',
575                 'SEQUENCE_TRACK_NAME',
576                 'INSTRUMENT_NAME',
577                 'LYRIC',
578                 'MARKER',
579                 'CUE_POINT',)
580         
581         def __init__ (self, type, text):
582                 self.clocks = 0
583                 self.type = type
584                 self.text = text
585
586         def dump (self):
587                 # urg, we should be sure that we're in a lyrics staff
588                 if self.type == midi.LYRIC:
589                         s = '"%s"' % self.text
590                         d = Duration (self.clocks)
591                         if explicit_durations_p \
592                            or Duration.compare (d,
593                                                 reference_note.duration):
594                                 s = s + Duration (self.clocks).dump ()
595                         s = s + ' '
596                 else:
597                         s = '\n  % [' + self.text_types[self.type] + '] ' + self.text + '\n  '
598                 return s
599
600         def __repr__ (self):
601                 return 'Text(%d=%s)' % (self.type, self.text)
602
603
604
605 def split_track (track):
606         chs = {}
607         for i in range(16):
608                 chs[i] = []
609                 
610         for e in track:
611                 data = list (e[1])
612                 if data[0] > 0x7f and data[0] < 0xf0:
613                         c = data[0] & 0x0f
614                         e = (e[0], tuple ([data[0] & 0xf0] + data[1:]))
615                         chs[c].append (e)
616                 else:
617                         chs[0].append (e)
618
619         for i in range (16):
620                 if chs[i] == []:
621                         del chs[i]
622
623         threads = []
624         for v in chs.values ():
625                 events = events_on_channel (v)
626                 thread = unthread_notes (events)
627                 if len (thread):
628                         threads.append (thread)
629         return threads
630
631
632 def quantise_clocks (clocks, quant):
633         q = int (clocks / quant) * quant
634         if q != clocks:
635                 for tquant in allowed_tuplet_clocks:
636                         if int (clocks / tquant) * tquant == clocks:
637                                 return clocks
638                 if 2 * (clocks - q) > quant:
639                         q = q + quant
640         return q
641
642 def end_note (pitches, notes, t, e):
643         try:
644                 (lt, vel) = pitches[e]
645                 del pitches[e]
646
647                 i = len (notes) - 1 
648                 while i > 0:
649                         if notes[i][0] > lt:
650                                 i = i -1
651                         else:
652                                 break
653                 d = t - lt
654                 if duration_quant_clocks:
655                         d = quantise_clocks (d, duration_quant_clocks)
656                         if not d:
657                                 d = duration_quant_clocks
658
659                 notes.insert (i + 1,
660                             (lt, Note (d, e, vel)))
661
662         except KeyError:
663                 pass
664
665 def events_on_channel (channel):
666         pitches = {}
667
668         notes = []
669         events = []
670         last_lyric = 0
671         last_time = 0
672         for e in channel:
673                 t = e[0]
674
675                 if start_quant_clocks:
676                         t = quantise_clocks (t, start_quant_clocks)
677
678
679                 if e[1][0] == midi.NOTE_OFF \
680                    or (e[1][0] == midi.NOTE_ON and e[1][2] == 0):
681                         end_note (pitches, notes, t, e[1][1])
682                         
683                 elif e[1][0] == midi.NOTE_ON:
684                         if not pitches.has_key (e[1][1]):
685                                 pitches[e[1][1]] = (t, e[1][2])
686                                 
687                 # all include ALL_NOTES_OFF
688                 elif e[1][0] >= midi.ALL_SOUND_OFF \
689                      and e[1][0] <= midi.POLY_MODE_ON:
690                         for i in pitches.keys ():
691                                 end_note (pitches, notes, t, i)
692                                 
693                 elif e[1][0] == midi.META_EVENT:
694                         if e[1][1] == midi.END_OF_TRACK:
695                                 for i in pitches.keys ():
696                                         end_note (pitches, notes, t, i)
697                                 break
698
699                         elif e[1][1] == midi.SET_TEMPO:
700                                 (u0, u1, u2) = map (ord, e[1][2])
701                                 us_per_4 = u2 + 256 * (u1 + 256 * u0)
702                                 seconds_per_1 = us_per_4 * 4 / 1e6
703                                 events.append ((t, Tempo (seconds_per_1)))
704                         elif e[1][1] == midi.TIME_SIGNATURE:
705                                 (num, dur, clocks4, count32) = map (ord, e[1][2])
706                                 den = 2 ** dur 
707                                 events.append ((t, Time (num, den)))
708                         elif e[1][1] == midi.KEY_SIGNATURE:
709                                 (alterations, minor) = map (ord, e[1][2])
710                                 sharps = 0
711                                 flats = 0
712                                 if alterations < 127:
713                                         sharps = alterations
714                                 else:
715                                         flats = 256 - alterations
716
717                                 k = Key (sharps, flats, minor)
718                                 events.append ((t, k))
719
720                                 # ugh, must set key while parsing
721                                 # because Note init uses key
722                                 # Better do Note.calc () at dump time?
723                                 global key
724                                 key = k
725
726                         elif e[1][1] == midi.LYRIC \
727                              or (text_lyrics_p and e[1][1] == midi.TEXT_EVENT):
728                                 if last_lyric:
729                                         last_lyric.clocks = t - last_time
730                                         events.append ((last_time, last_lyric))
731                                 last_time = t
732                                 last_lyric = Text (midi.LYRIC, e[1][2])
733
734                         elif e[1][1] >= midi.SEQUENCE_NUMBER \
735                              and e[1][1] <= midi.CUE_POINT:
736                                 events.append ((t, Text (e[1][1], e[1][2])))
737                         else:
738                                 if verbose_p:
739                                         sys.stderr.write ("SKIP: %s\n" % `e`)
740                                 pass
741                 else:
742                         if verbose_p:
743                                 sys.stderr.write ("SKIP: %s\n" % `e`)
744                         pass
745
746         if last_lyric:
747                 # last_lyric.clocks = t - last_time
748                 # hmm
749                 last_lyric.clocks = clocks_per_4
750                 events.append ((last_time, last_lyric))
751                 last_lyric = 0
752                 
753         i = 0
754         while len (notes):
755                 if i < len (events) and notes[0][0] >= events[i][0]:
756                         i = i + 1
757                 else:
758                         events.insert (i, notes[0])
759                         del notes[0]
760         return events
761
762 def unthread_notes (channel):
763         threads = []
764         while channel:
765                 thread = []
766                 end_busy_t = 0
767                 start_busy_t = 0
768                 todo = []
769                 for e in channel:
770                         t = e[0]
771                         if e[1].__class__ == Note \
772                            and ((t == start_busy_t \
773                                  and e[1].clocks + t == end_busy_t) \
774                             or t >= end_busy_t):
775                                 thread.append (e)
776                                 start_busy_t = t
777                                 end_busy_t = t + e[1].clocks
778                         elif e[1].__class__ == Time \
779                              or e[1].__class__ == Key \
780                              or e[1].__class__ == Text \
781                              or e[1].__class__ == Tempo:
782                                 thread.append (e)
783                         else:
784                                 todo.append (e)
785                 threads.append (thread)
786                 channel = todo
787
788         return threads
789
790 def gcd (a,b):
791         if b == 0:
792                 return a
793         c = a
794         while c: 
795                 c = a % b
796                 a = b
797                 b = c
798         return a
799         
800 def dump_skip (skip, clocks):
801         return skip + Duration (clocks).dump () + ' '
802
803 def dump (self):
804         return self.dump ()
805
806 def dump_chord (ch):
807         s = ''
808         notes = []
809         for i in ch:
810                 if i.__class__ == Note:
811                         notes.append (i)
812                 else:
813                         s = s + i.dump ()
814         if len (notes) == 1:
815                 s = s + dump (notes[0])
816         elif len (notes) > 1:
817                 global reference_note
818                 s = s + '<'
819                 s = s + notes[0].dump (dump_dur = 0)
820                 r = reference_note
821                 for i in notes[1:]:
822                         s = s + i.dump (dump_dur = 0 )
823                 s = s + '>'
824
825                 s = s + notes[0].duration.dump() + ' '
826                 reference_note = r
827         return s
828
829 def dump_bar_line (last_bar_t, t, bar_count):
830         s = ''
831         bar_t = time.bar_clocks ()
832         if t - last_bar_t >= bar_t:
833                 bar_count = bar_count + (t - last_bar_t) / bar_t
834                 
835                 if t - last_bar_t == bar_t:
836                         s = '|\n  %% %d\n  ' % bar_count
837                         last_bar_t = t
838                 else:
839                         # urg, this will barf at meter changes
840                         last_bar_t = last_bar_t + (t - last_bar_t) / bar_t * bar_t
841                         
842         return (s, last_bar_t, bar_count)
843
844                         
845 def dump_channel (thread, skip):
846         global key, reference_note, time
847
848         key = Key (0, 0, 0)
849         time = Time (4, 4)
850         # urg LilyPond doesn't start at c4, but
851         # remembers from previous tracks!
852         # reference_note = Note (clocks_per_4, 4*12, 0)
853         reference_note = Note (0, 4*12, 0)
854         last_e = None
855         chs = []
856         ch = []
857
858         for e in thread:
859                 if last_e and last_e[0] == e[0]:
860                         ch.append (e[1])
861                 else:
862                         if ch:
863                                 chs.append ((last_e[0], ch))
864                                 
865                         ch = [e[1]]
866                         
867                 last_e = e
868
869         if ch:
870                 chs.append ((last_e[0], ch))
871         t = 0
872         last_t = 0
873         last_bar_t = 0
874         bar_count = 1
875         
876         lines = ['']
877         for ch in chs: 
878                 t = ch[0]
879
880                 i = string.rfind (lines[-1], '\n') + 1
881                 if len (lines[-1][i:]) > LINE_BELL:
882                         lines.append ('')
883                         
884                 if t - last_t > 0:
885                         lines[-1] = lines[-1] + dump_skip (skip, t-last_t)
886                 elif t - last_t < 0:
887                         errorport.write ('BUG: time skew')
888
889                 (s, last_bar_t, bar_count) = dump_bar_line (last_bar_t,
890                                                             t, bar_count)
891                 lines[-1] = lines[-1] + s
892                 
893                 lines[-1] = lines[-1] + dump_chord (ch[1])
894
895                 clocks = 0
896                 for i in ch[1]:
897                         if i.clocks > clocks:
898                                 clocks = i.clocks
899                                 
900                 last_t = t + clocks
901                 
902                 (s, last_bar_t, bar_count) = dump_bar_line (last_bar_t,
903                                                             last_t, bar_count)
904                 lines[-1] = lines[-1] + s
905
906         return string.join (lines, '\n  ') + '\n'
907
908 def track_name (i):
909         return 'track%c' % (i + ord ('A'))
910
911 def channel_name (i):
912         return 'channel%c' % (i + ord ('A'))
913
914 def dump_track (channels, n):
915         s = '\n'
916         track = track_name (n)
917         clef = guess_clef (channels)
918
919         for i in range (len (channels)):
920                 channel = channel_name (i)
921                 item = thread_first_item (channels[i])
922
923                 if item and item.__class__ == Note:
924                         skip = 's'
925                         s = s + '%s = ' % (track + channel)
926                         if not absolute_p:
927                                 s = s + '\\relative c '
928                 elif item and item.__class__ == Text:
929                         skip = '" "'
930                         s = s + '%s = \\lyricmode ' % (track + channel)
931                 else:
932                         skip = '\\skip '
933                         s = s + '%s =  ' % (track + channel)
934                 s = s + '{\n'
935                 s = s + '  ' + dump_channel (channels[i][0], skip)
936                 s = s + '}\n\n'
937
938         s = s + '%s = <<\n' % track
939
940         if clef.type != 2:
941                 s = s + clef.dump () + '\n'
942
943         for i in range (len (channels)):
944                 channel = channel_name (i)
945                 item = thread_first_item (channels[i])
946                 if item and item.__class__ == Text:
947                         s = s + '  \\context Lyrics = %s \\%s\n' % (channel,
948                                                                     track + channel)
949                 else:
950                         s = s + '  \\context Voice = %s \\%s\n' % (channel,
951                                                                    track + channel)
952         s = s + '>>\n\n'
953         return s
954
955 def thread_first_item (thread):
956         for chord in thread:
957                 for event in chord:
958                         if (event[1].__class__ == Note 
959                             or (event[1].__class__ == Text 
960                                 and event[1].type == midi.LYRIC)):
961                                 
962                             return event[1]
963         return None
964
965 def track_first_item (track):
966         for thread in track:
967                 first = thread_first_item (thread)
968                 if first:
969                         return first
970         return None
971
972 def guess_clef (track):
973         i = 0
974         p = 0
975         for thread in track:
976                 for chord in thread:
977                         for event in chord:
978                                 if event[1].__class__ == Note:
979                                         i = i + 1
980                                         p = p + event[1].pitch
981         if i and p / i <= 3*12:
982                 return Clef (0)
983         elif i and p / i <= 5*12:
984                 return Clef (1)
985         elif i and p / i >= 7*12:
986                 return Clef (3)
987         else:
988                 return Clef (2)
989         
990
991 def convert_midi (in_file, out_file):
992         global clocks_per_1, clocks_per_4, key
993         global start_quant, start_quant_clocks
994         global duration_quant, duration_quant_clocks
995         global allowed_tuplet_clocks
996
997         str = open (in_file).read ()
998         midi_dump = midi.parse (str)
999         
1000         clocks_per_1 = midi_dump[0][1]
1001         clocks_per_4 = clocks_per_1 / 4
1002         
1003         if start_quant:
1004                 start_quant_clocks = clocks_per_1 / start_quant
1005
1006         if duration_quant:
1007                 duration_quant_clocks = clocks_per_1 / duration_quant
1008
1009         allowed_tuplet_clocks = []
1010         for (dur, num, den) in allowed_tuplets:
1011                 allowed_tuplet_clocks.append (clocks_per_1 * num / (dur * den))
1012
1013         tracks = []
1014         for t in midi_dump[1]:
1015                 key = Key (0, 0, 0)
1016                 tracks.append (split_track (t))
1017
1018         tag = '%% Lily was here -- automatically converted by %s from %s' % ( program_name, f)
1019
1020         
1021         s = ''
1022         s = tag + '\n\\version "2.7.18"\n\n'
1023         for i in range (len (tracks)):
1024                 s = s + dump_track (tracks[i], i)
1025
1026         s = s + '\n\\score {\n  <<\n'
1027         
1028         i = 0
1029         for t in tracks:
1030                 track = track_name (i)
1031                 item = track_first_item (t)
1032                 
1033                 if item and item.__class__ == Note:
1034                         s = s + '    \\context Staff=%s \\%s\n' % (track, track)
1035                 elif item and item.__class__ == Text:
1036                         s = s + '    \\context Lyrics=%s \\%s\n' % (track, track)
1037
1038                 i += 1
1039         s = s + '  >>\n}\n'
1040
1041         progress (_ ("%s output to `%s'...") % ('LY', o))
1042
1043         if o == '-':
1044                 handle = sys.stdout
1045         else:
1046                 handle = open (out_file, 'w')
1047
1048         handle.write (s)
1049         handle.close ()
1050
1051
1052 (sh, long) = getopt_args (option_definitions)
1053 try:
1054         (options, files) = getopt.getopt(sys.argv[1:], sh, long)
1055 except getopt.error, s:
1056         errorport.write ('\n')
1057         errorport.write (_ ("error: ") + _ ("getopt says: `%s\'" % s))
1058         errorport.write ('\n')
1059         errorport.write ('\n')
1060         help ()
1061         sys.exit (2)
1062         
1063 for opt in options:     
1064         o = opt[0]
1065         a = opt[1]
1066
1067         if 0:
1068                 pass
1069         elif o == '--help' or o == '-h':
1070                 help ()
1071                 errorport.write ('\n')
1072                 errorport.write (_ ("Example:"))
1073                 errorport.write  (r'''
1074     midi2ly --key=-2:1 --duration-quant=32 \
1075         --allow-tuplet=4*2/3 --allow-tuplet=2*4/3 foo.midi
1076 ''')
1077                 sys.exit (0)
1078         elif o == '--output' or o == '-o':
1079                 output_name = a
1080         elif o == '--verbose' or o == '-V':
1081                 verbose_p = 1
1082         elif o == '--version' or o == '-v':
1083                 identify ()
1084                 sys.exit (0)
1085         elif o == '--warranty' or o == '-w':
1086                 status = system ('lilypond -w', ignore_error = 1)
1087                 if status:
1088                         warranty ()
1089                 sys.exit (0)
1090
1091
1092         elif o == '--absolute-pitches' or o == '-a':
1093                 absolute_p = 1
1094         elif o == '--duration-quant' or o == '-d':
1095                 duration_quant = string.atoi (a)
1096         elif o == '--explicit-durations' or o == '-e':
1097                 explicit_durations_p = 1
1098         elif o == '--key' or o == '-k':
1099                 (alterations, minor) = map (string.atoi, string.split (a + ':0', ':'))[0:2]
1100                 sharps = 0
1101                 flats = 0
1102                 if alterations >= 0:
1103                         sharps = alterations
1104                 else:
1105                         flats = - alterations
1106                 key = Key (sharps, flats, minor)
1107         elif o == '--start-quant' or o == '-s':
1108                 start_quant = string.atoi (a)
1109         elif o == '--allow-tuplet' or o == '-t':
1110                 a = string.replace (a, '/', '*')
1111                 tuplet = map (string.atoi, string.split (a, '*'))
1112                 allowed_tuplets.append (tuplet)
1113         # lots of midi files use plain text for lyric events
1114         elif o == '--text-lyrics' or o == '-x':
1115                 text_lyrics_p = 1
1116
1117
1118 if not files or files[0] == '-':
1119
1120         # FIXME: read from stdin when files[0] = '-'
1121         help ()
1122         errorport.write (program_name + ":" + _ ("error: ") + _ ("no files specified on command line.") + '\n')
1123         sys.exit (2)
1124
1125
1126 for f in files:
1127         g = f
1128         g = strip_extension (g, '.midi')
1129         g = strip_extension (g, '.mid')
1130         g = strip_extension (g, '.MID')
1131         (outdir, outbase) = ('','')
1132
1133         if not output_name:
1134                 outdir = '.'
1135                 outbase = os.path.basename (g)
1136                 o = os.path.join (outdir, outbase + '-midi.ly')
1137         elif output_name[-1] == os.sep:
1138                 outdir = output_name
1139                 outbase = os.path.basename (g)
1140                 os.path.join (outdir, outbase + '-gen.ly')
1141         else:
1142                 o = output_name
1143                 (outdir, outbase) = os.path.split (o)
1144
1145         if outdir != '.' and outdir != '':
1146                 try:
1147                         os.mkdir (outdir, 0777)
1148                 except OSError:
1149                         pass
1150
1151         convert_midi (f, o)
1152