]> git.donarmstrong.com Git - lilypond.git/blob - scripts/midi2ly.py
* make/lilypond-vars.make (PYTHONPATH): Add python's outdir to
[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 # convert MIDI to LilyPond source
8 #
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 # If set, LILYPONDPREFIX must take prevalence
35 # if datadir is not set, we're doing a build and LILYPONDPREFIX
36 datadir = '@local_lilypond_datadir@'
37 if not os.path.isdir (datadir):
38         datadir = '@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
44 sys.path.insert (0, os.path.join (datadir, 'python'))
45 ################################################################
46
47 import midi
48
49
50 ################################################################
51 ################ CONSTANTS
52
53
54 output_name = ''
55 LINE_BELL = 60
56 scale_steps = [0,2,4,5,7,9,11]
57
58 clocks_per_1 = 1536
59 clocks_per_4 = 0
60 key = 0
61 time = 0
62 reference_note = 0
63 start_quant = 0
64 start_quant_clocks = 0
65 duration_quant = 0
66 duration_quant_clocks = 0
67 allowed_tuplets = []
68 allowed_tuplet_clocks = []
69 absolute_p = 0
70 explicit_durations_p = 0
71 text_lyrics_p = 0
72
73
74
75 ################################################################
76
77 localedir = '@localedir@'
78 try:
79         import gettext
80         gettext.bindtextdomain ('lilypond', localedir)
81         gettext.textdomain ('lilypond')
82         _ = gettext.gettext
83 except:
84         def _ (s):
85                 return s
86
87 program_name = 'midi2ly'
88 program_version = '@TOPLEVEL_VERSION@'
89
90 errorport = sys.stderr
91 verbose_p = 0
92
93 # temp_dir = os.path.join (original_dir,  '%s.dir' % program_name)
94 # original_dir = os.getcwd ()
95 # keep_temp_dir_p = 0
96
97
98 help_summary = _ ("Convert MIDI to LilyPond source")
99
100 option_definitions = [
101         ('', 'a', 'absolute-pitches', _ ("print absolute pitches")),
102         (_ ("DUR"), 'd', 'duration-quant', _ ("quantise note durations on DUR")),
103         ('', 'e', 'explicit-durations', _ ("print explicit durations")),
104         ('', 'h', 'help', _ ("this help")),
105         (_ ("ALT[:MINOR]"), 'k', 'key', _ ("set key: ALT=+sharps|-flats; MINOR=1")),
106         (_ ("FILE"), 'o', 'output', _ ("write ouput to FILE")),
107         (_ ("DUR"), 's', 'start-quant', _ ("quantise note starts on DUR")),
108         (_ ("DUR*NUM/DEN"), 't', 'allow-tuplet', _ ("allow tuplet durations DUR*NUM/DEN")),
109         ('', 'V', 'verbose', _ ("verbose")),
110         ('', 'v', 'version', _ ("print version number")),
111         ('', 'w', 'warranty', _ ("show warranty and copyright")),
112         ('', 'x', 'text-lyrics', _ ("treat every text as a lyric")),
113         ]
114
115 ################################################################
116 # lilylib.py -- options and stuff
117
118 # source file of the GNU LilyPond music typesetter
119
120 import os
121
122 try:
123         import gettext
124         gettext.bindtextdomain ('lilypond', localedir)
125         gettext.textdomain ('lilypond')
126         _ = gettext.gettext
127 except:
128         def _ (s):
129                 return s
130
131 if program_version == '@' + 'TOPLEVEL_VERSION' + '@':
132         program_version = '1.5.17'
133
134 def identify ():
135         sys.stdout.write ('%s (GNU LilyPond) %s\n' % (program_name, program_version))
136
137 def warranty ():
138         identify ()
139         sys.stdout.write ('\n')
140         sys.stdout.write (_ ('Copyright (c) %s by' % ' 2001--2002'))
141         sys.stdout.write ('\n')
142         sys.stdout.write ('  Han-Wen Nienhuys')
143         sys.stdout.write ('  Jan Nieuwenhuizen')
144         sys.stdout.write ('\n')
145         sys.stdout.write (_ (r'''
146 Distributed under terms of the GNU General Public License. It comes with
147 NO WARRANTY.'''))
148         sys.stdout.write ('\n')
149
150 def progress (s):
151         errorport.write (s + '\n')
152
153 def warning (s):
154         progress (_ ("warning: ") + s)
155                 
156 def error (s):
157
158
159         '''Report the error S.  Exit by raising an exception. Please
160         do not abuse by trying to catch this error. If you do not want
161         a stack trace, write to the output directly.
162
163         RETURN VALUE
164
165         None
166         
167         '''
168         
169         progress (_ ("error: ") + s)
170         raise _ ("Exiting ... ")
171
172 def getopt_args (opts):
173         '''Construct arguments (LONG, SHORT) for getopt from  list of options.'''
174         short = ''
175         long = []
176         for o in opts:
177                 if o[1]:
178                         short = short + o[1]
179                         if o[0]:
180                                 short = short + ':'
181                 if o[2]:
182                         l = o[2]
183                         if o[0]:
184                                 l = l + '='
185                         long.append (l)
186         return (short, long)
187
188 def option_help_str (o):
189         '''Transform one option description (4-tuple ) into neatly formatted string'''
190         sh = '  '       
191         if o[1]:
192                 sh = '-%s' % o[1]
193
194         sep = ' '
195         if o[1] and o[2]:
196                 sep = ','
197                 
198         long = ''
199         if o[2]:
200                 long= '--%s' % o[2]
201
202         arg = ''
203         if o[0]:
204                 if o[2]:
205                         arg = '='
206                 arg = arg + o[0]
207         return '  ' + sh + sep + long + arg
208
209
210 def options_help_str (opts):
211         '''Convert a list of options into a neatly formatted string'''
212         w = 0
213         strs =[]
214         helps = []
215
216         for o in opts:
217                 s = option_help_str (o)
218                 strs.append ((s, o[3]))
219                 if len (s) > w:
220                         w = len (s)
221
222         str = ''
223         for s in strs:
224                 str = str + '%s%s%s\n' % (s[0], ' ' * (w - len(s[0])  + 3), s[1])
225         return str
226
227 def help ():
228         ls = [(_ ("Usage: %s [OPTION]... FILE") % program_name),
229                 ('\n\n'),
230                 (help_summary),
231                 ('\n\n'),
232                 (_ ("Options:")),
233                 ('\n'),
234                 (options_help_str (option_definitions)),
235                 ('\n\n'),
236                 (_ ("Report bugs to %s") % 'bug-lilypond@gnu.org'),
237                 ('\n')]
238         map (sys.stdout.write, ls)
239         
240 def setup_temp ():
241         """
242         Create a temporary directory, and return its name. 
243         """
244         global temp_dir
245         if not keep_temp_dir_p:
246                 temp_dir = tempfile.mktemp (program_name)
247         try:
248                 os.mkdir (temp_dir, 0777)
249         except OSError:
250                 pass
251
252         return temp_dir
253
254
255 def system (cmd, ignore_error = 0):
256         """Run CMD. If IGNORE_ERROR is set, don't complain when CMD returns non zero.
257
258         RETURN VALUE
259
260         Exit status of CMD
261         """
262         
263         if verbose_p:
264                 progress (_ ("Invoking `%s\'") % cmd)
265         st = os.system (cmd)
266         if st:
267                 name = re.match ('[ \t]*([^ \t]*)', cmd).group (1)
268                 msg = name + ': ' + _ ("command exited with value %d") % st
269                 if ignore_error:
270                         warning (msg + ' ' + _ ("(ignored)") + ' ')
271                 else:
272                         error (msg)
273
274         return st
275
276
277 def cleanup_temp ():
278         if not keep_temp_dir_p:
279                 if verbose_p:
280                         progress (_ ("Cleaning %s...") % temp_dir)
281                 shutil.rmtree (temp_dir)
282
283
284 def strip_extension (f, ext):
285         (p, e) = os.path.splitext (f)
286         if e == ext:
287                 e = ''
288         return p + e
289
290 ################################################################
291 # END Library
292 ################################################################
293
294 \f
295
296
297 class Duration:
298         allowed_durs = (1, 2, 4, 8, 16, 32, 64, 128)
299         def __init__ (self, clocks):
300                 self.clocks = clocks
301                 if clocks <= 0:
302                         self.clocks = duration_quant_clocks
303                 (self.dur, self.num, self.den) = self.dur_num_den (clocks)
304                 
305         def dur_num_den (self, clocks):
306                 for i in range (len (allowed_tuplet_clocks)):
307                         if clocks == allowed_tuplet_clocks[i]:
308                                 return allowed_tuplets[i]
309
310                 dur = 0; num = 1; den = 1;
311                 g = gcd (clocks, clocks_per_1)
312                 if g:
313                         (dur, num) = (clocks_per_1 / g, clocks / g)
314                 if not dur in self.allowed_durs:
315                         dur = 4; num = clocks; den = clocks_per_4
316                 return (dur, num, den)
317
318         def dump (self):
319                 if self.den == 1:
320                         if self.num == 1:
321                                 s = '%d' % self.dur
322                         elif self.num == 3 and self.dur != 1:
323                                 s = '%d.' % (self.dur / 2)
324                         else:
325                                 s = '%d*%d' % (self.dur, self.num)
326                 else:
327                         s = '%d*%d/%d' % (self.dur, self.num, self.den)
328                         
329                 global reference_note
330                 reference_note.duration = self
331
332                 return s
333
334         def compare (self, other):
335                 return self.clocks - other.clocks
336
337 def sign (x):
338         if x >= 0:
339                 return 1
340         else:
341                 return -1
342
343 class Note:
344         names = (0, 0, 1, 1, 2, 3, 3, 4, 4, 5, 5, 6)
345         alterations = (0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 1, 0)
346         alteration_names = ('eses', 'es', '', 'is' , 'isis')
347         
348         def __init__ (self, clocks, pitch, velocity):
349                 self.pitch = pitch
350                 self.velocity = velocity
351                 # hmm
352                 self.clocks = clocks
353                 self.duration = Duration (clocks)
354                 (self.octave, self.notename, self.alteration) = self.o_n_a ()
355
356         def o_n_a (self):
357                 # major scale: do-do
358                 # minor scale: la-la  (= + 5) '''
359
360                 n = self.names[(self.pitch) % 12]
361                 a = self.alterations[(self.pitch) % 12]
362
363                 if a and key.flats:
364                         a = - self.alterations[(self.pitch) % 12]
365                         n = (n - a) % 7
366
367                 #  By tradition, all scales now consist of a sequence
368                 #  of 7 notes each with a distinct name, from amongst
369                 #  a b c d e f g.  But, minor scales have a wide
370                 #  second interval at the top - the 'leading note' is
371                 #  sharped. (Why? it just works that way! Anything
372                 #  else doesn't sound as good and isn't as flexible at
373                 #  saying things. In medieval times, scales only had 6
374                 #  notes to avoid this problem - the hexachords.)
375
376                 #  So, the d minor scale is d e f g a b-flat c-sharp d
377                 #  - using d-flat for the leading note would skip the
378                 #  name c and duplicate the name d.  Why isn't c-sharp
379                 #  put in the key signature? Tradition. (It's also
380                 #  supposedly based on the Pythagorean theory of the
381                 #  cycle of fifths, but that really only applies to
382                 #  major scales...)  Anyway, g minor is g a b-flat c d
383                 #  e-flat f-sharp g, and all the other flat minor keys
384                 #  end up with a natural leading note. And there you
385                 #  have it.
386
387                 #  John Sankey <bf250@freenet.carleton.ca>
388                 #
389                 #  Let's also do a-minor: a b c d e f gis a
390                 #
391                 #  --jcn
392
393                 o = self.pitch / 12 - 4
394
395                 if key.minor:
396                         # as -> gis
397                         if key.sharps == 0 and key.flats == 0 \
398                            and n == 5 and a == -1:
399                                 n = 4; a = 1
400                         # des -> cis
401                         elif key.flats == 1 and n == 1 and a == -1:
402                                 n = 0; a = 1
403                         # ges -> fis
404                         elif key.flats == 2 and n == 4 and a == -1:
405                                 n = 3; a = 1
406                         # g -> fisis
407                         elif key.sharps == 5 and n == 4 and a == 0:
408                                 n = 3; a = 2
409                         # d -> cisis
410                         elif key.sharps == 6 and n == 1 and a == 0:
411                                 n = 0; a = 2
412                         # a -> gisis
413                         elif key.sharps == 7 and n == 5 and a == 0:
414                                 n = 4; a = 2
415
416                 # b -> ces
417                 if key.flats >= 6 and n == 6 and a == 0:
418                         n = 0; a = -1; o = o + 1
419                 # e -> fes
420                 if key.flats >= 7 and n == 2 and a == 0:
421                         n = 3; a = -1
422
423                 # f -> eis
424                 if key.sharps >= 3 and n == 3 and a == 0:
425                         n = 2; a = 1
426                 # c -> bis
427                 if key.sharps >= 4 and n == 0 and a == 0:
428                         n = 6; a = 1; o = o - 1
429
430                 return (o, n, a)
431                 
432         def dump (self):
433                 s = chr ((self.notename + 2)  % 7 + ord ('a'))
434                 s = s + self.alteration_names[self.alteration + 2]
435                 if absolute_p:
436                         commas = self.octave
437                 else:
438                         delta = self.pitch - reference_note.pitch
439                         commas = sign (delta) * (abs (delta) / 12)
440                         if ((sign (delta) \
441                              * (self.notename - reference_note.notename) + 7) \
442                             % 7 >= 4) \
443                             or ((self.notename == reference_note.notename) \
444                                 and (abs (delta) > 4) and (abs (delta) < 12)):
445                                 commas = commas + sign (delta)
446                         
447                 if commas > 0:
448                         s = s + "'" * commas
449                 elif commas < 0:
450                         s = s + "," * -commas
451
452                 if explicit_durations_p \
453                    or Duration.compare (self.duration, reference_note.duration):
454                         s = s + self.duration.dump ()
455
456                 global reference_note
457                 reference_note = self
458                 
459                 # TODO: move space
460                 return s + ' '
461
462
463 class Time:
464         def __init__ (self, num, den):
465                 self.clocks = 0
466                 self.num = num
467                 self.den = den
468
469         def bar_clocks (self):
470                 return clocks_per_1 * self.num / self.den
471         
472         def dump (self):
473                 global time
474                 time = self
475                 return '\n  ' + '\\time %d/%d ' % (self.num, self.den) + '\n  '
476
477 class Tempo:
478         def __init__ (self, seconds_per_1):
479                 self.clocks = 0
480                 self.seconds_per_1 = seconds_per_1
481
482         def dump (self):
483                 return '\n  ' + '\\tempo 4 = %d ' % (4 * 60 / self.seconds_per_1) + '\n  '
484
485 class Clef:
486         clefs = ('"bass_8"', 'bass', 'violin', '"violin^8"')
487         def __init__ (self, type):
488                 self.type = type
489                 
490         def dump (self):
491                 return '\n  \\clef %s\n  ' % self.clefs[self.type]
492
493 class Key:
494         key_sharps = ('c', 'g', 'd', 'a', 'e', 'b', 'fis')
495         key_flats = ('BUG', 'f', 'bes', 'es', 'as', 'des', 'ges')
496
497         def __init__ (self, sharps, flats, minor):
498                 self.clocks = 0
499                 self.flats = flats
500                 self.sharps = sharps
501                 self.minor = minor
502
503         def dump (self):
504                 global key
505                 key = self
506
507                 s = ''
508                 if self.sharps and self.flats:
509                         s = '\\keysignature %s ' % 'TODO'
510                 else:
511                         
512                         if self.flats:
513                                 k = (ord ('cfbeadg'[self.flats % 7]) - ord ('a') - 2 -2 * self.minor + 7) % 7
514                         else:
515                                 k = (ord ('cgdaebf'[self.sharps % 7]) - ord ('a') - 2 -2 * self.minor + 7) % 7
516   
517                         if not self.minor:
518                                 name = chr ((k + 2) % 7 + ord ('a'))
519                         else:
520                                 name = chr ((k + 2) % 7 + ord ('a'))
521
522                         # fis cis gis dis ais eis bis
523                         sharps = (2, 4, 6, 1, 3, 5, 7)
524                         # bes es as des ges ces fes
525                         flats = (6, 4, 2, 7, 5, 3, 1)
526                         a = 0
527                         if self.flats:
528                                 if flats[k] <= self.flats:
529                                         a = -1
530                         else:
531                                 if sharps[k] <= self.sharps:
532                                         a = 1
533
534                         if a:
535                                 name = name + Note.alteration_names[a + 2]
536
537                         s = '\\key ' + name
538                         if self.minor:
539                                 s = s + ' \\minor'
540                         else:
541                                 s = s + ' \\major'
542
543                 return '\n\n  ' + s + '\n  '
544
545
546 class Text:
547         text_types = (
548                 'SEQUENCE_NUMBER',
549                 'TEXT_EVENT',
550                 'COPYRIGHT_NOTICE',
551                 'SEQUENCE_TRACK_NAME',
552                 'INSTRUMENT_NAME',
553                 'LYRIC',
554                 'MARKER',
555                 'CUE_POINT',)
556         
557         def __init__ (self, type, text):
558                 self.clocks = 0
559                 self.type = type
560                 self.text = text
561
562         def dump (self):
563                 # urg, we should be sure that we're in a lyrics staff
564                 if self.type == midi.LYRIC:
565                         s = '"%s"' % self.text
566                         d = Duration (self.clocks)
567                         if explicit_durations_p \
568                            or Duration.compare (d,
569                                                 reference_note.duration):
570                                 s = s + Duration (self.clocks).dump ()
571                         s = s + ' '
572                 else:
573                         s = '\n  % [' + self.text_types[self.type] + '] ' + self.text + '\n  '
574                 return s
575
576
577 def split_track (track):
578         chs = {}
579         for i in range(16):
580                 chs[i] = []
581                 
582         for e in track:
583                 data = list (e[1])
584                 if data[0] > 0x7f and data[0] < 0xf0:
585                         c = data[0] & 0x0f
586                         e = (e[0], tuple ([data[0] & 0xf0] + data[1:]))
587                         chs[c].append (e)
588                 else:
589                         chs[0].append (e)
590
591         for i in range (16):
592                 if chs[i] == []:
593                         del chs[i]
594
595         threads = []
596         for v in chs.values ():
597                 events = events_on_channel (v)
598                 thread = unthread_notes (events)
599                 if len (thread):
600                         threads.append (thread)
601         return threads
602
603
604 def quantise_clocks (clocks, quant):
605         q = int (clocks / quant) * quant
606         if q != clocks:
607                 for tquant in allowed_tuplet_clocks:
608                         if int (clocks / tquant) * tquant == clocks:
609                                 return clocks
610                 if 2 * (clocks - q) > quant:
611                         q = q + quant
612         return q
613
614 def end_note (pitches, notes, t, e):
615         try:
616                 (lt, vel) = pitches[e]
617                 del pitches[e]
618
619                 i = len (notes) - 1 
620                 while i > 0:
621                         if notes[i][0] > lt:
622                                 i = i -1
623                         else:
624                                 break
625                 d = t - lt
626                 if duration_quant_clocks:
627                         d = quantise_clocks (d, duration_quant_clocks)
628                         if not d:
629                                 d = duration_quant_clocks
630
631                 notes.insert (i + 1,
632                             (lt, Note (d, e, vel)))
633
634         except KeyError:
635                 pass
636
637 def events_on_channel (channel):
638         pitches = {}
639
640         notes = []
641         events = []
642         last_lyric = 0
643         last_time = 0
644         for e in channel:
645                 t = e[0]
646
647                 if start_quant_clocks:
648                         t = quantise_clocks (t, start_quant_clocks)
649
650
651                 if e[1][0] == midi.NOTE_OFF \
652                    or (e[1][0] == midi.NOTE_ON and e[1][2] == 0):
653                         end_note (pitches, notes, t, e[1][1])
654                         
655                 elif e[1][0] == midi.NOTE_ON:
656                         if not pitches.has_key (e[1][1]):
657                                 pitches[e[1][1]] = (t, e[1][2])
658                                 
659                 # all include ALL_NOTES_OFF
660                 elif e[1][0] >= midi.ALL_SOUND_OFF \
661                      and e[1][0] <= midi.POLY_MODE_ON:
662                         for i in pitches.keys ():
663                                 end_note (pitches, notes, t, i)
664                                 
665                 elif e[1][0] == midi.META_EVENT:
666                         if e[1][1] == midi.END_OF_TRACK:
667                                 for i in pitches.keys ():
668                                         end_note (pitches, notes, t, i)
669                                 break
670
671                         elif e[1][1] == midi.SET_TEMPO:
672                                 (u0, u1, u2) = map (ord, e[1][2])
673                                 us_per_4 = u2 + 256 * (u1 + 256 * u0)
674                                 seconds_per_1 = us_per_4 * 4 / 1e6
675                                 events.append ((t, Tempo (seconds_per_1)))
676                         elif e[1][1] == midi.TIME_SIGNATURE:
677                                 (num, dur, clocks4, count32) = map (ord, e[1][2])
678                                 den = 2 ** dur 
679                                 events.append ((t, Time (num, den)))
680                         elif e[1][1] == midi.KEY_SIGNATURE:
681                                 (alterations, minor) = map (ord, e[1][2])
682                                 sharps = 0
683                                 flats = 0
684                                 if alterations < 127:
685                                         sharps = alterations
686                                 else:
687                                         flats = 256 - alterations
688
689                                 k = Key (sharps, flats, minor)
690                                 events.append ((t, k))
691
692                                 # ugh, must set key while parsing
693                                 # because Note init uses key
694                                 # Better do Note.calc () at dump time?
695                                 global key
696                                 key = k
697
698                         elif e[1][1] == midi.LYRIC \
699                              or (text_lyrics_p and e[1][1] == midi.TEXT_EVENT):
700                                 if last_lyric:
701                                         last_lyric.clocks = t - last_time
702                                         events.append ((last_time, last_lyric))
703                                 last_time = t
704                                 last_lyric = Text (midi.LYRIC, e[1][2])
705
706                         elif e[1][1] >= midi.SEQUENCE_NUMBER \
707                              and e[1][1] <= midi.CUE_POINT:
708                                 events.append ((t, Text (e[1][1], e[1][2])))
709                         else:
710                                 if verbose_p:
711                                         sys.stderr.write ("SKIP: %s\n" % `e`)
712                                 pass
713                 else:
714                         if verbose_p:
715                                 sys.stderr.write ("SKIP: %s\n" % `e`)
716                         pass
717
718         if last_lyric:
719                 # last_lyric.clocks = t - last_time
720                 # hmm
721                 last_lyric.clocks = clocks_per_4
722                 events.append ((last_time, last_lyric))
723                 last_lyric = 0
724                 
725         i = 0
726         while len (notes):
727                 if i < len (events) and notes[0][0] >= events[i][0]:
728                         i = i + 1
729                 else:
730                         events.insert (i, notes[0])
731                         del notes[0]
732         return events
733
734 def unthread_notes (channel):
735         threads = []
736         while channel:
737                 thread = []
738                 end_busy_t = 0
739                 start_busy_t = 0
740                 todo = []
741                 for e in channel:
742                         t = e[0]
743                         if e[1].__class__ == Note \
744                            and ((t == start_busy_t \
745                                  and e[1].clocks + t == end_busy_t) \
746                             or t >= end_busy_t):
747                                 thread.append (e)
748                                 start_busy_t = t
749                                 end_busy_t = t + e[1].clocks
750                         elif e[1].__class__ == Time \
751                              or e[1].__class__ == Key \
752                              or e[1].__class__ == Text \
753                              or e[1].__class__ == Tempo:
754                                 thread.append (e)
755                         else:
756                                 todo.append (e)
757                 threads.append (thread)
758                 channel = todo
759
760         return threads
761
762 def gcd (a,b):
763         if b == 0:
764                 return a
765         c = a
766         while c: 
767                 c = a % b
768                 a = b
769                 b = c
770         return a
771         
772 def dump_skip (skip, clocks):
773         return skip + Duration (clocks).dump () + ' '
774
775 def dump (self):
776         return self.dump ()
777
778 def dump_chord (ch):
779         s = ''
780         notes = []
781         for i in ch:
782                 if i.__class__ == Note:
783                         notes.append (i)
784                 else:
785                         s = s + i.dump ()
786         if len (notes) == 1:
787                 s = s + dump (notes[0])
788         elif len (notes) > 1:
789                 global reference_note
790                 s = s + '<'
791                 s = s + notes[0].dump ()
792                 r = reference_note
793                 for i in notes[1:]:
794                         s = s + i.dump ()
795                 s = s + '>'
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                 global absolute_p
1055                 absolute_p = 1
1056         elif o == '--duration-quant' or o == '-d':
1057                 global duration_quant
1058                 duration_quant = string.atoi (a)
1059         elif o == '--explicit-durations' or o == '-e':
1060                 global explicit_durations_p
1061                 explicit_durations_p = 1
1062         elif o == '--key' or o == '-k':
1063                 (alterations, minor) = map (string.atoi, string.split (a + ':0', ':'))[0:2]
1064                 sharps = 0
1065                 flats = 0
1066                 if alterations >= 0:
1067                         sharps = alterations
1068                 else:
1069                         flats = - alterations
1070                 global key
1071                 key = Key (sharps, flats, minor)
1072         elif o == '--start-quant' or o == '-s':
1073                 start_quant = string.atoi (a)
1074         elif o == '--allow-tuplet' or o == '-t':
1075                 a = string.replace (a, '/', '*')
1076                 tuplet = map (string.atoi, string.split (a, '*'))
1077                 allowed_tuplets.append (tuplet)
1078         # lots of midi files use plain text for lyric events
1079         elif o == '--text-lyrics' or o == '-x':
1080                 text_lyrics_p = 1
1081
1082
1083 if not files or files[0] == '-':
1084
1085         # FIXME: read from stdin when files[0] = '-'
1086         help ()
1087         errorport.write (program_name + ":" + _ ("error: ") + _ ("no files specified on command line.") + '\n')
1088         sys.exit (2)
1089
1090
1091 for f in files:
1092
1093         g = f
1094         g = strip_extension (g, '.midi')
1095         g = strip_extension (g, '.mid')
1096         g = strip_extension (g, '.MID')
1097         (outdir, outbase) = ('','')
1098
1099         if not output_name:
1100                 outdir = '.'
1101                 outbase = os.path.basename (g)
1102                 o = os.path.join (outdir, outbase + '-midi.ly')
1103         elif output_name[-1] == os.sep:
1104                 outdir = output_name
1105                 outbase = os.path.basename (g)
1106                 os.path.join (outdir, outbase + '-gen.ly')
1107         else:
1108                 o = output_name
1109                 (outdir, outbase) = os.path.split (o)
1110
1111         if outdir != '.' and outdir != '':
1112                 try:
1113                         os.mkdir (outdir, 0777)
1114                 except OSError:
1115                         pass
1116
1117         convert_midi (f, o)
1118