3 # Copyright (C) 2009-2010 W. Trevor King <wking@drexel.edu>
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Lesser General Public License as
7 # published by the Free Software Foundation, either version 3 of the
8 # License, or (at your option) any later version.
10 # This program is distributed in the hope that it will be useful, but
11 # WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 # Lesser General Public License for more details.
15 # You should have received a copy of the GNU Lesser General Public
16 # License along with this program. If not, see
17 # <http://www.gnu.org/licenses/>.
19 """Play ABC_ files using abc2midi_ and timidity_.
23 abcplay.py my_collection.abc:3:7:8 another_collection.abc
25 An example of using timidity options would be slowing down a tune
26 while you are learning::
28 abcplay.py --timidity '--adjust-tempo 50' my_collection.abc:3
30 SIGINT (usually ^C) will kill the current tune and proceed the next
31 one. Two SIGINTs in less than one second will kill the abcplay.py
32 process. This should be familiar to users of mpg123_ or ogg123_.
34 You can also play LilyPond_ files (converted to MIDI via ``lilypond``)
35 by using the ``--ly`` option::
37 abcplay.py --ly somefile.ly anotherfile.ly
39 .. _abc2midi: http://abc.sourceforge.net/abcMIDI/
40 .. _timidity: http://timidity.sourceforge.net/
41 .. _mpg123: http://www.mpg123.org/
42 .. _ogg123: http://www.vorbis.com
43 .. _LilyPond: http://lilypond.org/
47 from subprocess import Popen
48 from tempfile import mkstemp
54 class MIDIPlayer (object):
55 def __init__(self, to_midi_program=None,
56 to_midi_options=None, timidity_options=None):
57 f,self._tempfile = mkstemp(prefix='abcplay-', suffix='.midi')
58 self._to_midi = [to_midi_program]
60 self._to_midi.extend(to_midi_options)
61 self._timidity = ['timidity']
63 self._timidity.extend(timidity_options)
64 self._last_interrupt = 0
68 remove(self._tempfile)
70 def play_files(self, filenames):
71 raise NotImplementedError()
73 def _return_after_interrupt(self):
75 ret = t-self._last_interrupt < 1
76 self._last_interrupt = t
79 def play(self, filename, **kwargs):
80 self._convert_to_midi(filename, **kwargs)
83 def _convert_to_midi(self, filename, **kwargs):
84 raise NotImplementedError()
87 self._p = Popen(self._timidity + [self._tempfile])
97 pass # no such process
105 class ABCPlayer (MIDIPlayer):
108 def __init__(self, abc2midi_options=None, timidity_options=None):
109 super(ABCPlayer, self).__init__(
110 'abc2midi', abc2midi_options, timidity_options)
112 def play_files(self, filenames):
113 for filename in filenames:
114 if self.refnum_sep in filename:
115 fields = filename.split(self.refnum_sep)
119 refnums = list(self._refnums(filename))
120 while len(refnums) > 0:
121 refnum = refnums.pop(0)
123 self.play(filename, refnum=refnum)
124 except KeyboardInterrupt:
126 if self._return_after_interrupt():
129 def _refnums(self, filename):
130 with open(filename, 'r') as f:
132 if line.startswith('X:'):
133 yield int(line[len('X:'):])
135 def _convert_to_midi(self, filename, refnum):
137 self._to_midi[:1] + [filename, str(refnum)] +
138 self._to_midi[1:] + ['-o', self._tempfile])
143 class LilyPondPlayer (MIDIPlayer):
144 def __init__(self, lilypond_options=None, timidity_options=None):
145 default_lilypond_options = [
146 '-dbackend=null', # don't create a typeset version
147 '-ddelete-intermediate-files', # clean up after ourselves
150 lilypond_options = default_lilypond_options + lilypond_options
152 lilypond_options = default_lilypond_options
153 super(LilyPondPlayer, self).__init__(
154 'lilypond', lilypond_options, timidity_options)
156 def play_files(self, filenames):
157 for filename in filenames:
160 except KeyboardInterrupt:
162 if self._return_after_interrupt():
165 def _convert_to_midi(self, filename):
166 ofilename,ext = os.path.splitext(self._tempfile)
167 assert ext == '.midi', ext # lilypond adds suffix automatically
168 #-dmidi-extension=midi
170 self._to_midi + ['-o', ofilename, filename])
175 if __name__ == '__main__':
179 usage = '%prog [options] file[:refnum[:refnum:...]] ...'
181 p = optparse.OptionParser(usage=usage, epilog=epilog)
182 p.format_epilog = lambda formatter: epilog+'\n'
183 p.add_option('-a', '--abc2midi', dest='abc2midi',
184 help='Extra options to pass to abc2midi.')
185 p.add_option('-t', '--timidity', dest='timidity',
186 help='Extra options to pass to timidity.')
187 p.add_option('-l', '--ly', dest='use_lilypond', action='store_true',
188 help='Use LilyPond input instead of ABC.')
189 p.add_option('--lilypond', dest='lilypond',
190 help='Extra options to pass to LilyPond.')
191 options,args = p.parse_args()
194 abc2midi = options.abc2midi
196 abc2midi = shlex.split(abc2midi)
197 timidity = options.timidity
199 timidity = shlex.split(timidity)
200 lilypond = options.lilypond
202 lilypond = shlex.split(lilypond)
204 if options.use_lilypond:
205 p = LilyPondPlayer(lilypond, timidity)
207 p = ABCPlayer(abc2midi, timidity)