Metadata-Version: 2.4
Name: sound_rs
Version: 0.2.1
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: MacOS
Classifier: Operating System :: Microsoft :: Windows
Classifier: Operating System :: POSIX :: Linux
Classifier: Programming Language :: Python :: 3 :: Only
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Programming Language :: Python :: 3.14
Classifier: Programming Language :: Python :: Implementation :: CPython
Classifier: Programming Language :: Rust
Classifier: Topic :: Multimedia :: Sound/Audio
Classifier: Topic :: Multimedia :: Sound/Audio :: Players
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Classifier: Typing :: Typed
Requires-Dist: numpy>=1.20 ; extra == 'numpy'
Provides-Extra: numpy
License-File: LICENSE
Summary: Fast Rust audio for Python: play and stream MP3, FLAC, WAV, OGG and M4A without ffmpeg
Keywords: audio,playback,player,streaming,mp3,flac,wav,aac,m4a,ogg,numpy,resampling,rust,pyo3,sound,dsp
License: MIT
Requires-Python: >=3.10
Description-Content-Type: text/markdown; charset=UTF-8; variant=GFM
Project-URL: Changelog, https://github.com/Neksand/sound_rs/releases
Project-URL: Homepage, https://pypi.org/project/sound-rs/
Project-URL: Issues, https://github.com/Neksand/sound_rs/issues
Project-URL: Repository, https://github.com/Neksand/sound_rs
Project-URL: Source, https://github.com/Neksand/sound_rs

# 🎵 sound_rs

[![PyPI](https://img.shields.io/pypi/v/sound_rs)](https://pypi.org/project/sound-rs/)
[![Downloads](https://img.shields.io/pypi/dm/sound_rs)](https://pypi.org/project/sound-rs/)
[![Python](https://img.shields.io/pypi/pyversions/sound_rs)](https://pypi.org/project/sound-rs/)
[![Platforms](https://img.shields.io/badge/platforms-Linux%20%7C%20Windows%20%7C%20macOS-blue)](#installation)
[![Typed](https://img.shields.io/badge/typing-typed-blue)](sound_rs.pyi)
[![License](https://img.shields.io/badge/license-MIT-blue)](LICENSE)

**sound_rs** is a fast, Rust-powered audio library for Python that I built and
maintain — decoding with [symphonia](https://github.com/pdeljanov/Symphonia) (plus
minimp3 for MP3), resampling with [rubato](https://github.com/HEnquist/rubato),
output through [cpal](https://github.com/RustAudio/cpal).

---

## 🚀 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 ~9 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) -> (ndarray, sample_rate)`, shape `(frames, channels)`
- **Parallel decode:** `load_many(paths)` uses every core, with the GIL released
- **Gapless (MP3, OGG, AAC/MP4):** encoder delay and padding are trimmed, so decoded
  audio lines up with the original
- **Fast metadata:** `get_audio_info` reads 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.SoundRsError` (a `RuntimeError` subclass)
- **No ffmpeg, no system libraries:** MP3, FLAC, OGG, AAC and WAV are decoded inside
  the wheel; `pip install sound_rs` needs nothing else
- **Typed:** ships `sound_rs.pyi` and `py.typed`, so editors autocomplete everything

---

## 🧠 Installation

```bash
pip install sound_rs
```

Wheels are built for Linux (x86_64), Windows (amd64) and macOS (arm64) for
Python 3.10–3.14. Other platforms build from source.

---

## 💻 Usage

### One-shot playback

```python
import sound_rs

sound_rs.play_audio("sound.wav")          # blocks, GIL released
sound_rs.play_audio("sound.mp3", device="MacBook Pro Speakers")
```

### The player

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

### Inspect and decode

```python
sample_rate, channels, duration = sound_rs.get_audio_info("music.mp3")  # no decode
audio, rate = sound_rs.load("music.flac")                               # (frames, channels) float32

pairs = sound_rs.load_many(["a.flac", "b.flac", "c.flac"])              # parallel
```

### From a NumPy array

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

```bash
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 | 131 ms | 155 ms | 1.2× |
| decode FLAC | 265 ms | 465 ms | 1.8× |
| decode MP3 | 247 ms | 272 ms | 1.1× |
| decode AAC | 361 ms | not supported | — |
| time to first sound (10 min) | 8.8 ms | 492 ms | 56× |
| peak RAM (10 min file) | 43 MB | 255 MB | 6× |
| playback overhead | 32 ms | 148 ms | 4.6× |
| batch decode (2 files) | 272 ms | 448 ms | 1.6× |

**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 | **247 / 249 ms** | 272 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` | **8.8 ms** | **43 MB** |
| soundfile + sounddevice | 492 ms | 255 MB |

**Playback of a 3 s tone** — overhead over the track duration:

| | overhead |
|---|---|
| sound_rs `Player` (stream kept open) | **+32 ms** |
| `sounddevice.play` (opens a stream per call) | +148 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, ~9 ms to first sound |
| 🔜 5 | Device selection UI | Input devices, hot-swap on disconnect |
| 🔜 6 | Recording | Microphone to array or WAV |
| 🔜 7 | Effects | Basic gain/fade/mix utilities |

---

## 🧪 Development

```bash
cargo test                      # resampler, channel conversion, gapless, seek
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 bench/run.py --compare   # full benchmark
```

---

## ⚙️ 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](https://github.com/Neksand)
📦 PyPI: [sound_rs](https://pypi.org/project/sound_rs)

---

## 📜 License

Licensed under the **MIT License** — free for personal and commercial use.

