]> git.donarmstrong.com Git - lilypond.git/blob - scripts/midi2ly.py
*** empty log message ***
[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                         s = '\\keysignature %s ' % 'TODO'
507                 else:
508                         
509                         if self.flats:
510                                 k = (ord ('cfbeadg'[self.flats % 7]) - ord ('a') - 2 -2 * self.minor + 7) % 7
511                         else:
512                                 k = (ord ('cgdaebf'[self.sharps % 7]) - ord ('a') - 2 -2 * self.minor + 7) % 7
513   
514                         if not self.minor:
515                                 name = chr ((k + 2) % 7 + ord ('a'))
516                         else:
517                                 name = chr ((k + 2) % 7 + ord ('a'))
518
519                         # fis cis gis dis ais eis bis
520                         sharps = (2, 4, 6, 1, 3, 5, 7)
521                         # bes es as des ges ces fes
522                         flats = (6, 4, 2, 7, 5, 3, 1)
523                         a = 0
524                         if self.flats:
525                                 if flats[k] <= self.flats:
526                                         a = -1
527                         else:
528                                 if sharps[k] <= self.sharps:
529                                         a = 1
530
531                         if a:
532                                 name = name + Note.alteration_names[a + 2]
533
534                         s = '\\key ' + name
535                         if self.minor:
536                                 s = s + ' \\minor'
537                         else:
538                                 s = s + ' \\major'
539
540                 return '\n\n  ' + s + '\n  '
541
542
543 class Text:
544         text_types = (
545                 'SEQUENCE_NUMBER',
546                 'TEXT_EVENT',
547                 'COPYRIGHT_NOTICE',
548                 'SEQUENCE_TRACK_NAME',
549                 'INSTRUMENT_NAME',
550                 'LYRIC',
551                 'MARKER',
552                 'CUE_POINT',)
553         
554         def __init__ (self, type, text):
555                 self.clocks = 0
556                 self.type = type
557                 self.text = text
558
559         def dump (self):
560                 # urg, we should be sure that we're in a lyrics staff
561                 if self.type == midi.LYRIC:
562                         s = '"%s"' % self.text
563                         d = Duration (self.clocks)
564                         if explicit_durations_p \
565                            or Duration.compare (d,
566                                                 reference_note.duration):
567                                 s = s + Duration (self.clocks).dump ()
568                         s = s + ' '
569                 else:
570                         s = '\n  % [' + self.text_types[self.type] + '] ' + self.text + '\n  '
571                 return s
572
573
574 def split_track (track):
575         chs = {}
576         for i in range(16):
577                 chs[i] = []
578                 
579         for e in track:
580                 data = list (e[1])
581                 if data[0] > 0x7f and data[0] < 0xf0:
582                         c = data[0] & 0x0f
583                         e = (e[0], tuple ([data[0] & 0xf0] + data[1:]))
584                         chs[c].append (e)
585                 else:
586                         chs[0].append (e)
587
588         for i in range (16):
589                 if chs[i] == []:
590                         del chs[i]
591
592         threads = []
593         for v in chs.values ():
594                 events = events_on_channel (v)
595                 thread = unthread_notes (events)
596                 if len (thread):
597                         threads.append (thread)
598         return threads
599
600
601 def quantise_clocks (clocks, quant):
602         q = int (clocks / quant) * quant
603         if q != clocks:
604                 for tquant in allowed_tuplet_clocks:
605                         if int (clocks / tquant) * tquant == clocks:
606                                 return clocks
607                 if 2 * (clocks - q) > quant:
608                         q = q + quant
609         return q
610
611 def end_note (pitches, notes, t, e):
612         try:
613                 (lt, vel) = pitches[e]
614                 del pitches[e]
615
616                 i = len (notes) - 1 
617                 while i > 0:
618                         if notes[i][0] > lt:
619                                 i = i -1
620                         else:
621                                 break
622                 d = t - lt
623                 if duration_quant_clocks:
624                         d = quantise_clocks (d, duration_quant_clocks)
625                         if not d:
626                                 d = duration_quant_clocks
627
628                 notes.insert (i + 1,
629                             (lt, Note (d, e, vel)))
630
631         except KeyError:
632                 pass
633
634 def events_on_channel (channel):
635         pitches = {}
636
637         notes = []
638         events = []
639         last_lyric = 0
640         last_time = 0
641         for e in channel:
642                 t = e[0]
643
644                 if start_quant_clocks:
645                         t = quantise_clocks (t, start_quant_clocks)
646
647
648                 if e[1][0] == midi.NOTE_OFF \
649                    or (e[1][0] == midi.NOTE_ON and e[1][2] == 0):
650                         end_note (pitches, notes, t, e[1][1])
651                         
652                 elif e[1][0] == midi.NOTE_ON:
653                         if not pitches.has_key (e[1][1]):
654                                 pitches[e[1][1]] = (t, e[1][2])
655                                 
656                 # all include ALL_NOTES_OFF
657                 elif e[1][0] >= midi.ALL_SOUND_OFF \
658                      and e[1][0] <= midi.POLY_MODE_ON:
659                         for i in pitches.keys ():
660                                 end_note (pitches, notes, t, i)
661                                 
662                 elif e[1][0] == midi.META_EVENT:
663                         if e[1][1] == midi.END_OF_TRACK:
664                                 for i in pitches.keys ():
665                                         end_note (pitches, notes, t, i)
666                                 break
667
668                         elif e[1][1] == midi.SET_TEMPO:
669                                 (u0, u1, u2) = map (ord, e[1][2])
670                                 us_per_4 = u2 + 256 * (u1 + 256 * u0)
671                                 seconds_per_1 = us_per_4 * 4 / 1e6
672                                 events.append ((t, Tempo (seconds_per_1)))
673                         elif e[1][1] == midi.TIME_SIGNATURE:
674                                 (num, dur, clocks4, count32) = map (ord, e[1][2])
675                                 den = 2 ** dur 
676                                 events.append ((t, Time (num, den)))
677                         elif e[1][1] == midi.KEY_SIGNATURE:
678                                 (alterations, minor) = map (ord, e[1][2])
679                                 sharps = 0
680                                 flats = 0
681                                 if alterations < 127:
682                                         sharps = alterations
683                                 else:
684                                         flats = 256 - alterations
685
686                                 k = Key (sharps, flats, minor)
687                                 events.append ((t, k))
688
689                                 # ugh, must set key while parsing
690                                 # because Note init uses key
691                                 # Better do Note.calc () at dump time?
692                                 global key
693                                 key = k
694
695                         elif e[1][1] == midi.LYRIC \
696                              or (text_lyrics_p and e[1][1] == midi.TEXT_EVENT):
697                                 if last_lyric:
698                                         last_lyric.clocks = t - last_time
699                                         events.append ((last_time, last_lyric))
700                                 last_time = t
701                                 last_lyric = Text (midi.LYRIC, e[1][2])
702
703                         elif e[1][1] >= midi.SEQUENCE_NUMBER \
704                              and e[1][1] <= midi.CUE_POINT:
705                                 events.append ((t, Text (e[1][1], e[1][2])))
706                         else:
707                                 if verbose_p:
708                                         sys.stderr.write ("SKIP: %s\n" % `e`)
709                                 pass
710                 else:
711                         if verbose_p:
712                                 sys.stderr.write ("SKIP: %s\n" % `e`)
713                         pass
714
715         if last_lyric:
716                 # last_lyric.clocks = t - last_time
717                 # hmm
718                 last_lyric.clocks = clocks_per_4
719                 events.append ((last_time, last_lyric))
720                 last_lyric = 0
721                 
722         i = 0
723         while len (notes):
724                 if i < len (events) and notes[0][0] >= events[i][0]:
725                         i = i + 1
726                 else:
727                         events.insert (i, notes[0])
728                         del notes[0]
729         return events
730
731 def unthread_notes (channel):
732         threads = []
733         while channel:
734                 thread = []
735                 end_busy_t = 0
736                 start_busy_t = 0
737                 todo = []
738                 for e in channel:
739                         t = e[0]
740                         if e[1].__class__ == Note \
741                            and ((t == start_busy_t \
742                                  and e[1].clocks + t == end_busy_t) \
743                             or t >= end_busy_t):
744                                 thread.append (e)
745                                 start_busy_t = t
746                                 end_busy_t = t + e[1].clocks
747                         elif e[1].__class__ == Time \
748                              or e[1].__class__ == Key \
749                              or e[1].__class__ == Text \
750                              or e[1].__class__ == Tempo:
751                                 thread.append (e)
752                         else:
753                                 todo.append (e)
754                 threads.append (thread)
755                 channel = todo
756
757         return threads
758
759 def gcd (a,b):
760         if b == 0:
761                 return a
762         c = a
763         while c: 
764                 c = a % b
765                 a = b
766                 b = c
767         return a
768         
769 def dump_skip (skip, clocks):
770         return skip + Duration (clocks).dump () + ' '
771
772 def dump (self):
773         return self.dump ()
774
775 def dump_chord (ch):
776         s = ''
777         notes = []
778         for i in ch:
779                 if i.__class__ == Note:
780                         notes.append (i)
781                 else:
782                         s = s + i.dump ()
783         if len (notes) == 1:
784                 s = s + dump (notes[0])
785         elif len (notes) > 1:
786                 global reference_note
787                 s = s + '<'
788                 s = s + notes[0].dump (dump_dur = 0)
789                 r = reference_note
790                 for i in notes[1:]:
791                         s = s + i.dump (dump_dur = 0 )
792                 s = s + '>'
793
794                 s = s + notes[0].duration.dump() + ' '
795                 reference_note = r
796         return s
797
798 def dump_bar_line (last_bar_t, t, bar_count):
799         s = ''
800         bar_t = time.bar_clocks ()
801         if t - last_bar_t >= bar_t:
802                 bar_count = bar_count + (t - last_bar_t) / bar_t
803                 
804                 if t - last_bar_t == bar_t:
805                         s = '|\n  %% %d\n  ' % bar_count
806                         last_bar_t = t
807                 else:
808                         # urg, this will barf at meter changes
809                         last_bar_t = last_bar_t + (t - last_bar_t) / bar_t * bar_t
810                         
811         return (s, last_bar_t, bar_count)
812
813                         
814 def dump_channel (thread, skip):
815         global key, reference_note, time
816
817         key = Key (0, 0, 0)
818         time = Time (4, 4)
819         # urg LilyPond doesn't start at c4, but
820         # remembers from previous tracks!
821         # reference_note = Note (clocks_per_4, 4*12, 0)
822         reference_note = Note (0, 4*12, 0)
823         last_e = None
824         chs = []
825         ch = []
826
827         for e in thread:
828                 if last_e and last_e[0] == e[0]:
829                         ch.append (e[1])
830                 else:
831                         if ch:
832                                 chs.append ((last_e[0], ch))
833                                 
834                         ch = [e[1]]
835                         
836                 last_e = e
837
838         if ch:
839                 chs.append ((last_e[0], ch))
840         t = 0
841         last_t = 0
842         last_bar_t = 0
843         bar_count = 1
844         
845         lines = ['']
846         for ch in chs: 
847                 t = ch[0]
848
849                 i = string.rfind (lines[-1], '\n') + 1
850                 if len (lines[-1][i:]) > LINE_BELL:
851                         lines.append ('')
852                         
853                 if t - last_t > 0:
854                         lines[-1] = lines[-1] + dump_skip (skip, t-last_t)
855                 elif t - last_t < 0:
856                         errorport.write ('BUG: time skew')
857
858                 (s, last_bar_t, bar_count) = dump_bar_line (last_bar_t,
859                                                             t, bar_count)
860                 lines[-1] = lines[-1] + s
861                 
862                 lines[-1] = lines[-1] + dump_chord (ch[1])
863
864                 clocks = 0
865                 for i in ch[1]:
866                         if i.clocks > clocks:
867                                 clocks = i.clocks
868                                 
869                 last_t = t + clocks
870                 
871                 (s, last_bar_t, bar_count) = dump_bar_line (last_bar_t,
872                                                             last_t, bar_count)
873                 lines[-1] = lines[-1] + s
874
875         return string.join (lines, '\n  ') + '\n'
876
877 def track_name (i):
878         return 'track%c' % (i + ord ('A'))
879
880 def channel_name (i):
881         return 'channel%c' % (i + ord ('A'))
882
883 def dump_track (channels, n):
884         s = '\n'
885         track = track_name (n)
886         clef = guess_clef (channels)
887
888         for i in range (len (channels)):
889                 channel = channel_name (i)
890                 item = thread_first_item (channels[i])
891
892                 if item and item.__class__ == Note:
893                         skip = 's'
894                         s = s + '%s = ' % (track + channel)
895                         if not absolute_p:
896                                 s = s + '\\relative c '
897                 elif item and item.__class__ == Text:
898                         skip = '" "'
899                         s = s + '%s = \\lyricmode ' % (track + channel)
900                 else:
901                         skip = '\\skip '
902                         s = s + '%s =  ' % (track + channel)
903                 s = s + '{\n'
904                 s = s + '  ' + dump_channel (channels[i][0], skip)
905                 s = s + '}\n\n'
906
907         s = s + '%s = <<\n' % track
908
909         if clef.type != 2:
910                 s = s + clef.dump () + '\n'
911
912         for i in range (len (channels)):
913                 channel = channel_name (i)
914                 item = thread_first_item (channels[i])
915                 if item and item.__class__ == Text:
916                         s = s + '  \\context Lyrics = %s \\%s\n' % (channel,
917                                                                     track + channel)
918                 else:
919                         s = s + '  \\context Voice = %s \\%s\n' % (channel,
920                                                                    track + channel)
921         s = s + '>>\n\n'
922         return s
923
924 def thread_first_item (thread):
925         for chord in thread:
926                 for event in chord:
927                         if event[1].__class__ == Note \
928                            or (event[1].__class__ == Text \
929                                and event[1].type == midi.LYRIC):
930                                 return event[1]
931         return 0
932
933 def track_first_item (track):
934         for thread in track:
935                 return thread_first_item (thread)
936
937 def guess_clef (track):
938         i = 0
939         p = 0
940         for thread in track:
941                 for chord in thread:
942                         for event in chord:
943                                 if event[1].__class__ == Note:
944                                         i = i + 1
945                                         p = p + event[1].pitch
946         if i and p / i <= 3*12:
947                 return Clef (0)
948         elif i and p / i <= 5*12:
949                 return Clef (1)
950         elif i and p / i >= 7*12:
951                 return Clef (3)
952         else:
953                 return Clef (2)
954         
955
956 def convert_midi (f, o):
957         global clocks_per_1, clocks_per_4, key
958
959         str = open (f).read ()
960         midi_dump = midi.parse (str)
961
962         clocks_per_1 = midi_dump[0][1]
963         clocks_per_4 = clocks_per_1 / 4
964         
965         global start_quant, start_quant_clocks
966         if start_quant:
967                 start_quant_clocks = clocks_per_1 / start_quant
968
969         global duration_quant, duration_quant_clocks
970         if duration_quant:
971                 duration_quant_clocks = clocks_per_1 / duration_quant
972
973         global allowed_tuplet_clocks
974         allowed_tuplet_clocks = []
975         for (dur, num, den) in allowed_tuplets:
976                 allowed_tuplet_clocks.append (clocks_per_1 * num / (dur * den))
977
978         tracks = []
979         for t in midi_dump[1]:
980                 key = Key (0, 0, 0)
981                 tracks.append (split_track (t))
982
983         tag = '%% Lily was here -- automatically converted by %s from %s' % ( program_name, f)
984
985         
986         s = ''
987         s = tag + '\n\\version "2.3.25"\n\n'
988         for i in range (len (tracks)):
989                 s = s + dump_track (tracks[i], i)
990
991         s = s + '\n\\score {\n  <<\n'
992         for i in range (len (tracks)):
993                 track = track_name (i)
994                 item = track_first_item (tracks[i])
995                 if item and item.__class__ == Note:
996                         s = s + '    \\context Staff=%s \\%s\n' % (track, track)
997                 elif item and item.__class__ == Text:
998                         s = s + '    \\context Lyrics=%s \\%s\n' % (track, track)
999         s = s + '  >>\n}\n'
1000
1001         progress (_ ("%s output to `%s'...") % ('LY', o))
1002
1003         if o == '-':
1004                 h = sys.stdout
1005         else:
1006                 h = open (o, 'w')
1007
1008         h.write (s)
1009         h.close ()
1010
1011
1012 (sh, long) = getopt_args (option_definitions)
1013 try:
1014         (options, files) = getopt.getopt(sys.argv[1:], sh, long)
1015 except getopt.error, s:
1016         errorport.write ('\n')
1017         errorport.write (_ ("error: ") + _ ("getopt says: `%s\'" % s))
1018         errorport.write ('\n')
1019         errorport.write ('\n')
1020         help ()
1021         sys.exit (2)
1022         
1023 for opt in options:     
1024         o = opt[0]
1025         a = opt[1]
1026
1027         if 0:
1028                 pass
1029         elif o == '--help' or o == '-h':
1030                 help ()
1031                 errorport.write ('\n')
1032                 errorport.write (_ ("Example:"))
1033                 errorport.write  (r'''
1034     midi2ly --key=-2:1 --duration-quant=32 \
1035         --allow-tuplet=4*2/3 --allow-tuplet=2*4/3 foo.midi
1036 ''')
1037                 sys.exit (0)
1038         elif o == '--output' or o == '-o':
1039                 output_name = a
1040         elif o == '--verbose' or o == '-V':
1041                 verbose_p = 1
1042         elif o == '--version' or o == '-v':
1043                 identify ()
1044                 sys.exit (0)
1045         elif o == '--warranty' or o == '-w':
1046                 status = system ('lilypond -w', ignore_error = 1)
1047                 if status:
1048                         warranty ()
1049                 sys.exit (0)
1050
1051
1052         elif o == '--absolute-pitches' or o == '-a':
1053                 absolute_p = 1
1054         elif o == '--duration-quant' or o == '-d':
1055                 duration_quant = string.atoi (a)
1056         elif o == '--explicit-durations' or o == '-e':
1057                 explicit_durations_p = 1
1058         elif o == '--key' or o == '-k':
1059                 (alterations, minor) = map (string.atoi, string.split (a + ':0', ':'))[0:2]
1060                 sharps = 0
1061                 flats = 0
1062                 if alterations >= 0:
1063                         sharps = alterations
1064                 else:
1065                         flats = - alterations
1066                 key = Key (sharps, flats, minor)
1067         elif o == '--start-quant' or o == '-s':
1068                 start_quant = string.atoi (a)
1069         elif o == '--allow-tuplet' or o == '-t':
1070                 a = string.replace (a, '/', '*')
1071                 tuplet = map (string.atoi, string.split (a, '*'))
1072                 allowed_tuplets.append (tuplet)
1073         # lots of midi files use plain text for lyric events
1074         elif o == '--text-lyrics' or o == '-x':
1075                 text_lyrics_p = 1
1076
1077
1078 if not files or files[0] == '-':
1079
1080         # FIXME: read from stdin when files[0] = '-'
1081         help ()
1082         errorport.write (program_name + ":" + _ ("error: ") + _ ("no files specified on command line.") + '\n')
1083         sys.exit (2)
1084
1085
1086 for f in files:
1087         g = f
1088         g = strip_extension (g, '.midi')
1089         g = strip_extension (g, '.mid')
1090         g = strip_extension (g, '.MID')
1091         (outdir, outbase) = ('','')
1092
1093         if not output_name:
1094                 outdir = '.'
1095                 outbase = os.path.basename (g)
1096                 o = os.path.join (outdir, outbase + '-midi.ly')
1097         elif output_name[-1] == os.sep:
1098                 outdir = output_name
1099                 outbase = os.path.basename (g)
1100                 os.path.join (outdir, outbase + '-gen.ly')
1101         else:
1102                 o = output_name
1103                 (outdir, outbase) = os.path.split (o)
1104
1105         if outdir != '.' and outdir != '':
1106                 try:
1107                         os.mkdir (outdir, 0777)
1108                 except OSError:
1109                         pass
1110
1111         convert_midi (f, o)
1112