annotate service/playSound/speechMusic.py @ 664:28cc07978a71

start rewrite for playSound Ignore-this: a4d4ccceea8ef0ba996ca2d81bf634c6
author drewp@bigasterisk.com
date Thu, 19 Dec 2019 16:45:40 -0800
parents service/speechMusic/speechMusic.py@7e09c0d0a86e
children
Ignore whitespace changes - Everywhere: Within whitespace: At end of lines:
rev   line source
75
1132ab1ade80 big rewrite of speechMusic to use klein and openal
drewp@bigasterisk.com
parents: 36
diff changeset
1 """
1132ab1ade80 big rewrite of speechMusic to use klein and openal
drewp@bigasterisk.com
parents: 36
diff changeset
2 play sounds according to POST requests.
4
be855a111619 move a bunch of services into this tree, give them all web status pages
drewp@bigasterisk.com
parents:
diff changeset
3 """
664
28cc07978a71 start rewrite for playSound
drewp@bigasterisk.com
parents: 455
diff changeset
4 from docopt import docopt
28cc07978a71 start rewrite for playSound
drewp@bigasterisk.com
parents: 455
diff changeset
5 import cyclone.web
455
7e09c0d0a86e speechmusic can now fetch from http
drewp@bigasterisk.com
parents: 454
diff changeset
6 import sys, tempfile, itertools
75
1132ab1ade80 big rewrite of speechMusic to use klein and openal
drewp@bigasterisk.com
parents: 36
diff changeset
7 from twisted.internet import reactor
454
ccde9f432e4e WIP speechmusic to load from http, but pulseaudio out is broken
drewp@bigasterisk.com
parents: 453
diff changeset
8 from cyclone.httpclient import fetch
4
be855a111619 move a bunch of services into this tree, give them all web status pages
drewp@bigasterisk.com
parents:
diff changeset
9 from generator import tts
be855a111619 move a bunch of services into this tree, give them all web status pages
drewp@bigasterisk.com
parents:
diff changeset
10 import xml.etree.ElementTree as ET
75
1132ab1ade80 big rewrite of speechMusic to use klein and openal
drewp@bigasterisk.com
parents: 36
diff changeset
11 from twisted.web.static import File
664
28cc07978a71 start rewrite for playSound
drewp@bigasterisk.com
parents: 455
diff changeset
12 from standardservice.logsetup import log, verboseLogging
28cc07978a71 start rewrite for playSound
drewp@bigasterisk.com
parents: 455
diff changeset
13 import os
4
be855a111619 move a bunch of services into this tree, give them all web status pages
drewp@bigasterisk.com
parents:
diff changeset
14
455
7e09c0d0a86e speechmusic can now fetch from http
drewp@bigasterisk.com
parents: 454
diff changeset
15 soundCount = itertools.count()
4
be855a111619 move a bunch of services into this tree, give them all web status pages
drewp@bigasterisk.com
parents:
diff changeset
16
75
1132ab1ade80 big rewrite of speechMusic to use klein and openal
drewp@bigasterisk.com
parents: 36
diff changeset
17 def makeSpeech(speech, fast=False):
4
be855a111619 move a bunch of services into this tree, give them all web status pages
drewp@bigasterisk.com
parents:
diff changeset
18 speechWav = tempfile.NamedTemporaryFile(suffix='.wav')
be855a111619 move a bunch of services into this tree, give them all web status pages
drewp@bigasterisk.com
parents:
diff changeset
19
be855a111619 move a bunch of services into this tree, give them all web status pages
drewp@bigasterisk.com
parents:
diff changeset
20 root = ET.Element("SABLE")
be855a111619 move a bunch of services into this tree, give them all web status pages
drewp@bigasterisk.com
parents:
diff changeset
21 r = ET.SubElement(root, "RATE",
be855a111619 move a bunch of services into this tree, give them all web status pages
drewp@bigasterisk.com
parents:
diff changeset
22 attrib=dict(SPEED="+50%" if fast else "+0%"))
be855a111619 move a bunch of services into this tree, give them all web status pages
drewp@bigasterisk.com
parents:
diff changeset
23 for sentence in speech.split('.'):
be855a111619 move a bunch of services into this tree, give them all web status pages
drewp@bigasterisk.com
parents:
diff changeset
24 div = ET.SubElement(r, "DIV")
be855a111619 move a bunch of services into this tree, give them all web status pages
drewp@bigasterisk.com
parents:
diff changeset
25 div.set("TYPE", "sentence")
be855a111619 move a bunch of services into this tree, give them all web status pages
drewp@bigasterisk.com
parents:
diff changeset
26 div.text = sentence
be855a111619 move a bunch of services into this tree, give them all web status pages
drewp@bigasterisk.com
parents:
diff changeset
27
454
ccde9f432e4e WIP speechmusic to load from http, but pulseaudio out is broken
drewp@bigasterisk.com
parents: 453
diff changeset
28 speechSecs = tts(speech, speechWav.name)
359
b3959142d7d8 move speech_music into docker and into pygame
drewp@bigasterisk.com
parents: 215
diff changeset
29 return pygame.mixer.Sound(speechWav.name), speechSecs
75
1132ab1ade80 big rewrite of speechMusic to use klein and openal
drewp@bigasterisk.com
parents: 36
diff changeset
30
454
ccde9f432e4e WIP speechmusic to load from http, but pulseaudio out is broken
drewp@bigasterisk.com
parents: 453
diff changeset
31 class LOADING(object): pass
ccde9f432e4e WIP speechmusic to load from http, but pulseaudio out is broken
drewp@bigasterisk.com
parents: 453
diff changeset
32
75
1132ab1ade80 big rewrite of speechMusic to use klein and openal
drewp@bigasterisk.com
parents: 36
diff changeset
33 class SoundEffects(object):
1132ab1ade80 big rewrite of speechMusic to use klein and openal
drewp@bigasterisk.com
parents: 36
diff changeset
34 def __init__(self):
454
ccde9f432e4e WIP speechmusic to load from http, but pulseaudio out is broken
drewp@bigasterisk.com
parents: 453
diff changeset
35 self.buffers = {} # URIRef : pygame.mixer.Sound
75
1132ab1ade80 big rewrite of speechMusic to use klein and openal
drewp@bigasterisk.com
parents: 36
diff changeset
36 self.playingSources = []
1132ab1ade80 big rewrite of speechMusic to use klein and openal
drewp@bigasterisk.com
parents: 36
diff changeset
37 self.queued = []
453
9fd92202c886 WIP updating to read sounds from http
drewp@bigasterisk.com
parents: 359
diff changeset
38 self.volume = 1 # level for the next sound that's played (or existing instances of the same sound)
4
be855a111619 move a bunch of services into this tree, give them all web status pages
drewp@bigasterisk.com
parents:
diff changeset
39
454
ccde9f432e4e WIP speechmusic to load from http, but pulseaudio out is broken
drewp@bigasterisk.com
parents: 453
diff changeset
40 def _getSound(self, uri):
ccde9f432e4e WIP speechmusic to load from http, but pulseaudio out is broken
drewp@bigasterisk.com
parents: 453
diff changeset
41 def done(resp):
455
7e09c0d0a86e speechmusic can now fetch from http
drewp@bigasterisk.com
parents: 454
diff changeset
42 path = '/tmp/sound_%s' % next(soundCount)
7e09c0d0a86e speechmusic can now fetch from http
drewp@bigasterisk.com
parents: 454
diff changeset
43 with open(path, 'w') as out:
7e09c0d0a86e speechmusic can now fetch from http
drewp@bigasterisk.com
parents: 454
diff changeset
44 out.write(resp.body)
7e09c0d0a86e speechmusic can now fetch from http
drewp@bigasterisk.com
parents: 454
diff changeset
45 log.info('write %s bytes to %s', len(resp.body), path)
7e09c0d0a86e speechmusic can now fetch from http
drewp@bigasterisk.com
parents: 454
diff changeset
46 self.buffers[uri] = pygame.mixer.Sound(path)
664
28cc07978a71 start rewrite for playSound
drewp@bigasterisk.com
parents: 455
diff changeset
47
455
7e09c0d0a86e speechmusic can now fetch from http
drewp@bigasterisk.com
parents: 454
diff changeset
48 return fetch(uri).addCallback(done).addErrback(log.error)
454
ccde9f432e4e WIP speechmusic to load from http, but pulseaudio out is broken
drewp@bigasterisk.com
parents: 453
diff changeset
49
ccde9f432e4e WIP speechmusic to load from http, but pulseaudio out is broken
drewp@bigasterisk.com
parents: 453
diff changeset
50 def playEffect(self, uri):
ccde9f432e4e WIP speechmusic to load from http, but pulseaudio out is broken
drewp@bigasterisk.com
parents: 453
diff changeset
51 if uri not in self.buffers:
ccde9f432e4e WIP speechmusic to load from http, but pulseaudio out is broken
drewp@bigasterisk.com
parents: 453
diff changeset
52 self.buffers[uri] = LOADING
455
7e09c0d0a86e speechmusic can now fetch from http
drewp@bigasterisk.com
parents: 454
diff changeset
53 self._getSound(uri).addCallback(lambda ret: self.playEffect(uri))
7e09c0d0a86e speechmusic can now fetch from http
drewp@bigasterisk.com
parents: 454
diff changeset
54 return
454
ccde9f432e4e WIP speechmusic to load from http, but pulseaudio out is broken
drewp@bigasterisk.com
parents: 453
diff changeset
55 if self.buffers[uri] is LOADING:
ccde9f432e4e WIP speechmusic to load from http, but pulseaudio out is broken
drewp@bigasterisk.com
parents: 453
diff changeset
56 # The first playback loads then plays, but any attempts
ccde9f432e4e WIP speechmusic to load from http, but pulseaudio out is broken
drewp@bigasterisk.com
parents: 453
diff changeset
57 # during that load are dropped, not queued.
ccde9f432e4e WIP speechmusic to load from http, but pulseaudio out is broken
drewp@bigasterisk.com
parents: 453
diff changeset
58 return
ccde9f432e4e WIP speechmusic to load from http, but pulseaudio out is broken
drewp@bigasterisk.com
parents: 453
diff changeset
59 snd = self.buffers[uri]
453
9fd92202c886 WIP updating to read sounds from http
drewp@bigasterisk.com
parents: 359
diff changeset
60 snd.set_volume(self.volume)
9fd92202c886 WIP updating to read sounds from http
drewp@bigasterisk.com
parents: 359
diff changeset
61 return self.playBuffer(snd)
4
be855a111619 move a bunch of services into this tree, give them all web status pages
drewp@bigasterisk.com
parents:
diff changeset
62
75
1132ab1ade80 big rewrite of speechMusic to use klein and openal
drewp@bigasterisk.com
parents: 36
diff changeset
63 def playSpeech(self, txt, preEffect=None, postEffect=None, preEffectOverlap=0):
1132ab1ade80 big rewrite of speechMusic to use klein and openal
drewp@bigasterisk.com
parents: 36
diff changeset
64 buf, secs = makeSpeech(txt)
1132ab1ade80 big rewrite of speechMusic to use klein and openal
drewp@bigasterisk.com
parents: 36
diff changeset
65 t = 0
1132ab1ade80 big rewrite of speechMusic to use klein and openal
drewp@bigasterisk.com
parents: 36
diff changeset
66 if preEffect:
1132ab1ade80 big rewrite of speechMusic to use klein and openal
drewp@bigasterisk.com
parents: 36
diff changeset
67 t += self.playEffect(preEffect)
4
be855a111619 move a bunch of services into this tree, give them all web status pages
drewp@bigasterisk.com
parents:
diff changeset
68
359
b3959142d7d8 move speech_music into docker and into pygame
drewp@bigasterisk.com
parents: 215
diff changeset
69 self.playingSources.append(buf)
b3959142d7d8 move speech_music into docker and into pygame
drewp@bigasterisk.com
parents: 215
diff changeset
70 reactor.callLater(secs + .1, self.done, buf)
75
1132ab1ade80 big rewrite of speechMusic to use klein and openal
drewp@bigasterisk.com
parents: 36
diff changeset
71 return secs
1132ab1ade80 big rewrite of speechMusic to use klein and openal
drewp@bigasterisk.com
parents: 36
diff changeset
72
1132ab1ade80 big rewrite of speechMusic to use klein and openal
drewp@bigasterisk.com
parents: 36
diff changeset
73 def done(self, src):
1132ab1ade80 big rewrite of speechMusic to use klein and openal
drewp@bigasterisk.com
parents: 36
diff changeset
74 try:
1132ab1ade80 big rewrite of speechMusic to use klein and openal
drewp@bigasterisk.com
parents: 36
diff changeset
75 self.playingSources.remove(src)
1132ab1ade80 big rewrite of speechMusic to use klein and openal
drewp@bigasterisk.com
parents: 36
diff changeset
76 except ValueError:
1132ab1ade80 big rewrite of speechMusic to use klein and openal
drewp@bigasterisk.com
parents: 36
diff changeset
77 pass
4
be855a111619 move a bunch of services into this tree, give them all web status pages
drewp@bigasterisk.com
parents:
diff changeset
78
75
1132ab1ade80 big rewrite of speechMusic to use klein and openal
drewp@bigasterisk.com
parents: 36
diff changeset
79 def stopAll(self):
1132ab1ade80 big rewrite of speechMusic to use klein and openal
drewp@bigasterisk.com
parents: 36
diff changeset
80 while self.playingSources:
1132ab1ade80 big rewrite of speechMusic to use klein and openal
drewp@bigasterisk.com
parents: 36
diff changeset
81 self.playingSources.pop().stop()
1132ab1ade80 big rewrite of speechMusic to use klein and openal
drewp@bigasterisk.com
parents: 36
diff changeset
82 for q in self.queued:
1132ab1ade80 big rewrite of speechMusic to use klein and openal
drewp@bigasterisk.com
parents: 36
diff changeset
83 q.cancel()
454
ccde9f432e4e WIP speechmusic to load from http, but pulseaudio out is broken
drewp@bigasterisk.com
parents: 453
diff changeset
84 # doesn't cover the callLater ones
4
be855a111619 move a bunch of services into this tree, give them all web status pages
drewp@bigasterisk.com
parents:
diff changeset
85
75
1132ab1ade80 big rewrite of speechMusic to use klein and openal
drewp@bigasterisk.com
parents: 36
diff changeset
86
664
28cc07978a71 start rewrite for playSound
drewp@bigasterisk.com
parents: 455
diff changeset
87 class Index(cyclone.web.RequestHandler):
28cc07978a71 start rewrite for playSound
drewp@bigasterisk.com
parents: 455
diff changeset
88 def get(self):
28cc07978a71 start rewrite for playSound
drewp@bigasterisk.com
parents: 455
diff changeset
89 self.render('index.html', effectNames=[
28cc07978a71 start rewrite for playSound
drewp@bigasterisk.com
parents: 455
diff changeset
90 dict(name=k, postUri='effects/%s' % k)
28cc07978a71 start rewrite for playSound
drewp@bigasterisk.com
parents: 455
diff changeset
91 for k in self.settings.sfx.buffers.keys()])
4
be855a111619 move a bunch of services into this tree, give them all web status pages
drewp@bigasterisk.com
parents:
diff changeset
92
664
28cc07978a71 start rewrite for playSound
drewp@bigasterisk.com
parents: 455
diff changeset
93 class Speak(cyclone.web.RequestHandler):
28cc07978a71 start rewrite for playSound
drewp@bigasterisk.com
parents: 455
diff changeset
94 def post(self):
28cc07978a71 start rewrite for playSound
drewp@bigasterisk.com
parents: 455
diff changeset
95 self.settings.sfx.playSpeech(self.get_argument('msg'))
28cc07978a71 start rewrite for playSound
drewp@bigasterisk.com
parents: 455
diff changeset
96 return "ok"
4
be855a111619 move a bunch of services into this tree, give them all web status pages
drewp@bigasterisk.com
parents:
diff changeset
97
664
28cc07978a71 start rewrite for playSound
drewp@bigasterisk.com
parents: 455
diff changeset
98 class PlaySound(cyclone.web.RequestHandler):
28cc07978a71 start rewrite for playSound
drewp@bigasterisk.com
parents: 455
diff changeset
99 def post(self):
28cc07978a71 start rewrite for playSound
drewp@bigasterisk.com
parents: 455
diff changeset
100 uri = self.get_argument('uri')
28cc07978a71 start rewrite for playSound
drewp@bigasterisk.com
parents: 455
diff changeset
101 self.settings.sfx.playEffect(uri)
28cc07978a71 start rewrite for playSound
drewp@bigasterisk.com
parents: 455
diff changeset
102 return "ok"
28cc07978a71 start rewrite for playSound
drewp@bigasterisk.com
parents: 455
diff changeset
103
28cc07978a71 start rewrite for playSound
drewp@bigasterisk.com
parents: 455
diff changeset
104 class Volume(cyclone.web.RequestHandler):
28cc07978a71 start rewrite for playSound
drewp@bigasterisk.com
parents: 455
diff changeset
105 def put(self):
28cc07978a71 start rewrite for playSound
drewp@bigasterisk.com
parents: 455
diff changeset
106 self.settings.sfx.setVolume(float(self.get_argument('v')))
75
1132ab1ade80 big rewrite of speechMusic to use klein and openal
drewp@bigasterisk.com
parents: 36
diff changeset
107 return "ok"
4
be855a111619 move a bunch of services into this tree, give them all web status pages
drewp@bigasterisk.com
parents:
diff changeset
108
664
28cc07978a71 start rewrite for playSound
drewp@bigasterisk.com
parents: 455
diff changeset
109 class StopAll(cyclone.web.RequestHandler):
28cc07978a71 start rewrite for playSound
drewp@bigasterisk.com
parents: 455
diff changeset
110 def post(self):
28cc07978a71 start rewrite for playSound
drewp@bigasterisk.com
parents: 455
diff changeset
111 self.settings.sfx.stopAll()
75
1132ab1ade80 big rewrite of speechMusic to use klein and openal
drewp@bigasterisk.com
parents: 36
diff changeset
112 return "ok"
4
be855a111619 move a bunch of services into this tree, give them all web status pages
drewp@bigasterisk.com
parents:
diff changeset
113
664
28cc07978a71 start rewrite for playSound
drewp@bigasterisk.com
parents: 455
diff changeset
114
28cc07978a71 start rewrite for playSound
drewp@bigasterisk.com
parents: 455
diff changeset
115 if __name__ == '__main__':
28cc07978a71 start rewrite for playSound
drewp@bigasterisk.com
parents: 455
diff changeset
116 arg = docopt('''
28cc07978a71 start rewrite for playSound
drewp@bigasterisk.com
parents: 455
diff changeset
117 Usage: playSound.py [options]
28cc07978a71 start rewrite for playSound
drewp@bigasterisk.com
parents: 455
diff changeset
118
28cc07978a71 start rewrite for playSound
drewp@bigasterisk.com
parents: 455
diff changeset
119 -v Verbose
28cc07978a71 start rewrite for playSound
drewp@bigasterisk.com
parents: 455
diff changeset
120 ''')
28cc07978a71 start rewrite for playSound
drewp@bigasterisk.com
parents: 455
diff changeset
121 verboseLogging(arg['-v'])
359
b3959142d7d8 move speech_music into docker and into pygame
drewp@bigasterisk.com
parents: 215
diff changeset
122
664
28cc07978a71 start rewrite for playSound
drewp@bigasterisk.com
parents: 455
diff changeset
123 import pygame
28cc07978a71 start rewrite for playSound
drewp@bigasterisk.com
parents: 455
diff changeset
124 print('mixer init pulse')
28cc07978a71 start rewrite for playSound
drewp@bigasterisk.com
parents: 455
diff changeset
125 import pygame.mixer
28cc07978a71 start rewrite for playSound
drewp@bigasterisk.com
parents: 455
diff changeset
126 pygame.mixer.init()
28cc07978a71 start rewrite for playSound
drewp@bigasterisk.com
parents: 455
diff changeset
127 sfx = SoundEffects()
4
be855a111619 move a bunch of services into this tree, give them all web status pages
drewp@bigasterisk.com
parents:
diff changeset
128
664
28cc07978a71 start rewrite for playSound
drewp@bigasterisk.com
parents: 455
diff changeset
129 reactor.listenTCP(9049, cyclone.web.Application(handlers=[
28cc07978a71 start rewrite for playSound
drewp@bigasterisk.com
parents: 455
diff changeset
130 (r'/', Index),
28cc07978a71 start rewrite for playSound
drewp@bigasterisk.com
parents: 455
diff changeset
131 (r'/speak', Speak),
28cc07978a71 start rewrite for playSound
drewp@bigasterisk.com
parents: 455
diff changeset
132 (r'/playSound', PlaySound),
28cc07978a71 start rewrite for playSound
drewp@bigasterisk.com
parents: 455
diff changeset
133 (r'/volume', Volume),
28cc07978a71 start rewrite for playSound
drewp@bigasterisk.com
parents: 455
diff changeset
134 (r'/stopAll', StopAll),
28cc07978a71 start rewrite for playSound
drewp@bigasterisk.com
parents: 455
diff changeset
135 (r'/static/(.*)', cyclone.web.StaticFileHandler, {'path': 'static'}),
28cc07978a71 start rewrite for playSound
drewp@bigasterisk.com
parents: 455
diff changeset
136 ], template_path='.', sfx=sfx))
28cc07978a71 start rewrite for playSound
drewp@bigasterisk.com
parents: 455
diff changeset
137 reactor.run()
359
b3959142d7d8 move speech_music into docker and into pygame
drewp@bigasterisk.com
parents: 215
diff changeset
138 server.app.run(endpoint_description=r"tcp6:port=9049:interface=\:\:")