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): log.info('compileCurrents') 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: if not self.currentFaders: log.warn("lookupFader called when we had no current control->fader mapping") return { 'quneo': { 44: L9['show/dance2024/fadePage1f0'], 45: L9['show/dance2024/fadePage1f0'], 46: L9['show/dance2024/fadePage1f0'], }, 'bcf2000': self.currentFaders, }[dev][control]