3 # midi2ly.py -- LilyPond midi import script
5 # This file is part of LilyPond, the GNU music typesetter.
7 # Copyright (C) 1998--2011 Han-Wen Nienhuys <hanwen@xs4all.nl>
8 # Jan Nieuwenhuizen <janneke@gnu.org>
10 # LilyPond is free software: you can redistribute it and/or modify
11 # it under the terms of the GNU General Public License as published by
12 # the Free Software Foundation, either version 3 of the License, or
13 # (at your option) any later version.
15 # LilyPond is distributed in the hope that it will be useful,
16 # but WITHOUT ANY WARRANTY; without even the implied warranty of
17 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 # GNU General Public License for more details.
20 # You should have received a copy of the GNU General Public License
21 # along with LilyPond. If not, see <http://www.gnu.org/licenses/>.
26 * test on weird and unquantised midi input (lily-devel)
27 * update doc and manpage
29 * simply insert clef changes whenever too many ledger lines
30 [to avoid tex capacity exceeded]
31 * do not ever quant skips
32 * better lyrics handling
33 * [see if it is feasible to] move ly-classes to library for use in
34 other converters, while leaving midi specific stuff here
48 ################################################################
53 scale_steps
= [0, 2, 4, 5, 7, 9, 11]
61 start_quant_clocks
= 0
63 duration_quant_clocks
= 0
64 allowed_tuplet_clocks
= []
67 ################################################################
70 program_name
= sys
.argv
[0]
71 program_version
= '@TOPLEVEL_VERSION@'
73 authors
= ('Jan Nieuwenhuizen <janneke@gnu.org>',
74 'Han-Wen Nienhuys <hanwen@xs4all.nl>')
76 errorport
= sys
.stderr
79 sys
.stdout
.write ('%s (GNU LilyPond) %s\n' % (program_name
, program_version
))
83 ly
.encoded_write (sys
.stdout
, '''
90 ''' % ( _ ('Copyright (c) %s by') % '1998--2011',
92 _ ('Distributed under terms of the GNU General Public License.'),
93 _ ('It comes with NO WARRANTY.')))
96 ly
.encoded_write (errorport
, s
+ '\n')
99 progress (_ ("warning: ") + s
)
102 progress (_ ("error: ") + s
)
103 raise Exception (_ ("Exiting... "))
105 def system (cmd
, ignore_error
= 0):
106 return ly
.system (cmd
, ignore_error
=ignore_error
)
108 def strip_extension (f
, ext
):
109 (p
, e
) = os
.path
.splitext (f
)
116 allowed_durs
= (1, 2, 4, 8, 16, 32, 64, 128)
117 def __init__ (self
, clocks
):
120 self
.clocks
= duration_quant_clocks
121 (self
.dur
, self
.num
, self
.den
) = self
.dur_num_den (clocks
)
123 def dur_num_den (self
, clocks
):
124 for i
in range (len (allowed_tuplet_clocks
)):
125 if clocks
== allowed_tuplet_clocks
[i
]:
126 return global_options
.allowed_tuplets
[i
]
128 dur
= 0; num
= 1; den
= 1;
129 g
= gcd (clocks
, clocks_per_1
)
131 (dur
, num
) = (clocks_per_1
/ g
, clocks
/ g
)
132 if not dur
in self
.allowed_durs
:
133 dur
= 4; num
= clocks
; den
= clocks_per_4
134 return (dur
, num
, den
)
140 elif self
.num
== 3 and self
.dur
!= 1:
141 s
= '%d.' % (self
.dur
/ 2)
143 s
= '%d*%d' % (self
.dur
, self
.num
)
145 s
= '%d*%d/%d' % (self
.dur
, self
.num
, self
.den
)
147 global reference_note
148 reference_note
.duration
= self
152 def compare (self
, other
):
153 return self
.clocks
- other
.clocks
162 names
= (0, 0, 1, 1, 2, 3, 3, 4, 4, 5, 5, 6)
163 alterations
= (0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 1, 0)
164 alteration_names
= ('eses', 'es', '', 'is' , 'isis')
165 def __init__ (self
, clocks
, pitch
, velocity
):
167 self
.velocity
= velocity
170 self
.duration
= Duration (clocks
)
171 (self
.octave
, self
.notename
, self
.alteration
) = self
.o_n_a ()
175 # minor scale: la-la (= + 5) '''
177 n
= self
.names
[(self
.pitch
) % 12]
178 a
= self
.alterations
[(self
.pitch
) % 12]
180 if a
and global_options
.key
.flats
:
181 a
= - self
.alterations
[(self
.pitch
) % 12]
184 # By tradition, all scales now consist of a sequence
185 # of 7 notes each with a distinct name, from amongst
186 # a b c d e f g. But, minor scales have a wide
187 # second interval at the top - the 'leading note' is
188 # sharped. (Why? it just works that way! Anything
189 # else doesn't sound as good and isn't as flexible at
190 # saying things. In medieval times, scales only had 6
191 # notes to avoid this problem - the hexachords.)
193 # So, the d minor scale is d e f g a b-flat c-sharp d
194 # - using d-flat for the leading note would skip the
195 # name c and duplicate the name d. Why isn't c-sharp
196 # put in the key signature? Tradition. (It's also
197 # supposedly based on the Pythagorean theory of the
198 # cycle of fifths, but that really only applies to
199 # major scales...) Anyway, g minor is g a b-flat c d
200 # e-flat f-sharp g, and all the other flat minor keys
201 # end up with a natural leading note. And there you
204 # John Sankey <bf250@freenet.carleton.ca>
206 # Let's also do a-minor: a b c d e f gis a
210 o
= self
.pitch
/ 12 - 4
212 key
= global_options
.key
215 if (key
.sharps
== 0 and key
.flats
== 0
216 and n
== 5 and a
== -1):
219 elif key
.flats
== 1 and n
== 1 and a
== -1:
222 elif key
.flats
== 2 and n
== 4 and a
== -1:
225 elif key
.sharps
== 5 and n
== 4 and a
== 0:
228 elif key
.sharps
== 6 and n
== 1 and a
== 0:
231 elif key
.sharps
== 7 and n
== 5 and a
== 0:
235 if key
.flats
>= 6 and n
== 6 and a
== 0:
236 n
= 0; a
= -1; o
= o
+ 1
238 if key
.flats
>= 7 and n
== 2 and a
== 0:
242 if key
.sharps
>= 3 and n
== 3 and a
== 0:
245 if key
.sharps
>= 4 and n
== 0 and a
== 0:
246 n
= 6; a
= 1; o
= o
- 1
251 s
= chr ((self
.notename
+ 2) % 7 + ord ('a'))
252 return 'Note(%s %s)' % (s
, self
.duration
.dump ())
254 def dump (self
, dump_dur
= 1):
255 global reference_note
256 s
= chr ((self
.notename
+ 2) % 7 + ord ('a'))
257 s
= s
+ self
.alteration_names
[self
.alteration
+ 2]
258 if global_options
.absolute_pitches
:
261 delta
= self
.pitch
- reference_note
.pitch
262 commas
= sign (delta
) * (abs (delta
) / 12)
264 * (self
.notename
- reference_note
.notename
) + 7)
266 or ((self
.notename
== reference_note
.notename
)
267 and (abs (delta
) > 4) and (abs (delta
) < 12))):
268 commas
= commas
+ sign (delta
)
273 s
= s
+ "," * -commas
275 ## FIXME: compile fix --jcn
276 if (dump_dur
and (global_options
.explicit_durations
277 or self
.duration
.compare (reference_note
.duration
))):
278 s
= s
+ self
.duration
.dump ()
280 reference_note
= self
287 def __init__ (self
, num
, den
):
292 def bar_clocks (self
):
293 return clocks_per_1
* self
.num
/ self
.den
296 return 'Time(%d/%d)' % (self
.num
, self
.den
)
301 return '\n ' + '\\time %d/%d ' % (self
.num
, self
.den
) + '\n '
304 def __init__ (self
, seconds_per_1
):
306 self
.seconds_per_1
= seconds_per_1
309 return 'Tempo(%d)' % self
.bpm ()
312 return 4 * 60 / self
.seconds_per_1
315 return '\n ' + '\\tempo 4 = %d ' % (self
.bpm ()) + '\n '
318 clefs
= ('"bass_8"', 'bass', 'violin', '"violin^8"')
319 def __init__ (self
, type):
323 return 'Clef(%s)' % self
.clefs
[self
.type]
326 return '\n \\clef %s\n ' % self
.clefs
[self
.type]
329 key_sharps
= ('c', 'g', 'd', 'a', 'e', 'b', 'fis')
330 key_flats
= ('BUG', 'f', 'bes', 'es', 'as', 'des', 'ges')
332 def __init__ (self
, sharps
, flats
, minor
):
339 global_options
.key
= self
342 if self
.sharps
and self
.flats
:
346 k
= (ord ('cfbeadg'[self
.flats
% 7]) - ord ('a') - 2 -2 * self
.minor
+ 7) % 7
348 k
= (ord ('cgdaebf'[self
.sharps
% 7]) - ord ('a') - 2 -2 * self
.minor
+ 7) % 7
351 name
= chr ((k
+ 2) % 7 + ord ('a'))
353 name
= chr ((k
+ 2) % 7 + ord ('a'))
355 # fis cis gis dis ais eis bis
356 sharps
= (2, 4, 6, 1, 3, 5, 7)
357 # bes es as des ges ces fes
358 flats
= (6, 4, 2, 7, 5, 3, 1)
361 if flats
[k
] <= self
.flats
:
364 if sharps
[k
] <= self
.sharps
:
368 name
= name
+ Note
.alteration_names
[a
+ 2]
376 return '\n\n ' + s
+ '\n '
384 'SEQUENCE_TRACK_NAME',
390 def __init__ (self
, type, text
):
396 # urg, we should be sure that we're in a lyrics staff
397 if self
.type == midi
.LYRIC
:
398 s
= '"%s"' % self
.text
399 d
= Duration (self
.clocks
)
400 if (global_options
.explicit_durations
401 or d
.compare (reference_note
.duration
)):
402 s
= s
+ Duration (self
.clocks
).dump ()
405 s
= '\n % [' + self
.text_types
[self
.type] + '] ' + self
.text
+ '\n '
409 return 'Text(%d=%s)' % (self
.type, self
.text
)
413 def split_track (track
):
420 if data
[0] > 0x7f and data
[0] < 0xf0:
422 e
= (e
[0], tuple ([data
[0] & 0xf0] + data
[1:]))
432 for v
in chs
.values ():
433 events
= events_on_channel (v
)
434 thread
= unthread_notes (events
)
436 threads
.append (thread
)
440 def quantise_clocks (clocks
, quant
):
441 q
= int (clocks
/ quant
) * quant
443 for tquant
in allowed_tuplet_clocks
:
444 if int (clocks
/ tquant
) * tquant
== clocks
:
446 if 2 * (clocks
- q
) > quant
:
450 def end_note (pitches
, notes
, t
, e
):
452 (lt
, vel
) = pitches
[e
]
462 if duration_quant_clocks
:
463 d
= quantise_clocks (d
, duration_quant_clocks
)
465 d
= duration_quant_clocks
468 (lt
, Note (d
, e
, vel
)))
473 def events_on_channel (channel
):
483 if start_quant_clocks
:
484 t
= quantise_clocks (t
, start_quant_clocks
)
487 if (e
[1][0] == midi
.NOTE_OFF
488 or (e
[1][0] == midi
.NOTE_ON
and e
[1][2] == 0)):
489 end_note (pitches
, notes
, t
, e
[1][1])
491 elif e
[1][0] == midi
.NOTE_ON
:
492 if not pitches
.has_key (e
[1][1]):
493 pitches
[e
[1][1]] = (t
, e
[1][2])
495 # all include ALL_NOTES_OFF
496 elif (e
[1][0] >= midi
.ALL_SOUND_OFF
497 and e
[1][0] <= midi
.POLY_MODE_ON
):
499 end_note (pitches
, notes
, t
, i
)
501 elif e
[1][0] == midi
.META_EVENT
:
502 if e
[1][1] == midi
.END_OF_TRACK
:
504 end_note (pitches
, notes
, t
, i
)
507 elif e
[1][1] == midi
.SET_TEMPO
:
508 (u0
, u1
, u2
) = map (ord, e
[1][2])
509 us_per_4
= u2
+ 256 * (u1
+ 256 * u0
)
510 seconds_per_1
= us_per_4
* 4 / 1e6
511 events
.append ((t
, Tempo (seconds_per_1
)))
512 elif e
[1][1] == midi
.TIME_SIGNATURE
:
513 (num
, dur
, clocks4
, count32
) = map (ord, e
[1][2])
515 events
.append ((t
, Time (num
, den
)))
516 elif e
[1][1] == midi
.KEY_SIGNATURE
:
517 (alterations
, minor
) = map (ord, e
[1][2])
520 if alterations
< 127:
523 flats
= 256 - alterations
525 k
= Key (sharps
, flats
, minor
)
526 events
.append ((t
, k
))
528 # ugh, must set key while parsing
529 # because Note init uses key
530 # Better do Note.calc () at dump time?
531 global_options
.key
= k
533 elif (e
[1][1] == midi
.LYRIC
534 or (global_options
.text_lyrics
and e
[1][1] == midi
.TEXT_EVENT
)):
536 last_lyric
.clocks
= t
- last_time
537 events
.append ((last_time
, last_lyric
))
539 last_lyric
= Text (midi
.LYRIC
, e
[1][2])
541 elif (e
[1][1] >= midi
.SEQUENCE_NUMBER
542 and e
[1][1] <= midi
.CUE_POINT
):
543 events
.append ((t
, Text (e
[1][1], e
[1][2])))
545 if global_options
.verbose
:
546 sys
.stderr
.write ("SKIP: %s\n" % `e`
)
549 if global_options
.verbose
:
550 sys
.stderr
.write ("SKIP: %s\n" % `e`
)
554 # last_lyric.clocks = t - last_time
556 last_lyric
.clocks
= clocks_per_4
557 events
.append ((last_time
, last_lyric
))
562 if i
< len (events
) and notes
[0][0] >= events
[i
][0]:
565 events
.insert (i
, notes
[0])
569 def unthread_notes (channel
):
578 if (e
[1].__class
__ == Note
579 and ((t
== start_busy_t
580 and e
[1].clocks
+ t
== end_busy_t
)
581 or t
>= end_busy_t
)):
584 end_busy_t
= t
+ e
[1].clocks
585 elif (e
[1].__class
__ == Time
586 or e
[1].__class
__ == Key
587 or e
[1].__class
__ == Text
588 or e
[1].__class
__ == Tempo
):
592 threads
.append (thread
)
607 def dump_skip (skip
, clocks
):
608 return skip
+ Duration (clocks
).dump () + ' '
617 if i
.__class
__ == Note
:
622 s
= s
+ dump (notes
[0])
623 elif len (notes
) > 1:
624 global reference_note
626 s
= s
+ notes
[0].dump (dump_dur
= 0)
629 s
= s
+ i
.dump (dump_dur
= 0 )
632 s
= s
+ notes
[0].duration
.dump () + ' '
636 def dump_bar_line (last_bar_t
, t
, bar_count
):
638 bar_t
= time
.bar_clocks ()
639 if t
- last_bar_t
>= bar_t
:
640 bar_count
= bar_count
+ (t
- last_bar_t
) / bar_t
642 if t
- last_bar_t
== bar_t
:
643 s
= '|\n %% %d\n ' % bar_count
646 # urg, this will barf at meter changes
647 last_bar_t
= last_bar_t
+ (t
- last_bar_t
) / bar_t
* bar_t
649 return (s
, last_bar_t
, bar_count
)
652 def dump_channel (thread
, skip
):
653 global reference_note
, time
655 global_options
.key
= Key (0, 0, 0)
657 # urg LilyPond doesn't start at c4, but
658 # remembers from previous tracks!
659 # reference_note = Note (clocks_per_4, 4*12, 0)
660 reference_note
= Note (0, 4*12, 0)
666 if last_e
and last_e
[0] == e
[0]:
670 chs
.append ((last_e
[0], ch
))
677 chs
.append ((last_e
[0], ch
))
687 i
= lines
[-1].rfind ('\n') + 1
688 if len (lines
[-1][i
:]) > LINE_BELL
:
693 if bar_max
and t
> time
.bar_clocks () * bar_max
:
694 d
= time
.bar_clocks () * bar_max
- last_t
695 lines
[-1] = lines
[-1] + dump_skip (skip
, d
)
697 errorport
.write ('BUG: time skew')
699 (s
, last_bar_t
, bar_count
) = dump_bar_line (last_bar_t
,
702 if bar_max
and bar_count
> bar_max
:
705 lines
[-1] = lines
[-1] + s
706 lines
[-1] = lines
[-1] + dump_chord (ch
[1])
710 if i
.clocks
> clocks
:
715 (s
, last_bar_t
, bar_count
) = dump_bar_line (last_bar_t
,
717 lines
[-1] = lines
[-1] + s
719 return '\n '.join (lines
) + '\n'
721 def number2ascii (i
):
726 s
= '%c' % (m
+ ord ('A')) + s
731 return 'track' + number2ascii (i
)
733 def channel_name (i
):
734 return 'channel' + number2ascii (i
)
736 def dump_track (channels
, n
):
738 track
= track_name (n
)
739 clef
= guess_clef (channels
)
741 for i
in range (len (channels
)):
742 channel
= channel_name (i
)
743 item
= thread_first_item (channels
[i
])
745 if item
and item
.__class
__ == Note
:
747 s
= s
+ '%s = ' % (track
+ channel
)
748 if not global_options
.absolute_pitches
:
749 s
= s
+ '\\relative c '
750 elif item
and item
.__class
__ == Text
:
752 s
= s
+ '%s = \\lyricmode ' % (track
+ channel
)
755 s
= s
+ '%s = ' % (track
+ channel
)
757 s
= s
+ ' ' + dump_channel (channels
[i
][0], skip
)
760 s
= s
+ '%s = <<\n' % track
763 s
= s
+ clef
.dump () + '\n'
765 for i
in range (len (channels
)):
766 channel
= channel_name (i
)
767 item
= thread_first_item (channels
[i
])
768 if item
and item
.__class
__ == Text
:
769 s
= s
+ ' \\context Lyrics = %s \\%s\n' % (channel
,
772 s
= s
+ ' \\context Voice = %s \\%s\n' % (channel
,
777 def thread_first_item (thread
):
780 if (event
[1].__class
__ == Note
781 or (event
[1].__class
__ == Text
782 and event
[1].type == midi
.LYRIC
)):
787 def track_first_item (track
):
789 first
= thread_first_item (thread
)
794 def guess_clef (track
):
800 if event
[1].__class
__ == Note
:
802 p
= p
+ event
[1].pitch
803 if i
and p
/ i
<= 3*12:
805 elif i
and p
/ i
<= 5*12:
807 elif i
and p
/ i
>= 7*12:
813 def convert_midi (in_file
, out_file
):
814 global clocks_per_1
, clocks_per_4
, key
815 global start_quant_clocks
816 global duration_quant_clocks
817 global allowed_tuplet_clocks
819 str = open (in_file
, 'rb').read ()
820 midi_dump
= midi
.parse (str)
822 clocks_per_1
= midi_dump
[0][1]
823 clocks_per_4
= clocks_per_1
/ 4
825 if global_options
.start_quant
:
826 start_quant_clocks
= clocks_per_1
/ global_options
.start_quant
828 if global_options
.duration_quant
:
829 duration_quant_clocks
= clocks_per_1
/ global_options
.duration_quant
831 allowed_tuplet_clocks
= []
832 for (dur
, num
, den
) in global_options
.allowed_tuplets
:
833 allowed_tuplet_clocks
.append (clocks_per_1
/ dur
* num
/ den
)
835 if global_options
.verbose
:
836 print 'allowed tuplet clocks:', allowed_tuplet_clocks
839 for t
in midi_dump
[1]:
840 global_options
.key
= Key (0, 0, 0)
841 tracks
.append (split_track (t
))
843 tag
= '%% Lily was here -- automatically converted by %s from %s' % ( program_name
, in_file
)
847 s
= tag
+ '\n\\version "2.7.38"\n\n'
848 for i
in range (len (tracks
)):
849 s
= s
+ dump_track (tracks
[i
], i
)
851 s
= s
+ '\n\\score {\n <<\n'
855 track
= track_name (i
)
856 item
= track_first_item (t
)
858 if item
and item
.__class
__ == Note
:
859 s
= s
+ ' \\context Staff=%s \\%s\n' % (track
, track
)
860 elif item
and item
.__class
__ == Text
:
861 s
= s
+ ' \\context Lyrics=%s \\%s\n' % (track
, track
)
866 progress (_ ("%s output to `%s'...") % ('LY', out_file
))
871 handle
= open (out_file
, 'w')
877 def get_option_parser ():
878 p
= ly
.get_option_parser (usage
=_ ("%s [OPTION]... FILE") % 'midi2ly',
879 description
=_ ("Convert %s to LilyPond input.\n") % 'MIDI',
880 add_help_option
=False)
882 p
.add_option ('-a', '--absolute-pitches',
884 help=_ ('print absolute pitches'))
885 p
.add_option ('-d', '--duration-quant',
887 help=_ ('quantise note durations on DUR'))
888 p
.add_option ('-e', '--explicit-durations',
890 help=_ ('print explicit durations'))
891 p
.add_option('-h', '--help',
893 help=_ ('show this help and exit'))
894 p
.add_option('-k', '--key', help=_ ('set key: ALT=+sharps|-flats; MINOR=1'),
895 metavar
=_ ('ALT[:MINOR]'),
897 p
.add_option ('-o', '--output', help=_ ('write output to FILE'),
900 p
.add_option ('-p', '--preview', help=_ ('preview of first 4 bars'),
902 p
.add_option ('-s', '--start-quant',help= _ ('quantise note starts on DUR'),
904 p
.add_option ('-t', '--allow-tuplet',
905 metavar
=_ ('DUR*NUM/DEN'),
907 dest
='allowed_tuplets',
908 help=_ ('allow tuplet durations DUR*NUM/DEN'),
910 p
.add_option ('-V', '--verbose', help=_ ('be verbose'),
913 p
.version
= 'midi2ly (LilyPond) @TOPLEVEL_VERSION@'
914 p
.add_option ('--version',
916 help=_ ('show version number and exit'))
917 p
.add_option ('-w', '--warranty', help=_ ('show warranty and copyright'),
920 p
.add_option ('-x', '--text-lyrics', help=_ ('treat every text as a lyric'),
923 p
.add_option_group (ly
.display_encode (_ ('Examples')),
925 $ midi2ly --key=-2:1 --duration-quant=32 --allow-tuplet=4*2/3 --allow-tuplet=2*4/3 foo.midi
927 p
.add_option_group ('',
929 _ ('Report bugs via %s')
930 % 'http://post.gmane.org/post.php'
931 '?group=gmane.comp.gnu.lilypond.bugs') + '\n')
937 opt_parser
= get_option_parser ()
938 (options
, args
) = opt_parser
.parse_args ()
940 if not args
or args
[0] == '-':
941 opt_parser
.print_help ()
942 ly
.stderr_write ('\n%s: %s %s\n' % (program_name
, _ ('error: '),
943 _ ('no files specified on command line.')))
946 if options
.duration_quant
:
947 options
.duration_quant
= int (options
.duration_quant
)
953 (alterations
, minor
) = map (int, (options
.key
+ ':0').split (':'))[0:2]
959 flats
= - alterations
961 options
.key
= Key (sharps
, flats
, minor
)
963 if options
.start_quant
:
964 options
.start_quant
= int (options
.start_quant
)
970 options
.allowed_tuplets
= [map (int, a
.replace ('/','*').split ('*'))
971 for a
in options
.allowed_tuplets
]
974 sys
.stderr
.write ('Allowed tuplets: %s\n' % `options
.allowed_tuplets`
)
976 global global_options
977 global_options
= options
982 files
= do_options ()
986 g
= strip_extension (g
, '.midi')
987 g
= strip_extension (g
, '.mid')
988 g
= strip_extension (g
, '.MID')
989 (outdir
, outbase
) = ('','')
991 if not global_options
.output
:
993 outbase
= os
.path
.basename (g
)
994 o
= os
.path
.join (outdir
, outbase
+ '-midi.ly')
995 elif global_options
.output
[-1] == os
.sep
:
996 outdir
= global_options
.output
997 outbase
= os
.path
.basename (g
)
998 os
.path
.join (outdir
, outbase
+ '-gen.ly')
1000 o
= global_options
.output
1001 (outdir
, outbase
) = os
.path
.split (o
)
1003 if outdir
!= '.' and outdir
!= '':
1005 os
.mkdir (outdir
, 0777)
1011 if __name__
== '__main__':