comparison bin/effecteval @ 1033:b5ee7aa9341a

effecteval now runs effects in the background, following the current song, and sends dmx output Ignore-this: 3f6a727a8b431cbb1fa1beb454dc3c4
author Drew Perttula <drewp@bigasterisk.com>
date Tue, 27 May 2014 06:29:00 +0000
parents a38414bd3929
children a4632a7b2e17
comparison
equal deleted inserted replaced
1032:54027815c6cc 1033:b5ee7aa9341a
1 #!bin/python 1 #!bin/python
2 from run_local import log 2 from run_local import log
3 from twisted.internet import reactor 3 from twisted.internet import reactor, task
4 from twisted.internet.defer import inlineCallbacks 4 from twisted.internet.defer import inlineCallbacks
5 import cyclone.web, cyclone.websocket, cyclone.httpclient 5 import cyclone.web, cyclone.websocket, cyclone.httpclient
6 import sys, optparse, logging, subprocess, json, re 6 import sys, optparse, logging, subprocess, json, re, time
7 from rdflib import URIRef, RDF 7 from rdflib import URIRef, RDF
8 8
9 sys.path.append(".") 9 sys.path.append(".")
10 from light9 import networking, showconfig, Submaster 10 from light9 import networking, showconfig, Submaster, dmxclient
11 from light9.rdfdb.syncedgraph import SyncedGraph 11 from light9.rdfdb.syncedgraph import SyncedGraph
12 from light9.curvecalc.curve import Curve 12 from light9.curvecalc.curve import Curve
13 from light9.namespaces import L9, DCTERMS 13 from light9.namespaces import L9, DCTERMS
14 14
15 sys.path.append("/my/proj/homeauto/lib") 15 sys.path.append("/my/proj/homeauto/lib")
53 raise NotImplementedError 53 raise NotImplementedError
54 54
55 class EffectNode(object): 55 class EffectNode(object):
56 def __init__(self, graph, uri): 56 def __init__(self, graph, uri):
57 self.graph, self.uri = graph, uri 57 self.graph, self.uri = graph, uri
58 58 self.graph.addHandler(self.prepare)
59 def eval(self, songTime): 59
60 with self.graph.currentState(tripleFilter=(self.uri, L9['code'], None)) as g: 60 def prepare(self):
61 code = g.value(self.uri, L9['code']) 61 self.code = self.graph.value(self.uri, L9['code'])
62 # consider http://waxeye.org/ for a parser that can be used in py and js 62 m = re.match(r'^out = sub\((.*?), intensity=(.*?)\)', self.code)
63 m = re.match(r'^out = sub\((.*?), intensity=(.*?)\)', code)
64 if not m: 63 if not m:
65 raise NotImplementedError 64 raise NotImplementedError
66 sub = uriFromCurie(m.group(1)) 65 subUri = uriFromCode(m.group(1))
67 intensityCurve = uriFromCurie(m.group(2)) 66 subs = Submaster.get_global_submasters(self.graph)
68 67 self.sub = subs.get_sub_by_uri(subUri)
69 print vars()
70 68
71 def effectDmxDict(graph, effect, songTime): 69 intensityCurve = uriFromCode(m.group(2))
72 subs = Submaster.get_global_submasters(graph) 70 self.curve = Curve()
73 71 self.curve.set_from_string(self.graph.value(intensityCurve, L9['points']))
74 curve = URIRef("http://ex/effect/song1/opening") 72
75 c = Curve() 73 def eval(self, songTime):
76 with graph.currentState(tripleFilter=(curve, None, None)) as g: 74 # consider http://waxeye.org/ for a parser that can be used in py and js
77 c.set_from_string(g.value(curve, L9['points'])) 75 level = self.curve.eval(songTime)
78 76 scaledSubs = self.sub * level
79 with graph.currentState(tripleFilter=(effect, None, None)) as g: 77 return scaledSubs
80 print 'got code', g.value(effect, L9['code'])
81 en = EffectNode(graph, effect)
82
83 en.eval(songTime)
84
85 sub = subs.get_sub_by_uri(URIRef("http://light9.bigasterisk.com/show/dance2014/sub/stageleft"))
86 level = c.eval(songTime)
87 scaledSubs = [sub * level]
88
89 out = Submaster.sub_maxes(*scaledSubs)
90 levels_dict = out.get_levels()
91 dmx = out.get_dmx_list()
92 return json.dumps(dmx)
93 78
94 79
95 class EffectEval(PrettyErrorHandler, cyclone.web.RequestHandler): 80 class EffectEval(PrettyErrorHandler, cyclone.web.RequestHandler):
96 @inlineCallbacks 81 @inlineCallbacks
97 def get(self): 82 def get(self):
98 # return dmx dict for that effect 83 # return dmx list for that effect
99 uri = URIRef(self.get_argument('uri')) 84 uri = URIRef(self.get_argument('uri'))
100 response = yield cyclone.httpclient.fetch( 85 response = yield cyclone.httpclient.fetch(
101 networking.musicPlayer.path('time')) 86 networking.musicPlayer.path('time'))
102 songTime = json.loads(response.body)['t'] 87 songTime = json.loads(response.body)['t']
103 self.write(effectDmxDict(self.settings.graph, uri, songTime)) 88
104 89 node = EffectNode(self.settings.graph, uri)
90 outSub = node.eval(songTime)
91 self.write(json.dumps(outSub.get_dmx_list()))
92
93
94 # Completely not sure where the effect background loop should
95 # go. Another process could own it, and get this request repeatedly:
105 class SongEffectsEval(PrettyErrorHandler, cyclone.web.RequestHandler): 96 class SongEffectsEval(PrettyErrorHandler, cyclone.web.RequestHandler):
106 def get(self): 97 def get(self):
107 song = URIRef(self.get_argument('song')) 98 song = URIRef(self.get_argument('song'))
108 effects = effectsForSong(self.settings.graph, song) 99 effects = effectsForSong(self.settings.graph, song)
100 raise NotImplementedError
109 self.write(maxDict(effectDmxDict(e) for e in effects)) 101 self.write(maxDict(effectDmxDict(e) for e in effects))
110 # return dmx dict for all effects in the song, already combined 102 # return dmx dict for all effects in the song, already combined
111 103
104 # Or, we could own that loop, like this:
105 @inlineCallbacks
106 def effectLoop(graph):
107 t1 = time.time()
108 response = json.loads((yield cyclone.httpclient.fetch(
109 networking.musicPlayer.path('time'))).body)
110 song = URIRef(response['song'])
111 songTime = response['t']
112 # Possibilities to make this shut up about graph copies:
113 # - implement the cheap readonly currentState response
114 # - do multiple little currentState calls (in this code) over just
115 # the required triples
116 # - use addHandler instead and only fire dmx when there is a data
117 # change (and also somehow call it when there is a time change)
118
119 outSubs = []
120 with graph.currentState(tripleFilter=(song, L9['effect'], None)) as g:
121 for effectUri in g.objects(song, L9['effect']):
122 node = EffectNode(graph, effectUri)
123 outSubs.append(node.eval(songTime))
124 out = Submaster.sub_maxes(*outSubs)
125 # out.get_levels() for a more readable view
126 dmx = out.get_dmx_list()
127
128 if log.isEnabledFor(logging.DEBUG):
129 log.debug("send dmx: %r", out.get_levels())
130 yield dmxclient.outputlevels(dmx, twisted=True)
131
132 loopTime = time.time() - t1
133 log.debug('loopTime %.1f ms', 1000 * loopTime)
134
112 class App(object): 135 class App(object):
113 def __init__(self, show): 136 def __init__(self, show):
114 self.show = show 137 self.show = show
115 self.graph = SyncedGraph("effectEval") 138 self.graph = SyncedGraph("effectEval")
116 SFH = cyclone.web.StaticFileHandler 139 SFH = cyclone.web.StaticFileHandler
124 (r'/effectData', EffectData), 147 (r'/effectData', EffectData),
125 (r'/static/(.*)', SFH, {'path': 'static/'}), 148 (r'/static/(.*)', SFH, {'path': 'static/'}),
126 (r'/effect/eval', EffectEval), 149 (r'/effect/eval', EffectEval),
127 (r'/songEffects/eval', SongEffectsEval), 150 (r'/songEffects/eval', SongEffectsEval),
128 ], debug=True, graph=self.graph) 151 ], debug=True, graph=self.graph)
129 #graph.initiallySynced.addCallback( 152 self.graph.initiallySynced.addCallback(self.launch)
130 153
131 # see bin/subserver 154 def launch(self, *args):
155 task.LoopingCall(effectLoop, self.graph).start(1)
132 156
133 class StaticCoffee(PrettyErrorHandler, cyclone.web.RequestHandler): 157 class StaticCoffee(PrettyErrorHandler, cyclone.web.RequestHandler):
134 def initialize(self, src): 158 def initialize(self, src):
135 super(StaticCoffee, self).initialize() 159 super(StaticCoffee, self).initialize()
136 self.src = src 160 self.src = src