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