Live audio¶
kudio.core.stream
¶
Audio streaming: playback, local recording, and remote (TCP) streams.
PyAudio is an optional dependency (pip install kudio[audio]); it is only
imported when a stream/recorder is actually created, so the package can be
used for offline processing without any audio hardware.
Classes:
| Name | Description |
|---|---|
Recorder |
Blocking recorder for a fixed duration. |
RemoteStreamReader |
Receive an audio stream over TCP and feed it into an |
LocalStreamReader |
Stream from a local microphone into an |
StreamRecorder |
Record for as long as you like, and watch the level while it happens. |
Functions:
| Name | Description |
|---|---|
record |
Blocking record for seconds via sounddevice (modern backend). |
wave_decode |
Raw int16 bytes -> array of shape |
wave_encode |
Multi-channel array -> interleaved int16 bytes. |
play_audio |
Play audio from a file path, an opened |
Attributes:
| Name | Type | Description |
|---|---|---|
PA_FLOAT32 |
PortAudio's code for 32-bit float samples ( |
|
PA_INT16 |
PortAudio's code for 16-bit integer samples ( |
PA_FLOAT32
module-attribute
¶
PortAudio's code for 32-bit float samples (pyaudio.paFloat32), for
LocalStreamReader(wav_format=...).
PA_INT16
module-attribute
¶
PortAudio's code for 16-bit integer samples (pyaudio.paInt16), the
default wav_format.
Recorder
¶
Recorder(
device: Optional[int] = None,
record_times: int = 3,
rate: int = 16000,
channels: int = 1,
frames_per_buffer: int = 512,
)
Blocking recorder for a fixed duration.
Examples¶
>>> with Recorder(device=11, record_times=3, channels=6, rate=16000) as r:
... wave_data = r.start_record()
... r.save('kudio_test.wav')
Methods:
| Name | Description |
|---|---|
start_record |
Record for |
save |
Write the last recording to file_dir (16-bit PCM). |
Source code in kudio/core/stream.py
start_record
¶
Record for record_times seconds; returns the waveform, with one
column per channel (squeezed for mono).
Source code in kudio/core/stream.py
save
¶
Write the last recording to file_dir (16-bit PCM).
Source code in kudio/core/stream.py
RemoteStreamReader
¶
RemoteStreamReader(buffer: Optional[AudioBuffer])
Bases: Thread
Receive an audio stream over TCP and feed it into an
AudioBuffer (optionally playing it back locally).
Methods:
| Name | Description |
|---|---|
sock_send |
Send a text command; returns decoded waveform when the reply is |
Source code in kudio/core/stream.py
sock_send
¶
Send a text command; returns decoded waveform when the reply is binary audio data.
Source code in kudio/core/stream.py
LocalStreamReader
¶
LocalStreamReader(
device: Optional[int] = None,
rate: Optional[int] = None,
channels: int = 1,
frame_size: int = 2048,
wav_format: int = PA_INT16,
buffer: Optional[AudioBuffer] = None,
on_frame: Optional[Callable[[ndarray], None]] = None,
)
Stream from a local microphone into an AudioBuffer, with
optional live monitoring through the default output device.
>>> lsr = LocalStreamReader(channels=1, frame_size=2048, wav_format=PA_INT16)
>>> if lsr.get_status():
... lsr.play_audio() # optional monitoring
... lsr.stream_start()
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
device
|
Optional[int]
|
PyAudio input index. Omitted, the last device
that opens as an input ( |
None
|
rate
|
Optional[int]
|
sample rate, a request. Omitted or refused, the first of
16000, 22050 and 44100 the device accepts, else its own default;
|
None
|
channels
|
int
|
channel count, default 1 |
1
|
frame_size
|
int
|
frames per buffer, default 2048 |
2048
|
wav_format
|
int
|
|
PA_INT16
|
buffer
|
Optional[AudioBuffer]
|
target AudioBuffer (optional) |
None
|
on_frame
|
Optional[Callable[[ndarray], None]]
|
called with each captured chunk. An
|
None
|
Methods:
| Name | Description |
|---|---|
play_audio |
Open a playback stream that echoes the microphone input. |
Source code in kudio/core/stream.py
play_audio
¶
Open a playback stream that echoes the microphone input.
Source code in kudio/core/stream.py
StreamRecorder
¶
StreamRecorder(
device: Optional[int] = None,
sr: Optional[int] = None,
channels: int = 1,
frame_size: int = 1024,
max_seconds: Optional[float] = 3600.0,
monitor: bool = False,
)
Record for as long as you like, and watch the level while it happens.
kudio.record is one blocking call: you commit to a duration up
front and nothing is observable until it returns. That is fine for a script
and useless for a person at a microphone, who needs to see the level before
committing and stop when the sentence ends.
>>> rec = kudio.StreamRecorder(sr=16000)
>>> rec.start()
>>> rec.level_db()
-23.4
>>> y = rec.stop() # everything captured, float32
Capture runs on PortAudio's own thread; level, tail and
elapsed are safe to poll from a UI timer. Nothing is dropped —
unlike AudioBuffer, which is a monitor and discards when full.
Requires kudio[audio] (PyAudio).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
device
|
Optional[int]
|
PyAudio input index ( |
None
|
sr
|
Optional[int]
|
sample rate. Omitted, the first of 16000, 22050 and 44100 the device accepts, and its own default only when none of them opens. |
None
|
max_seconds
|
Optional[float]
|
hard cap on the take, so a recorder left running
cannot fill memory. |
3600.0
|
monitor
|
bool
|
echo the input to the default output while recording. |
False
|
Methods:
| Name | Description |
|---|---|
start |
Open the stream and begin capturing. Idempotent. |
stop |
Close the stream and return everything captured. |
take |
Everything captured so far, without stopping. |
level |
Peak of the most recent chunk, linear |
level_db |
The same reading in dBFS; |
elapsed |
Seconds captured so far. |
tail |
The most recent seconds of audio, for a rolling display. |
drain |
Audio captured since the last call, and never twice. |
reset |
Throw the take away and start counting again, stream untouched. |
Attributes:
| Name | Type | Description |
|---|---|---|
sr |
int
|
The rate actually negotiated with the device. |
capped |
bool
|
True once |
Source code in kudio/core/stream.py
start
¶
Open the stream and begin capturing. Idempotent.
Source code in kudio/core/stream.py
stop
¶
Close the stream and return everything captured.
Mono comes back shaped (n,) and multi-channel (n, channels) —
the layout kudio.file_load and kudio.save_wave use.
Calling this twice returns the same take rather than an empty array.
Source code in kudio/core/stream.py
take
¶
Everything captured so far, without stopping.
Source code in kudio/core/stream.py
level
¶
level_db
¶
elapsed
¶
tail
¶
The most recent seconds of audio, for a rolling display.
Cheap enough to call from a repeating timer: it copies only the window asked for, not the whole take.
Source code in kudio/core/stream.py
drain
¶
Audio captured since the last call, and never twice.
tail returns a sliding window, which overlaps between calls —
right for a display, wrong for anything stateful. Feeding overlapping
audio to a streaming denoiser processes the same samples repeatedly and
corrupts every recursive estimate it holds.
Source code in kudio/core/stream.py
reset
¶
Throw the take away and start counting again, stream untouched.
record
¶
record(
seconds: float,
sr: int = 16000,
channels: int = 1,
device: Optional[int] = None,
) -> ndarray
Blocking record for seconds via sounddevice (modern backend).
Returns float32 samples, shape (n,) for mono else (n, channels).
Requires kudio[audio].
device is a sounddevice index — get one from
kudio.list_devices, not from kudio.CheckDevice, which
numbers devices through PyAudio for the streaming classes.
Source code in kudio/core/stream.py
wave_decode
¶
Raw int16 bytes -> array of shape (chunk_length, channels).
Source code in kudio/core/stream.py
wave_encode
¶
play_audio
¶
play_audio(
wav: Union[str, Path, Wave_read, ndarray],
rate: Optional[int] = None,
channels: int = 1,
) -> Optional[str]
Play audio from a file path, an opened wave.Wave_read, or an ndarray.
ndarray input must be int16 or float32; rate is required in that case,
and a (frames, channels) array plays on as many channels. A path is
read with soundfile, so any format kudio.file_load reads plays.
Returns an error message string when the audio cannot be played, else
None.
Raises:
| Type | Description |
|---|---|
DependencyError
|
when neither sounddevice nor PyAudio is there. |
Source code in kudio/core/stream.py
kudio.core.buffer
¶
Thread-safe frame buffers shared between audio producers and consumers.
Classes:
| Name | Description |
|---|---|
AudioBuffer |
Bounded FIFO of audio frames shared between producer/consumer threads. |
FetchBuffer |
Collect a fixed number of frames from an |
AudioBuffer
¶
Bounded FIFO of audio frames shared between producer/consumer threads.
A stream reader calls add for every incoming frame; consumers either
poll frames with get (e.g. for real-time plotting) or collect a fixed
number of consecutive frames with get_fixed_size_data (e.g. timed
recording). All methods are thread-safe.
Methods:
| Name | Description |
|---|---|
add |
Append one frame. |
get |
Pop the oldest frame, blocking until one is available. |
get_fixed_size_data |
Block until queue_size frames have arrived, then return them |
clear |
Drop all buffered frames. Returns True if anything was dropped. |
Source code in kudio/core/buffer.py
add
¶
Append one frame.
With drop_full=True (default) the oldest frame is dropped when the
buffer is full; otherwise the call blocks until a slot is free.
Source code in kudio/core/buffer.py
get
¶
Pop the oldest frame, blocking until one is available.
Raises TimeoutError if timeout (seconds) elapses first.
Source code in kudio/core/buffer.py
get_fixed_size_data
¶
Block until queue_size frames have arrived, then return them
concatenated with np.hstack.
Raises ValueError when queue_size is below 1, because that wait
would never end, and TypeError when it is not a whole number. A
timeout that expires raises TimeoutError and ends the capture,
so nothing is left waiting on the buffer to take the next one's
frames. One capture runs at a time: a second one started while the
first waits raises RuntimeError, since the two would share one
set of frames.
Source code in kudio/core/buffer.py
clear
¶
Drop all buffered frames. Returns True if anything was dropped.
Source code in kudio/core/buffer.py
FetchBuffer
¶
FetchBuffer(buffer: AudioBuffer)
Bases: Thread
Collect a fixed number of frames from an AudioBuffer in a
background thread.
One instance runs one capture, because a thread cannot be started twice.
Methods:
| Name | Description |
|---|---|
get_data |
Run the capture and return the frames. |
Source code in kudio/core/buffer.py
get_data
¶
Run the capture and return the frames.
Always returns an array; a timeout that expires raises
TimeoutError instead, so the caller can tell a stream that stopped
delivering from a capture that came back short.
Source code in kudio/core/buffer.py
kudio.util.check
¶
Audio device discovery and validation (requires PyAudio).
Classes:
| Name | Description |
|---|---|
CheckDevice |
Query/validate PortAudio devices. |
Functions:
| Name | Description |
|---|---|
list_devices |
Devices as sounddevice sees them. |
is_available |
Smoke-test the terminal colors and the audio input stack. |
check_device |
List system devices and probe an input; True on success. |
CheckDevice
¶
Query/validate PortAudio devices.
>>> with CheckDevice() as cd:
... devices = cd.system_devices()
... device, rate, channels = cd.input_device()
Methods:
| Name | Description |
|---|---|
system_devices |
Return |
check_available_rate |
Return the standard sample rates the device accepts, or False. |
input_device |
Resolve and validate an input device; returns (device, rate, channels). |
Source code in kudio/util/check.py
system_devices
¶
Return [{'Device', 'index', 'fs', 'out', 'in'}, ...] for host API 0.
Source code in kudio/util/check.py
check_available_rate
¶
Return the standard sample rates the device accepts, or False.
Source code in kudio/util/check.py
input_device
¶
input_device(
device: Optional[int] = None,
rate: Optional[int] = None,
channels: Optional[int] = None,
) -> Tuple[int, int, int]
Resolve and validate an input device; returns (device, rate, channels).
Missing arguments are auto-detected (last input-capable device, first working rate of 16000/22050/44100, mono).
Raises:
| Type | Description |
|---|---|
DeviceError
|
when there is no input, or the device, rate or channel count will not open. |
Source code in kudio/util/check.py
list_devices
¶
Devices as sounddevice sees them.
This is the index space kudio.record accepts. It is not the same
numbering as CheckDevice, which enumerates through PyAudio for the
streaming/recorder classes — PortAudio's two Python bindings index devices
independently, so an index from one must never be passed to the other.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
kind
|
Optional[str]
|
|
None
|
Returns:
| Type | Description |
|---|---|
List[dict]
|
Source code in kudio/util/check.py
is_available
¶
check_device
¶
List system devices and probe an input; True on success.
The input probed is the one CheckDevice.input_device picks with no
arguments: the last input-capable device PyAudio lists, at the first of
16000, 22050 and 44100 Hz it opens at.