Mercurial > code > home > repos > light9
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 |