Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,6 @@ dist/
build/
PySoundFile.egg-info/
.eggs/
*.egg-info/
.cache/
.vscode/
46 changes: 33 additions & 13 deletions soundfile.py
Original file line number Diff line number Diff line change
Expand Up @@ -1176,6 +1176,12 @@ def blocks(self, blocksize=None, overlap=0, frames=-1, dtype='float64',
----------------
always_2d, fill_value, out
See :meth:`.read`.
fill_value : float, optional
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
--------
Expand All @@ -1185,33 +1191,47 @@ 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 overlap != 0 and not self.seekable():
raise ValueError("overlap is only allowed for seekable files")

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)
while frames > 0:
if frames < blocksize:
if fill_value is not None and out is None:
out = self._create_empty_array(blocksize, always_2d, dtype)
blocksize = frames
block = self.read(blocksize, dtype, always_2d, fill_value, out)
frames -= blocksize
if frames > 0 and self.seekable():
self.seek(-overlap, SEEK_CUR)
frames += overlap
yield block
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:])

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

def truncate(self, frames=None):
"""Truncate the file to a given number of frames.
Expand Down
15 changes: 11 additions & 4 deletions tests/test_pysoundfile.py
Original file line number Diff line number Diff line change
Expand Up @@ -382,6 +382,17 @@ def test_blocks_with_out(file_stereo_r):
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))
Expand Down Expand Up @@ -1007,10 +1018,6 @@ def test_write_non_seekable_file(file_w):
f.read()
assert "frames" in str(excinfo.value)

with pytest.raises(ValueError) as excinfo:
list(f.blocks(blocksize=3, overlap=1))
assert "overlap" in str(excinfo.value)

data, fs = sf.read(filename_new, dtype='int16')
assert np.all(data == data_mono)
assert fs == 44100
Expand Down