NonameSsSs commited on
Commit
770d8ab
1 Parent(s): c75d308

Upload soundfile.py

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