Changeset - 44fc477970bf
[Not reviewed]
pdm.lock
Show inline comments
 
@@ -2,13 +2,13 @@
 
# It is not intended for manual editing.
 

	
 
[metadata]
 
groups = ["default", "dev"]
 
strategy = ["cross_platform", "inherit_metadata"]
 
lock_version = "4.4.1"
 
content_hash = "sha256:5393d5c679935ba9f042f2b4f4d6efd58dbf03519b2e9f25e08f1e9d421e52f1"
 
content_hash = "sha256:6fac24ed6ab93fd328a74d22973a01c77d9de5b4a0b22cd111a884afd99f235f"
 

	
 
[[package]]
 
name = "aiohttp"
 
version = "3.9.5"
 
requires_python = ">=3.8"
 
summary = "Async http client/server framework (asyncio)"
 
@@ -2312,25 +2312,12 @@ groups = ["dev"]
 
files = [
 
    {file = "zipp-3.18.1-py3-none-any.whl", hash = "sha256:206f5a15f2af3dbaee80769fb7dc6f249695e940acca08dfb2a4769fe61e538b"},
 
    {file = "zipp-3.18.1.tar.gz", hash = "sha256:2884ed22e7d8961de1c9a05142eb69a247f120291bc0206a00a7642f09b5b715"},
 
]
 

	
 
[[package]]
 
name = "zmq"
 
version = "0.0.0"
 
summary = "You are probably looking for pyzmq."
 
groups = ["default"]
 
dependencies = [
 
    "pyzmq",
 
]
 
files = [
 
    {file = "zmq-0.0.0.tar.gz", hash = "sha256:6b1a1de53338646e8c8405803cffb659e8eb7bb02fff4c9be62a7acfac8370c9"},
 
    {file = "zmq-0.0.0.zip", hash = "sha256:21cfc6be254c9bc25e4dabb8a3b2006a4227966b7b39a637426084c8dc6901f7"},
 
]
 

	
 
[[package]]
 
name = "zope-interface"
 
version = "6.3"
 
requires_python = ">=3.7"
 
summary = "Interfaces for Python"
 
groups = ["default"]
 
dependencies = [
pyproject.toml
Show inline comments
 
@@ -35,13 +35,12 @@ dependencies = [
 
    "uvicorn[standard]>=0.17.6",
 
    "watchdog>=2.1.7",
 
    "webcolors>=1.11.1",
 
    "scipy>=1.9.3",
 
    "braillegraph>=0.6",
 
    "tenacity>=8.2.2",
 
    "zmq>=0.0.0",
 
    "mido>=1.2.10",
 
    "alsa-midi>=1.0.1",
 
    "treq>=22.2.0",
 
    "light9 @ file:///${PROJECT_ROOT}/",
 
    "python-debouncer>=0.1.4",
 
    "pytest>=8.2.0",
src/light9/collector/collector.py
Show inline comments
 
import logging
 
import time
 
from typing import Dict, List, Set, Tuple, cast
 
from light9.typedgraph import typedValue
 

	
 
from prometheus_client import Summary
 
from rdfdb.syncedgraph.syncedgraph import SyncedGraph
 
from rdflib import URIRef
 

	
 
from light9.collector.device import resolve, toOutputAttrs
 
from light9.collector.output import Output as OutputInstance
 
from light9.collector.weblisteners import WebListeners
 
from light9.effect.settings import DeviceSettings
 
from light9.namespaces import L9, RDF
 
from light9.newtypes import (ClientSessionType, ClientType, DeviceAttr, DeviceClass, DeviceSetting, DeviceUri, DmxIndex, DmxMessageIndex, OutputAttr,
 
                             OutputRange, OutputUri, OutputValue, UnixTime, VTUnion, uriTail)
 
from light9.newtypes import (
 
    ClientSessionType,
 
    ClientType,
 
    DeviceAttr,
 
    DeviceClass,
 
    DeviceUri,
 
    DmxIndex,
 
    DmxMessageIndex,
 
    OutputAttr,
 
    OutputRange,
 
    OutputUri,
 
    OutputValue,
 
    UnixTime,
 
    VTUnion,
 
    uriTail,
 
)
 
from light9.typedgraph import typedValue
 

	
 
log = logging.getLogger('collector')
 

	
 
STAT_SETATTR = Summary('set_attr', 'setAttr calls')
 

	
 

	
 
def makeDmxMessageIndex(base: DmxIndex, offset: DmxIndex) -> DmxMessageIndex:
 
    return DmxMessageIndex(base + offset - 1)
 

	
 

	
 
def _outputMap(graph: SyncedGraph, outputs: Set[OutputUri]) -> Dict[Tuple[DeviceUri, OutputAttr], Tuple[OutputUri, DmxMessageIndex]]:
 
    """From rdf config graph, compute a map of
src/light9/collector/collector_client_asyncio.py
Show inline comments
 
import asyncio
 
import json
 
import logging
 
import time
 
from light9 import networking
 
from light9.effect.settings import DeviceSettings
 

	
 
import zmq.asyncio
 
from prometheus_client import Summary
 

	
 
from light9.effect.settings import DeviceSettings
 

	
 
log = logging.getLogger('coll_client')
 

	
 
ZMQ_SEND = Summary('zmq_send', 'calls')
 

	
 

	
 
def toCollectorJson(client, session, settings: DeviceSettings) -> str:
src/light9/collector/device.py
Show inline comments
 
import logging
 
from typing import Dict, List, Any, TypeVar, cast
 
from light9.namespaces import L9
 
from typing import Dict, List, cast
 

	
 
import colormath.color_conversions
 
from colormath.color_objects import CMYColor, sRGBColor
 
from rdflib import Literal, URIRef
 
from webcolors import hex_to_rgb, rgb_to_hex
 
from colormath.color_objects import sRGBColor, CMYColor
 
import colormath.color_conversions
 
from light9.newtypes import VT, DeviceClass, HexColor, OutputAttr, OutputValue, DeviceUri, DeviceAttr, VTUnion
 

	
 
from light9.namespaces import L9
 
from light9.newtypes import (
 
    DeviceAttr,
 
    DeviceClass,
 
    HexColor,
 
    OutputAttr,
 
    OutputValue,
 
    VTUnion,
 
)
 

	
 
log = logging.getLogger('device')
 

	
 

	
 
class Device:
 
    pass
 
@@ -47,16 +56,13 @@ def _8bit(f):
 
def _maxColor(values: List[HexColor]) -> HexColor:
 
    rgbs = [hex_to_rgb(v) for v in values]
 
    maxes = [max(component) for component in zip(*rgbs)]
 
    return cast(HexColor, rgb_to_hex(tuple(maxes)))
 

	
 

	
 
def resolve(
 
        deviceType: DeviceClass,
 
        deviceAttr: DeviceAttr,
 
        values: List[VTUnion]) -> VTUnion:  # todo: return should be VT
 
def resolve(deviceType: DeviceClass, deviceAttr: DeviceAttr, values: List[VTUnion]) -> VTUnion:  # todo: return should be VT
 
    """
 
    return one value to use for this attr, given a set of them that
 
    have come in simultaneously. len(values) >= 1.
 

	
 
    bug: some callers are passing a device instance for 1st arg
 
    """
 
@@ -79,13 +85,15 @@ def resolve(
 
        return sum(floatVals) / len(floatVals)
 
    return max(values)
 

	
 

	
 
def toOutputAttrs(
 
        deviceType: DeviceClass,
 
        deviceAttrSettings: Dict[DeviceAttr, VTUnion  # TODO
 
        deviceAttrSettings: Dict[
 
            DeviceAttr,
 
            VTUnion  # TODO
 
                                ]) -> Dict[OutputAttr, OutputValue]:
 
    return dict((OutputAttr(u), OutputValue(v)) for u, v in untype_toOutputAttrs(deviceType, deviceAttrSettings).items())
 

	
 

	
 
def untype_toOutputAttrs(deviceType, deviceAttrSettings) -> Dict[URIRef, int]:
 
    """
src/light9/collector/weblisteners.py
Show inline comments
 
import asyncio
 
import io
 
import json
 
import logging
 
import time
 
from typing import Any, Awaitable, Dict, List, Protocol, Tuple
 

	
 
import fastavro
 
from fastavro.schema import load_schema
 
from light9.collector.output import Output as OutputInstance
 
from light9.newtypes import (DeviceUri, DmxIndex, DmxMessageIndex, OutputAttr, OutputUri, OutputValue)
 
import starlette.websockets
 
import websockets
 

	
 
log = logging.getLogger('weblisteners')
 

	
 
@@ -94,8 +92,7 @@ class WebListeners:
 
        for row in attrRows:
 
            row['chan'] = '%s %s' % (row['chan'][0], row['chan'][1])
 

	
 
        out = io.BytesIO()
 
        fastavro.schemaless_writer(out, self.CollectorUpdateSchema, {'OutputAttrsSet': {'dev': dev, 'attrs': attrRows}})
 
        msg = out.getvalue()
 
        log.info(f'made update message {len(msg)=}')
 
        return msg
src/light9/effect/edit.py
Show inline comments
 
from rdflib import URIRef, Literal
 
import treq
 
from rdfdb.patch import Patch
 
from rdflib import Literal, URIRef
 
from twisted.internet.defer import inlineCallbacks, returnValue
 
import treq
 

	
 
from light9 import networking
 
from light9.curvecalc.curve import CurveResource
 
from light9.namespaces import L9, RDF, RDFS
 
from rdfdb.patch import Patch
 

	
 

	
 
def clamp(x, lo, hi):
 
    return max(lo, min(hi, x))
 

	
 

	
 
@@ -50,19 +50,16 @@ def songEffectPatch(graph, dropped, song
 
        if L9['EffectClass'] in droppedTypes:
 
            quads.extend([
 
                (effect, RDF.type, dropped, ctx),
 
            ] + [(effect, L9['code'], c, ctx) for c in droppedCodes])
 
        elif L9['Submaster'] in droppedTypes:
 
            quads.extend([
 
                (effect, L9['code'], Literal('out = %s * env' % dropped.n3()),
 
                 ctx),
 
                (effect, L9['code'], Literal('out = %s * env' % dropped.n3()), ctx),
 
            ])
 
        else:
 
            raise NotImplementedError(
 
                "don't know how to add an effect from %r (types=%r)" %
 
                (dropped, droppedTypes))
 
            raise NotImplementedError("don't know how to add an effect from %r (types=%r)" % (dropped, droppedTypes))
 

	
 
        _maybeAddMusicLine(quads, effect, song, ctx)
 

	
 
    print("adding")
 
    for qq in quads:
 
        print(qq)
 
@@ -87,35 +84,31 @@ def songNotePatch(graph, dropped, song, 
 
        songTime = musicStatus['t']
 
        _finishCurve(graph, note, quads, ctx, songTime)
 
    else:
 
        if L9['Effect'] in droppedTypes:
 
            musicStatus = yield getMusicStatus()
 
            songTime = musicStatus['t']
 
            note = _makeNote(graph, song, note, quads, ctx, dropped, songTime,
 
                             event, fade)
 
            note = _makeNote(graph, song, note, quads, ctx, dropped, songTime, event, fade)
 
        else:
 
            raise NotImplementedError
 

	
 
    returnValue((note, Patch(addQuads=quads)))
 

	
 

	
 
def _point(ctx, uri, t, v):
 
    return [(uri, L9['time'], Literal(round(t, 3)), ctx),
 
            (uri, L9['value'], Literal(round(v, 3)), ctx)]
 
    return [(uri, L9['time'], Literal(round(t, 3)), ctx), (uri, L9['value'], Literal(round(v, 3)), ctx)]
 

	
 

	
 
def _finishCurve(graph, note, quads, ctx, songTime):
 
    with graph.currentState() as g:
 
        origin = g.value(note, L9['originTime']).toPython()
 
        curve = g.value(note, L9['curve'])
 

	
 
    pt2 = graph.sequentialUri(curve + 'p')
 
    pt3 = graph.sequentialUri(curve + 'p')
 
    quads.extend([(curve, L9['point'], pt2, ctx)] +
 
                 _point(ctx, pt2, songTime - origin, 1) +
 
                 [(curve, L9['point'], pt3, ctx)] +
 
    quads.extend([(curve, L9['point'], pt2, ctx)] + _point(ctx, pt2, songTime - origin, 1) + [(curve, L9['point'], pt3, ctx)] +
 
                 _point(ctx, pt3, songTime - origin + .5, 0))
 

	
 

	
 
def _makeNote(graph, song, note, quads, ctx, dropped, songTime, event, fade):
 
    note = graph.sequentialUri(song + '/n')
 
    curve = graph.sequentialUri(note + 'c')
 
@@ -196,10 +189,8 @@ def _maybeAddMusicLine(quads, effect, so
 
    add a line getting the current music into 'music' if any code might
 
    be mentioning that var
 
    """
 

	
 
    for spoc in quads:
 
        if spoc[1] == L9['code'] and 'music' in spoc[2]:
 
            quads.extend([(effect, L9['code'],
 
                           Literal('music = %s' % musicCurveForSong(song).n3()),
 
                           ctx)])
 
            quads.extend([(effect, L9['code'], Literal('music = %s' % musicCurveForSong(song).n3()), ctx)])
 
            break
src/light9/effect/effect_functions.py
Show inline comments
 
import logging
 
import random
 
from typing import cast
 

	
 
from PIL import Image
 
from webcolors import rgb_to_hex
 

	
 
from light9.effect.scale import scale
 
from light9.effect.settings import DeviceSettings
 
from light9.namespaces import L9
 
from light9.newtypes import HexColor
 

	
 
random.seed(0)
 

	
 
log = logging.getLogger('effectfunc')
 

	
 

	
 
def sample8(img, x, y, repeat=False):
 
def sample8(img, x, y, repeat=False) -> tuple[int, int, int]:
 
    if not (0 <= y < img.height):
 
        return (0, 0, 0)
 
    if 0 <= x < img.width:
 
        return img.getpixel((x, y))
 
    elif not repeat:
 
        return (0, 0, 0)
 
@@ -51,13 +53,13 @@ def effect_image(
 
    period: float,
 
    image: Image.Image,
 
    devs: DeviceSettings,
 
) -> DeviceSettings:
 
    x = int((songTime / period) * image.width)
 
    out = []
 
    for y, (d, da, v) in enumerate(devs.asOrderedList()):
 
    for y, (d, da, v) in enumerate(devs.asList()):
 
        if da != L9['color']:
 
            continue
 
        color8 = sample8(image, x, y, repeat=True)
 
        color = rgb_to_hex(tuple(color8))
 
        out.append((d, da, scale(color, strength * v)))
 
    return DeviceSettings(devs.graph, out)
 
\ No newline at end of file
 
        color = HexColor(rgb_to_hex(color8))
 
        out.append((d, da, scale(color, strength * cast(float, v))))
 
    return DeviceSettings(devs.graph, out)
src/light9/effect/effecteval2.py
Show inline comments
 
import traceback
 
import inspect
 
import logging
 
import traceback
 
from dataclasses import dataclass
 
from typing import Callable, List, Optional
 

	
 
from rdfdb.syncedgraph.syncedgraph import SyncedGraph
 
from rdflib import RDF
 
from rdflib.term import Node
 

	
 
from light9.effect.effect_function_library import EffectFunctionLibrary
 
from light9.effect.settings import DeviceSettings, EffectSettings
 
from light9.namespaces import L9
 
from light9.newtypes import (DeviceAttr, DeviceUri, EffectAttr, EffectFunction, EffectUri, VTUnion)
 
from light9.newtypes import (
 
    DeviceAttr,
 
    DeviceUri,
 
    EffectAttr,
 
    EffectFunction,
 
    EffectUri,
 
    VTUnion,
 
)
 
from light9.typedgraph import typedValue
 

	
 
log = logging.getLogger('effecteval')
 

	
 

	
 
@dataclass
src/light9/effect/scale.py
Show inline comments
 
import logging
 
from decimal import Decimal
 

	
 
from webcolors import hex_to_rgb, rgb_to_hex
 

	
 
from light9.newtypes import VTUnion
 

	
 
log = logging.getLogger('scale')
 

	
 

	
 
def scale(value: VTUnion, strength: float):
 
    if isinstance(value, Decimal):
 
        raise TypeError()
 

	
 
    if isinstance(value, str):
src/light9/effect/sequencer/eval_faders.py
Show inline comments
 
import traceback
 
import logging
 
import time
 
import traceback
 
from dataclasses import dataclass
 
from typing import List, Optional, cast
 

	
 
from prometheus_client import Summary
 
from rdfdb import SyncedGraph
 
from rdflib import URIRef
src/light9/effect/sequencer/service.py
Show inline comments
 
@@ -11,14 +11,14 @@ from louie import dispatcher
 
from rdfdb.syncedgraph.syncedgraph import SyncedGraph
 
from sse_starlette.sse import EventSourceResponse
 
from starlette.applications import Starlette
 
from starlette.routing import Route
 
from starlette_exporter import PrometheusMiddleware, handle_metrics
 

	
 
from light9 import networking
 
from light9.background_loop import loop_forever
 
from light9 import networking
 
from light9.collector.collector_client_asyncio import sendToCollector
 
from light9.effect.effect_function_library import EffectFunctionLibrary
 
from light9.effect.sequencer.eval_faders import FaderEval
 
from light9.effect.sequencer.sequencer import Sequencer, StateUpdate
 
from light9.run_local import log
 

	
src/light9/effect/settings.py
Show inline comments
 
@@ -16,13 +16,13 @@ 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 light9.newtypes import DeviceAttr, DeviceUri, EffectAttr, HexColor, VTUnion
 

	
 
log = logging.getLogger('settings')
 

	
 

	
 
def parseHex(h):
 
    if h[0] != '#':
0 comments (0 inline, 0 general)