diff --git a/light9/effect/effecteval.py b/light9/effect/effecteval.py
--- a/light9/effect/effecteval.py
+++ b/light9/effect/effecteval.py
@@ -3,19 +3,20 @@ import math
import random
from colorsys import hsv_to_rgb
from dataclasses import dataclass
-from typing import Dict, Tuple
+from typing import Dict, Optional, Tuple
+from light9.typedgraph import typedValue
from noise import pnoise1
from PIL import Image
from rdfdb.syncedgraph.syncedgraph import SyncedGraph
-from rdflib import Literal, Namespace
+from rdflib import RDF, Literal, Namespace, URIRef
from webcolors import hex_to_rgb, rgb_to_hex
from light9.effect.scale import scale
from light9.effect.settings import BareEffectSettings, DeviceSettings, EffectSettings
from light9.effect.simple_outputs import SimpleOutputs
from light9.namespaces import DEV, L9
-from light9.newtypes import (DeviceAttr, DeviceUri, EffectAttr, EffectClass, VTUnion)
+from light9.newtypes import (DeviceAttr, DeviceUri, EffectAttr, EffectClass, EffectUri, VTUnion)
SKY = Namespace('http://light9.bigasterisk.com/theater/skyline/device/')
@@ -71,10 +72,54 @@ def _8bit(f):
@dataclass
+class EffectEval2:
+ graph: SyncedGraph
+ uri: EffectUri
+
+ effectFunction: Optional[URIRef]=None
+
+ def __post_init__(self):
+ self.graph.addHandler(self._compile)
+ self.effectFunction = L9['todo']
+
+ def _compile(self):
+ if not self.graph.contains((self.uri, RDF.type, L9['Effect'])):
+ raise ValueError(f'{self.uri} not an :Effect')
+
+ self.function = effect_scale
+ devs = []
+ for s in self.graph.objects(self.uri, L9['setting']):
+ d = typedValue(DeviceUri, self.graph, s, L9['device'])
+ da = typedValue(DeviceAttr, self.graph, s, L9['deviceAttr'])
+ v = typedValue(VTUnion, self.graph, s, L9['value'])
+ devs.append((d, da, v))
+ self.devs = DeviceSettings(self.graph, devs)
+
+ def compute(self, inputs:EffectSettings) -> DeviceSettings:
+
+ s=0
+ for e,ea,v in inputs.asList():
+ if not isinstance(v, float):
+ raise TypeError
+ if ea==L9['strength']:
+ s = v
+
+ print('scaled to', effect_scale(s, self.devs))
+ return effect_scale(s,self.devs )
+
+ return self.function(inputs)
+
+def effect_scale(strength: float, devs: DeviceSettings) -> DeviceSettings:
+ out = []
+ for d,da,v in devs.asList():
+ out.append((d, da, scale(v, strength)))
+ return DeviceSettings(devs.graph, out)
+
+@dataclass
class EffectEval:
"""
runs one effect's code to turn effect attr settings into output
- device settings. No state; suitable for reload().
+ device settings. No effect state; suitable for reload().
"""
graph: SyncedGraph
effect: EffectClass
diff --git a/light9/effect/sequencer/eval_faders.py b/light9/effect/sequencer/eval_faders.py
--- a/light9/effect/sequencer/eval_faders.py
+++ b/light9/effect/sequencer/eval_faders.py
@@ -1,25 +1,40 @@
import asyncio
+from dataclasses import dataclass
import logging
import time
-from typing import Callable, Coroutine, List, cast
+from typing import Callable, Coroutine, List, Optional, cast
+from light9.collector.collector import uriTail
+from light9.typedgraph import typedValue
from rdfdb.syncedgraph.syncedgraph import SyncedGraph
from rdflib import URIRef
from light9.effect import effecteval
from light9.effect.sequencer import Note
-from light9.effect.settings import DeviceSettings
+from light9.effect.settings import DeviceSettings, EffectSettings
from light9.effect.simple_outputs import SimpleOutputs
from light9.metrics import metrics
from light9.namespaces import L9, RDF
-from light9.newtypes import EffectAttr, NoteUri, UnixTime
+from light9.newtypes import EffectAttr, EffectUri, NoteUri, UnixTime
+from rdflib.term import Node
log = logging.getLogger('seq.fader')
+@dataclass
+class Fader:
+ graph: SyncedGraph
+ uri: URIRef
+ effect: EffectUri
+ setEffectAttr: EffectAttr
+
+ value: Optional[float]=None # mutable
+
+ def __post_init__(self):
+ self.ee = effecteval.EffectEval2(self.graph, self.effect)
+
class FaderEval:
"""peer to Sequencer, but this one takes the current :Fader settings -> sendToCollector
- The current faders become Notes in here, for more code reuse.
"""
def __init__(self,
graph: SyncedGraph,
@@ -28,12 +43,11 @@ class FaderEval:
self.graph = graph
self.sendToCollector = sendToCollector
- # Notes without times- always on
- self.notes: List[Note] = []
+ self.faders: List[Fader] = []
- self.simpleOutputs = SimpleOutputs(self.graph)
+ # self.simpleOutputs = SimpleOutputs(self.graph)
log.info('fader adds handler')
- self.graph.addHandler(self.compileGraph)
+ self.graph.addHandler(self._compile)
self.lastLoopSucceeded = False
# self.codeWatcher = CodeWatcher(onChange=self.onCodeChange)
@@ -46,44 +60,43 @@ class FaderEval:
def onCodeChange(self):
log.debug('seq.onCodeChange')
- self.graph.addHandler(self.compileGraph)
+ self.graph.addHandler(self._compile)
#self.updateLoop()
@metrics('compile_graph_fader').time()
- def compileGraph(self) -> None:
+ def _compile(self) -> None:
"""rebuild our data from the graph"""
- self.notes = []
+ self.faders = []
for fader in self.graph.subjects(RDF.type, L9['Fader']):
- def compileFader() -> Note:
- return self.compileFader(cast(URIRef, fader))
+ effect = typedValue(EffectUri, self.graph, fader, L9['effect'])
+ setting = typedValue(Node, self.graph, fader, L9['setting'])
+ setAttr = typedValue(EffectAttr, self.graph, setting, L9['effectAttr'])
+ self.faders.append(Fader(self.graph, cast(URIRef, fader), effect, setAttr))
- self.notes.append(compileFader())
- # if self.notes:
- # asyncio.create_task(self.startUpdating())
-
-
- @metrics('compile_fader').time()
- def compileFader(self, fader: URIRef) -> Note:
- return Note(self.graph, cast(NoteUri, fader),
- timed=False)
+ # this could go in a second, smaller addHandler call to avoid rebuilding Fader objs constantly
+ for f in self.faders:
+ f.value = typedValue(float, self.graph, f.uri, L9['value'])
def computeOutput(self) -> DeviceSettings:
- notesSettings = []
+ notesSettings:List[DeviceSettings] = []
now = UnixTime(time.time())
- for note in self.notes:
- effectSettings, report = note.outputCurrent()
+ for f in self.faders:
+ if f.value is None:
+ raise TypeError('f.value should be set by now')
+ effectSettings = EffectSettings(self.graph, [(f.effect, f.setEffectAttr, f.value)])
- if effectSettings.s[EffectAttr(L9['strength'])]==0:
- continue
+ print(f'{effectSettings=}')
+ notesSettings.append(f.ee.compute(effectSettings))
- ee = effecteval.EffectEval(self.graph, note.effectClass, self.simpleOutputs)
- deviceSettings, report = ee.outputFromEffect(
- effectSettings,
- songTime=now, # probably wrong
- noteTime=now, # wrong
- )
- if deviceSettings:
- notesSettings.append(deviceSettings)
+ # ee = effecteval.EffectEval(self.graph, f.effectClass, self.simpleOutputs)
+ # deviceSettings, report = ee.outputFromEffect(
+ # effectSettings,
+ # songTime=now, # probably wrong
+ # noteTime=now, # wrong
+ # )
+ # log.info(f' 𝅘𝅥𝅮 {uriTail(f.uri)}: {effectSettings=} -> {deviceSettings=}')
+ # if deviceSettings:
+ # notesSettings.append(deviceSettings)
return DeviceSettings.merge(self.graph, notesSettings)
diff --git a/show/dance2023/theaterLightConfig.n3 b/show/dance2023/theaterLightConfig.n3
--- a/show/dance2023/theaterLightConfig.n3
+++ b/show/dance2023/theaterLightConfig.n3
@@ -6,25 +6,50 @@
@prefix show: .
@prefix xsd: .
+:EffectFunction rdfs:comment """
+Linked to a code function. That func's Inputs are
+1) magic stuff like time,
+2) the effectAttrs listed under (?effect :input ?)
+
+Outputs are always a DeviceSettings list which can affect arbitrary devices.
+""" .
+show:fadePage1f0 rdfs:comment ":strength connects the fader to sub0" .
show:fadePage1 a :FadePage; rdfs:label "live controls"; :fader show:fadePage1f0, show:fadePage1f1 .
-show:fadePage1f0 a :Fader; :column "1"; :effectAttr :strength; :effectClass effect:effect0; :value 0.306 .
-show:fadePage1f1 a :Fader; :column "2"; :effectAttr :strength; :effectClass effect:effect2; :value 0.58 .
+show:fadePage1f0
+ a :Fader;
+ :column "1";
+ :effect effect:sub0;
+ :setting [ :effectAttr :strength ] . # fader value is applied to this attr.
-effect:effect0
+effect:sub0
a :Effect;
- :setting effect:effect0_set1 .
-effect:effect0_set1 :device dev:plain1; :deviceAttr :brightness; :scaledValue 0.5 .
+ :effectFunction effect:scale;
+ :input [ :effectAttr :strength ]; # also put the time here if the func needs it.
+ :setting
+ [ :device dev:plain1; :deviceAttr :brightness; :value 0.5 ],
+ [ :device dev:par2; :deviceAttr :color; :value "#ff8000" ] .
-effect:effect2 a :Effect;
- rdfs:label "effect2";
- :publishAttr :strength;
- :setting effect:effect2_set0, effect:effect2_set1, effect:effect2_set2, effect:effect2_set3 .
-effect:effect2_set0 :device dev:par6; :deviceAttr :color; :scaledValue 0.251 .
-effect:effect2_set1 :device dev:par2; :deviceAttr :color; :scaledValue 0.714 .
-effect:effect2_set2 :device dev:strip1; :deviceAttr :color; :scaledValue 0.651 .
-effect:effect2_set3 :device dev:strip2; :deviceAttr :color; :scaledValue 0.22 .
+effect:scale
+ a :EffectFunction;
+ rdfs:label "a submaster- a few devices at specified colors";
+ :input
+ [ :effectAttr :strength; :value 0 ], # overridden by fader
+ [ :effectAttr :output; :value2 [ # something to say 'this attr value must resemble the following graph'
+ :device :valueRequired; # ...and it's repeatable, unlike :strength
+ :deviceAttr :valueRequired;
+ :value :valueRequired ] ] .
+
+
+# show:fadePage1f1 a :Fader; :column "2"; :effectAttr :strength; :effect effect:effect2 .
+# effect:effect2 a :Effect;
+# :publishAttr :strength;
+# :setting effect:effect2_set0, effect:effect2_set1, effect:effect2_set2, effect:effect2_set3 .
+# effect:effect2_set0 :device dev:par6; :deviceAttr :color; :scaledValue 0.251 .
+# effect:effect2_set1 :device dev:par2; :deviceAttr :color; :scaledValue 0.714 .
+# effect:effect2_set2 :device dev:strip1; :deviceAttr :color; :scaledValue 0.651 .
+# effect:effect2_set3 :device dev:strip2; :deviceAttr :color; :scaledValue 0.22 .
dev:strip1 a :Bar612601d; :dmxUniverse dmxA:; :dmxBase 175 .