]> git.donarmstrong.com Git - lilypond.git/blob - scripts/midi2ly.py
(print): suicide if we don't have any
[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 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 Duration.compare (self.duration,
303                                         reference_note.duration)):
304                         s = s + self.duration.dump ()
305
306                 reference_note = self
307                 
308                 # TODO: move space
309                 return s + ' '
310
311
312 class Time:
313         def __init__ (self, num, den):
314                 self.clocks = 0
315                 self.num = num
316                 self.den = den
317
318         def bar_clocks (self):
319                 return clocks_per_1 * self.num / self.den
320
321         def __repr__ (self):
322                 return 'Time(%d/%d)' % (self.num, self.den)
323         
324         def dump (self):
325                 global time
326                 time = self
327                 return '\n  ' + '\\time %d/%d ' % (self.num, self.den) + '\n  '
328
329 class Tempo:
330         def __init__ (self, seconds_per_1):
331                 self.clocks = 0
332                 self.seconds_per_1 = seconds_per_1
333
334         def __repr__ (self):
335                 return 'Tempo(%d)' % self.bpm ()
336         
337         def bpm (self):
338                 return 4 * 60 / self.seconds_per_1
339         
340         def dump (self):
341                 return '\n  ' + '\\tempo 4 = %d ' % (self.bpm()) + '\n  '
342
343 class Clef:
344         clefs = ('"bass_8"', 'bass', 'violin', '"violin^8"')
345         def __init__ (self, type):
346                 self.type = type
347
348         def __repr__ (self):
349                 return 'Clef(%s)' % self.clefs[self.type]
350         
351         def dump (self):
352                 return '\n  \\clef %s\n  ' % self.clefs[self.type]
353
354 class Key:
355         key_sharps = ('c', 'g', 'd', 'a', 'e', 'b', 'fis')
356         key_flats = ('BUG', 'f', 'bes', 'es', 'as', 'des', 'ges')
357
358         def __init__ (self, sharps, flats, minor):
359                 self.clocks = 0
360                 self.flats = flats
361                 self.sharps = sharps
362                 self.minor = minor
363
364         def dump (self):
365                 global key
366                 key = self
367
368                 s = ''
369                 if self.sharps and self.flats:
370                         pass
371                 else:
372                         if self.flats:
373                                 k = (ord ('cfbeadg'[self.flats % 7]) - ord ('a') - 2 -2 * self.minor + 7) % 7
374                         else:
375                                 k = (ord ('cgdaebf'[self.sharps % 7]) - ord ('a') - 2 -2 * self.minor + 7) % 7
376   
377                         if not self.minor:
378                                 name = chr ((k + 2) % 7 + ord ('a'))
379                         else:
380                                 name = chr ((k + 2) % 7 + ord ('a'))
381
382                         # fis cis gis dis ais eis bis
383                         sharps = (2, 4, 6, 1, 3, 5, 7)
384                         # bes es as des ges ces fes
385                         flats = (6, 4, 2, 7, 5, 3, 1)
386                         a = 0
387                         if self.flats:
388                                 if flats[k] <= self.flats:
389                                         a = -1
390                         else:
391                                 if sharps[k] <= self.sharps:
392                                         a = 1
393
394                         if a:
395                                 name = name + Note.alteration_names[a + 2]
396
397                         s = '\\key ' + name
398                         if self.minor:
399                                 s = s + ' \\minor'
400                         else:
401                                 s = s + ' \\major'
402
403                 return '\n\n  ' + s + '\n  '
404
405
406 class Text:
407         text_types = (
408                 'SEQUENCE_NUMBER',
409                 'TEXT_EVENT',
410                 'COPYRIGHT_NOTICE',
411                 'SEQUENCE_TRACK_NAME',
412                 'INSTRUMENT_NAME',
413                 'LYRIC',
414                 'MARKER',
415                 'CUE_POINT',)
416         
417         def __init__ (self, type, text):
418                 self.clocks = 0
419                 self.type = type
420                 self.text = text
421
422         def dump (self):
423                 # urg, we should be sure that we're in a lyrics staff
424                 if self.type == midi.LYRIC:
425                         s = '"%s"' % self.text
426                         d = Duration (self.clocks)
427                         if global_options.explicit_durations \
428                            or Duration.compare (d,
429                                                 reference_note.duration):
430                                 s = s + Duration (self.clocks).dump ()
431                         s = s + ' '
432                 else:
433                         s = '\n  % [' + self.text_types[self.type] + '] ' + self.text + '\n  '
434                 return s
435
436         def __repr__ (self):
437                 return 'Text(%d=%s)' % (self.type, self.text)
438
439
440
441 def split_track (track):
442         chs = {}
443         for i in range(16):
444                 chs[i] = []
445                 
446         for e in track:
447                 data = list (e[1])
448                 if data[0] > 0x7f and data[0] < 0xf0:
449                         c = data[0] & 0x0f
450                         e = (e[0], tuple ([data[0] & 0xf0] + data[1:]))
451                         chs[c].append (e)
452                 else:
453                         chs[0].append (e)
454
455         for i in range (16):
456                 if chs[i] == []:
457                         del chs[i]
458
459         threads = []
460         for v in chs.values ():
461                 events = events_on_channel (v)
462                 thread = unthread_notes (events)
463                 if len (thread):
464                         threads.append (thread)
465         return threads
466
467
468 def quantise_clocks (clocks, quant):
469         q = int (clocks / quant) * quant
470         if q != clocks:
471                 for tquant in allowed_tuplet_clocks:
472                         if int (clocks / tquant) * tquant == clocks:
473                                 return clocks
474                 if 2 * (clocks - q) > quant:
475                         q = q + quant
476         return q
477
478 def end_note (pitches, notes, t, e):
479         try:
480                 (lt, vel) = pitches[e]
481                 del pitches[e]
482
483                 i = len (notes) - 1 
484                 while i > 0:
485                         if notes[i][0] > lt:
486                                 i = i -1
487                         else:
488                                 break
489                 d = t - lt
490                 if duration_quant_clocks:
491                         d = quantise_clocks (d, duration_quant_clocks)
492                         if not d:
493                                 d = duration_quant_clocks
494
495                 notes.insert (i + 1,
496                             (lt, Note (d, e, vel)))
497
498         except KeyError:
499                 pass
500
501 def events_on_channel (channel):
502         pitches = {}
503
504         notes = []
505         events = []
506         last_lyric = 0
507         last_time = 0
508         for e in channel:
509                 t = e[0]
510
511                 if start_quant_clocks:
512                         t = quantise_clocks (t, start_quant_clocks)
513
514
515                 if e[1][0] == midi.NOTE_OFF \
516                    or (e[1][0] == midi.NOTE_ON and e[1][2] == 0):
517                         end_note (pitches, notes, t, e[1][1])
518                         
519                 elif e[1][0] == midi.NOTE_ON:
520                         if not pitches.has_key (e[1][1]):
521                                 pitches[e[1][1]] = (t, e[1][2])
522                                 
523                 # all include ALL_NOTES_OFF
524                 elif e[1][0] >= midi.ALL_SOUND_OFF \
525                      and e[1][0] <= midi.POLY_MODE_ON:
526                         for i in pitches.keys ():
527                                 end_note (pitches, notes, t, i)
528                                 
529                 elif e[1][0] == midi.META_EVENT:
530                         if e[1][1] == midi.END_OF_TRACK:
531                                 for i in pitches.keys ():
532                                         end_note (pitches, notes, t, i)
533                                 break
534
535                         elif e[1][1] == midi.SET_TEMPO:
536                                 (u0, u1, u2) = map (ord, e[1][2])
537                                 us_per_4 = u2 + 256 * (u1 + 256 * u0)
538                                 seconds_per_1 = us_per_4 * 4 / 1e6
539                                 events.append ((t, Tempo (seconds_per_1)))
540                         elif e[1][1] == midi.TIME_SIGNATURE:
541                                 (num, dur, clocks4, count32) = map (ord, e[1][2])
542                                 den = 2 ** dur 
543                                 events.append ((t, Time (num, den)))
544                         elif e[1][1] == midi.KEY_SIGNATURE:
545                                 (alterations, minor) = map (ord, e[1][2])
546                                 sharps = 0
547                                 flats = 0
548                                 if alterations < 127:
549                                         sharps = alterations
550                                 else:
551                                         flats = 256 - alterations
552
553                                 k = Key (sharps, flats, minor)
554                                 events.append ((t, k))
555
556                                 # ugh, must set key while parsing
557                                 # because Note init uses key
558                                 # Better do Note.calc () at dump time?
559                                 global key
560                                 key = k
561
562                         elif e[1][1] == midi.LYRIC \
563                              or (global_options.text_lyrics and e[1][1] == midi.TEXT_EVENT):
564                                 if last_lyric:
565                                         last_lyric.clocks = t - last_time
566                                         events.append ((last_time, last_lyric))
567                                 last_time = t
568                                 last_lyric = Text (midi.LYRIC, e[1][2])
569
570                         elif e[1][1] >= midi.SEQUENCE_NUMBER \
571                              and e[1][1] <= midi.CUE_POINT:
572                                 events.append ((t, Text (e[1][1], e[1][2])))
573                         else:
574                                 if global_options.verbose:
575                                         sys.stderr.write ("SKIP: %s\n" % `e`)
576                                 pass
577                 else:
578                         if global_options.verbose:
579                                 sys.stderr.write ("SKIP: %s\n" % `e`)
580                         pass
581
582         if last_lyric:
583                 # last_lyric.clocks = t - last_time
584                 # hmm
585                 last_lyric.clocks = clocks_per_4
586                 events.append ((last_time, last_lyric))
587                 last_lyric = 0
588                 
589         i = 0
590         while len (notes):
591                 if i < len (events) and notes[0][0] >= events[i][0]:
592                         i = i + 1
593                 else:
594                         events.insert (i, notes[0])
595                         del notes[0]
596         return events
597
598 def unthread_notes (channel):
599         threads = []
600         while channel:
601                 thread = []
602                 end_busy_t = 0
603                 start_busy_t = 0
604                 todo = []
605                 for e in channel:
606                         t = e[0]
607                         if e[1].__class__ == Note \
608                            and ((t == start_busy_t \
609                                  and e[1].clocks + t == end_busy_t) \
610                             or t >= end_busy_t):
611                                 thread.append (e)
612                                 start_busy_t = t
613                                 end_busy_t = t + e[1].clocks
614                         elif e[1].__class__ == Time \
615                              or e[1].__class__ == Key \
616                              or e[1].__class__ == Text \
617                              or e[1].__class__ == Tempo:
618                                 thread.append (e)
619                         else:
620                                 todo.append (e)
621                 threads.append (thread)
622                 channel = todo
623
624         return threads
625
626 def gcd (a,b):
627         if b == 0:
628                 return a
629         c = a
630         while c: 
631                 c = a % b
632                 a = b
633                 b = c
634         return a
635         
636 def dump_skip (skip, clocks):
637         return skip + Duration (clocks).dump () + ' '
638
639 def dump (self):
640         return self.dump ()
641
642 def dump_chord (ch):
643         s = ''
644         notes = []
645         for i in ch:
646                 if i.__class__ == Note:
647                         notes.append (i)
648                 else:
649                         s = s + i.dump ()
650         if len (notes) == 1:
651                 s = s + dump (notes[0])
652         elif len (notes) > 1:
653                 global reference_note
654                 s = s + '<'
655                 s = s + notes[0].dump (dump_dur = 0)
656                 r = reference_note
657                 for i in notes[1:]:
658                         s = s + i.dump (dump_dur = 0 )
659                 s = s + '>'
660
661                 s = s + notes[0].duration.dump() + ' '
662                 reference_note = r
663         return s
664
665 def dump_bar_line (last_bar_t, t, bar_count):
666         s = ''
667         bar_t = time.bar_clocks ()
668         if t - last_bar_t >= bar_t:
669                 bar_count = bar_count + (t - last_bar_t) / bar_t
670                 
671                 if t - last_bar_t == bar_t:
672                         s = '|\n  %% %d\n  ' % bar_count
673                         last_bar_t = t
674                 else:
675                         # urg, this will barf at meter changes
676                         last_bar_t = last_bar_t + (t - last_bar_t) / bar_t * bar_t
677                         
678         return (s, last_bar_t, bar_count)
679
680                         
681 def dump_channel (thread, skip):
682         global key, reference_note, time
683
684         key = Key (0, 0, 0)
685         time = Time (4, 4)
686         # urg LilyPond doesn't start at c4, but
687         # remembers from previous tracks!
688         # reference_note = Note (clocks_per_4, 4*12, 0)
689         reference_note = Note (0, 4*12, 0)
690         last_e = None
691         chs = []
692         ch = []
693
694         for e in thread:
695                 if last_e and last_e[0] == e[0]:
696                         ch.append (e[1])
697                 else:
698                         if ch:
699                                 chs.append ((last_e[0], ch))
700                                 
701                         ch = [e[1]]
702                         
703                 last_e = e
704
705         if ch:
706                 chs.append ((last_e[0], ch))
707         t = 0
708         last_t = 0
709         last_bar_t = 0
710         bar_count = 1
711         
712         lines = ['']
713         for ch in chs: 
714                 t = ch[0]
715
716                 i = string.rfind (lines[-1], '\n') + 1
717                 if len (lines[-1][i:]) > LINE_BELL:
718                         lines.append ('')
719                         
720                 if t - last_t > 0:
721                         lines[-1] = lines[-1] + dump_skip (skip, t-last_t)
722                 elif t - last_t < 0:
723                         errorport.write ('BUG: time skew')
724
725                 (s, last_bar_t, bar_count) = dump_bar_line (last_bar_t,
726                                                             t, bar_count)
727                 lines[-1] = lines[-1] + s
728                 
729                 lines[-1] = lines[-1] + dump_chord (ch[1])
730
731                 clocks = 0
732                 for i in ch[1]:
733                         if i.clocks > clocks:
734                                 clocks = i.clocks
735                                 
736                 last_t = t + clocks
737                 
738                 (s, last_bar_t, bar_count) = dump_bar_line (last_bar_t,
739                                                             last_t, bar_count)
740                 lines[-1] = lines[-1] + s
741
742         return string.join (lines, '\n  ') + '\n'
743
744 def track_name (i):
745         return 'track%c' % (i + ord ('A'))
746
747 def channel_name (i):
748         return 'channel%c' % (i + ord ('A'))
749
750 def dump_track (channels, n):
751         s = '\n'
752         track = track_name (n)
753         clef = guess_clef (channels)
754
755         for i in range (len (channels)):
756                 channel = channel_name (i)
757                 item = thread_first_item (channels[i])
758
759                 if item and item.__class__ == Note:
760                         skip = 's'
761                         s = s + '%s = ' % (track + channel)
762                         if not global_options.absolute_pitches:
763                                 s = s + '\\relative c '
764                 elif item and item.__class__ == Text:
765                         skip = '" "'
766                         s = s + '%s = \\lyricmode ' % (track + channel)
767                 else:
768                         skip = '\\skip '
769                         s = s + '%s =  ' % (track + channel)
770                 s = s + '{\n'
771                 s = s + '  ' + dump_channel (channels[i][0], skip)
772                 s = s + '}\n\n'
773
774         s = s + '%s = <<\n' % track
775
776         if clef.type != 2:
777                 s = s + clef.dump () + '\n'
778
779         for i in range (len (channels)):
780                 channel = channel_name (i)
781                 item = thread_first_item (channels[i])
782                 if item and item.__class__ == Text:
783                         s = s + '  \\context Lyrics = %s \\%s\n' % (channel,
784                                                                     track + channel)
785                 else:
786                         s = s + '  \\context Voice = %s \\%s\n' % (channel,
787                                                                    track + channel)
788         s = s + '>>\n\n'
789         return s
790
791 def thread_first_item (thread):
792         for chord in thread:
793                 for event in chord:
794                         if (event[1].__class__ == Note 
795                             or (event[1].__class__ == Text 
796                                 and event[1].type == midi.LYRIC)):
797                                 
798                             return event[1]
799         return None
800
801 def track_first_item (track):
802         for thread in track:
803                 first = thread_first_item (thread)
804                 if first:
805                         return first
806         return None
807
808 def guess_clef (track):
809         i = 0
810         p = 0
811         for thread in track:
812                 for chord in thread:
813                         for event in chord:
814                                 if event[1].__class__ == Note:
815                                         i = i + 1
816                                         p = p + event[1].pitch
817         if i and p / i <= 3*12:
818                 return Clef (0)
819         elif i and p / i <= 5*12:
820                 return Clef (1)
821         elif i and p / i >= 7*12:
822                 return Clef (3)
823         else:
824                 return Clef (2)
825         
826
827 def convert_midi (in_file, out_file):
828         global clocks_per_1, clocks_per_4, key
829         global start_quant_clocks
830         global  duration_quant_clocks
831         global allowed_tuplet_clocks
832
833         str = open (in_file).read ()
834         midi_dump = midi.parse (str)
835         
836         clocks_per_1 = midi_dump[0][1]
837         clocks_per_4 = clocks_per_1 / 4
838         
839         if global_options.start_quant:
840                 start_quant_clocks = clocks_per_1 / global_options.start_quant
841
842         if global_options.duration_quant:
843                 duration_quant_clocks = clocks_per_1 / global_options.duration_quant
844
845         allowed_tuplet_clocks = []
846         for (dur, num, den) in global_options.allowed_tuplets:
847                 allowed_tuplet_clocks.append (clocks_per_1 * num / (dur * den))
848
849         tracks = []
850         for t in midi_dump[1]:
851                 key = Key (0, 0, 0)
852                 tracks.append (split_track (t))
853
854         tag = '%% Lily was here -- automatically converted by %s from %s' % ( program_name, f)
855
856         
857         s = ''
858         s = tag + '\n\\version "2.7.18"\n\n'
859         for i in range (len (tracks)):
860                 s = s + dump_track (tracks[i], i)
861
862         s = s + '\n\\score {\n  <<\n'
863         
864         i = 0
865         for t in tracks:
866                 track = track_name (i)
867                 item = track_first_item (t)
868                 
869                 if item and item.__class__ == Note:
870                         s = s + '    \\context Staff=%s \\%s\n' % (track, track)
871                 elif item and item.__class__ == Text:
872                         s = s + '    \\context Lyrics=%s \\%s\n' % (track, track)
873
874                 i += 1
875         s = s + '  >>\n}\n'
876
877         progress (_ ("%s output to `%s'...") % ('LY', o))
878
879         if o == '-':
880                 handle = sys.stdout
881         else:
882                 handle = open (out_file, 'w')
883
884         handle.write (s)
885         handle.close ()
886
887
888 def get_option_parser ():
889         p = ly.get_option_parser (usage='midi2ly [OPTIONS] FILE',
890                                   version="midi2ly (LilyPond) @TOPLEVEL_VERSION@",
891                                   description=_('''Convert MIDI to LilyPond source.'''))
892
893         p.add_option ('-a', '--absolute-pitches',
894                       action='store_true',
895                       help=_ ("print absolute pitches"))
896         p.add_option ('-d', '--duration-quant',
897                       metavar= _("DUR"),
898                       help=_("quantise note durations on DUR"))
899         p.add_option ('-e', '--explicit-durations',
900                       action='store_true',
901                       help=_ ("print explicit durations"))
902         p.add_option('-k', '--key', help=_ ("set key: ALT=+sharps|-flats; MINOR=1"),
903                      metavar=_ ("ALT[:MINOR]"),
904                      default='0'),
905         p.add_option ('-o', '--output', help=_("write output to FILE"),
906                       metavar=_("FILE"),
907                       action='store')
908         p.add_option ('-s', '--start-quant',help= _ ("quantise note starts on DUR"),
909                       metavar=_ ("DUR"))
910         p.add_option ('-t', '--allow-tuplet',
911                       metavar=_ ("DUR*NUM/DEN"),
912                       action = "append",
913                       dest="allowed_tuplets",
914                       help=_ ("allow tuplet durations DUR*NUM/DEN"),
915                       default=[])
916         p.add_option ('-V', '--verbose', help=_ ("be verbose"),
917                       action='store_true',
918                       ),
919         p.add_option ('-w', '--warranty', help=_ ("show warranty"),
920                       action='store_true',
921                       ),
922         p.add_option ('-x', '--text-lyrics', help=_("treat every text as a lyric"),
923                       action='store_true')
924
925         p.add_option_group (_ ("example"),
926                             description = r'''
927     midi2ly --key=-2:1 --duration-quant=32 \
928         --allow-tuplet=4*2/3 --allow-tuplet=2*4/3 foo.midi
929 ''')
930         
931         p.add_option_group  ('bugs',
932                              description='''Report bugs via http://post.gmane.org/post.php'''
933                              '''?group=gmane.comp.gnu.lilypond.bugs\n''')
934         
935         return p
936
937
938
939 def do_options ():
940         opt_parser = get_option_parser()
941         (options, args) = opt_parser.parse_args ()
942
943         if not args or args[0] == '-':
944                 opt_parser.print_help ()
945                 sys.stderr.write ('\n%s: %s %s\n' % (program_name, _ ("error: "),
946                                                      _ ("no files specified on command line.")))
947                 sys.exit (2)
948
949         if options.duration_quant:
950                 options.duration_quant = string.atoi (options.duration_quant)
951
952         if options.warranty:
953                 warranty ()
954                 sys.exit (0)
955         if 1:
956                 (alterations, minor) = map (string.atoi, string.split (options.key + ':0', ':'))[0:2]
957                 sharps = 0
958                 flats = 0
959                 if alterations >= 0:
960                         sharps = alterations
961                 else:
962                         flats = - alterations
963
964                 options.key = Key (sharps, flats, minor)
965
966                 
967         if options.start_quant:
968                 options.start_quant = string.atoi (a)
969                 
970         options.allowed_tuplets = [map (string.atoi, a.replace ('/','*').split ('*'))
971                                 for a in options.allowed_tuplets]
972         
973         global global_options
974         global_options = options
975
976         return args
977
978
979 files = do_options()
980
981 for f in files:
982         g = f
983         g = strip_extension (g, '.midi')
984         g = strip_extension (g, '.mid')
985         g = strip_extension (g, '.MID')
986         (outdir, outbase) = ('','')
987
988         if not output_name:
989                 outdir = '.'
990                 outbase = os.path.basename (g)
991                 o = os.path.join (outdir, outbase + '-midi.ly')
992         elif output_name[-1] == os.sep:
993                 outdir = output_name
994                 outbase = os.path.basename (g)
995                 os.path.join (outdir, outbase + '-gen.ly')
996         else:
997                 o = output_name
998                 (outdir, outbase) = os.path.split (o)
999
1000         if outdir != '.' and outdir != '':
1001                 try:
1002                         os.mkdir (outdir, 0777)
1003                 except OSError:
1004                         pass
1005
1006         convert_midi (f, o)
1007