# HG changeset patch # User drewp@bigasterisk.com # Date 2023-05-20 23:32:16 # Node ID ffae830fda129db31e295319717d3b7efe932050 # Parent 51c670ce5d502862df82281fe1e2bcae2a222e4a reformat diff --git a/light9/effect/effecteval.py b/light9/effect/effecteval.py --- a/light9/effect/effecteval.py +++ b/light9/effect/effecteval.py @@ -1,21 +1,21 @@ -from dataclasses import dataclass import logging import math import random from colorsys import hsv_to_rgb -from typing import Any, Dict, Tuple -from light9.effect.simple_outputs import SimpleOutputs -from light9.newtypes import DeviceAttr, DeviceUri, EffectAttr, EffectClass, VTUnion +from dataclasses import dataclass +from typing import Dict, Tuple from noise import pnoise1 from PIL import Image -from rdflib import Literal, Namespace, URIRef +from rdfdb.syncedgraph.syncedgraph import SyncedGraph +from rdflib import Literal, Namespace 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.settings import BareEffectSettings, EffectSettings +from light9.effect.simple_outputs import SimpleOutputs from light9.namespaces import DEV, L9 -from rdfdb.syncedgraph.syncedgraph import SyncedGraph +from light9.newtypes import (DeviceAttr, DeviceUri, EffectAttr, EffectClass, VTUnion) SKY = Namespace('http://light9.bigasterisk.com/theater/skyline/device/') @@ -28,7 +28,7 @@ log.info("reload effecteval") def literalColor(rnorm, gnorm, bnorm): return Literal(rgb_to_hex(( int(rnorm * 255), # - int(gnorm * 255), # + int(gnorm * 255), # int(bnorm * 255)))) diff --git a/light9/effect/scale.py b/light9/effect/scale.py --- a/light9/effect/scale.py +++ b/light9/effect/scale.py @@ -1,6 +1,7 @@ +from decimal import Decimal + from rdflib import Literal -from decimal import Decimal -from webcolors import rgb_to_hex, hex_to_rgb +from webcolors import hex_to_rgb, rgb_to_hex def scale(value, strength): @@ -21,7 +22,7 @@ def scale(value, strength): 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)]) + return rgb_to_hex((int(r * sr), int(g * sg), int(b * sb))) elif isinstance(value, (int, float)): return value * strength 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 @@ -18,7 +18,7 @@ log = logging.getLogger('seq.fader') 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, @@ -53,7 +53,7 @@ class FaderEval: def compileGraph(self) -> None: """rebuild our data from the graph""" self.notes = [] - for fader in self.graph.subjects(RDF.type, L9['Fader']): + for fader in self.graph.subjects(RDF.type, L9['Fader']): def compileFader() -> Note: return self.compileFader(cast(URIRef, fader)) @@ -64,9 +64,9 @@ class FaderEval: @metrics('compile_fader').time() def compileFader(self, fader: URIRef) -> Note: - return Note(self.graph, cast(NoteUri, fader), + return Note(self.graph, cast(NoteUri, fader), timed=False) - + def computeOutput(self) -> DeviceSettings: notesSettings = [] now = UnixTime(time.time()) @@ -78,7 +78,7 @@ class FaderEval: ee = effecteval.EffectEval(self.graph, note.effectClass, self.simpleOutputs) deviceSettings, report = ee.outputFromEffect( - effectSettings, + effectSettings, songTime=now, # probably wrong noteTime=now, # wrong ) diff --git a/light9/effect/sequencer/eval_faders_test.py b/light9/effect/sequencer/eval_faders_test.py --- a/light9/effect/sequencer/eval_faders_test.py +++ b/light9/effect/sequencer/eval_faders_test.py @@ -1,10 +1,10 @@ from unittest import mock + from light9.effect.sequencer.eval_faders import FaderEval from light9.effect.settings import DeviceSettings from light9.mock_syncedgraph import MockSyncedGraph from light9.namespaces import L9 - PREFIXES = ''' @prefix : . @prefix effect: . @@ -16,46 +16,48 @@ PREFIXES = ''' ''' NOTE_GRAPH = PREFIXES + ''' - :brightness - a :DeviceAttr; - rdfs:label "brightness"; + :brightness + a :DeviceAttr; + rdfs:label "brightness"; :dataType :scalar . :strength a :EffectAttr; rdfs:label "strength" . - :SimpleDimmer - a :DeviceClass; + :SimpleDimmer + a :DeviceClass; rdfs:label "SimpleDimmer"; :deviceAttr :brightness; :attr [ :outputAttr :level; :dmxOffset 0 ] . - - :light1 + + :light1 a :SimpleDimmer; :dmxUniverse dmxA:; :dmxBase 178 . - effect:effect1 - a :EffectClass; + effect:effect1 + a :Effect; :setting effect:effect1_set1 . - effect:effect1_set1 - :device :light1; - :deviceAttr :brightness; + effect:effect1_set1 + :device :light1; + :deviceAttr :brightness; :scaledValue 0.5 . - :fade1 - a :Fader; - :effectClass effect:effect1; - :effectAttr :strength; + :fade1 + a :Fader; + :effectClass effect:effect1; + :effectAttr :strength; :value 0.6 . ''' + class TestFaderEval: + def test_faderValueScalesEffectSettings(self): g = MockSyncedGraph(NOTE_GRAPH) sender = mock.MagicMock() - + f = FaderEval(g, sender) devSettings = f.computeOutput() assert devSettings == DeviceSettings(g, [(L9['light1'], L9['brightness'], 0.3)]) \ No newline at end of file diff --git a/light9/effect/sequencer/note.py b/light9/effect/sequencer/note.py --- a/light9/effect/sequencer/note.py +++ b/light9/effect/sequencer/note.py @@ -1,18 +1,16 @@ import bisect -from dataclasses import dataclass import logging import time +from dataclasses import dataclass from decimal import Decimal from typing import Any, Dict, List, Optional, Tuple, Union, cast -from light9.effect.effecteval import EffectEval -from light9.effect.settings import BareEffectSettings, DeviceSettings, EffectSettings -from light9.effect.simple_outputs import SimpleOutputs from rdfdb.syncedgraph.syncedgraph import SyncedGraph -from rdflib import Literal, URIRef +from rdflib import Literal +from light9.effect.settings import BareEffectSettings from light9.namespaces import L9 -from light9.newtypes import Curve, DeviceAttr, EffectAttr, EffectClass, NoteUri, VTUnion, typedValue +from light9.newtypes import (Curve, EffectAttr, EffectClass, NoteUri, VTUnion, typedValue) log = logging.getLogger('sequencer') @@ -33,20 +31,20 @@ def prettyFormat(x: Union[float, str]): @dataclass class Note: """A Note outputs EffectAttr settings. - + Sample graph: :note1 a :Note; :curve :n1c1; :effectClass effect:allcolor; It can animate the EffectAttr settings over time, in two ways: - * a `timed note` has an envelope curve that animates + * a `timed note` has an envelope curve that animates the :strength EffectAttr over time - * an `untimed note` has no curve, a fixed strength, but - still passes the wall clock time to its effect so the + * an `untimed note` has no curve, a fixed strength, but + still passes the wall clock time to its effect so the effect can include animation. A `Fader` is an untimed note. - - This obj is immutable, I think, but the graph can change, - which can affect the output. However, I think this doesn't - do its own rebuilds, and it's up to the caller to addHandler + + This obj is immutable, I think, but the graph can change, + which can affect the output. However, I think this doesn't + do its own rebuilds, and it's up to the caller to addHandler around the creation of Note objects. """ graph: SyncedGraph @@ -54,7 +52,7 @@ class Note: # simpleOutputs: SimpleOutputs timed: bool = True - def __post_init__(self): # graph ok + def __post_init__(self): # graph ok ec = self.graph.value(self.uri, L9['effectClass']) if ec is None: raise ValueError(f'note {self.uri} has no :effectClass') @@ -70,10 +68,10 @@ class Note: self.points.sort() else: self.points = [] - self.value = typedValue(float, self.graph, self.uri, L9['value']) + self.value = typedValue(float, self.graph, self.uri, L9['value']) - def getBaseEffectSettings(self) -> BareEffectSettings: # graph ok - """i think these are settings that are fixed over time, + def getBaseEffectSettings(self) -> BareEffectSettings: # graph ok + """i think these are settings that are fixed over time, e.g. that you set in the note's body in the timeline editor """ out: Dict[EffectAttr, VTUnion] = {} @@ -119,14 +117,14 @@ class Note: return y def outputCurrent(self): # no graph - + return self._outputSettings(t=None, strength=self.value) def _outputSettings( self, t: float | None, strength: Optional[float] = None # - ) -> Tuple[BareEffectSettings, Dict]: # no graph + ) -> Tuple[BareEffectSettings, Dict]: # no graph if t is None: if self.timed: diff --git a/light9/effect/sequencer/note_test.py b/light9/effect/sequencer/note_test.py --- a/light9/effect/sequencer/note_test.py +++ b/light9/effect/sequencer/note_test.py @@ -17,10 +17,10 @@ PREFIXES = ''' ''' FADER_GRAPH = PREFIXES + ''' - :fade1 - a :Fader; - :effectClass effect:effect1; - :effectAttr :strength; + :fade1 + a :Fader; + :effectClass effect:effect1; + :effectAttr :strength; :value 0.6 . ''' @@ -36,39 +36,38 @@ class TestUntimedFaderNote: NOTE_GRAPH = PREFIXES + ''' - :brightness - a :DeviceAttr; - rdfs:label "brightness"; - :dataType :scalar . + :brightness + a :DeviceAttr; + rdfs:label "brightness"; + :dataType :scalar . - :strength - a :EffectAttr; - rdfs:label "strength" . + :strength + a :EffectAttr; + rdfs:label "strength" . + + :SimpleDimmer + a :DeviceClass; + rdfs:label "SimpleDimmer"; + :deviceAttr :brightness; + :attr [ :outputAttr :level; :dmxOffset 0 ] . - :SimpleDimmer - a :DeviceClass; - rdfs:label "SimpleDimmer"; - :deviceAttr :brightness; - :attr [ :outputAttr :level; :dmxOffset 0 ] . - - dev:light1 - a :SimpleDimmer; - :dmxUniverse dmxA:; - :dmxBase 178 . + dev:light1 + a :SimpleDimmer; + :dmxUniverse dmxA:; + :dmxBase 178 . - effect:effect1 - a :EffectClass; - :setting effect:effect1_set1 . - effect:effect1_set1 - :device dev:light1; - :deviceAttr :brightness; - :scaledValue 0.5 . - :fade1 - a :Fader; - :effectClass effect:effect1; - :effectAttr :strength; - :value 0.6 . - + effect:effect1 + a :EffectClass; + :setting effect:effect1_set1 . + effect:effect1_set1 + :device dev:light1; + :deviceAttr :brightness; + :scaledValue 0.5 . + :fade1 + a :Fader; + :effectClass effect:effect1; + :effectAttr :strength; + :value 0.6 . ''' diff --git a/light9/effect/sequencer/service.py b/light9/effect/sequencer/service.py --- a/light9/effect/sequencer/service.py +++ b/light9/effect/sequencer/service.py @@ -45,23 +45,28 @@ async def changes(): async def send_page_updates(request): return EventSourceResponse(changes()) + ################################################################### -async def _send_one(faders:FaderEval): +async def _send_one(faders: FaderEval): ds = faders.computeOutput() await sendToCollector('effectSequencer', session='0', settings=ds) + async def _forever(faders): while True: await _send_one(faders) await asyncio.sleep(0.1) + def send_updates_forever(faders): asyncio.create_task(_forever(faders)) + #################################################################### + def main(): session = 'effectSequencer' graph = SyncedGraph(networking.rdfdb.url, "effectSequencer") diff --git a/light9/effect/sequencer/service_test.py b/light9/effect/sequencer/service_test.py --- a/light9/effect/sequencer/service_test.py +++ b/light9/effect/sequencer/service_test.py @@ -1,5 +1,5 @@ +import asyncio -import asyncio from light9.run_local import log diff --git a/light9/effect/settings.py b/light9/effect/settings.py --- a/light9/effect/settings.py +++ b/light9/effect/settings.py @@ -10,16 +10,16 @@ from __future__ import annotations import decimal import logging from dataclasses import dataclass -from typing import Any, Dict, Iterable, List, Sequence, Set, Tuple, Union, cast -from light9.localsyncedgraph import LocalSyncedGraph +from typing import Any, Dict, Iterable, List, Sequence, Set, Tuple, cast import numpy +from rdfdb.syncedgraph.syncedgraph import SyncedGraph from rdflib import Literal, URIRef from light9.collector.device import resolve +from light9.localsyncedgraph import LocalSyncedGraph from light9.namespaces import L9, RDF -from light9.newtypes import DeviceAttr, DeviceUri, EffectAttr, HexColor, VTUnion -from rdfdb.syncedgraph.syncedgraph import SyncedGraph +from light9.newtypes import (DeviceAttr, DeviceUri, EffectAttr, HexColor, VTUnion) log = logging.getLogger('settings') diff --git a/light9/effect/settings_test.py b/light9/effect/settings_test.py --- a/light9/effect/settings_test.py +++ b/light9/effect/settings_test.py @@ -1,18 +1,19 @@ +import unittest from typing import cast -import unittest -from light9.newtypes import DeviceAttr, DeviceUri, HexColor, VTUnion + +from rdfdb.patch import Patch from rdflib import Literal -from rdfdb.patch import Patch + +from light9.effect.settings import DeviceSettings from light9.localsyncedgraph import LocalSyncedGraph -from light9.namespaces import L9, DEV -from light9.effect.settings import DeviceSettings +from light9.namespaces import DEV, L9 +from light9.newtypes import DeviceAttr, DeviceUri, HexColor, VTUnion class TestDeviceSettings(unittest.TestCase): def setUp(self): - self.graph = LocalSyncedGraph( - files=['test/cam/lightConfig.n3', 'test/cam/bg.n3']) + self.graph = LocalSyncedGraph(files=['test/cam/lightConfig.n3', 'test/cam/bg.n3']) def testToVectorZero(self): ds = DeviceSettings(self.graph, []) @@ -31,14 +32,12 @@ class TestDeviceSettings(unittest.TestCa self.assertFalse(s1 != s2) def testMissingFieldsEqZero(self): - self.assertEqual( - DeviceSettings(self.graph, [ - (L9['aura1'], L9['rx'], 0), - ]), DeviceSettings(self.graph, [])) + self.assertEqual(DeviceSettings(self.graph, [ + (L9['aura1'], L9['rx'], 0), + ]), DeviceSettings(self.graph, [])) def testFalseIfZero(self): - self.assertTrue( - DeviceSettings(self.graph, [(L9['aura1'], L9['rx'], 0.1)])) + self.assertTrue(DeviceSettings(self.graph, [(L9['aura1'], L9['rx'], 0.1)])) self.assertFalse(DeviceSettings(self.graph, [])) def testFromResource(self): @@ -56,27 +55,20 @@ class TestDeviceSettings(unittest.TestCa ])) s = DeviceSettings.fromResource(self.graph, DeviceUri(L9['foo'])) - self.assertEqual( - DeviceSettings(self.graph, [ - (L9['light1'], L9['brightness'], 0.1), - (L9['light1'], L9['speed'], 0.2), - ]), s) + self.assertEqual(DeviceSettings(self.graph, [ + (L9['light1'], L9['brightness'], 0.1), + (L9['light1'], L9['speed'], 0.2), + ]), s) def testToVector(self): v = DeviceSettings(self.graph, [ (DeviceUri(DEV['aura1']), DeviceAttr(L9['rx']), 0.5), (DeviceUri(DEV['aura1']), DeviceAttr(L9['color']), HexColor('#00ff00')), ]).toVector() - self.assertEqual([ - 0, 1, 0, 0.5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0 - ], v) + self.assertEqual([0, 1, 0, 0.5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], v) def testFromVector(self): - s = DeviceSettings.fromVector(self.graph, [ - 0, 1, 0, 0.5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0 - ]) + s = DeviceSettings.fromVector(self.graph, [0, 1, 0, 0.5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) self.assertEqual( DeviceSettings(self.graph, [ @@ -86,7 +78,7 @@ class TestDeviceSettings(unittest.TestCa def testAsList(self): sets = [ - (DeviceUri(L9['light1']), DeviceAttr(L9['attr2']), cast(VTUnion,0.3)), + (DeviceUri(L9['light1']), DeviceAttr(L9['attr2']), cast(VTUnion, 0.3)), (DeviceUri(L9['light1']), DeviceAttr(L9['attr1']), 0.5), ] self.assertCountEqual(sets, DeviceSettings(self.graph, sets).asList()) @@ -132,22 +124,15 @@ ZOOM = L9['zoom'] class TestFromBlend(unittest.TestCase): def setUp(self): - self.graph = LocalSyncedGraph( - files=['test/cam/lightConfig.n3', 'test/cam/bg.n3']) + self.graph = LocalSyncedGraph(files=['test/cam/lightConfig.n3', 'test/cam/bg.n3']) def testSingle(self): - self.assertEqual( - DeviceSettings(self.graph, [(L1, ZOOM, 0.5)]), - DeviceSettings.fromBlend( - self.graph, - [(1, DeviceSettings(self.graph, [(L1, ZOOM, 0.5)]))])) + self.assertEqual(DeviceSettings(self.graph, [(L1, ZOOM, 0.5)]), + DeviceSettings.fromBlend(self.graph, [(1, DeviceSettings(self.graph, [(L1, ZOOM, 0.5)]))])) def testScale(self): - self.assertEqual( - DeviceSettings(self.graph, [(L1, ZOOM, 0.1)]), - DeviceSettings.fromBlend( - self.graph, - [(.2, DeviceSettings(self.graph, [(L1, ZOOM, 0.5)]))])) + self.assertEqual(DeviceSettings(self.graph, [(L1, ZOOM, 0.1)]), + DeviceSettings.fromBlend(self.graph, [(.2, DeviceSettings(self.graph, [(L1, ZOOM, 0.5)]))])) def testMixFloats(self): self.assertEqual( diff --git a/light9/effect/simple_outputs.py b/light9/effect/simple_outputs.py --- a/light9/effect/simple_outputs.py +++ b/light9/effect/simple_outputs.py @@ -12,7 +12,7 @@ log = logging.getLogger('simple') class SimpleOutputs: """ - Watches graph for effects that are just fading output attrs. + Watches graph for effects that are just fading output attrs. Call `values` to get (dev,attr):value settings. """ @@ -25,7 +25,7 @@ class SimpleOutputs: self.graph.addHandler(self.updateEffectsFromGraph) def updateEffectsFromGraph(self): - self.effectOutputs={} + self.effectOutputs = {} for effect in self.graph.subjects(RDF.type, L9['Effect']): log.debug(f' {effect=}') settings = [] diff --git a/light9/newtypes.py b/light9/newtypes.py --- a/light9/newtypes.py +++ b/light9/newtypes.py @@ -1,5 +1,6 @@ import decimal -from typing import Tuple, NewType, Type, TypeVar, Union, cast +from typing import NewType, Tuple, Type, TypeVar, Union, cast + from rdflib import URIRef from rdflib.term import Node @@ -12,7 +13,7 @@ DeviceClass = NewType('DeviceClass', URI DmxIndex = NewType('DmxIndex', int) # 1..512 DmxMessageIndex = NewType('DmxMessageIndex', int) # 0..511 DeviceAttr = NewType('DeviceAttr', URIRef) # e.g. :rx -EffectClass = NewType('EffectClass', URIRef) # e.g. effect:chase +EffectClass = NewType('EffectClass', URIRef) # e.g. effect:chase EffectAttr = NewType('EffectAttr', URIRef) # e.g. :chaseSpeed NoteUri = NewType('NoteUri', URIRef) OutputAttr = NewType('OutputAttr', URIRef) # e.g. :xFine @@ -42,7 +43,7 @@ def _isSubclass2(t1: Type, t2: Type) -> def typedValue(objType: Type[_ObjType], graph, subj, pred) -> _ObjType: - """graph.value(subj, pred) with a given return type. + """graph.value(subj, pred) with a given return type. If objType is not an rdflib.Node, we toPython() the value.""" obj = graph.value(subj, pred) if obj is None: