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