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