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