1"""SoundFile is an audio library based on libsndfile, CFFI and NumPy.
2
3Sound files can be read or written directly using the functions
4:func:`read` and :func:`write`.
5To read a sound file in a block-wise fashion, use :func:`blocks`.
6Alternatively, sound files can be opened as :class:`SoundFile` objects.
7
8For further information, see http://pysoundfile.readthedocs.org/.
9
10"""
11__version__ = "0.10.3"
12
13import os as _os
14import sys as _sys
15from os import SEEK_SET, SEEK_CUR, SEEK_END
16from ctypes.util import find_library as _find_library
17from _soundfile import ffi as _ffi
18
19try:
20    _unicode = unicode  # doesn't exist in Python 3.x
21except NameError:
22    _unicode = str
23
24
25_str_types = {
26    'title':       0x01,
27    'copyright':   0x02,
28    'software':    0x03,
29    'artist':      0x04,
30    'comment':     0x05,
31    'date':        0x06,
32    'album':       0x07,
33    'license':     0x08,
34    'tracknumber': 0x09,
35    'genre':       0x10,
36}
37
38_formats = {
39    'WAV':   0x010000,  # Microsoft WAV format (little endian default).
40    'AIFF':  0x020000,  # Apple/SGI AIFF format (big endian).
41    'AU':    0x030000,  # Sun/NeXT AU format (big endian).
42    'RAW':   0x040000,  # RAW PCM data.
43    'PAF':   0x050000,  # Ensoniq PARIS file format.
44    'SVX':   0x060000,  # Amiga IFF / SVX8 / SV16 format.
45    'NIST':  0x070000,  # Sphere NIST format.
46    'VOC':   0x080000,  # VOC files.
47    'IRCAM': 0x0A0000,  # Berkeley/IRCAM/CARL
48    'W64':   0x0B0000,  # Sonic Foundry's 64 bit RIFF/WAV
49    'MAT4':  0x0C0000,  # Matlab (tm) V4.2 / GNU Octave 2.0
50    'MAT5':  0x0D0000,  # Matlab (tm) V5.0 / GNU Octave 2.1
51    'PVF':   0x0E0000,  # Portable Voice Format
52    'XI':    0x0F0000,  # Fasttracker 2 Extended Instrument
53    'HTK':   0x100000,  # HMM Tool Kit format
54    'SDS':   0x110000,  # Midi Sample Dump Standard
55    'AVR':   0x120000,  # Audio Visual Research
56    'WAVEX': 0x130000,  # MS WAVE with WAVEFORMATEX
57    'SD2':   0x160000,  # Sound Designer 2
58    'FLAC':  0x170000,  # FLAC lossless file format
59    'CAF':   0x180000,  # Core Audio File format
60    'WVE':   0x190000,  # Psion WVE format
61    'OGG':   0x200000,  # Xiph OGG container
62    'MPC2K': 0x210000,  # Akai MPC 2000 sampler
63    'RF64':  0x220000,  # RF64 WAV file
64}
65
66_subtypes = {
67    'PCM_S8':    0x0001,  # Signed 8 bit data
68    'PCM_16':    0x0002,  # Signed 16 bit data
69    'PCM_24':    0x0003,  # Signed 24 bit data
70    'PCM_32':    0x0004,  # Signed 32 bit data
71    'PCM_U8':    0x0005,  # Unsigned 8 bit data (WAV and RAW only)
72    'FLOAT':     0x0006,  # 32 bit float data
73    'DOUBLE':    0x0007,  # 64 bit float data
74    'ULAW':      0x0010,  # U-Law encoded.
75    'ALAW':      0x0011,  # A-Law encoded.
76    'IMA_ADPCM': 0x0012,  # IMA ADPCM.
77    'MS_ADPCM':  0x0013,  # Microsoft ADPCM.
78    'GSM610':    0x0020,  # GSM 6.10 encoding.
79    'VOX_ADPCM': 0x0021,  # OKI / Dialogix ADPCM
80    'G721_32':   0x0030,  # 32kbs G721 ADPCM encoding.
81    'G723_24':   0x0031,  # 24kbs G723 ADPCM encoding.
82    'G723_40':   0x0032,  # 40kbs G723 ADPCM encoding.
83    'DWVW_12':   0x0040,  # 12 bit Delta Width Variable Word encoding.
84    'DWVW_16':   0x0041,  # 16 bit Delta Width Variable Word encoding.
85    'DWVW_24':   0x0042,  # 24 bit Delta Width Variable Word encoding.
86    'DWVW_N':    0x0043,  # N bit Delta Width Variable Word encoding.
87    'DPCM_8':    0x0050,  # 8 bit differential PCM (XI only)
88    'DPCM_16':   0x0051,  # 16 bit differential PCM (XI only)
89    'VORBIS':    0x0060,  # Xiph Vorbis encoding.
90    'ALAC_16':   0x0070,  # Apple Lossless Audio Codec (16 bit).
91    'ALAC_20':   0x0071,  # Apple Lossless Audio Codec (20 bit).
92    'ALAC_24':   0x0072,  # Apple Lossless Audio Codec (24 bit).
93    'ALAC_32':   0x0073,  # Apple Lossless Audio Codec (32 bit).
94}
95
96_endians = {
97    'FILE':   0x00000000,  # Default file endian-ness.
98    'LITTLE': 0x10000000,  # Force little endian-ness.
99    'BIG':    0x20000000,  # Force big endian-ness.
100    'CPU':    0x30000000,  # Force CPU endian-ness.
101}
102
103# libsndfile doesn't specify default subtypes, these are somehow arbitrary:
104_default_subtypes = {
105    'WAV':   'PCM_16',
106    'AIFF':  'PCM_16',
107    'AU':    'PCM_16',
108    # 'RAW':  # subtype must be explicit!
109    'PAF':   'PCM_16',
110    'SVX':   'PCM_16',
111    'NIST':  'PCM_16',
112    'VOC':   'PCM_16',
113    'IRCAM': 'PCM_16',
114    'W64':   'PCM_16',
115    'MAT4':  'DOUBLE',
116    'MAT5':  'DOUBLE',
117    'PVF':   'PCM_16',
118    'XI':    'DPCM_16',
119    'HTK':   'PCM_16',
120    'SDS':   'PCM_16',
121    'AVR':   'PCM_16',
122    'WAVEX': 'PCM_16',
123    'SD2':   'PCM_16',
124    'FLAC':  'PCM_16',
125    'CAF':   'PCM_16',
126    'WVE':   'ALAW',
127    'OGG':   'VORBIS',
128    'MPC2K': 'PCM_16',
129    'RF64':  'PCM_16',
130}
131
132_ffi_types = {
133    'float64': 'double',
134    'float32': 'float',
135    'int32': 'int',
136    'int16': 'short'
137}
138
139try:
140    _libname = _find_library('sndfile')
141    if _libname is None:
142        raise OSError('sndfile library not found')
143    _snd = _ffi.dlopen(_libname)
144except OSError:
145    if _sys.platform == 'darwin':
146        _libname = 'libsndfile.dylib'
147    elif _sys.platform == 'win32':
148        from platform import architecture as _architecture
149        _libname = 'libsndfile' + _architecture()[0] + '.dll'
150    else:
151        raise
152
153    # hack for packaging tools like cx_Freeze, which
154    # compress all scripts into a zip file
155    # which causes __file__ to be inside this zip file
156
157    _path = _os.path.dirname(_os.path.abspath(__file__))
158
159    while not _os.path.isdir(_path):
160        _path = _os.path.abspath(_os.path.join(_path, '..'))
161
162    _snd = _ffi.dlopen(_os.path.join(
163        _path, '_soundfile_data', _libname))
164
165__libsndfile_version__ = _ffi.string(_snd.sf_version_string()).decode('utf-8', 'replace')
166if __libsndfile_version__.startswith('libsndfile-'):
167    __libsndfile_version__ = __libsndfile_version__[len('libsndfile-'):]
168
169
170def read(file, frames=-1, start=0, stop=None, dtype='float64', always_2d=False,
171         fill_value=None, out=None, samplerate=None, channels=None,
172         format=None, subtype=None, endian=None, closefd=True):
173    """Provide audio data from a sound file as NumPy array.
174
175    By default, the whole file is read from the beginning, but the
176    position to start reading can be specified with `start` and the
177    number of frames to read can be specified with `frames`.
178    Alternatively, a range can be specified with `start` and `stop`.
179
180    If there is less data left in the file than requested, the rest of
181    the frames are filled with `fill_value`.
182    If no `fill_value` is specified, a smaller array is returned.
183
184    Parameters
185    ----------
186    file : str or int or file-like object
187        The file to read from.  See :class:`SoundFile` for details.
188    frames : int, optional
189        The number of frames to read. If `frames` is negative, the whole
190        rest of the file is read.  Not allowed if `stop` is given.
191    start : int, optional
192        Where to start reading.  A negative value counts from the end.
193    stop : int, optional
194        The index after the last frame to be read.  A negative value
195        counts from the end.  Not allowed if `frames` is given.
196    dtype : {'float64', 'float32', 'int32', 'int16'}, optional
197        Data type of the returned array, by default ``'float64'``.
198        Floating point audio data is typically in the range from
199        ``-1.0`` to ``1.0``.  Integer data is in the range from
200        ``-2**15`` to ``2**15-1`` for ``'int16'`` and from ``-2**31`` to
201        ``2**31-1`` for ``'int32'``.
202
203        .. note:: Reading int values from a float file will *not*
204            scale the data to [-1.0, 1.0). If the file contains
205            ``np.array([42.6], dtype='float32')``, you will read
206            ``np.array([43], dtype='int32')`` for ``dtype='int32'``.
207
208    Returns
209    -------
210    audiodata : numpy.ndarray or type(out)
211        A two-dimensional (frames x channels) NumPy array is returned.
212        If the sound file has only one channel, a one-dimensional array
213        is returned.  Use ``always_2d=True`` to return a two-dimensional
214        array anyway.
215
216        If `out` was specified, it is returned.  If `out` has more
217        frames than available in the file (or if `frames` is smaller
218        than the length of `out`) and no `fill_value` is given, then
219        only a part of `out` is overwritten and a view containing all
220        valid frames is returned.
221    samplerate : int
222        The sample rate of the audio file.
223
224    Other Parameters
225    ----------------
226    always_2d : bool, optional
227        By default, reading a mono sound file will return a
228        one-dimensional array.  With ``always_2d=True``, audio data is
229        always returned as a two-dimensional array, even if the audio
230        file has only one channel.
231    fill_value : float, optional
232        If more frames are requested than available in the file, the
233        rest of the output is be filled with `fill_value`.  If
234        `fill_value` is not specified, a smaller array is returned.
235    out : numpy.ndarray or subclass, optional
236        If `out` is specified, the data is written into the given array
237        instead of creating a new array.  In this case, the arguments
238        `dtype` and `always_2d` are silently ignored!  If `frames` is
239        not given, it is obtained from the length of `out`.
240    samplerate, channels, format, subtype, endian, closefd
241        See :class:`SoundFile`.
242
243    Examples
244    --------
245    >>> import soundfile as sf
246    >>> data, samplerate = sf.read('stereo_file.wav')
247    >>> data
248    array([[ 0.71329652,  0.06294799],
249           [-0.26450912, -0.38874483],
250           ...
251           [ 0.67398441, -0.11516333]])
252    >>> samplerate
253    44100
254
255    """
256    with SoundFile(file, 'r', samplerate, channels,
257                   subtype, endian, format, closefd) as f:
258        frames = f._prepare_read(start, stop, frames)
259        data = f.read(frames, dtype, always_2d, fill_value, out)
260    return data, f.samplerate
261
262
263def write(file, data, samplerate, subtype=None, endian=None, format=None,
264          closefd=True):
265    """Write data to a sound file.
266
267    .. note:: If `file` exists, it will be truncated and overwritten!
268
269    Parameters
270    ----------
271    file : str or int or file-like object
272        The file to write to.  See :class:`SoundFile` for details.
273    data : array_like
274        The data to write.  Usually two-dimensional (frames x channels),
275        but one-dimensional `data` can be used for mono files.
276        Only the data types ``'float64'``, ``'float32'``, ``'int32'``
277        and ``'int16'`` are supported.
278
279        .. note:: The data type of `data` does **not** select the data
280                  type of the written file. Audio data will be
281                  converted to the given `subtype`. Writing int values
282                  to a float file will *not* scale the values to
283                  [-1.0, 1.0). If you write the value ``np.array([42],
284                  dtype='int32')``, to a ``subtype='FLOAT'`` file, the
285                  file will then contain ``np.array([42.],
286                  dtype='float32')``.
287
288    samplerate : int
289        The sample rate of the audio data.
290    subtype : str, optional
291        See :func:`default_subtype` for the default value and
292        :func:`available_subtypes` for all possible values.
293
294    Other Parameters
295    ----------------
296    format, endian, closefd
297        See :class:`SoundFile`.
298
299    Examples
300    --------
301    Write 10 frames of random data to a new file:
302
303    >>> import numpy as np
304    >>> import soundfile as sf
305    >>> sf.write('stereo_file.wav', np.random.randn(10, 2), 44100, 'PCM_24')
306
307    """
308    import numpy as np
309    data = np.asarray(data)
310    if data.ndim == 1:
311        channels = 1
312    else:
313        channels = data.shape[1]
314    with SoundFile(file, 'w', samplerate, channels,
315                   subtype, endian, format, closefd) as f:
316        f.write(data)
317
318
319def blocks(file, blocksize=None, overlap=0, frames=-1, start=0, stop=None,
320           dtype='float64', always_2d=False, fill_value=None, out=None,
321           samplerate=None, channels=None,
322           format=None, subtype=None, endian=None, closefd=True):
323    """Return a generator for block-wise reading.
324
325    By default, iteration starts at the beginning and stops at the end
326    of the file.  Use `start` to start at a later position and `frames`
327    or `stop` to stop earlier.
328
329    If you stop iterating over the generator before it's exhausted,
330    the sound file is not closed. This is normally not a problem
331    because the file is opened in read-only mode. To close the file
332    properly, the generator's ``close()`` method can be called.
333
334    Parameters
335    ----------
336    file : str or int or file-like object
337        The file to read from.  See :class:`SoundFile` for details.
338    blocksize : int
339        The number of frames to read per block.
340        Either this or `out` must be given.
341    overlap : int, optional
342        The number of frames to rewind between each block.
343
344    Yields
345    ------
346    numpy.ndarray or type(out)
347        Blocks of audio data.
348        If `out` was given, and the requested frames are not an integer
349        multiple of the length of `out`, and no `fill_value` was given,
350        the last block will be a smaller view into `out`.
351
352    Other Parameters
353    ----------------
354    frames, start, stop
355        See :func:`read`.
356    dtype : {'float64', 'float32', 'int32', 'int16'}, optional
357        See :func:`read`.
358    always_2d, fill_value, out
359        See :func:`read`.
360    samplerate, channels, format, subtype, endian, closefd
361        See :class:`SoundFile`.
362
363    Examples
364    --------
365    >>> import soundfile as sf
366    >>> for block in sf.blocks('stereo_file.wav', blocksize=1024):
367    >>>     pass  # do something with 'block'
368
369    """
370    with SoundFile(file, 'r', samplerate, channels,
371                   subtype, endian, format, closefd) as f:
372        frames = f._prepare_read(start, stop, frames)
373        for block in f.blocks(blocksize, overlap, frames,
374                              dtype, always_2d, fill_value, out):
375            yield block
376
377
378class _SoundFileInfo(object):
379    """Information about a SoundFile"""
380
381    def __init__(self, file, verbose):
382        self.verbose = verbose
383        with SoundFile(file) as f:
384            self.name = f.name
385            self.samplerate = f.samplerate
386            self.channels = f.channels
387            self.frames = f.frames
388            self.duration = float(self.frames)/f.samplerate
389            self.format = f.format
390            self.subtype = f.subtype
391            self.endian = f.endian
392            self.format_info = f.format_info
393            self.subtype_info = f.subtype_info
394            self.sections = f.sections
395            self.extra_info = f.extra_info
396
397    @property
398    def _duration_str(self):
399        hours, rest = divmod(self.duration, 3600)
400        minutes, seconds = divmod(rest, 60)
401        if hours >= 1:
402            duration = "{0:.0g}:{1:02.0g}:{2:05.3f} h".format(hours, minutes, seconds)
403        elif minutes >= 1:
404            duration = "{0:02.0g}:{1:05.3f} min".format(minutes, seconds)
405        elif seconds <= 1:
406            duration = "{0:d} samples".format(self.frames)
407        else:
408            duration = "{0:.3f} s".format(seconds)
409        return duration
410
411    def __repr__(self):
412        info = "\n".join(
413            ["{0.name}",
414             "samplerate: {0.samplerate} Hz",
415             "channels: {0.channels}",
416             "duration: {0._duration_str}",
417             "format: {0.format_info} [{0.format}]",
418             "subtype: {0.subtype_info} [{0.subtype}]"])
419        if self.verbose:
420            info += "\n".join(
421                ["\nendian: {0.endian}",
422                 "sections: {0.sections}",
423                 "frames: {0.frames}",
424                 'extra_info: """',
425                 '    {1}"""'])
426        indented_extra_info = ("\n"+" "*4).join(self.extra_info.split("\n"))
427        return info.format(self, indented_extra_info)
428
429
430def info(file, verbose=False):
431    """Returns an object with information about a SoundFile.
432
433    Parameters
434    ----------
435    verbose : bool
436        Whether to print additional information.
437    """
438    return _SoundFileInfo(file, verbose)
439
440
441def available_formats():
442    """Return a dictionary of available major formats.
443
444    Examples
445    --------
446    >>> import soundfile as sf
447    >>> sf.available_formats()
448    {'FLAC': 'FLAC (FLAC Lossless Audio Codec)',
449     'OGG': 'OGG (OGG Container format)',
450     'WAV': 'WAV (Microsoft)',
451     'AIFF': 'AIFF (Apple/SGI)',
452     ...
453     'WAVEX': 'WAVEX (Microsoft)',
454     'RAW': 'RAW (header-less)',
455     'MAT5': 'MAT5 (GNU Octave 2.1 / Matlab 5.0)'}
456
457    """
458    return dict(_available_formats_helper(_snd.SFC_GET_FORMAT_MAJOR_COUNT,
459                                          _snd.SFC_GET_FORMAT_MAJOR))
460
461
462def available_subtypes(format=None):
463    """Return a dictionary of available subtypes.
464
465    Parameters
466    ----------
467    format : str
468        If given, only compatible subtypes are returned.
469
470    Examples
471    --------
472    >>> import soundfile as sf
473    >>> sf.available_subtypes('FLAC')
474    {'PCM_24': 'Signed 24 bit PCM',
475     'PCM_16': 'Signed 16 bit PCM',
476     'PCM_S8': 'Signed 8 bit PCM'}
477
478    """
479    subtypes = _available_formats_helper(_snd.SFC_GET_FORMAT_SUBTYPE_COUNT,
480                                         _snd.SFC_GET_FORMAT_SUBTYPE)
481    return dict((subtype, name) for subtype, name in subtypes
482                if format is None or check_format(format, subtype))
483
484
485def check_format(format, subtype=None, endian=None):
486    """Check if the combination of format/subtype/endian is valid.
487
488    Examples
489    --------
490    >>> import soundfile as sf
491    >>> sf.check_format('WAV', 'PCM_24')
492    True
493    >>> sf.check_format('FLAC', 'VORBIS')
494    False
495
496    """
497    try:
498        return bool(_format_int(format, subtype, endian))
499    except (ValueError, TypeError):
500        return False
501
502
503def default_subtype(format):
504    """Return the default subtype for a given format.
505
506    Examples
507    --------
508    >>> import soundfile as sf
509    >>> sf.default_subtype('WAV')
510    'PCM_16'
511    >>> sf.default_subtype('MAT5')
512    'DOUBLE'
513
514    """
515    _check_format(format)
516    return _default_subtypes.get(format.upper())
517
518
519class SoundFile(object):
520    """A sound file.
521
522    For more documentation see the __init__() docstring (which is also
523    used for the online documentation (http://pysoundfile.readthedocs.org/).
524
525    """
526
527    def __init__(self, file, mode='r', samplerate=None, channels=None,
528                 subtype=None, endian=None, format=None, closefd=True):
529        """Open a sound file.
530
531        If a file is opened with `mode` ``'r'`` (the default) or
532        ``'r+'``, no sample rate, channels or file format need to be
533        given because the information is obtained from the file. An
534        exception is the ``'RAW'`` data format, which always requires
535        these data points.
536
537        File formats consist of three case-insensitive strings:
538
539        * a *major format* which is by default obtained from the
540          extension of the file name (if known) and which can be
541          forced with the format argument (e.g. ``format='WAVEX'``).
542        * a *subtype*, e.g. ``'PCM_24'``. Most major formats have a
543          default subtype which is used if no subtype is specified.
544        * an *endian-ness*, which doesn't have to be specified at all in
545          most cases.
546
547        A :class:`SoundFile` object is a *context manager*, which means
548        if used in a "with" statement, :meth:`.close` is automatically
549        called when reaching the end of the code block inside the "with"
550        statement.
551
552        Parameters
553        ----------
554        file : str or int or file-like object
555            The file to open.  This can be a file name, a file
556            descriptor or a Python file object (or a similar object with
557            the methods ``read()``/``readinto()``, ``write()``,
558            ``seek()`` and ``tell()``).
559        mode : {'r', 'r+', 'w', 'w+', 'x', 'x+'}, optional
560            Open mode.  Has to begin with one of these three characters:
561            ``'r'`` for reading, ``'w'`` for writing (truncates `file`)
562            or ``'x'`` for writing (raises an error if `file` already
563            exists).  Additionally, it may contain ``'+'`` to open
564            `file` for both reading and writing.
565            The character ``'b'`` for *binary mode* is implied because
566            all sound files have to be opened in this mode.
567            If `file` is a file descriptor or a file-like object,
568            ``'w'`` doesn't truncate and ``'x'`` doesn't raise an error.
569        samplerate : int
570            The sample rate of the file.  If `mode` contains ``'r'``,
571            this is obtained from the file (except for ``'RAW'`` files).
572        channels : int
573            The number of channels of the file.
574            If `mode` contains ``'r'``, this is obtained from the file
575            (except for ``'RAW'`` files).
576        subtype : str, sometimes optional
577            The subtype of the sound file.  If `mode` contains ``'r'``,
578            this is obtained from the file (except for ``'RAW'``
579            files), if not, the default value depends on the selected
580            `format` (see :func:`default_subtype`).
581            See :func:`available_subtypes` for all possible subtypes for
582            a given `format`.
583        endian : {'FILE', 'LITTLE', 'BIG', 'CPU'}, sometimes optional
584            The endian-ness of the sound file.  If `mode` contains
585            ``'r'``, this is obtained from the file (except for
586            ``'RAW'`` files), if not, the default value is ``'FILE'``,
587            which is correct in most cases.
588        format : str, sometimes optional
589            The major format of the sound file.  If `mode` contains
590            ``'r'``, this is obtained from the file (except for
591            ``'RAW'`` files), if not, the default value is determined
592            from the file extension.  See :func:`available_formats` for
593            all possible values.
594        closefd : bool, optional
595            Whether to close the file descriptor on :meth:`.close`. Only
596            applicable if the `file` argument is a file descriptor.
597
598        Examples
599        --------
600        >>> from soundfile import SoundFile
601
602        Open an existing file for reading:
603
604        >>> myfile = SoundFile('existing_file.wav')
605        >>> # do something with myfile
606        >>> myfile.close()
607
608        Create a new sound file for reading and writing using a with
609        statement:
610
611        >>> with SoundFile('new_file.wav', 'x+', 44100, 2) as myfile:
612        >>>     # do something with myfile
613        >>>     # ...
614        >>>     assert not myfile.closed
615        >>>     # myfile.close() is called automatically at the end
616        >>> assert myfile.closed
617
618        """
619        # resolve PathLike objects (see PEP519 for details):
620        # can be replaced with _os.fspath(file) for Python >= 3.6
621        file = file.__fspath__() if hasattr(file, '__fspath__') else file
622        self._name = file
623        if mode is None:
624            mode = getattr(file, 'mode', None)
625        mode_int = _check_mode(mode)
626        self._mode = mode
627        self._info = _create_info_struct(file, mode, samplerate, channels,
628                                         format, subtype, endian)
629        self._file = self._open(file, mode_int, closefd)
630        if set(mode).issuperset('r+') and self.seekable():
631            # Move write position to 0 (like in Python file objects)
632            self.seek(0)
633        _snd.sf_command(self._file, _snd.SFC_SET_CLIPPING, _ffi.NULL,
634                        _snd.SF_TRUE)
635
636    name = property(lambda self: self._name)
637    """The file name of the sound file."""
638    mode = property(lambda self: self._mode)
639    """The open mode the sound file was opened with."""
640    samplerate = property(lambda self: self._info.samplerate)
641    """The sample rate of the sound file."""
642    frames = property(lambda self: self._info.frames)
643    """The number of frames in the sound file."""
644    channels = property(lambda self: self._info.channels)
645    """The number of channels in the sound file."""
646    format = property(
647        lambda self: _format_str(self._info.format & _snd.SF_FORMAT_TYPEMASK))
648    """The major format of the sound file."""
649    subtype = property(
650        lambda self: _format_str(self._info.format & _snd.SF_FORMAT_SUBMASK))
651    """The subtype of data in the the sound file."""
652    endian = property(
653        lambda self: _format_str(self._info.format & _snd.SF_FORMAT_ENDMASK))
654    """The endian-ness of the data in the sound file."""
655    format_info = property(
656        lambda self: _format_info(self._info.format &
657                                  _snd.SF_FORMAT_TYPEMASK)[1])
658    """A description of the major format of the sound file."""
659    subtype_info = property(
660        lambda self: _format_info(self._info.format &
661                                  _snd.SF_FORMAT_SUBMASK)[1])
662    """A description of the subtype of the sound file."""
663    sections = property(lambda self: self._info.sections)
664    """The number of sections of the sound file."""
665    closed = property(lambda self: self._file is None)
666    """Whether the sound file is closed or not."""
667    _errorcode = property(lambda self: _snd.sf_error(self._file))
668    """A pending sndfile error code."""
669
670    @property
671    def extra_info(self):
672        """Retrieve the log string generated when opening the file."""
673        info = _ffi.new("char[]", 2**14)
674        _snd.sf_command(self._file, _snd.SFC_GET_LOG_INFO,
675                        info, _ffi.sizeof(info))
676        return _ffi.string(info).decode('utf-8', 'replace')
677
678    # avoid confusion if something goes wrong before assigning self._file:
679    _file = None
680
681    def __repr__(self):
682        return ("SoundFile({0.name!r}, mode={0.mode!r}, "
683                "samplerate={0.samplerate}, channels={0.channels}, "
684                "format={0.format!r}, subtype={0.subtype!r}, "
685                "endian={0.endian!r})".format(self))
686
687    def __del__(self):
688        self.close()
689
690    def __enter__(self):
691        return self
692
693    def __exit__(self, *args):
694        self.close()
695
696    def __setattr__(self, name, value):
697        """Write text meta-data in the sound file through properties."""
698        if name in _str_types:
699            self._check_if_closed()
700            err = _snd.sf_set_string(self._file, _str_types[name],
701                                     value.encode())
702            _error_check(err)
703        else:
704            object.__setattr__(self, name, value)
705
706    def __getattr__(self, name):
707        """Read text meta-data in the sound file through properties."""
708        if name in _str_types:
709            self._check_if_closed()
710            data = _snd.sf_get_string(self._file, _str_types[name])
711            return _ffi.string(data).decode('utf-8', 'replace') if data else ""
712        else:
713            raise AttributeError(
714                "'SoundFile' object has no attribute {0!r}".format(name))
715
716    def __len__(self):
717        # Note: This is deprecated and will be removed at some point,
718        # see https://github.com/bastibe/SoundFile/issues/199
719        return self._info.frames
720
721    def __bool__(self):
722        # Note: This is temporary until __len__ is removed, afterwards it
723        # can (and should) be removed without change of behavior
724        return True
725
726    def __nonzero__(self):
727        # Note: This is only for compatibility with Python 2 and it shall be
728        # removed at the same time as __bool__().
729        return self.__bool__()
730
731    def seekable(self):
732        """Return True if the file supports seeking."""
733        return self._info.seekable == _snd.SF_TRUE
734
735    def seek(self, frames, whence=SEEK_SET):
736        """Set the read/write position.
737
738        Parameters
739        ----------
740        frames : int
741            The frame index or offset to seek.
742        whence : {SEEK_SET, SEEK_CUR, SEEK_END}, optional
743            By default (``whence=SEEK_SET``), `frames` are counted from
744            the beginning of the file.
745            ``whence=SEEK_CUR`` seeks from the current position
746            (positive and negative values are allowed for `frames`).
747            ``whence=SEEK_END`` seeks from the end (use negative value
748            for `frames`).
749
750        Returns
751        -------
752        int
753            The new absolute read/write position in frames.
754
755        Examples
756        --------
757        >>> from soundfile import SoundFile, SEEK_END
758        >>> myfile = SoundFile('stereo_file.wav')
759
760        Seek to the beginning of the file:
761
762        >>> myfile.seek(0)
763        0
764
765        Seek to the end of the file:
766
767        >>> myfile.seek(0, SEEK_END)
768        44100  # this is the file length
769
770        """
771        self._check_if_closed()
772        position = _snd.sf_seek(self._file, frames, whence)
773        _error_check(self._errorcode)
774        return position
775
776    def tell(self):
777        """Return the current read/write position."""
778        return self.seek(0, SEEK_CUR)
779
780    def read(self, frames=-1, dtype='float64', always_2d=False,
781             fill_value=None, out=None):
782        """Read from the file and return data as NumPy array.
783
784        Reads the given number of frames in the given data format
785        starting at the current read/write position.  This advances the
786        read/write position by the same number of frames.
787        By default, all frames from the current read/write position to
788        the end of the file are returned.
789        Use :meth:`.seek` to move the current read/write position.
790
791        Parameters
792        ----------
793        frames : int, optional
794            The number of frames to read. If ``frames < 0``, the whole
795            rest of the file is read.
796        dtype : {'float64', 'float32', 'int32', 'int16'}, optional
797            Data type of the returned array, by default ``'float64'``.
798            Floating point audio data is typically in the range from
799            ``-1.0`` to ``1.0``. Integer data is in the range from
800            ``-2**15`` to ``2**15-1`` for ``'int16'`` and from
801            ``-2**31`` to ``2**31-1`` for ``'int32'``.
802
803            .. note:: Reading int values from a float file will *not*
804                scale the data to [-1.0, 1.0). If the file contains
805                ``np.array([42.6], dtype='float32')``, you will read
806                ``np.array([43], dtype='int32')`` for
807                ``dtype='int32'``.
808
809        Returns
810        -------
811        audiodata : numpy.ndarray or type(out)
812            A two-dimensional NumPy (frames x channels) array is
813            returned. If the sound file has only one channel, a
814            one-dimensional array is returned. Use ``always_2d=True``
815            to return a two-dimensional array anyway.
816
817            If `out` was specified, it is returned. If `out` has more
818            frames than available in the file (or if `frames` is
819            smaller than the length of `out`) and no `fill_value` is
820            given, then only a part of `out` is overwritten and a view
821            containing all valid frames is returned. numpy.ndarray or
822            type(out)
823
824        Other Parameters
825        ----------------
826        always_2d : bool, optional
827            By default, reading a mono sound file will return a
828            one-dimensional array. With ``always_2d=True``, audio data
829            is always returned as a two-dimensional array, even if the
830            audio file has only one channel.
831        fill_value : float, optional
832            If more frames are requested than available in the file,
833            the rest of the output is be filled with `fill_value`. If
834            `fill_value` is not specified, a smaller array is
835            returned.
836        out : numpy.ndarray or subclass, optional
837            If `out` is specified, the data is written into the given
838            array instead of creating a new array. In this case, the
839            arguments `dtype` and `always_2d` are silently ignored! If
840            `frames` is not given, it is obtained from the length of
841            `out`.
842
843        Examples
844        --------
845        >>> from soundfile import SoundFile
846        >>> myfile = SoundFile('stereo_file.wav')
847
848        Reading 3 frames from a stereo file:
849
850        >>> myfile.read(3)
851        array([[ 0.71329652,  0.06294799],
852               [-0.26450912, -0.38874483],
853               [ 0.67398441, -0.11516333]])
854        >>> myfile.close()
855
856        See Also
857        --------
858        buffer_read, .write
859
860        """
861        if out is None:
862            frames = self._check_frames(frames, fill_value)
863            out = self._create_empty_array(frames, always_2d, dtype)
864        else:
865            if frames < 0 or frames > len(out):
866                frames = len(out)
867        frames = self._array_io('read', out, frames)
868        if len(out) > frames:
869            if fill_value is None:
870                out = out[:frames]
871            else:
872                out[frames:] = fill_value
873        return out
874
875    def buffer_read(self, frames=-1, dtype=None):
876        """Read from the file and return data as buffer object.
877
878        Reads the given number of `frames` in the given data format
879        starting at the current read/write position.  This advances the
880        read/write position by the same number of frames.
881        By default, all frames from the current read/write position to
882        the end of the file are returned.
883        Use :meth:`.seek` to move the current read/write position.
884
885        Parameters
886        ----------
887        frames : int, optional
888            The number of frames to read. If `frames < 0`, the whole
889            rest of the file is read.
890        dtype : {'float64', 'float32', 'int32', 'int16'}
891            Audio data will be converted to the given data type.
892
893        Returns
894        -------
895        buffer
896            A buffer containing the read data.
897
898        See Also
899        --------
900        buffer_read_into, .read, buffer_write
901
902        """
903        frames = self._check_frames(frames, fill_value=None)
904        ctype = self._check_dtype(dtype)
905        cdata = _ffi.new(ctype + '[]', frames * self.channels)
906        read_frames = self._cdata_io('read', cdata, ctype, frames)
907        assert read_frames == frames
908        return _ffi.buffer(cdata)
909
910    def buffer_read_into(self, buffer, dtype):
911        """Read from the file into a given buffer object.
912
913        Fills the given `buffer` with frames in the given data format
914        starting at the current read/write position (which can be
915        changed with :meth:`.seek`) until the buffer is full or the end
916        of the file is reached.  This advances the read/write position
917        by the number of frames that were read.
918
919        Parameters
920        ----------
921        buffer : writable buffer
922            Audio frames from the file are written to this buffer.
923        dtype : {'float64', 'float32', 'int32', 'int16'}
924            The data type of `buffer`.
925
926        Returns
927        -------
928        int
929            The number of frames that were read from the file.
930            This can be less than the size of `buffer`.
931            The rest of the buffer is not filled with meaningful data.
932
933        See Also
934        --------
935        buffer_read, .read
936
937        """
938        ctype = self._check_dtype(dtype)
939        cdata, frames = self._check_buffer(buffer, ctype)
940        frames = self._cdata_io('read', cdata, ctype, frames)
941        return frames
942
943    def write(self, data):
944        """Write audio data from a NumPy array to the file.
945
946        Writes a number of frames at the read/write position to the
947        file. This also advances the read/write position by the same
948        number of frames and enlarges the file if necessary.
949
950        Note that writing int values to a float file will *not* scale
951        the values to [-1.0, 1.0). If you write the value
952        ``np.array([42], dtype='int32')``, to a ``subtype='FLOAT'``
953        file, the file will then contain ``np.array([42.],
954        dtype='float32')``.
955
956        Parameters
957        ----------
958        data : array_like
959            The data to write. Usually two-dimensional (frames x
960            channels), but one-dimensional `data` can be used for mono
961            files. Only the data types ``'float64'``, ``'float32'``,
962            ``'int32'`` and ``'int16'`` are supported.
963
964            .. note:: The data type of `data` does **not** select the
965                  data type of the written file. Audio data will be
966                  converted to the given `subtype`. Writing int values
967                  to a float file will *not* scale the values to
968                  [-1.0, 1.0). If you write the value ``np.array([42],
969                  dtype='int32')``, to a ``subtype='FLOAT'`` file, the
970                  file will then contain ``np.array([42.],
971                  dtype='float32')``.
972
973        Examples
974        --------
975        >>> import numpy as np
976        >>> from soundfile import SoundFile
977        >>> myfile = SoundFile('stereo_file.wav')
978
979        Write 10 frames of random data to a new file:
980
981        >>> with SoundFile('stereo_file.wav', 'w', 44100, 2, 'PCM_24') as f:
982        >>>     f.write(np.random.randn(10, 2))
983
984        See Also
985        --------
986        buffer_write, .read
987
988        """
989        import numpy as np
990        # no copy is made if data has already the correct memory layout:
991        data = np.ascontiguousarray(data)
992        written = self._array_io('write', data, len(data))
993        assert written == len(data)
994        self._update_frames(written)
995
996    def buffer_write(self, data, dtype):
997        """Write audio data from a buffer/bytes object to the file.
998
999        Writes the contents of `data` to the file at the current
1000        read/write position.
1001        This also advances the read/write position by the number of
1002        frames that were written and enlarges the file if necessary.
1003
1004        Parameters
1005        ----------
1006        data : buffer or bytes
1007            A buffer or bytes object containing the audio data to be
1008            written.
1009        dtype : {'float64', 'float32', 'int32', 'int16'}
1010            The data type of the audio data stored in `data`.
1011
1012        See Also
1013        --------
1014        .write, buffer_read
1015
1016        """
1017        ctype = self._check_dtype(dtype)
1018        cdata, frames = self._check_buffer(data, ctype)
1019        written = self._cdata_io('write', cdata, ctype, frames)
1020        assert written == frames
1021        self._update_frames(written)
1022
1023    def blocks(self, blocksize=None, overlap=0, frames=-1, dtype='float64',
1024               always_2d=False, fill_value=None, out=None):
1025        """Return a generator for block-wise reading.
1026
1027        By default, the generator yields blocks of the given
1028        `blocksize` (using a given `overlap`) until the end of the file
1029        is reached; `frames` can be used to stop earlier.
1030
1031        Parameters
1032        ----------
1033        blocksize : int
1034            The number of frames to read per block. Either this or `out`
1035            must be given.
1036        overlap : int, optional
1037            The number of frames to rewind between each block.
1038        frames : int, optional
1039            The number of frames to read.
1040            If ``frames < 0``, the file is read until the end.
1041        dtype : {'float64', 'float32', 'int32', 'int16'}, optional
1042            See :meth:`.read`.
1043
1044        Yields
1045        ------
1046        numpy.ndarray or type(out)
1047            Blocks of audio data.
1048            If `out` was given, and the requested frames are not an
1049            integer multiple of the length of `out`, and no
1050            `fill_value` was given, the last block will be a smaller
1051            view into `out`.
1052
1053
1054        Other Parameters
1055        ----------------
1056        always_2d, fill_value, out
1057            See :meth:`.read`.
1058        fill_value : float, optional
1059            See :meth:`.read`.
1060        out : numpy.ndarray or subclass, optional
1061            If `out` is specified, the data is written into the given
1062            array instead of creating a new array. In this case, the
1063            arguments `dtype` and `always_2d` are silently ignored!
1064
1065        Examples
1066        --------
1067        >>> from soundfile import SoundFile
1068        >>> with SoundFile('stereo_file.wav') as f:
1069        >>>     for block in f.blocks(blocksize=1024):
1070        >>>         pass  # do something with 'block'
1071
1072        """
1073        import numpy as np
1074
1075        if 'r' not in self.mode and '+' not in self.mode:
1076            raise RuntimeError("blocks() is not allowed in write-only mode")
1077
1078        if out is None:
1079            if blocksize is None:
1080                raise TypeError("One of {blocksize, out} must be specified")
1081            out = self._create_empty_array(blocksize, always_2d, dtype)
1082            copy_out = True
1083        else:
1084            if blocksize is not None:
1085                raise TypeError(
1086                    "Only one of {blocksize, out} may be specified")
1087            blocksize = len(out)
1088            copy_out = False
1089
1090        overlap_memory = None
1091        frames = self._check_frames(frames, fill_value)
1092        while frames > 0:
1093            if overlap_memory is None:
1094                output_offset = 0
1095            else:
1096                output_offset = len(overlap_memory)
1097                out[:output_offset] = overlap_memory
1098
1099            toread = min(blocksize - output_offset, frames)
1100            self.read(toread, dtype, always_2d, fill_value, out[output_offset:])
1101
1102            if overlap:
1103                if overlap_memory is None:
1104                    overlap_memory = np.copy(out[-overlap:])
1105                else:
1106                    overlap_memory[:] = out[-overlap:]
1107
1108            if blocksize > frames + overlap and fill_value is None:
1109                block = out[:frames + overlap]
1110            else:
1111                block = out
1112            yield np.copy(block) if copy_out else block
1113            frames -= toread
1114
1115    def truncate(self, frames=None):
1116        """Truncate the file to a given number of frames.
1117
1118        After this command, the read/write position will be at the new
1119        end of the file.
1120
1121        Parameters
1122        ----------
1123        frames : int, optional
1124            Only the data before `frames` is kept, the rest is deleted.
1125            If not specified, the current read/write position is used.
1126
1127        """
1128        if frames is None:
1129            frames = self.tell()
1130        err = _snd.sf_command(self._file, _snd.SFC_FILE_TRUNCATE,
1131                              _ffi.new("sf_count_t*", frames),
1132                              _ffi.sizeof("sf_count_t"))
1133        if err:
1134            raise RuntimeError("Error truncating the file")
1135        self._info.frames = frames
1136
1137    def flush(self):
1138        """Write unwritten data to the file system.
1139
1140        Data written with :meth:`.write` is not immediately written to
1141        the file system but buffered in memory to be written at a later
1142        time.  Calling :meth:`.flush` makes sure that all changes are
1143        actually written to the file system.
1144
1145        This has no effect on files opened in read-only mode.
1146
1147        """
1148        self._check_if_closed()
1149        _snd.sf_write_sync(self._file)
1150
1151    def close(self):
1152        """Close the file.  Can be called multiple times."""
1153        if not self.closed:
1154            # be sure to flush data to disk before closing the file
1155            self.flush()
1156            err = _snd.sf_close(self._file)
1157            self._file = None
1158            _error_check(err)
1159
1160    def _open(self, file, mode_int, closefd):
1161        """Call the appropriate sf_open*() function from libsndfile."""
1162        if isinstance(file, (_unicode, bytes)):
1163            if _os.path.isfile(file):
1164                if 'x' in self.mode:
1165                    raise OSError("File exists: {0!r}".format(self.name))
1166                elif set(self.mode).issuperset('w+'):
1167                    # truncate the file, because SFM_RDWR doesn't:
1168                    _os.close(_os.open(file, _os.O_WRONLY | _os.O_TRUNC))
1169            openfunction = _snd.sf_open
1170            if isinstance(file, _unicode):
1171                if _sys.platform == 'win32':
1172                    openfunction = _snd.sf_wchar_open
1173                else:
1174                    file = file.encode(_sys.getfilesystemencoding())
1175            file_ptr = openfunction(file, mode_int, self._info)
1176        elif isinstance(file, int):
1177            file_ptr = _snd.sf_open_fd(file, mode_int, self._info, closefd)
1178        elif _has_virtual_io_attrs(file, mode_int):
1179            file_ptr = _snd.sf_open_virtual(self._init_virtual_io(file),
1180                                            mode_int, self._info, _ffi.NULL)
1181        else:
1182            raise TypeError("Invalid file: {0!r}".format(self.name))
1183        _error_check(_snd.sf_error(file_ptr),
1184                     "Error opening {0!r}: ".format(self.name))
1185        if mode_int == _snd.SFM_WRITE:
1186            # Due to a bug in libsndfile version <= 1.0.25, frames != 0
1187            # when opening a named pipe in SFM_WRITE mode.
1188            # See http://github.com/erikd/libsndfile/issues/77.
1189            self._info.frames = 0
1190            # This is not necessary for "normal" files (because
1191            # frames == 0 in this case), but it doesn't hurt, either.
1192        return file_ptr
1193
1194    def _init_virtual_io(self, file):
1195        """Initialize callback functions for sf_open_virtual()."""
1196        @_ffi.callback("sf_vio_get_filelen")
1197        def vio_get_filelen(user_data):
1198            curr = file.tell()
1199            file.seek(0, SEEK_END)
1200            size = file.tell()
1201            file.seek(curr, SEEK_SET)
1202            return size
1203
1204        @_ffi.callback("sf_vio_seek")
1205        def vio_seek(offset, whence, user_data):
1206            file.seek(offset, whence)
1207            return file.tell()
1208
1209        @_ffi.callback("sf_vio_read")
1210        def vio_read(ptr, count, user_data):
1211            # first try readinto(), if not available fall back to read()
1212            try:
1213                buf = _ffi.buffer(ptr, count)
1214                data_read = file.readinto(buf)
1215            except AttributeError:
1216                data = file.read(count)
1217                data_read = len(data)
1218                buf = _ffi.buffer(ptr, data_read)
1219                buf[0:data_read] = data
1220            return data_read
1221
1222        @_ffi.callback("sf_vio_write")
1223        def vio_write(ptr, count, user_data):
1224            buf = _ffi.buffer(ptr, count)
1225            data = buf[:]
1226            written = file.write(data)
1227            # write() returns None for file objects in Python <= 2.7:
1228            if written is None:
1229                written = count
1230            return written
1231
1232        @_ffi.callback("sf_vio_tell")
1233        def vio_tell(user_data):
1234            return file.tell()
1235
1236        # Note: the callback functions must be kept alive!
1237        self._virtual_io = {'get_filelen': vio_get_filelen,
1238                            'seek': vio_seek,
1239                            'read': vio_read,
1240                            'write': vio_write,
1241                            'tell': vio_tell}
1242
1243        return _ffi.new("SF_VIRTUAL_IO*", self._virtual_io)
1244
1245    def _getAttributeNames(self):
1246        """Return all attributes used in __setattr__ and __getattr__.
1247
1248        This is useful for auto-completion (e.g. IPython).
1249
1250        """
1251        return _str_types
1252
1253    def _check_if_closed(self):
1254        """Check if the file is closed and raise an error if it is.
1255
1256        This should be used in every method that uses self._file.
1257
1258        """
1259        if self.closed:
1260            raise RuntimeError("I/O operation on closed file")
1261
1262    def _check_frames(self, frames, fill_value):
1263        """Reduce frames to no more than are available in the file."""
1264        if self.seekable():
1265            remaining_frames = self.frames - self.tell()
1266            if frames < 0 or (frames > remaining_frames and
1267                              fill_value is None):
1268                frames = remaining_frames
1269        elif frames < 0:
1270            raise ValueError("frames must be specified for non-seekable files")
1271        return frames
1272
1273    def _check_buffer(self, data, ctype):
1274        """Convert buffer to cdata and check for valid size."""
1275        assert ctype in _ffi_types.values()
1276        if not isinstance(data, bytes):
1277            data = _ffi.from_buffer(data)
1278        frames, remainder = divmod(len(data),
1279                                   self.channels * _ffi.sizeof(ctype))
1280        if remainder:
1281            raise ValueError("Data size must be a multiple of frame size")
1282        return data, frames
1283
1284    def _create_empty_array(self, frames, always_2d, dtype):
1285        """Create an empty array with appropriate shape."""
1286        import numpy as np
1287        if always_2d or self.channels > 1:
1288            shape = frames, self.channels
1289        else:
1290            shape = frames,
1291        return np.empty(shape, dtype, order='C')
1292
1293    def _check_dtype(self, dtype):
1294        """Check if dtype string is valid and return ctype string."""
1295        try:
1296            return _ffi_types[dtype]
1297        except KeyError:
1298            raise ValueError("dtype must be one of {0!r} and not {1!r}".format(
1299                sorted(_ffi_types.keys()), dtype))
1300
1301    def _array_io(self, action, array, frames):
1302        """Check array and call low-level IO function."""
1303        if (array.ndim not in (1, 2) or
1304                array.ndim == 1 and self.channels != 1 or
1305                array.ndim == 2 and array.shape[1] != self.channels):
1306            raise ValueError("Invalid shape: {0!r}".format(array.shape))
1307        if not array.flags.c_contiguous:
1308            raise ValueError("Data must be C-contiguous")
1309        ctype = self._check_dtype(array.dtype.name)
1310        assert array.dtype.itemsize == _ffi.sizeof(ctype)
1311        cdata = _ffi.cast(ctype + '*', array.__array_interface__['data'][0])
1312        return self._cdata_io(action, cdata, ctype, frames)
1313
1314    def _cdata_io(self, action, data, ctype, frames):
1315        """Call one of libsndfile's read/write functions."""
1316        assert ctype in _ffi_types.values()
1317        self._check_if_closed()
1318        if self.seekable():
1319            curr = self.tell()
1320        func = getattr(_snd, 'sf_' + action + 'f_' + ctype)
1321        frames = func(self._file, data, frames)
1322        _error_check(self._errorcode)
1323        if self.seekable():
1324            self.seek(curr + frames, SEEK_SET)  # Update read & write position
1325        return frames
1326
1327    def _update_frames(self, written):
1328        """Update self.frames after writing."""
1329        if self.seekable():
1330            curr = self.tell()
1331            self._info.frames = self.seek(0, SEEK_END)
1332            self.seek(curr, SEEK_SET)
1333        else:
1334            self._info.frames += written
1335
1336    def _prepare_read(self, start, stop, frames):
1337        """Seek to start frame and calculate length."""
1338        if start != 0 and not self.seekable():
1339            raise ValueError("start is only allowed for seekable files")
1340        if frames >= 0 and stop is not None:
1341            raise TypeError("Only one of {frames, stop} may be used")
1342
1343        start, stop, _ = slice(start, stop).indices(self.frames)
1344        if stop < start:
1345            stop = start
1346        if frames < 0:
1347            frames = stop - start
1348        if self.seekable():
1349            self.seek(start, SEEK_SET)
1350        return frames
1351
1352
1353def _error_check(err, prefix=""):
1354    """Pretty-print a numerical error code if there is an error."""
1355    if err != 0:
1356        err_str = _snd.sf_error_number(err)
1357        raise RuntimeError(prefix + _ffi.string(err_str).decode('utf-8', 'replace'))
1358
1359
1360def _format_int(format, subtype, endian):
1361    """Return numeric ID for given format|subtype|endian combo."""
1362    result = _check_format(format)
1363    if subtype is None:
1364        subtype = default_subtype(format)
1365        if subtype is None:
1366            raise TypeError(
1367                "No default subtype for major format {0!r}".format(format))
1368    elif not isinstance(subtype, (_unicode, str)):
1369        raise TypeError("Invalid subtype: {0!r}".format(subtype))
1370    try:
1371        result |= _subtypes[subtype.upper()]
1372    except KeyError:
1373        raise ValueError("Unknown subtype: {0!r}".format(subtype))
1374    if endian is None:
1375        endian = 'FILE'
1376    elif not isinstance(endian, (_unicode, str)):
1377        raise TypeError("Invalid endian-ness: {0!r}".format(endian))
1378    try:
1379        result |= _endians[endian.upper()]
1380    except KeyError:
1381        raise ValueError("Unknown endian-ness: {0!r}".format(endian))
1382
1383    info = _ffi.new("SF_INFO*")
1384    info.format = result
1385    info.channels = 1
1386    if _snd.sf_format_check(info) == _snd.SF_FALSE:
1387        raise ValueError(
1388            "Invalid combination of format, subtype and endian")
1389    return result
1390
1391
1392def _check_mode(mode):
1393    """Check if mode is valid and return its integer representation."""
1394    if not isinstance(mode, (_unicode, str)):
1395        raise TypeError("Invalid mode: {0!r}".format(mode))
1396    mode_set = set(mode)
1397    if mode_set.difference('xrwb+') or len(mode) > len(mode_set):
1398        raise ValueError("Invalid mode: {0!r}".format(mode))
1399    if len(mode_set.intersection('xrw')) != 1:
1400        raise ValueError("mode must contain exactly one of 'xrw'")
1401
1402    if '+' in mode_set:
1403        mode_int = _snd.SFM_RDWR
1404    elif 'r' in mode_set:
1405        mode_int = _snd.SFM_READ
1406    else:
1407        mode_int = _snd.SFM_WRITE
1408    return mode_int
1409
1410
1411def _create_info_struct(file, mode, samplerate, channels,
1412                        format, subtype, endian):
1413    """Check arguments and create SF_INFO struct."""
1414    original_format = format
1415    if format is None:
1416        format = _get_format_from_filename(file, mode)
1417        assert isinstance(format, (_unicode, str))
1418    else:
1419        _check_format(format)
1420
1421    info = _ffi.new("SF_INFO*")
1422    if 'r' not in mode or format.upper() == 'RAW':
1423        if samplerate is None:
1424            raise TypeError("samplerate must be specified")
1425        info.samplerate = samplerate
1426        if channels is None:
1427            raise TypeError("channels must be specified")
1428        info.channels = channels
1429        info.format = _format_int(format, subtype, endian)
1430    else:
1431        if any(arg is not None for arg in (
1432                samplerate, channels, original_format, subtype, endian)):
1433            raise TypeError("Not allowed for existing files (except 'RAW'): "
1434                            "samplerate, channels, format, subtype, endian")
1435    return info
1436
1437
1438def _get_format_from_filename(file, mode):
1439    """Return a format string obtained from file (or file.name).
1440
1441    If file already exists (= read mode), an empty string is returned on
1442    error.  If not, an exception is raised.
1443    The return type will always be str or unicode (even if
1444    file/file.name is a bytes object).
1445
1446    """
1447    format = ''
1448    file = getattr(file, 'name', file)
1449    try:
1450        # This raises an exception if file is not a (Unicode/byte) string:
1451        format = _os.path.splitext(file)[-1][1:]
1452        # Convert bytes to unicode (raises AttributeError on Python 3 str):
1453        format = format.decode('utf-8', 'replace')
1454    except Exception:
1455        pass
1456    if format.upper() not in _formats and 'r' not in mode:
1457        raise TypeError("No format specified and unable to get format from "
1458                        "file extension: {0!r}".format(file))
1459    return format
1460
1461
1462def _format_str(format_int):
1463    """Return the string representation of a given numeric format."""
1464    for dictionary in _formats, _subtypes, _endians:
1465        for k, v in dictionary.items():
1466            if v == format_int:
1467                return k
1468    else:
1469        return 'n/a'
1470
1471
1472def _format_info(format_int, format_flag=_snd.SFC_GET_FORMAT_INFO):
1473    """Return the ID and short description of a given format."""
1474    format_info = _ffi.new("SF_FORMAT_INFO*")
1475    format_info.format = format_int
1476    _snd.sf_command(_ffi.NULL, format_flag, format_info,
1477                    _ffi.sizeof("SF_FORMAT_INFO"))
1478    name = format_info.name
1479    return (_format_str(format_info.format),
1480            _ffi.string(name).decode('utf-8', 'replace') if name else "")
1481
1482
1483def _available_formats_helper(count_flag, format_flag):
1484    """Helper for available_formats() and available_subtypes()."""
1485    count = _ffi.new("int*")
1486    _snd.sf_command(_ffi.NULL, count_flag, count, _ffi.sizeof("int"))
1487    for format_int in range(count[0]):
1488        yield _format_info(format_int, format_flag)
1489
1490
1491def _check_format(format_str):
1492    """Check if `format_str` is valid and return format ID."""
1493    if not isinstance(format_str, (_unicode, str)):
1494        raise TypeError("Invalid format: {0!r}".format(format_str))
1495    try:
1496        format_int = _formats[format_str.upper()]
1497    except KeyError:
1498        raise ValueError("Unknown format: {0!r}".format(format_str))
1499    return format_int
1500
1501
1502def _has_virtual_io_attrs(file, mode_int):
1503    """Check if file has all the necessary attributes for virtual IO."""
1504    readonly = mode_int == _snd.SFM_READ
1505    writeonly = mode_int == _snd.SFM_WRITE
1506    return all([
1507        hasattr(file, 'seek'),
1508        hasattr(file, 'tell'),
1509        hasattr(file, 'write') or readonly,
1510        hasattr(file, 'read') or hasattr(file, 'readinto') or writeonly,
1511    ])
1512