from typing import Callable, Union, Optional
from functools import partial
from stream2py import SourceReader, StreamBuffer, BufferReader
from stream2py.sources.audio import PyAudioSourceReader
from taped.util import bytes_to_waveform
DFLT_FRM_PER_BUFFER = 2048
[docs]class VisualizationStream(SourceReader):
def __init__(self, mk_int16_array_gen: Callable, chk_to_viz: Callable):
self.mk_int16_array_gen = mk_int16_array_gen
self.chk_to_viz = chk_to_viz
self._viz_gen = None
def _mk_viz_gen(self):
for i, a in enumerate(self.mk_int16_array_gen()):
yield i, self.chk_to_viz(a)
[docs] def open(self):
"""Setup data generator"""
self._viz_gen = self._mk_viz_gen()
[docs] def read(self):
return next(self._viz_gen)
[docs] def close(self):
"""Clean up if needed"""
del self._viz_gen
self._viz_gen = None
@property
def info(self):
"""Whatever info is useful to you
StreamBuffer will record info right after open"""
return dict(mk_int16_array_gen=str(self.mk_int16_array_gen), chk_to_viz=str(self.chk_to_viz))
[docs] def key(self, data):
"""Convert data to a sortable value that increases with each read.
the enumerate index in this case
"""
return data[0]
def device_info_by_index(index):
try:
return next(d for d in PyAudioSourceReader.list_device_info() if d['index'] == index)
except StopIteration:
raise ValueError(f"Not found for input device index: {index}")
def mk_pyaudio_to_int16_array_gen_callable(audio_reader: BufferReader):
_info = audio_reader.source_reader_info
specific_bytes_to_wf = partial(bytes_to_waveform,
sr=_info['rate'],
n_channels=_info['channels'],
sample_width=_info['width'])
def mk_pyaudio_to_int16_array_gen():
for timestamp, wf_bytes, frame_count, time_info, status_flags in audio_reader:
yield specific_bytes_to_wf(wf_bytes)
return mk_pyaudio_to_int16_array_gen
[docs]class AudioStreamBuffer(StreamBuffer):
def __init__(self,
*,
buffer_size_seconds: Union[int, float] = 60.0,
input_device_index=None,
sr=44100,
width=2,
frames_per_buffer=DFLT_FRM_PER_BUFFER,
sleep_time_on_read_none_s: Optional[Union[int, float]] = 0.05,
auto_drop=True
):
_info = device_info_by_index(input_device_index)
sr = sr or int(_info['defaultSampleRate'])
channels = _info['maxInputChannels']
super().__init__(
source_reader=PyAudioSourceReader(input_device_index=input_device_index,
rate=sr,
width=width,
channels=channels,
frames_per_buffer=frames_per_buffer,
unsigned=False,
),
maxlen=PyAudioSourceReader.audio_buffer_size_seconds_to_maxlen(
buffer_size_seconds=buffer_size_seconds,
rate=sr,
frames_per_buffer=frames_per_buffer,
),
sleep_time_on_read_none_s=sleep_time_on_read_none_s,
auto_drop=auto_drop
)
# TODO: Reflect: Class versus function?
# def pyaudio_source_reader(
# buffer_size_seconds: Union[int, float] = 60.0,
# input_device_index: Optional[int] = None,
# sr=44100,
# width=2,
# frames_per_buffer=2048,
# sleep_time_on_read_none_s: Optional[Union[int, float]] = 0.05,
# auto_drop=True
# ):
# _info = device_info_by_index(input_device_index)
# sr = sr or int(_info['defaultSampleRate'])
# channels = _info['maxInputChannels']
#
# return StreamBuffer(
# source_reader=PyAudioSourceReader(input_device_index=input_device_index,
# rate=sr,
# width=width,
# channels=channels,
# frames_per_buffer=frames_per_buffer,
# ),
# maxlen=PyAudioSourceReader.audio_buffer_size_seconds_to_maxlen(
# buffer_size_seconds=buffer_size_seconds,
# rate=sr,
# frames_per_buffer=frames_per_buffer,
# ),
# sleep_time_on_read_none_s=sleep_time_on_read_none_s,
# auto_drop=auto_drop
# )
def launch_audio_tracking(chk_callback: Callable,
input_device_index: int,
buffer_size_seconds=60,
sr=None,
width=2,
frames_per_buffer=DFLT_FRM_PER_BUFFER,
):
try:
with AudioStreamBuffer(
buffer_size_seconds=buffer_size_seconds,
input_device_index=input_device_index,
sr=sr,
width=width,
frames_per_buffer=frames_per_buffer,
auto_drop=False,
sleep_time_on_read_none_s=0.1
) as audio_buffer:
audio_reader = audio_buffer.mk_reader()
with StreamBuffer(
source_reader=VisualizationStream(
mk_int16_array_gen=mk_pyaudio_to_int16_array_gen_callable(audio_reader),
chk_to_viz=chk_callback
),
maxlen=10
) as viz_buffer:
viz_reader = viz_buffer.mk_reader()
yield from viz_reader
except KeyboardInterrupt:
print("KeyboardInterrupt: So I'm stopping the process")