# HG changeset patch # User drewp@bigasterisk.com # Date 2023-05-23 19:34:04 # Node ID b6f8f1b089594cf140f097f13c03f55297e40d25 # Parent 5edb163780e2f5e8ebc5cd52fc33a0caefc904a0 finally: a fader controls an effect strength, which controls an effect, which emits deviceattrs 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 .