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