🎵 sound_rs
sound_rs is a fast, Rust-powered audio library for Python that I built and maintain — decoding with symphonia (plus minimp3 for MP3), resampling with rubato, output through cpal.
Documentation: neksand.github.io/sound_rs — guides,
API reference, the soundrs command and the benchmark methodology.
Latest release: 0.3.0 — reliability, datasets, recording, queues. Build report: docs/reports/0.3.0-report.md.
🚀 Features
- Formats: WAV, MP3, FLAC, OGG/Vorbis, M4A, AAC — any bit depth, including 24-bit
- Streaming playback: a two-hour file costs ~4 MB of RAM, not gigabytes, and the first sound arrives in ~11 ms instead of after the whole decode
Player: one output stream stays open —play,pause,resume,seek,stop,volume,position,duration- Decode to NumPy:
load(path, sample_rate=, mono=, offset=, duration=)— resample to 16 kHz mono in one call, or decode only a window - Parallel decode:
load_many(paths, sample_rate=, mono=)uses every core, GIL released - Recording:
Recorder()captures from the microphone to a float32 array - Queues and live input:
Player.enqueue(path)chains tracks;begin_feed/feedpush samples from Python with backpressure - Gapless (MP3, OGG, AAC/MP4): encoder delay and padding are trimmed, so decoded audio lines up with the original
- Fast metadata:
get_audio_inforeads the container header, ~1 ms regardless of file length - Resampling: rubato's FFT resampler with a proper anti-aliasing filter
- Device-native output: the stream opens in whatever sample format the device wants (f32, f64, i16, i32, u8, u16)
- Errors: everything raises
sound_rs.SoundRsErrorwith a stable.code(io,format,device,stalled,unsupported,usage) - Tested guarantees: no panics on corrupt input (mutation fuzzing), deterministic decode (PCM hashes in CI), underrun reporting. The full list is on the docs site.
- No ffmpeg, no system libraries: MP3, FLAC, OGG, AAC and WAV are decoded inside
the wheel;
pip install sound_rsneeds nothing else - Typed: ships
sound_rs.pyiandpy.typed, so editors autocomplete everything
🧠 Installation
pip install sound_rs
Wheels are built with the stable ABI (abi3-py310): one per platform — Linux
x86_64 and aarch64, Windows amd64, macOS arm64 and Intel — each covering Python
3.10 and newer. Other platforms build from source.
⌨️ Command line
Installing the package also installs soundrs, so you can check the setup and read the docs
without opening Python:
soundrs --help # commands and the documentation link
soundrs devices # output devices, default marked with *
soundrs info a.flac # rate, channels, duration from the header
soundrs play a.flac --volume 0.5
soundrs docs # prints https://neksand.github.io/sound_rs/
Exit codes: 0 success, 1 runtime failure, 2 bad usage.
💻 Usage
One-shot playback
import sound_rs
sound_rs.play_audio("sound.wav") # blocks, GIL released
sound_rs.play_audio("sound.mp3", device="MacBook Pro Speakers")
The player
import sound_rs
with sound_rs.Player() as player:
player.play_file("music.flac") # returns immediately, streams from disk
player.volume = 0.7
player.seek(42.0)
player.pause()
player.resume()
print(player.position, player.duration)
player.wait() # block until it ends
sound_rs.list_devices() # [(0, "MacBook Pro Speakers", True), ...]
Record
import sound_rs
with sound_rs.Recorder() as recorder:
audio, rate = recorder.record(5.0) # float32, (frames, channels)
Queue and live input
import numpy as np
import sound_rs
with sound_rs.Player() as player:
player.play_file("a.flac")
player.enqueue("b.mp3") # plays next, own rate and layout
player.wait()
player = sound_rs.Player()
player.begin_feed(48000)
player.feed(np.zeros(4800, dtype=np.float32)) # blocks when the buffer is full
player.end_feed()
player.wait()
Inspect and decode
sample_rate, channels, duration = sound_rs.get_audio_info("music.mp3") # no decode
audio, rate = sound_rs.load("music.flac") # (frames, channels) float32
audio, rate = sound_rs.load("music.flac", sample_rate=16000, mono=True) # dataset shape
pairs = sound_rs.load_many(["a.flac", "b.flac", "c.flac"]) # parallel
From a NumPy array
import numpy as np
rate = 44100
t = np.linspace(0, 2.0, rate * 2, endpoint=False, dtype=np.float32)
sound_rs.play_array(np.sin(2 * np.pi * 440 * t), rate) # mono, 1D
sound_rs.play_array(np.stack([left, right]), rate) # 2D, channels first
sound_rs.play_sine_wave(440.0, 1.0, 44100)
🧩 Benchmark
Every measurement runs in its own process: peak RSS is process-wide, so timing and memory cannot share one. Median and p95 over repeated runs, competitors run side by side on the same files.
python bench/run.py # sound_rs only
python bench/run.py --compare # + soundfile / sounddevice
python bench/run.py --reps 9
Hardware I measured on: Apple M5 Pro, macOS 26.6.2, Python 3.14.7, release
build (lto = "fat", opt-level = 3). Competitors: libsndfile 1.2
(soundfile 0.14), PortAudio (sounddevice). Corpus: 3 s and 10 min stereo
44.1 kHz, generated by the harness.
Scoreboard — every axis in this section, side by side:
| axis | sound_rs | best competitor | ratio |
|---|---|---|---|
| import | 1.1 ms | 31.8 ms (soundfile) | 29× |
| metadata (60 s file) | 1.2 ms | 31.7 ms (soundfile) | 26× |
| decode WAV | 130 ms | 157 ms | 1.2× |
| decode FLAC | 265 ms | 455 ms | 1.7× |
| decode MP3 | 241 ms | 268 ms | 1.1× |
| decode AAC | 349 ms | not supported | — |
| time to first sound (10 min) | 11 ms | 302 ms | 27× |
| peak RAM (10 min file) | 43 MB | 255 MB | 6× |
| playback overhead | 30 ms | 241 ms | 8× |
| batch decode (2 files) | 177 ms | 299 ms | 1.7× |
Import time (cold process):
| time | peak RSS | |
|---|---|---|
| sound_rs | 1.1 ms | 25 MB |
| soundfile | 31.8 ms | 34 MB |
| sounddevice | 75.3 ms | 32 MB |
Metadata (60 s file): sound_rs 1.2 ms, soundfile 31.7 ms.
Decode 60 s stereo, median / p95:
| format | sound_rs | libsndfile | result | PCM check |
|---|---|---|---|---|
| WAV | 131 / 160 ms | 155 ms | 1.2× faster | identical |
| FLAC | 265 / 267 ms | 465 ms | 1.8× faster | identical |
| MP3 | 241 / 249 ms | 268 ms | 1.1× faster | 1.6e-05, 47-sample offset |
| M4A (AAC) | 361 / 365 ms | unsupported | sound_rs only | matches ffmpeg to 8e-08 |
Streaming a 10-minute FLAC — time to the first sound and peak memory:
| first sound | peak RSS | |
|---|---|---|
sound_rs Player |
11.3 ms | 43 MB |
| soundfile + sounddevice | 302 ms | 256 MB |
Playback of a 3 s tone — overhead over the track duration:
| overhead | |
|---|---|
sound_rs Player (stream kept open) |
+30 ms |
sounddevice.play (opens a stream per call) |
+241 ms |
sound_rs.play_array (one-shot, opens a stream) |
+182 ms |
Batch decode two FLAC files: load_many 272 ms vs a soundfile loop 448 ms.
What the numbers do not hide:
- MP3 output is offset by 47 samples (~1 ms) versus libsndfile and ffmpeg. Sample values agree to 1.6e-05 once aligned; the offset comes from how the decoder delay is trimmed, and the length matches exactly.
- WAV decode is a memcpy race with libsndfile; the gap there is small by nature, the real wins are FLAC, metadata, memory and latency.
- AAC decoding is slower than everything else — it is a newer, less tuned decoder — but libsndfile cannot read AAC at all.
🛠️ Roadmap
| Step | Feature | Description |
|---|---|---|
| ✅ 1 | Playback + formats | WAV, MP3, FLAC, OGG, M4A, 24-bit |
| ✅ 2 | NumPy integration | 1D/2D arrays, load, load_many |
| ✅ 3 | Player |
Persistent stream: pause, seek, volume, position |
| ✅ 4 | Streaming decode | Constant memory, ~11 ms to first sound |
| ✅ 5 | Device selection | list_devices, list_input_devices, device= |
| ✅ 6 | Recording | Recorder: microphone to float32 array |
| ✅ 7 | Queues and live input | enqueue, begin_feed/feed |
| ✅ 8 | Datasets | load(..., sample_rate=16000, mono=True), parallel load_many |
| 🔜 9 | Hot-swap on disconnect | Reopen automatically instead of raising stalled |
| 🔜 10 | Effects | Basic gain/fade/mix utilities |
🧪 Development
cargo test # decode, gapless, seek, fuzzing, properties, player
cargo clippy --all-targets -- -D warnings
maturin develop --release # build into your venv
python tests/smoke.py # end-to-end decode + API checks
SOUND_RS_PLAY=1 python tests/smoke.py # also plays (needs an audio device)
python tests/decode_regression.py # PCM hashes; must not change
python docs/check_api.py # every public name is documented
python bench/run.py --compare --report bench/REPORT.md
python3 docs/_build.py # rebuild the documentation site
⚙️ Built With
- 🦀 Rust — performance and safety
- 🔗 PyO3 — Python bridge
- 🧰 Maturin — building and publishing
- 🔊 cpal, symphonia, minimp3, rubato — output, decoding, resampling
👨💻 Author
Nurassyl Mussainov Kazakhstan 🇰🇿 Python / Rust Developer
🔗 GitHub: Neksand 📦 PyPI: sound_rs
📜 License
Licensed under the MIT License — free for personal and commercial use.
Metadata
Release files for sound-rs 0.3.1
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| sound_rs-0.3.1.tar.gz | 222.5 kB | Without provenance | Details for sound_rs-0.3.1.tar.gz |
Built distributions (wheels)
| File | Actions Reset | |||
|---|---|---|---|---|
| sound_rs-0.3.1-cp310-abi3-win_amd64.whl | CPython 3.10 | abi3 | Windows x86-64 | Details for sound_rs-0.3.1-cp310-abi3-win_amd64.whl |
| sound_rs-0.3.1-cp310-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl | CPython 3.10 | abi3 | Linux glibc 2.17+ x86-64 | Details for sound_rs-0.3.1-cp310-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl |
| sound_rs-0.3.1-cp310-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl | CPython 3.10 | abi3 | Linux glibc 2.17+ ARM64 | Details for sound_rs-0.3.1-cp310-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl |
| sound_rs-0.3.1-cp310-abi3-macosx_11_0_arm64.whl | CPython 3.10 | abi3 | macOS 11.0+ ARM64 | Details for sound_rs-0.3.1-cp310-abi3-macosx_11_0_arm64.whl |
| sound_rs-0.3.1-cp310-abi3-macosx_10_12_x86_64.whl | CPython 3.10 | abi3 | macOS 10.12+ x86-64 | Details for sound_rs-0.3.1-cp310-abi3-macosx_10_12_x86_64.whl |
Total release size: 7.7 MB
Release files / sound_rs-0.3.1.tar.gz
| Download URL | sound_rs-0.3.1.tar.gz |
|---|---|
| Size | 222.5 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
b034a9b6ecf011f964be98e0e90e97eabd46dd199afc4992be09ec4ab439aa0a
|
|
BLAKE2b-256 checksum How to use checksums |
cb242ca2febd2f622bd6cb12de0ae8c57030925e7a7467ce14db6d03579a1d4a
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
Release files / sound_rs-0.3.1-cp310-abi3-win_amd64.whl
| Download URL | sound_rs-0.3.1-cp310-abi3-win_amd64.whl |
|---|---|
| Size | 1.4 MB |
| Tags | CPython 3.10 Windows x86-64 abi3 |
|
SHA-256 checksum How to use checksums |
f6850ec586ae3f3987f457ef19bf666d12ab19775c1f3bca3e61c8b61a4a4a68
|
|
BLAKE2b-256 checksum How to use checksums |
05b0b2c01ed901664ab1a996db84a346c890603cb21819950437d93c7effd57e
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
Release files / sound_rs-0.3.1-cp310-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl
| Download URL | sound_rs-0.3.1-cp310-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl |
|---|---|
| Size | 1.9 MB |
| Tags | CPython 3.10 Linux glibc 2.17+ x86-64 abi3 |
|
SHA-256 checksum How to use checksums |
4e1314ea84cac08155c79c942bb2ac1b141893f91f857f4f9730ba4f644c8016
|
|
BLAKE2b-256 checksum How to use checksums |
6c53cf6cf77eb10702d6d548d172972ce63d504dd845d517e3df5518bcc13bbc
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
Release files / sound_rs-0.3.1-cp310-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl
| Download URL | sound_rs-0.3.1-cp310-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl |
|---|---|
| Size | 1.6 MB |
| Tags | CPython 3.10 Linux glibc 2.17+ ARM64 abi3 |
|
SHA-256 checksum How to use checksums |
4442036a28bf6823bcf2843a91a5341fc80afb39e0e8612d0bcda078dce5dc44
|
|
BLAKE2b-256 checksum How to use checksums |
ccb01f5ddfd626e27ad1bec186eff53b305a0f0b69bbccf718588024e720a0d6
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
Release files / sound_rs-0.3.1-cp310-abi3-macosx_11_0_arm64.whl
| Download URL | sound_rs-0.3.1-cp310-abi3-macosx_11_0_arm64.whl |
|---|---|
| Size | 1.2 MB |
| Tags | CPython 3.10 abi3 macOS 11.0+ ARM64 |
|
SHA-256 checksum How to use checksums |
e6e970dcad61fe160a62559ba55bcbd7a850437f8eb9bf72efebd80a0eee734b
|
|
BLAKE2b-256 checksum How to use checksums |
d378248d402d9e1cfde3649939b4ccf869e1b7c845c3dc05154e119eb49c68b6
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
Release files / sound_rs-0.3.1-cp310-abi3-macosx_10_12_x86_64.whl
| Download URL | sound_rs-0.3.1-cp310-abi3-macosx_10_12_x86_64.whl |
|---|---|
| Size | 1.4 MB |
| Tags | CPython 3.10 abi3 macOS 10.12+ x86-64 |
|
SHA-256 checksum How to use checksums |
7912eaf4c21bb7702f26071cc6909dcb5edc3bd2217a98d085daa3ba0ad426c2
|
|
BLAKE2b-256 checksum How to use checksums |
d38b3e049ada90aea8028c94dbe8c17511919a8d395b39a52680006e2f3d2f59
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
Security for sound-rs 0.3.1
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.