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