]> git.donarmstrong.com Git - lilypond.git/blob - scripts/midi2ly.py
c61cce84ed610a43b00ca60a74eec4450bfaf886
[lilypond.git] / scripts / midi2ly.py
1 #!@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()