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