Giter VIP home page Giter VIP logo

python-soundfile's Introduction

python-soundfile

version python status license

contributors downloads

The soundfile module is an audio library based on libsndfile, CFFI and NumPy. Full documentation is available on https://python-soundfile.readthedocs.io/.

The soundfile module can read and write sound files. File reading/writing is supported through libsndfile, which is a free, cross-platform, open-source (LGPL) library for reading and writing many different sampled sound file formats that runs on many platforms including Windows, OS X, and Unix. It is accessed through CFFI, which is a foreign function interface for Python calling C code. CFFI is supported for CPython 2.6+, 3.x and PyPy 2.0+. The soundfile module represents audio data as NumPy arrays.

python-soundfile is BSD licensed (BSD 3-Clause License).
(c) 2013, Bastian Bechtold

open-issues closed-issues open-prs closed-prs

Breaking Changes

The soundfile module has evolved rapidly in the past. Most notably, we changed the import name from import pysoundfile to import soundfile in 0.7. In 0.6, we cleaned up many small inconsistencies, particularly in the the ordering and naming of function arguments and the removal of the indexing interface.

In 0.8.0, we changed the default value of always_2d from True to False. Also, the order of arguments of the write function changed from write(data, file, ...) to write(file, data, ...).

In 0.9.0, we changed the ctype arguments of the buffer_* methods to dtype, using the Numpy dtype notation. The old ctype arguments still work, but are now officially deprecated.

In 0.12.0, we changed the load order of the libsndfile library. Now, the packaged libsndfile in the platform-specific wheels is tried before falling back to any system-provided libsndfile. If you would prefer using the system-provided libsndfile, install the source package or source wheel instead of the platform-specific wheels.

Installation

The soundfile module depends on the Python packages CFFI and NumPy, and the library libsndfile.

In a modern Python, you can use pip install soundfile to download and install the latest release of the soundfile module and its dependencies. On Windows (64/32) and OS X (Intel/ARM) and Linux 64, this will also install a current version of the library libsndfile. If you install the source module, you need to install libsndfile using your distribution's package manager, for example sudo apt install libsndfile1.

If you are running on an unusual platform or if you are using an older version of Python, you might need to install NumPy and CFFI separately, for example using the Anaconda package manager or the Unofficial Windows Binaries for Python Extension Packages.

Building

Soundfile itself does not contain any compiled code and can be bundled into a wheel with the usual python setup.py bdist_wheel. However, soundfile relies on libsndfile, and optionally ships its own copy of libsndfile in the wheel.

To build a binary wheel that contains libsndfile, make sure to checkout and update the _soundfile_data submodule, then run python setup.py bdist_wheel as usual. If the resulting file size of the wheel is around one megabyte, a matching libsndfile has been bundled (without libsndfile, it's around 25 KB).

To build binary wheels for all supported platforms, run python build_wheels.py, which will python setup.py bdist_wheel for each of the platforms we have precompiled libsndfiles for.

Error Reporting

In case of API usage errors the soundfile module raises the usual ValueError or TypeError.

For other errors SoundFileError is raised (used to be RuntimeError). Particularly, a LibsndfileError subclass of this exception is raised on errors reported by the libsndfile library. In that case the exception object provides the libsndfile internal error code in the LibsndfileError.code attribute and the raw libsndfile error message in the LibsndfileError.error_string attribute.

Read/Write Functions

Data can be written to the file using soundfile.write(), or read from the file using soundfile.read(). The soundfile module can open all file formats that libsndfile supports, for example WAV, FLAC, OGG and MAT files (see Known Issues below about writing OGG files).

Here is an example for a program that reads a wave file and copies it into an FLAC file:

import soundfile as sf

data, samplerate = sf.read('existing_file.wav')
sf.write('new_file.flac', data, samplerate)

Block Processing

Sound files can also be read in short, optionally overlapping blocks with soundfile.blocks(). For example, this calculates the signal level for each block of a long file:

import numpy as np
import soundfile as sf

rms = [np.sqrt(np.mean(block**2)) for block in
       sf.blocks('myfile.wav', blocksize=1024, overlap=512)]

SoundFile Objects

Sound files can also be opened as SoundFile objects. Every SoundFile has a specific sample rate, data format and a set number of channels.

If a file is opened, it is kept open for as long as the SoundFile object exists. The file closes when the object is garbage collected, but you should use the SoundFile.close() method or the context manager to close the file explicitly:

import soundfile as sf

with sf.SoundFile('myfile.wav', 'r+') as f:
    while f.tell() < f.frames:
        pos = f.tell()
        data = f.read(1024)
        f.seek(pos)
        f.write(data*2)

All data access uses frames as index. A frame is one discrete time-step in the sound file. Every frame contains as many samples as there are channels in the file.

RAW Files

soundfile.read() can usually auto-detect the file type of sound files. This is not possible for RAW files, though:

import soundfile as sf

data, samplerate = sf.read('myfile.raw', channels=1, samplerate=44100,
                           subtype='FLOAT')

Note that on x86, this defaults to endian='LITTLE'. If you are reading big endian data (mostly old PowerPC/6800-based files), you have to set endian='BIG' accordingly.

You can write RAW files in a similar way, but be advised that in most cases, a more expressive format is better and should be used instead.

Virtual IO

If you have an open file-like object, soundfile.read() can open it just like regular files:

import soundfile as sf
with open('filename.flac', 'rb') as f:
    data, samplerate = sf.read(f)

Here is an example using an HTTP request:

import io
import soundfile as sf
from urllib.request import urlopen

url = "http://tinyurl.com/shepard-risset"
data, samplerate = sf.read(io.BytesIO(urlopen(url).read()))

Note that the above example only works with Python 3.x. For Python 2.x support, replace the third line with:

from urllib2 import urlopen

In-memory files

Chunks of audio, i.e. bytes, can also be read and written without touching the filesystem. In the following example OGG is converted to WAV entirely in memory (without writing files to the disk):

import io
import soundfile as sf

def ogg2wav(ogg: bytes):
    ogg_buf = io.BytesIO(ogg)
    ogg_buf.name = 'file.ogg'
    data, samplerate = sf.read(ogg_buf)
    wav_buf = io.BytesIO()
    wav_buf.name = 'file.wav'
    sf.write(wav_buf, data, samplerate)
    wav_buf.seek(0)  # Necessary for `.read()` to return all bytes
    return wav_buf.read()

Known Issues

Writing to OGG files can result in empty files with certain versions of libsndfile. See #130 for news on this issue.

If using a Buildroot style system, Python has trouble locating libsndfile.so file, which causes python-soundfile to not be loaded. This is apparently a bug in python. For the time being, in soundfile.py, you can remove the call to _find_library and hardcode the location of the libsndfile.so in _ffi.dlopen. See #258 for discussion on this issue.

News

2013-08-27 V0.1.0 Bastian Bechtold:

Initial prototype. A simple wrapper for libsndfile in Python

2013-08-30 V0.2.0 Bastian Bechtold:

Bugfixes and more consistency with PySoundCard

2013-08-30 V0.2.1 Bastian Bechtold:

Bugfixes

2013-09-27 V0.3.0 Bastian Bechtold:

Added binary installer for Windows, and context manager

2013-11-06 V0.3.1 Bastian Bechtold:

Switched from distutils to setuptools for easier installation

2013-11-29 V0.4.0 Bastian Bechtold:

Thanks to David Blewett, now with Virtual IO!

2013-12-08 V0.4.1 Bastian Bechtold:

Thanks to Xidorn Quan, FLAC files are not float32 any more.

2014-02-26 V0.5.0 Bastian Bechtold:

Thanks to Matthias Geier, improved seeking and a flush() method.

2015-01-19 V0.6.0 Bastian Bechtold:

A big, big thank you to Matthias Geier, who did most of the work!

  • Switched to float64 as default data type.
  • Function arguments changed for consistency.
  • Added unit tests.
  • Added global read(), write(), blocks() convenience functions.
  • Documentation overhaul and hosting on readthedocs.
  • Added 'x' open mode.
  • Added tell() method.
  • Added __repr__() method.
2015-04-12 V0.7.0 Bastian Bechtold:

Again, thanks to Matthias Geier for all of his hard work, but also Nils Werner and Whistler7 for their many suggestions and help.

  • Renamed import pysoundfile to import soundfile.
  • Installation through pip wheels that contain the necessary libraries for OS X and Windows.
  • Removed exclusive_creation argument to write().
  • Added truncate() method.
2015-10-20 V0.8.0 Bastian Bechtold:

Again, Matthias Geier contributed a whole lot of hard work to this release.

  • Changed the default value of always_2d from True to False.
  • Numpy is now optional, and only loaded for read and write.
  • Added SoundFile.buffer_read() and SoundFile.buffer_read_into() and SoundFile.buffer_write(), which read/write raw data without involving Numpy.
  • Added info() function that returns metadata of a sound file.
  • Changed the argument order of the write() function from write(data, file, ...) to write(file, data, ...)

And many more minor bug fixes.

2017-02-02 V0.9.0 Bastian Bechtold:

Thank you, Matthias Geier, Tomas Garcia, and Todd, for contributions for this release.

  • Adds support for ALAC files.
  • Adds new member __libsndfile_version__
  • Adds number of frames to info class
  • Adds dtype argument to buffer_* methods
  • Deprecates ctype argument to buffer_* methods
  • Adds official support for Python 3.6

And some minor bug fixes.

2017-11-12 V0.10.0 Bastian Bechtold:

Thank you, Matthias Geier, Toni Barth, Jon Peirce, Till Hoffmann, and Tomas Garcia, for contributions to this release.

  • Should now work with cx_freeze.
  • Several documentation fixes in the README.
  • Removes deprecated ctype argument in favor of dtype in buffer_*().
  • Adds SoundFile.frames in favor of now-deprecated __len__().
  • Improves performance of blocks() and SoundFile.blocks().
  • Improves import time by using CFFI's out of line mode.
  • Adds a build script for building distributions.
2022-06-02 V0.11.0 Bastian Bechtold:

Thank you, tennies, Hannes Helmholz, Christoph Boeddeker, Matt Vollrath, Matthias Geier, Jacek Konieczny, Boris Verkhovskiy, Jonas Haag, Eduardo Moguillansky, Panos Laganakos, Jarvy Jarvison, Domingo Ramirez, Tim Chagnon, Kyle Benesch, Fabian-Robert Stöter, Joe Todd

  • MP3 support
  • Adds binary wheels for macOS M1
  • Improves compatibility with macOS, specifically for M1 machines
  • Fixes file descriptor open for binary wheels on Windows and Python 3.5+
  • Updates libsndfile to v1.1.0
  • Adds get_strings method for retrieving all metadata at once
  • Improves documentation, error messages and tests
  • Displays length of very short files in samples
  • Supports the file system path protocol (pathlib et al)
2023-02-02 V0.12.0 Bastian Bechtold

Thank you, Barabazs, Andrew Murray, Jon Peirce, for contributions to this release.

  • Updated libsndfile to v1.2.0
  • Improves precompiled library location, especially with py2app or cx-freeze.
  • Now provide binary wheels for Linux x86_64
  • Now prefers packaged libsndfile over system-installed libsndfile
2023-02-15 V0.12.1 Bastian Bechtold

Thank you, funnypig, for the bug report

  • Fixed typo on library location detection if no packaged lib and no system lib was found

python-soundfile's People

Contributors

aoirint avatar azuwis avatar bastibe avatar boeddeker avatar davidblewett avatar gesellkammer avatar haheho avatar hexdecimal avatar jajcus avatar jarvyj avatar jneuendorf-i4h avatar jonashaag avatar mcclure avatar mgeier avatar msuch avatar mvollrath avatar nils-werner avatar panosl avatar peircej avatar radarhere avatar sebix avatar tgarc avatar tillahoffmann avatar timtam avatar toddrjen avatar upsuper avatar verhovsky avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

python-soundfile's Issues

By default, return float64, but provide options for reading different formats.

I think it's a good idea to return np.float32 by default (although I'm not 100% sure because the default type in NumPy is np.float64).

If the file has 64bit values, however, they shouldn't be truncated by default.

So probably the default argument to read() should be format=None which should be changed appropriately within the function.

BTW, format may not be the best name choice for this parameter, maybe dtype would be rather what people would expect?

Remove unused branches

Currently, there are many old and unused branches which everyone will get by default when cloning the repository.
This is quite confusing and since they (at least most of them) are not needed anyway, it would be great if you could remove them!

silencing portaudio warnings

Hi there, I'm looking at PySoundCard/File for use in PsychoPy as a cross-platform way to load and play sounds quickly (not necessarily with lots of high-level features). I'm currently testing on linux and things look pretty good so far (it was easy to install, load and start playing!)

I'm getting a load of warnings though as the pysoundcard package loads. I suspect they're coming from portaudio and it looks like they're due to devices/channels not loading correctly. Doesn't have any effect that I can see on using the default_output_device though. I wonder if they can be silenced, or whether this is some flag set at compile time in libportaudio?

They'll be pretty annoying if I see that every time I run a script!

ALSA lib pcm.c:2239:(snd_pcm_open_noupdate) Unknown PCM cards.pcm.rear ALSA lib pcm.c:2239:(snd_pcm_open_noupdate) Unknown PCM cards.pcm.center_lfe ALSA lib pcm.c:2239:(snd_pcm_open_noupdate) Unknown PCM cards.pcm.side Expression 'alsa_snd_pcm_hw_params_set_period_size_near( pcm, hwParams, &alsaPeriodFrames, &dir )' failed in 'src/hostapi/alsa/pa_linux_alsa.c', line: 924 Expression 'alsa_snd_pcm_hw_params_set_period_size_near( pcm, hwParams, &alsaPeriodFrames, &dir )' failed in 'src/hostapi/alsa/pa_linux_alsa.c', line: 924 Expression 'alsa_snd_pcm_hw_params_set_period_size_near( pcm, hwParams, &alsaPeriodFrames, &dir )' failed in 'src/hostapi/alsa/pa_linux_alsa.c', line: 924 Expression 'alsa_snd_pcm_hw_params_set_period_size_near( pcm, hwParams, &alsaPeriodFrames, &dir )' failed in 'src/hostapi/alsa/pa_linux_alsa.c', line: 924 Expression 'alsa_snd_pcm_hw_params_set_period_size_near( pcm, hwParams, &alsaPeriodFrames, &dir )' failed in 'src/hostapi/alsa/pa_linux_alsa.c', line: 924 Expression 'alsa_snd_pcm_hw_params_set_period_size_near( pcm, hwParams, &alsaPeriodFrames, &dir )' failed in 'src/hostapi/alsa/pa_linux_alsa.c', line: 924 ALSA lib confmisc.c:1286:(snd_func_refer) Unable to find definition 'cards.AV200.pcm.hdmi.0:CARD=0,AES0=4,AES1=130,AES2=0,AES3=2' ALSA lib conf.c:4248:(_snd_config_evaluate) function snd_func_refer returned error: No such file or directory ALSA lib conf.c:4727:(snd_config_expand) Evaluate error: No such file or directory ALSA lib pcm.c:2239:(snd_pcm_open_noupdate) Unknown PCM hdmi ALSA lib confmisc.c:1286:(snd_func_refer) Unable to find definition 'cards.AV200.pcm.hdmi.0:CARD=0,AES0=4,AES1=130,AES2=0,AES3=2' ALSA lib conf.c:4248:(_snd_config_evaluate) function snd_func_refer returned error: No such file or directory ALSA lib conf.c:4727:(snd_config_expand) Evaluate error: No such file or directory ALSA lib pcm.c:2239:(snd_pcm_open_noupdate) Unknown PCM hdmi ALSA lib confmisc.c:1286:(snd_func_refer) Unable to find definition 'cards.AV200.pcm.modem.0:CARD=0' ALSA lib conf.c:4248:(_snd_config_evaluate) function snd_func_refer returned error: No such file or directory ALSA lib conf.c:4727:(snd_config_expand) Evaluate error: No such file or directory ALSA lib pcm.c:2239:(snd_pcm_open_noupdate) Unknown PCM cards.pcm.phoneline:CARD=0,DEV=0 ALSA lib confmisc.c:1286:(snd_func_refer) Unable to find definition 'cards.AV200.pcm.modem.0:CARD=0' ALSA lib conf.c:4248:(_snd_config_evaluate) function snd_func_refer returned error: No such file or directory ALSA lib conf.c:4727:(snd_config_expand) Evaluate error: No such file or directory ALSA lib pcm.c:2239:(snd_pcm_open_noupdate) Unknown PCM cards.pcm.phoneline:CARD=0,DEV=0 ALSA lib confmisc.c:1286:(snd_func_refer) Unable to find definition 'cards.AV200.pcm.modem.0:CARD=0' ALSA lib conf.c:4248:(_snd_config_evaluate) function snd_func_refer returned error: No such file or directory ALSA lib conf.c:4727:(snd_config_expand) Evaluate error: No such file or directory ALSA lib pcm.c:2239:(snd_pcm_open_noupdate) Unknown PCM phoneline ALSA lib confmisc.c:1286:(snd_func_refer) Unable to find definition 'cards.AV200.pcm.modem.0:CARD=0' ALSA lib conf.c:4248:(_snd_config_evaluate) function snd_func_refer returned error: No such file or directory ALSA lib conf.c:4727:(snd_config_expand) Evaluate error: No such file or directory ALSA lib pcm.c:2239:(snd_pcm_open_noupdate) Unknown PCM phoneline bt_audio_service_open: connect() failed: Connection refused (111) bt_audio_service_open: connect() failed: Connection refused (111) bt_audio_service_open: connect() failed: Connection refused (111) bt_audio_service_open: connect() failed: Connection refused (111) Cannot connect to server socket err = No such file or directory Cannot connect to server request channel jack server is not running or cannot be started

Improve tests

  • we shouldn't write a file with PySoundFile and then test reading it with PySoundFile. If there is a systematic error in reading and writing, we won't find it
  • we shouldn't write a file in each test case. It would be better to create one (or few) very small test file(s) that we ship with the repo (just a few hundred bytes). We should only write files when we're testing writing files.
  • we shouldn't fill the test file with copies of the same number. The samples should be different.

off-by-one error in __len__()

The returned value is one frame too small.

f = SoundFile('test.wav')
len(f), f.__len__(), f.frames

This prints two times the wrong length (off by 1) and the third time the right length.

BTW, I don't understand the comment in the function, why don't you just use the following?

def __len__(self):
    return self.frames

Test Unicode and bytes handling (Python 2 and 3) in all string arguments

After merging #119, the file argument should support str and unicode in Python 2 and str and bytes in Python 3. The arguments mode/format/subtype/endian should support str and unicode in Python 2 and only str in Python 3 (bytes should be disallowed there).

There are some facts that are especially annoying when testing this:

  • in Python2, unicode can be implicitly converted/compared to str (as long as the string consists of only ASCII characters), this is not possible for Python3's str and bytes. That means that test cases that pass in Python2 may fail in Python3.
  • file names should be tested with both Unicode and byte strings. A bytes object may also contain non-ASCII characters. All combinations of Unicode/bytes and ASCII/non-ASCII should be tested.
  • not only the success cases but also the expected failures should be tested.
  • an (invalid) file extension may contain non-ASCII characters (but should still lead to a reasonable error message
  • If local files, the actual file system encoding is unknown, it may be hard to test sys.getfilesystemencoding().
  • as always, 'RAW' files are special, so separate test cases have to be constructed for them.

Add tests

Now that we are changing so many things, we should make sure that we don't break stuff.

What are the goals of PySoundFile?

When I originally started working on PySoundCard, I expressly did not want to wrap portaudio verbatim. Instead, my aim was to create a pythonic way to talk to sound cards and relegate portaudio to an implementation detail.

This made a huge amount of sense for PySoundCard, since portaudio can be pretty hard to figure out. Libsndfile is a much cleaner library than portaudio, with much cleaner abstractions. Still, my aim was to provide as pythonic an interface as possible. Emulating all of libsndfile was never on my agenda.

As we continue to work on PySoundFile, we should formulate consistent goals for our efforts. In the many discussions with @mgeier, I realize that PySoundFile is many things to many people, and my own views are not always representative of what other people want or think.

In particular, this pertains to the questions:

  • should we support every feature of libsndfile?
  • should we focus on usability first, or speed first?
  • should we focus on clean abstractions over convenience?
  • should we break backwards compatibility?
  • what about documentation and tests?

This is all the more relevant since we have over a thousand downloads on PyPi every month, so these decisions probably affect quite a few people.

sndfile not found on os x

yosemite 10.10.2

$ python
Python 2.7.9 |Continuum Analytics, Inc.| (default, Dec 15 2014, 10:37:34) 
[GCC 4.2.1 (Apple Inc. build 5577)] on darwin
>>> import pysoundfile as sf
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/software/anaconda/envs/devpype/lib/python2.7/site-packages/pysoundfile.py", line 240, in <module>
    _snd = _ffi.dlopen('sndfile')
  File "/software/anaconda/envs/devpype/lib/python2.7/site-packages/cffi/api.py", line 121, in dlopen
    lib, function_cache = _make_ffi_library(self, name, flags)
  File "/software/anaconda/envs/devpype/lib/python2.7/site-packages/cffi/api.py", line 493, in _make_ffi_library
    backendlib = _load_backend_lib(backend, libname, flags)
  File "/software/anaconda/envs/devpype/lib/python2.7/site-packages/cffi/api.py", line 481, in _load_backend_lib
    raise OSError("library not found: %r" % (name,))
OSError: library not found: 'sndfile'

and the sndfile binaries and libraries are all available.

$ snd<TAB>
sndfile-cmp           sndfile-deinterleave  sndfile-metadata-get  sndfile-regtest       
sndfile-concat        sndfile-info          sndfile-metadata-set  sndfile-salvage       
sndfile-convert       sndfile-interleave    sndfile-play          

and

$ ls /usr/local/Cellar/libsndfile/1.0.25/
AUTHORS         INSTALL_RECEIPT.json    bin         share
COPYING         NEWS            include
ChangeLog       README          lib

Is there a way to get the version number?

Sometimes there is a module member __version__ for that (e.g. numpy.__version__), how can it be done in PySoundFile?

This doesn't work:

import pysoundfile
pysoundfile.__version__

Re-order functions in pysoundfile.py

I think it's better if the high-level functions are listed earlier in the source file (and therefore in the Sphinx documentation, see also #27).

I think the read() function should be the very first, followed by the write() function.

Then probably available_formats(), available_subtypes(), default_subtype() and format_check().

After that, probably the blocks() function.

I guess it makes sense to put all the underscore-prefixed functions right after that.
Or should we put them immediately before or after the function they are used in?

I would move the class definition of Soundfile to the very end.
Within the class we should probably also move some methods around, but I'm not sure what's the best strategy there.

We should do this before re-writing the docstrings in NumPy style (#27), but probably after setting up Sphinx.

Need binaries for libsndfile 1.0.26

Hi it seems the binaries produced by this are not using the latest version 1.0.26. This is required to use some new methods to extract certain chunks that this project doesn't provide

ie this from their chunk test

memset (&chunk_info, 0, sizeof (chunk_info)) ;
    snprintf (chunk_info.id, sizeof (chunk_info.id), "Test") ;
    chunk_info.id_size = 4 ;

    iterator = sf_get_chunk_iterator (file, &chunk_info) ;
    err = sf_get_chunk_size (iterator, &chunk_info) ;

it would look like

 chunk_info = _ffi.new("SF_CHUNK_INFO*")

        chunk_info.id = b'iXML'
        chunk_info.id_size = 4;

        iterator = _snd.sf_get_chunk_iterator(self._file, chunk_info)

The linux packages are also 1.0.25 it seems also sadly. It might be possible to have to make a heap of builds for different platforms otherwise.

Any ideas ?

How to get each encoded frame to variable rather than writing to file ?

i would like to get something like

wave = SoundFile('existing_file.wav')
ogg  = SoundFile('new_file.ogg', sample_rate=wave.sample_rate,
                 channels=wave.channels, format=ogg_file,
                 mode=write_mode)

data = wave.read(1024)
while len(data) > 0:
    encoded_frame = ogg.write(data,return_encoded_frame=True)
    data = wave.read(1024) 

i just wanna return the encoded frame from ogg.write() function.

libsndfile32bit.dll path

Hi, I was building a package with PyInstaller for a python script that calls PySoundFile functions. The DLLs are bundled into a 'dist' folder together with a Windows EXE of the program.
However, when I ran the EXE, an error occurred in soundfile.py as it was looking for libsndfile32bit.dll in ...._soundfile_data folder though the DLL is already bundled into the same directory as the EXE.

When I looked into soundfile.py, the following section looks like the part that searches the DLL:

try:
_snd = _ffi.dlopen('sndfile')
except OSError as err:
if _sys.platform == 'darwin':
_libname = 'libsndfile.dylib'
elif _sys.platform == 'win32':
from platform import architecture as _architecture
_libname = 'libsndfile' + _architecture()[0] + '.dll'
else:
raise
_snd = _ffi.dlopen(_os.path.join(
_os.path.dirname(_os.path.abspath(__file__)),
'_soundfile_data', _libname))

Is there a better way to go about doing this as I would have to manually create "_soundfile_data" in the dist folder in order for the program to run correctly.

Thanks.

subprocess PIPE error

Using PySoundFile with a subprocess PIPE file-like-object leads to an error:
Example raising the error:

sp = subprocess.Popen(
    ['ffmpeg', '-i', 'test.mp3', '-f', 'wav', '-'],
    stdout=subprocess.PIPE)

sp.stdout.read(8)
Out[69]: b'RIFF\xff\xff\xff\xff'

sp.stdout.seek(0)
Out[70]: 0

dir(sp.stdout)
Out[74]: 
['__class__',
 '__del__',
 '__delattr__',
 '__dict__',
 '__dir__',
 '__doc__',
 '__enter__',
 '__eq__',
 '__exit__',
 '__format__',
 '__ge__',
 '__getattribute__',
 '__getstate__',
 '__gt__',
 '__hash__',
 '__init__',
 '__iter__',
 '__le__',
 '__lt__',
 '__ne__',
 '__new__',
 '__next__',
 '__reduce__',
 '__reduce_ex__',
 '__repr__',
 '__setattr__',
 '__sizeof__',
 '__str__',
 '__subclasshook__',
 '_checkClosed',
 '_checkReadable',
 '_checkSeekable',
 '_checkWritable',
 '_dealloc_warn',
 '_finalizing',
 'close',
 'closed',
 'detach',
 'fileno',
 'flush',
 'isatty',
 'mode',
 'name',
 'peek',
 'raw',
 'read',
 'read1',
 'readable',
 'readinto',
 'readline',
 'readlines',
 'seek',
 'seekable',
 'tell',
 'truncate',
 'writable',
 'write',
 'writelines']

# Try reading stdout with soundfile
sf = soundfile.SoundFile(sp.stdout)
From cffi callback <function SoundFile._init_virtual_io.<locals>.vio_get_filelen at 0x0000000005DF3730>:
Traceback (most recent call last):
  File "C:\Miniconda3\envs\anaconda\lib\site-packages\soundfile.py", line 1027, in vio_get_filelen
    file.seek(curr, SEEK_SET)
OSError: [Errno 22] Invalid argument
---------------------------------------------------------------------------
RuntimeError                              Traceback (most recent call last)
<ipython-input-73-38dc580aa6b1> in <module>()
----> 1 sf = soundfile.SoundFile(sp.stdout)

C:\Miniconda3\envs\anaconda\lib\site-packages\soundfile.py in __init__(self, file, mode, samplerate, channels, subtype, endian, format, closefd)
    654         self._info = _create_info_struct(file, mode, samplerate, channels,
    655                                          format, subtype, endian)
--> 656         self._file = self._open(file, mode_int, closefd)
    657         if set(mode).issuperset('r+') and self.seekable():
    658             # Move write position to 0 (like in Python file objects)

C:\Miniconda3\envs\anaconda\lib\site-packages\soundfile.py in _open(self, file, mode_int, closefd)
   1008             raise TypeError("Invalid file: {0!r}".format(self.name))
   1009         _error_check(_snd.sf_error(file_ptr),
-> 1010                      "Error opening {0!r}: ".format(self.name))
   1011         if mode_int == _snd.SFM_WRITE:
   1012             # Due to a bug in libsndfile version <= 1.0.25, frames != 0

C:\Miniconda3\envs\anaconda\lib\site-packages\soundfile.py in _error_check(err, prefix)
   1167     if err != 0:
   1168         err_str = _snd.sf_error_number(err)
-> 1169         raise RuntimeError(prefix + _ffi.string(err_str).decode())
   1170 
   1171 

RuntimeError: Error opening <_io.BufferedReader name=7>: File contains data in an unknown format.

I'd be very happy getting some advice to fix it without reading the whole stdout in cache befor loading a SoundFile instance.

Thanks!

sequence interface: off-by-one error

When reading a whole file with the sequence interface, the last frame is missing:

from pysoundfile import SoundFile
f = SoundFile('test.wav')
s = f[:]
assert f.frames == s.shape[0], 'number of frames inconsistent!'

Using file descriptors on Windows?

I was trying PySoundFile the first time under Windows (Windows 7, 64-bit, Pyzo), and it seems that using file descriptors doesn't work on my system.

I was trying it with the current code from #60, but the same problems occur when using the code from the master branch.

When I try writing to a file using a file descriptor, I get this error:

Error : this file format does not support pipe write.

When I try reading a file using a file descriptor, I get this:

File contains data in an unknown format.

The same things work fine when using a file name and also when using a Python file object.

I found the following note in the libsndfile docs:

Note: On Microsoft Windows, this function does not work if the application and the libsndfile DLL are linked to different versions of the Microsoft C runtime DLL.

What exactly does this mean?

Is this a known problem with PySoundFile?

Is there an easy way to fix this?

If not, it's probably not a good idea to use file descriptors by default, as I suggested in #60.

Reading a file object can't guess the format, but won't allow it as an argument either.

Consider this code:

from pysoundfile import read, write
from tempfile import NamedTemporaryFile

tmp_file = NamedTemporaryFile(suffix='.wav')
write(signal, tmp_file, fs, format='wav')

So far, this works fine, but you can't read the file:

read(tmp_file) # raises RuntimeError('File contains data in an unknown format.')
read(tmp_file, format='wav') # raises TypeError("Not allowed for existing files (except 'RAW'): [...] format [...]")

In this particular case, read(tmp_file.name) works fine, but not every file object has a name.

Support interface normalized to [-1,1) range?

I would like to interface Numpy arrays to 16, 24, 32 and 64-bit WAV formats, and PySoundFile looks like a good match for my needs. I saw the discussion in issue #17, and I plan to wait until the next version that avoids truncating 64-bit data to 32-bit before I install PySoundFile.

I plan to do all my signal processing with dtype=float64 and a range of -1 to +1. For 32-bit and 64-bit floating-point WAV files, this is the natural range. For the PCM WAV formats, scaling will be necessary to achieve this in the interface to Numpy arrays. I found another Python WAV interface package that supports this style:
https://github.com/gesellkammer/sndfileio
However, it does not support the 64-bit WAV format or Python 3.

My suggestion for PySoundFile is to support the [-1,1) range for audio data. I am not clear if PySoundFile already does this when using float64 Numpy arrays with PCM WAV files. If not, my suggestion is to add this as an option. Maybe using an optional 'normalized' argument. This would avoid the need for me to provide wrapper functions around PySoundFile's read and write functions in order to do the scaling that depends on the number of bits in the PCM format.

The scaling could be implemented in Numpy with the ldexp() function. I have not programmed in C++, but I think I saw that the ldexp() function in C++ also, which would be ideal for speed.

How about a 6-month release?

It's been just over 6 months since the release of v0.5.0. Since a v1.0 release looks months away, how about a new release?

I have a new installation of 64-bit Anaconda v2.0.1, which is based on Python 3.4.1 and includes PyCParser in the installer. Assuming I use conda to install CFFI from the Anaconda repository, is it an option to a new release to use the Windows installer for PySoundFile v0.5.0, and manually update the files from the GitHub master branch?

Push PySoundFile into Debian

As soon as 0.6.0 is released, I'll ask the Debian people to add it to their repository.

I guess this will eventually also become available for Ubuntu, but I don't know if/how we can speed this up.

Why I don't use PySoundFile

  1. I saw that issue #20 was closed yesterday without an option being added for users who need to interpret two's complement fixed-point WAV files in the same way that hardware manufacturers do. Erik de Castro Lopo's 32767 scale factor for 16-bit WAV files produces audible distortion, so it is usable for me. Therefore, I wrote my own code to translate between 64-bit float and fixed-point WAV files.

  2. For Windows, Python package binaries (such as libsndfile.dll) need to be compiled with the same MSVC compiler as is used to compile Python. Otherwise, the different runtime libraries for Python and packages can result in crashes and strange error conditions. As I understand, the PySoundFile team is using libsndfile.dll supplied by Erik de Castro Lopo rather than building from source with the MSVC version that matches each Python's version.

My work-around to problem 2) is to use pure Python packages:
a) https://github.com/WarrenWeckesser/wavio for 24-bit WAV files
b) scipy.io.wavfile for other WAV files

Did I make any false assumptions about problem 2)?

Can't read a SoundFile object for a wav file

It is impossible to read a wav file contained in a SoundFile object without specify a length, even though it is possible for the read function. Consider this (using the PySoundFile test file mono.wav):

>>> import soundfile as sf
>>>
>>> sf.read('mono.wav')
(array([  0.00000000e+00,   3.05175781e-05,   6.10351562e-05,
         -6.10351562e-05,  -3.05175781e-05]), 44100)
>>> sobj = sf.SoundFile('mono.wav', 'r')
>>> sobj.read()
ValueError                                Traceback (most recent call last)
<ipython-input-9-8c7a6773277d> in <module>()
----> 1 sobj.read()

/usr/lib/python3.5/site-packages/soundfile.py in read(self, frames, dtype, always_2d, fill_value, out)
    944         """
    945         if out is None:
--> 946             frames = self._check_frames(frames, fill_value)
    947             out = self._create_empty_array(frames, always_2d, dtype)
    948         else:

/usr/lib/python3.5/site-packages/soundfile.py in _check_frames(self, frames, fill_value)
   1328                 frames = remaining_frames
   1329         elif frames < 0:
-> 1330             raise ValueError("frames must be specified for non-seekable files")
   1331         return frames
   1332 

ValueError: frames must be specified for non-seekable files

I would think that if it is possible to call sf.read(fname) on a file, it would also be possible to call read() on an object for the same file.

Handle 'rw' files correctly in __init__()

The problem is that 'rw' files can be two quite different things: either existing files or new files.
If it's a new file, some arguments are required (like channels, subtype etc.), if it's an already existing file, those arguments are forbidden (except for 'RAW' files).

As far as I know, there is no function in libsndfile that allows to check before opening which of the two cases we're dealing with.

I see three options which could theoretically work:

  1. Use a function from the Python standard library to check if the file already exists
  2. Try to open the file with libsndfile. If there is an error, check if the reason are the required arguments. If there is no error, check if any of the forbidden arguments were given and raise an error.
  3. Split 'rw' mode into two distinct modes, one that only works for existing files and another one that only works for new files (or alternatively, it can also delete the existing file content first, like 'w+' mode does in Python's open() function)

Are there more options?

I'm currently leaning towards option 3, see also #60.

Writing OGG produces an empty file

I don't know if I'm doing something wrong, but this produces an OGG file with zero frames:

import soundfile as sf
import numpy as np
a = np.empty(100000)
sf.write(a, 'delme.ogg', 44100)

This doesn't seem to depend on my libsndfile binary, since the same error happens both on Debian Linux and on Windows XP 32bit.
To be sure, I compiled a little test program in C, which correctly writes a non-empty OGG file (this I tried only on Linux), so it cannot really be related to my installed version of libsndfile, or can it?

Any ideas what's wrong?

Can anybody write OGG files with PySoundFile?

Reading OGGs works perfectly, BTW ...
Writing other formats, too ...

Create test case for named pipes

This might be hard because the pipe has to be written to and read from simultaneously.
Probably this has to be done with threads.
Also, I don't know if/how this can be done in Windows.

See #68 for example usage of named pipes.

Investigate "full" set of tests

During our discussions, we frequently came to the conclusion that we wouldn't test some things, as they are implicitly tested by other test cases or unlikely to fail. This includes things like

  • Testing both the global functions and the SoundFile methods.
  • Extending tests to run every combination of stereo/mono and int/float test data
  • Running tests for more file formats (wav, flac, ogg, etc.).

Implementing these tests would multiply the number of tests, while not necesserily resulting in a much higher coverage of the Python code. It might however catch some combination of preconditions that our current tests don't catch.

Since these tests would take more time to run than our existing tests, it would probably be a good idea to not run them by default (not sure how to do that yet).

`python setup.py sdist` should not include the binaries

Currently, the wheels build fine, but the source distribution includes all binaries in pysoundfile_data. This really is not much of a problem for users, since there is a "source" wheel without binaries available on PyPI, but a "true" source distribution would be nice, too.

README example code is wrong

from pysoundfile import SoundFile

wave = SoundFile('existing_file.wav')
ogg  = SoundFile('new_file.ogg', samplerate=wave.samplerate,
                 channels=wave.channels, format=ogg_file,
                 mode=write_mode)

data = wave.read(1024)
while len(data) > 0:
    ogg.write(data)
    data = wave.read(1024) 

should be

 from pysoundfile import SoundFile,ogg_file,write_mode

wave = SoundFile('existing_file.wav')
ogg  = SoundFile('new_file.ogg', sample_rate=wave.sample_rate,
                 channels=wave.channels, format=ogg_file,
                 mode=write_mode)

data = wave.read(1024)
while len(data) > 0:
    ogg.write(data)
    data = wave.read(1024)

IndexError: tuple index out of range

I always get the error IndexError: tuple index out of range when trying to write. I cannot even run the example on the documentation. Do you know why it always skips the check 414 if data.ndim == 1: and fails at 417 channels = data.shape[1]

import soundfile as sf
data, samplerate = sf.read('piano.wav') # mono signal 
sf.write(data, 'new_file.ogg', samplerate=samplerate)

I tried on Ubuntu 14.04 and OSX 10.11

Rename seek() to seek_relative()?

This would seem more symmetrical, as there is also seek_absolute().

Using seek() without being explicit may have lead to bugs like these two:

https://github.com/bastibe/PySoundFile/blob/cd9fb6a287731288b43153cf243245943e88b54b/pysoundfile.py#L445

https://github.com/bastibe/PySoundFile/blob/cd9fb6a287731288b43153cf243245943e88b54b/pysoundfile.py#L471

In both cases there should be seek_absolute() instead of seek(), which is correctly used at the third place:

https://github.com/bastibe/PySoundFile/blob/cd9fb6a287731288b43153cf243245943e88b54b/pysoundfile.py#L423

BTW, this 3-fold repetition calls for refactoring, probably using a Context Manager ...?

Add the number of frames in info

I need to get the number of frames in a file before calling blocks.
That would be nice to either have info.duration as a float or add a num_frames attribute.

2-dimensional indexing on SoundFile objects?

As mentioned in #12, it would be nice to select certain channels by indexing.

I wanted to create a pull request, but I have still a few questions which make me think maybe it's not such a good idea after all.

Should data which was read before be somehow cached?

If a channel is requested from an interleaved file, all channels have to be read anyway, so if another channel is requested afterwards it would make sense to re-use the data.
OTOH, if only one of many channels is ever needed, the memory for all other channels will not be freed up.

Should indexing be possible when writing files?
In this case the affected frames would have to be read first, only to overwrite a part of them afterwards.
But I guess this doesn't make sense in any case ...

All-in-all, maybe the current read() and write() methods are enough, and indexing isn't needed at all?

Maybe instead, to be able to be as succinct as possible, there should be some convenience functions à la Octave's wavread() (to avoid explicit creation of the SoundFile object)?
Because then, indexing could be used like this:

from pysoundfile import readfile
wave = readfile('existing_file.wav')[:, 1]

And if someone doesn't want to read the whole file, they'll have to fall back to read():

from pysoundfile import SoundFile
with SoundFile('existing_file.wav') as f:
    f.seek_absolute(22050)
    wave = f.read(44100)[:, 1]

I guess this wouldn't be much worse than:

from pysoundfile import SoundFile
wave = SoundFile('existing_file.wav')[22050:66150, 1]

(Although latter may have the __del__() problem discussed in #13)

Return one-dimensional arrays for mono files?

I guess this is a controversial topic for Matlab converts because there are no one-dimensional matrices in Matlab ...

Currently, when reading a mono file, a two-dimensional array with shape == (frames, 1) is returned.

Wouldn't it be more NumPythonic to return a one-dimensional array with shape == (frames,)?

latency issues

Lets say s is a Stream() that has exactly 2.000s worth of data to be played (e.g., 96000 samples at 48000Hz). If I time the duration from just prior to s.start() to right after s.is_active() goes false, I usually get an elapsed time of 2.000 to 2.003 s -- which is great.

However, the very first time I run a script like this, it will typically take ~2.52s. If I run it again right away it will be ~2.0s. I can also get a similar ~0.5s longer apparent duration if I play sound s, time.sleep(60), and then start a second 2.000s sound. Here again the apparent duration of the second one will be 0.5s longer than if I only time.sleep(20). In all cases, the sound seems to be the same sound (the pitch would change if samples were stretched out over 2.5s instead of 2.0s).

I am assuming that the extra 0.5s is all just latency to start, i.e., silence prior to the onset / production of any sound, but am not completely certain.

So it seems like maybe portaudio or the hardware soundcard has a "wake up" time of 0.5s, and falls asleep if there's no sound-card (?) activity for about a minute.

Maybe this issue is out of scope for pyscoundcard, but it seems like it might be having a big effect on the achievable latency. People will at least want to know about it. Maybe something like a "stay awake" minimal sound could be sent to the sound card (e.g., a couple samples that are effectively zero in volume).

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.