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