Skip to content

Commit 53e0013

Browse files
committed
DOC: Add asyncio examples
1 parent 24c045a commit 53e0013

File tree

3 files changed

+191
-0
lines changed

3 files changed

+191
-0
lines changed

doc/examples.rst

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,3 +42,17 @@ Recording with Arbitrary Duration
4242
:download:`rec_unlimited.py <../examples/rec_unlimited.py>`
4343

4444
.. literalinclude:: ../examples/rec_unlimited.py
45+
46+
Using a stream in an `asyncio` coroutine
47+
----------------------------------------
48+
49+
:download:`asyncio_coroutines.py <../examples/asyncio_coroutines.py>`
50+
51+
.. literalinclude:: ../examples/asyncio_coroutines.py
52+
53+
Creating an `asyncio` generator for audio blocks
54+
------------------------------------------------
55+
56+
:download:`asyncio_generators.py <../examples/asyncio_generators.py>`
57+
58+
.. literalinclude:: ../examples/asyncio_generators.py

examples/asyncio_coroutines.py

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
#!/usr/bin/env python3
2+
"""An example for using a stream in an asyncio coroutine.
3+
4+
This example shows how to create a stream in a coroutine and how to wait for
5+
the completion of the stream.
6+
7+
You need Python 3.7 or newer to run this.
8+
9+
"""
10+
import asyncio
11+
12+
import numpy as np
13+
import sounddevice as sd
14+
15+
16+
async def record_buffer(buffer, **kwargs):
17+
loop = asyncio.get_event_loop()
18+
event = asyncio.Event()
19+
idx = 0
20+
21+
def callback(indata, frame_count, time_info, status):
22+
nonlocal idx
23+
if status:
24+
print(status)
25+
remainder = len(buffer) - idx
26+
if remainder == 0:
27+
loop.call_soon_threadsafe(event.set)
28+
raise sd.CallbackStop
29+
indata = indata[:remainder]
30+
buffer[idx:idx + len(indata)] = indata
31+
idx += len(indata)
32+
33+
stream = sd.InputStream(callback=callback, dtype=buffer.dtype,
34+
channels=buffer.shape[1], **kwargs)
35+
with stream:
36+
await event.wait()
37+
38+
39+
async def play_buffer(buffer, **kwargs):
40+
loop = asyncio.get_event_loop()
41+
event = asyncio.Event()
42+
idx = 0
43+
44+
def callback(outdata, frame_count, time_info, status):
45+
nonlocal idx
46+
if status:
47+
print(status)
48+
remainder = len(buffer) - idx
49+
if remainder == 0:
50+
loop.call_soon_threadsafe(event.set)
51+
raise sd.CallbackStop
52+
valid_frames = frame_count if remainder >= frame_count else remainder
53+
outdata[:valid_frames] = buffer[idx:idx + valid_frames]
54+
outdata[valid_frames:] = 0
55+
idx += valid_frames
56+
57+
stream = sd.OutputStream(callback=callback, dtype=buffer.dtype,
58+
channels=buffer.shape[1], **kwargs)
59+
with stream:
60+
await event.wait()
61+
62+
63+
async def main(frames=150_000, channels=1, dtype='float32', **kwargs):
64+
buffer = np.empty((frames, channels), dtype=dtype)
65+
print('recording buffer ...')
66+
await record_buffer(buffer, **kwargs)
67+
print('playing buffer ...')
68+
await play_buffer(buffer, **kwargs)
69+
print('done')
70+
71+
72+
if __name__ == "__main__":
73+
asyncio.run(main())

examples/asyncio_generators.py

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
#!/usr/bin/env python3
2+
"""Creating an asyncio generator for blocks of audio data.
3+
4+
This example shows how a generator can be used to analyze audio input blocks.
5+
In addition, it shows how a generator can be created that yields not only input
6+
blocks but also output blocks where audio data can be written to.
7+
8+
You need Python 3.7 or newer to run this.
9+
10+
"""
11+
import asyncio
12+
import queue
13+
14+
import numpy as np
15+
import sounddevice as sd
16+
17+
18+
async def inputstream_generator(channels=1, **kwargs):
19+
"""Generator that yields blocks of input data as NumPy arrays."""
20+
q_in = asyncio.Queue()
21+
loop = asyncio.get_event_loop()
22+
23+
def callback(indata, frame_count, time_info, status):
24+
loop.call_soon_threadsafe(q_in.put_nowait, (indata.copy(), status))
25+
26+
stream = sd.InputStream(callback=callback, channels=channels, **kwargs)
27+
with stream:
28+
while True:
29+
indata, status = await q_in.get()
30+
yield indata, status
31+
32+
33+
async def stream_generator(blocksize, *, channels=1, dtype='float32',
34+
pre_fill_blocks=10, **kwargs):
35+
"""Generator that yields blocks of input/output data as NumPy arrays.
36+
37+
The output blocks are uninitialized and have to be filled with
38+
appropriate audio signals.
39+
40+
"""
41+
assert blocksize != 0
42+
q_in = asyncio.Queue()
43+
q_out = queue.Queue()
44+
loop = asyncio.get_event_loop()
45+
46+
def callback(indata, outdata, frame_count, time_info, status):
47+
loop.call_soon_threadsafe(q_in.put_nowait, (indata.copy(), status))
48+
outdata[:] = q_out.get_nowait()
49+
50+
# pre-fill output queue
51+
for _ in range(pre_fill_blocks):
52+
q_out.put(np.zeros((blocksize, channels), dtype=dtype))
53+
54+
stream = sd.Stream(blocksize=blocksize, callback=callback, dtype=dtype,
55+
channels=channels, **kwargs)
56+
with stream:
57+
while True:
58+
indata, status = await q_in.get()
59+
outdata = np.empty((blocksize, channels), dtype=dtype)
60+
yield indata, outdata, status
61+
q_out.put_nowait(outdata)
62+
63+
64+
async def print_input_infos(**kwargs):
65+
"""Show minimum and maximum value of each incoming audio block."""
66+
async for indata, status in inputstream_generator(**kwargs):
67+
if status:
68+
print(status)
69+
print('min:', indata.min(), '\t', 'max:', indata.max())
70+
71+
72+
async def wire_coro(**kwargs):
73+
"""Create a connection between audio inputs and outputs.
74+
75+
Asynchronously iterates over a stream generator and for each block
76+
simply copies the input data into the output block.
77+
78+
"""
79+
async for indata, outdata, status in stream_generator(**kwargs):
80+
if status:
81+
print(status)
82+
outdata[:] = indata
83+
84+
85+
async def main(**kwargs):
86+
print('Some informations about the input signal:')
87+
try:
88+
await asyncio.wait_for(print_input_infos(), timeout=2)
89+
except asyncio.TimeoutError:
90+
pass
91+
print('\nEnough of that, activating wire ...\n')
92+
audio_task = asyncio.create_task(wire_coro(**kwargs))
93+
for i in range(10, 0, -1):
94+
print(i)
95+
await asyncio.sleep(1)
96+
audio_task.cancel()
97+
try:
98+
await audio_task
99+
except asyncio.CancelledError:
100+
print('wire was cancelled')
101+
102+
103+
if __name__ == "__main__":
104+
asyncio.run(main(blocksize=1024))

0 commit comments

Comments
 (0)