]> git.donarmstrong.com Git - lilypond.git/blob - scripts/midi2ly.py
update.
[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--2005  Han-Wen Nienhuys <hanwen@cs.uu.nl>
8 #                 Jan Nieuwenhuizen <janneke@gnu.org>
9
10
11 '''
12 TODO:
13    * test on weird and unquantised midi input (lily-devel)
14    * update doc and manpage
15
16    * simply insert clef changes whenever too many ledger lines
17      [to avoid tex capacity exceeded]
18    * do not ever quant skips
19    * better lyrics handling
20    * [see if it is feasible to] move ly-classes to library for use in
21      other converters, while leaving midi specific stuff here
22 '''
23
24 import getopt
25 import os
26 import string
27 import sys
28
29
30 ################################################################
31 # Users of python modules should include this snippet.
32 #
33 # This soon to be removed for: import lilypond.lilylib as ly
34 libdir = '@local_lilypond_libdir@'
35 if not os.path.isdir (libdir):
36        libdir = '@lilypond_libdir@'
37 sys.path.insert (0, os.path.join (libdir, 'python'))
38
39
40 ################################################################
41
42 import midi
43
44
45 ################################################################
46 ################ CONSTANTS
47
48
49 output_name = ''
50 LINE_BELL = 60
51 scale_steps = [0,2,4,5,7,9,11]
52
53 clocks_per_1 = 1536
54 clocks_per_4 = 0
55 key = 0
56 time = 0
57 reference_note = 0
58 start_quant = 0
59 start_quant_clocks = 0
60 duration_quant = 0
61 duration_quant_clocks = 0
62 allowed_tuplets = []
63 allowed_tuplet_clocks = []
64 absolute_p = 0
65 explicit_durations_p = 0
66 text_lyrics_p = 0
67
68
69
70 ################################################################
71
72 localedir = '@localedir@'
73 try:
74         import gettext
75         gettext.bindtextdomain ('lilypond', localedir)
76         gettext.textdomain ('lilypond')
77         _ = gettext.gettext
78 except:
79         def _ (s):
80                 return s
81
82 program_name = sys.argv[0]
83 program_version = '@TOPLEVEL_VERSION@'
84
85 errorport = sys.stderr
86 verbose_p = 0
87
88 # temp_dir = os.path.join (original_dir,  '%s.dir' % program_name)
89 # original_dir = os.getcwd ()
90 # keep_temp_dir_p = 0
91
92
93 help_summary = _ ("Convert MIDI to LilyPond source.")
94
95 option_definitions = [
96         ('', 'a', 'absolute-pitches', _ ("print absolute pitches")),
97         (_ ("DUR"), 'd', 'duration-quant', _ ("quantise note durations on DUR")),
98         ('', 'e', 'explicit-durations', _ ("print explicit durations")),
99         ('', 'h', 'help', _ ("print this help")),
100         (_ ("ALT[:MINOR]"), 'k', 'key', _ ("set key: ALT=+sharps|-flats; MINOR=1")),
101         (_ ("FILE"), 'o', 'output', _ ("write output to FILE")),
102         (_ ("DUR"), 's', 'start-quant', _ ("quantise note starts on DUR")),
103         (_ ("DUR*NUM/DEN"), 't', 'allow-tuplet', _ ("allow tuplet durations DUR*NUM/DEN")),
104         ('', 'V', 'verbose', _ ("be verbose")),
105         ('', 'v', 'version', _ ("print version number")),
106         ('', 'w', 'warranty', _ ("show warranty and copyright")),
107         ('', 'x', 'text-lyrics', _ ("treat every text as a lyric")),
108         ]
109
110 ################################################################
111 # lilylib.py -- options and stuff
112
113 # source file of the GNU LilyPond music typesetter
114
115 import os
116
117 try:
118         import gettext
119         gettext.bindtextdomain ('lilypond', localedir)
120         gettext.textdomain ('lilypond')
121         _ = gettext.gettext
122 except:
123         def _ (s):
124                 return s
125
126 if program_version == '@' + 'TOPLEVEL_VERSION' + '@':
127         program_version = '1.5.17'
128
129 def identify ():
130         sys.stdout.write ('%s (GNU LilyPond) %s\n' % (program_name, program_version))
131
132 def warranty ():
133         identify ()
134         sys.stdout.write ('\n')
135         sys.stdout.write (_ ('Copyright (c) %s by') % ' 2001--2005')
136         sys.stdout.write ('\n')
137         sys.stdout.write ('  Han-Wen Nienhuys')
138         sys.stdout.write ('  Jan Nieuwenhuizen')
139         sys.stdout.write ('\n\n')
140         sys.stdout.write (_ ("Distributed under terms of the GNU General Public License."))
141         sys.stdout.write (_ ("It comes with 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 [OPTIONS]... 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, dump_dur = 1):
427                 global reference_note
428                 s = chr ((self.notename + 2)  % 7 + ord ('a'))
429                 s = s + self.alteration_names[self.alteration + 2]
430                 if absolute_p:
431                         commas = self.octave
432                 else:
433                         delta = self.pitch - reference_note.pitch
434                         commas = sign (delta) * (abs (delta) / 12)
435                         if ((sign (delta) \
436                              * (self.notename - reference_note.notename) + 7) \
437                             % 7 >= 4) \
438                             or ((self.notename == reference_note.notename) \
439                                 and (abs (delta) > 4) and (abs (delta) < 12)):
440                                 commas = commas + sign (delta)
441                         
442                 if commas > 0:
443                         s = s + "'" * commas
444                 elif commas < 0:
445                         s = s + "," * -commas
446
447                 ## FIXME: compile fix --jcn
448                 if dump_dur and (explicit_durations_p \
449                    or Duration.compare (self.duration,
450                                         reference_note.duration)):
451                         s = s + self.duration.dump ()
452
453                 reference_note = self
454                 
455                 # TODO: move space
456                 return s + ' '
457
458
459 class Time:
460         def __init__ (self, num, den):
461                 self.clocks = 0
462                 self.num = num
463                 self.den = den
464
465         def bar_clocks (self):
466                 return clocks_per_1 * self.num / self.den
467         
468         def dump (self):
469                 global time
470                 time = self
471                 return '\n  ' + '\\time %d/%d ' % (self.num, self.den) + '\n  '
472
473 class Tempo:
474         def __init__ (self, seconds_per_1):
475                 self.clocks = 0
476                 self.seconds_per_1 = seconds_per_1
477
478         def dump (self):
479                 return '\n  ' + '\\tempo 4 = %d ' % (4 * 60 / self.seconds_per_1) + '\n  '
480
481 class Clef:
482         clefs = ('"bass_8"', 'bass', 'violin', '"violin^8"')
483         def __init__ (self, type):
484                 self.type = type
485                 
486         def dump (self):
487                 return '\n  \\clef %s\n  ' % self.clefs[self.type]
488
489 class Key:
490         key_sharps = ('c', 'g', 'd', 'a', 'e', 'b', 'fis')
491         key_flats = ('BUG', 'f', 'bes', 'es', 'as', 'des', 'ges')
492
493         def __init__ (self, sharps, flats, minor):
494                 self.clocks = 0
495                 self.flats = flats
496                 self.sharps = sharps
497                 self.minor = minor
498
499         def dump (self):
500                 global key
501                 key = self
502
503                 s = ''
504                 if self.sharps and self.flats:
505                         pass
506                 else:
507                         if self.flats:
508                                 k = (ord ('cfbeadg'[self.flats % 7]) - ord ('a') - 2 -2 * self.minor + 7) % 7
509                         else:
510                                 k = (ord ('cgdaebf'[self.sharps % 7]) - ord ('a') - 2 -2 * self.minor + 7) % 7
511   
512                         if not self.minor:
513                                 name = chr ((k + 2) % 7 + ord ('a'))
514                         else:
515                                 name = chr ((k + 2) % 7 + ord ('a'))
516
517                         # fis cis gis dis ais eis bis
518                         sharps = (2, 4, 6, 1, 3, 5, 7)
519                         # bes es as des ges ces fes
520                         flats = (6, 4, 2, 7, 5, 3, 1)
521                         a = 0
522                         if self.flats:
523                                 if flats[k] <= self.flats:
524                                         a = -1
525                         else:
526                                 if sharps[k] <= self.sharps:
527                                         a = 1
528
529                         if a:
530                                 name = name + Note.alteration_names[a + 2]
531
532                         s = '\\key ' + name
533                         if self.minor:
534                                 s = s + ' \\minor'
535                         else:
536                                 s = s + ' \\major'
537
538                 return '\n\n  ' + s + '\n  '
539
540
541 class Text:
542         text_types = (
543                 'SEQUENCE_NUMBER',
544                 'TEXT_EVENT',
545                 'COPYRIGHT_NOTICE',
546                 'SEQUENCE_TRACK_NAME',
547                 'INSTRUMENT_NAME',
548                 'LYRIC',
549                 'MARKER',
550                 'CUE_POINT',)
551         
552         def __init__ (self, type, text):
553                 self.clocks = 0
554                 self.type = type
555                 self.text = text
556
557         def dump (self):
558                 # urg, we should be sure that we're in a lyrics staff
559                 if self.type == midi.LYRIC:
560                         s = '"%s"' % self.text
561                         d = Duration (self.clocks)
562                         if explicit_durations_p \
563                            or Duration.compare (d,
564                                                 reference_note.duration):
565                                 s = s + Duration (self.clocks).dump ()
566                         s = s + ' '
567                 else:
568                         s = '\n  % [' + self.text_types[self.type] + '] ' + self.text + '\n  '
569                 return s
570
571
572 def split_track (track):
573         chs = {}
574         for i in range(16):
575                 chs[i] = []
576                 
577         for e in track:
578                 data = list (e[1])
579                 if data[0] > 0x7f and data[0] < 0xf0:
580                         c = data[0] & 0x0f
581                         e = (e[0], tuple ([data[0] & 0xf0] + data[1:]))
582                         chs[c].append (e)
583                 else:
584                         chs[0].append (e)
585
586         for i in range (16):
587                 if chs[i] == []:
588                         del chs[i]
589
590         threads = []
591         for v in chs.values ():
592                 events = events_on_channel (v)
593                 thread = unthread_notes (events)
594                 if len (thread):
595                         threads.append (thread)
596         return threads
597
598
599 def quantise_clocks (clocks, quant):
600         q = int (clocks / quant) * quant
601         if q != clocks:
602                 for tquant in allowed_tuplet_clocks:
603                         if int (clocks / tquant) * tquant == clocks:
604                                 return clocks
605                 if 2 * (clocks - q) > quant:
606                         q = q + quant
607         return q
608
609 def end_note (pitches, notes, t, e):
610         try:
611                 (lt, vel) = pitches[e]
612                 del pitches[e]
613
614                 i = len (notes) - 1 
615                 while i > 0:
616                         if notes[i][0] > lt:
617                                 i = i -1
618                         else:
619                                 break
620                 d = t - lt
621                 if duration_quant_clocks:
622                         d = quantise_clocks (d, duration_quant_clocks)
623                         if not d:
624                                 d = duration_quant_clocks
625
626                 notes.insert (i + 1,
627                             (lt, Note (d, e, vel)))
628
629         except KeyError:
630                 pass
631
632 def events_on_channel (channel):
633         pitches = {}
634
635         notes = []
636         events = []
637         last_lyric = 0
638         last_time = 0
639         for e in channel:
640                 t = e[0]
641
642                 if start_quant_clocks:
643                         t = quantise_clocks (t, start_quant_clocks)
644
645
646                 if e[1][0] == midi.NOTE_OFF \
647                    or (e[1][0] == midi.NOTE_ON and e[1][2] == 0):
648                         end_note (pitches, notes, t, e[1][1])
649                         
650                 elif e[1][0] == midi.NOTE_ON:
651                         if not pitches.has_key (e[1][1]):
652                                 pitches[e[1][1]] = (t, e[1][2])
653                                 
654                 # all include ALL_NOTES_OFF
655                 elif e[1][0] >= midi.ALL_SOUND_OFF \
656                      and e[1][0] <= midi.POLY_MODE_ON:
657                         for i in pitches.keys ():
658                                 end_note (pitches, notes, t, i)
659                                 
660                 elif e[1][0] == midi.META_EVENT:
661                         if e[1][1] == midi.END_OF_TRACK:
662                                 for i in pitches.keys ():
663                                         end_note (pitches, notes, t, i)
664                                 break
665
666                         elif e[1][1] == midi.SET_TEMPO:
667                                 (u0, u1, u2) = map (ord, e[1][2])
668                                 us_per_4 = u2 + 256 * (u1 + 256 * u0)
669                                 seconds_per_1 = us_per_4 * 4 / 1e6
670                                 events.append ((t, Tempo (seconds_per_1)))
671                         elif e[1][1] == midi.TIME_SIGNATURE:
672                                 (num, dur, clocks4, count32) = map (ord, e[1][2])
673                                 den = 2 ** dur 
674                                 events.append ((t, Time (num, den)))
675                         elif e[1][1] == midi.KEY_SIGNATURE:
676                                 (alterations, minor) = map (ord, e[1][2])
677                                 sharps = 0
678                                 flats = 0
679                                 if alterations < 127:
680                                         sharps = alterations
681                                 else:
682                                         flats = 256 - alterations
683
684                                 k = Key (sharps, flats, minor)
685                                 events.append ((t, k))
686
687                                 # ugh, must set key while parsing
688                                 # because Note init uses key
689                                 # Better do Note.calc () at dump time?
690                                 global key
691                                 key = k
692
693                         elif e[1][1] == midi.LYRIC \
694                              or (text_lyrics_p and e[1][1] == midi.TEXT_EVENT):
695                                 if last_lyric:
696                                         last_lyric.clocks = t - last_time
697                                         events.append ((last_time, last_lyric))
698                                 last_time = t
699                                 last_lyric = Text (midi.LYRIC, e[1][2])
700
701                         elif e[1][1] >= midi.SEQUENCE_NUMBER \
702                              and e[1][1] <= midi.CUE_POINT:
703                                 events.append ((t, Text (e[1][1], e[1][2])))
704                         else:
705                                 if verbose_p:
706                                         sys.stderr.write ("SKIP: %s\n" % `e`)
707                                 pass
708                 else:
709                         if verbose_p:
710                                 sys.stderr.write ("SKIP: %s\n" % `e`)
711                         pass
712
713         if last_lyric:
714                 # last_lyric.clocks = t - last_time
715                 # hmm
716                 last_lyric.clocks = clocks_per_4
717                 events.append ((last_time, last_lyric))
718                 last_lyric = 0
719                 
720         i = 0
721         while len (notes):
722                 if i < len (events) and notes[0][0] >= events[i][0]:
723                         i = i + 1
724                 else:
725                         events.insert (i, notes[0])
726                         del notes[0]
727         return events
728
729 def unthread_notes (channel):
730         threads = []
731         while channel:
732                 thread = []
733                 end_busy_t = 0
734                 start_busy_t = 0
735                 todo = []
736                 for e in channel:
737                         t = e[0]
738                         if e[1].__class__ == Note \
739                            and ((t == start_busy_t \
740                                  and e[1].clocks + t == end_busy_t) \
741                             or t >= end_busy_t):
742                                 thread.append (e)
743                                 start_busy_t = t
744                                 end_busy_t = t + e[1].clocks
745                         elif e[1].__class__ == Time \
746                              or e[1].__class__ == Key \
747                              or e[1].__class__ == Text \
748                              or e[1].__class__ == Tempo:
749                                 thread.append (e)
750                         else:
751                                 todo.append (e)
752                 threads.append (thread)
753                 channel = todo
754
755         return threads
756
757 def gcd (a,b):
758         if b == 0:
759                 return a
760         c = a
761         while c: 
762                 c = a % b
763                 a = b
764                 b = c
765         return a
766         
767 def dump_skip (skip, clocks):
768         return skip + Duration (clocks).dump () + ' '
769
770 def dump (self):
771         return self.dump ()
772
773 def dump_chord (ch):
774         s = ''
775         notes = []
776         for i in ch:
777                 if i.__class__ == Note:
778                         notes.append (i)
779                 else:
780                         s = s + i.dump ()
781         if len (notes) == 1:
782                 s = s + dump (notes[0])
783         elif len (notes) > 1:
784                 global reference_note
785                 s = s + '<'
786                 s = s + notes[0].dump (dump_dur = 0)
787                 r = reference_note
788                 for i in notes[1:]:
789                         s = s + i.dump (dump_dur = 0 )
790                 s = s + '>'
791
792                 s = s + notes[0].duration.dump() + ' '
793                 reference_note = r
794         return s
795
796 def dump_bar_line (last_bar_t, t, bar_count):
797         s = ''
798         bar_t = time.bar_clocks ()
799         if t - last_bar_t >= bar_t:
800                 bar_count = bar_count + (t - last_bar_t) / bar_t
801                 
802                 if t - last_bar_t == bar_t:
803                         s = '|\n  %% %d\n  ' % bar_count
804                         last_bar_t = t
805                 else:
806                         # urg, this will barf at meter changes
807                         last_bar_t = last_bar_t + (t - last_bar_t) / bar_t * bar_t
808                         
809         return (s, last_bar_t, bar_count)
810
811                         
812 def dump_channel (thread, skip):
813         global key, reference_note, time
814
815         key = Key (0, 0, 0)
816         time = Time (4, 4)
817         # urg LilyPond doesn't start at c4, but
818         # remembers from previous tracks!
819         # reference_note = Note (clocks_per_4, 4*12, 0)
820         reference_note = Note (0, 4*12, 0)
821         last_e = None
822         chs = []
823         ch = []
824
825         for e in thread:
826                 if last_e and last_e[0] == e[0]:
827                         ch.append (e[1])
828                 else:
829                         if ch:
830                                 chs.append ((last_e[0], ch))
831                                 
832                         ch = [e[1]]
833                         
834                 last_e = e
835
836         if ch:
837                 chs.append ((last_e[0], ch))
838         t = 0
839         last_t = 0
840         last_bar_t = 0
841         bar_count = 1
842         
843         lines = ['']
844         for ch in chs: 
845                 t = ch[0]
846
847                 i = string.rfind (lines[-1], '\n') + 1
848                 if len (lines[-1][i:]) > LINE_BELL:
849                         lines.append ('')
850                         
851                 if t - last_t > 0:
852                         lines[-1] = lines[-1] + dump_skip (skip, t-last_t)
853                 elif t - last_t < 0:
854                         errorport.write ('BUG: time skew')
855
856                 (s, last_bar_t, bar_count) = dump_bar_line (last_bar_t,
857                                                             t, bar_count)
858                 lines[-1] = lines[-1] + s
859                 
860                 lines[-1] = lines[-1] + dump_chord (ch[1])
861
862                 clocks = 0
863                 for i in ch[1]:
864                         if i.clocks > clocks:
865                                 clocks = i.clocks
866                                 
867                 last_t = t + clocks
868                 
869                 (s, last_bar_t, bar_count) = dump_bar_line (last_bar_t,
870                                                             last_t, bar_count)
871                 lines[-1] = lines[-1] + s
872
873         return string.join (lines, '\n  ') + '\n'
874
875 def track_name (i):
876         return 'track%c' % (i + ord ('A'))
877
878 def channel_name (i):
879         return 'channel%c' % (i + ord ('A'))
880
881 def dump_track (channels, n):
882         s = '\n'
883         track = track_name (n)
884         clef = guess_clef (channels)
885
886         for i in range (len (channels)):
887                 channel = channel_name (i)
888                 item = thread_first_item (channels[i])
889
890                 if item and item.__class__ == Note:
891                         skip = 's'
892                         s = s + '%s = ' % (track + channel)
893                         if not absolute_p:
894                                 s = s + '\\relative c '
895                 elif item and item.__class__ == Text:
896                         skip = '" "'
897                         s = s + '%s = \\lyricmode ' % (track + channel)
898                 else:
899                         skip = '\\skip '
900                         s = s + '%s =  ' % (track + channel)
901                 s = s + '{\n'
902                 s = s + '  ' + dump_channel (channels[i][0], skip)
903                 s = s + '}\n\n'
904
905         s = s + '%s = <<\n' % track
906
907         if clef.type != 2:
908                 s = s + clef.dump () + '\n'
909
910         for i in range (len (channels)):
911                 channel = channel_name (i)
912                 item = thread_first_item (channels[i])
913                 if item and item.__class__ == Text:
914                         s = s + '  \\context Lyrics = %s \\%s\n' % (channel,
915                                                                     track + channel)
916                 else:
917                         s = s + '  \\context Voice = %s \\%s\n' % (channel,
918                                                                    track + channel)
919         s = s + '>>\n\n'
920         return s
921
922 def thread_first_item (thread):
923         for chord in thread:
924                 for event in chord:
925                         if event[1].__class__ == Note \
926                            or (event[1].__class__ == Text \
927                                and event[1].type == midi.LYRIC):
928                                 return event[1]
929         return 0
930
931 def track_first_item (track):
932         for thread in track:
933                 return thread_first_item (thread)
934
935 def guess_clef (track):
936         i = 0
937         p = 0
938         for thread in track:
939                 for chord in thread:
940                         for event in chord:
941                                 if event[1].__class__ == Note:
942                                         i = i + 1
943                                         p = p + event[1].pitch
944         if i and p / i <= 3*12:
945                 return Clef (0)
946         elif i and p / i <= 5*12:
947                 return Clef (1)
948         elif i and p / i >= 7*12:
949                 return Clef (3)
950         else:
951                 return Clef (2)
952         
953
954 def convert_midi (f, o):
955         global clocks_per_1, clocks_per_4, key
956
957         str = open (f).read ()
958         midi_dump = midi.parse (str)
959
960         clocks_per_1 = midi_dump[0][1]
961         clocks_per_4 = clocks_per_1 / 4
962         
963         global start_quant, start_quant_clocks
964         if start_quant:
965                 start_quant_clocks = clocks_per_1 / start_quant
966
967         global duration_quant, duration_quant_clocks
968         if duration_quant:
969                 duration_quant_clocks = clocks_per_1 / duration_quant
970
971         global allowed_tuplet_clocks
972         allowed_tuplet_clocks = []
973         for (dur, num, den) in allowed_tuplets:
974                 allowed_tuplet_clocks.append (clocks_per_1 * num / (dur * den))
975
976         tracks = []
977         for t in midi_dump[1]:
978                 key = Key (0, 0, 0)
979                 tracks.append (split_track (t))
980
981         tag = '%% Lily was here -- automatically converted by %s from %s' % ( program_name, f)
982
983         
984         s = ''
985         s = tag + '\n\\version "2.3.25"\n\n'
986         for i in range (len (tracks)):
987                 s = s + dump_track (tracks[i], i)
988
989         s = s + '\n\\score {\n  <<\n'
990         for i in range (len (tracks)):
991                 track = track_name (i)
992                 item = track_first_item (tracks[i])
993                 if item and item.__class__ == Note:
994                         s = s + '    \\context Staff=%s \\%s\n' % (track, track)
995                 elif item and item.__class__ == Text:
996                         s = s + '    \\context Lyrics=%s \\%s\n' % (track, track)
997         s = s + '  >>\n}\n'
998
999         progress (_ ("%s output to `%s'...") % ('LY', o))
1000
1001         if o == '-':
1002                 h = sys.stdout
1003         else:
1004                 h = open (o, 'w')
1005
1006         h.write (s)
1007         h.close ()
1008
1009
1010 (sh, long) = getopt_args (option_definitions)
1011 try:
1012         (options, files) = getopt.getopt(sys.argv[1:], sh, long)
1013 except getopt.error, s:
1014         errorport.write ('\n')
1015         errorport.write (_ ("error: ") + _ ("getopt says: `%s\'" % s))
1016         errorport.write ('\n')
1017         errorport.write ('\n')
1018         help ()
1019         sys.exit (2)
1020         
1021 for opt in options:     
1022         o = opt[0]
1023         a = opt[1]
1024
1025         if 0:
1026                 pass
1027         elif o == '--help' or o == '-h':
1028                 help ()
1029                 errorport.write ('\n')
1030                 errorport.write (_ ("Example:"))
1031                 errorport.write  (r'''
1032     midi2ly --key=-2:1 --duration-quant=32 \
1033         --allow-tuplet=4*2/3 --allow-tuplet=2*4/3 foo.midi
1034 ''')
1035                 sys.exit (0)
1036         elif o == '--output' or o == '-o':
1037                 output_name = a
1038         elif o == '--verbose' or o == '-V':
1039                 verbose_p = 1
1040         elif o == '--version' or o == '-v':
1041                 identify ()
1042                 sys.exit (0)
1043         elif o == '--warranty' or o == '-w':
1044                 status = system ('lilypond -w', ignore_error = 1)
1045                 if status:
1046                         warranty ()
1047                 sys.exit (0)
1048
1049
1050         elif o == '--absolute-pitches' or o == '-a':
1051                 absolute_p = 1
1052         elif o == '--duration-quant' or o == '-d':
1053                 duration_quant = string.atoi (a)
1054         elif o == '--explicit-durations' or o == '-e':
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                 key = Key (sharps, flats, minor)
1065         elif o == '--start-quant' or o == '-s':
1066                 start_quant = string.atoi (a)
1067         elif o == '--allow-tuplet' or o == '-t':
1068                 a = string.replace (a, '/', '*')
1069                 tuplet = map (string.atoi, string.split (a, '*'))
1070                 allowed_tuplets.append (tuplet)
1071         # lots of midi files use plain text for lyric events
1072         elif o == '--text-lyrics' or o == '-x':
1073                 text_lyrics_p = 1
1074
1075
1076 if not files or files[0] == '-':
1077
1078         # FIXME: read from stdin when files[0] = '-'
1079         help ()
1080         errorport.write (program_name + ":" + _ ("error: ") + _ ("no files specified on command line.") + '\n')
1081         sys.exit (2)
1082
1083
1084 for f in files:
1085         g = f
1086         g = strip_extension (g, '.midi')
1087         g = strip_extension (g, '.mid')
1088         g = strip_extension (g, '.MID')
1089         (outdir, outbase) = ('','')
1090
1091         if not output_name:
1092                 outdir = '.'
1093                 outbase = os.path.basename (g)
1094                 o = os.path.join (outdir, outbase + '-midi.ly')
1095         elif output_name[-1] == os.sep:
1096                 outdir = output_name
1097                 outbase = os.path.basename (g)
1098                 os.path.join (outdir, outbase + '-gen.ly')
1099         else:
1100                 o = output_name
1101                 (outdir, outbase) = os.path.split (o)
1102
1103         if outdir != '.' and outdir != '':
1104                 try:
1105                         os.mkdir (outdir, 0777)
1106                 except OSError:
1107                         pass
1108
1109         convert_midi (f, o)
1110