3 # midi2ly.py -- LilyPond midi import script
5 # source file of the GNU LilyPond music typesetter
7 # (c) 1998--2005 Han-Wen Nienhuys <hanwen@cs.uu.nl>
8 # Jan Nieuwenhuizen <janneke@gnu.org>
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
30 ################################################################
31 # Users of python modules should include this snippet.
33 # This soon to be removed for: import lilypond.lilylib as ly
34 libdir
= '@local_lilypond_libdir@'
35 if not os
.path
.isdir (libdir
):
36 libdir
= '@lilypond_libdir@'
37 sys
.path
.insert (0, os
.path
.join (libdir
, 'python'))
40 ################################################################
45 ################################################################
46 ################ CONSTANTS
51 scale_steps
= [0,2,4,5,7,9,11]
59 start_quant_clocks
= 0
61 duration_quant_clocks
= 0
63 allowed_tuplet_clocks
= []
65 explicit_durations_p
= 0
70 ################################################################
72 localedir
= '@localedir@'
75 gettext
.bindtextdomain ('lilypond', localedir
)
76 gettext
.textdomain ('lilypond')
82 program_name
= sys
.argv
[0]
83 program_version
= '@TOPLEVEL_VERSION@'
85 errorport
= sys
.stderr
88 # temp_dir = os.path.join (original_dir, '%s.dir' % program_name)
89 # original_dir = os.getcwd ()
93 help_summary
= _ ("Convert MIDI to LilyPond source.")
95 option_definitions
= [
96 ('', 'a', 'absolute-pitches', _ ("print absolute pitches")),
97 (_ ("DUR"), 'd', 'duration-quant', _ ("quantise note durations on DUR")),
98 ('', 'e', 'explicit-durations', _ ("print explicit durations")),
99 ('', 'h', 'help', _ ("print this help")),
100 (_ ("ALT[:MINOR]"), 'k', 'key', _ ("set key: ALT=+sharps|-flats; MINOR=1")),
101 (_ ("FILE"), 'o', 'output', _ ("write output to FILE")),
102 (_ ("DUR"), 's', 'start-quant', _ ("quantise note starts on DUR")),
103 (_ ("DUR*NUM/DEN"), 't', 'allow-tuplet', _ ("allow tuplet durations DUR*NUM/DEN")),
104 ('', 'V', 'verbose', _ ("be verbose")),
105 ('', 'v', 'version', _ ("print version number")),
106 ('', 'w', 'warranty', _ ("show warranty and copyright")),
107 ('', 'x', 'text-lyrics', _ ("treat every text as a lyric")),
110 ################################################################
111 # lilylib.py -- options and stuff
113 # source file of the GNU LilyPond music typesetter
119 gettext
.bindtextdomain ('lilypond', localedir
)
120 gettext
.textdomain ('lilypond')
126 if program_version
== '@' + 'TOPLEVEL_VERSION' + '@':
127 program_version
= '1.5.17'
130 sys
.stdout
.write ('%s (GNU LilyPond) %s\n' % (program_name
, program_version
))
134 sys
.stdout
.write ('\n')
135 sys
.stdout
.write (_ ('Copyright (c) %s by') % ' 2001--2005')
136 sys
.stdout
.write ('\n')
137 sys
.stdout
.write (' Han-Wen Nienhuys')
138 sys
.stdout
.write (' Jan Nieuwenhuizen')
139 sys
.stdout
.write ('\n\n')
140 sys
.stdout
.write (_ ("Distributed under terms of the GNU General Public License."))
141 sys
.stdout
.write (_ ("It comes with NO WARRANTY."))
142 sys
.stdout
.write ('\n')
145 errorport
.write (s
+ '\n')
148 progress (_ ("warning: ") + 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.
163 progress (_ ("error: ") + s
)
164 raise _ ("Exiting ... ")
166 def getopt_args (opts
):
167 '''Construct arguments (LONG, SHORT) for getopt from list of options.'''
182 def option_help_str (o
):
183 '''Transform one option description (4-tuple ) into neatly formatted string'''
201 return ' ' + sh
+ sep
+ long + arg
204 def options_help_str (opts
):
205 '''Convert a list of options into a neatly formatted string'''
211 s
= option_help_str (o
)
212 strs
.append ((s
, o
[3]))
218 str = str + '%s%s%s\n' % (s
[0], ' ' * (w
- len(s
[0]) + 3), s
[1])
222 ls
= [(_ ("Usage: %s [OPTIONS]... FILE") % program_name
),
228 (options_help_str (option_definitions
)),
230 (_ ("Report bugs to %s.") % 'bug-lilypond@gnu.org'),
232 map (sys
.stdout
.write
, ls
)
236 Create a temporary directory, and return its name.
239 if not keep_temp_dir_p
:
240 temp_dir
= tempfile
.mktemp (program_name
)
242 os
.mkdir (temp_dir
, 0777)
249 def system (cmd
, ignore_error
= 0):
250 """Run CMD. If IGNORE_ERROR is set, don't complain when CMD returns non zero.
258 progress (_ ("Invoking `%s\'") % cmd
)
261 name
= re
.match ('[ \t]*([^ \t]*)', cmd
).group (1)
262 msg
= name
+ ': ' + _ ("command exited with value %d") % st
264 warning (msg
+ ' ' + _ ("(ignored)") + ' ')
272 if not keep_temp_dir_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
)
284 ################################################################
286 ################################################################
292 allowed_durs
= (1, 2, 4, 8, 16, 32, 64, 128)
293 def __init__ (self
, clocks
):
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
)
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
)
316 elif self
.num
== 3 and self
.dur
!= 1:
317 s
= '%d.' % (self
.dur
/ 2)
319 s
= '%d*%d' % (self
.dur
, self
.num
)
321 s
= '%d*%d/%d' % (self
.dur
, self
.num
, self
.den
)
323 global reference_note
324 reference_note
.duration
= self
328 def compare (self
, other
):
329 return self
.clocks
- other
.clocks
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
):
344 self
.velocity
= velocity
347 self
.duration
= Duration (clocks
)
348 (self
.octave
, self
.notename
, self
.alteration
) = self
.o_n_a ()
352 # minor scale: la-la (= + 5) '''
354 n
= self
.names
[(self
.pitch
) % 12]
355 a
= self
.alterations
[(self
.pitch
) % 12]
358 a
= - self
.alterations
[(self
.pitch
) % 12]
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
381 # John Sankey <bf250@freenet.carleton.ca>
383 # Let's also do a-minor: a b c d e f gis a
387 o
= self
.pitch
/ 12 - 4
391 if key
.sharps
== 0 and key
.flats
== 0 \
392 and n
== 5 and a
== -1:
395 elif key
.flats
== 1 and n
== 1 and a
== -1:
398 elif key
.flats
== 2 and n
== 4 and a
== -1:
401 elif key
.sharps
== 5 and n
== 4 and a
== 0:
404 elif key
.sharps
== 6 and n
== 1 and a
== 0:
407 elif key
.sharps
== 7 and n
== 5 and a
== 0:
411 if key
.flats
>= 6 and n
== 6 and a
== 0:
412 n
= 0; a
= -1; o
= o
+ 1
414 if key
.flats
>= 7 and n
== 2 and a
== 0:
418 if key
.sharps
>= 3 and n
== 3 and a
== 0:
421 if key
.sharps
>= 4 and n
== 0 and a
== 0:
422 n
= 6; a
= 1; o
= o
- 1
426 def dump (self
, dump_dur
= 1):
427 global reference_note
428 s
= chr ((self
.notename
+ 2) % 7 + ord ('a'))
429 s
= s
+ self
.alteration_names
[self
.alteration
+ 2]
433 delta
= self
.pitch
- reference_note
.pitch
434 commas
= sign (delta
) * (abs (delta
) / 12)
436 * (self
.notename
- reference_note
.notename
) + 7) \
438 or ((self
.notename
== reference_note
.notename
) \
439 and (abs (delta
) > 4) and (abs (delta
) < 12)):
440 commas
= commas
+ sign (delta
)
445 s
= s
+ "," * -commas
447 ## FIXME: compile fix --jcn
448 if dump_dur
and (explicit_durations_p \
449 or Duration
.compare (self
.duration
,
450 reference_note
.duration
)):
451 s
= s
+ self
.duration
.dump ()
453 reference_note
= self
460 def __init__ (self
, num
, den
):
465 def bar_clocks (self
):
466 return clocks_per_1
* self
.num
/ self
.den
471 return '\n ' + '\\time %d/%d ' % (self
.num
, self
.den
) + '\n '
474 def __init__ (self
, seconds_per_1
):
476 self
.seconds_per_1
= seconds_per_1
479 return '\n ' + '\\tempo 4 = %d ' % (4 * 60 / self
.seconds_per_1
) + '\n '
482 clefs
= ('"bass_8"', 'bass', 'violin', '"violin^8"')
483 def __init__ (self
, type):
487 return '\n \\clef %s\n ' % self
.clefs
[self
.type]
490 key_sharps
= ('c', 'g', 'd', 'a', 'e', 'b', 'fis')
491 key_flats
= ('BUG', 'f', 'bes', 'es', 'as', 'des', 'ges')
493 def __init__ (self
, sharps
, flats
, minor
):
504 if self
.sharps
and self
.flats
:
508 k
= (ord ('cfbeadg'[self
.flats
% 7]) - ord ('a') - 2 -2 * self
.minor
+ 7) % 7
510 k
= (ord ('cgdaebf'[self
.sharps
% 7]) - ord ('a') - 2 -2 * self
.minor
+ 7) % 7
513 name
= chr ((k
+ 2) % 7 + ord ('a'))
515 name
= chr ((k
+ 2) % 7 + ord ('a'))
517 # fis cis gis dis ais eis bis
518 sharps
= (2, 4, 6, 1, 3, 5, 7)
519 # bes es as des ges ces fes
520 flats
= (6, 4, 2, 7, 5, 3, 1)
523 if flats
[k
] <= self
.flats
:
526 if sharps
[k
] <= self
.sharps
:
530 name
= name
+ Note
.alteration_names
[a
+ 2]
538 return '\n\n ' + s
+ '\n '
546 'SEQUENCE_TRACK_NAME',
552 def __init__ (self
, type, text
):
558 # urg, we should be sure that we're in a lyrics staff
559 if self
.type == midi
.LYRIC
:
560 s
= '"%s"' % self
.text
561 d
= Duration (self
.clocks
)
562 if explicit_durations_p \
563 or Duration
.compare (d
,
564 reference_note
.duration
):
565 s
= s
+ Duration (self
.clocks
).dump ()
568 s
= '\n % [' + self
.text_types
[self
.type] + '] ' + self
.text
+ '\n '
572 def split_track (track
):
579 if data
[0] > 0x7f and data
[0] < 0xf0:
581 e
= (e
[0], tuple ([data
[0] & 0xf0] + data
[1:]))
591 for v
in chs
.values ():
592 events
= events_on_channel (v
)
593 thread
= unthread_notes (events
)
595 threads
.append (thread
)
599 def quantise_clocks (clocks
, quant
):
600 q
= int (clocks
/ quant
) * quant
602 for tquant
in allowed_tuplet_clocks
:
603 if int (clocks
/ tquant
) * tquant
== clocks
:
605 if 2 * (clocks
- q
) > quant
:
609 def end_note (pitches
, notes
, t
, e
):
611 (lt
, vel
) = pitches
[e
]
621 if duration_quant_clocks
:
622 d
= quantise_clocks (d
, duration_quant_clocks
)
624 d
= duration_quant_clocks
627 (lt
, Note (d
, e
, vel
)))
632 def events_on_channel (channel
):
642 if start_quant_clocks
:
643 t
= quantise_clocks (t
, start_quant_clocks
)
646 if e
[1][0] == midi
.NOTE_OFF \
647 or (e
[1][0] == midi
.NOTE_ON
and e
[1][2] == 0):
648 end_note (pitches
, notes
, t
, e
[1][1])
650 elif e
[1][0] == midi
.NOTE_ON
:
651 if not pitches
.has_key (e
[1][1]):
652 pitches
[e
[1][1]] = (t
, e
[1][2])
654 # all include ALL_NOTES_OFF
655 elif e
[1][0] >= midi
.ALL_SOUND_OFF \
656 and e
[1][0] <= midi
.POLY_MODE_ON
:
657 for i
in pitches
.keys ():
658 end_note (pitches
, notes
, t
, i
)
660 elif e
[1][0] == midi
.META_EVENT
:
661 if e
[1][1] == midi
.END_OF_TRACK
:
662 for i
in pitches
.keys ():
663 end_note (pitches
, notes
, t
, i
)
666 elif e
[1][1] == midi
.SET_TEMPO
:
667 (u0
, u1
, u2
) = map (ord, e
[1][2])
668 us_per_4
= u2
+ 256 * (u1
+ 256 * u0
)
669 seconds_per_1
= us_per_4
* 4 / 1e6
670 events
.append ((t
, Tempo (seconds_per_1
)))
671 elif e
[1][1] == midi
.TIME_SIGNATURE
:
672 (num
, dur
, clocks4
, count32
) = map (ord, e
[1][2])
674 events
.append ((t
, Time (num
, den
)))
675 elif e
[1][1] == midi
.KEY_SIGNATURE
:
676 (alterations
, minor
) = map (ord, e
[1][2])
679 if alterations
< 127:
682 flats
= 256 - alterations
684 k
= Key (sharps
, flats
, minor
)
685 events
.append ((t
, k
))
687 # ugh, must set key while parsing
688 # because Note init uses key
689 # Better do Note.calc () at dump time?
693 elif e
[1][1] == midi
.LYRIC \
694 or (text_lyrics_p
and e
[1][1] == midi
.TEXT_EVENT
):
696 last_lyric
.clocks
= t
- last_time
697 events
.append ((last_time
, last_lyric
))
699 last_lyric
= Text (midi
.LYRIC
, e
[1][2])
701 elif e
[1][1] >= midi
.SEQUENCE_NUMBER \
702 and e
[1][1] <= midi
.CUE_POINT
:
703 events
.append ((t
, Text (e
[1][1], e
[1][2])))
706 sys
.stderr
.write ("SKIP: %s\n" % `e`
)
710 sys
.stderr
.write ("SKIP: %s\n" % `e`
)
714 # last_lyric.clocks = t - last_time
716 last_lyric
.clocks
= clocks_per_4
717 events
.append ((last_time
, last_lyric
))
722 if i
< len (events
) and notes
[0][0] >= events
[i
][0]:
725 events
.insert (i
, notes
[0])
729 def unthread_notes (channel
):
738 if e
[1].__class
__ == Note \
739 and ((t
== start_busy_t \
740 and e
[1].clocks
+ t
== end_busy_t
) \
744 end_busy_t
= t
+ e
[1].clocks
745 elif e
[1].__class
__ == Time \
746 or e
[1].__class
__ == Key \
747 or e
[1].__class
__ == Text \
748 or e
[1].__class
__ == Tempo
:
752 threads
.append (thread
)
767 def dump_skip (skip
, clocks
):
768 return skip
+ Duration (clocks
).dump () + ' '
777 if i
.__class
__ == Note
:
782 s
= s
+ dump (notes
[0])
783 elif len (notes
) > 1:
784 global reference_note
786 s
= s
+ notes
[0].dump (dump_dur
= 0)
789 s
= s
+ i
.dump (dump_dur
= 0 )
792 s
= s
+ notes
[0].duration
.dump() + ' '
796 def dump_bar_line (last_bar_t
, t
, bar_count
):
798 bar_t
= time
.bar_clocks ()
799 if t
- last_bar_t
>= bar_t
:
800 bar_count
= bar_count
+ (t
- last_bar_t
) / bar_t
802 if t
- last_bar_t
== bar_t
:
803 s
= '|\n %% %d\n ' % bar_count
806 # urg, this will barf at meter changes
807 last_bar_t
= last_bar_t
+ (t
- last_bar_t
) / bar_t
* bar_t
809 return (s
, last_bar_t
, bar_count
)
812 def dump_channel (thread
, skip
):
813 global key
, reference_note
, time
817 # urg LilyPond doesn't start at c4, but
818 # remembers from previous tracks!
819 # reference_note = Note (clocks_per_4, 4*12, 0)
820 reference_note
= Note (0, 4*12, 0)
826 if last_e
and last_e
[0] == e
[0]:
830 chs
.append ((last_e
[0], ch
))
837 chs
.append ((last_e
[0], ch
))
847 i
= string
.rfind (lines
[-1], '\n') + 1
848 if len (lines
[-1][i
:]) > LINE_BELL
:
852 lines
[-1] = lines
[-1] + dump_skip (skip
, t
-last_t
)
854 errorport
.write ('BUG: time skew')
856 (s
, last_bar_t
, bar_count
) = dump_bar_line (last_bar_t
,
858 lines
[-1] = lines
[-1] + s
860 lines
[-1] = lines
[-1] + dump_chord (ch
[1])
864 if i
.clocks
> clocks
:
869 (s
, last_bar_t
, bar_count
) = dump_bar_line (last_bar_t
,
871 lines
[-1] = lines
[-1] + s
873 return string
.join (lines
, '\n ') + '\n'
876 return 'track%c' % (i
+ ord ('A'))
878 def channel_name (i
):
879 return 'channel%c' % (i
+ ord ('A'))
881 def dump_track (channels
, n
):
883 track
= track_name (n
)
884 clef
= guess_clef (channels
)
886 for i
in range (len (channels
)):
887 channel
= channel_name (i
)
888 item
= thread_first_item (channels
[i
])
890 if item
and item
.__class
__ == Note
:
892 s
= s
+ '%s = ' % (track
+ channel
)
894 s
= s
+ '\\relative c '
895 elif item
and item
.__class
__ == Text
:
897 s
= s
+ '%s = \\lyricmode ' % (track
+ channel
)
900 s
= s
+ '%s = ' % (track
+ channel
)
902 s
= s
+ ' ' + dump_channel (channels
[i
][0], skip
)
905 s
= s
+ '%s = <<\n' % track
908 s
= s
+ clef
.dump () + '\n'
910 for i
in range (len (channels
)):
911 channel
= channel_name (i
)
912 item
= thread_first_item (channels
[i
])
913 if item
and item
.__class
__ == Text
:
914 s
= s
+ ' \\context Lyrics = %s \\%s\n' % (channel
,
917 s
= s
+ ' \\context Voice = %s \\%s\n' % (channel
,
922 def thread_first_item (thread
):
925 if event
[1].__class
__ == Note \
926 or (event
[1].__class
__ == Text \
927 and event
[1].type == midi
.LYRIC
):
931 def track_first_item (track
):
933 return thread_first_item (thread
)
935 def guess_clef (track
):
941 if event
[1].__class
__ == Note
:
943 p
= p
+ event
[1].pitch
944 if i
and p
/ i
<= 3*12:
946 elif i
and p
/ i
<= 5*12:
948 elif i
and p
/ i
>= 7*12:
954 def convert_midi (f
, o
):
955 global clocks_per_1
, clocks_per_4
, key
957 str = open (f
).read ()
958 midi_dump
= midi
.parse (str)
960 clocks_per_1
= midi_dump
[0][1]
961 clocks_per_4
= clocks_per_1
/ 4
963 global start_quant
, start_quant_clocks
965 start_quant_clocks
= clocks_per_1
/ start_quant
967 global duration_quant
, duration_quant_clocks
969 duration_quant_clocks
= clocks_per_1
/ duration_quant
971 global allowed_tuplet_clocks
972 allowed_tuplet_clocks
= []
973 for (dur
, num
, den
) in allowed_tuplets
:
974 allowed_tuplet_clocks
.append (clocks_per_1
* num
/ (dur
* den
))
977 for t
in midi_dump
[1]:
979 tracks
.append (split_track (t
))
981 tag
= '%% Lily was here -- automatically converted by %s from %s' % ( program_name
, f
)
985 s
= tag
+ '\n\\version "2.3.25"\n\n'
986 for i
in range (len (tracks
)):
987 s
= s
+ dump_track (tracks
[i
], i
)
989 s
= s
+ '\n\\score {\n <<\n'
990 for i
in range (len (tracks
)):
991 track
= track_name (i
)
992 item
= track_first_item (tracks
[i
])
993 if item
and item
.__class
__ == Note
:
994 s
= s
+ ' \\context Staff=%s \\%s\n' % (track
, track
)
995 elif item
and item
.__class
__ == Text
:
996 s
= s
+ ' \\context Lyrics=%s \\%s\n' % (track
, track
)
999 progress (_ ("%s output to `%s'...") % ('LY', o
))
1010 (sh
, long) = getopt_args (option_definitions
)
1012 (options
, files
) = getopt
.getopt(sys
.argv
[1:], sh
, long)
1013 except getopt
.error
, s
:
1014 errorport
.write ('\n')
1015 errorport
.write (_ ("error: ") + _ ("getopt says: `%s\'" % s
))
1016 errorport
.write ('\n')
1017 errorport
.write ('\n')
1027 elif o
== '--help' or o
== '-h':
1029 errorport
.write ('\n')
1030 errorport
.write (_ ("Example:"))
1031 errorport
.write (r
'''
1032 midi2ly --key=-2:1 --duration-quant=32 \
1033 --allow-tuplet=4*2/3 --allow-tuplet=2*4/3 foo.midi
1036 elif o
== '--output' or o
== '-o':
1038 elif o
== '--verbose' or o
== '-V':
1040 elif o
== '--version' or o
== '-v':
1043 elif o
== '--warranty' or o
== '-w':
1044 status
= system ('lilypond -w', ignore_error
= 1)
1050 elif o
== '--absolute-pitches' or o
== '-a':
1052 elif o
== '--duration-quant' or o
== '-d':
1053 duration_quant
= string
.atoi (a
)
1054 elif o
== '--explicit-durations' or o
== '-e':
1055 explicit_durations_p
= 1
1056 elif o
== '--key' or o
== '-k':
1057 (alterations
, minor
) = map (string
.atoi
, string
.split (a
+ ':0', ':'))[0:2]
1060 if alterations
>= 0:
1061 sharps
= alterations
1063 flats
= - alterations
1064 key
= Key (sharps
, flats
, minor
)
1065 elif o
== '--start-quant' or o
== '-s':
1066 start_quant
= string
.atoi (a
)
1067 elif o
== '--allow-tuplet' or o
== '-t':
1068 a
= string
.replace (a
, '/', '*')
1069 tuplet
= map (string
.atoi
, string
.split (a
, '*'))
1070 allowed_tuplets
.append (tuplet
)
1071 # lots of midi files use plain text for lyric events
1072 elif o
== '--text-lyrics' or o
== '-x':
1076 if not files
or files
[0] == '-':
1078 # FIXME: read from stdin when files[0] = '-'
1080 errorport
.write (program_name
+ ":" + _ ("error: ") + _ ("no files specified on command line.") + '\n')
1086 g
= strip_extension (g
, '.midi')
1087 g
= strip_extension (g
, '.mid')
1088 g
= strip_extension (g
, '.MID')
1089 (outdir
, outbase
) = ('','')
1093 outbase
= os
.path
.basename (g
)
1094 o
= os
.path
.join (outdir
, outbase
+ '-midi.ly')
1095 elif output_name
[-1] == os
.sep
:
1096 outdir
= output_name
1097 outbase
= os
.path
.basename (g
)
1098 os
.path
.join (outdir
, outbase
+ '-gen.ly')
1101 (outdir
, outbase
) = os
.path
.split (o
)
1103 if outdir
!= '.' and outdir
!= '':
1105 os
.mkdir (outdir
, 0777)