]> git.donarmstrong.com Git - lilypond.git/blobdiff - scripts/lilysong.py
Singing support
[lilypond.git] / scripts / lilysong.py
diff --git a/scripts/lilysong.py b/scripts/lilysong.py
new file mode 100755 (executable)
index 0000000..18d047a
--- /dev/null
@@ -0,0 +1,217 @@
+#!@TARGET_PYTHON@
+
+# Copyright (C) 2006, 2007 Brailcom, o.p.s.
+#
+# Author: Milan Zamazal <pdm@brailcom.org>
+#
+# COPYRIGHT NOTICE
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+# or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+# for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA.
+
+
+import codecs
+import optparse
+import os
+import popen2
+import sys
+import tempfile
+
+"""
+@relocate-preamble@
+"""
+
+
+FESTIVAL_COMMAND = 'festival --pipe'
+VOICE_CODINGS = {'voice_czech_ph': 'iso-8859-2'}
+
+_USAGE = """lilysong [ -p PLAY-PROGRAM ] FILE.xml [ LANGUAGE-CODE-OR-VOICE [ SPEEDUP ] ]
+./lilysong FILE.ly [ LANGUAGE-CODE-OR-VOICE ]
+./lilysong --list-voices
+./lilysong --list-languages
+"""
+
+def usage ():
+    print 'usage:', _USAGE
+    sys.exit (2)
+
+def process_options (args):
+    parser = optparse.OptionParser (usage=_USAGE, version="@TOPLEVEL_VERSION@")
+    parser.add_option ('', '--list-voices', action='store_true', dest='list_voices',
+                       help="list available Festival voices")
+    parser.add_option ('', '--list-languages', action='store_true', dest='list_languages',
+                       help="list available Festival languages")
+    parser.add_option ('-p', '--play-program', metavar='PROGRAM',
+                       action='store', type='string', dest='play_program',
+                       help="use PROGRAM to play song immediately")
+    options, args = parser.parse_args (args)
+    return options, args
+
+def call_festival (scheme_code):
+    in_, out = popen2.popen2 (FESTIVAL_COMMAND)
+    out.write (scheme_code)
+    out.close ()
+    answer = ''
+    while True:
+        process_output = in_.read ()
+        if not process_output:
+            break
+        answer = answer + process_output
+    return answer
+
+def select_voice (language_or_voice):
+    if language_or_voice[:6] == 'voice_':
+        voice = language_or_voice
+    else:
+        voice = call_festival ('''
+(let ((candidates '()))
+  (mapcar (lambda (v)
+            (if (eq (cadr (assoc 'language (cadr (voice.description v)))) '%s)
+                (set! candidates (cons v candidates))))
+          (append (voice.list) (mapcar car Voice_descriptions)))
+  (if candidates
+      (format t "voice_%%s" (car candidates))
+      (format t "nil")))
+''' % (language_or_voice,))
+        if voice == 'nil':
+            voice = None
+    return voice
+
+def list_voices ():
+    print call_festival ('''
+(let ((voices (voice.list))
+      (print-voice (lambda (v) (format t "voice_%s\n" v))))
+  (mapcar print-voice voices)
+  (mapcar (lambda (v) (if (not (member v voices)) (print-voice v)))
+          (mapcar car Voice_descriptions)))
+''')
+
+def list_languages ():
+    print call_festival ('''
+(let ((languages '()))
+  (let ((voices (voice.list))
+        (print-language (lambda (v)
+                          (let ((language (cadr (assoc 'language (cadr (voice.description v))))))
+                            (if (and language (not (member language languages)))
+                                (begin
+                                  (set! languages (cons language languages))
+                                  (print language)))))))
+    (mapcar print-language voices)
+    (mapcar (lambda (v) (if (not (member v voices)) (print-language v)))
+            (mapcar car Voice_descriptions))))
+''')
+
+def process_xml_file (file_name, voice, speedup, play_program):
+    if speedup == 1:
+        speedup = None
+    coding = (VOICE_CODINGS.get (voice) or 'iso-8859-1')
+    _, xml_temp_file = tempfile.mkstemp ('.xml')
+    try:
+        # recode the XML file
+        recodep = (coding != 'utf-8')
+        if recodep:
+            decode = codecs.getdecoder ('utf-8')
+            encode = codecs.getencoder (coding)
+        input = open (file_name)
+        output = open (xml_temp_file, 'w')
+        while True:
+            data = input.read ()
+            if not data:
+                break
+            if recodep:
+                data = encode (decode (data)[0])[0]
+            output.write (data)
+        output.close ()
+        # synthesize
+        wav_file = file_name[:-3] + 'wav'
+        if speedup:
+            _, wav_temp_file = tempfile.mkstemp ('.wav')
+        else:
+            wav_temp_file = wav_file
+        try:
+            print "text2wave -eval '(%s)' -mode singing '%s' -o '%s'" % (voice, xml_temp_file, wav_temp_file,)
+            result = os.system ("text2wave -eval '(%s)' -mode singing '%s' -o '%s'" %
+                                (voice, xml_temp_file, wav_temp_file,))
+            if result:
+                sys.stdout.write ("Festival processing failed.\n")
+                return
+            if speedup:
+                result = os.system ("sox '%s' '%s' speed '%f'" % (wav_temp_file, wav_file, speedup,))
+                if result:
+                    sys.stdout.write ("Festival processing failed.\n")
+                    return
+        finally:
+            if speedup:
+                try:
+                    os.delete (wav_temp_file)
+                except:
+                    pass
+        sys.stdout.write ("%s created.\n" % (wav_file,))
+        # play
+        if play_program:
+            os.system ("%s '%s' >/dev/null" % (play_program, wav_file,))
+    finally:
+        try:
+            os.delete (xml_temp_file)
+        except:
+            pass
+
+def process_ly_file (file_name, voice):
+    result = os.system ("lilypond '%s'" % (file_name,))
+    if result:
+        return
+    xml_file = None
+    for f in os.listdir (os.path.dirname (file_name) or '.'):
+        if (f[-4:] == '.xml' and
+            (not xml_file or os.stat.st_mtime (f) > os.stat.st_mtime (xml_file))):
+            xml_file = f
+    if xml_file:
+        process_xml_file (xml_file, voice, None, None)
+    else:
+        sys.stderr.write ("No XML file found\n")
+
+def go ():
+    options, args = process_options (sys.argv[1:])
+    if options.list_voices:
+        list_voices ()
+    elif options.list_languages:
+        list_languages ()
+    else:
+        arglen = len (args)
+        if arglen < 1:
+            usage ()
+        file_name = args[0]
+        if arglen > 1:
+            language_or_voice = args[1]
+            voice = select_voice (language_or_voice)
+        else:
+            voice = None
+        if file_name[-3:] == '.ly':
+            if arglen > 2:
+                usage ()
+            process_ly_file (file_name, voice)
+        else:
+            if arglen > 3:
+                usage ()
+            elif arglen == 3:
+                try:
+                    speedup = float (args[2])
+                except ValueError:
+                    usage ()
+            else:
+                speedup = None
+            process_xml_file (file_name, voice, speedup, options.play_program)
+
+if __name__ == '__main__':
+    go ()