Fix InstrumentSwitch grob definition.
[lilypond.git] / scripts / midi2ly.py
blobd86ea1fd7f5ced4e84a86918f2af1792a5a3ccc0
1 #!@TARGET_PYTHON@
3 # midi2ly.py -- LilyPond midi import script
4 #
5 # source file of the GNU LilyPond music typesetter
7 # (c) 1998--2007 Han-Wen Nienhuys <hanwen@xs4all.nl>
8 # Jan Nieuwenhuizen <janneke@gnu.org>
11 '''
12 TODO:
13 * test on weird and unquantised midi input (lily-devel)
14 * update doc and manpage
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 '''
24 import os
25 import sys
27 """
28 @relocate-preamble@
29 """
31 import midi
32 import lilylib as ly
33 global _;_=ly._
35 ################################################################
36 ## CONSTANTS
39 LINE_BELL = 60
40 scale_steps = [0, 2, 4, 5, 7, 9, 11]
41 global_options = None
43 clocks_per_1 = 1536
44 clocks_per_4 = 0
46 time = 0
47 reference_note = 0
48 start_quant_clocks = 0
50 duration_quant_clocks = 0
51 allowed_tuplet_clocks = []
54 ################################################################
57 program_name = sys.argv[0]
58 program_version = '@TOPLEVEL_VERSION@'
60 errorport = sys.stderr
62 def identify ():
63 sys.stdout.write ('%s (GNU LilyPond) %s\n' % (program_name, program_version))
65 def warranty ():
66 identify ()
67 ly.encoded_write (sys.stdout, '''
68 Copyright (c) %s by
70 Han-Wen Nienhuys
71 Jan Nieuwenhuizen
75 ''' ( '2001--2006',
76 _('Distributed under terms of the GNU General Public License.'),
77 _('It comes with NO WARRANTY.')))
80 def progress (s):
81 ly.encoded_write (errorport, s + '\n')
83 def warning (s):
84 progress (_ ("warning: ") + s)
86 def error (s):
87 progress (_ ("error: ") + s)
88 raise Exception (_ ("Exiting... "))
90 def system (cmd, ignore_error = 0):
91 return ly.system (cmd, ignore_error=ignore_error)
93 def strip_extension (f, ext):
94 (p, e) = os.path.splitext (f)
95 if e == ext:
96 e = ''
97 return p + e
100 class Duration:
101 allowed_durs = (1, 2, 4, 8, 16, 32, 64, 128)
102 def __init__ (self, clocks):
103 self.clocks = clocks
104 if clocks <= 0:
105 self.clocks = duration_quant_clocks
106 (self.dur, self.num, self.den) = self.dur_num_den (clocks)
108 def dur_num_den (self, clocks):
109 for i in range (len (allowed_tuplet_clocks)):
110 if clocks == allowed_tuplet_clocks[i]:
111 return global_options.allowed_tuplets[i]
113 dur = 0; num = 1; den = 1;
114 g = gcd (clocks, clocks_per_1)
115 if g:
116 (dur, num) = (clocks_per_1 / g, clocks / g)
117 if not dur in self.allowed_durs:
118 dur = 4; num = clocks; den = clocks_per_4
119 return (dur, num, den)
121 def dump (self):
122 if self.den == 1:
123 if self.num == 1:
124 s = '%d' % self.dur
125 elif self.num == 3 and self.dur != 1:
126 s = '%d.' % (self.dur / 2)
127 else:
128 s = '%d*%d' % (self.dur, self.num)
129 else:
130 s = '%d*%d/%d' % (self.dur, self.num, self.den)
132 global reference_note
133 reference_note.duration = self
135 return s
137 def compare (self, other):
138 return self.clocks - other.clocks
140 def sign (x):
141 if x >= 0:
142 return 1
143 else:
144 return -1
146 class Note:
147 names = (0, 0, 1, 1, 2, 3, 3, 4, 4, 5, 5, 6)
148 alterations = (0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 1, 0)
149 alteration_names = ('eses', 'es', '', 'is' , 'isis')
150 def __init__ (self, clocks, pitch, velocity):
151 self.pitch = pitch
152 self.velocity = velocity
153 # hmm
154 self.clocks = clocks
155 self.duration = Duration (clocks)
156 (self.octave, self.notename, self.alteration) = self.o_n_a ()
158 def o_n_a (self):
159 # major scale: do-do
160 # minor scale: la-la (= + 5) '''
162 n = self.names[(self.pitch) % 12]
163 a = self.alterations[(self.pitch) % 12]
165 if a and global_options.key.flats:
166 a = - self.alterations[(self.pitch) % 12]
167 n = (n - a) % 7
169 # By tradition, all scales now consist of a sequence
170 # of 7 notes each with a distinct name, from amongst
171 # a b c d e f g. But, minor scales have a wide
172 # second interval at the top - the 'leading note' is
173 # sharped. (Why? it just works that way! Anything
174 # else doesn't sound as good and isn't as flexible at
175 # saying things. In medieval times, scales only had 6
176 # notes to avoid this problem - the hexachords.)
178 # So, the d minor scale is d e f g a b-flat c-sharp d
179 # - using d-flat for the leading note would skip the
180 # name c and duplicate the name d. Why isn't c-sharp
181 # put in the key signature? Tradition. (It's also
182 # supposedly based on the Pythagorean theory of the
183 # cycle of fifths, but that really only applies to
184 # major scales...) Anyway, g minor is g a b-flat c d
185 # e-flat f-sharp g, and all the other flat minor keys
186 # end up with a natural leading note. And there you
187 # have it.
189 # John Sankey <bf250@freenet.carleton.ca>
191 # Let's also do a-minor: a b c d e f gis a
193 # --jcn
195 o = self.pitch / 12 - 4
197 key = global_options.key
198 if key.minor:
199 # as -> gis
200 if (key.sharps == 0 and key.flats == 0
201 and n == 5 and a == -1):
202 n = 4; a = 1
203 # des -> cis
204 elif key.flats == 1 and n == 1 and a == -1:
205 n = 0; a = 1
206 # ges -> fis
207 elif key.flats == 2 and n == 4 and a == -1:
208 n = 3; a = 1
209 # g -> fisis
210 elif key.sharps == 5 and n == 4 and a == 0:
211 n = 3; a = 2
212 # d -> cisis
213 elif key.sharps == 6 and n == 1 and a == 0:
214 n = 0; a = 2
215 # a -> gisis
216 elif key.sharps == 7 and n == 5 and a == 0:
217 n = 4; a = 2
219 # b -> ces
220 if key.flats >= 6 and n == 6 and a == 0:
221 n = 0; a = -1; o = o + 1
222 # e -> fes
223 if key.flats >= 7 and n == 2 and a == 0:
224 n = 3; a = -1
226 # f -> eis
227 if key.sharps >= 3 and n == 3 and a == 0:
228 n = 2; a = 1
229 # c -> bis
230 if key.sharps >= 4 and n == 0 and a == 0:
231 n = 6; a = 1; o = o - 1
233 return (o, n, a)
235 def __repr__ (self):
236 s = chr ((self.notename + 2) % 7 + ord ('a'))
237 return 'Note(%s %s)' % (s, self.duration.dump())
239 def dump (self, dump_dur = 1):
240 global reference_note
241 s = chr ((self.notename + 2) % 7 + ord ('a'))
242 s = s + self.alteration_names[self.alteration + 2]
243 if global_options.absolute_pitches:
244 commas = self.octave
245 else:
246 delta = self.pitch - reference_note.pitch
247 commas = sign (delta) * (abs (delta) / 12)
248 if ((sign (delta) \
249 * (self.notename - reference_note.notename) + 7) \
250 % 7 >= 4) \
251 or ((self.notename == reference_note.notename) \
252 and (abs (delta) > 4) and (abs (delta) < 12)):
253 commas = commas + sign (delta)
255 if commas > 0:
256 s = s + "'" * commas
257 elif commas < 0:
258 s = s + "," * -commas
260 ## FIXME: compile fix --jcn
261 if dump_dur and (global_options.explicit_durations \
262 or self.duration.compare (reference_note.duration)):
263 s = s + self.duration.dump ()
265 reference_note = self
267 # TODO: move space
268 return s + ' '
271 class Time:
272 def __init__ (self, num, den):
273 self.clocks = 0
274 self.num = num
275 self.den = den
277 def bar_clocks (self):
278 return clocks_per_1 * self.num / self.den
280 def __repr__ (self):
281 return 'Time(%d/%d)' % (self.num, self.den)
283 def dump (self):
284 global time
285 time = self
286 return '\n ' + '\\time %d/%d ' % (self.num, self.den) + '\n '
288 class Tempo:
289 def __init__ (self, seconds_per_1):
290 self.clocks = 0
291 self.seconds_per_1 = seconds_per_1
293 def __repr__ (self):
294 return 'Tempo(%d)' % self.bpm ()
296 def bpm (self):
297 return 4 * 60 / self.seconds_per_1
299 def dump (self):
300 return '\n ' + '\\tempo 4 = %d ' % (self.bpm()) + '\n '
302 class Clef:
303 clefs = ('"bass_8"', 'bass', 'violin', '"violin^8"')
304 def __init__ (self, type):
305 self.type = type
307 def __repr__ (self):
308 return 'Clef(%s)' % self.clefs[self.type]
310 def dump (self):
311 return '\n \\clef %s\n ' % self.clefs[self.type]
313 class Key:
314 key_sharps = ('c', 'g', 'd', 'a', 'e', 'b', 'fis')
315 key_flats = ('BUG', 'f', 'bes', 'es', 'as', 'des', 'ges')
317 def __init__ (self, sharps, flats, minor):
318 self.clocks = 0
319 self.flats = flats
320 self.sharps = sharps
321 self.minor = minor
323 def dump (self):
324 global_options.key = self
326 s = ''
327 if self.sharps and self.flats:
328 pass
329 else:
330 if self.flats:
331 k = (ord ('cfbeadg'[self.flats % 7]) - ord ('a') - 2 -2 * self.minor + 7) % 7
332 else:
333 k = (ord ('cgdaebf'[self.sharps % 7]) - ord ('a') - 2 -2 * self.minor + 7) % 7
335 if not self.minor:
336 name = chr ((k + 2) % 7 + ord ('a'))
337 else:
338 name = chr ((k + 2) % 7 + ord ('a'))
340 # fis cis gis dis ais eis bis
341 sharps = (2, 4, 6, 1, 3, 5, 7)
342 # bes es as des ges ces fes
343 flats = (6, 4, 2, 7, 5, 3, 1)
344 a = 0
345 if self.flats:
346 if flats[k] <= self.flats:
347 a = -1
348 else:
349 if sharps[k] <= self.sharps:
350 a = 1
352 if a:
353 name = name + Note.alteration_names[a + 2]
355 s = '\\key ' + name
356 if self.minor:
357 s = s + ' \\minor'
358 else:
359 s = s + ' \\major'
361 return '\n\n ' + s + '\n '
364 class Text:
365 text_types = (
366 'SEQUENCE_NUMBER',
367 'TEXT_EVENT',
368 'COPYRIGHT_NOTICE',
369 'SEQUENCE_TRACK_NAME',
370 'INSTRUMENT_NAME',
371 'LYRIC',
372 'MARKER',
373 'CUE_POINT',)
375 def __init__ (self, type, text):
376 self.clocks = 0
377 self.type = type
378 self.text = text
380 def dump (self):
381 # urg, we should be sure that we're in a lyrics staff
382 if self.type == midi.LYRIC:
383 s = '"%s"' % self.text
384 d = Duration (self.clocks)
385 if global_options.explicit_durations \
386 or d.compare (reference_note.duration):
387 s = s + Duration (self.clocks).dump ()
388 s = s + ' '
389 else:
390 s = '\n % [' + self.text_types[self.type] + '] ' + self.text + '\n '
391 return s
393 def __repr__ (self):
394 return 'Text(%d=%s)' % (self.type, self.text)
398 def split_track (track):
399 chs = {}
400 for i in range(16):
401 chs[i] = []
403 for e in track:
404 data = list (e[1])
405 if data[0] > 0x7f and data[0] < 0xf0:
406 c = data[0] & 0x0f
407 e = (e[0], tuple ([data[0] & 0xf0] + data[1:]))
408 chs[c].append (e)
409 else:
410 chs[0].append (e)
412 for i in range (16):
413 if chs[i] == []:
414 del chs[i]
416 threads = []
417 for v in chs.values ():
418 events = events_on_channel (v)
419 thread = unthread_notes (events)
420 if len (thread):
421 threads.append (thread)
422 return threads
425 def quantise_clocks (clocks, quant):
426 q = int (clocks / quant) * quant
427 if q != clocks:
428 for tquant in allowed_tuplet_clocks:
429 if int (clocks / tquant) * tquant == clocks:
430 return clocks
431 if 2 * (clocks - q) > quant:
432 q = q + quant
433 return q
435 def end_note (pitches, notes, t, e):
436 try:
437 (lt, vel) = pitches[e]
438 del pitches[e]
440 i = len (notes) - 1
441 while i > 0:
442 if notes[i][0] > lt:
443 i = i -1
444 else:
445 break
446 d = t - lt
447 if duration_quant_clocks:
448 d = quantise_clocks (d, duration_quant_clocks)
449 if not d:
450 d = duration_quant_clocks
452 notes.insert (i + 1,
453 (lt, Note (d, e, vel)))
455 except KeyError:
456 pass
458 def events_on_channel (channel):
459 pitches = {}
461 notes = []
462 events = []
463 last_lyric = 0
464 last_time = 0
465 for e in channel:
466 t = e[0]
468 if start_quant_clocks:
469 t = quantise_clocks (t, start_quant_clocks)
472 if e[1][0] == midi.NOTE_OFF \
473 or (e[1][0] == midi.NOTE_ON and e[1][2] == 0):
474 end_note (pitches, notes, t, e[1][1])
476 elif e[1][0] == midi.NOTE_ON:
477 if not pitches.has_key (e[1][1]):
478 pitches[e[1][1]] = (t, e[1][2])
480 # all include ALL_NOTES_OFF
481 elif e[1][0] >= midi.ALL_SOUND_OFF \
482 and e[1][0] <= midi.POLY_MODE_ON:
483 for i in pitches:
484 end_note (pitches, notes, t, i)
486 elif e[1][0] == midi.META_EVENT:
487 if e[1][1] == midi.END_OF_TRACK:
488 for i in pitches:
489 end_note (pitches, notes, t, i)
490 break
492 elif e[1][1] == midi.SET_TEMPO:
493 (u0, u1, u2) = map (ord, e[1][2])
494 us_per_4 = u2 + 256 * (u1 + 256 * u0)
495 seconds_per_1 = us_per_4 * 4 / 1e6
496 events.append ((t, Tempo (seconds_per_1)))
497 elif e[1][1] == midi.TIME_SIGNATURE:
498 (num, dur, clocks4, count32) = map (ord, e[1][2])
499 den = 2 ** dur
500 events.append ((t, Time (num, den)))
501 elif e[1][1] == midi.KEY_SIGNATURE:
502 (alterations, minor) = map (ord, e[1][2])
503 sharps = 0
504 flats = 0
505 if alterations < 127:
506 sharps = alterations
507 else:
508 flats = 256 - alterations
510 k = Key (sharps, flats, minor)
511 events.append ((t, k))
513 # ugh, must set key while parsing
514 # because Note init uses key
515 # Better do Note.calc () at dump time?
516 global_options.key = k
518 elif e[1][1] == midi.LYRIC \
519 or (global_options.text_lyrics and e[1][1] == midi.TEXT_EVENT):
520 if last_lyric:
521 last_lyric.clocks = t - last_time
522 events.append ((last_time, last_lyric))
523 last_time = t
524 last_lyric = Text (midi.LYRIC, e[1][2])
526 elif e[1][1] >= midi.SEQUENCE_NUMBER \
527 and e[1][1] <= midi.CUE_POINT:
528 events.append ((t, Text (e[1][1], e[1][2])))
529 else:
530 if global_options.verbose:
531 sys.stderr.write ("SKIP: %s\n" % `e`)
532 pass
533 else:
534 if global_options.verbose:
535 sys.stderr.write ("SKIP: %s\n" % `e`)
536 pass
538 if last_lyric:
539 # last_lyric.clocks = t - last_time
540 # hmm
541 last_lyric.clocks = clocks_per_4
542 events.append ((last_time, last_lyric))
543 last_lyric = 0
545 i = 0
546 while len (notes):
547 if i < len (events) and notes[0][0] >= events[i][0]:
548 i = i + 1
549 else:
550 events.insert (i, notes[0])
551 del notes[0]
552 return events
554 def unthread_notes (channel):
555 threads = []
556 while channel:
557 thread = []
558 end_busy_t = 0
559 start_busy_t = 0
560 todo = []
561 for e in channel:
562 t = e[0]
563 if e[1].__class__ == Note \
564 and ((t == start_busy_t \
565 and e[1].clocks + t == end_busy_t) \
566 or t >= end_busy_t):
567 thread.append (e)
568 start_busy_t = t
569 end_busy_t = t + e[1].clocks
570 elif e[1].__class__ == Time \
571 or e[1].__class__ == Key \
572 or e[1].__class__ == Text \
573 or e[1].__class__ == Tempo:
574 thread.append (e)
575 else:
576 todo.append (e)
577 threads.append (thread)
578 channel = todo
580 return threads
582 def gcd (a,b):
583 if b == 0:
584 return a
585 c = a
586 while c:
587 c = a % b
588 a = b
589 b = c
590 return a
592 def dump_skip (skip, clocks):
593 return skip + Duration (clocks).dump () + ' '
595 def dump (d):
596 return d.dump ()
598 def dump_chord (ch):
599 s = ''
600 notes = []
601 for i in ch:
602 if i.__class__ == Note:
603 notes.append (i)
604 else:
605 s = s + i.dump ()
606 if len (notes) == 1:
607 s = s + dump (notes[0])
608 elif len (notes) > 1:
609 global reference_note
610 s = s + '<'
611 s = s + notes[0].dump (dump_dur = 0)
612 r = reference_note
613 for i in notes[1:]:
614 s = s + i.dump (dump_dur = 0 )
615 s = s + '>'
617 s = s + notes[0].duration.dump() + ' '
618 reference_note = r
619 return s
621 def dump_bar_line (last_bar_t, t, bar_count):
622 s = ''
623 bar_t = time.bar_clocks ()
624 if t - last_bar_t >= bar_t:
625 bar_count = bar_count + (t - last_bar_t) / bar_t
627 if t - last_bar_t == bar_t:
628 s = '|\n %% %d\n ' % bar_count
629 last_bar_t = t
630 else:
631 # urg, this will barf at meter changes
632 last_bar_t = last_bar_t + (t - last_bar_t) / bar_t * bar_t
634 return (s, last_bar_t, bar_count)
637 def dump_channel (thread, skip):
638 global reference_note, time
640 global_options.key = Key (0, 0, 0)
641 time = Time (4, 4)
642 # urg LilyPond doesn't start at c4, but
643 # remembers from previous tracks!
644 # reference_note = Note (clocks_per_4, 4*12, 0)
645 reference_note = Note (0, 4*12, 0)
646 last_e = None
647 chs = []
648 ch = []
650 for e in thread:
651 if last_e and last_e[0] == e[0]:
652 ch.append (e[1])
653 else:
654 if ch:
655 chs.append ((last_e[0], ch))
657 ch = [e[1]]
659 last_e = e
661 if ch:
662 chs.append ((last_e[0], ch))
663 t = 0
664 last_t = 0
665 last_bar_t = 0
666 bar_count = 1
668 lines = ['']
669 for ch in chs:
670 t = ch[0]
672 i = lines[-1].rfind ('\n') + 1
673 if len (lines[-1][i:]) > LINE_BELL:
674 lines.append ('')
676 if t - last_t > 0:
677 lines[-1] = lines[-1] + dump_skip (skip, t-last_t)
678 elif t - last_t < 0:
679 errorport.write ('BUG: time skew')
681 (s, last_bar_t, bar_count) = dump_bar_line (last_bar_t,
682 t, bar_count)
683 lines[-1] = lines[-1] + s
685 lines[-1] = lines[-1] + dump_chord (ch[1])
687 clocks = 0
688 for i in ch[1]:
689 if i.clocks > clocks:
690 clocks = i.clocks
692 last_t = t + clocks
694 (s, last_bar_t, bar_count) = dump_bar_line (last_bar_t,
695 last_t, bar_count)
696 lines[-1] = lines[-1] + s
698 return '\n '.join (lines) + '\n'
700 def track_name (i):
701 return 'track%c' % (i + ord ('A'))
703 def channel_name (i):
704 return 'channel%c' % (i + ord ('A'))
706 def dump_track (channels, n):
707 s = '\n'
708 track = track_name (n)
709 clef = guess_clef (channels)
711 for i in range (len (channels)):
712 channel = channel_name (i)
713 item = thread_first_item (channels[i])
715 if item and item.__class__ == Note:
716 skip = 's'
717 s = s + '%s = ' % (track + channel)
718 if not global_options.absolute_pitches:
719 s = s + '\\relative c '
720 elif item and item.__class__ == Text:
721 skip = '" "'
722 s = s + '%s = \\lyricmode ' % (track + channel)
723 else:
724 skip = '\\skip '
725 s = s + '%s = ' % (track + channel)
726 s = s + '{\n'
727 s = s + ' ' + dump_channel (channels[i][0], skip)
728 s = s + '}\n\n'
730 s = s + '%s = <<\n' % track
732 if clef.type != 2:
733 s = s + clef.dump () + '\n'
735 for i in range (len (channels)):
736 channel = channel_name (i)
737 item = thread_first_item (channels[i])
738 if item and item.__class__ == Text:
739 s = s + ' \\context Lyrics = %s \\%s\n' % (channel,
740 track + channel)
741 else:
742 s = s + ' \\context Voice = %s \\%s\n' % (channel,
743 track + channel)
744 s = s + '>>\n\n'
745 return s
747 def thread_first_item (thread):
748 for chord in thread:
749 for event in chord:
750 if (event[1].__class__ == Note
751 or (event[1].__class__ == Text
752 and event[1].type == midi.LYRIC)):
754 return event[1]
755 return None
757 def track_first_item (track):
758 for thread in track:
759 first = thread_first_item (thread)
760 if first:
761 return first
762 return None
764 def guess_clef (track):
765 i = 0
766 p = 0
767 for thread in track:
768 for chord in thread:
769 for event in chord:
770 if event[1].__class__ == Note:
771 i = i + 1
772 p = p + event[1].pitch
773 if i and p / i <= 3*12:
774 return Clef (0)
775 elif i and p / i <= 5*12:
776 return Clef (1)
777 elif i and p / i >= 7*12:
778 return Clef (3)
779 else:
780 return Clef (2)
783 def convert_midi (in_file, out_file):
784 global clocks_per_1, clocks_per_4, key
785 global start_quant_clocks
786 global duration_quant_clocks
787 global allowed_tuplet_clocks
789 str = open (in_file).read ()
790 midi_dump = midi.parse (str)
792 clocks_per_1 = midi_dump[0][1]
793 clocks_per_4 = clocks_per_1 / 4
795 if global_options.start_quant:
796 start_quant_clocks = clocks_per_1 / global_options.start_quant
798 if global_options.duration_quant:
799 duration_quant_clocks = clocks_per_1 / global_options.duration_quant
801 allowed_tuplet_clocks = []
802 for (dur, num, den) in global_options.allowed_tuplets:
803 allowed_tuplet_clocks.append (clocks_per_1 / den)
805 tracks = []
806 for t in midi_dump[1]:
807 global_options.key = Key (0, 0, 0)
808 tracks.append (split_track (t))
810 tag = '%% Lily was here -- automatically converted by %s from %s' % ( program_name, in_file)
813 s = ''
814 s = tag + '\n\\version "2.7.18"\n\n'
815 for i in range (len (tracks)):
816 s = s + dump_track (tracks[i], i)
818 s = s + '\n\\score {\n <<\n'
820 i = 0
821 for t in tracks:
822 track = track_name (i)
823 item = track_first_item (t)
825 if item and item.__class__ == Note:
826 s = s + ' \\context Staff=%s \\%s\n' % (track, track)
827 elif item and item.__class__ == Text:
828 s = s + ' \\context Lyrics=%s \\%s\n' % (track, track)
830 i += 1
831 s = s + ' >>\n}\n'
833 progress (_ ("%s output to `%s'...") % ('LY', out_file))
835 if out_file == '-':
836 handle = sys.stdout
837 else:
838 handle = open (out_file, 'w')
840 handle.write (s)
841 handle.close ()
844 def get_option_parser ():
845 p = ly.get_option_parser (usage=_ ("%s [OPTION]... FILE") % 'midi2ly',
846 description=_ ("Convert %s to LilyPond input.\n") % 'MIDI',
847 add_help_option=False)
849 p.add_option ('-a', '--absolute-pitches',
850 action='store_true',
851 help=_ ("print absolute pitches"))
852 p.add_option ('-d', '--duration-quant',
853 metavar= _("DUR"),
854 help=_ ("quantise note durations on DUR"))
855 p.add_option ('-e', '--explicit-durations',
856 action='store_true',
857 help=_ ("print explicit durations"))
858 p.add_option("-h", "--help",
859 action="help",
860 help=_ ("show this help and exit"))
861 p.add_option('-k', '--key', help=_ ("set key: ALT=+sharps|-flats; MINOR=1"),
862 metavar=_ ("ALT[:MINOR]"),
863 default='0'),
864 p.add_option ('-o', '--output', help=_ ("write output to FILE"),
865 metavar=_("FILE"),
866 action='store')
867 p.add_option ('-s', '--start-quant',help= _ ("quantise note starts on DUR"),
868 metavar=_ ("DUR"))
869 p.add_option ('-t', '--allow-tuplet',
870 metavar=_ ("DUR*NUM/DEN"),
871 action = "append",
872 dest="allowed_tuplets",
873 help=_ ("allow tuplet durations DUR*NUM/DEN"),
874 default=[])
875 p.add_option ('-V', '--verbose', help=_ ("be verbose"),
876 action='store_true'
878 p.version = "midi2ly (LilyPond) @TOPLEVEL_VERSION@"
879 p.add_option("--version",
880 action="version",
881 help=_ ("show version number and exit"))
882 p.add_option ('-w', '--warranty', help=_ ("show warranty and copyright"),
883 action='store_true',
885 p.add_option ('-x', '--text-lyrics', help=_ ("treat every text as a lyric"),
886 action='store_true')
888 p.add_option_group (ly.display_encode (_ ("Examples")),
889 description = r'''
890 $ midi2ly --key=-2:1 --duration-quant=32 --allow-tuplet=4*2/3 --allow-tuplet=2*4/3 foo.midi
891 ''')
892 p.add_option_group ('',
893 description=(_ ('Report bugs via')
894 + ''' http://post.gmane.org/post.php'''
895 '''?group=gmane.comp.gnu.lilypond.bugs\n'''))
896 return p
900 def do_options ():
901 opt_parser = get_option_parser()
902 (options, args) = opt_parser.parse_args ()
904 if not args or args[0] == '-':
905 opt_parser.print_help ()
906 ly.stderr_write ('\n%s: %s %s\n' % (program_name, _ ("error: "),
907 _ ("no files specified on command line.")))
908 sys.exit (2)
910 if options.duration_quant:
911 options.duration_quant = int (options.duration_quant)
913 if options.warranty:
914 warranty ()
915 sys.exit (0)
916 if 1:
917 (alterations, minor) = map (int, (options.key + ':0').split (':'))[0:2]
918 sharps = 0
919 flats = 0
920 if alterations >= 0:
921 sharps = alterations
922 else:
923 flats = - alterations
925 options.key = Key (sharps, flats, minor)
928 if options.start_quant:
929 options.start_quant = int (options.start_quant)
931 options.allowed_tuplets = [map (int, a.replace ('/','*').split ('*'))
932 for a in options.allowed_tuplets]
934 global global_options
935 global_options = options
937 return args
939 def main():
940 files = do_options()
942 for f in files:
943 g = f
944 g = strip_extension (g, '.midi')
945 g = strip_extension (g, '.mid')
946 g = strip_extension (g, '.MID')
947 (outdir, outbase) = ('','')
949 if not global_options.output:
950 outdir = '.'
951 outbase = os.path.basename (g)
952 o = os.path.join (outdir, outbase + '-midi.ly')
953 elif global_options.output[-1] == os.sep:
954 outdir = global_options.output
955 outbase = os.path.basename (g)
956 os.path.join (outdir, outbase + '-gen.ly')
957 else:
958 o = global_options.output
959 (outdir, outbase) = os.path.split (o)
961 if outdir != '.' and outdir != '':
962 try:
963 os.mkdir (outdir, 0777)
964 except OSError:
965 pass
967 convert_midi (f, o)
968 if __name__ == '__main__':
969 main()