From 140018f42e6fde2a239dcb7048f74cdf55ee09cf Mon Sep 17 00:00:00 2001 From: Matthias Geier Date: Sun, 20 Nov 2016 17:10:13 +0100 Subject: [PATCH] Use dtype instead of ctype for "buffer" methods See #178. --- soundfile.py | 55 +++++++++++++++++++++++++++++---------- tests/test_pysoundfile.py | 24 ++++++++--------- 2 files changed, 53 insertions(+), 26 deletions(-) diff --git a/soundfile.py b/soundfile.py index 18c93ef..307dbae 100644 --- a/soundfile.py +++ b/soundfile.py @@ -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 @@ -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 ------- @@ -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 @@ -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 @@ -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 @@ -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 @@ -1100,7 +1104,7 @@ 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 @@ -1108,6 +1112,8 @@ def buffer_write(self, data, ctype): .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 @@ -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), @@ -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 @@ -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() diff --git a/tests/test_pysoundfile.py b/tests/test_pysoundfile.py index 7ea2237..ca10325 100644 --- a/tests/test_pysoundfile.py +++ b/tests/test_pysoundfile.py @@ -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) @@ -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 @@ -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) @@ -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]]) @@ -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)