3 # midi2ly.py -- LilyPond midi import script
5 # source file of the GNU LilyPond music typesetter
7 # (c) 1998--2008 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 ################################################################
40 scale_steps
= [0, 2, 4, 5, 7, 9, 11]
48 start_quant_clocks
= 0
50 duration_quant_clocks
= 0
51 allowed_tuplet_clocks
= []
54 ################################################################
57 program_name
= sys
.argv
[0]
58 program_version
= '@TOPLEVEL_VERSION@'
60 errorport
= sys
.stderr
63 sys
.stdout
.write ('%s (GNU LilyPond) %s\n' % (program_name
, program_version
))
67 ly
.encoded_write (sys
.stdout
, '''
76 _('Distributed under terms of the GNU General Public License.'),
77 _('It comes with NO WARRANTY.')))
81 ly
.encoded_write (errorport
, s
+ '\n')
84 progress (_ ("warning: ") + s
)
87 progress (_ ("error: ") + s
)
88 raise Exception (_ ("Exiting... "))
90 def system (cmd
, ignore_error
= 0):
91 return ly
.system (cmd
, ignore_error
=ignore_error
)
93 def strip_extension (f
, ext
):
94 (p
, e
) = os
.path
.splitext (f
)
101 allowed_durs
= (1, 2, 4, 8, 16, 32, 64, 128)
102 def __init__ (self
, clocks
):
105 self
.clocks
= duration_quant_clocks
106 (self
.dur
, self
.num
, self
.den
) = self
.dur_num_den (clocks
)
108 def dur_num_den (self
, clocks
):
109 for i
in range (len (allowed_tuplet_clocks
)):
110 if clocks
== allowed_tuplet_clocks
[i
]:
111 return global_options
.allowed_tuplets
[i
]
113 dur
= 0; num
= 1; den
= 1;
114 g
= gcd (clocks
, clocks_per_1
)
116 (dur
, num
) = (clocks_per_1
/ g
, clocks
/ g
)
117 if not dur
in self
.allowed_durs
:
118 dur
= 4; num
= clocks
; den
= clocks_per_4
119 return (dur
, num
, den
)
125 elif self
.num
== 3 and self
.dur
!= 1:
126 s
= '%d.' % (self
.dur
/ 2)
128 s
= '%d*%d' % (self
.dur
, self
.num
)
130 s
= '%d*%d/%d' % (self
.dur
, self
.num
, self
.den
)
132 global reference_note
133 reference_note
.duration
= self
137 def compare (self
, other
):
138 return self
.clocks
- other
.clocks
147 names
= (0, 0, 1, 1, 2, 3, 3, 4, 4, 5, 5, 6)
148 alterations
= (0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 1, 0)
149 alteration_names
= ('eses', 'es', '', 'is' , 'isis')
150 def __init__ (self
, clocks
, pitch
, velocity
):
152 self
.velocity
= velocity
155 self
.duration
= Duration (clocks
)
156 (self
.octave
, self
.notename
, self
.alteration
) = self
.o_n_a ()
160 # minor scale: la-la (= + 5) '''
162 n
= self
.names
[(self
.pitch
) % 12]
163 a
= self
.alterations
[(self
.pitch
) % 12]
165 if a
and global_options
.key
.flats
:
166 a
= - self
.alterations
[(self
.pitch
) % 12]
169 # By tradition, all scales now consist of a sequence
170 # of 7 notes each with a distinct name, from amongst
171 # a b c d e f g. But, minor scales have a wide
172 # second interval at the top - the 'leading note' is
173 # sharped. (Why? it just works that way! Anything
174 # else doesn't sound as good and isn't as flexible at
175 # saying things. In medieval times, scales only had 6
176 # notes to avoid this problem - the hexachords.)
178 # So, the d minor scale is d e f g a b-flat c-sharp d
179 # - using d-flat for the leading note would skip the
180 # name c and duplicate the name d. Why isn't c-sharp
181 # put in the key signature? Tradition. (It's also
182 # supposedly based on the Pythagorean theory of the
183 # cycle of fifths, but that really only applies to
184 # major scales...) Anyway, g minor is g a b-flat c d
185 # e-flat f-sharp g, and all the other flat minor keys
186 # end up with a natural leading note. And there you
189 # John Sankey <bf250@freenet.carleton.ca>
191 # Let's also do a-minor: a b c d e f gis a
195 o
= self
.pitch
/ 12 - 4
197 key
= global_options
.key
200 if (key
.sharps
== 0 and key
.flats
== 0
201 and n
== 5 and a
== -1):
204 elif key
.flats
== 1 and n
== 1 and a
== -1:
207 elif key
.flats
== 2 and n
== 4 and a
== -1:
210 elif key
.sharps
== 5 and n
== 4 and a
== 0:
213 elif key
.sharps
== 6 and n
== 1 and a
== 0:
216 elif key
.sharps
== 7 and n
== 5 and a
== 0:
220 if key
.flats
>= 6 and n
== 6 and a
== 0:
221 n
= 0; a
= -1; o
= o
+ 1
223 if key
.flats
>= 7 and n
== 2 and a
== 0:
227 if key
.sharps
>= 3 and n
== 3 and a
== 0:
230 if key
.sharps
>= 4 and n
== 0 and a
== 0:
231 n
= 6; a
= 1; o
= o
- 1
236 s
= chr ((self
.notename
+ 2) % 7 + ord ('a'))
237 return 'Note(%s %s)' % (s
, self
.duration
.dump())
239 def dump (self
, dump_dur
= 1):
240 global reference_note
241 s
= chr ((self
.notename
+ 2) % 7 + ord ('a'))
242 s
= s
+ self
.alteration_names
[self
.alteration
+ 2]
243 if global_options
.absolute_pitches
:
246 delta
= self
.pitch
- reference_note
.pitch
247 commas
= sign (delta
) * (abs (delta
) / 12)
249 * (self
.notename
- reference_note
.notename
) + 7) \
251 or ((self
.notename
== reference_note
.notename
) \
252 and (abs (delta
) > 4) and (abs (delta
) < 12)):
253 commas
= commas
+ sign (delta
)
258 s
= s
+ "," * -commas
260 ## FIXME: compile fix --jcn
261 if dump_dur
and (global_options
.explicit_durations \
262 or self
.duration
.compare (reference_note
.duration
)):
263 s
= s
+ self
.duration
.dump ()
265 reference_note
= self
272 def __init__ (self
, num
, den
):
277 def bar_clocks (self
):
278 return clocks_per_1
* self
.num
/ self
.den
281 return 'Time(%d/%d)' % (self
.num
, self
.den
)
286 return '\n ' + '\\time %d/%d ' % (self
.num
, self
.den
) + '\n '
289 def __init__ (self
, seconds_per_1
):
291 self
.seconds_per_1
= seconds_per_1
294 return 'Tempo(%d)' % self
.bpm ()
297 return 4 * 60 / self
.seconds_per_1
300 return '\n ' + '\\tempo 4 = %d ' % (self
.bpm()) + '\n '
303 clefs
= ('"bass_8"', 'bass', 'violin', '"violin^8"')
304 def __init__ (self
, type):
308 return 'Clef(%s)' % self
.clefs
[self
.type]
311 return '\n \\clef %s\n ' % self
.clefs
[self
.type]
314 key_sharps
= ('c', 'g', 'd', 'a', 'e', 'b', 'fis')
315 key_flats
= ('BUG', 'f', 'bes', 'es', 'as', 'des', 'ges')
317 def __init__ (self
, sharps
, flats
, minor
):
324 global_options
.key
= self
327 if self
.sharps
and self
.flats
:
331 k
= (ord ('cfbeadg'[self
.flats
% 7]) - ord ('a') - 2 -2 * self
.minor
+ 7) % 7
333 k
= (ord ('cgdaebf'[self
.sharps
% 7]) - ord ('a') - 2 -2 * self
.minor
+ 7) % 7
336 name
= chr ((k
+ 2) % 7 + ord ('a'))
338 name
= chr ((k
+ 2) % 7 + ord ('a'))
340 # fis cis gis dis ais eis bis
341 sharps
= (2, 4, 6, 1, 3, 5, 7)
342 # bes es as des ges ces fes
343 flats
= (6, 4, 2, 7, 5, 3, 1)
346 if flats
[k
] <= self
.flats
:
349 if sharps
[k
] <= self
.sharps
:
353 name
= name
+ Note
.alteration_names
[a
+ 2]
361 return '\n\n ' + s
+ '\n '
369 'SEQUENCE_TRACK_NAME',
375 def __init__ (self
, type, text
):
381 # urg, we should be sure that we're in a lyrics staff
382 if self
.type == midi
.LYRIC
:
383 s
= '"%s"' % self
.text
384 d
= Duration (self
.clocks
)
385 if global_options
.explicit_durations \
386 or d
.compare (reference_note
.duration
):
387 s
= s
+ Duration (self
.clocks
).dump ()
390 s
= '\n % [' + self
.text_types
[self
.type] + '] ' + self
.text
+ '\n '
394 return 'Text(%d=%s)' % (self
.type, self
.text
)
398 def split_track (track
):
405 if data
[0] > 0x7f and data
[0] < 0xf0:
407 e
= (e
[0], tuple ([data
[0] & 0xf0] + data
[1:]))
417 for v
in chs
.values ():
418 events
= events_on_channel (v
)
419 thread
= unthread_notes (events
)
421 threads
.append (thread
)
425 def quantise_clocks (clocks
, quant
):
426 q
= int (clocks
/ quant
) * quant
428 for tquant
in allowed_tuplet_clocks
:
429 if int (clocks
/ tquant
) * tquant
== clocks
:
431 if 2 * (clocks
- q
) > quant
:
435 def end_note (pitches
, notes
, t
, e
):
437 (lt
, vel
) = pitches
[e
]
447 if duration_quant_clocks
:
448 d
= quantise_clocks (d
, duration_quant_clocks
)
450 d
= duration_quant_clocks
453 (lt
, Note (d
, e
, vel
)))
458 def events_on_channel (channel
):
468 if start_quant_clocks
:
469 t
= quantise_clocks (t
, start_quant_clocks
)
472 if e
[1][0] == midi
.NOTE_OFF \
473 or (e
[1][0] == midi
.NOTE_ON
and e
[1][2] == 0):
474 end_note (pitches
, notes
, t
, e
[1][1])
476 elif e
[1][0] == midi
.NOTE_ON
:
477 if not pitches
.has_key (e
[1][1]):
478 pitches
[e
[1][1]] = (t
, e
[1][2])
480 # all include ALL_NOTES_OFF
481 elif e
[1][0] >= midi
.ALL_SOUND_OFF \
482 and e
[1][0] <= midi
.POLY_MODE_ON
:
484 end_note (pitches
, notes
, t
, i
)
486 elif e
[1][0] == midi
.META_EVENT
:
487 if e
[1][1] == midi
.END_OF_TRACK
:
489 end_note (pitches
, notes
, t
, i
)
492 elif e
[1][1] == midi
.SET_TEMPO
:
493 (u0
, u1
, u2
) = map (ord, e
[1][2])
494 us_per_4
= u2
+ 256 * (u1
+ 256 * u0
)
495 seconds_per_1
= us_per_4
* 4 / 1e6
496 events
.append ((t
, Tempo (seconds_per_1
)))
497 elif e
[1][1] == midi
.TIME_SIGNATURE
:
498 (num
, dur
, clocks4
, count32
) = map (ord, e
[1][2])
500 events
.append ((t
, Time (num
, den
)))
501 elif e
[1][1] == midi
.KEY_SIGNATURE
:
502 (alterations
, minor
) = map (ord, e
[1][2])
505 if alterations
< 127:
508 flats
= 256 - alterations
510 k
= Key (sharps
, flats
, minor
)
511 events
.append ((t
, k
))
513 # ugh, must set key while parsing
514 # because Note init uses key
515 # Better do Note.calc () at dump time?
516 global_options
.key
= k
518 elif e
[1][1] == midi
.LYRIC \
519 or (global_options
.text_lyrics
and e
[1][1] == midi
.TEXT_EVENT
):
521 last_lyric
.clocks
= t
- last_time
522 events
.append ((last_time
, last_lyric
))
524 last_lyric
= Text (midi
.LYRIC
, e
[1][2])
526 elif e
[1][1] >= midi
.SEQUENCE_NUMBER \
527 and e
[1][1] <= midi
.CUE_POINT
:
528 events
.append ((t
, Text (e
[1][1], e
[1][2])))
530 if global_options
.verbose
:
531 sys
.stderr
.write ("SKIP: %s\n" % `e`
)
534 if global_options
.verbose
:
535 sys
.stderr
.write ("SKIP: %s\n" % `e`
)
539 # last_lyric.clocks = t - last_time
541 last_lyric
.clocks
= clocks_per_4
542 events
.append ((last_time
, last_lyric
))
547 if i
< len (events
) and notes
[0][0] >= events
[i
][0]:
550 events
.insert (i
, notes
[0])
554 def unthread_notes (channel
):
563 if e
[1].__class
__ == Note \
564 and ((t
== start_busy_t \
565 and e
[1].clocks
+ t
== end_busy_t
) \
569 end_busy_t
= t
+ e
[1].clocks
570 elif e
[1].__class
__ == Time \
571 or e
[1].__class
__ == Key \
572 or e
[1].__class
__ == Text \
573 or e
[1].__class
__ == Tempo
:
577 threads
.append (thread
)
592 def dump_skip (skip
, clocks
):
593 return skip
+ Duration (clocks
).dump () + ' '
602 if i
.__class
__ == Note
:
607 s
= s
+ dump (notes
[0])
608 elif len (notes
) > 1:
609 global reference_note
611 s
= s
+ notes
[0].dump (dump_dur
= 0)
614 s
= s
+ i
.dump (dump_dur
= 0 )
617 s
= s
+ notes
[0].duration
.dump() + ' '
621 def dump_bar_line (last_bar_t
, t
, bar_count
):
623 bar_t
= time
.bar_clocks ()
624 if t
- last_bar_t
>= bar_t
:
625 bar_count
= bar_count
+ (t
- last_bar_t
) / bar_t
627 if t
- last_bar_t
== bar_t
:
628 s
= '|\n %% %d\n ' % bar_count
631 # urg, this will barf at meter changes
632 last_bar_t
= last_bar_t
+ (t
- last_bar_t
) / bar_t
* bar_t
634 return (s
, last_bar_t
, bar_count
)
637 def dump_channel (thread
, skip
):
638 global reference_note
, time
640 global_options
.key
= Key (0, 0, 0)
642 # urg LilyPond doesn't start at c4, but
643 # remembers from previous tracks!
644 # reference_note = Note (clocks_per_4, 4*12, 0)
645 reference_note
= Note (0, 4*12, 0)
651 if last_e
and last_e
[0] == e
[0]:
655 chs
.append ((last_e
[0], ch
))
662 chs
.append ((last_e
[0], ch
))
672 i
= lines
[-1].rfind ('\n') + 1
673 if len (lines
[-1][i
:]) > LINE_BELL
:
677 lines
[-1] = lines
[-1] + dump_skip (skip
, t
-last_t
)
679 errorport
.write ('BUG: time skew')
681 (s
, last_bar_t
, bar_count
) = dump_bar_line (last_bar_t
,
683 lines
[-1] = lines
[-1] + s
685 lines
[-1] = lines
[-1] + dump_chord (ch
[1])
689 if i
.clocks
> clocks
:
694 (s
, last_bar_t
, bar_count
) = dump_bar_line (last_bar_t
,
696 lines
[-1] = lines
[-1] + s
698 return '\n '.join (lines
) + '\n'
701 return 'track%c' % (i
+ ord ('A'))
703 def channel_name (i
):
704 return 'channel%c' % (i
+ ord ('A'))
706 def dump_track (channels
, n
):
708 track
= track_name (n
)
709 clef
= guess_clef (channels
)
711 for i
in range (len (channels
)):
712 channel
= channel_name (i
)
713 item
= thread_first_item (channels
[i
])
715 if item
and item
.__class
__ == Note
:
717 s
= s
+ '%s = ' % (track
+ channel
)
718 if not global_options
.absolute_pitches
:
719 s
= s
+ '\\relative c '
720 elif item
and item
.__class
__ == Text
:
722 s
= s
+ '%s = \\lyricmode ' % (track
+ channel
)
725 s
= s
+ '%s = ' % (track
+ channel
)
727 s
= s
+ ' ' + dump_channel (channels
[i
][0], skip
)
730 s
= s
+ '%s = <<\n' % track
733 s
= s
+ clef
.dump () + '\n'
735 for i
in range (len (channels
)):
736 channel
= channel_name (i
)
737 item
= thread_first_item (channels
[i
])
738 if item
and item
.__class
__ == Text
:
739 s
= s
+ ' \\context Lyrics = %s \\%s\n' % (channel
,
742 s
= s
+ ' \\context Voice = %s \\%s\n' % (channel
,
747 def thread_first_item (thread
):
750 if (event
[1].__class
__ == Note
751 or (event
[1].__class
__ == Text
752 and event
[1].type == midi
.LYRIC
)):
757 def track_first_item (track
):
759 first
= thread_first_item (thread
)
764 def guess_clef (track
):
770 if event
[1].__class
__ == Note
:
772 p
= p
+ event
[1].pitch
773 if i
and p
/ i
<= 3*12:
775 elif i
and p
/ i
<= 5*12:
777 elif i
and p
/ i
>= 7*12:
783 def convert_midi (in_file
, out_file
):
784 global clocks_per_1
, clocks_per_4
, key
785 global start_quant_clocks
786 global duration_quant_clocks
787 global allowed_tuplet_clocks
789 str = open (in_file
).read ()
790 midi_dump
= midi
.parse (str)
792 clocks_per_1
= midi_dump
[0][1]
793 clocks_per_4
= clocks_per_1
/ 4
795 if global_options
.start_quant
:
796 start_quant_clocks
= clocks_per_1
/ global_options
.start_quant
798 if global_options
.duration_quant
:
799 duration_quant_clocks
= clocks_per_1
/ global_options
.duration_quant
801 allowed_tuplet_clocks
= []
802 for (dur
, num
, den
) in global_options
.allowed_tuplets
:
803 allowed_tuplet_clocks
.append (clocks_per_1
/ den
)
806 for t
in midi_dump
[1]:
807 global_options
.key
= Key (0, 0, 0)
808 tracks
.append (split_track (t
))
810 tag
= '%% Lily was here -- automatically converted by %s from %s' % ( program_name
, in_file
)
814 s
= tag
+ '\n\\version "2.7.18"\n\n'
815 for i
in range (len (tracks
)):
816 s
= s
+ dump_track (tracks
[i
], i
)
818 s
= s
+ '\n\\score {\n <<\n'
822 track
= track_name (i
)
823 item
= track_first_item (t
)
825 if item
and item
.__class
__ == Note
:
826 s
= s
+ ' \\context Staff=%s \\%s\n' % (track
, track
)
827 elif item
and item
.__class
__ == Text
:
828 s
= s
+ ' \\context Lyrics=%s \\%s\n' % (track
, track
)
833 progress (_ ("%s output to `%s'...") % ('LY', out_file
))
838 handle
= open (out_file
, 'w')
844 def get_option_parser ():
845 p
= ly
.get_option_parser (usage
=_ ("%s [OPTION]... FILE") % 'midi2ly',
846 description
=_ ("Convert %s to LilyPond input.\n") % 'MIDI',
847 add_help_option
=False)
849 p
.add_option ('-a', '--absolute-pitches',
851 help=_ ("print absolute pitches"))
852 p
.add_option ('-d', '--duration-quant',
854 help=_ ("quantise note durations on DUR"))
855 p
.add_option ('-e', '--explicit-durations',
857 help=_ ("print explicit durations"))
858 p
.add_option("-h", "--help",
860 help=_ ("show this help and exit"))
861 p
.add_option('-k', '--key', help=_ ("set key: ALT=+sharps|-flats; MINOR=1"),
862 metavar
=_ ("ALT[:MINOR]"),
864 p
.add_option ('-o', '--output', help=_ ("write output to FILE"),
867 p
.add_option ('-s', '--start-quant',help= _ ("quantise note starts on DUR"),
869 p
.add_option ('-t', '--allow-tuplet',
870 metavar
=_ ("DUR*NUM/DEN"),
872 dest
="allowed_tuplets",
873 help=_ ("allow tuplet durations DUR*NUM/DEN"),
875 p
.add_option ('-V', '--verbose', help=_ ("be verbose"),
878 p
.version
= "midi2ly (LilyPond) @TOPLEVEL_VERSION@"
879 p
.add_option("--version",
881 help=_ ("show version number and exit"))
882 p
.add_option ('-w', '--warranty', help=_ ("show warranty and copyright"),
885 p
.add_option ('-x', '--text-lyrics', help=_ ("treat every text as a lyric"),
888 p
.add_option_group (ly
.display_encode (_ ("Examples")),
890 $ midi2ly --key=-2:1 --duration-quant=32 --allow-tuplet=4*2/3 --allow-tuplet=2*4/3 foo.midi
892 p
.add_option_group ('',
893 description
=(_ ('Report bugs via')
894 + ''' http://post.gmane.org/post.php'''
895 '''?group=gmane.comp.gnu.lilypond.bugs\n'''))
901 opt_parser
= get_option_parser()
902 (options
, args
) = opt_parser
.parse_args ()
904 if not args
or args
[0] == '-':
905 opt_parser
.print_help ()
906 ly
.stderr_write ('\n%s: %s %s\n' % (program_name
, _ ("error: "),
907 _ ("no files specified on command line.")))
910 if options
.duration_quant
:
911 options
.duration_quant
= int (options
.duration_quant
)
917 (alterations
, minor
) = map (int, (options
.key
+ ':0').split (':'))[0:2]
923 flats
= - alterations
925 options
.key
= Key (sharps
, flats
, minor
)
928 if options
.start_quant
:
929 options
.start_quant
= int (options
.start_quant
)
931 options
.allowed_tuplets
= [map (int, a
.replace ('/','*').split ('*'))
932 for a
in options
.allowed_tuplets
]
934 global global_options
935 global_options
= options
944 g
= strip_extension (g
, '.midi')
945 g
= strip_extension (g
, '.mid')
946 g
= strip_extension (g
, '.MID')
947 (outdir
, outbase
) = ('','')
949 if not global_options
.output
:
951 outbase
= os
.path
.basename (g
)
952 o
= os
.path
.join (outdir
, outbase
+ '-midi.ly')
953 elif global_options
.output
[-1] == os
.sep
:
954 outdir
= global_options
.output
955 outbase
= os
.path
.basename (g
)
956 os
.path
.join (outdir
, outbase
+ '-gen.ly')
958 o
= global_options
.output
959 (outdir
, outbase
) = os
.path
.split (o
)
961 if outdir
!= '.' and outdir
!= '':
963 os
.mkdir (outdir
, 0777)
968 if __name__
== '__main__':