/
Asyncio  + music Łukasz Asyncio  + music Łukasz

Asyncio + music Łukasz - PowerPoint Presentation

jezebelfox
jezebelfox . @jezebelfox
Follow
342 views
Uploaded On 2020-10-06

Asyncio + music Łukasz - PPT Presentation

Langa Łukasz Langa ambv on GitHub fbme ambv llanga lukaszlangapl todays plan Discover how AsyncIO can be used to process MIDI to drive hardware synthesizers LIVE todays equipment ID: 813168

midi loop circuit asyncio loop midi asyncio circuit callback clock station mono time queue event async def main msg

Share:

Link:

Embed:

Download Presentation from below link

Download The PPT/PDF document "Asyncio + music Łukasz" is the property of its rightful owner. Permission is granted to download and print the materials on this web site for personal, non-commercial use only, and to display it on your personal computer provided you do not modify the materials and that you retain all copyright notices contained in the materials. By downloading content from our website, you accept the terms of this agreement.


Presentation Transcript

Slide1

Asyncio + music

Łukasz

Langa

Slide2

Łukasz Langa

ambv on GitHub

fb.me/ambv@llangalukasz@langa.pl

Slide3

today's plan

Discover how

AsyncIO

can be used to

process MIDI

to drive

hardware synthesizers

LIVE

Slide4

Slide5

today's equipment

Slide6

today's equipment

Slide7

today's equipment

Slide8

Callback 1

Callback 2

Callback N

AsyncIO

primer in 5 minutes

Slide9

AsyncIO

is an event loop

import

asyncio

if __name__ == "__main__":

loop =

asyncio.get_event_loop()

loop.run_forever

()

Slide10

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

Slide11

You schedule things for the loop to call

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()

Slide12

Time consuming callbacks slow down the entire loop:

no concurrency?!

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()

Slide13

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())

Slide14

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())

Slide15

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

)

Slide16

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)

Slide17

Sliced coroutines on the event loop

coro

(

1

)._step()

coro

(

2

)._step()

coro

(

3

)._step()

coro

(

1

)._step()

coro

(

2

)._step()

...

Slide18

MIDI, what's that?

Slide19

MIDI, what's that?

COMPUTER

SYNTHESIZER

KEYBOARD CONTROLLER

USB-MIDI

MIDI

OUT

MIDI

IN

MIDI

IN

MIDI

OUT

Slide20

MIDI: a synchronous unidirectional

point-to-point network protocol

(

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

MIDI

OUT

Slide21

MIDI clock

(

CLOCK

)

(

CLOCK

)

(CLOCK)(CLOCK)(

CLOCK)

(START)(CLOCK)

(

CLOCK

)

(

CLOCK

)

(

STOP

)

(

CLOCK

)

(

CLOCK

)

(

CLOCK

)(CLOCK)

MIDI

IN

MIDI OUT

24

ppqn

(pulses per quarter note)

time

Slide22

MIDI

IN

from Circuit

MIDI

OUT

to Circuit

What we're going to do next

MIDI

OUT

to Mono Station

DRUM MACHINE

ANALOG BASS

CLOCK

Slide23

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

Slide24

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

()

Slide25

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)

Slide26

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

Slide27

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

()

Slide28

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

Slide29

Demo time

MIDI

IN

from Circuit

MIDI

OUT

to Circuit

MIDI

OUT

to Mono Station

DRUM MACHINE

ANALOG BASS

CLOCK