[SCM] pysoundfile/master: New upstream version 0.10.0

umlaeute at users.alioth.debian.org umlaeute at users.alioth.debian.org
Thu Nov 23 12:52:25 UTC 2017


The following commit has been merged in the master branch:
commit cffd49fec17fe3e105c08e193ed41cdfb110706a
Author: IOhannes m zmölnig <zmoelnig at umlautS.umlaeute.mur.at>
Date:   Thu Nov 23 12:11:58 2017 +0100

    New upstream version 0.10.0

diff --git a/.gitignore b/.gitignore
index e054ea1..76bda8d 100644
--- a/.gitignore
+++ b/.gitignore
@@ -4,3 +4,6 @@ dist/
 build/
 PySoundFile.egg-info/
 .eggs/
+*.egg-info/
+.cache/
+.vscode/
diff --git a/MANIFEST.in b/MANIFEST.in
index 90a504a..dd0e55e 100644
--- a/MANIFEST.in
+++ b/MANIFEST.in
@@ -1,3 +1,4 @@
 include LICENSE
+include soundfile_build.py
 recursive-include doc *.py *.rst *.txt Makefile
 recursive-include tests *.py *.wav *.raw
diff --git a/README.rst b/README.rst
index 2c1312e..3ba7634 100644
--- a/README.rst
+++ b/README.rst
@@ -1,27 +1,27 @@
-PySoundFile
-===========
+SoundFile
+=========
 
-`PySoundFile <https://github.com/bastibe/PySoundFile>`__ is an audio
+`SoundFile <https://github.com/bastibe/SoundFile>`__ is an audio
 library based on libsndfile, CFFI and NumPy. Full documentation is
 available on http://pysoundfile.readthedocs.org/.
 
-PySoundFile can read and write sound files. File reading/writing is
+SoundFile can read and write sound files. File reading/writing is
 supported through `libsndfile <http://www.mega-nerd.com/libsndfile/>`__,
 which is a free, cross-platform, open-source (LGPL) library for reading
 and writing many different sampled sound file formats that runs on many
 platforms including Windows, OS X, and Unix. It is accessed through
 `CFFI <http://cffi.readthedocs.org/>`__, which is a foreign function
 interface for Python calling C code. CFFI is supported for CPython 2.6+,
-3.x and PyPy 2.0+. PySoundFile represents audio data as NumPy arrays.
+3.x and PyPy 2.0+. SoundFile represents audio data as NumPy arrays.
 
-| PySoundFile is BSD licensed (BSD 3-Clause License).
+| SoundFile is BSD licensed (BSD 3-Clause License).
 | (c) 2013, Bastian Bechtold
 
 
 Breaking Changes
 ----------------
 
-PySoundFile has evolved rapidly during the last few releases. Most
+SoundFile has evolved rapidly during the last few releases. Most
 notably, we changed the import name from ``import pysoundfile`` to
 ``import soundfile`` in 0.7. In 0.6, we cleaned up many small
 inconsistencies, particularly in the the ordering and naming of
@@ -38,41 +38,40 @@ methods to ``dtype``, using the Numpy ``dtype`` notation. The old
 Installation
 ------------
 
-PySoundFile depends on the Python packages CFFI and NumPy, and the
+SoundFile depends on the Python packages CFFI and NumPy, and the
 system library libsndfile.
 
-To install the Python dependencies, I recommend using the `Anaconda
-<http://continuum.io/downloads>`__ distribution of Python 3. This will
-come with all dependencies pre-installed. To install the dependencies
-manually, you can use the ``conda`` package manager, which will
-install all dependencies using ``conda install cffi numpy`` (conda is
-also available independently of Anaconda with ``pip install conda;
-conda init``).
+In a modern Python, you can use ``pip install soundfile`` to download
+and install the latest release of SoundFile and its dependencies.
+On Windows and OS X, this will also install the library libsndfile.
+On Linux, you need to install libsndfile using your distribution's
+package manager, for example ``sudo apt-get install libsndfile1``.
 
-With CFFI and NumPy installed, you can use ``pip install pysoundfile``
-to download and install the latest release of PySoundFile. On Windows
-and OS X, this will also install the library libsndfile. On Linux, you
-need to install libsndfile using your distribution's package manager,
-for example ``sudo apt-get install libsndfile1``.
+If you are running on an unusual platform or if you are using an older
+version of Python, you might need to install NumPy and CFFI separately,
+for example using the Anaconda_ package manager or the `Unofficial Windows
+Binaries for Python Extension Packages <http://www.lfd.uci.edu/~gohlke/pythonlibs/>`_.
+
+.. _Anaconda: https://www.continuum.io/downloads
 
 Read/Write Functions
 --------------------
 
 Data can be written to the file using `soundfile.write()`, or read from
-the file using `soundfile.read()`. PySoundFile can open all file formats
+the file using `soundfile.read()`. SoundFile can open all file formats
 that `libsndfile supports
 <http://www.mega-nerd.com/libsndfile/#Features>`__, for example WAV,
-FLAC, OGG and MAT files.
+FLAC, OGG and MAT files (see `Known Issues <https://github.com/bastibe/SoundFile#known-issues>`__ below about writing OGG files).
 
 Here is an example for a program that reads a wave file and copies it
-into an ogg-vorbis file:
+into an FLAC file:
 
 .. code:: python
 
     import soundfile as sf
 
     data, samplerate = sf.read('existing_file.wav')
-    sf.write('new_file.ogg', data, samplerate)
+    sf.write('new_file.flac', data, samplerate)
 
 Block Processing
 ----------------
@@ -106,8 +105,8 @@ context manager to close the file explicitly:
 
    import soundfile as sf
 
-   with sf.SoundFile('myfile.wav', 'rw') as f:
-       while f.tell() < len(f):
+   with sf.SoundFile('myfile.wav', 'r+') as f:
+       while f.tell() < f.frames:
            pos = f.tell()
            data = f.read(1024)
            f.seek(pos)
@@ -167,6 +166,11 @@ For Python 2.x support, replace the third line with:
 
     from urllib2 import urlopen
 
+Known Issues
+------------
+
+Writing to OGG files can result in empty files with certain versions of libsndfile. See `#130 <https://github.com/bastibe/SoundFile/issues/130>`__ for news on this issue.
+
 News
 ----
 
@@ -246,3 +250,14 @@ News
     - Adds official support for Python 3.6
 
     And some minor bug fixes.
+
+2017-11-12 V0.10.0 Bastian Bechtold:
+    Thank you, Matthias Geier, Toni Barth, Jon Peirce, Till Hoffmann,
+    and Tomas Garcia, for contributions to this release.
+
+    - Should now work with cx_freeze.
+    - Several documentation fixes in the README.
+    - Removes deprecated ``ctype`` argument in favor of ``dtype`` in ``buffer_*()``.
+    - Adds ``SoundFile.frames`` in favor of now-deprecated ``__len__()``.
+    - Improves performance of ``blocks`` and ``SoundFile.blocks()``.
+    - Improves import time by using CFFI's out of line mode.
diff --git a/doc/conf.py b/doc/conf.py
index 0db0bc9..20c7053 100644
--- a/doc/conf.py
+++ b/doc/conf.py
@@ -277,5 +277,5 @@ napoleon_include_special_with_doc = False
 # Fake imports to avoid actually loading NumPy and libsndfile
 import fake_numpy
 sys.modules['numpy'] = sys.modules['fake_numpy']
-import fake_cffi
-sys.modules['cffi'] = sys.modules['fake_cffi']
+import fake__soundfile
+sys.modules['_soundfile'] = sys.modules['fake__soundfile']
diff --git a/doc/fake_cffi.py b/doc/fake__soundfile.py
similarity index 82%
rename from doc/fake_cffi.py
rename to doc/fake__soundfile.py
index ea1cc30..4887706 100644
--- a/doc/fake_cffi.py
+++ b/doc/fake__soundfile.py
@@ -1,10 +1,7 @@
 """Mock module for Sphinx autodoc."""
 
 
-class FFI(object):
-
-    def cdef(self, _):
-        pass
+class ffi(object):
 
     def dlopen(self, _):
         return self
@@ -16,3 +13,6 @@ class FFI(object):
         return NotImplemented
 
     SFC_GET_FORMAT_INFO = NotImplemented
+
+
+ffi = ffi()
diff --git a/setup.py b/setup.py
index cfd57f1..6bd8632 100644
--- a/setup.py
+++ b/setup.py
@@ -87,7 +87,7 @@ else:
 
 setup(
     name='SoundFile',
-    version='0.9.0',
+    version='0.10.0',
     description='An audio library based on libsndfile, CFFI and NumPy',
     author='Bastian Bechtold',
     author_email='basti at bastibe.de',
@@ -98,11 +98,13 @@ setup(
     package_data=package_data,
     zip_safe=zip_safe,
     license='BSD 3-Clause License',
-    install_requires=['cffi>=0.6'],
+    setup_requires=["cffi>=1.0"],
+    install_requires=['cffi>=1.0'],
+    cffi_modules=["soundfile_build.py:ffibuilder"],
     extras_require={'numpy': ['numpy']},
     platforms='any',
     classifiers=[
-        'Development Status :: 3 - Alpha',
+        'Development Status :: 5 - Production/Stable',
         'Intended Audience :: Developers',
         'Intended Audience :: Science/Research',
         'License :: OSI Approved :: BSD License',
diff --git a/soundfile.py b/soundfile.py
index 4e62296..5b4bb85 100644
--- a/soundfile.py
+++ b/soundfile.py
@@ -1,4 +1,4 @@
-"""PySoundFile is an audio library based on libsndfile, CFFI and NumPy.
+"""SoundFile is an audio library based on libsndfile, CFFI and NumPy.
 
 Sound files can be read or written directly using the functions
 :func:`read` and :func:`write`.
@@ -8,146 +8,19 @@ Alternatively, sound files can be opened as :class:`SoundFile` objects.
 For further information, see http://pysoundfile.readthedocs.org/.
 
 """
-__version__ = "0.9.0"
+__version__ = "0.10.0"
 
 import os as _os
 import sys as _sys
-from cffi import FFI as _FFI
 from os import SEEK_SET, SEEK_CUR, SEEK_END
+from ctypes.util import find_library as _find_library
+from _soundfile import ffi as _ffi
 
 try:
     _unicode = unicode  # doesn't exist in Python 3.x
 except NameError:
     _unicode = str
 
-_ffi = _FFI()
-_ffi.cdef("""
-enum
-{
-    SF_FORMAT_SUBMASK       = 0x0000FFFF,
-    SF_FORMAT_TYPEMASK      = 0x0FFF0000,
-    SF_FORMAT_ENDMASK       = 0x30000000
-} ;
-
-enum
-{
-    SFC_GET_LIB_VERSION             = 0x1000,
-    SFC_GET_LOG_INFO                = 0x1001,
-    SFC_GET_FORMAT_INFO             = 0x1028,
-
-    SFC_GET_FORMAT_MAJOR_COUNT      = 0x1030,
-    SFC_GET_FORMAT_MAJOR            = 0x1031,
-    SFC_GET_FORMAT_SUBTYPE_COUNT    = 0x1032,
-    SFC_GET_FORMAT_SUBTYPE          = 0x1033,
-    SFC_FILE_TRUNCATE               = 0x1080,
-    SFC_SET_CLIPPING                = 0x10C0,
-
-    SFC_SET_SCALE_FLOAT_INT_READ    = 0x1014,
-    SFC_SET_SCALE_INT_FLOAT_WRITE   = 0x1015,
-} ;
-
-enum
-{
-    SF_FALSE    = 0,
-    SF_TRUE     = 1,
-
-    /* Modes for opening files. */
-    SFM_READ    = 0x10,
-    SFM_WRITE   = 0x20,
-    SFM_RDWR    = 0x30,
-} ;
-
-typedef int64_t sf_count_t ;
-
-typedef struct SNDFILE_tag SNDFILE ;
-
-typedef struct SF_INFO
-{
-    sf_count_t frames ;        /* Used to be called samples.  Changed to avoid confusion. */
-    int        samplerate ;
-    int        channels ;
-    int        format ;
-    int        sections ;
-    int        seekable ;
-} SF_INFO ;
-
-SNDFILE*    sf_open          (const char *path, int mode, SF_INFO *sfinfo) ;
-int         sf_format_check  (const SF_INFO *info) ;
-
-sf_count_t  sf_seek          (SNDFILE *sndfile, sf_count_t frames, int whence) ;
-
-int         sf_command       (SNDFILE *sndfile, int cmd, void *data, int datasize) ;
-
-int         sf_error         (SNDFILE *sndfile) ;
-const char* sf_strerror      (SNDFILE *sndfile) ;
-const char* sf_error_number  (int errnum) ;
-
-int         sf_perror        (SNDFILE *sndfile) ;
-int         sf_error_str     (SNDFILE *sndfile, char* str, size_t len) ;
-
-int         sf_close         (SNDFILE *sndfile) ;
-void        sf_write_sync    (SNDFILE *sndfile) ;
-
-sf_count_t  sf_read_short    (SNDFILE *sndfile, short *ptr, sf_count_t items) ;
-sf_count_t  sf_read_int      (SNDFILE *sndfile, int *ptr, sf_count_t items) ;
-sf_count_t  sf_read_float    (SNDFILE *sndfile, float *ptr, sf_count_t items) ;
-sf_count_t  sf_read_double   (SNDFILE *sndfile, double *ptr, sf_count_t items) ;
-
-/* Note: Data ptr argument types are declared as void* here in order to
-         avoid an implicit cast warning. (gh183). */
-sf_count_t  sf_readf_short   (SNDFILE *sndfile, void *ptr, sf_count_t frames) ;
-sf_count_t  sf_readf_int     (SNDFILE *sndfile, void *ptr, sf_count_t frames) ;
-sf_count_t  sf_readf_float   (SNDFILE *sndfile, void *ptr, sf_count_t frames) ;
-sf_count_t  sf_readf_double  (SNDFILE *sndfile, void *ptr, sf_count_t frames) ;
-
-sf_count_t  sf_write_short   (SNDFILE *sndfile, short *ptr, sf_count_t items) ;
-sf_count_t  sf_write_int     (SNDFILE *sndfile, int *ptr, sf_count_t items) ;
-sf_count_t  sf_write_float   (SNDFILE *sndfile, float *ptr, sf_count_t items) ;
-sf_count_t  sf_write_double  (SNDFILE *sndfile, double *ptr, sf_count_t items) ;
-
-/* Note: The argument types were changed to void* in order to allow
-         writing bytes in SoundFile.buffer_write() */
-sf_count_t  sf_writef_short  (SNDFILE *sndfile, void *ptr, sf_count_t frames) ;
-sf_count_t  sf_writef_int    (SNDFILE *sndfile, void *ptr, sf_count_t frames) ;
-sf_count_t  sf_writef_float  (SNDFILE *sndfile, void *ptr, sf_count_t frames) ;
-sf_count_t  sf_writef_double (SNDFILE *sndfile, void *ptr, sf_count_t frames) ;
-
-sf_count_t  sf_read_raw      (SNDFILE *sndfile, void *ptr, sf_count_t bytes) ;
-sf_count_t  sf_write_raw     (SNDFILE *sndfile, void *ptr, sf_count_t bytes) ;
-
-const char* sf_get_string    (SNDFILE *sndfile, int str_type) ;
-int         sf_set_string    (SNDFILE *sndfile, int str_type, const char* str) ;
-const char * sf_version_string (void) ;
-
-typedef sf_count_t  (*sf_vio_get_filelen) (void *user_data) ;
-typedef sf_count_t  (*sf_vio_seek)        (sf_count_t offset, int whence, void *user_data) ;
-typedef sf_count_t  (*sf_vio_read)        (void *ptr, sf_count_t count, void *user_data) ;
-typedef sf_count_t  (*sf_vio_write)       (const void *ptr, sf_count_t count, void *user_data) ;
-typedef sf_count_t  (*sf_vio_tell)        (void *user_data) ;
-
-typedef struct SF_VIRTUAL_IO
-{    sf_count_t  (*get_filelen) (void *user_data) ;
-     sf_count_t  (*seek)        (sf_count_t offset, int whence, void *user_data) ;
-     sf_count_t  (*read)        (void *ptr, sf_count_t count, void *user_data) ;
-     sf_count_t  (*write)       (const void *ptr, sf_count_t count, void *user_data) ;
-     sf_count_t  (*tell)        (void *user_data) ;
-} SF_VIRTUAL_IO ;
-
-SNDFILE*    sf_open_virtual   (SF_VIRTUAL_IO *sfvirtual, int mode, SF_INFO *sfinfo, void *user_data) ;
-SNDFILE*    sf_open_fd        (int fd, int mode, SF_INFO *sfinfo, int close_desc) ;
-
-typedef struct SF_FORMAT_INFO
-{
-    int         format ;
-    const char* name ;
-    const char* extension ;
-} SF_FORMAT_INFO ;
-""")
-
-if _sys.platform == 'win32':
-    _ffi.cdef("""
-    SNDFILE* sf_wchar_open (LPCWSTR wpath, int mode, SF_INFO *sfinfo) ;
-    """)
 
 _str_types = {
     'title':       0x01,
@@ -264,7 +137,7 @@ _ffi_types = {
 }
 
 try:
-    _snd = _ffi.dlopen('sndfile')
+    _snd = _ffi.dlopen(_find_library('sndfile'))
 except OSError:
     if _sys.platform == 'darwin':
         _libname = 'libsndfile.dylib'
@@ -273,9 +146,18 @@ except OSError:
         _libname = 'libsndfile' + _architecture()[0] + '.dll'
     else:
         raise
+
+    # hack for packaging tools like cx_Freeze, which
+    # compress all scripts into a zip file
+    # which causes __file__ to be inside this zip file
+
+    _path = _os.path.dirname(_os.path.abspath(__file__))
+
+    while not _os.path.isdir(_path):
+        _path = _os.path.abspath(_os.path.join(_path, '..'))
+
     _snd = _ffi.dlopen(_os.path.join(
-        _os.path.dirname(_os.path.abspath(__file__)),
-        '_soundfile_data', _libname))
+        _path, '_soundfile_data', _libname))
 
 __libsndfile_version__ = _ffi.string(_snd.sf_version_string()).decode('utf-8', 'replace')
 if __libsndfile_version__.startswith('libsndfile-'):
@@ -323,8 +205,7 @@ def read(file, frames=-1, start=0, stop=None, dtype='float64', always_2d=False,
     Returns
     -------
     audiodata : numpy.ndarray or type(out)
-        A two-dimensional NumPy array is returned, where the channels
-        are stored along the first dimension, i.e. as columns.
+        A two-dimensional (frames x channels) NumPy array is returned.
         If the sound file has only one channel, a one-dimensional array
         is returned.  Use ``always_2d=True`` to return a two-dimensional
         array anyway.
@@ -387,7 +268,7 @@ def write(file, data, samplerate, subtype=None, endian=None, format=None,
     file : str or int or file-like object
         The file to write to.  See :class:`SoundFile` for details.
     data : array_like
-        The data to write.  Usually two-dimensional (channels x frames),
+        The data to write.  Usually two-dimensional (frames x channels),
         but one-dimensional `data` can be used for mono files.
         Only the data types ``'float64'``, ``'float32'``, ``'int32'``
         and ``'int16'`` are supported.
@@ -500,8 +381,8 @@ class _SoundFileInfo(object):
             self.name = f.name
             self.samplerate = f.samplerate
             self.channels = f.channels
-            self.frames = len(f)
-            self.duration = self.frames/f.samplerate
+            self.frames = f.frames
+            self.duration = float(self.frames)/f.samplerate
             self.format = f.format
             self.subtype = f.subtype
             self.endian = f.endian
@@ -750,6 +631,8 @@ class SoundFile(object):
     """The open mode the sound file was opened with."""
     samplerate = property(lambda self: self._info.samplerate)
     """The sample rate of the sound file."""
+    frames = property(lambda self: self._info.frames)
+    """The number of frames in the sound file."""
     channels = property(lambda self: self._info.channels)
     """The number of channels in the sound file."""
     format = property(
@@ -823,8 +706,20 @@ class SoundFile(object):
                 "'SoundFile' object has no attribute {0!r}".format(name))
 
     def __len__(self):
+        # Note: This is deprecated and will be removed at some point,
+        # see https://github.com/bastibe/SoundFile/issues/199
         return self._info.frames
 
+    def __bool__(self):
+        # Note: This is temporary until __len__ is removed, afterwards it
+        # can (and should) be removed without change of behavior
+        return True
+
+    def __nonzero__(self):
+        # Note: This is only for compatibility with Python 2 and it shall be
+        # removed at the same time as __bool__().
+        return self.__bool__()
+
     def seekable(self):
         """Return True if the file supports seeking."""
         return self._info.seekable == _snd.SF_TRUE
@@ -906,9 +801,8 @@ class SoundFile(object):
         Returns
         -------
         audiodata : numpy.ndarray or type(out)
-            A two-dimensional NumPy array is returned, where the
-            channels are stored along the first dimension, i.e. as
-            columns. If the sound file has only one channel, a
+            A two-dimensional NumPy (frames x channels) array is
+            returned. If the sound file has only one channel, a
             one-dimensional array is returned. Use ``always_2d=True``
             to return a two-dimensional array anyway.
 
@@ -970,7 +864,7 @@ class SoundFile(object):
                 out[frames:] = fill_value
         return out
 
-    def buffer_read(self, frames=-1, ctype=None, dtype=None):
+    def buffer_read(self, frames=-1, dtype=None):
         """Read from the file and return data as buffer object.
 
         Reads the given number of `frames` in the given data format
@@ -999,14 +893,13 @@ class SoundFile(object):
 
         """
         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=None, dtype=None):
+    def buffer_read_into(self, buffer, dtype):
         """Read from the file into a given buffer object.
 
         Fills the given `buffer` with frames in the given data format
@@ -1034,7 +927,6 @@ class SoundFile(object):
         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)
@@ -1056,8 +948,8 @@ class SoundFile(object):
         Parameters
         ----------
         data : array_like
-            The data to write. Usually two-dimensional (channels x
-            frames), but one-dimensional `data` can be used for mono
+            The data to write. Usually two-dimensional (frames x
+            channels), but one-dimensional `data` can be used for mono
             files. Only the data types ``'float64'``, ``'float32'``,
             ``'int32'`` and ``'int16'`` are supported.
 
@@ -1091,9 +983,9 @@ class SoundFile(object):
         data = np.ascontiguousarray(data)
         written = self._array_io('write', data, len(data))
         assert written == len(data)
-        self._update_len(written)
+        self._update_frames(written)
 
-    def buffer_write(self, data, ctype=None, dtype=None):
+    def buffer_write(self, data, dtype):
         """Write audio data from a buffer/bytes object to the file.
 
         Writes the contents of `data` to the file at the current
@@ -1114,12 +1006,11 @@ class SoundFile(object):
         .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
-        self._update_len(written)
+        self._update_frames(written)
 
     def blocks(self, blocksize=None, overlap=0, frames=-1, dtype='float64',
                always_2d=False, fill_value=None, out=None):
@@ -1138,7 +1029,7 @@ class SoundFile(object):
             The number of frames to rewind between each block.
         frames : int, optional
             The number of frames to read.
-            If ``frames < 1``, the file is read until the end.
+            If ``frames < 0``, the file is read until the end.
         dtype : {'float64', 'float32', 'int32', 'int16'}, optional
             See :meth:`.read`.
 
@@ -1156,6 +1047,12 @@ class SoundFile(object):
         ----------------
         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
         --------
@@ -1165,33 +1062,47 @@ class SoundFile(object):
         >>>         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.
@@ -1343,7 +1254,7 @@ class SoundFile(object):
     def _check_frames(self, frames, fill_value):
         """Reduce frames to no more than are available in the file."""
         if self.seekable():
-            remaining_frames = len(self) - self.tell()
+            remaining_frames = self.frames - self.tell()
             if frames < 0 or (frames > remaining_frames and
                               fill_value is None):
                 frames = remaining_frames
@@ -1379,23 +1290,6 @@ class SoundFile(object):
             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
@@ -1422,8 +1316,8 @@ class SoundFile(object):
             self.seek(curr + frames, SEEK_SET)  # Update read & write position
         return frames
 
-    def _update_len(self, written):
-        """Update len(self) after writing."""
+    def _update_frames(self, written):
+        """Update self.frames after writing."""
         if self.seekable():
             curr = self.tell()
             self._info.frames = self.seek(0, SEEK_END)
@@ -1438,7 +1332,7 @@ class SoundFile(object):
         if frames >= 0 and stop is not None:
             raise TypeError("Only one of {frames, stop} may be used")
 
-        start, stop, _ = slice(start, stop).indices(len(self))
+        start, stop, _ = slice(start, stop).indices(self.frames)
         if stop < start:
             stop = start
         if frames < 0:
diff --git a/soundfile_build.py b/soundfile_build.py
new file mode 100644
index 0000000..5fdcc17
--- /dev/null
+++ b/soundfile_build.py
@@ -0,0 +1,135 @@
+import sys
+from cffi import FFI
+
+ffibuilder = FFI()
+ffibuilder.set_source("_soundfile", None)
+ffibuilder.cdef("""
+enum
+{
+    SF_FORMAT_SUBMASK       = 0x0000FFFF,
+    SF_FORMAT_TYPEMASK      = 0x0FFF0000,
+    SF_FORMAT_ENDMASK       = 0x30000000
+} ;
+
+enum
+{
+    SFC_GET_LIB_VERSION             = 0x1000,
+    SFC_GET_LOG_INFO                = 0x1001,
+    SFC_GET_FORMAT_INFO             = 0x1028,
+
+    SFC_GET_FORMAT_MAJOR_COUNT      = 0x1030,
+    SFC_GET_FORMAT_MAJOR            = 0x1031,
+    SFC_GET_FORMAT_SUBTYPE_COUNT    = 0x1032,
+    SFC_GET_FORMAT_SUBTYPE          = 0x1033,
+    SFC_FILE_TRUNCATE               = 0x1080,
+    SFC_SET_CLIPPING                = 0x10C0,
+
+    SFC_SET_SCALE_FLOAT_INT_READ    = 0x1014,
+    SFC_SET_SCALE_INT_FLOAT_WRITE   = 0x1015,
+} ;
+
+enum
+{
+    SF_FALSE    = 0,
+    SF_TRUE     = 1,
+
+    /* Modes for opening files. */
+    SFM_READ    = 0x10,
+    SFM_WRITE   = 0x20,
+    SFM_RDWR    = 0x30,
+} ;
+
+typedef int64_t sf_count_t ;
+
+typedef struct SNDFILE_tag SNDFILE ;
+
+typedef struct SF_INFO
+{
+    sf_count_t frames ;        /* Used to be called samples.  Changed to avoid confusion. */
+    int        samplerate ;
+    int        channels ;
+    int        format ;
+    int        sections ;
+    int        seekable ;
+} SF_INFO ;
+
+SNDFILE*    sf_open          (const char *path, int mode, SF_INFO *sfinfo) ;
+int         sf_format_check  (const SF_INFO *info) ;
+
+sf_count_t  sf_seek          (SNDFILE *sndfile, sf_count_t frames, int whence) ;
+
+int         sf_command       (SNDFILE *sndfile, int cmd, void *data, int datasize) ;
+
+int         sf_error         (SNDFILE *sndfile) ;
+const char* sf_strerror      (SNDFILE *sndfile) ;
+const char* sf_error_number  (int errnum) ;
+
+int         sf_perror        (SNDFILE *sndfile) ;
+int         sf_error_str     (SNDFILE *sndfile, char* str, size_t len) ;
+
+int         sf_close         (SNDFILE *sndfile) ;
+void        sf_write_sync    (SNDFILE *sndfile) ;
+
+sf_count_t  sf_read_short    (SNDFILE *sndfile, short *ptr, sf_count_t items) ;
+sf_count_t  sf_read_int      (SNDFILE *sndfile, int *ptr, sf_count_t items) ;
+sf_count_t  sf_read_float    (SNDFILE *sndfile, float *ptr, sf_count_t items) ;
+sf_count_t  sf_read_double   (SNDFILE *sndfile, double *ptr, sf_count_t items) ;
+
+/* Note: Data ptr argument types are declared as void* here in order to
+         avoid an implicit cast warning. (gh183). */
+sf_count_t  sf_readf_short   (SNDFILE *sndfile, void *ptr, sf_count_t frames) ;
+sf_count_t  sf_readf_int     (SNDFILE *sndfile, void *ptr, sf_count_t frames) ;
+sf_count_t  sf_readf_float   (SNDFILE *sndfile, void *ptr, sf_count_t frames) ;
+sf_count_t  sf_readf_double  (SNDFILE *sndfile, void *ptr, sf_count_t frames) ;
+
+sf_count_t  sf_write_short   (SNDFILE *sndfile, short *ptr, sf_count_t items) ;
+sf_count_t  sf_write_int     (SNDFILE *sndfile, int *ptr, sf_count_t items) ;
+sf_count_t  sf_write_float   (SNDFILE *sndfile, float *ptr, sf_count_t items) ;
+sf_count_t  sf_write_double  (SNDFILE *sndfile, double *ptr, sf_count_t items) ;
+
+/* Note: The argument types were changed to void* in order to allow
+         writing bytes in SoundFile.buffer_write() */
+sf_count_t  sf_writef_short  (SNDFILE *sndfile, void *ptr, sf_count_t frames) ;
+sf_count_t  sf_writef_int    (SNDFILE *sndfile, void *ptr, sf_count_t frames) ;
+sf_count_t  sf_writef_float  (SNDFILE *sndfile, void *ptr, sf_count_t frames) ;
+sf_count_t  sf_writef_double (SNDFILE *sndfile, void *ptr, sf_count_t frames) ;
+
+sf_count_t  sf_read_raw      (SNDFILE *sndfile, void *ptr, sf_count_t bytes) ;
+sf_count_t  sf_write_raw     (SNDFILE *sndfile, void *ptr, sf_count_t bytes) ;
+
+const char* sf_get_string    (SNDFILE *sndfile, int str_type) ;
+int         sf_set_string    (SNDFILE *sndfile, int str_type, const char* str) ;
+const char * sf_version_string (void) ;
+
+typedef sf_count_t  (*sf_vio_get_filelen) (void *user_data) ;
+typedef sf_count_t  (*sf_vio_seek)        (sf_count_t offset, int whence, void *user_data) ;
+typedef sf_count_t  (*sf_vio_read)        (void *ptr, sf_count_t count, void *user_data) ;
+typedef sf_count_t  (*sf_vio_write)       (const void *ptr, sf_count_t count, void *user_data) ;
+typedef sf_count_t  (*sf_vio_tell)        (void *user_data) ;
+
+typedef struct SF_VIRTUAL_IO
+{    sf_count_t  (*get_filelen) (void *user_data) ;
+     sf_count_t  (*seek)        (sf_count_t offset, int whence, void *user_data) ;
+     sf_count_t  (*read)        (void *ptr, sf_count_t count, void *user_data) ;
+     sf_count_t  (*write)       (const void *ptr, sf_count_t count, void *user_data) ;
+     sf_count_t  (*tell)        (void *user_data) ;
+} SF_VIRTUAL_IO ;
+
+SNDFILE*    sf_open_virtual   (SF_VIRTUAL_IO *sfvirtual, int mode, SF_INFO *sfinfo, void *user_data) ;
+SNDFILE*    sf_open_fd        (int fd, int mode, SF_INFO *sfinfo, int close_desc) ;
+
+typedef struct SF_FORMAT_INFO
+{
+    int         format ;
+    const char* name ;
+    const char* extension ;
+} SF_FORMAT_INFO ;
+""")
+
+if sys.platform == 'win32':
+    ffibuilder.cdef("""
+    SNDFILE* sf_wchar_open (LPCWSTR wpath, int mode, SF_INFO *sfinfo) ;
+    """)
+
+if __name__ == "__main__":
+    ffibuilder.compile(verbose=True)
diff --git a/tests/test_pysoundfile.py b/tests/test_pysoundfile.py
index ca10325..7c1ed6e 100644
--- a/tests/test_pysoundfile.py
+++ b/tests/test_pysoundfile.py
@@ -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))
@@ -515,7 +526,7 @@ def test_if_open_with_mode_w_truncates(file_stereo_rplus, mode):
             assert f.samplerate == 48000
             assert f.channels == 6
             assert f.format == 'AIFF'
-            assert len(f) == 0
+            assert f.frames == 0
         else:
             # This doesn't really work for file descriptors and file objects
             pass
@@ -586,7 +597,7 @@ def test_file_attributes_in_read_mode(sf_stereo_r):
     assert sf_stereo_r.sections == 1
     assert sf_stereo_r.closed is False
     assert sf_stereo_r.seekable() is True
-    assert len(sf_stereo_r) == len(data_stereo)
+    assert sf_stereo_r.frames == len(data_stereo)
 
 
 def test__repr__(sf_stereo_r):
@@ -602,13 +613,18 @@ def test_extra_info(sf_stereo_r):
 
 def test_mode_should_be_in_write_mode(sf_stereo_w):
     assert sf_stereo_w.mode == 'w'
-    assert len(sf_stereo_w) == 0
+    assert sf_stereo_w.frames == 0
 
 
 def test_mode_should_be_in_readwrite_mode(sf_stereo_rplus):
     assert sf_stereo_rplus.mode == 'r+'
 
 
+def test_file_truthiness(file_w):
+    with sf.SoundFile(file_w, 'w', 44100, 2, format='WAV') as f:
+        assert f
+
+
 # -----------------------------------------------------------------------------
 # Test seek/tell
 # -----------------------------------------------------------------------------
@@ -652,7 +668,7 @@ def test_truncate(file_stereo_rplus, use_default):
             else:
                 f.truncate(2)
             assert f.tell() == 2
-            assert len(f) == 2
+            assert f.frames == 2
         if isinstance(file_stereo_rplus, int):
             os.lseek(file_stereo_rplus, 0, os.SEEK_SET)
         data, fs = sf.read(file_stereo_rplus)
@@ -748,6 +764,9 @@ def test_buffer_read(sf_stereo_r):
     with pytest.raises(ValueError) as excinfo:
         sf_stereo_r.buffer_read(dtype='int8')
     assert "dtype must be one of" in str(excinfo.value)
+    with pytest.raises(ValueError) as excinfo:
+        sf_stereo_r.buffer_read()
+    assert "dtype must be one of" in str(excinfo.value)
 
 
 @xfail_from_buffer
@@ -975,9 +994,9 @@ def test_default_subtype():
 def test_write_non_seekable_file(file_w):
     with sf.SoundFile(file_w, 'w', 44100, 1, format='XI') as f:
         assert not f.seekable()
-        assert len(f) == 0
+        assert f.frames == 0
         f.write(data_mono)
-        assert len(f) == len(data_mono)
+        assert f.frames == len(data_mono)
 
         with pytest.raises(RuntimeError) as excinfo:
             f.seek(2)
@@ -985,7 +1004,7 @@ def test_write_non_seekable_file(file_w):
 
     with sf.SoundFile(filename_new) as f:
         assert not f.seekable()
-        assert len(f) == len(data_mono)
+        assert f.frames == len(data_mono)
         data = f.read(3, dtype='int16')
         assert np.all(data == data_mono[:3])
         data = f.read(666, dtype='int16')
@@ -999,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

-- 
pysoundfile packaging



More information about the pkg-multimedia-commits mailing list