view src/light9/midifade/eventqueue.py @ 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
line wrap: on
line source

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"""

    def __init__(self, MAX_SEND_RATE: float, onMessage) -> None:
        self.MAX_SEND_RATE = MAX_SEND_RATE
        self.onMessage = onMessage
        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
            d = message.dict()
            ev = Event(dev, d['type'], d['control'], d['value'])
            log.info(f'enqueue {ev} {"*" * ev.value}')
            mainThreadLoop.call_soon_threadsafe(
                self.newEvents.put_nowait,
                ev,
            )

        return cb

    async def run(self):
        while True:
            recentEvents = [await self.newEvents.get()]
            while not self.newEvents.empty():
                recentEvents.append(self.newEvents.get_nowait())
            log.info(f'{recentEvents=}')
            try:
                for msg in latestEventPerControl(recentEvents):
                    log.info(f'handle {msg=}')
                    await self.onMessage(msg)
            except Exception:
                traceback.print_exc()
                log.warning("error in onMessage- continuing anyway")
            await asyncio.sleep(1 / self.MAX_SEND_RATE)


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