Library / SDK
bastibe/SoundCard avatar
bastibe/SoundCard

SoundCard plays and records audio without a compiled CPython extension

A Pure-Python Real-Time Audio Library

767 stars77 forksPythonBSD-3-Clause

At a glance

What is it?
SoundCard is a BSD licensed Python library that talks to PulseAudio, CoreAudio and WASAPI through CFFI, giving the same recording and playback interface on Linux, macOS and Windows at the cost of an OS level latency negotiation you have to make explicitly.
Who is it for?
Adopt SoundCard if you need multi-channel recording and playback from a Python program and want one interface across PulseAudio, CoreAudio and WASAPI without compiling anything. Do not adopt it if you need a hard real-time guarantee, since the documentation says the total latency depends on how the operating system handles the buffers and can be significantly higher than what you request.
Can I use it commercially?
Yes. BSD-3-Clause is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 128 days ago.
What is it written in?
Mainly Python, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.

Editorial analysis

CFFI instead of a compiled extension module

SoundCard is a library for playing and recording audio without resorting to a CPython extension, and that constraint is the whole design. Rather than shipping a compiled C module that has to be rebuilt for every Python version and platform, it is implemented with CFFI and the native audio libraries of Linux, Windows and macOS. The backing APIs are named in the documentation: PulseAudio on Linux, CoreAudio on macOS and WASAPI on Windows. That gives the project a portability property that matters more in a research or prototyping setting than in production: the install is a pure Python wheel with numpy and cffi as the only requirements, and the version constraint in the package metadata is python_requires of 3.5 or newer, with numpy at 1.11 or newer. The programming interface is identical across the three platforms, so the same script enumerates devices and records on any of them, but the documentation is careful to add that sound card naming schemes and default block sizes vary between devices and platforms, so code that hard-codes a device name or an assumed latency will break somewhere.

Device discovery by exact name or by fuzzy substring

The tutorial starts where every audio program starts, by finding the hardware. Four functions cover the common cases, and they return Speaker and Microphone objects:

python
import soundcard as sc

# get a list of all speakers:
speakers = sc.all_speakers()
# get the current default speaker on your system:
default_speaker = sc.default_speaker()
# get a list of all microphones:
mics = sc.all_microphones()
# get the current default microphone on your system:
default_mic = sc.default_microphone()

Beyond the defaults there is a search function, and the documentation shows both an exact substring and a fuzzy form of it, which is the practical way to find an interface device whose full name you do not want to hard-code:

python
# search for a sound card by substring:
>>> sc.get_speaker('Scarlett')
<Speaker Focusrite Scarlett 2i2 (2 channels)>
>>> one_mic = sc.get_microphone('Scarlett')
<Microphone Focusrite Scalett 2i2 (2 channels)>
# fuzzy-search to get the same results:
one_speaker = sc.get_speaker('FS2i2')
one_mic = sc.get_microphone('FS2i2')

The repr of a device includes its channel count, which is a useful hint about how you will have to handle it later. One thing to know before writing code against this API is that the data passing in and out of these objects is frames by channels numpy arrays, and the documentation states that the input and output data are scaled to 0dBFS, so the values must be restricted between -1 and 1 to avoid clipping.

One-shot capture and continuous recorder and player objects

Recording and playback come in two shapes. The first is a single call, which the documentation uses to record and play back one second of audio and then normalise the data before sending it to the speaker:

python
# record and play back one second of audio:
data = default_mic.record(samplerate=48000, numframes=48000)
# normalized playback
default_speaker.play(data/numpy.max(numpy.abs(data)), samplerate=48000)

The second is a pair of context managers, a Recorder and a Player, which is what a continuous application needs:

python
# alternatively, get a `Recorder` and `Player` object
# and play or record continuously:
with default_mic.recorder(samplerate=48000) as mic, \
      default_speaker.player(samplerate=48000) as sp:
    for _ in range(100):
        data = mic.record(numframes=1024)
        sp.play(data)

The structure of that loop is the shape of a real time audio application: fixed size blocks of 1024 frames moved from the input to the output. The samplerate argument appears in both directions, and it is worth being deliberate about it, because a mismatch between the rate you request and the rate the device runs at is a common source of recordings that play back at the wrong speed.

Latency is a request to the operating system, not a guarantee

The latency section is the most useful part of the documentation and the most sobering. By default SoundCard records and plays at the operating system's default configuration, and on laptops that configuration can have extreme latencies, up to multiple seconds. The mechanism for improvement is a blocksize passed to player or recorder, which tells the operating system the desired latency and lets it try to honour that request as best it can. On Windows and WASAPI, exclusive_mode=True might also help, and the documentation marks that as currently experimental. Two more knobs matter. The record function buffers output up to the requested numframes, so for optimal latency numframes should be significantly lower than the blocksize, maybe by a factor of two or four. Setting numframes=None returns whatever audio data is available right now without any buffering, at the cost of receiving a different number of frames each time. With these settings, block sizes of 256 samples or ten milliseconds are usually no problem, but the documentation is explicit that the total latency of playback and recording depends on how the operating system handles those buffers and might be significantly higher. On Linux there is also a PulseAudio side fix, editing the default configuration to replace the module line with a version that disables the time scheduler:

bash
    load-module module-udev-detect tsched=0

and then restarting the sound server with pulseaudio -k. That is the sort of change that belongs in a setup document rather than in a library, which is a fair criticism of any wrapper that has to work around a host configuration.

Channel maps mean physical channels on Windows and macOS, logical positions on Linux

Professional interfaces often have more channels than you want to touch, and SoundCard lets you select a subset by passing a channel map, which is a list of channel specifiers. The index of each specifier in the list is the channel index in the numpy array the library hands you, so a map both selects and reorders:

python
# record one second of audio from backend channels 0 to 3:
data = default_mic.record(samplerate=48000, channels=[0, 1, 2, 3], numframes=48000)

# play back the recorded audio in reverse channel order:
default_speaker.play(data=data, channels=[3, 2, 1, 0], samplerate=48000)

The trap is that the meaning of a specifier depends on the backend. For WASAPI on Windows and CoreAudio on macOS the indices refer to the physical output channels of the sound device in use. For the PulseAudio backend on Linux the specifiers refer to logical channel positions instead of physical hardware channels, and those positions follow the PulseAudio channel map definition, with a dictionary of all possible positions and channel indices provided by the library because the mapping is not obvious. In other words the same list of numbers means two different things on two different platforms, and the same microphone can be stereo on one machine and a surround device on another.

A hobby project with a permissive licence and a slow release history

The licence is BSD 3-clause, held by Bastian Bechtold, which is about as permissive as a licence gets and suits a library you might vendor into a tool. The dependencies are equally light: numpy and cffi, with the package shipping its own .py.h interface headers and a PyInstaller hook for people who freeze their scripts. The maintenance picture is the part to weigh. The documentation says plainly that SoundCard is still in development, that all major features work on all platforms but that a few known issues still need to be fixed, and that the project is a hobby project developed for free in spare time, with no guarantee of a timely response to issues. The release history matches that description: 0.4.4 in June 2025, 0.4.5 in September 2025 and 0.4.6 in April 2026, with the last push on 2026-06-17. The documentation is published at soundcard.readthedocs.io and the repository carries a test file, a Pipfile and a doc directory. So the code is small, readable and permissively licensed, and the risk is not that it is wrong, it is that when something breaks on a new platform you are the person who fixes it.

Editorial conclusion

Adopt SoundCard if you need multi-channel recording and playback from a Python program and want one interface across PulseAudio, CoreAudio and WASAPI without compiling anything. Do not adopt it if you need a hard real-time guarantee, since the documentation says the total latency depends on how the operating system handles the buffers and can be significantly higher than what you request. Before you rely on it, measure the round trip on the machine you actually target, decide whether the 0dBFS scaling with its clipping risk suits your pipeline, and note that the project is a hobby effort, with the last push on 2026-06-17 and a release history that moves when the maintainer has time.

Frequently asked questions

Does SoundCard require a compiled CPython extension?

No. The library is implemented with CFFI and the native audio libraries of Linux, Windows and macOS, and its only install requirements are numpy at 1.11 or newer and cffi, with python_requires of 3.5 or newer.

Which audio backends does SoundCard support?

Linux through PulseAudio, macOS through CoreAudio and Windows through WASAPI. The programming interface is identical across platforms, but device naming schemes and default block sizes vary between devices and platforms.

How do I reduce recording latency in SoundCard?

Pass a blocksize to player or recorder, keep numframes significantly lower than that blocksize, perhaps by a factor of two or four, and use numframes=None to return whatever data is available without buffering. On Windows and WASAPI, exclusive_mode=True might also help, and the documentation marks that option as currently experimental.

What scale does SoundCard use for audio data?

The input and output data are scaled to 0dBFS, so the documentation says to restrict all data between -1 and 1 to avoid clipping. The data passed to and from the Speaker and Microphone objects are frames by channels numpy arrays.

Official sources

  1. bastibe/SoundCard on GitHub
  2. License: BSD-3-Clause
  3. Project website
  4. README
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/bastibe-soundcard.svg)](https://hysenlabs.com/projects/bastibe-soundcard)

Was this useful?