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