changeset 2212:b6f8f1b08959

finally: a fader controls an effect strength, which controls an effect, which emits deviceattrs
author drewp@bigasterisk.com
date Tue, 23 May 2023 12:34:04 -0700
parents 5edb163780e2
children 22131be6639b
files light9/effect/effecteval.py light9/effect/sequencer/eval_faders.py show/dance2023/theaterLightConfig.n3
diffstat 3 files changed, 135 insertions(+), 52 deletions(-) [+]
line wrap: on
line diff
--- a/light9/effect/effecteval.py	Tue May 23 11:44:54 2023 -0700
+++ b/light9/effect/effecteval.py	Tue May 23 12:34:04 2023 -0700
@@ -3,19 +3,20 @@
 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 @@
 
 
 @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
--- a/light9/effect/sequencer/eval_faders.py	Tue May 23 11:44:54 2023 -0700
+++ b/light9/effect/sequencer/eval_faders.py	Tue May 23 12:34:04 2023 -0700
@@ -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 @@
         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 @@
 
     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)
 
 
--- a/show/dance2023/theaterLightConfig.n3	Tue May 23 11:44:54 2023 -0700
+++ b/show/dance2023/theaterLightConfig.n3	Tue May 23 12:34:04 2023 -0700
@@ -6,25 +6,50 @@
 @prefix show: <http://light9.bigasterisk.com/show/dance2023/> .
 @prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
 
+: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 .