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
55 changes: 41 additions & 14 deletions soundfile.py
Original file line number Diff line number Diff line change
Expand Up @@ -968,7 +968,7 @@ def read(self, frames=-1, dtype='float64', always_2d=False,
out[frames:] = fill_value
return out

def buffer_read(self, frames=-1, ctype='double'):
def buffer_read(self, frames=-1, ctype=None, dtype=None):
"""Read from the file and return data as buffer object.

Reads the given number of `frames` in the given data format
Expand All @@ -983,8 +983,8 @@ def buffer_read(self, frames=-1, ctype='double'):
frames : int, optional
The number of frames to read. If `frames < 0`, the whole
rest of the file is read.
ctype : {'double', 'float', 'int', 'short'}, optional
Audio data will be converted to the given C data type.
dtype : {'float64', 'float32', 'int32', 'int16'}
Audio data will be converted to the given data type.

Returns
-------
Expand All @@ -997,12 +997,14 @@ def buffer_read(self, frames=-1, ctype='double'):

"""
frames = self._check_frames(frames, fill_value=None)
dtype = self._ctype_is_deprecated(ctype, dtype)
ctype = self._check_dtype(dtype)
cdata = _ffi.new(ctype + '[]', frames * self.channels)
read_frames = self._cdata_io('read', cdata, ctype, frames)
assert read_frames == frames
return _ffi.buffer(cdata)

def buffer_read_into(self, buffer, ctype='double'):
def buffer_read_into(self, buffer, ctype=None, dtype=None):
"""Read from the file into a given buffer object.

Fills the given `buffer` with frames in the given data format
Expand All @@ -1015,7 +1017,7 @@ def buffer_read_into(self, buffer, ctype='double'):
----------
buffer : writable buffer
Audio frames from the file are written to this buffer.
ctype : {'double', 'float', 'int', 'short'}, optional
dtype : {'float64', 'float32', 'int32', 'int16'}
The data type of `buffer`.

Returns
Expand All @@ -1030,6 +1032,8 @@ def buffer_read_into(self, buffer, ctype='double'):
buffer_read, .read

"""
dtype = self._ctype_is_deprecated(ctype, dtype)
ctype = self._check_dtype(dtype)
cdata, frames = self._check_buffer(buffer, ctype)
frames = self._cdata_io('read', cdata, ctype, frames)
return frames
Expand Down Expand Up @@ -1087,7 +1091,7 @@ def write(self, data):
assert written == len(data)
self._update_len(written)

def buffer_write(self, data, ctype):
def buffer_write(self, data, ctype=None, dtype=None):
"""Write audio data from a buffer/bytes object to the file.

Writes the contents of `data` to the file at the current
Expand All @@ -1100,14 +1104,16 @@ def buffer_write(self, data, ctype):
data : buffer or bytes
A buffer or bytes object containing the audio data to be
written.
ctype : {'double', 'float', 'int', 'short'}, optional
dtype : {'float64', 'float32', 'int32', 'int16'}
The data type of the audio data stored in `data`.

See Also
--------
.write, buffer_read

"""
dtype = self._ctype_is_deprecated(ctype, dtype)
ctype = self._check_dtype(dtype)
cdata, frames = self._check_buffer(data, ctype)
written = self._cdata_io('write', cdata, ctype, frames)
assert written == frames
Expand Down Expand Up @@ -1345,6 +1351,7 @@ def _check_frames(self, frames, fill_value):

def _check_buffer(self, data, ctype):
"""Convert buffer to cdata and check for valid size."""
assert ctype in _ffi_types.values()
if not isinstance(data, bytes):
data = _ffi.from_buffer(data)
frames, remainder = divmod(len(data),
Expand All @@ -1362,6 +1369,31 @@ def _create_empty_array(self, frames, always_2d, dtype):
shape = frames,
return np.empty(shape, dtype, order='C')

def _check_dtype(self, dtype):
"""Check if dtype string is valid and return ctype string."""
try:
return _ffi_types[dtype]
except KeyError:
raise ValueError("dtype must be one of {0!r}".format(
sorted(_ffi_types.keys())))

def _ctype_is_deprecated(self, ctype, dtype):
"""Show warning if ctype is used instead of dtype.

At some point, ctype arguments shall be removed and the
corresponding dtype arguments shall lose their default value.

"""
if ctype is not None:
from warnings import warn
warn('ctype is deprecated; use dtype instead', Warning)
if dtype is not None:
raise TypeError('Use dtype instead of ctype')
for k, v in _ffi_types.items():
if v == ctype:
return k
return dtype

def _array_io(self, action, array, frames):
"""Check array and call low-level IO function."""
if (array.ndim not in (1, 2) or
Expand All @@ -1370,19 +1402,14 @@ def _array_io(self, action, array, frames):
raise ValueError("Invalid shape: {0!r}".format(array.shape))
if not array.flags.c_contiguous:
raise ValueError("Data must be C-contiguous")
try:
ctype = _ffi_types[array.dtype.name]
except KeyError:
raise TypeError("dtype must be one of {0!r}".format(
sorted(_ffi_types.keys())))
ctype = self._check_dtype(array.dtype.name)
assert array.dtype.itemsize == _ffi.sizeof(ctype)
cdata = _ffi.cast(ctype + '*', array.__array_interface__['data'][0])
return self._cdata_io(action, cdata, ctype, frames)

def _cdata_io(self, action, data, ctype, frames):
"""Call one of libsndfile's read/write functions."""
if ctype not in _ffi_types.values():
raise ValueError("Unsupported data type: {0!r}".format(ctype))
assert ctype in _ffi_types.values()
self._check_if_closed()
if self.seekable():
curr = self.tell()
Expand Down
24 changes: 12 additions & 12 deletions tests/test_pysoundfile.py
Original file line number Diff line number Diff line change
Expand Up @@ -193,7 +193,7 @@ def test_read_into_non_contiguous_out(file_stereo_r):

def test_read_into_out_with_invalid_dtype(file_stereo_r):
out = np.empty((3, 2), dtype='int64')
with pytest.raises(TypeError) as excinfo:
with pytest.raises(ValueError) as excinfo:
sf.read(file_stereo_r, out=out)
assert "dtype must be one of" in str(excinfo.value)

Expand Down Expand Up @@ -731,33 +731,33 @@ def test_read_into_out_over_end_with_fill_should_return_full_data_and_write_into


def test_buffer_read(sf_stereo_r):
buf = sf_stereo_r.buffer_read(2)
buf = sf_stereo_r.buffer_read(2, dtype='float64')
assert len(buf) == 2 * 2 * 8
assert sf_stereo_r.seek(0, sf.SEEK_CUR) == 2
data = np.frombuffer(buf, dtype='float64').reshape(-1, 2)
assert np.all(data == data_stereo[:2])
buf = sf_stereo_r.buffer_read(ctype='float')
buf = sf_stereo_r.buffer_read(dtype='float32')
assert len(buf) == 2 * 2 * 4
assert sf_stereo_r.seek(0, sf.SEEK_CUR) == 4
data = np.frombuffer(buf, dtype='float32').reshape(-1, 2)
assert np.all(data == data_stereo[2:])
buf = sf_stereo_r.buffer_read()
buf = sf_stereo_r.buffer_read(dtype='float32')
assert len(buf) == 0
buf = sf_stereo_r.buffer_read(666)
buf = sf_stereo_r.buffer_read(666, dtype='float32')
assert len(buf) == 0
with pytest.raises(ValueError) as excinfo:
sf_stereo_r.buffer_read(ctype='char')
assert "Unsupported data type" in str(excinfo.value)
sf_stereo_r.buffer_read(dtype='int8')
assert "dtype must be one of" in str(excinfo.value)


@xfail_from_buffer
def test_buffer_read_into(sf_stereo_r):
out = np.ones((3, 2))
frames = sf_stereo_r.buffer_read_into(out)
frames = sf_stereo_r.buffer_read_into(out, dtype='float64')
assert frames == 3
assert np.all(out == data_stereo[:3])
assert sf_stereo_r.seek(0, sf.SEEK_CUR) == 3
frames = sf_stereo_r.buffer_read_into(out)
frames = sf_stereo_r.buffer_read_into(out, dtype='float64')
assert frames == 1
assert np.all(out[:1] == data_stereo[3:])
assert sf_stereo_r.seek(0, sf.SEEK_CUR) == 4
Expand Down Expand Up @@ -815,7 +815,7 @@ def test_rplus_append_data(sf_stereo_rplus):
@xfail_from_buffer
def test_buffer_write(sf_stereo_w):
buf = np.array([[1, 2], [-1, -2]], dtype='int16')
sf_stereo_w.buffer_write(buf, 'short')
sf_stereo_w.buffer_write(buf, dtype='int16')
sf_stereo_w.close()
data, fs = sf.read(filename_new, dtype='int16')
assert np.all(data == buf)
Expand All @@ -824,7 +824,7 @@ def test_buffer_write(sf_stereo_w):

def test_buffer_write_with_bytes(sf_stereo_w):
b = b"\x01\x00\xFF\xFF\xFF\x00\x00\xFF"
sf_stereo_w.buffer_write(b, 'short')
sf_stereo_w.buffer_write(b, dtype='int16')
sf_stereo_w.close()
data, fs = sf.read(filename_new, dtype='int16')
assert np.all(data == [[1, -1], [255, -256]])
Expand All @@ -835,7 +835,7 @@ def test_buffer_write_with_bytes(sf_stereo_w):
def test_buffer_write_with_wrong_size(sf_stereo_w):
buf = np.array([1, 2, 3], dtype='int16')
with pytest.raises(ValueError) as excinfo:
sf_stereo_w.buffer_write(buf, 'short')
sf_stereo_w.buffer_write(buf, dtype='int16')
assert "multiple of frame size" in str(excinfo.value)


Expand Down