Repository navigation
Conversation
`LocalAudioPlayer.play()` and `play_stream()` reshape int16 input to `(-1, channels)` but left float32 input untouched. A 1-D float32 array (e.g. a mono waveform from soundfile/librosa, which is a valid input per the `play()` type signature) then broke: - `play()` raised `IndexError: tuple index out of range` from `channels=audio_content.shape[1]` when building the output stream. - `play_stream()` raised a broadcasting error in the callback when assigning a 1-D slice into the 2-D `outdata` buffer. Reshape float32 input to `(-1, channels)` in both paths, mirroring the existing int16 handling. Already-2-D input is unaffected (no-op reshape).
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 7603230061
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| audio_content = (input.astype(np.float32) / 32767.0).reshape(-1, self.channels) | ||
| elif input.dtype == np.float32: | ||
| audio_content = cast("npt.NDArray[np.float32]", input) | ||
| audio_content = cast("npt.NDArray[np.float32]", input).reshape(-1, self.channels) |
There was a problem hiding this comment.
Preserve multi-channel float32 playback
When play() receives a 2-D float32 array such as (frames, 2) stereo audio, self.channels is hardcoded to 1, so this reshape flattens interleaved left/right samples into a single channel with twice as many frames. The subsequent OutputStream therefore plays corrupted mono audio instead of the stereo stream supported by the previous audio_content.shape[1] behavior. Reshape only 1-D input, while preserving the channel dimension of existing 2-D arrays.
AGENTS.md reference: AGENTS.md:L5-L8
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Fixed in 472919a. The float32 reshape now only applies to 1-D input, so a 2-D array keeps its channel dimension and play() still opens the stream with shape[1] channels. The same guard is in play_stream. Added test_play_preserves_two_dimensional_float32_channels, which fails without the guard.
|
AI-assisted offline comparison at head Four
The one-dimensional fix is confirmed in this harness. The third case may deserve an explicit test or documented contract: the new reshape also flattens already two-dimensional float32 input when its channel dimension differs from Minimal array for the third case: player = LocalAudioPlayer() # channels defaults to 1
samples = np.array([[1, 10], [2, 20]], dtype=np.float32)
await player.play(samples)Values are fictional markers, not recorded audio. Setting |
A 2-D float32 array already carries its channel dimension, which play() uses to open the output stream. Reshaping it to self.channels flattened stereo input into mono.
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
Changes being requested
LocalAudioPlayer(insrc/openai/helpers/local_audio_player.py) mishandles 1-Dfloat32NumPy arrays.Both
play()andplay_stream()reshapeint16input to(-1, channels), but passfloat32input through without reshaping. A 1-Dfloat32array — e.g. a mono waveform loaded viasoundfile/librosa, and an explicitly declared input type ofplay()(npt.NDArray[np.float32]) — then breaks:play()raisesIndexError: tuple index out of range, because the still-1-D array is used aschannels=audio_content.shape[1]when constructing thesounddevice.OutputStream.play_stream()raises a broadcasting error inside the audio callback when assigning a 1-D slice into the 2-Doutdatabuffer.int16input already works because it is reshaped; only thefloat32branch was missing the reshape.Repro (
play())Fix
Reshape
float32input to(-1, self.channels)in both code paths, mirroring the existingint16handling. Already-2-D input (e.g. the(-1, 1)buffer produced from a streamed TTS response) is unaffected — the reshape is a no-op.if current_buffer.dtype == np.int16 and self.dtype == np.float32: current_buffer = (current_buffer.astype(np.float32) / 32767.0).reshape(-1, self.channels) + elif current_buffer.dtype == np.float32: + current_buffer = current_buffer.reshape(-1, self.channels)Why this is a hand-maintained file (not generated)
src/openai/helpers/local_audio_player.pyhas noFile generated from our OpenAPI spec by Stainlessheader and is not part of the OpenAPI surface — it is one of the hand-written helpers underhelpers/. The change is a self-contained correctness fix in that helper; it touches no generated code.Tests
Added
tests/lib/test_local_audio_player.pywith a lightweight, hardware-free fake forsounddevice.OutputStreamthat drives the real callback flow from a background thread:test_play_accepts_one_dimensional_float32— reproduces theIndexError(fails before the fix) and asserts the played frames match the input.test_play_accepts_one_dimensional_int16— regression guard for the existingint16path.test_play_stream_accepts_one_dimensional_float32— covers the streaming path.Each test fails on
mainfor the exact reason described above and passes with the fix. No network, no API key, no audio device.Validation (run locally)
pytest tests/lib/test_local_audio_player.py— 3 passed (and confirmed red onmain:IndexErrorforplay, broadcast/hang forplay_stream).pytest tests/lib/— 259 passed.ruff format --check/ruff check .— clean.scripts/run-pyright -p pyproject.toml— 0 errors.mypy .— Success: no issues.Additional context & links
int16and already-2-Dfloat32behavior is unchanged.local_audio_player.pycarries# mypy: ignore-errors, so mypy does not type-check it; the change is pyright-clean.