]> git.donarmstrong.com Git - lilypond.git/blob - scripts/midi2ly.py
* Merge from stable:
[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--2002  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 # 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                 global reference_note
434                 s = chr ((self.notename + 2)  % 7 + ord ('a'))
435                 s = s + self.alteration_names[self.alteration + 2]
436                 if absolute_p:
437                         commas = self.octave
438                 else:
439                         delta = self.pitch - reference_note.pitch
440                         commas = sign (delta) * (abs (delta) / 12)
441                         if ((sign (delta) \
442                              * (self.notename - reference_note.notename) + 7) \
443                             % 7 >= 4) \
444                             or ((self.notename == reference_note.notename) \
445                                 and (abs (delta) > 4) and (abs (delta) < 12)):
446                                 commas = commas + sign (delta)
447                         
448                 if commas > 0:
449                         s = s + "'" * commas
450                 elif commas < 0:
451                         s = s + "," * -commas
452
453                 if explicit_durations_p \
454                    or Duration.compare (self.duration,
455                                         reference_note.duration):
456                         s = s + self.duration.dump ()
457
458                 reference_note = self
459                 
460                 # TODO: move space
461                 return s + ' '
462
463
464 class Time:
465         def __init__ (self, num, den):
466                 self.clocks = 0
467                 self.num = num
468                 self.den = den
469
470         def bar_clocks (self):
471                 return clocks_per_1 * self.num / self.den
472         
473         def dump (self):
474                 global time
475                 time = self
476                 return '\n  ' + '\\time %d/%d ' % (self.num, self.den) + '\n  '
477
478 class Tempo:
479         def __init__ (self, seconds_per_1):
480                 self.clocks = 0
481                 self.seconds_per_1 = seconds_per_1
482
483         def dump (self):
484                 return '\n  ' + '\\tempo 4 = %d ' % (4 * 60 / self.seconds_per_1) + '\n  '
485
486 class Clef:
487         clefs = ('"bass_8"', 'bass', 'violin', '"violin^8"')
488         def __init__ (self, type):
489                 self.type = type
490                 
491         def dump (self):
492                 return '\n  \\clef %s\n  ' % self.clefs[self.type]
493
494 class Key:
495         key_sharps = ('c', 'g', 'd', 'a', 'e', 'b', 'fis')
496         key_flats = ('BUG', 'f', 'bes', 'es', 'as', 'des', 'ges')
497
498         def __init__ (self, sharps, flats, minor):
499                 self.clocks = 0
500                 self.flats = flats
501                 self.sharps = sharps
502                 self.minor = minor
503
504         def dump (self):
505                 global key
506                 key = self
507
508                 s = ''
509                 if self.sharps and self.flats:
510                         s = '\\keysignature %s ' % 'TODO'
511                 else:
512                         
513                         if self.flats:
514                                 k = (ord ('cfbeadg'[self.flats % 7]) - ord ('a') - 2 -2 * self.minor + 7) % 7
515                         else:
516                                 k = (ord ('cgdaebf'[self.sharps % 7]) - ord ('a') - 2 -2 * self.minor + 7) % 7
517   
518                         if not self.minor:
519                                 name = chr ((k + 2) % 7 + ord ('a'))
520                         else:
521                                 name = chr ((k + 2) % 7 + ord ('a'))
522
523                         # fis cis gis dis ais eis bis
524                         sharps = (2, 4, 6, 1, 3, 5, 7)
525                         # bes es as des ges ces fes
526                         flats = (6, 4, 2, 7, 5, 3, 1)
527                         a = 0
528                         if self.flats:
529                                 if flats[k] <= self.flats:
530                                         a = -1
531                         else:
532                                 if sharps[k] <= self.sharps:
533                                         a = 1
534
535                         if a:
536                                 name = name + Note.alteration_names[a + 2]
537
538                         s = '\\key ' + name
539                         if self.minor:
540                                 s = s + ' \\minor'
541                         else:
542                                 s = s + ' \\major'
543
544                 return '\n\n  ' + s + '\n  '
545
546
547 class Text:
548         text_types = (
549                 'SEQUENCE_NUMBER',
550                 'TEXT_EVENT',
551                 'COPYRIGHT_NOTICE',
552                 'SEQUENCE_TRACK_NAME',
553                 'INSTRUMENT_NAME',
554                 'LYRIC',
555                 'MARKER',
556                 'CUE_POINT',)
557         
558         def __init__ (self, type, text):
559                 self.clocks = 0
560                 self.type = type
561                 self.text = text
562
563         def dump (self):
564                 # urg, we should be sure that we're in a lyrics staff
565                 if self.type == midi.LYRIC:
566                         s = '"%s"' % self.text
567                         d = Duration (self.clocks)
568                         if explicit_durations_p \
569                            or Duration.compare (d,
570                                                 reference_note.duration):
571                                 s = s + Duration (self.clocks).dump ()
572                         s = s + ' '
573                 else:
574                         s = '\n  % [' + self.text_types[self.type] + '] ' + self.text + '\n  '
575                 return s
576
577
578 def split_track (track):
579         chs = {}
580         for i in range(16):
581                 chs[i] = []
582                 
583         for e in track:
584                 data = list (e[1])
585                 if data[0] > 0x7f and data[0] < 0xf0:
586                         c = data[0] & 0x0f
587                         e = (e[0], tuple ([data[0] & 0xf0] + data[1:]))
588                         chs[c].append (e)
589                 else:
590                         chs[0].append (e)
591
592         for i in range (16):
593                 if chs[i] == []:
594                         del chs[i]
595
596         threads = []
597         for v in chs.values ():
598                 events = events_on_channel (v)
599                 thread = unthread_notes (events)
600                 if len (thread):
601                         threads.append (thread)
602         return threads
603
604
605 def quantise_clocks (clocks, quant):
606         q = int (clocks / quant) * quant
607         if q != clocks:
608                 for tquant in allowed_tuplet_clocks:
609                         if int (clocks / tquant) * tquant == clocks:
610                                 return clocks
611                 if 2 * (clocks - q) > quant:
612                         q = q + quant
613         return q
614
615 def end_note (pitches, notes, t, e):
616         try:
617                 (lt, vel) = pitches[e]
618                 del pitches[e]
619
620                 i = len (notes) - 1 
621                 while i > 0:
622                         if notes[i][0] > lt:
623                                 i = i -1
624                         else:
625                                 break
626                 d = t - lt
627                 if duration_quant_clocks:
628                         d = quantise_clocks (d, duration_quant_clocks)
629                         if not d:
630                                 d = duration_quant_clocks
631
632                 notes.insert (i + 1,
633                             (lt, Note (d, e, vel)))
634
635         except KeyError:
636                 pass
637
638 def events_on_channel (channel):
639         pitches = {}
640
641         notes = []
642         events = []
643         last_lyric = 0
644         last_time = 0
645         for e in channel:
646                 t = e[0]
647
648                 if start_quant_clocks:
649                         t = quantise_clocks (t, start_quant_clocks)
650
651
652                 if e[1][0] == midi.NOTE_OFF \
653                    or (e[1][0] == midi.NOTE_ON and e[1][2] == 0):
654                         end_note (pitches, notes, t, e[1][1])
655                         
656                 elif e[1][0] == midi.NOTE_ON:
657                         if not pitches.has_key (e[1][1]):
658                                 pitches[e[1][1]] = (t, e[1][2])
659                                 
660                 # all include ALL_NOTES_OFF
661                 elif e[1][0] >= midi.ALL_SOUND_OFF \
662                      and e[1][0] <= midi.POLY_MODE_ON:
663                         for i in pitches.keys ():
664                                 end_note (pitches, notes, t, i)
665                                 
666                 elif e[1][0] == midi.META_EVENT:
667                         if e[1][1] == midi.END_OF_TRACK:
668                                 for i in pitches.keys ():
669                                         end_note (pitches, notes, t, i)
670                                 break
671
672                         elif e[1][1] == midi.SET_TEMPO:
673                                 (u0, u1, u2) = map (ord, e[1][2])
674                                 us_per_4 = u2 + 256 * (u1 + 256 * u0)
675                                 seconds_per_1 = us_per_4 * 4 / 1e6
676                                 events.append ((t, Tempo (seconds_per_1)))
677                         elif e[1][1] == midi.TIME_SIGNATURE:
678                                 (num, dur, clocks4, count32) = map (ord, e[1][2])
679                                 den = 2 ** dur 
680                                 events.append ((t, Time (num, den)))
681                         elif e[1][1] == midi.KEY_SIGNATURE:
682                                 (alterations, minor) = map (ord, e[1][2])
683                                 sharps = 0
684                                 flats = 0
685                                 if alterations < 127:
686                                         sharps = alterations
687                                 else:
688                                         flats = 256 - alterations
689
690                                 k = Key (sharps, flats, minor)
691                                 events.append ((t, k))
692
693                                 # ugh, must set key while parsing
694                                 # because Note init uses key
695                                 # Better do Note.calc () at dump time?
696                                 global key
697                                 key = k
698
699                         elif e[1][1] == midi.LYRIC \
700                              or (text_lyrics_p and e[1][1] == midi.TEXT_EVENT):
701                                 if last_lyric:
702                                         last_lyric.clocks = t - last_time
703                                         events.append ((last_time, last_lyric))
704                                 last_time = t
705                                 last_lyric = Text (midi.LYRIC, e[1][2])
706
707                         elif e[1][1] >= midi.SEQUENCE_NUMBER \
708                              and e[1][1] <= midi.CUE_POINT:
709                                 events.append ((t, Text (e[1][1], e[1][2])))
710                         else:
711                                 if verbose_p:
712                                         sys.stderr.write ("SKIP: %s\n" % `e`)
713                                 pass
714                 else:
715                         if verbose_p:
716                                 sys.stderr.write ("SKIP: %s\n" % `e`)
717                         pass
718
719         if last_lyric:
720                 # last_lyric.clocks = t - last_time
721                 # hmm
722                 last_lyric.clocks = clocks_per_4
723                 events.append ((last_time, last_lyric))
724                 last_lyric = 0
725                 
726         i = 0
727         while len (notes):
728                 if i < len (events) and notes[0][0] >= events[i][0]:
729                         i = i + 1
730                 else:
731                         events.insert (i, notes[0])
732                         del notes[0]
733         return events
734
735 def unthread_notes (channel):
736         threads = []
737         while channel:
738                 thread = []
739                 end_busy_t = 0
740                 start_busy_t = 0
741                 todo = []
742                 for e in channel:
743                         t = e[0]
744                         if e[1].__class__ == Note \
745                            and ((t == start_busy_t \
746                                  and e[1].clocks + t == end_busy_t) \
747                             or t >= end_busy_t):
748                                 thread.append (e)
749                                 start_busy_t = t
750                                 end_busy_t = t + e[1].clocks
751                         elif e[1].__class__ == Time \
752                              or e[1].__class__ == Key \
753                              or e[1].__class__ == Text \
754                              or e[1].__class__ == Tempo:
755                                 thread.append (e)
756                         else:
757                                 todo.append (e)
758                 threads.append (thread)
759                 channel = todo
760
761         return threads
762
763 def gcd (a,b):
764         if b == 0:
765                 return a
766         c = a
767         while c: 
768                 c = a % b
769                 a = b
770                 b = c
771         return a
772         
773 def dump_skip (skip, clocks):
774         return skip + Duration (clocks).dump () + ' '
775
776 def dump (self):
777         return self.dump ()
778
779 def dump_chord (ch):
780         s = ''
781         notes = []
782         for i in ch:
783                 if i.__class__ == Note:
784                         notes.append (i)
785                 else:
786                         s = s + i.dump ()
787         if len (notes) == 1:
788                 s = s + dump (notes[0])
789         elif len (notes) > 1:
790                 global reference_note
791                 s = s + '<'
792                 s = s + notes[0].dump ()
793                 r = reference_note
794                 for i in notes[1:]:
795                         s = s + i.dump ()
796                 s = s + '>'
797                 reference_note = r
798         return s
799
800 def dump_bar_line (last_bar_t, t, bar_count):
801         s = ''
802         bar_t = time.bar_clocks ()
803         if t - last_bar_t >= bar_t:
804                 bar_count = bar_count + (t - last_bar_t) / bar_t
805                 
806                 if t - last_bar_t == bar_t:
807                         s = '|\n  %% %d\n  ' % bar_count
808                         last_bar_t = t
809                 else:
810                         # urg, this will barf at meter changes
811                         last_bar_t = last_bar_t + (t - last_bar_t) / bar_t * bar_t
812                         
813         return (s, last_bar_t, bar_count)
814
815                         
816 def dump_channel (thread, skip):
817         global key, reference_note, time
818
819         key = Key (0, 0, 0)
820         time = Time (4, 4)
821         # urg LilyPond doesn't start at c4, but
822         # remembers from previous tracks!
823         # reference_note = Note (clocks_per_4, 4*12, 0)
824         reference_note = Note (0, 4*12, 0)
825         last_e = None
826         chs = []
827         ch = []
828
829         for e in thread:
830                 if last_e and last_e[0] == e[0]:
831                         ch.append (e[1])
832                 else:
833                         if ch:
834                                 chs.append ((last_e[0], ch))
835                                 
836                         ch = [e[1]]
837                         
838                 last_e = e
839
840         if ch:
841                 chs.append ((last_e[0], ch))
842         t = 0
843         last_t = 0
844         last_bar_t = 0
845         bar_count = 1
846         
847         lines = ['']
848         for ch in chs: 
849                 t = ch[0]
850
851                 i = string.rfind (lines[-1], '\n') + 1
852                 if len (lines[-1][i:]) > LINE_BELL:
853                         lines.append ('')
854                         
855                 if t - last_t > 0:
856                         lines[-1] = lines[-1] + dump_skip (skip, t-last_t)
857                 elif t - last_t < 0:
858                         errorport.write ('BUG: time skew')
859
860                 (s, last_bar_t, bar_count) = dump_bar_line (last_bar_t,
861                                                             t, bar_count)
862                 lines[-1] = lines[-1] + s
863                 
864                 lines[-1] = lines[-1] + dump_chord (ch[1])
865
866                 clocks = 0
867                 for i in ch[1]:
868                         if i.clocks > clocks:
869                                 clocks = i.clocks
870                                 
871                 last_t = t + clocks
872                 
873                 (s, last_bar_t, bar_count) = dump_bar_line (last_bar_t,
874                                                             last_t, bar_count)
875                 lines[-1] = lines[-1] + s
876
877         return string.join (lines, '\n  ') + '\n'
878
879 def track_name (i):
880         return 'track%c' % (i + ord ('A'))
881
882 def channel_name (i):
883         return 'channel%c' % (i + ord ('A'))
884
885 def dump_track (channels, n):
886         s = '\n'
887         track = track_name (n)
888         clef = guess_clef (channels)
889
890         for i in range (len (channels)):
891                 channel = channel_name (i)
892                 item = thread_first_item (channels[i])
893
894                 if item and item.__class__ == Note:
895                         skip = 's'
896                         s = s + '%s = \\notes' % (track + channel)
897                         if not absolute_p:
898                                 s = s + '\\relative c '
899                 elif item and item.__class__ == Text:
900                         skip = '" "'
901                         s = s + '%s = \\lyrics ' % (track + channel)
902                 else:
903                         skip = '\\skip '
904                         # must be in \notes mode for parsing \skip
905                         s = s + '%s = \\notes ' % (track + channel)
906                 s = s + '{\n'
907                 s = s + '  ' + dump_channel (channels[i][0], skip)
908                 s = s + '}\n\n'
909
910         s = s + '%s = <\n' % track
911
912         if clef.type != 2:
913                 s = s + clef.dump () + '\n'
914
915         for i in range (len (channels)):
916                 channel = channel_name (i)
917                 item = thread_first_item (channels[i])
918                 if item and item.__class__ == Text:
919                         s = s + '  \\context Lyrics = %s \\%s\n' % (channel,
920                                                                     track + channel)
921                 else:
922                         s = s + '  \\context Voice = %s \\%s\n' % (channel,
923                                                                    track + channel)
924         s = s + '>\n\n'
925         return s
926
927 def thread_first_item (thread):
928         for chord in thread:
929                 for event in chord:
930                         if event[1].__class__ == Note \
931                            or (event[1].__class__ == Text \
932                                and event[1].type == midi.LYRIC):
933                                 return event[1]
934         return 0
935
936 def track_first_item (track):
937         for thread in track:
938                 return thread_first_item (thread)
939
940 def guess_clef (track):
941         i = 0
942         p = 0
943         for thread in track:
944                 for chord in thread:
945                         for event in chord:
946                                 if event[1].__class__ == Note:
947                                         i = i + 1
948                                         p = p + event[1].pitch
949         if i and p / i <= 3*12:
950                 return Clef (0)
951         elif i and p / i <= 5*12:
952                 return Clef (1)
953         elif i and p / i >= 7*12:
954                 return Clef (3)
955         else:
956                 return Clef (2)
957         
958
959 def convert_midi (f, o):
960         global clocks_per_1, clocks_per_4, key
961
962         str = open (f).read ()
963         midi_dump = midi.parse (str)
964
965         clocks_per_1 = midi_dump[0][1]
966         clocks_per_4 = clocks_per_1 / 4
967         
968         global start_quant, start_quant_clocks
969         if start_quant:
970                 start_quant_clocks = clocks_per_1 / start_quant
971
972         global duration_quant, duration_quant_clocks
973         if duration_quant:
974                 duration_quant_clocks = clocks_per_1 / duration_quant
975
976         global allowed_tuplet_clocks
977         allowed_tuplet_clocks = []
978         for (dur, num, den) in allowed_tuplets:
979                 allowed_tuplet_clocks.append (clocks_per_1 * num / (dur * den))
980
981         tracks = []
982         for t in midi_dump[1]:
983                 key = Key (0, 0, 0)
984                 tracks.append (split_track (t))
985
986         tag = '%% Lily was here -- automatically converted by %s from %s' % ( program_name, f)
987
988         s = ''
989         s = tag + '\n\n'
990         for i in range (len (tracks)):
991                 s = s + dump_track (tracks[i], i)
992
993         s = s + '\n\\score {\n  <\n'
994         for i in range (len (tracks)):
995                 track = track_name (i)
996                 item = track_first_item (tracks[i])
997                 if item and item.__class__ == Note:
998                         s = s + '    \\context Staff=%s \\%s\n' % (track, track)
999                 elif item and item.__class__ == Text:
1000                         s = s + '    \\context Lyrics=%s \\%s\n' % (track, track)
1001         s = s + '  >\n}\n'
1002
1003         progress (_ ("%s output to `%s'...") % ('LY', o))
1004
1005         if o == '-':
1006                 h = sys.stdout
1007         else:
1008                 h = open (o, 'w')
1009
1010         h.write (s)
1011         h.close ()
1012
1013
1014 (sh, long) = getopt_args (option_definitions)
1015 try:
1016         (options, files) = getopt.getopt(sys.argv[1:], sh, long)
1017 except getopt.error, s:
1018         errorport.write ('\n')
1019         errorport.write (_ ("error: ") + _ ("getopt says: `%s\'" % s))
1020         errorport.write ('\n')
1021         errorport.write ('\n')
1022         help ()
1023         sys.exit (2)
1024         
1025 for opt in options:     
1026         o = opt[0]
1027         a = opt[1]
1028
1029         if 0:
1030                 pass
1031         elif o == '--help' or o == '-h':
1032                 help ()
1033                 errorport.write ('\n')
1034                 errorport.write (_ ("Example:"))
1035                 errorport.write  (r'''
1036     midi2ly --key=-2:1 --duration-quant=32 \
1037         --allow-tuplet=4*2/3 --allow-tuplet=2*4/3 foo.midi
1038 ''')
1039                 sys.exit (0)
1040         elif o == '--output' or o == '-o':
1041                 output_name = a
1042         elif o == '--verbose' or o == '-V':
1043                 verbose_p = 1
1044         elif o == '--version' or o == '-v':
1045                 identify ()
1046                 sys.exit (0)
1047         elif o == '--warranty' or o == '-w':
1048                 status = system ('lilypond -w', ignore_error = 1)
1049                 if status:
1050                         warranty ()
1051                 sys.exit (0)
1052
1053
1054         elif o == '--absolute-pitches' or o == '-a':
1055                 absolute_p = 1
1056         elif o == '--duration-quant' or o == '-d':
1057                 duration_quant = string.atoi (a)
1058         elif o == '--explicit-durations' or o == '-e':
1059                 explicit_durations_p = 1
1060         elif o == '--key' or o == '-k':
1061                 (alterations, minor) = map (string.atoi, string.split (a + ':0', ':'))[0:2]
1062                 sharps = 0
1063                 flats = 0
1064                 if alterations >= 0:
1065                         sharps = alterations
1066                 else:
1067                         flats = - alterations
1068                 key = Key (sharps, flats, minor)
1069         elif o == '--start-quant' or o == '-s':
1070                 start_quant = string.atoi (a)
1071         elif o == '--allow-tuplet' or o == '-t':
1072                 a = string.replace (a, '/', '*')
1073                 tuplet = map (string.atoi, string.split (a, '*'))
1074                 allowed_tuplets.append (tuplet)
1075         # lots of midi files use plain text for lyric events
1076         elif o == '--text-lyrics' or o == '-x':
1077                 text_lyrics_p = 1
1078
1079
1080 if not files or files[0] == '-':
1081
1082         # FIXME: read from stdin when files[0] = '-'
1083         help ()
1084         errorport.write (program_name + ":" + _ ("error: ") + _ ("no files specified on command line.") + '\n')
1085         sys.exit (2)
1086
1087
1088 for f in files:
1089
1090         g = f
1091         g = strip_extension (g, '.midi')
1092         g = strip_extension (g, '.mid')
1093         g = strip_extension (g, '.MID')
1094         (outdir, outbase) = ('','')
1095
1096         if not output_name:
1097                 outdir = '.'
1098                 outbase = os.path.basename (g)
1099                 o = os.path.join (outdir, outbase + '-midi.ly')
1100         elif output_name[-1] == os.sep:
1101                 outdir = output_name
1102                 outbase = os.path.basename (g)
1103                 os.path.join (outdir, outbase + '-gen.ly')
1104         else:
1105                 o = output_name
1106                 (outdir, outbase) = os.path.split (o)
1107
1108         if outdir != '.' and outdir != '':
1109                 try:
1110                         os.mkdir (outdir, 0777)
1111                 except OSError:
1112                         pass
1113
1114         convert_midi (f, o)
1115