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