diff --git a/light9/effect/effecteval.py b/light9/effect/effecteval.py --- a/light9/effect/effecteval.py +++ b/light9/effect/effecteval.py @@ -10,6 +10,7 @@ from noise import pnoise1 import logging import time from light9.effect.settings import DeviceSettings +from light9.effect.scale import scale log = logging.getLogger('effecteval') @@ -29,71 +30,16 @@ def lerp(a, b, t): def noise(t): return pnoise1(t % 1000.0, 2) -def scale(value, strength): - if isinstance(value, Literal): - value = value.toPython() - - if isinstance(value, Decimal): - value = float(value) - - if isinstance(value, basestring): - if value[0] == '#': - if strength == '#ffffff': - return value - r,g,b = hex_to_rgb(value) - if isinstance(strength, Literal): - strength = strength.toPython() - if isinstance(strength, basestring): - sr, sg, sb = [v/255 for v in hex_to_rgb(strength)] - else: - sr = sg = sb = strength - return rgb_to_hex([int(r * sr), int(g * sg), int(b * sb)]) - elif isinstance(value, (int, float)): - return value * strength - - raise NotImplementedError("%r,%r" % (value, strength)) - class EffectEval(object): """ runs one effect's code to turn effect attr settings into output device settings. No state; suitable for reload(). """ - def __init__(self, graph, effect, sharedEffectOutputs): + def __init__(self, graph, effect, simpleOutputs): self.graph = graph - self.effect = effect - - # effect : [(dev, attr, value, isScaled)] - self.effectOutputs = sharedEffectOutputs - - if not self.effectOutputs: - self.graph.addHandler(self.updateEffectsFromGraph) + self.effect = effect + self.simpleOutputs = simpleOutputs - def updateEffectsFromGraph(self): - for effect in self.graph.subjects(RDF.type, L9['Effect']): - settings = [] - for setting in self.graph.objects(effect, L9['setting']): - settingValues = dict(self.graph.predicate_objects(setting)) - try: - d = settingValues.get(L9['device'], None) - a = settingValues.get(L9['deviceAttr'], None) - v = settingValues.get(L9['value'], None) - sv = settingValues.get(L9['scaledValue'], None) - if not (bool(v) ^ bool(sv)): - raise NotImplementedError('no value for %s' % setting) - if d is None: - raise TypeError('no device on %s' % effect) - if a is None: - raise TypeError('no attr on %s' % effect) - except Exception: - traceback.print_exc() - continue - - settings.append((d, a, v if v is not None else sv, bool(sv))) - - if settings: - self.effectOutputs[effect] = settings - # also have to read eff :effectAttr [ :tint x; :tintStrength y ] - def outputFromEffect(self, effectSettings, songTime, noteTime): """ From effect attr settings, like strength=0.75, to output device @@ -109,8 +55,8 @@ class EffectEval(object): out = {} # (dev, attr): value - out.update(self.simpleOutput(strength, - effectSettings.get(L9['colorScale'], None))) + out.update(self.simpleOutputs.values( + self.effect, strength, effectSettings.get(L9['colorScale'], None))) if self.effect.startswith(L9['effect/']): tail = 'effect_' + self.effect[len(L9['effect/']):] @@ -123,20 +69,7 @@ class EffectEval(object): outList = [(d, a, v) for (d, a), v in out.iteritems()] return DeviceSettings(self.graph, outList) - - def simpleOutput(self, strength, colorScale): - out = {} - for dev, devAttr, value, isScaled in self.effectOutputs.get(self.effect, []): - if isScaled: - value = scale(value, strength) - if colorScale is not None and devAttr == L9['color']: - value = scale(value, colorScale) - out[(dev, devAttr)] = value - return out - - - - + def effect_Curtain(effectSettings, strength, songTime, noteTime): return { diff --git a/light9/effect/scale.py b/light9/effect/scale.py new file mode 100644 --- /dev/null +++ b/light9/effect/scale.py @@ -0,0 +1,30 @@ +from __future__ import division +from rdflib import Literal +from decimal import Decimal +from webcolors import rgb_to_hex, hex_to_rgb + + +def scale(value, strength): + if isinstance(value, Literal): + value = value.toPython() + + if isinstance(value, Decimal): + value = float(value) + + if isinstance(value, basestring): + if value[0] == '#': + if strength == '#ffffff': + return value + r,g,b = hex_to_rgb(value) + if isinstance(strength, Literal): + strength = strength.toPython() + if isinstance(strength, basestring): + sr, sg, sb = [v/255 for v in hex_to_rgb(strength)] + else: + sr = sg = sb = strength + return rgb_to_hex([int(r * sr), int(g * sg), int(b * sb)]) + elif isinstance(value, (int, float)): + return value * strength + + raise NotImplementedError("%r,%r" % (value, strength)) + diff --git a/light9/effect/sequencer.py b/light9/effect/sequencer.py --- a/light9/effect/sequencer.py +++ b/light9/effect/sequencer.py @@ -18,6 +18,7 @@ from light9.namespaces import L9, RDF from light9.vidref.musictime import MusicTime from light9.effect import effecteval from light9.effect.settings import DeviceSettings +from light9.effect.simple_outputs import SimpleOutputs from greplin import scales from txzmq import ZmqEndpoint, ZmqFactory, ZmqPushConnection @@ -25,6 +26,7 @@ from txzmq import ZmqEndpoint, ZmqFactor log = logging.getLogger('sequencer') stats = scales.collection('/sequencer/', scales.PmfStat('update'), + scales.PmfStat('compile'), scales.DoubleStat('recentFps'), ) @@ -77,11 +79,11 @@ def sendToCollector(client, session, set class Note(object): - def __init__(self, graph, uri, effectevalModule, sharedEffectOutputs): + def __init__(self, graph, uri, effectevalModule, simpleOutputs): g = self.graph = graph self.uri = uri self.effectEval = effectevalModule.EffectEval( - graph, g.value(uri, L9['effectClass']), sharedEffectOutputs) + graph, g.value(uri, L9['effectClass']), simpleOutputs) self.baseEffectSettings = {} # {effectAttr: value} for s in g.objects(uri, L9['setting']): settingValues = dict(g.predicate_objects(s)) @@ -92,16 +94,22 @@ class Note(object): originTime = floatVal(uri, L9['originTime']) self.points = [] for curve in g.objects(uri, L9['curve']): - po = list(g.predicate_objects(curve)) - if dict(po).get(L9['attr'], None) != L9['strength']: - continue - for point in [row[1] for row in po if row[0] == L9['point']]: - po2 = dict(g.predicate_objects(point)) - self.points.append(( - originTime + float(po2[L9['time']]), - float(po2[L9['value']]))) - self.points.sort() - + self.points.extend( + self.getCurvePoints(curve, L9['strength'], originTime)) + self.points.sort() + + def getCurvePoints(self, curve, attr, originTime): + points = [] + po = list(self.graph.predicate_objects(curve)) + if dict(po).get(L9['attr'], None) != attr: + return [] + for point in [row[1] for row in po if row[0] == L9['point']]: + po2 = dict(self.graph.predicate_objects(point)) + points.append(( + originTime + float(po2[L9['time']]), + float(po2[L9['value']]))) + return points + def activeAt(self, t): return self.points[0][0] <= t <= self.points[-1][0] @@ -164,24 +172,26 @@ class Sequencer(object): self.lastStatLog = 0 self._compileGraphCall = None self.notes = {} # song: [notes] + self.simpleOutputs = SimpleOutputs(self.graph) self.graph.addHandler(self.compileGraph) self.update() self.codeWatcher = CodeWatcher( onChange=lambda: self.graph.addHandler(self.compileGraph)) + @stats.compile.time() def compileGraph(self): """rebuild our data from the graph""" log.info('compileGraph start') t1 = time.time() g = self.graph - sharedEffectOutputs = {} for song in g.subjects(RDF.type, L9['Song']): # ideally, wrap this (or smaller) in a sub-handler to run less on each patch self.notes[song] = [] for note in g.objects(song, L9['note']): - self.notes[song].append(Note(g, note, effecteval, sharedEffectOutputs)) + self.notes[song].append(Note(g, note, effecteval, + self.simpleOutputs)) log.info('compileGraph done %.2f ms', 1000 * (time.time() - t1)) @stats.update.time() diff --git a/light9/effect/simple_outputs.py b/light9/effect/simple_outputs.py new file mode 100644 --- /dev/null +++ b/light9/effect/simple_outputs.py @@ -0,0 +1,51 @@ +from __future__ import division +import traceback +from light9.namespaces import L9, RDF +from light9.effect.scale import scale + +class SimpleOutputs(object): + def __init__(self, graph): + self.graph = graph + + # effect : [(dev, attr, value, isScaled)] + self.effectOutputs = {} + + self.graph.addHandler(self.updateEffectsFromGraph) + + def updateEffectsFromGraph(self): + for effect in self.graph.subjects(RDF.type, L9['Effect']): + settings = [] + for setting in self.graph.objects(effect, L9['setting']): + settingValues = dict(self.graph.predicate_objects(setting)) + try: + d = settingValues.get(L9['device'], None) + a = settingValues.get(L9['deviceAttr'], None) + v = settingValues.get(L9['value'], None) + sv = settingValues.get(L9['scaledValue'], None) + if not (bool(v) ^ bool(sv)): + raise NotImplementedError('no value for %s' % setting) + if d is None: + raise TypeError('no device on %s' % effect) + if a is None: + raise TypeError('no attr on %s' % effect) + except Exception: + traceback.print_exc() + continue + + settings.append((d, a, v if v is not None else sv, bool(sv))) + + if settings: + self.effectOutputs[effect] = settings + # also have to read eff :effectAttr [ :tint x; :tintStrength y ] + + + def values(self, effect, strength, colorScale): + out = {} + for dev, devAttr, value, isScaled in self.effectOutputs.get(effect, []): + if isScaled: + value = scale(value, strength) + if colorScale is not None and devAttr == L9['color']: + value = scale(value, colorScale) + out[(dev, devAttr)] = value + return out +