Fix metronome alignment.
[lilypond/mpolesky.git] / scripts / lilysong.py
blobb8d05aa20a9173d22de61a6b499198633290e1a6
1 #!@TARGET_PYTHON@
3 # Copyright (c) 2006--2010 Brailcom, o.p.s.
5 # Author: Milan Zamazal <pdm@brailcom.org>
7 # This file is part of LilyPond, the GNU music typesetter.
9 # LilyPond is free software: you can redistribute it and/or modify
10 # it under the terms of the GNU General Public License as published by
11 # the Free Software Foundation, either version 3 of the License, or
12 # (at your option) any later version.
14 # LilyPond is distributed in the hope that it will be useful,
15 # but WITHOUT ANY WARRANTY; without even the implied warranty of
16 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 # GNU General Public License for more details.
19 # You should have received a copy of the GNU General Public License
20 # along with LilyPond. If not, see <http://www.gnu.org/licenses/>.
23 import codecs
24 import optparse
25 import os
26 import popen2
27 import sys
28 import tempfile
30 """
31 @relocate-preamble@
32 """
35 FESTIVAL_COMMAND = 'festival --pipe'
36 VOICE_CODINGS = {'voice_czech_ph': 'iso-8859-2'}
38 _USAGE = """lilysong [-p PLAY-PROGRAM] FILE.xml [LANGUAGE-CODE-OR-VOICE [SPEEDUP]]
39 lilysong FILE.ly [LANGUAGE-CODE-OR-VOICE]
40 lilysong --list-voices
41 lilysong --list-languages
42 """
44 def usage ():
45 print 'Usage:', _USAGE
46 sys.exit (2)
48 def process_options (args):
49 parser = optparse.OptionParser (usage=_USAGE, version="@TOPLEVEL_VERSION@")
50 parser.add_option ('', '--list-voices', action='store_true', dest='list_voices',
51 help="list available Festival voices")
52 parser.add_option ('', '--list-languages', action='store_true', dest='list_languages',
53 help="list available Festival languages")
54 parser.add_option ('-p', '--play-program', metavar='PROGRAM',
55 action='store', type='string', dest='play_program',
56 help="use PROGRAM to play song immediately")
57 options, args = parser.parse_args (args)
58 return options, args
60 def call_festival (scheme_code):
61 in_, out = popen2.popen2 (FESTIVAL_COMMAND)
62 out.write (scheme_code)
63 out.close ()
64 answer = ''
65 while True:
66 process_output = in_.read ()
67 if not process_output:
68 break
69 answer = answer + process_output
70 return answer
72 def select_voice (language_or_voice):
73 if language_or_voice[:6] == 'voice_':
74 voice = language_or_voice
75 else:
76 voice = call_festival ('''
77 (let ((candidates '()))
78 (mapcar (lambda (v)
79 (if (eq (cadr (assoc 'language (cadr (voice.description v)))) '%s)
80 (set! candidates (cons v candidates))))
81 (append (voice.list) (mapcar car Voice_descriptions)))
82 (if candidates
83 (format t "voice_%%s" (car candidates))
84 (format t "nil")))
85 ''' % (language_or_voice,))
86 if voice == 'nil':
87 voice = None
88 return voice
90 def list_voices ():
91 print call_festival ('''
92 (let ((voices (voice.list))
93 (print-voice (lambda (v) (format t "voice_%s\n" v))))
94 (mapcar print-voice voices)
95 (mapcar (lambda (v) (if (not (member v voices)) (print-voice v)))
96 (mapcar car Voice_descriptions)))
97 ''')
99 def list_languages ():
100 print call_festival ('''
101 (let ((languages '()))
102 (let ((voices (voice.list))
103 (print-language (lambda (v)
104 (let ((language (cadr (assoc 'language (cadr (voice.description v))))))
105 (if (and language (not (member language languages)))
106 (begin
107 (set! languages (cons language languages))
108 (print language)))))))
109 (mapcar print-language voices)
110 (mapcar (lambda (v) (if (not (member v voices)) (print-language v)))
111 (mapcar car Voice_descriptions))))
112 ''')
114 def process_xml_file (file_name, voice, speedup, play_program):
115 if speedup == 1:
116 speedup = None
117 coding = (VOICE_CODINGS.get (voice) or 'iso-8859-1')
118 _, xml_temp_file = tempfile.mkstemp ('.xml')
119 try:
120 # recode the XML file
121 recodep = (coding != 'utf-8')
122 if recodep:
123 decode = codecs.getdecoder ('utf-8')
124 encode = codecs.getencoder (coding)
125 input = open (file_name)
126 output = open (xml_temp_file, 'w')
127 while True:
128 data = input.read ()
129 if not data:
130 break
131 if recodep:
132 data = encode (decode (data)[0])[0]
133 output.write (data)
134 output.close ()
135 # synthesize
136 wav_file = file_name[:-3] + 'wav'
137 if speedup:
138 _, wav_temp_file = tempfile.mkstemp ('.wav')
139 else:
140 wav_temp_file = wav_file
141 try:
142 print "text2wave -eval '(%s)' -mode singing '%s' -o '%s'" % (voice, xml_temp_file, wav_temp_file,)
143 result = os.system ("text2wave -eval '(%s)' -mode singing '%s' -o '%s'" %
144 (voice, xml_temp_file, wav_temp_file,))
145 if result:
146 sys.stdout.write ("Festival processing failed.\n")
147 return
148 if speedup:
149 result = os.system ("sox '%s' '%s' speed '%f'" % (wav_temp_file, wav_file, speedup,))
150 if result:
151 sys.stdout.write ("Festival processing failed.\n")
152 return
153 finally:
154 if speedup:
155 try:
156 os.delete (wav_temp_file)
157 except:
158 pass
159 sys.stdout.write ("%s created.\n" % (wav_file,))
160 # play
161 if play_program:
162 os.system ("%s '%s' >/dev/null" % (play_program, wav_file,))
163 finally:
164 try:
165 os.delete (xml_temp_file)
166 except:
167 pass
169 def process_ly_file (file_name, voice):
170 result = os.system ("lilypond '%s'" % (file_name,))
171 if result:
172 return
173 xml_file = None
174 for f in os.listdir (os.path.dirname (file_name) or '.'):
175 if (f[-4:] == '.xml' and
176 (not xml_file or os.stat.st_mtime (f) > os.stat.st_mtime (xml_file))):
177 xml_file = f
178 if xml_file:
179 process_xml_file (xml_file, voice, None, None)
180 else:
181 sys.stderr.write ("No XML file found\n")
183 def go ():
184 options, args = process_options (sys.argv[1:])
185 if options.list_voices:
186 list_voices ()
187 elif options.list_languages:
188 list_languages ()
189 else:
190 arglen = len (args)
191 if arglen < 1:
192 usage ()
193 file_name = args[0]
194 if arglen > 1:
195 language_or_voice = args[1]
196 voice = select_voice (language_or_voice)
197 else:
198 voice = None
199 if file_name[-3:] == '.ly':
200 if arglen > 2:
201 usage ()
202 process_ly_file (file_name, voice)
203 else:
204 if arglen > 3:
205 usage ()
206 elif arglen == 3:
207 try:
208 speedup = float (args[2])
209 except ValueError:
210 usage ()
211 else:
212 speedup = None
213 process_xml_file (file_name, voice, speedup, options.play_program)
215 if __name__ == '__main__':
216 go ()