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