Changeset - af38643cffd4
[Not reviewed]
default
0 1 0
drewp@bigasterisk.com - 20 months ago 2023-06-03 20:10:24
drewp@bigasterisk.com
jam in fader page control, fix _lastSet feedback
1 file changed with 91 insertions and 35 deletions:
0 comments (0 inline, 0 general)
light9/midifade/midifade.py
Show inline comments
 
@@ -5,12 +5,13 @@ Read midi events, write fade levels to g
 
import asyncio
 
import logging
 
import traceback
 
from typing import Dict, List
 
from typing import Dict, List, cast
 
from light9.effect.edit import clamp
 

	
 
import mido
 
from rdfdb.syncedgraph.syncedgraph import SyncedGraph
 
from rdflib import Literal, URIRef
 

	
 
from rdflib import RDF, ConjunctiveGraph, Literal, URIRef
 
from rdfdb.syncedgraph.readonly_graph import ReadOnlyConjunctiveGraph
 
from light9 import networking
 
from light9.namespaces import L9
 
from light9.newtypes import decimalLiteral
 
@@ -20,10 +21,70 @@ from light9.showconfig import showUri
 
mido.set_backend('alsa_midi.mido_backend')
 
MAX_SEND_RATE = 20
 

	
 
_lastSet = {}  #Fader:value
 
_lastSet = {}  #midictlchannel:value7bit
 

	
 
currentFaders = {}  # midi control channel num : FaderUri
 
ctx = URIRef(showUri() + '/fade')
 

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

	
 

	
 
def getGraphMappingNode(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 setFader(graph: SyncedGraph, ctx, fader: URIRef, strength: float):
 
def getCurMappedPage(g: SyncedGraph):
 
    mapping = getGraphMappingNode(g)
 
    return g.value(mapping, L9['outputs'])
 

	
 
def setCurMappedPage(g: SyncedGraph, mapping: URIRef, newPage:URIRef):
 
    g.patchObject(ctx, mapping, L9.outputs, newPage)
 

	
 
def getChansToFaders(g: SyncedGraph) -> Dict[int, URIRef]:
 
    fadePage = getCurMappedPage(g)
 
    ret = []
 
    for f in g.objects(fadePage, L9.fader):
 
        columnLit = cast(Literal, g.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(g: SyncedGraph, dp: int):
 
    """dp==-1, make the previous page active, etc. Writes to graph"""
 

	
 
    with g.currentState() as current:
 
        allPages = sorted(current.subjects(RDF.type, L9.FadePage), key=lambda fp: str(fp))
 
        mapping = 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)
 
    print('change from ', idx, newIdx)
 
    newPage =allPages[newIdx]
 
    setCurMappedPage(g, mapping, newPage)
 

	
 
def writeHwValueToGraph(graph: SyncedGraph, ctx, fader: URIRef, strength: float):
 
    log.info(f'setFader(fader={fader}, strength={strength:.03f}')
 
    valueLit = decimalLiteral(round(strength, 3))
 
    with graph.currentState() as g:
 
@@ -31,13 +92,18 @@ def setFader(graph: SyncedGraph, ctx, fa
 
    if fadeSet is None:
 
        raise ValueError(f'fader {fader} has no :setting')
 
    graph.patchObject(ctx, fadeSet, L9['value'], valueLit)
 
    _lastSet[fader] = strength
 

	
 

	
 
def onMessage(graph: SyncedGraph, ctx: URIRef, m: Dict):
 
    if m['type'] == 'active_sensing':
 
        return
 
    if m['type'] == 'control_change':
 
        if m['dev'] == 'bcf2000' and m['control'] == 91:
 
            changePage(graph, -1)
 
            return
 
        if m['dev'] == 'bcf2000' and m['control'] == 92:
 
            changePage(graph, 1)
 
            return
 

	
 
        try:
 
            fader = {
 
@@ -46,22 +112,14 @@ def onMessage(graph: SyncedGraph, ctx: U
 
                    45: L9['show/dance2023/fadePage1f0'],
 
                    46: L9['show/dance2023/fadePage1f0'],
 
                },
 
                'bcf2000': {
 
                    81: L9['show/dance2023/fader0'],
 
                    82: L9['show/dance2023/fader1'],
 
                    83: L9['show/dance2023/fader2'],
 
                    84: L9['show/dance2023/fader3'],
 
                    85: L9['show/dance2023/fader4'],
 
                    86: L9['show/dance2023/fader5'],
 
                    87: L9['show/dance2023/fader6'],
 
                    88: L9['show/dance2023/fader7'],
 
                }
 
                'bcf2000': currentFaders,
 
            }[m['dev']][m['control']]
 
        except KeyError:
 
            log.info(f'unknown control {m}')
 
            return
 
        try:
 
            setFader(graph, ctx, fader, m['value'] / 127)
 
            writeHwValueToGraph(graph, ctx, fader, m['value'] / 127)
 
            _lastSet[m['control']] = m['value']
 
        except ValueError as e:
 
            log.warning(f'{e!r} - ignoring')
 
    else:
 
@@ -91,29 +149,22 @@ class WriteBackFaders:
 

	
 
    def _update(self):
 
        g = self.graph
 
        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}')
 
        fadePage = g.value(mapping, L9['outputs'])
 
        nupdated=0
 
        faders = list(g.objects(fadePage, L9.fader))
 
        for f in faders:
 
            column = int(g.value(f, L9.column).toPython())
 
        m = getChansToFaders(g)
 
        for midi_ctl_addr, f in m.items():
 
            fset = g.value(f, L9.setting)
 
            # could split this to a separate handler per fader
 
            value = g.value(fset, L9.value).toPython()
 
            hwcurrent = int(self.getCurrentValue(f) * 127)
 
            hwcurrent = self.getCurrentValue(midi_ctl_addr) 
 
            hwgoal = int(value * 127)
 
            if abs(hwcurrent - hwgoal) > 5:
 
                midi_ctl_addr = column + 80
 
            print(f'{f} {hwcurrent=} {hwgoal=}')
 
            if abs(hwcurrent - hwgoal) > 2:
 
                self.sendToBcf(midi_ctl_addr, hwgoal)
 
                nupdated+=1
 
        log.info(f'updated {nupdated} of {len(faders)} connected faders')
 
        log.info(f'wrote to {nupdated} of {len(m)} mapped faders')
 

	
 
    def sendToBcf(self, control, value):
 
        _lastSet[control] = value
 
        msg = mido.Message('control_change', control=control, value=value)
 
        self.bcf_out.send(msg)
 

	
 
@@ -147,7 +198,7 @@ async def main():
 

	
 
    asyncio.create_task(reader())
 
    openPorts = []
 
    for inputName in mido.get_input_names():
 
    for inputName in mido.get_input_names():  # type: ignore
 
        if inputName.startswith('Keystation'):
 
            dev = "keystation"
 
        elif inputName.startswith('BCF2000'):
 
@@ -157,13 +208,18 @@ async def main():
 
        else:
 
            continue
 
        log.info(f'listening on input {inputName} {dev=}')
 
        openPorts.append(mido.open_input(  #
 
        openPorts.append(mido.open_input(  # type: ignore
 
            inputName,  #
 
            callback=lambda message, dev=dev: onMessageMidoThread(dev, message)))
 

	
 
    bcf_out = mido.open_output('BCF2000:BCF2000 MIDI 1 28:0')
 
    graph.addHandler(lambda: compileCurrents(graph))
 

	
 
    for outputName in mido.get_output_names():  # type: ignore
 
        if outputName.startswith('BCF2000'):
 
            bcf_out = mido.open_output(outputName)  # type: ignore
 
    wb = WriteBackFaders(graph, bcf_out, getCurrentValue=lambda f: _lastSet.get(f, 0))
 
    graph.addHandler(wb.update)
 
            break
 

	
 
    while True:
 
        await asyncio.sleep(1)
0 comments (0 inline, 0 general)