# soundfile: sampled audio files as NumPy arrays, with libsndfile doing the codec work

> soundfile is a Python module that reads and writes many sampled sound formats by wrapping libsndfile through CFFI, with data handled as NumPy arrays. It ships wheels that bundle libsndfile, and it has changed its defaults and argument orders more than once.

**bastibe/python-soundfile** — SoundFile is an audio library based on libsndfile, CFFI, and NumPy

- Repository: https://github.com/bastibe/python-soundfile
- Stars: 868 · Forks: 131
- Language: Python
- License: BSD-3-Clause
- Published: 2026-10-20 · Updated: 2026-10-20 · Language: en
- Canonical page: https://hysenlabs.com/projects/bastibe-python-soundfile

## Three layers sit between your script and the file format

soundfile is a thin Python module with three moving parts. libsndfile is the C library that actually reads and writes the file formats, and it is described in the README as a free, cross-platform, open-source library under the LGPL that runs on Windows, OS X and Unix. CFFI is the foreign function interface that lets Python call that C code. NumPy is what the data becomes once it is inside Python.

That division of labour is the whole design. You never touch libsndfile's C API directly, the module handles the call boundary, and what comes back is an ordinary NumPy array rather than a buffer you have to interpret. The project says as much in its one-line description: an audio library based on libsndfile, CFFI and NumPy.

The format coverage follows from libsndfile. The README states that the module can open all file formats libsndfile supports, naming WAV, FLAC, OGG and MAT as examples, and points to the libsndfile features page for the authoritative list. Note the qualifier: the set of formats is libsndfile's set, not something this project curates.

## Read and write in two lines, then change the shape of the data

The basic call pattern is short enough to fit in two statements. Reading gives you the samples and the sample rate; writing takes the file name, the data and the rate back:

```python
    import soundfile as sf

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

The same pair of functions covers conversion between formats, since the source format is decided by the file you read and the target by the name you write.

Long files do not have to be loaded whole. soundfile.blocks() reads a file in short, optionally overlapping blocks, and the README's example turns that into a running signal level measurement:

```python
   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)]
```

The overlap argument is what makes block level analysis meaningful rather than lossy at the edges, so a block size and an overlap are choices you have to make deliberately.

## A SoundFile object keeps the file open and indexes by frames

For work that needs random access, files can be opened as SoundFile objects. Each one carries a specific sample rate, a data format and a number of channels, and the file stays open for as long as the object exists. The README is explicit that you should close it yourself with SoundFile.close() or by using the context manager, rather than waiting for garbage collection.

The pattern it shows edits a file in place, which is the case where the explicit close matters most:

```python
   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)
```

The 'r+' mode is what allows writing back to an open file, and the seek to the recorded position is what makes the in-place edit work at all.

All data access uses frames as the index. A frame is one discrete time step in the sound file, and every frame holds as many samples as the file has channels, so a stereo file's frame contains two samples. The README also has a RAW Files section, indicating that the module reads and writes headerless raw audio, though the section is cut short in the documentation available here.

## The wheel bundles libsndfile, and 0.12 changed which copy wins

Installation is one command in a modern Python:

```bash
pip install soundfile
```

That command also installs a current version of libsndfile on Windows (64/32/ARM64), OS X (Intel/ARM) and Linux (64/AARCH64), because the platform-specific wheels carry the library inside them. If you install the source module instead, you have to install libsndfile yourself with your distribution's package manager, and the README's example uses the apt package manager to install libsndfile1. On an unusual platform or an older Python, NumPy and CFFI may need to be installed separately, and the README points at the Anaconda package manager as an example.

The load order is the detail that bites during upgrades. As of 0.12.0, the packaged libsndfile in the platform-specific wheels is tried before any system-provided libsndfile. If you would rather use the system copy, the README says to install the source package or source wheel instead. In other words, the same pip command can give you two different libraries depending on the wheel, and the difference is decided by the load order rather than by anything in your code.

The CFFI note in the README is also dated: it states that CFFI is supported for CPython 2.6+, 3.x and PyPy 2.0+.

## A one megabyte wheel is the sign that libsndfile came along

soundfile itself contains no compiled code, so it can be bundled into a wheel with the usual command:

```bash
python setup.py bdist_wheel
```

Building a binary wheel that contains libsndfile takes one extra step: check out and update the _soundfile_data submodule first. The size of the resulting wheel is the check. If it is around one megabyte, a matching libsndfile has been bundled; without libsndfile it is around 25 KB. That difference is large enough to see at a glance in a build pipeline.

The repository ships the machinery to do this for every supported platform. build_wheels.py runs the wheel build for each platform the project has precompiled libsndfile for, and setup.py works out which library file to include from the target platform and architecture, producing library names such as libsndfile_x86_64.dylib, libsndfile_arm64.dll and libsndfile_x86_64.so. It also defines a custom wheel command described as creating OS-dependent but Python-independent wheels, so the same binary serves several Python versions on one platform.

Cross packaging is possible through two environment variables, PYSOUNDFILE_PLATFORM and PYSOUNDFILE_ARCHITECTURE, which override the detected platform and architecture. The wheel tags it produces include macosx_10_9_x86_64, macosx_11_0_arm64 and win_arm64.

## Errors come in two flavours, and the libsndfile one carries a code

The error design is worth knowing because it separates your mistakes from the library's. API usage errors raise the usual ValueError or TypeError, so a wrong argument type or a bad value is caught as a Python error in the normal way. Everything else is wrapped in SoundFileError, which the README notes used to be RuntimeError.

Within that, a LibsndfileError subclass is raised when libsndfile itself reports the error. The exception object then carries two attributes: the libsndfile internal error code in LibsndfileError.code, and the raw libsndfile message in LibsndfileError.error_string. If you need to distinguish a truncated file from an unsupported codec, that pair is what you branch on.

The change from RuntimeError to SoundFileError is one of a series of API moves the project has made, and it is the kind of thing that catches you in an except clause during an upgrade.

## The import name changed, the argument order changed, the defaults changed

The README keeps a Breaking Changes section, which is unusual and useful. The largest break was the import name: the module was called pysoundfile and became soundfile in version 0.7. Version 0.6 cleaned up many small inconsistencies, particularly in the ordering and naming of function arguments, and removed the indexing interface.

Two later changes affect code that looks stable. In 0.8.0 the default value of always_2d changed from True to False, which changes the shape of the array you get back from a read, and the argument order of write changed from write(data, file, ...) to write(file, data, ...). In 0.9.0 the ctype arguments of the buffer_* methods were renamed to dtype, using the NumPy dtype notation, with the old names still working but officially deprecated.

That is four separate breaking changes in the argument surface since the early versions, and each one can break a script that looks untouched. Read the Breaking Changes section of the README when you upgrade rather than discovering it in production. The library documentation is hosted on Read the Docs, and the repository keeps its own doc/ directory and a tests/ directory, so behaviour is documented in two places.

## Where soundfile is the wrong tool

soundfile handles sampled sound files. It does not play audio, it does not capture from a live input device, and it does not stream. If your task is playback or recording in real time, this module is the wrong layer regardless of how well it reads the files.

The second limit is libsndfile's format list, and it is worth being concrete about one entry in it. The README notes a Known Issues section for writing OGG files, which means at least one format in the supported set has a caveat on the write side that you should look up before shipping it. The same reasoning applies to any exotic codec: support for reading a format does not promise a clean write path.

The third limit is the licensing surface around the bundled library. The module itself is BSD 3-Clause, but the library it can ship inside the wheel is LGPL, and the repository keeps a separate licensing/ directory with license_notes.md alongside the bundled COPYING file. Read those notes if you redistribute a wheel that contains libsndfile. This is not legal advice, just a pointer to the files that hold the terms.

Finally, the alternative is not always this module. If you need full control over the C API, or a format-specific feature libsndfile offers that this wrapper does not expose, calling libsndfile yourself through CFFI is the direct approach. The difference is that you then own the buffer handling, the conversion to NumPy, the error mapping and the library loading yourself, which is exactly the work this module already does.

## Conclusion

soundfile suits Python audio work that needs many file formats, block wise processing of long recordings, or direct NumPy arrays, and the bundled wheel means you usually avoid installing libsndfile yourself. It does not suit real time playback, streaming of live input, or formats libsndfile does not handle, and you should read the Known Issues section the README links for OGG writing before relying on it. Before adopting it, check which libsndfile your install actually loads, since version 0.12 changed the order to prefer the packaged copy over the system one.

## FAQ

### What is the soundfile module and what does it do?

It is a Python audio library that reads and writes many sampled sound file formats by wrapping libsndfile through CFFI, with audio data represented as NumPy arrays. Formats include WAV, FLAC, OGG and MAT, following whatever libsndfile supports.

### How do I install soundfile and do I need libsndfile separately?

Run pip install soundfile. On Windows, OS X and Linux the platform-specific wheels also install a current libsndfile, but if you install the source module you need libsndfile from your distribution's package manager yourself.

### How do I read a large audio file without loading it all?

Use soundfile.blocks() with a blocksize and an overlap, which reads the file in short, optionally overlapping blocks. The README's example uses it to compute an RMS level per block.

### Which libsndfile does soundfile load?

Since version 0.12.0 the libsndfile packaged in the platform-specific wheels is tried before any system-provided libsndfile. The README says to install the source package or source wheel if you prefer the system-provided library.

## Sources

- [bastibe/python-soundfile on GitHub](https://github.com/bastibe/python-soundfile)
- [Issues](https://github.com/bastibe/python-soundfile/issues)
- [License: BSD-3-Clause](https://github.com/bastibe/python-soundfile/blob/master/LICENSE)
- [README](https://github.com/bastibe/python-soundfile/blob/master/README.md)
- [Releases](https://github.com/bastibe/python-soundfile/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/bastibe-python-soundfile
