How can I integrate Python mido and asyncio?
Asked Answered
S

1

7

I have a device which does file I/O over MIDI. I have a script using Mido that downloads files but it is a mess of global variables. I want to tidy it up to use asyncio properly but I am not sure how to integrate the mido callback. I think the documentation says I should use a Future object but I am not sure how the mido callback function can obtain that object.

Shaw answered 23/5, 2019 at 14:28 Comment(0)
H
8

mido provides a callback-based API which will invoke the callback from a different thread. Your implementation of the callback can communicate with asyncio by calling loop.call_soon_threadsafe. Note that you won't be able to just set the value of a Future because the callback will be called multiple times, and a future can only be set once - it is meant for one-shot computations.

A common pattern for multiply invoked callbacks is to push events onto an asyncio queue and pop stuff from it in asyncio code. This can be made even more convenient by exposing the queue as an async iterator. This function automates the process:

def make_stream():
    loop = asyncio.get_event_loop()
    queue = asyncio.Queue()
    def callback(message):
        loop.call_soon_threadsafe(queue.put_nowait, message)
    async def stream():
        while True:
            yield await queue.get()
    return callback, stream()

make_stream returns two objects:

  • a callback, which you can pass to mido.open_input()
  • a stream, which you can iterate with async for to get new messages

Whenever the callback is invoked by mido in its background thread, your asyncio async for loop iterating over the stream will wake up with a new item. Effectively, make_stream converts a threaded callback into an async iterator. For example (untested):

async def print_messages():
    # create a callback/stream pair and pass callback to mido
    cb, stream = make_stream()
    mido.open_input(callback=cb)

    # print messages as they come just by reading from stream
    async for message in stream:
        print(message)
Horwitz answered 23/5, 2019 at 17:14 Comment(5)
This is exactly what I was looking for but all it does print for me is an endless stream of <generator object Queue.get at 0x1080fc150> <generator object Queue.get at 0x1080fc0f8> regardless if I send any MIDI data. I understand it is untested, but I have a hard time figuring out what is wrong..Daglock
I changed yield queue.get() into yield await queue.get(). That did the trick. Thanks!Daglock
@Daglock You're right! queue is an asyncio queue, so its get() method is a coroutine and must be awaited, duh! BTW what version of Python are you using?Horwitz
we are on 3.6.10Daglock
@Daglock Thanks, that explains the output that says generator object Queue.get, and which threw me off at first - asyncio.Queue was modified to use async def for Python 3.7. You might want to consider switching to a newer version; Python 3.7 was released almost two years ago.Horwitz

© 2022 - 2024 — McMap. All rights reserved.