changeset 2389:0e90dd50e8c4

midifade: improve the midi-event-skip behavior
author drewp@bigasterisk.com
date Tue, 14 May 2024 12:16:53 -0700
parents 3cd80b266561
children 485148ef5686
files src/light9/midifade/eventqueue.py src/light9/midifade/midifade.py src/light9/midifade/pages.py
diffstat 3 files changed, 47 insertions(+), 26 deletions(-) [+]
line wrap: on
line diff
--- a/src/light9/midifade/eventqueue.py	Mon May 13 21:39:53 2024 -0700
+++ b/src/light9/midifade/eventqueue.py	Tue May 14 12:16:53 2024 -0700
@@ -1,9 +1,19 @@
 import asyncio
+from dataclasses import dataclass
 import logging
 import traceback
 
 log = logging.getLogger()
 
+@dataclass
+class Event:
+    dev: str
+    type: str
+    control: int
+    value: int
+    def __repr__(self):
+        return f"<Event {self.dev}/{self.type}/{self.control}->{self.value}>"
+
 
 class EventQueue:
     """midi events come in fast; graph consumes them slower"""
@@ -11,29 +21,32 @@
     def __init__(self, MAX_SEND_RATE: float, onMessage) -> None:
         self.MAX_SEND_RATE = MAX_SEND_RATE
         self.onMessage = onMessage
-        self.msgs = asyncio.Queue()
+        self.newEvents: asyncio.Queue[Event] = asyncio.Queue()
 
     def callbackFor(self, dev):
         mainThreadLoop = asyncio.get_running_loop()
 
         def cb(message):
             # this is running in mido's thread
-            log.info(f'enqueue {message} {"*" * message.dict()["value"]}')
+            d = message.dict()
+            ev = Event(dev, d['type'], d['control'], d['value'])
+            log.info(f'enqueue {ev} {"*" * ev.value}')
             mainThreadLoop.call_soon_threadsafe(
-                self.msgs.put_nowait,
-                message.dict() | {'dev': dev},
+                self.newEvents.put_nowait,
+                ev,
             )
 
         return cb
 
     async def run(self):
         while True:
-            recents = [await self.msgs.get()]
-            while not self.msgs.empty():
-                recents.append(self.msgs.get_nowait())
+            recentEvents = [await self.newEvents.get()]
+            while not self.newEvents.empty():
+                recentEvents.append(self.newEvents.get_nowait())
+            log.info(f'{recentEvents=}')
             try:
-                for msg in reduceToLatestValue(recents):
-                    # log.info(f'handle {msg=}')
+                for msg in latestEventPerControl(recentEvents):
+                    log.info(f'handle {msg=}')
                     await self.onMessage(msg)
             except Exception:
                 traceback.print_exc()
@@ -41,9 +54,14 @@
             await asyncio.sleep(1 / self.MAX_SEND_RATE)
 
 
-def reduceToLatestValue(ms: list[dict]) -> list[dict]:
-    merge = {}
-    for m in ms:
-        normal_key = tuple(sorted(dict((k, v) for k, v in m.items() if k != 'value')))
-        merge[normal_key] = m
-    return list(merge.values())
+def latestEventPerControl(evs: list[Event]) -> list[Event]:
+    ret = []
+    seenControl = set()
+    for ev in reversed(evs):
+        c = (ev.dev, ev.control)
+        if c in seenControl:
+            continue
+        seenControl.add(c)
+        ret.append(ev)
+    ret.reverse()
+    return ret
--- a/src/light9/midifade/midifade.py	Mon May 13 21:39:53 2024 -0700
+++ b/src/light9/midifade/midifade.py	Tue May 14 12:16:53 2024 -0700
@@ -11,7 +11,7 @@
 import mido
 from light9 import networking
 from light9.effect.edit import clamp
-from light9.midifade.eventqueue import EventQueue
+from light9.midifade.eventqueue import Event, EventQueue
 from light9.midifade.mididevs import connectToMidiOutput, listenToMidiInputs
 from light9.midifade.pages import Pages
 from light9.namespaces import L9
@@ -44,28 +44,28 @@
     graph.patchObject(ctx, L9.grandMaster, L9['value'], decimalLiteral(newValue))
 
 
-async def onMessage(graph: SyncedGraph, pages: Pages, ctx: URIRef, _lastSet: dict[int, int], m: dict):
-    if m['type'] == 'active_sensing':
+async def onMessage(graph: SyncedGraph, pages: Pages, ctx: URIRef, _lastSet: dict[int, int], m: Event):
+    if m.type == 'active_sensing':
         return
-    if m['type'] == 'control_change':
-        if m['dev'] == 'bcf2000' and m['control'] == 91:
+    if m.type == 'control_change':
+        if m.dev == 'bcf2000' and m.control == 91:
             pages.changePage(-1)
             return
-        if m['dev'] == 'bcf2000' and m['control'] == 92:
+        if m.dev == 'bcf2000' and m.control == 92:
             pages.changePage(1)
             return
-        if m['dev'] == 'bcf2000' and m['control'] == 8:
-            changeGrandMaster(graph, clamp(m['value'] / 127 * 1.5, 0, 1), ctx)
+        if m.dev == 'bcf2000' and m.control == 8:
+            changeGrandMaster(graph, clamp(m.value / 127 * 1.5, 0, 1), ctx)
             return
 
         try:
-            fader = pages.lookupFader(m['dev'], m['control'])
+            fader = pages.lookupFader(m.dev, m.control)
         except KeyError:
             log.info(f'unknown control {m}')
             return
         try:
-            await writeHwValueToGraph(graph, ctx, fader, m['value'] / 127)
-            _lastSet[m['control']] = m['value']
+            await writeHwValueToGraph(graph, ctx, fader, m.value / 127)
+            _lastSet[m.control] = m.value
         except ValueError as e:
             log.warning(f'{e!r} - ignoring')
     else:
--- a/src/light9/midifade/pages.py	Mon May 13 21:39:53 2024 -0700
+++ b/src/light9/midifade/pages.py	Tue May 14 12:16:53 2024 -0700
@@ -70,6 +70,7 @@
         return mapping
 
     def compileCurrents(self):
+        log.info('compileCurrents')
         self.currentFaders.clear()
         try:
             new = self.getChansToFaders()
@@ -78,6 +79,8 @@
         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/dance2023/fadePage1f0'],