Skip to content
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
191 changes: 190 additions & 1 deletion soundfile.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@
SFC_FILE_TRUNCATE = 0x1080,
SFC_SET_CLIPPING = 0x10C0,

SFC_RAW_DATA_NEEDS_ENDSWAP = 0x1110,

SFC_SET_SCALE_FLOAT_INT_READ = 0x1014,
SFC_SET_SCALE_INT_FLOAT_WRITE = 0x1015,
} ;
Expand Down Expand Up @@ -258,7 +260,17 @@
'float64': 'double',
'float32': 'float',
'int32': 'int',
'int16': 'short'
'int16': 'short',
}

_samplesize = {
'float64': 8,
'float32': 4,
'int32': 4,
'int24': 3,
'int16': 2,
'int8': 1,
'uint8': 1,
}

try:
Expand Down Expand Up @@ -782,6 +794,16 @@ def extra_info(self):
info, _ffi.sizeof(info))
return _ffi.string(info).decode()

@property
def needs_endswap(self):
"""\
Determine if raw data read using
:meth:`.read_raw`/:meth:`.read_raw_into` needs to be end swapped on the
host CPU.
"""
return _snd.sf_command(self._file, _snd.SFC_RAW_DATA_NEEDS_ENDSWAP,
_ffi.NULL, 0)

# avoid confusion if something goes wrong before assigning self._file:
_file = None

Expand Down Expand Up @@ -968,6 +990,120 @@ def read(self, frames=-1, dtype='float64', always_2d=False,
out[frames:] = fill_value
return out

def read_raw(self, frames=-1, dtype=None):
Copy link
Owner

@bastibe bastibe Jan 9, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that the dtype parameter is confusing here. Maybe instead of having both frames and dtype a simple numbytes would be preferable. At any rate, dtype can't be an argument, since the file itself has a non-mutable datatype.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That was my initial thought too but the raw_read/write functions actually require that you specify a number of bytes that is a multiple of the audio file frame size. From the libsndfile docs:

The number of bytes read or written must always be an integer multiple of the number of channels multiplied by the number of bytes required to represent one sample from one channel.

Hence, it made more sense to make the interface this way.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But isn't the dtype given by the audio file?

"""\
Read raw audio data from the audio file (not to be confused with reading
RAW header-less PCM files).

Note : The result of using of both regular reads/writes and raw
reads/writes on compressed file formats other than ALAW and ULAW is
undefined.

Parameters
----------
frames : int, optional
The number of frames to read. If `frames < 0`, the whole
rest of the file is read.
dtype : {'float64', 'float32', 'int32', 'int24', 'int16', 'int8', 'uint8'}
Audio data sample format.

Returns
-------
buffer
A buffer containing the read data.
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I do not want pysoundfile to leak CFFI data structures. I would much prefer a bytes or bytearray object instead of a cffi buffer.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@bastibe I don't think there is reason for concern here. The repr() mentions some CFFI type, but it is really just like any other Python buffer object.

I think it's the right thing to use buffer objects, since those are the lowest level Python data structures and supported by many built-in and third-party libraries.

Many things might also work with bytearray(), but I see no advantage in wrapping the buffers in bytearrays.


See Also
--------
read_raw_into, write_raw

"""
#_check_dtype
try:
_samplesize[dtype]
except KeyError:
raise ValueError("dtype must be one of {0!r}".format(
sorted(_samplesize.keys())))

frames = self._check_frames(frames, fill_value=None)
nbytes = frames * self.channels * _samplesize[dtype]
cdata = _ffi.new('unsigned char[]', nbytes)

# _cdata_io
self._check_if_closed()
if self.seekable():
curr = self.tell()
read_bytes = _snd.sf_read_raw(self._file, cdata, nbytes)
_error_check(self._errorcode)
if self.seekable():
self.seek(curr + frames, SEEK_SET) # Update read & write position

assert read_bytes == nbytes

return _ffi.buffer(cdata)

def read_raw_into(self, buffer, dtype=None):
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

doesn't need a dtype, see above.

"""\
Read from the file into a given buffer object.

Fills the given `buffer` with frames in the given data format
starting at the current read/write position (which can be
changed with :meth:`.seek`) until the buffer is full or the end
of the file is reached. This advances the read/write position
by the number of frames that were read.

Note : The result of using of both regular reads/writes and raw
reads/writes on compressed file formats other than ALAW and ULAW is
undefined.

Parameters
----------
buffer : writable buffer
Audio frames from the file are written to this buffer.
dtype : {'float64', 'float32', 'int32', 'int24', 'int16', 'int8', 'uint8'}
Audio data sample format.

Returns
-------
int
The number of frames that were read from the file.
This can be less than the size of `buffer`.
The rest of the buffer is not filled with meaningful data.

See Also
--------
read_raw, write_raw

"""
#_check_dtype
try:
_samplesize[dtype]
except KeyError:
raise ValueError("dtype must be one of {0!r}".format(
sorted(_samplesize.keys())))

#_check_buffer
if not isinstance(buffer, bytes):
cdata = _ffi.from_buffer(buffer)
else:
cdata = buffer
frames, remainder = divmod(len(cdata),
self.channels * _samplesize[dtype])
if remainder:
raise ValueError("Data size must be a multiple of frame size")

nbytes = frames * self.channels * _samplesize[dtype]

# _cdata_io
self._check_if_closed()
if self.seekable():
curr = self.tell()
read_bytes = _snd.sf_read_raw(self._file, cdata, nbytes)
_error_check(self._errorcode)
if self.seekable():
self.seek(curr + frames, SEEK_SET) # Update read & write position

return frames

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

Expand Down Expand Up @@ -1091,6 +1227,59 @@ def write(self, data):
assert written == len(data)
self._update_len(written)

def write_raw(self, data, dtype=None):
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

doesn't need a dtype, see above.

"""\
Write raw audio data to the audio file (not to be confused with writing
RAW header-less PCM files).

Note : The result of using of both regular reads/writes and raw
reads/writes on compressed file formats other than ALAW and ULAW is
undefined.

Parameters
----------
data : buffer or bytes
A buffer or bytes object containing the audio data to be
written.
dtype : {'float64', 'float32', 'int32', 'int24', 'int16', 'int8', 'uint8'}
Audio data sample format.

See Also
--------
read_raw

"""
#_check_dtype
try:
_samplesize[dtype]
except KeyError:
raise ValueError("dtype must be one of {0!r}".format(
sorted(_samplesize.keys())))

#_check_buffer
if not isinstance(data, bytes):
cdata = _ffi.from_buffer(data)
else:
cdata = data
frames, remainder = divmod(len(cdata),
self.channels * _samplesize[dtype])
if remainder:
raise ValueError("Data size must be a multiple of frame size")

nbytes = frames * self.channels * _samplesize[dtype]

# implements _cdata_io
self._check_if_closed()
if self.seekable():
curr = self.tell()
written = _snd.sf_write_raw(self._file, cdata, nbytes)
_error_check(self._errorcode)
if self.seekable():
self.seek(curr + frames, SEEK_SET) # Update read & write position

assert written == nbytes
self._update_len(frames)

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

Expand Down