import asyncio
from functools import partial
import logging
from dataclasses import dataclass
import time
from typing import cast
from light9.typedgraph import typedValue
import mido
from debouncer import DebounceOptions, debounce
from light9.midifade.pages import Pages
from light9.namespaces import L9
from rdfdb.syncedgraph.syncedgraph import SyncedGraph
from rdflib import Literal, URIRef
log = logging.getLogger()
class FaderSyncer:
"""get this midi output set to the right value
todo: note that these could be active at the moment we switch fader pages,
which might result in the wrong output
"""
def __init__(self, fader: URIRef, getHwValue, getGraphValue, setHwValue, getLastMidiInTime):
self.fader = fader
self.getHwValue = getHwValue
self.getGraphValue = getGraphValue
self.setHwValue = setHwValue
self.getLastMidiInTime = getLastMidiInTime
self.done = False
asyncio.create_task(self.task())
async def task(self):
try:
await self._waitForPauseInInput()
goal = self.getGraphValue()
hw = self.getHwValue()
if abs(goal - hw) > 2:
log.info(f'hw at {hw} {goal=} -> send to hw')
self.setHwValue(goal)
finally:
self.done = True
async def _waitForPauseInInput(self):
while True:
if time.time() > self.getLastMidiInTime() + 1:
return
await asyncio.sleep(.05)
@dataclass
class WriteBackFaders:
graph: SyncedGraph
pages: Pages
bcf_out: mido.ports.BaseOutput
_lastSet: dict[int, int]
_lastMidiInTime: dict[int, float]
_syncer: dict[int, FaderSyncer]
def getCurrentValue(self, f):
return self._lastSet.get(f, 0)
def onGraphPatch(self):
for midi_ctl_addr, f in self.pages.getChansToFaders().items():
fset = typedValue(URIRef, self.graph, f, L9.setting)
self.graph.value(fset, L9.value) # dependency for handler
existingSyncer = self._syncer.get(midi_ctl_addr, None)
if not existingSyncer or existingSyncer.done:
self._syncer[midi_ctl_addr] = FaderSyncer(
f,
partial(self.getCurrentValue, midi_ctl_addr),
partial(self.getMidiValue, fset),
partial(self.sendToBcf, midi_ctl_addr),
partial(self._lastMidiInTime.get, midi_ctl_addr, 0),
)
def getMidiValue(self, fset: URIRef) -> int:
with self.graph.currentState() as g:
value = cast(Literal, g.value(fset, L9.value)).toPython()
return int(value * 127)
def sendToBcf(self, control: int, value: int):
self._lastSet[control] = value
msg = mido.Message('control_change', control=control, value=value)
self.bcf_out.send(msg)