diff --git a/soundfile.py b/soundfile.py index 5b4bb85..52ed691 100644 --- a/soundfile.py +++ b/soundfile.py @@ -211,10 +211,10 @@ def read(file, frames=-1, start=0, stop=None, dtype='float64', always_2d=False, array anyway. If `out` was specified, it is returned. If `out` has more - frames than available in the file (or if `frames` is smaller - than the length of `out`) and no `fill_value` is given, then - only a part of `out` is overwritten and a view containing all - valid frames is returned. + frames than available in the file and no `fill_value` is given, + or if `frames` is smaller than the length of `out`, then only a + part of `out` is overwritten and a view containing all valid + frames is returned. samplerate : int The sample rate of the audio file. @@ -806,12 +806,11 @@ def read(self, frames=-1, dtype='float64', always_2d=False, one-dimensional array is returned. Use ``always_2d=True`` to return a two-dimensional array anyway. - If `out` was specified, it is returned. If `out` has more - frames than available in the file (or if `frames` is - smaller than the length of `out`) and no `fill_value` is - given, then only a part of `out` is overwritten and a view - containing all valid frames is returned. numpy.ndarray or - type(out) + If `out` was specified, it is returned. If `out` has more + frames than available in the file and no `fill_value` is + given, or if `frames` is smaller than the length of `out`, + then only a part of `out` is overwritten and a view + containing all valid frames is returned. Other Parameters ---------------- @@ -850,18 +849,25 @@ def read(self, frames=-1, dtype='float64', always_2d=False, buffer_read, .write """ + explicit_frames = frames >= 0 + if out is not None and (frames < 0 or frames > len(out)): + frames = len(out) + max_frames = self._check_frames(frames, fill_value) if out is None: - frames = self._check_frames(frames, fill_value) - out = self._create_empty_array(frames, always_2d, dtype) - else: - if frames < 0 or frames > len(out): - frames = len(out) - frames = self._array_io('read', out, frames) - if len(out) > frames: + out = self._create_empty_array(max_frames, always_2d, dtype) + read_frames = self._array_io('read', out, max_frames) + if read_frames < max_frames: if fill_value is None: - out = out[:frames] + # NB: This can only happen in non-seekable files + assert not self.seekable() + out = out[:read_frames] + else: + out[read_frames:max_frames] = fill_value + if max_frames < len(out): + if explicit_frames or fill_value is None: + out = out[:max_frames] else: - out[frames:] = fill_value + out[max_frames:] = fill_value return out def buffer_read(self, frames=-1, dtype=None): @@ -1332,13 +1338,16 @@ def _prepare_read(self, start, stop, frames): if frames >= 0 and stop is not None: raise TypeError("Only one of {frames, stop} may be used") + explicit_stop = stop is not None start, stop, _ = slice(start, stop).indices(self.frames) if stop < start: stop = start - if frames < 0: + if explicit_stop: frames = stop - start if self.seekable(): self.seek(start, SEEK_SET) + if frames < 0: + frames = self.frames - start return frames diff --git a/tests/test_pysoundfile.py b/tests/test_pysoundfile.py index 7da2f18..c1f106e 100644 --- a/tests/test_pysoundfile.py +++ b/tests/test_pysoundfile.py @@ -364,8 +364,7 @@ def test_blocks_with_frames(file_stereo_r): def test_blocks_with_frames_and_fill_value(file_stereo_r): blocks = list( sf.blocks(file_stereo_r, blocksize=2, frames=3, fill_value=0)) - last_block = np.row_stack((data_stereo[2:3], np.zeros((1, 2)))) - assert_equal_list_of_arrays(blocks, [data_stereo[0:2], last_block]) + assert_equal_list_of_arrays(blocks, [data_stereo[0:2], data_stereo[2:3]]) def test_blocks_with_out(file_stereo_r): @@ -742,6 +741,36 @@ def test_read_into_out_over_end_with_fill_should_return_full_data_and_write_into assert out.shape == (4, sf_stereo_r.channels) +def test_read_into_out_with_frames_and_fill_value(sf_stereo_r): + out = np.ones((8, sf_stereo_r.channels), dtype='float64') + data = sf_stereo_r.read(3, out=out, fill_value=0) + assert len(data) == 3 + assert np.all(data == data_stereo[:3]) + assert np.all(data == out[:3]) + assert data.base is out + assert np.all(out[3:] == 1) + + +def test_read_into_out_over_end_with_frames_and_fill_value(sf_stereo_r): + out = np.ones((8, sf_stereo_r.channels), dtype='float64') + data = sf_stereo_r.read(6, out=out, fill_value=0) + assert len(data) == 6 + assert np.all(out[:4] == data_stereo) + assert np.all(out[4:6] == 0) + assert np.all(out[6:8] == 1) + assert np.all(data == out[:6]) + assert data.base is out + + +def test_read_into_out_over_end_with_too_large_frames_and_fill_value(sf_stereo_r): + out = np.ones((8, sf_stereo_r.channels), dtype='float64') + data = sf_stereo_r.read(99, out=out, fill_value=0) + assert len(data) == 8 + assert np.all(out[:4] == data_stereo) + assert np.all(out[4:8] == 0) + assert data is out + + # ----------------------------------------------------------------------------- # Test buffer read # -----------------------------------------------------------------------------