import traceback
import logging
import time
from dataclasses import dataclass
from typing import List, Optional, cast
from prometheus_client import Summary
from rdfdb import SyncedGraph
from rdflib import URIRef
from rdflib.term import Node
from light9.effect.effect_function_library import EffectFunctionLibrary
from light9.effect.effecteval2 import EffectEval2
from light9.effect.settings import DeviceSettings, EffectSettings
from light9.namespaces import L9, RDF
from light9.newtypes import EffectAttr, EffectUri, UnixTime
from light9.typedgraph import typedValue
log = logging.getLogger('seq.fader')
COMPILE = Summary('compile_graph_fader', '')
COMPUTE_ALL_FADERS = Summary('compute_all_faders', '')
@dataclass
class Fader:
graph: SyncedGraph
lib: EffectFunctionLibrary
uri: URIRef
effect: EffectUri
setEffectAttr: EffectAttr
value: Optional[float] = None # mutable
def __post_init__(self):
self.ee = EffectEval2(self.graph, self.effect, self.lib)
class FaderEval:
"""peer to Sequencer, but this one takes the current :Fader settings -> sendToCollector
"""
def __init__(self, graph: SyncedGraph, lib: EffectFunctionLibrary):
self.graph = graph
self.lib = lib
self.faders: List[Fader] = []
self.graph.addHandler(self._compile)
self.lastLoopSucceeded = False
@COMPILE.time()
def _compile(self) -> None:
"""rebuild our data from the graph"""
self.faders = []
for fader in self.graph.subjects(RDF.type, L9['Fader']):
try:
self.faders.append(self._compileFader(fader))
except ValueError:
pass
# this could go in a second, smaller addHandler call to avoid rebuilding Fader objs constantly
for f in self.faders:
try:
setting = typedValue(Node, self.graph, f.uri, L9['setting'])
except ValueError:
f.value = None
else:
f.value = typedValue(float, self.graph, setting, L9['value'])
def _compileFader(self, fader: 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'])
return (Fader(self.graph, self.lib, cast(URIRef, fader), effect, setAttr))
@COMPUTE_ALL_FADERS.time()
def computeOutput(self) -> DeviceSettings:
faderEffectOutputs: List[DeviceSettings] = []
now = UnixTime(time.time())
for f in self.faders:
try:
if f.value is None:
log.warning(f'{f.value=}; should be set during _compile. Skipping {f.uri}')
continue
effectSettings = EffectSettings(self.graph, [(f.effect, f.setEffectAttr, f.value)])
ds = f.ee.compute(now, effectSettings)
faderEffectOutputs.append(ds)
except Exception:
log.warning(f'on fader {f}')
traceback.print_exc()
continue
merged = DeviceSettings.merge(self.graph, faderEffectOutputs)
# please remove (after fixing stats display to show it)
log.debug("computed %s faders in %.1fms", len(self.faders), (time.time()-now)*1000)
return merged