/examples/asyncio_generators.py

https://github.com/spatialaudio/python-sounddevice · Python · 108 lines · 65 code · 18 blank · 25 comment · 17 complexity · 48df930af50d43e28cd3e82ec2bdd7ed MD5 · raw file

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