set hsize for plain tex, but only for
[lilypond.git] / scripts / midi2ly.py
blob7fe8b24b04157976fa1a429d7f205c7af0318129
1 #!@PYTHON@
3 # midi2ly.py -- LilyPond midi import script
4 #
5 # source file of the GNU LilyPond music typesetter
7 # convert MIDI to LilyPond source
11 '''
12 TODO:
13 * test on weird and unquantised midi input (lily-devel)
14 * drop c++ midi2ly
15 * update doc and manpage
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 '''
25 import os
26 import sys
27 import getopt
28 import sys
29 import string
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@'
39 sys.path.append (os.path.join (datadir, 'python'))
40 sys.path.append (os.path.join (datadir, 'python/out'))
42 import midi
44 ################################################################
45 ################ CONSTANTS
48 output_name = ''
49 LINE_BELL = 60
50 scale_steps = [0,2,4,5,7,9,11]
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
69 ################################################################
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
81 program_name = 'midi2ly'
82 program_version = '@TOPLEVEL_VERSION@'
84 errorport = sys.stderr
85 verbose_p = 0
87 # temp_dir = os.path.join (original_dir, '%s.dir' % program_name)
88 # original_dir = os.getcwd ()
89 # keep_temp_dir_p = 0
92 help_summary = _ ("Convert MIDI to LilyPond source")
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")),
109 ################################################################
110 # lilylib.py -- options and stuff
112 # source file of the GNU LilyPond music typesetter
114 import os
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
125 if program_version == '@' + 'TOPLEVEL_VERSION' + '@':
126 program_version = '1.5.17'
128 def identify ():
129 sys.stdout.write ('%s (GNU LilyPond) %s\n' % (program_name, program_version))
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')
144 def progress (s):
145 errorport.write (s + '\n')
147 def warning (s):
148 progress (_ ("warning: ") + s)
150 def error (s):
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.
157 RETURN VALUE
159 None
163 progress (_ ("error: ") + s)
164 raise _ ("Exiting ... ")
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)
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]
188 sep = ' '
189 if o[1] and o[2]:
190 sep = ','
192 long = ''
193 if o[2]:
194 long= '--%s' % o[2]
196 arg = ''
197 if o[0]:
198 if o[2]:
199 arg = '='
200 arg = arg + o[0]
201 return ' ' + sh + sep + long + arg
204 def options_help_str (opts):
205 '''Convert a list of options into a neatly formatted string'''
206 w = 0
207 strs =[]
208 helps = []
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)
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
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)
234 def setup_temp ():
236 Create a temporary directory, and return its name.
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
246 return temp_dir
249 def system (cmd, ignore_error = 0):
250 """Run CMD. If IGNORE_ERROR is set, don't complain when CMD returns non zero.
252 RETURN VALUE
254 Exit status of CMD
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)
268 return st
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)
278 def strip_extension (f, ext):
279 (p, e) = os.path.splitext (f)
280 if e == ext:
281 e = ''
282 return p + e
284 ################################################################
285 # END Library
286 ################################################################
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)
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]
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)
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)
323 global reference_note
324 reference_note.duration = self
326 return s
328 def compare (self, other):
329 return self.clocks - other.clocks
331 def sign (x):
332 if x >= 0:
333 return 1
334 else:
335 return -1
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')
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 ()
350 def o_n_a (self):
351 # major scale: do-do
352 # minor scale: la-la (= + 5) '''
354 n = self.names[(self.pitch) % 12]
355 a = self.alterations[(self.pitch) % 12]
357 if a and key.flats:
358 a = - self.alterations[(self.pitch) % 12]
359 n = (n - a) % 7
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.)
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.
381 # John Sankey <bf250@freenet.carleton.ca>
383 # Let's also do a-minor: a b c d e f gis a
385 # --jcn
387 o = self.pitch / 12 - 4
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
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
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
424 return (o, n, a)
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)
441 if commas > 0:
442 s = s + "'" * commas
443 elif commas < 0:
444 s = s + "," * -commas
446 if explicit_durations_p \
447 or Duration.compare (self.duration, reference_note.duration):
448 s = s + self.duration.dump ()
450 global reference_note
451 reference_note = self
453 # TODO: move space
454 return s + ' '
457 class Time:
458 def __init__ (self, num, den):
459 self.clocks = 0
460 self.num = num
461 self.den = den
463 def bar_clocks (self):
464 return clocks_per_1 * self.num / self.den
466 def dump (self):
467 global time
468 time = self
469 return '\n ' + '\\time %d/%d ' % (self.num, self.den) + '\n '
471 class Tempo:
472 def __init__ (self, seconds_per_1):
473 self.clocks = 0
474 self.seconds_per_1 = seconds_per_1
476 def dump (self):
477 return '\n ' + '\\tempo 4 = %d ' % (4 * 60 / self.seconds_per_1) + '\n '
479 class Clef:
480 clefs = ('"bass_8"', 'bass', 'violin', '"violin^8"')
481 def __init__ (self, type):
482 self.type = type
484 def dump (self):
485 return '\n \\clef %s\n ' % self.clefs[self.type]
487 class Key:
488 key_sharps = ('c', 'g', 'd', 'a', 'e', 'b', 'fis')
489 key_flats = ('BUG', 'f', 'bes', 'es', 'as', 'des', 'ges')
491 def __init__ (self, sharps, flats, minor):
492 self.clocks = 0
493 self.flats = flats
494 self.sharps = sharps
495 self.minor = minor
497 def dump (self):
498 global key
499 key = self
501 s = ''
502 if self.sharps and self.flats:
503 s = '\\keysignature %s ' % 'TODO'
504 else:
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
511 if not self.minor:
512 name = chr ((k + 2) % 7 + ord ('a'))
513 else:
514 name = chr ((k + 2) % 7 + ord ('a'))
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
528 if a:
529 name = name + Note.alteration_names[a + 2]
531 s = '\\key ' + name
532 if self.minor:
533 s = s + ' \\minor'
534 else:
535 s = s + ' \\major'
537 return '\n\n ' + s + '\n '
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',)
551 def __init__ (self, type, text):
552 self.clocks = 0
553 self.type = type
554 self.text = text
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
571 def split_track (track):
572 chs = {}
573 for i in range(16):
574 chs[i] = []
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)
585 for i in range (16):
586 if chs[i] == []:
587 del chs[i]
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
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
608 def end_note (pitches, notes, t, e):
609 try:
610 (lt, vel) = pitches[e]
611 del pitches[e]
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
625 notes.insert (i + 1,
626 (lt, Note (d, e, vel)))
628 except KeyError:
629 pass
631 def events_on_channel (channel):
632 pitches = {}
634 notes = []
635 events = []
636 last_lyric = 0
637 last_time = 0
638 for e in channel:
639 t = e[0]
641 if start_quant_clocks:
642 t = quantise_clocks (t, start_quant_clocks)
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])
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])
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)
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
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
683 k = Key (sharps, flats, minor)
684 events.append ((t, k))
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
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])
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
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
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
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
754 return threads
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
766 def dump_skip (skip, clocks):
767 return skip + Duration (clocks).dump () + ' '
769 def dump (self):
770 return self.dump ()
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
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
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
806 return (s, last_bar_t, bar_count)
809 def dump_channel (thread, skip):
810 global key, reference_note, time
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 = []
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))
829 ch = [e[1]]
831 last_e = e
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
840 lines = ['']
841 for ch in chs:
842 t = ch[0]
844 i = string.rfind (lines[-1], '\n') + 1
845 if len (lines[-1][i:]) > LINE_BELL:
846 lines.append ('')
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')
853 (s, last_bar_t, bar_count) = dump_bar_line (last_bar_t,
854 t, bar_count)
855 lines[-1] = lines[-1] + s
857 lines[-1] = lines[-1] + dump_chord (ch[1])
859 clocks = 0
860 for i in ch[1]:
861 if i.clocks > clocks:
862 clocks = i.clocks
864 last_t = t + clocks
866 (s, last_bar_t, bar_count) = dump_bar_line (last_bar_t,
867 last_t, bar_count)
868 lines[-1] = lines[-1] + s
870 return string.join (lines, '\n ') + '\n'
872 def track_name (i):
873 return 'track%c' % (i + ord ('A'))
875 def channel_name (i):
876 return 'channel%c' % (i + ord ('A'))
878 def dump_track (channels, n):
879 s = '\n'
880 track = track_name (n)
881 clef = guess_clef (channels)
883 for i in range (len (channels)):
884 channel = channel_name (i)
885 item = thread_first_item (channels[i])
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'
903 s = s + '%s = <\n' % track
905 if clef.type != 2:
906 s = s + clef.dump () + '\n'
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
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
929 def track_first_item (track):
930 for thread in track:
931 return thread_first_item (thread)
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)
952 def convert_midi (f, o):
953 global clocks_per_1, clocks_per_4, key
955 str = open (f).read ()
956 midi_dump = midi.parse (str)
958 clocks_per_1 = midi_dump[0][1]
959 clocks_per_4 = clocks_per_1 / 4
961 global start_quant, start_quant_clocks
962 if start_quant:
963 start_quant_clocks = clocks_per_1 / start_quant
965 global duration_quant, duration_quant_clocks
966 if duration_quant:
967 duration_quant_clocks = clocks_per_1 / duration_quant
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))
974 tracks = []
975 for t in midi_dump[1]:
976 key = Key (0, 0, 0)
977 tracks.append (split_track (t))
979 tag = '%% Lily was here -- automatically converted by %s from %s' % ( program_name, f)
981 s = ''
982 s = tag + '\n\n'
983 for i in range (len (tracks)):
984 s = s + dump_track (tracks[i], i)
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'
996 progress (_ ("%s output to `%s'...") % ('LY', o))
998 if o == '-':
999 h = sys.stdout
1000 else:
1001 h = open (o, 'w')
1003 h.write (s)
1004 h.close ()
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)
1018 for opt in options:
1019 o = opt[0]
1020 a = opt[1]
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)
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
1077 if not files or files[0] == '-':
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)
1085 for f in files:
1087 g = f
1088 g = strip_extension (g, '.midi')
1089 g = strip_extension (g, '.mid')
1090 g = strip_extension (g, '.MID')
1091 (outdir, outbase) = ('','')
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)
1105 if outdir != '.' and outdir != '':
1106 try:
1107 os.mkdir (outdir, 0777)
1108 except OSError:
1109 pass
1111 convert_midi (f, o)