Changeset - ffae830fda12
[Not reviewed]
light9/effect/effecteval.py
Show inline comments
 
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))))
 

	
 

	
light9/effect/scale.py
Show inline comments
 
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
 

	
light9/effect/sequencer/eval_faders.py
Show inline comments
 
@@ -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
 
                )
light9/effect/sequencer/eval_faders_test.py
Show inline comments
 
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 : <http://light9.bigasterisk.com/> .
 
@prefix effect: <http://light9.bigasterisk.com/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
light9/effect/sequencer/note.py
Show inline comments
 
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:
light9/effect/sequencer/note_test.py
Show inline comments
 
@@ -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 .
 
        '''
 

	
 

	
light9/effect/sequencer/service.py
Show inline comments
 
@@ -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")
light9/effect/sequencer/service_test.py
Show inline comments
 
import asyncio
 

	
 
import asyncio
 
from light9.run_local import log
 

	
 

	
light9/effect/settings.py
Show inline comments
 
@@ -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')
 

	
light9/effect/settings_test.py
Show inline comments
 
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(
light9/effect/simple_outputs.py
Show inline comments
 
@@ -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 = []
light9/newtypes.py
Show inline comments
 
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:
0 comments (0 inline, 0 general)