From 765fe526eb343c69dae181956f39c12c7c88146e Mon Sep 17 00:00:00 2001 From: Matthias Geier Date: Wed, 25 Oct 2017 11:06:08 +0200 Subject: [PATCH 1/2] More tests for blocks() --- tests/test_pysoundfile.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/tests/test_pysoundfile.py b/tests/test_pysoundfile.py index 7c1ed6e..47eca43 100644 --- a/tests/test_pysoundfile.py +++ b/tests/test_pysoundfile.py @@ -361,6 +361,11 @@ def test_blocks_with_frames(file_stereo_r): assert_equal_list_of_arrays(blocks, [data_stereo[0:2], data_stereo[2:3]]) +def test_blocks_with_too_large_frames(file_stereo_r): + blocks = list(sf.blocks(file_stereo_r, blocksize=2, frames=99)) + assert_equal_list_of_arrays(blocks, [data_stereo[0:2], data_stereo[2:4]]) + + 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)) @@ -368,6 +373,13 @@ def test_blocks_with_frames_and_fill_value(file_stereo_r): assert_equal_list_of_arrays(blocks, [data_stereo[0:2], last_block]) +def test_blocks_with_too_large_frames_and_fill_value(file_stereo_r): + blocks = list( + sf.blocks(file_stereo_r, blocksize=3, frames=3000, fill_value=0)) + last_block = np.row_stack((data_stereo[3:4], np.zeros((2, 2)))) + assert_equal_list_of_arrays(blocks, [data_stereo[0:3], last_block]) + + def test_blocks_with_out(file_stereo_r): out = np.empty((3, 2)) blocks = list(sf.blocks(file_stereo_r, out=out)) From 34a32687e257da52b443a1c9793b316b1ae3d51a Mon Sep 17 00:00:00 2001 From: Matthias Geier Date: Sat, 14 Oct 2017 13:51:39 +0200 Subject: [PATCH 2/2] Remove "out" from blocks() This is a continuation of PR #209. --- soundfile.py | 98 +++++++++++++-------------------------- tests/test_argspec.py | 2 +- tests/test_pysoundfile.py | 25 ---------- 3 files changed, 34 insertions(+), 91 deletions(-) diff --git a/soundfile.py b/soundfile.py index 9214dab..6f6d603 100644 --- a/soundfile.py +++ b/soundfile.py @@ -313,8 +313,8 @@ def write(file, data, samplerate, subtype=None, endian=None, format=None, f.write(data) -def blocks(file, blocksize=None, overlap=0, frames=-1, start=0, stop=None, - dtype='float64', always_2d=False, fill_value=None, out=None, +def blocks(file, blocksize, overlap=0, frames=-1, start=0, stop=None, + dtype='float64', always_2d=False, fill_value=None, samplerate=None, channels=None, format=None, subtype=None, endian=None, closefd=True): """Return a generator for block-wise reading. @@ -334,26 +334,26 @@ def blocks(file, blocksize=None, overlap=0, frames=-1, start=0, stop=None, The file to read from. See :class:`SoundFile` for details. blocksize : int The number of frames to read per block. - Either this or `out` must be given. overlap : int, optional The number of frames to rewind between each block. Yields ------ - numpy.ndarray or type(out) + numpy.ndarray Blocks of audio data. - If `out` was given, and the requested frames are not an integer - multiple of the length of `out`, and no `fill_value` was given, - the last block will be a smaller view into `out`. Other Parameters ---------------- - frames, start, stop + frames + See :meth:`SoundFile.blocks`. + start, stop See :func:`read`. - dtype : {'float64', 'float32', 'int32', 'int16'}, optional + dtype See :func:`read`. - always_2d, fill_value, out + always_2d See :func:`read`. + fill_value + See :meth:`SoundFile.blocks`. samplerate, channels, format, subtype, endian, closefd See :class:`SoundFile`. @@ -368,7 +368,7 @@ def blocks(file, blocksize=None, overlap=0, frames=-1, start=0, stop=None, subtype, endian, format, closefd) as f: frames = f._prepare_read(start, stop, frames) for block in f.blocks(blocksize, overlap, frames, - dtype, always_2d, fill_value, out): + dtype, always_2d, fill_value): yield block @@ -1012,8 +1012,8 @@ def buffer_write(self, data, dtype): assert written == frames self._update_frames(written) - def blocks(self, blocksize=None, overlap=0, frames=-1, dtype='float64', - always_2d=False, fill_value=None, out=None): + def blocks(self, blocksize, overlap=0, frames=-1, dtype='float64', + always_2d=False, fill_value=None): """Return a generator for block-wise reading. By default, the generator yields blocks of the given @@ -1023,36 +1023,29 @@ def blocks(self, blocksize=None, overlap=0, frames=-1, dtype='float64', Parameters ---------- blocksize : int - The number of frames to read per block. Either this or `out` - must be given. + The number of frames to read per block. overlap : int, optional The number of frames to rewind between each block. frames : int, optional The number of frames to read. - If ``frames < 0``, the file is read until the end. + If not specified, or if larger than the remaining number of + frames, the file is read until the end. dtype : {'float64', 'float32', 'int32', 'int16'}, optional See :meth:`.read`. + fill_value : float, optional + The last generated block may be smaller than *blocksize*. + If *fill_value* is specified, it will be used to fill the + remaining space and all blocks will have the same size. Yields ------ - numpy.ndarray or type(out) + numpy.ndarray Blocks of audio data. - If `out` was given, and the requested frames are not an - integer multiple of the length of `out`, and no - `fill_value` was given, the last block will be a smaller - view into `out`. - Other Parameters ---------------- - always_2d, fill_value, out - See :meth:`.read`. - fill_value : float, optional + always_2d See :meth:`.read`. - out : numpy.ndarray or subclass, optional - If `out` is specified, the data is written into the given - array instead of creating a new array. In this case, the - arguments `dtype` and `always_2d` are silently ignored! Examples -------- @@ -1062,47 +1055,22 @@ def blocks(self, blocksize=None, overlap=0, frames=-1, dtype='float64', >>> pass # do something with 'block' """ - import numpy as np - if 'r' not in self.mode and '+' not in self.mode: raise RuntimeError("blocks() is not allowed in write-only mode") - if out is None: - if blocksize is None: - raise TypeError("One of {blocksize, out} must be specified") - out = self._create_empty_array(blocksize, always_2d, dtype) - copy_out = True - else: - if blocksize is not None: - raise TypeError( - "Only one of {blocksize, out} may be specified") - blocksize = len(out) - copy_out = False - - overlap_memory = None - frames = self._check_frames(frames, fill_value) + frames = self._check_frames(frames, fill_value=None) + buffer = self._create_empty_array(blocksize, always_2d, dtype) + read_target = buffer while frames > 0: - if overlap_memory is None: - output_offset = 0 - else: - output_offset = len(overlap_memory) - out[:output_offset] = overlap_memory - - toread = min(blocksize - output_offset, frames) - self.read(toread, dtype, always_2d, fill_value, out[output_offset:]) - + read_result = self.read(frames, fill_value=fill_value, + out=read_target) + out_frames = blocksize - len(read_target) + len(read_result) + yield buffer[:out_frames].copy(order='K') + frames -= len(read_result) if overlap: - if overlap_memory is None: - overlap_memory = np.copy(out[-overlap:]) - else: - overlap_memory[:] = out[-overlap:] - - if blocksize > frames + overlap and fill_value is None: - block = out[:frames + overlap] - else: - block = out - yield np.copy(block) if copy_out else block - frames -= toread + # Copy the end of the block to the beginning of the next + buffer[:overlap] = buffer[-overlap:] + read_target = buffer[overlap:] def truncate(self, frames=None): """Truncate the file to a given number of frames. diff --git a/tests/test_argspec.py b/tests/test_argspec.py index 97d985c..c094809 100644 --- a/tests/test_argspec.py +++ b/tests/test_argspec.py @@ -76,4 +76,4 @@ def test_order_of_blocks_arguments(): meth_args = list(signature(sf.SoundFile.blocks).parameters)[1:] meth_args[3:3] = ['start', 'stop'] func_args = list(signature(sf.blocks).parameters) - assert func_args[:10] == ['file'] + meth_args + assert func_args[:9] == ['file'] + meth_args diff --git a/tests/test_pysoundfile.py b/tests/test_pysoundfile.py index 47eca43..dcbf3ae 100644 --- a/tests/test_pysoundfile.py +++ b/tests/test_pysoundfile.py @@ -380,31 +380,6 @@ def test_blocks_with_too_large_frames_and_fill_value(file_stereo_r): assert_equal_list_of_arrays(blocks, [data_stereo[0:3], last_block]) -def test_blocks_with_out(file_stereo_r): - out = np.empty((3, 2)) - blocks = list(sf.blocks(file_stereo_r, out=out)) - assert blocks[0] is out - # First frame was overwritten by second block: - assert np.all(blocks[0] == data_stereo[[3, 1, 2]]) - - assert blocks[1].base is out - assert np.all(blocks[1] == data_stereo[[3]]) - - with pytest.raises(TypeError): - list(sf.blocks(filename_stereo, blocksize=3, out=out)) - - -def test_blocks_inplace_modification(file_stereo_r): - out = np.empty((3, 2)) - blocks = [] - for block in sf.blocks(file_stereo_r, out=out, overlap=1): - blocks.append(np.copy(block)) - block *= 2 - - expected_blocks = [data_stereo[0:3], data_stereo[2:5]] - assert_equal_list_of_arrays(blocks, expected_blocks) - - def test_blocks_mono(): blocks = list(sf.blocks(filename_mono, blocksize=3, dtype='int16', fill_value=0))