From 794bfeec2ea2002f454e94066939f86df81f7602 Mon Sep 17 00:00:00 2001 From: Dylan Pulver Date: Tue, 1 Sep 2026 18:06:10 +0300 Subject: [PATCH] Do not yield stale frames from blocks() when out= is shorter than the file The last block was sliced as out[:frames + overlap], where frames is the number of frames still unread. That is only the valid length when the iteration already carries overlap frames at the front of the buffer. On the first pass output_offset is 0, so the slice reaches overlap frames past the data actually read, and with out= those frames are whatever the caller left in the array. The valid length is output_offset + toread on every pass. Slicing to it also removes the need for the blocksize > frames + overlap guard, which did not fire when blocksize == frames + overlap and yielded the whole buffer instead. #446 fixed the same arithmetic for the out is None path by allocating min(blocksize, frames); a caller-supplied array cannot be shrunk, so that path kept the defect. --- soundfile.py | 5 +++-- tests/test_soundfile.py | 12 ++++++++++++ 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/soundfile.py b/soundfile.py index 6db0b4b6..045ac6ed 100644 --- a/soundfile.py +++ b/soundfile.py @@ -1205,8 +1205,9 @@ def blocks(self, blocksize: int | None = None, overlap: int = 0, else: overlap_memory[:] = out[-overlap:] - if blocksize > frames + overlap and fill_value is None: - block = out[:frames + overlap] + valid_frames = output_offset + toread + if valid_frames < len(out) and fill_value is None: + block = out[:valid_frames] else: block = out yield np.copy(block) if copy_out else block diff --git a/tests/test_soundfile.py b/tests/test_soundfile.py index 1222721d..49d418e7 100644 --- a/tests/test_soundfile.py +++ b/tests/test_soundfile.py @@ -464,6 +464,18 @@ def test_blocks_inplace_modification(file_stereo_r): assert_equal_list_of_arrays(blocks, expected_blocks) +@pytest.mark.parametrize("blocksize", [len(data_stereo) + 2, len(data_stereo) + 4]) +def test_blocks_with_out_longer_than_file_and_overlap(file_stereo_r, blocksize): + # The file is shorter than out, so there is exactly one block and it must + # not reach past the end of the file. The two blocksizes cover both sides + # of blocksize > frames + overlap. + out = np.full((blocksize, 2), 999.0) + blocks = list(sf.blocks(file_stereo_r, out=out, overlap=2)) + assert len(blocks) == 1 + assert blocks[0].shape == data_stereo.shape + assert np.all(blocks[0] == data_stereo) + + def test_blocks_mono(): blocks = list(sf.blocks(filename_mono, blocksize=3, dtype='int16')) assert_equal_list_of_arrays(blocks, [[0, 1, 2], [-2, -1]])