Extend abcplay.py to also handle LilyPond input.
authorW. Trevor King <wking@drexel.edu>
Wed, 8 Dec 2010 13:09:32 +0000 (08:09 -0500)
committerW. Trevor King <wking@drexel.edu>
Wed, 8 Dec 2010 13:09:32 +0000 (08:09 -0500)
posts/abcplay.mdwn
posts/abcplay/abcplay.py

index 6df247c610f3644954a6833f60edccdd340cacf4..9d65958c2265ba54f3d45e9839966de6f411cc8c 100644 (file)
@@ -20,7 +20,9 @@ search tools and repositories listed on the [ABC homepage][ABC] and
 
 To make playing ABC files as easy as possible, I've written a little
 script, [[abcplay.py]], which uses `abc2midi` to generate a MIDI file
-for each tune and then plays them with [timidity++][].
+for each tune and then plays them with [timidity++][].  Because the
+processing is so similar, `abcplay.py` also plays [LilyPond][] files,
+using `lilypond` to convert them to MIDI.
 
 [Bill Whipple]: http://www.wiplstix.com/
 [ABC]: http://abcnotation.com/
@@ -31,6 +33,7 @@ for each tune and then plays them with [timidity++][].
 [Sunrise overlay]: http://overlays.gentoo.org/proj/sunrise
 [The Session]: http://www.thesession.org/
 [timidity++]: http://timidity.sourceforge.net/
+[LilyPond]: http://lilypond.org/
 
 [[!tag tags/fun]]
 [[!tag tags/python]]
index 846416409c2fbf08d1a77500eee8470c8a729f00..b708209a9f3fdef8bcab7ccfe9da3ea9f833ee06 100755 (executable)
@@ -31,10 +31,16 @@ SIGINT (usually ^C) will kill the current tune and proceed the next
 one.  Two SIGINTs in less than one second will kill the abcplay.py
 process.  This should be familiar to users of mpg123_ or ogg123_.
 
+You can also play LilyPond_ files (converted to MIDI via ``lilypond``)
+by using the ``--ly`` option::
+
+    abcplay.py --ly somefile.ly anotherfile.ly
+
 .. _abc2midi: http://abc.sourceforge.net/abcMIDI/
 .. _timidity: http://timidity.sourceforge.net/
 .. _mpg123: http://www.mpg123.org/
 .. _ogg123: http://www.vorbis.com
+.. _LilyPond: http://lilypond.org/
 """
 
 import shlex
@@ -42,16 +48,16 @@ from subprocess import Popen
 from tempfile import mkstemp
 from time import time
 from os import remove
+import os.path
 
 
-REFNUM_SEP = ':'
-
-class ABCPlayer (object):
-    def __init__(self, abc2midi_options=None, timidity_options=None):
+class MIDIPlayer (object):
+    def __init__(self, to_midi_program=None,
+                 to_midi_options=None, timidity_options=None):
         f,self._tempfile = mkstemp(prefix='abcplay-', suffix='.midi')
-        self._abc2midi = ['abc2midi']
-        if abc2midi_options:
-            self._abc2midi.extend(abc2midi_options)
+        self._to_midi = [to_midi_program]
+        if to_midi_options:
+            self._to_midi.extend(to_midi_options)
         self._timidity = ['timidity']
         if timidity_options:
             self._timidity.extend(timidity_options)
@@ -62,21 +68,7 @@ class ABCPlayer (object):
         remove(self._tempfile)
 
     def play_files(self, filenames):
-        for filename in filenames:
-            if REFNUM_SEP in filename:
-                fields = filename.split(REFNUM_SEP)
-                filename = fields[0]
-                refnums = fields[1:]
-            else:
-                refnums = list(self._refnums(filename))
-            while len(refnums) > 0:
-                refnum = refnums.pop(0)
-                try:
-                    self.play(filename, refnum)
-                except KeyboardInterrupt:
-                    self._kill_p()
-                    if self._return_after_interrupt():
-                        return
+        raise NotImplementedError()
 
     def _return_after_interrupt(self):
         t = time()
@@ -84,21 +76,12 @@ class ABCPlayer (object):
         self._last_interrupt = t
         return ret
 
-    def _refnums(self, filename):
-        with open(filename, 'r') as f:
-            for line in f:
-                if line.startswith('X:'):
-                    yield int(line[len('X:'):])
-
-    def play(self, filename, refnum):
-        self._convert_to_midi(filename, refnum)
+    def play(self, filename, **kwargs):
+        self._convert_to_midi(filename, **kwargs)
         self._play_midi()
 
-    def _convert_to_midi(self, filename, refnum):
-        self._p = Popen(
-            self._abc2midi + [filename, str(refnum), '-o', self._tempfile])
-        self._p.wait()
-        self._p = None
+    def _convert_to_midi(self, filename, **kwargs):
+        raise NotImplementedError()
 
     def _play_midi(self):
         self._p = Popen(self._timidity + [self._tempfile])
@@ -118,6 +101,76 @@ class ABCPlayer (object):
                 self._p.wait()
             self._p = None
 
+
+class ABCPlayer (MIDIPlayer):
+    refnum_sep = ':'
+
+    def __init__(self, abc2midi_options=None, timidity_options=None):
+        super(ABCPlayer, self).__init__(
+            'abc2midi', abc2midi_options, timidity_options)
+
+    def play_files(self, filenames):
+        for filename in filenames:
+            if self.refnum_sep in filename:
+                fields = filename.split(self.refnum_sep)
+                filename = fields[0]
+                refnums = fields[1:]
+            else:
+                refnums = list(self._refnums(filename))
+            while len(refnums) > 0:
+                refnum = refnums.pop(0)
+                try:
+                    self.play(filename, refnum=refnum)
+                except KeyboardInterrupt:
+                    self._kill_p()
+                    if self._return_after_interrupt():
+                        return
+
+    def _refnums(self, filename):
+        with open(filename, 'r') as f:
+            for line in f:
+                if line.startswith('X:'):
+                    yield int(line[len('X:'):])
+
+    def _convert_to_midi(self, filename, refnum):
+        self._p = Popen(
+            self._to_midi + [filename, str(refnum), '-o', self._tempfile])
+        self._p.wait()
+        self._p = None
+
+
+class LilyPondPlayer (MIDIPlayer):
+    def __init__(self, lilypond_options=None, timidity_options=None):
+        default_lilypond_options = [
+            '-dbackend=null',  # don't create a typeset version
+            '-ddelete-intermediate-files',  # clean up after ourselves
+            ]
+        if lilypond_options:
+            lilypond_options = default_lilypond_options + lilypond_options
+        else:
+            lilypond_options = default_lilypond_options
+        super(LilyPondPlayer, self).__init__(
+            'lilypond', lilypond_options, timidity_options)
+
+    def play_files(self, filenames):
+        for filename in filenames:
+            try:
+                self.play(filename)
+            except KeyboardInterrupt:
+                self._kill_p()
+                if self._return_after_interrupt():
+                    return
+
+    def _convert_to_midi(self, filename):
+        ofilename,ext = os.path.splitext(self._tempfile)
+        assert ext == '.midi', ext  # lilypond adds suffix automatically
+        #-dmidi-extension=midi
+        self._p = Popen(
+            self._to_midi + ['-o', ofilename, filename])
+        self._p.wait()
+        self._p = None
+
+
 if __name__ == '__main__':
     import sys
     import optparse
@@ -130,6 +183,10 @@ if __name__ == '__main__':
                  help='Extra options to pass to abc2midi.')
     p.add_option('-t', '--timidity', dest='timidity',
                  help='Extra options to pass to timidity.')
+    p.add_option('-l', '--ly', dest='use_lilypond', action='store_true',
+                 help='Use LilyPond input instead of ABC.')
+    p.add_option('--lilypond', dest='lilypond',
+                 help='Extra options to pass to LilyPond.')
     options,args = p.parse_args()
     del p
 
@@ -139,8 +196,14 @@ if __name__ == '__main__':
     timidity = options.timidity
     if timidity:
         timidity = shlex.split(timidity)
-
-    p = ABCPlayer(abc2midi, timidity)
+    lilypond = options.lilypond
+    if lilypond:
+        lilypond = shlex.split(lilypond)
+
+    if options.use_lilypond:
+        p = LilyPondPlayer(lilypond, timidity)
+    else:
+        p = ABCPlayer(abc2midi, timidity)
     try:
         p.play_files(args)
     finally: