3 # midi2ly.py -- LilyPond midi import script
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>
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
35 ################################################################
41 scale_steps
= [0, 2, 4, 5, 7, 9, 11]
49 start_quant_clocks
= 0
51 duration_quant_clocks
= 0
52 allowed_tuplet_clocks
= []
55 ################################################################
58 program_name
= sys
.argv
[0]
59 program_version
= '@TOPLEVEL_VERSION@'
61 errorport
= sys
.stderr
64 sys
.stdout
.write ('%s (GNU LilyPond) %s\n' % (program_name
, program_version
))
68 ly
.encoded_write (sys
.stdout
, '''
77 _('Distributed under terms of the GNU General Public License.'),
78 _('It comes with NO WARRANTY.')))
82 ly
.encoded_write (errorport
, s
+ '\n')
85 progress (_ ("warning: ") + s
)
88 progress (_ ("error: ") + s
)
89 raise Exception (_ ("Exiting... "))
91 def system (cmd
, ignore_error
= 0):
92 return ly
.system (cmd
, ignore_error
=ignore_error
)
94 def strip_extension (f
, ext
):
95 (p
, e
) = os
.path
.splitext (f
)
102 allowed_durs
= (1, 2, 4, 8, 16, 32, 64, 128)
103 def __init__ (self
, clocks
):
106 self
.clocks
= duration_quant_clocks
107 (self
.dur
, self
.num
, self
.den
) = self
.dur_num_den (clocks
)
109 def dur_num_den (self
, clocks
):
110 for i
in range (len (allowed_tuplet_clocks
)):
111 if clocks
== allowed_tuplet_clocks
[i
]:
112 return global_options
.allowed_tuplets
[i
]
114 dur
= 0; num
= 1; den
= 1;
115 g
= gcd (clocks
, clocks_per_1
)
117 (dur
, num
) = (clocks_per_1
/ g
, clocks
/ g
)
118 if not dur
in self
.allowed_durs
:
119 dur
= 4; num
= clocks
; den
= clocks_per_4
120 return (dur
, num
, den
)
126 elif self
.num
== 3 and self
.dur
!= 1:
127 s
= '%d.' % (self
.dur
/ 2)
129 s
= '%d*%d' % (self
.dur
, self
.num
)
131 s
= '%d*%d/%d' % (self
.dur
, self
.num
, self
.den
)
133 global reference_note
134 reference_note
.duration
= self
138 def compare (self
, other
):
139 return self
.clocks
- other
.clocks
148 names
= (0, 0, 1, 1, 2, 3, 3, 4, 4, 5, 5, 6)
149 alterations
= (0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 1, 0)
150 alteration_names
= ('eses', 'es', '', 'is' , 'isis')
151 def __init__ (self
, clocks
, pitch
, velocity
):
153 self
.velocity
= velocity
156 self
.duration
= Duration (clocks
)
157 (self
.octave
, self
.notename
, self
.alteration
) = self
.o_n_a ()
161 # minor scale: la-la (= + 5) '''
163 n
= self
.names
[(self
.pitch
) % 12]
164 a
= self
.alterations
[(self
.pitch
) % 12]
166 if a
and global_options
.key
.flats
:
167 a
= - self
.alterations
[(self
.pitch
) % 12]
170 # By tradition, all scales now consist of a sequence
171 # of 7 notes each with a distinct name, from amongst
172 # a b c d e f g. But, minor scales have a wide
173 # second interval at the top - the 'leading note' is
174 # sharped. (Why? it just works that way! Anything
175 # else doesn't sound as good and isn't as flexible at
176 # saying things. In medieval times, scales only had 6
177 # notes to avoid this problem - the hexachords.)
179 # So, the d minor scale is d e f g a b-flat c-sharp d
180 # - using d-flat for the leading note would skip the
181 # name c and duplicate the name d. Why isn't c-sharp
182 # put in the key signature? Tradition. (It's also
183 # supposedly based on the Pythagorean theory of the
184 # cycle of fifths, but that really only applies to
185 # major scales...) Anyway, g minor is g a b-flat c d
186 # e-flat f-sharp g, and all the other flat minor keys
187 # end up with a natural leading note. And there you
190 # John Sankey <bf250@freenet.carleton.ca>
192 # Let's also do a-minor: a b c d e f gis a
196 o
= self
.pitch
/ 12 - 4
198 key
= global_options
.key
201 if (key
.sharps
== 0 and key
.flats
== 0
202 and n
== 5 and a
== -1):
205 elif key
.flats
== 1 and n
== 1 and a
== -1:
208 elif key
.flats
== 2 and n
== 4 and a
== -1:
211 elif key
.sharps
== 5 and n
== 4 and a
== 0:
214 elif key
.sharps
== 6 and n
== 1 and a
== 0:
217 elif key
.sharps
== 7 and n
== 5 and a
== 0:
221 if key
.flats
>= 6 and n
== 6 and a
== 0:
222 n
= 0; a
= -1; o
= o
+ 1
224 if key
.flats
>= 7 and n
== 2 and a
== 0:
228 if key
.sharps
>= 3 and n
== 3 and a
== 0:
231 if key
.sharps
>= 4 and n
== 0 and a
== 0:
232 n
= 6; a
= 1; o
= o
- 1
237 s
= chr ((self
.notename
+ 2) % 7 + ord ('a'))
238 return 'Note(%s %s)' % (s
, self
.duration
.dump())
240 def dump (self
, dump_dur
= 1):
241 global reference_note
242 s
= chr ((self
.notename
+ 2) % 7 + ord ('a'))
243 s
= s
+ self
.alteration_names
[self
.alteration
+ 2]
244 if global_options
.absolute_pitches
:
247 delta
= self
.pitch
- reference_note
.pitch
248 commas
= sign (delta
) * (abs (delta
) / 12)
250 * (self
.notename
- reference_note
.notename
) + 7) \
252 or ((self
.notename
== reference_note
.notename
) \
253 and (abs (delta
) > 4) and (abs (delta
) < 12)):
254 commas
= commas
+ sign (delta
)
259 s
= s
+ "," * -commas
261 ## FIXME: compile fix --jcn
262 if dump_dur
and (global_options
.explicit_durations \
263 or self
.duration
.compare (reference_note
.duration
)):
264 s
= s
+ self
.duration
.dump ()
266 reference_note
= self
273 def __init__ (self
, num
, den
):
278 def bar_clocks (self
):
279 return clocks_per_1
* self
.num
/ self
.den
282 return 'Time(%d/%d)' % (self
.num
, self
.den
)
287 return '\n ' + '\\time %d/%d ' % (self
.num
, self
.den
) + '\n '
290 def __init__ (self
, seconds_per_1
):
292 self
.seconds_per_1
= seconds_per_1
295 return 'Tempo(%d)' % self
.bpm ()
298 return 4 * 60 / self
.seconds_per_1
301 return '\n ' + '\\tempo 4 = %d ' % (self
.bpm()) + '\n '
304 clefs
= ('"bass_8"', 'bass', 'violin', '"violin^8"')
305 def __init__ (self
, type):
309 return 'Clef(%s)' % self
.clefs
[self
.type]
312 return '\n \\clef %s\n ' % self
.clefs
[self
.type]
315 key_sharps
= ('c', 'g', 'd', 'a', 'e', 'b', 'fis')
316 key_flats
= ('BUG', 'f', 'bes', 'es', 'as', 'des', 'ges')
318 def __init__ (self
, sharps
, flats
, minor
):
325 global_options
.key
= self
328 if self
.sharps
and self
.flats
:
332 k
= (ord ('cfbeadg'[self
.flats
% 7]) - ord ('a') - 2 -2 * self
.minor
+ 7) % 7
334 k
= (ord ('cgdaebf'[self
.sharps
% 7]) - ord ('a') - 2 -2 * self
.minor
+ 7) % 7
337 name
= chr ((k
+ 2) % 7 + ord ('a'))
339 name
= chr ((k
+ 2) % 7 + ord ('a'))
341 # fis cis gis dis ais eis bis
342 sharps
= (2, 4, 6, 1, 3, 5, 7)
343 # bes es as des ges ces fes
344 flats
= (6, 4, 2, 7, 5, 3, 1)
347 if flats
[k
] <= self
.flats
:
350 if sharps
[k
] <= self
.sharps
:
354 name
= name
+ Note
.alteration_names
[a
+ 2]
362 return '\n\n ' + s
+ '\n '
370 'SEQUENCE_TRACK_NAME',
376 def __init__ (self
, type, text
):
382 # urg, we should be sure that we're in a lyrics staff
383 if self
.type == midi
.LYRIC
:
384 s
= '"%s"' % self
.text
385 d
= Duration (self
.clocks
)
386 if global_options
.explicit_durations \
387 or d
.compare (reference_note
.duration
):
388 s
= s
+ Duration (self
.clocks
).dump ()
391 s
= '\n % [' + self
.text_types
[self
.type] + '] ' + self
.text
+ '\n '
395 return 'Text(%d=%s)' % (self
.type, self
.text
)
399 def split_track (track
):
406 if data
[0] > 0x7f and data
[0] < 0xf0:
408 e
= (e
[0], tuple ([data
[0] & 0xf0] + data
[1:]))
418 for v
in chs
.values ():
419 events
= events_on_channel (v
)
420 thread
= unthread_notes (events
)
422 threads
.append (thread
)
426 def quantise_clocks (clocks
, quant
):
427 q
= int (clocks
/ quant
) * quant
429 for tquant
in allowed_tuplet_clocks
:
430 if int (clocks
/ tquant
) * tquant
== clocks
:
432 if 2 * (clocks
- q
) > quant
:
436 def end_note (pitches
, notes
, t
, e
):
438 (lt
, vel
) = pitches
[e
]
448 if duration_quant_clocks
:
449 d
= quantise_clocks (d
, duration_quant_clocks
)
451 d
= duration_quant_clocks
454 (lt
, Note (d
, e
, vel
)))
459 def events_on_channel (channel
):
469 if start_quant_clocks
:
470 t
= quantise_clocks (t
, start_quant_clocks
)
473 if e
[1][0] == midi
.NOTE_OFF \
474 or (e
[1][0] == midi
.NOTE_ON
and e
[1][2] == 0):
475 end_note (pitches
, notes
, t
, e
[1][1])
477 elif e
[1][0] == midi
.NOTE_ON
:
478 if not pitches
.has_key (e
[1][1]):
479 pitches
[e
[1][1]] = (t
, e
[1][2])
481 # all include ALL_NOTES_OFF
482 elif e
[1][0] >= midi
.ALL_SOUND_OFF \
483 and e
[1][0] <= midi
.POLY_MODE_ON
:
485 end_note (pitches
, notes
, t
, i
)
487 elif e
[1][0] == midi
.META_EVENT
:
488 if e
[1][1] == midi
.END_OF_TRACK
:
490 end_note (pitches
, notes
, t
, i
)
493 elif e
[1][1] == midi
.SET_TEMPO
:
494 (u0
, u1
, u2
) = map (ord, e
[1][2])
495 us_per_4
= u2
+ 256 * (u1
+ 256 * u0
)
496 seconds_per_1
= us_per_4
* 4 / 1e6
497 events
.append ((t
, Tempo (seconds_per_1
)))
498 elif e
[1][1] == midi
.TIME_SIGNATURE
:
499 (num
, dur
, clocks4
, count32
) = map (ord, e
[1][2])
501 events
.append ((t
, Time (num
, den
)))
502 elif e
[1][1] == midi
.KEY_SIGNATURE
:
503 (alterations
, minor
) = map (ord, e
[1][2])
506 if alterations
< 127:
509 flats
= 256 - alterations
511 k
= Key (sharps
, flats
, minor
)
512 events
.append ((t
, k
))
514 # ugh, must set key while parsing
515 # because Note init uses key
516 # Better do Note.calc () at dump time?
517 global_options
.key
= k
519 elif e
[1][1] == midi
.LYRIC \
520 or (global_options
.text_lyrics
and e
[1][1] == midi
.TEXT_EVENT
):
522 last_lyric
.clocks
= t
- last_time
523 events
.append ((last_time
, last_lyric
))
525 last_lyric
= Text (midi
.LYRIC
, e
[1][2])
527 elif e
[1][1] >= midi
.SEQUENCE_NUMBER \
528 and e
[1][1] <= midi
.CUE_POINT
:
529 events
.append ((t
, Text (e
[1][1], e
[1][2])))
531 if global_options
.verbose
:
532 sys
.stderr
.write ("SKIP: %s\n" % `e`
)
535 if global_options
.verbose
:
536 sys
.stderr
.write ("SKIP: %s\n" % `e`
)
540 # last_lyric.clocks = t - last_time
542 last_lyric
.clocks
= clocks_per_4
543 events
.append ((last_time
, last_lyric
))
548 if i
< len (events
) and notes
[0][0] >= events
[i
][0]:
551 events
.insert (i
, notes
[0])
555 def unthread_notes (channel
):
564 if e
[1].__class
__ == Note \
565 and ((t
== start_busy_t \
566 and e
[1].clocks
+ t
== end_busy_t
) \
570 end_busy_t
= t
+ e
[1].clocks
571 elif e
[1].__class
__ == Time \
572 or e
[1].__class
__ == Key \
573 or e
[1].__class
__ == Text \
574 or e
[1].__class
__ == Tempo
:
578 threads
.append (thread
)
593 def dump_skip (skip
, clocks
):
594 return skip
+ Duration (clocks
).dump () + ' '
603 if i
.__class
__ == Note
:
608 s
= s
+ dump (notes
[0])
609 elif len (notes
) > 1:
610 global reference_note
612 s
= s
+ notes
[0].dump (dump_dur
= 0)
615 s
= s
+ i
.dump (dump_dur
= 0 )
618 s
= s
+ notes
[0].duration
.dump() + ' '
622 def dump_bar_line (last_bar_t
, t
, bar_count
):
624 bar_t
= time
.bar_clocks ()
625 if t
- last_bar_t
>= bar_t
:
626 bar_count
= bar_count
+ (t
- last_bar_t
) / bar_t
628 if t
- last_bar_t
== bar_t
:
629 s
= '|\n %% %d\n ' % bar_count
632 # urg, this will barf at meter changes
633 last_bar_t
= last_bar_t
+ (t
- last_bar_t
) / bar_t
* bar_t
635 return (s
, last_bar_t
, bar_count
)
638 def dump_channel (thread
, skip
):
639 global reference_note
, time
641 global_options
.key
= Key (0, 0, 0)
643 # urg LilyPond doesn't start at c4, but
644 # remembers from previous tracks!
645 # reference_note = Note (clocks_per_4, 4*12, 0)
646 reference_note
= Note (0, 4*12, 0)
652 if last_e
and last_e
[0] == e
[0]:
656 chs
.append ((last_e
[0], ch
))
663 chs
.append ((last_e
[0], ch
))
673 i
= lines
[-1].rfind ('\n') + 1
674 if len (lines
[-1][i
:]) > LINE_BELL
:
678 lines
[-1] = lines
[-1] + dump_skip (skip
, t
-last_t
)
680 errorport
.write ('BUG: time skew')
682 (s
, last_bar_t
, bar_count
) = dump_bar_line (last_bar_t
,
684 lines
[-1] = lines
[-1] + s
686 lines
[-1] = lines
[-1] + dump_chord (ch
[1])
690 if i
.clocks
> clocks
:
695 (s
, last_bar_t
, bar_count
) = dump_bar_line (last_bar_t
,
697 lines
[-1] = lines
[-1] + s
699 return '\n '.join (lines
) + '\n'
702 return 'track%c' % (i
+ ord ('A'))
704 def channel_name (i
):
705 return 'channel%c' % (i
+ ord ('A'))
707 def dump_track (channels
, n
):
709 track
= track_name (n
)
710 clef
= guess_clef (channels
)
712 for i
in range (len (channels
)):
713 channel
= channel_name (i
)
714 item
= thread_first_item (channels
[i
])
716 if item
and item
.__class
__ == Note
:
718 s
= s
+ '%s = ' % (track
+ channel
)
719 if not global_options
.absolute_pitches
:
720 s
= s
+ '\\relative c '
721 elif item
and item
.__class
__ == Text
:
723 s
= s
+ '%s = \\lyricmode ' % (track
+ channel
)
726 s
= s
+ '%s = ' % (track
+ channel
)
728 s
= s
+ ' ' + dump_channel (channels
[i
][0], skip
)
731 s
= s
+ '%s = <<\n' % track
734 s
= s
+ clef
.dump () + '\n'
736 for i
in range (len (channels
)):
737 channel
= channel_name (i
)
738 item
= thread_first_item (channels
[i
])
739 if item
and item
.__class
__ == Text
:
740 s
= s
+ ' \\context Lyrics = %s \\%s\n' % (channel
,
743 s
= s
+ ' \\context Voice = %s \\%s\n' % (channel
,
748 def thread_first_item (thread
):
751 if (event
[1].__class
__ == Note
752 or (event
[1].__class
__ == Text
753 and event
[1].type == midi
.LYRIC
)):
758 def track_first_item (track
):
760 first
= thread_first_item (thread
)
765 def guess_clef (track
):
771 if event
[1].__class
__ == Note
:
773 p
= p
+ event
[1].pitch
774 if i
and p
/ i
<= 3*12:
776 elif i
and p
/ i
<= 5*12:
778 elif i
and p
/ i
>= 7*12:
784 def convert_midi (in_file
, out_file
):
785 global clocks_per_1
, clocks_per_4
, key
786 global start_quant_clocks
787 global duration_quant_clocks
788 global allowed_tuplet_clocks
790 str = open (in_file
).read ()
791 midi_dump
= midi
.parse (str)
793 clocks_per_1
= midi_dump
[0][1]
794 clocks_per_4
= clocks_per_1
/ 4
796 if global_options
.start_quant
:
797 start_quant_clocks
= clocks_per_1
/ global_options
.start_quant
799 if global_options
.duration_quant
:
800 duration_quant_clocks
= clocks_per_1
/ global_options
.duration_quant
802 allowed_tuplet_clocks
= []
803 for (dur
, num
, den
) in global_options
.allowed_tuplets
:
804 allowed_tuplet_clocks
.append (clocks_per_1
/ den
)
807 for t
in midi_dump
[1]:
808 global_options
.key
= Key (0, 0, 0)
809 tracks
.append (split_track (t
))
811 tag
= '%% Lily was here -- automatically converted by %s from %s' % ( program_name
, in_file
)
815 s
= tag
+ '\n\\version "2.7.18"\n\n'
816 for i
in range (len (tracks
)):
817 s
= s
+ dump_track (tracks
[i
], i
)
819 s
= s
+ '\n\\score {\n <<\n'
823 track
= track_name (i
)
824 item
= track_first_item (t
)
826 if item
and item
.__class
__ == Note
:
827 s
= s
+ ' \\context Staff=%s \\%s\n' % (track
, track
)
828 elif item
and item
.__class
__ == Text
:
829 s
= s
+ ' \\context Lyrics=%s \\%s\n' % (track
, track
)
834 progress (_ ("%s output to `%s'...") % ('LY', out_file
))
839 handle
= open (out_file
, 'w')
845 def get_option_parser ():
846 p
= ly
.get_option_parser (usage
=_ ("%s [OPTION]... FILE") % 'midi2ly',
847 description
=_ ("Convert %s to LilyPond input.") % 'MIDI',
848 add_help_option
=False)
850 p
.add_option ('-a', '--absolute-pitches',
852 help=_ ("print absolute pitches"))
853 p
.add_option ('-d', '--duration-quant',
855 help=_ ("quantise note durations on DUR"))
856 p
.add_option ('-e', '--explicit-durations',
858 help=_ ("print explicit durations"))
859 p
.add_option("-h", "--help",
861 help=_ ("show this help and exit"))
862 p
.add_option('-k', '--key', help=_ ("set key: ALT=+sharps|-flats; MINOR=1"),
863 metavar
=_ ("ALT[:MINOR]"),
865 p
.add_option ('-o', '--output', help=_ ("write output to FILE"),
868 p
.add_option ('-s', '--start-quant',help= _ ("quantise note starts on DUR"),
870 p
.add_option ('-t', '--allow-tuplet',
871 metavar
=_ ("DUR*NUM/DEN"),
873 dest
="allowed_tuplets",
874 help=_ ("allow tuplet durations DUR*NUM/DEN"),
876 p
.add_option ('-V', '--verbose', help=_ ("be verbose"),
879 p
.version
= "midi2ly (LilyPond) @TOPLEVEL_VERSION@"
880 p
.add_option("--version",
882 help=_ ("show version number and exit"))
883 p
.add_option ('-w', '--warranty', help=_ ("show warranty and copyright"),
886 p
.add_option ('-x', '--text-lyrics', help=_ ("treat every text as a lyric"),
889 p
.add_option_group (ly
.display_encode (_ ("Examples")),
891 midi2ly --key=-2:1 --duration-quant=32 \
892 --allow-tuplet=4*2/3 --allow-tuplet=2*4/3 foo.midi
894 p
.add_option_group (ly
.display_encode (_ ('Bugs')),
895 description
=(_ ('Report bugs via')
896 + ''' http://post.gmane.org/post.php'''
897 '''?group=gmane.comp.gnu.lilypond.bugs\n'''))
903 opt_parser
= get_option_parser()
904 (options
, args
) = opt_parser
.parse_args ()
906 if not args
or args
[0] == '-':
907 opt_parser
.print_help ()
908 ly
.stderr_write ('\n%s: %s %s\n' % (program_name
, _ ("error: "),
909 _ ("no files specified on command line.")))
912 if options
.duration_quant
:
913 options
.duration_quant
= int (options
.duration_quant
)
919 (alterations
, minor
) = map (int, (options
.key
+ ':0').split (':'))[0:2]
925 flats
= - alterations
927 options
.key
= Key (sharps
, flats
, minor
)
930 if options
.start_quant
:
931 options
.start_quant
= int (options
.start_quant
)
933 options
.allowed_tuplets
= [map (int, a
.replace ('/','*').split ('*'))
934 for a
in options
.allowed_tuplets
]
936 global global_options
937 global_options
= options
946 g
= strip_extension (g
, '.midi')
947 g
= strip_extension (g
, '.mid')
948 g
= strip_extension (g
, '.MID')
949 (outdir
, outbase
) = ('','')
953 outbase
= os
.path
.basename (g
)
954 o
= os
.path
.join (outdir
, outbase
+ '-midi.ly')
955 elif output_name
[-1] == os
.sep
:
957 outbase
= os
.path
.basename (g
)
958 os
.path
.join (outdir
, outbase
+ '-gen.ly')
961 (outdir
, outbase
) = os
.path
.split (o
)
963 if outdir
!= '.' and outdir
!= '':
965 os
.mkdir (outdir
, 0777)
970 if __name__
== '__main__':