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