3 # midi2ly.py -- LilyPond midi import script
5 # This file is part of LilyPond, the GNU music typesetter.
7 # Copyright (C) 1998--2009 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') % '2001--2009',
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
) \
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
:
692 lines
[-1] = lines
[-1] + dump_skip (skip
, t
-last_t
)
694 errorport
.write ('BUG: time skew')
696 (s
, last_bar_t
, bar_count
) = dump_bar_line (last_bar_t
,
698 lines
[-1] = lines
[-1] + s
700 lines
[-1] = lines
[-1] + dump_chord (ch
[1])
704 if i
.clocks
> clocks
:
709 (s
, last_bar_t
, bar_count
) = dump_bar_line (last_bar_t
,
711 lines
[-1] = lines
[-1] + s
713 return '\n '.join (lines
) + '\n'
716 return 'track%c' % (i
+ ord ('A'))
718 def channel_name (i
):
719 return 'channel%c' % (i
+ ord ('A'))
721 def dump_track (channels
, n
):
723 track
= track_name (n
)
724 clef
= guess_clef (channels
)
726 for i
in range (len (channels
)):
727 channel
= channel_name (i
)
728 item
= thread_first_item (channels
[i
])
730 if item
and item
.__class
__ == Note
:
732 s
= s
+ '%s = ' % (track
+ channel
)
733 if not global_options
.absolute_pitches
:
734 s
= s
+ '\\relative c '
735 elif item
and item
.__class
__ == Text
:
737 s
= s
+ '%s = \\lyricmode ' % (track
+ channel
)
740 s
= s
+ '%s = ' % (track
+ channel
)
742 s
= s
+ ' ' + dump_channel (channels
[i
][0], skip
)
745 s
= s
+ '%s = <<\n' % track
748 s
= s
+ clef
.dump () + '\n'
750 for i
in range (len (channels
)):
751 channel
= channel_name (i
)
752 item
= thread_first_item (channels
[i
])
753 if item
and item
.__class
__ == Text
:
754 s
= s
+ ' \\context Lyrics = %s \\%s\n' % (channel
,
757 s
= s
+ ' \\context Voice = %s \\%s\n' % (channel
,
762 def thread_first_item (thread
):
765 if (event
[1].__class
__ == Note
766 or (event
[1].__class
__ == Text
767 and event
[1].type == midi
.LYRIC
)):
772 def track_first_item (track
):
774 first
= thread_first_item (thread
)
779 def guess_clef (track
):
785 if event
[1].__class
__ == Note
:
787 p
= p
+ event
[1].pitch
788 if i
and p
/ i
<= 3*12:
790 elif i
and p
/ i
<= 5*12:
792 elif i
and p
/ i
>= 7*12:
798 def convert_midi (in_file
, out_file
):
799 global clocks_per_1
, clocks_per_4
, key
800 global start_quant_clocks
801 global duration_quant_clocks
802 global allowed_tuplet_clocks
804 str = open (in_file
).read ()
805 midi_dump
= midi
.parse (str)
807 clocks_per_1
= midi_dump
[0][1]
808 clocks_per_4
= clocks_per_1
/ 4
810 if global_options
.start_quant
:
811 start_quant_clocks
= clocks_per_1
/ global_options
.start_quant
813 if global_options
.duration_quant
:
814 duration_quant_clocks
= clocks_per_1
/ global_options
.duration_quant
816 allowed_tuplet_clocks
= []
817 for (dur
, num
, den
) in global_options
.allowed_tuplets
:
818 allowed_tuplet_clocks
.append (clocks_per_1
/ den
)
821 for t
in midi_dump
[1]:
822 global_options
.key
= Key (0, 0, 0)
823 tracks
.append (split_track (t
))
825 tag
= '%% Lily was here -- automatically converted by %s from %s' % ( program_name
, in_file
)
829 s
= tag
+ '\n\\version "2.7.18"\n\n'
830 for i
in range (len (tracks
)):
831 s
= s
+ dump_track (tracks
[i
], i
)
833 s
= s
+ '\n\\score {\n <<\n'
837 track
= track_name (i
)
838 item
= track_first_item (t
)
840 if item
and item
.__class
__ == Note
:
841 s
= s
+ ' \\context Staff=%s \\%s\n' % (track
, track
)
842 elif item
and item
.__class
__ == Text
:
843 s
= s
+ ' \\context Lyrics=%s \\%s\n' % (track
, track
)
848 progress (_ ("%s output to `%s'...") % ('LY', out_file
))
853 handle
= open (out_file
, 'w')
859 def get_option_parser ():
860 p
= ly
.get_option_parser (usage
=_ ("%s [OPTION]... FILE") % 'midi2ly',
861 description
=_ ("Convert %s to LilyPond input.\n") % 'MIDI',
862 add_help_option
=False)
864 p
.add_option ('-a', '--absolute-pitches',
866 help=_ ("print absolute pitches"))
867 p
.add_option ('-d', '--duration-quant',
869 help=_ ("quantise note durations on DUR"))
870 p
.add_option ('-e', '--explicit-durations',
872 help=_ ("print explicit durations"))
873 p
.add_option("-h", "--help",
875 help=_ ("show this help and exit"))
876 p
.add_option('-k', '--key', help=_ ("set key: ALT=+sharps|-flats; MINOR=1"),
877 metavar
=_ ("ALT[:MINOR]"),
879 p
.add_option ('-o', '--output', help=_ ("write output to FILE"),
882 p
.add_option ('-s', '--start-quant',help= _ ("quantise note starts on DUR"),
884 p
.add_option ('-t', '--allow-tuplet',
885 metavar
=_ ("DUR*NUM/DEN"),
887 dest
="allowed_tuplets",
888 help=_ ("allow tuplet durations DUR*NUM/DEN"),
890 p
.add_option ('-V', '--verbose', help=_ ("be verbose"),
893 p
.version
= "midi2ly (LilyPond) @TOPLEVEL_VERSION@"
894 p
.add_option("--version",
896 help=_ ("show version number and exit"))
897 p
.add_option ('-w', '--warranty', help=_ ("show warranty and copyright"),
900 p
.add_option ('-x', '--text-lyrics', help=_ ("treat every text as a lyric"),
903 p
.add_option_group (ly
.display_encode (_ ("Examples")),
905 $ midi2ly --key=-2:1 --duration-quant=32 --allow-tuplet=4*2/3 --allow-tuplet=2*4/3 foo.midi
907 p
.add_option_group ('',
909 _ ('Report bugs via %s')
910 % 'http://post.gmane.org/post.php'
911 '?group=gmane.comp.gnu.lilypond.bugs') + '\n')
917 opt_parser
= get_option_parser()
918 (options
, args
) = opt_parser
.parse_args ()
920 if not args
or args
[0] == '-':
921 opt_parser
.print_help ()
922 ly
.stderr_write ('\n%s: %s %s\n' % (program_name
, _ ("error: "),
923 _ ("no files specified on command line.")))
926 if options
.duration_quant
:
927 options
.duration_quant
= int (options
.duration_quant
)
933 (alterations
, minor
) = map (int, (options
.key
+ ':0').split (':'))[0:2]
939 flats
= - alterations
941 options
.key
= Key (sharps
, flats
, minor
)
944 if options
.start_quant
:
945 options
.start_quant
= int (options
.start_quant
)
947 options
.allowed_tuplets
= [map (int, a
.replace ('/','*').split ('*'))
948 for a
in options
.allowed_tuplets
]
950 global global_options
951 global_options
= options
960 g
= strip_extension (g
, '.midi')
961 g
= strip_extension (g
, '.mid')
962 g
= strip_extension (g
, '.MID')
963 (outdir
, outbase
) = ('','')
965 if not global_options
.output
:
967 outbase
= os
.path
.basename (g
)
968 o
= os
.path
.join (outdir
, outbase
+ '-midi.ly')
969 elif global_options
.output
[-1] == os
.sep
:
970 outdir
= global_options
.output
971 outbase
= os
.path
.basename (g
)
972 os
.path
.join (outdir
, outbase
+ '-gen.ly')
974 o
= global_options
.output
975 (outdir
, outbase
) = os
.path
.split (o
)
977 if outdir
!= '.' and outdir
!= '':
979 os
.mkdir (outdir
, 0777)
984 if __name__
== '__main__':