Download presentation
Presentation is loading. Please wait.
1
Asyncio + music Łukasz Langa
2
Łukasz Langa ambv on GitHub fb.me/ambv @llanga
3
LIVE today's plan Discover how AsyncIO can be used to process MIDI
to drive hardware synthesizers
5
today's equipment standalone music groovebox production-grade
we'll use it as a drum machine
6
today's equipment monophonic analog bass synthesizer
7
today's equipment hardware mixer
8
AsyncIO primer in 5 minutes
Callback 1 Callback 2 … Callback N AsyncIO primer in 5 minutes
9
AsyncIO is an event loop
import asyncio if __name__ == "__main__": loop = asyncio.get_event_loop() loop.run_forever()
10
AsyncIO is literally an event loop
class BaseEventLoop: def run_forever(self): """Run until stop() is called.""" self._check_closed() self._running = True try: while True: try: self._run_once() except _StopError: break finally: self._running = False
11
You schedule things for the loop to call
anything(1) anything(2) anything(3) … time passes ... loop.stop() def callback(i: int) -> None: print(i, datetime.datetime.now()) if __name__ == "__main__": loop = asyncio.get_event_loop() loop.call_later(2, loop.stop) for i in range(1, 4): loop.call_soon(callback, i) try: loop.run_forever() finally: loop.close()
12
Time consuming callbacks slow down the entire loop: no concurrency?!
anything(1) anything(2) anything(3) … time passes ... loop.stop() def callback(i: int) -> None: print(i, datetime.datetime.now()) time.sleep(i) # time consuming action if __name__ == "__main__": loop = asyncio.get_event_loop() loop.call_later(2, loop.stop) for i in range(1, 4): loop.call_soon(callback, i) try: loop.run_forever() finally: loop.close()
13
Coroutines to the rescue
async def callback(i: int) -> None: print(i, datetime.datetime.now()) await asyncio.sleep(i) async def main() -> None: await callback(1) await callback(2) await callback(3) if __name__ == "__main__": asyncio.run(main())
14
Run many coroutines concurrently by gathering them
async def callback(i: int) -> None: print(i, datetime.datetime.now()) await asyncio.sleep(i) async def main() -> None: await asyncio.gather( callback(1), callback(2), callback(3), ) if __name__ == "__main__": asyncio.run(main())
15
Coroutines still use the event loop
class Task(futures.Future): def __init__(self, coro, loop=None): super().__init__(loop=loop) self._loop.call_soon(self._step)
16
Coroutines still use the event loop
class Task(futures.Future): def _step(self): try: result = next(self._coro) except StopIteration as exc: self.set_result(exc.value) except BaseException as exc: self.set_exception(exc) raise else: self._loop.call_soon(self._step)
17
Sliced coroutines on the event loop
coro(1)._step() coro(2)._step() coro(3)._step() ... Sliced coroutines on the event loop
18
MIDI, what's that?
19
MIDI, what's that? USB-MIDI COMPUTER SYNTHESIZER KEYBOARD CONTROLLER
MIDI IN MIDI OUT USB-MIDI COMPUTER SYNTHESIZER MIDI OUT MIDI IN KEYBOARD CONTROLLER
20
MIDI: a synchronous unidirectional point-to-point network protocol
MIDI OUT (NOTE_ON on CHANNEL 10, C-1, velocity 127) (NOTE_ON on CHANNEL 2, E-2, velocity 80) ... (NOTE_OFF on CHANNEL 10, C-1, velocity 0) (NOTE_OFF on CHANNEL 2, E-2, velocity 0) (NOTE_ON on CHANNEL 10, F-1, velocity 80) (NOTE_OFF on CHANNEL 10, F-1, velocity 80) time MIDI IN
21
MIDI clock time (CLOCK) (START) (STOP)
MIDI OUT (CLOCK) (START) (STOP) 24 ppqn (pulses per quarter note) time MIDI IN
22
What we're going to do next
MIDI OUT to Circuit MIDI OUT to Mono Station the main function fits on a single slide MIDI IN from Circuit CLOCK DRUM MACHINE ANALOG BASS
23
async def async_main() -> None:
from_circuit, to_circuit = get_ports("Circuit", clock_source=True) from_mono_station, to_mono_station = get_ports("Circuit Mono Station")
24
async def async_main() -> None:
from_circuit, to_circuit = get_ports("Circuit", clock_source=True) from_mono_station, to_mono_station = get_ports("Circuit Mono Station") queue: asyncio.Queue[MidiMessage] = asyncio.Queue(maxsize=256) loop = asyncio.get_event_loop()
25
async def async_main() -> None:
from_circuit, to_circuit = get_ports("Circuit", clock_source=True) from_mono_station, to_mono_station = get_ports("Circuit Mono Station") queue: asyncio.Queue[MidiMessage] = asyncio.Queue(maxsize=256) loop = asyncio.get_event_loop() def midi_callback(msg, data=None): sent_time = time.time() midi_message, event_delta = msg loop.call_soon_threadsafe( queue.put_nowait, (midi_message, event_delta, sent_time) ) from_circuit.set_callback(midi_callback)
26
async def async_main() -> None:
from_circuit, to_circuit = get_ports("Circuit", clock_source=True) from_mono_station, to_mono_station = get_ports("Circuit Mono Station") queue: asyncio.Queue[MidiMessage] = asyncio.Queue(maxsize=256) loop = asyncio.get_event_loop() def midi_callback(msg, data=None): sent_time = time.time() midi_message, event_delta = msg loop.call_soon_threadsafe( queue.put_nowait, (midi_message, event_delta, sent_time) ) from_circuit.set_callback(midi_callback) from_mono_station.close_port() # we won't be using that one now
27
async def async_main() -> None:
from_circuit, to_circuit = get_ports("Circuit", clock_source=True) from_mono_station, to_mono_station = get_ports("Circuit Mono Station") queue: asyncio.Queue[MidiMessage] = asyncio.Queue(maxsize=256) loop = asyncio.get_event_loop() def midi_callback(msg, data=None): sent_time = time.time() midi_message, event_delta = msg loop.call_soon_threadsafe( queue.put_nowait, (midi_message, event_delta, sent_time) ) from_circuit.set_callback(midi_callback) from_mono_station.close_port() # we won't be using that one now performance = Performance(drums=to_circuit, bass=to_mono_station) try: await midi_consumer(queue, performance) except asyncio.CancelledError: from_circuit.cancel_callback()
28
async def midi_consumer(
queue: asyncio.Queue[MidiMessage], performance: Performance ) -> None: drums: Optional[asyncio.Task] = None last_msg: MidiPacket = [0] while True: msg, delta, sent_time = await queue.get() if msg[0] == CLOCK: if last_msg[0] == CLOCK: performance.pulse_delta = delta elif msg[0] == START: if drums is None: drums = asyncio.create_task(drum_machine(performance)) elif msg[0] == STOP: if drums is not None: drums.cancel() drums = None last_msg = msg
29
Demo time MIDI OUT to Circuit MIDI OUT to Mono Station CLOCK
MIDI IN from Circuit CLOCK DRUM MACHINE ANALOG BASS
Similar presentations
© 2025 SlidePlayer.com Inc.
All rights reserved.