Files @ 3cd80b266561
Branch filter:

Location: light9/src/light9/midifade/pages.py

drewp@bigasterisk.com
refactor midiFade a lot; try to catch all midi events even better,
even if rdfdb runs slow
import logging
from typing import cast

from light9.effect.edit import clamp
from light9.namespaces import L9
from rdfdb.syncedgraph.readonly_graph import ReadOnlyConjunctiveGraph
from rdfdb.syncedgraph.syncedgraph import SyncedGraph
from rdflib import RDF, Literal, URIRef

log = logging.getLogger()


class Pages:
    """converts between fader numbers and FaderUri, which is a mapping that can
    be changed by moving between pages"""

    def __init__(self, graph: SyncedGraph, ctx: URIRef):
        self.graph = graph
        self.ctx = ctx

        self.currentFaders = {}  # midi control channel num : FaderUri

    def getChansToFaders(self) -> dict[int, URIRef]:
        fadePage = self.getCurMappedPage()
        ret = []
        for f in self.graph.objects(fadePage, L9.fader):
            columnLit = cast(Literal, self.graph.value(f, L9['column']))
            col = int(columnLit.toPython())
            ret.append((col, f))

        ret.sort()
        ctl_channels = list(range(81, 88 + 1))
        out = {}
        for chan, (col, f) in zip(ctl_channels, ret):
            out[chan] = f
        return out

    def changePage(self, dp: int):
        """dp==-1, make the previous page active, etc. Writes to graph"""

        with self.graph.currentState() as current:
            allPages = sorted(current.subjects(RDF.type, L9.FadePage), key=lambda fp: str(fp))
            mapping = self.getGraphMappingNode(current)
            curPage = current.value(mapping, L9.outputs)
        if curPage is None:
            curPage = allPages[0]
        idx = allPages.index(curPage)
        newIdx = clamp(idx + dp, 0, len(allPages) - 1)
        log.info(f'change from {idx} {newIdx}')
        newPage = allPages[newIdx]
        self.setCurMappedPage(mapping, newPage)

    def getCurMappedPage(self) -> URIRef:
        mapping = self.getGraphMappingNode(self.graph)
        ret = self.graph.value(mapping, L9['outputs'])
        assert ret is not None
        return ret

    def setCurMappedPage(self, mapping: URIRef, newPage: URIRef):
        self.graph.patchObject(self.ctx, mapping, L9.outputs, newPage)

    def getGraphMappingNode(self, g: ReadOnlyConjunctiveGraph | SyncedGraph) -> URIRef:
        mapping = g.value(L9['midiControl'], L9['map'])
        if mapping is None:
            raise ValueError('no :midiControl :map ?mapping')
        midiDev = g.value(mapping, L9['midiDev'])
        ourDev = 'bcf2000'
        if midiDev != Literal(ourDev):
            raise NotImplementedError(f'need {mapping} to have :midiDev {ourDev!r}')
        return mapping

    def compileCurrents(self):
        self.currentFaders.clear()
        try:
            new = self.getChansToFaders()
        except ValueError:
            return  # e.g. empty-graph startup
        self.currentFaders.update(new)

    def lookupFader(self, dev: str, control: int) -> URIRef:
        return {
            'quneo': {
                44: L9['show/dance2023/fadePage1f0'],
                45: L9['show/dance2023/fadePage1f0'],
                46: L9['show/dance2023/fadePage1f0'],
            },
            'bcf2000': self.currentFaders,
        }[dev][control]