Metadata-Version: 2.4
Name: soniclab
Version: 2026.1.4
Summary: A modular Python DSP engine for audio synthesis, effects, sequencing, and MIDI
Project-URL: Homepage, https://github.com/rasigle/soniclab
Project-URL: Repository, https://github.com/rasigle/soniclab
Project-URL: Issues, https://github.com/rasigle/soniclab/issues
Project-URL: Changelog, https://github.com/rasigle/soniclab/blob/main/CHANGELOG.md
Author-email: Rainer Sigle <rainer.sigle@live.de>
License: MIT
License-File: LICENSE
Keywords: audio,dsp,music,sound-design,synthesis,synthesizer
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: Intended Audience :: Education
Classifier: Intended Audience :: Science/Research
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
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: Topic :: Multimedia :: Sound/Audio :: Sound Synthesis
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Classifier: Typing :: Typed
Requires-Python: >=3.11
Requires-Dist: numpy>=1.26
Requires-Dist: scipy>=1.11
Provides-Extra: audio-io
Requires-Dist: numba>=0.66.0; extra == 'audio-io'
Requires-Dist: sounddevice>=0.5.5; extra == 'audio-io'
Provides-Extra: examples
Requires-Dist: ipykernel>=7.3.0; extra == 'examples'
Requires-Dist: ipython>=9.15.0; extra == 'examples'
Requires-Dist: librosa>=0.11.0; extra == 'examples'
Requires-Dist: matplotlib>=3.11.0; extra == 'examples'
Requires-Dist: notebook>=7.4.7; extra == 'examples'
Provides-Extra: full
Requires-Dist: ipykernel>=7.3.0; extra == 'full'
Requires-Dist: ipython>=9.15.0; extra == 'full'
Requires-Dist: librosa>=0.11.0; extra == 'full'
Requires-Dist: matplotlib>=3.11.0; extra == 'full'
Requires-Dist: mido>=1.3.0; extra == 'full'
Requires-Dist: notebook>=7.4.7; extra == 'full'
Requires-Dist: numba>=0.66.0; extra == 'full'
Requires-Dist: python-rtmidi>=1.5.8; extra == 'full'
Requires-Dist: sounddevice>=0.5.5; extra == 'full'
Provides-Extra: midi
Requires-Dist: mido>=1.3.0; extra == 'midi'
Requires-Dist: python-rtmidi>=1.5.8; extra == 'midi'
Provides-Extra: speed
Requires-Dist: numba>=0.66.0; extra == 'speed'
Description-Content-Type: text/markdown

# SonicLab

[![CI](https://github.com/rasigle/soniclab/actions/workflows/engine-package.yml/badge.svg)](https://github.com/rasigle/soniclab/actions/workflows/engine-package.yml)
[![PyPI version](https://img.shields.io/pypi/v/soniclab)](https://pypi.org/project/soniclab/)
[![Python versions](https://img.shields.io/pypi/pyversions/soniclab)](https://pypi.org/project/soniclab/)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)

SonicLab is a modular **Python DSP / audio synthesis engine**: vectorized oscillators,
envelopes, filters, effects, sequencing, optional realtime audio I/O, and MIDI adapters.

> **Status:** Beta. Public APIs may still evolve; see [docs/stability.md](docs/stability.md)
> and [CHANGELOG.md](CHANGELOG.md).

## Features

- **Oscillators:** sine, square, sawtooth, triangle, PolyBLEP bandlimited variants
- **Modulation:** ADSR / decay envelopes, modulated oscillators & frequency helpers
- **Modifiers:** volume, panning, clipping, frequency processors
- **Composition:** `Chain` (serial), `WaveAdder` (parallel mix)
- **Noise:** white, pink, brownian, blue, grey, velvet, sample-and-hold, Perlin
- **Filters:** Butterworth utilities, biquad resonant, acid/303-style filter
- **Effects:** distortion, delay, reverb, compressor
- **Sequencing:** step sequencers, arpeggiator, clock, accent/slide, TB-303 helpers
- **Presets:** `PresetBuilder` / `PresetLibrary`
- **I/O (optional):** `AudioOutput` via the `audio-io` extra
- **MIDI (optional):** input/files, mono & poly synths (velocity, sustain, bend),
  mono note-stack CV and multi-voice `PolyphonicMIDIToCV` via the `midi` extra

## Install

From PyPI:

```bash
pip install soniclab
```

Optional extras:

```bash
pip install "soniclab[audio-io]"   # sounddevice + numba speed path
pip install "soniclab[midi]"       # mido + python-rtmidi
pip install "soniclab[speed]"      # numba only
pip install "soniclab[examples]"   # notebooks / plotting
pip install "soniclab[full]"       # audio-io + midi + examples
```

From a checkout (recommended while developing):

```powershell
uv sync
# or:
python -m pip install -e ".[dev]"
```

Notes:

- Base install is the core NumPy/SciPy engine only.
- Realtime device playback needs working system audio drivers plus `audio-io`.
- MIDI needs local MIDI ports/backends plus the `midi` extra.

## Minimal example

```python
from soniclab import SineOscillator, __version__

print(__version__)
osc = SineOscillator(frequency=440, amplitude=0.3)
samples = osc.get_samples(44100)  # 1 second @ 44.1 kHz, float32
print(samples.shape, samples.dtype)
```

Chain + stereo pan:

```python
from soniclab import SineOscillator, Chain, Volume, Panner

chain = Chain(
    SineOscillator(frequency=440, amplitude=0.4),
    Volume(0.8),
    Panner(0.25),
)
stereo = chain.get_samples(2048, mode="vectorized")  # shape (2048, 2)
```

PolyBLEP oscillator:

```python
from soniclab import PolyBLEPOscillator, WaveShape

osc = PolyBLEPOscillator(
    frequency=110,
    amplitude=0.5,
    wave_shape=WaveShape.SAWTOOTH_UP,
)
samples = osc.get_samples(1024, mode="vectorized")
```

Optional audio output:

```python
from soniclab.audio_io import AudioOutput
# See examples/ and soniclab/audio_io for callback-based playback.
```

## Sample generation modes

Most generators support:

| Mode           | Behavior                                                                                                                                                            |
|----------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `"vectorized"` | NumPy block rendering (preferred for production)                                                                                                                    |
| `"iterator"`   | Python sample loop (flexible, slower)                                                                                                                               |
| `"auto"`       | oscillators: vectorized when `n >= 512`, else iterator; **composers (`Chain` / `WaveAdder`) always use vectorized** so small realtime buffers stay on the fast path |

For realtime or offline renderers, prefer `"vectorized"` (or `"auto"` with buffer sizes ≥ 512).

## Documentation

- [docs/](docs/) — getting started, API overview, recipes, stability policy
- Local preview: `uv run --with mkdocs mkdocs serve`
- [CHANGELOG.md](CHANGELOG.md)
- [CONTRIBUTING.md](CONTRIBUTING.md)
- [SECURITY.md](SECURITY.md)

## Testing

```powershell
uv run pytest tests -q
uv run ruff check soniclab tests
uv run mypy soniclab
```

## Project layout

```text
soniclab/          # installable package
tests/             # pytest suite (mirrors soniclab/ packages)
examples/          # demos & notebooks (not in sdist)
docs/              # user documentation (MkDocs)
scripts/           # benchmarks & local tooling
```

## Public API

Import the stable surface from the top-level package:

```python
from soniclab import (
    SineOscillator,
    SquareOscillator,
    TriangleOscillator,
    SawtoothOscillator,
    PolyBLEPOscillator,
    ADSREnvelope,
    Chain,
    WaveAdder,
    Volume,
    Panner,
    Delay,
    Reverb,
)
```

Optional subsystems stay in subpackages so the core install stays light:

```python
from soniclab.audio_io import AudioOutput
from soniclab.midi_io import MIDIInput  # requires midi extra
```

Polyphonic MIDI synth (velocity-sensitive voices):

```python
from soniclab import ADSREnvelope, Chain, ModulatedVolume, SineOscillator
from soniclab.midi_io import PolyphonicSynth  # requires midi extra

def voice_factory():
    osc = SineOscillator(440, amplitude=0.25)
    env = ADSREnvelope(attack_duration=0.01, release_duration=0.2)
    return Chain(osc, ModulatedVolume(env))

synth = PolyphonicSynth(voice_factory, max_voices=8)
synth.note_on(60, 100)
synth.note_on(64, 80)
samples = synth.get_samples(2048)
```

See `examples/midi/polyphony_usage.py` and [docs/recipes.md](docs/recipes.md).

## Contributing

1. Prefer `uv` for environments (`uv sync --group dev`).
2. Keep changes covered by tests under `tests/`.
3. Run `pytest`, `ruff`, and `mypy` before opening a PR.
4. Follow existing module patterns (component descriptor + registry registration).
5. See [CONTRIBUTING.md](CONTRIBUTING.md) for details.

## Publishing a release

Version is read from `soniclab/_version.py` (CalVer-style `YYYY.MINOR.MICRO`).
Publishing uses GitHub Actions (Trusted Publisher) on a GitHub **Release**.

1. **Prep**
   - Bump `major` / `minor` / `micro` in `soniclab/_version.py`.
   - Move the matching section in `CHANGELOG.md` from *Unreleased* to a dated
     heading (e.g. `## [2026.1.3] - 2026-07-31`).
   - Commit on `main` and push so CI is green (`pytest`, `ruff`, `mypy`, build).

2. **Local sanity (optional but recommended)**

   ```powershell
   uv run pytest tests -q
   uv run ruff check soniclab tests
   uv run mypy soniclab
   uv build
   uv run --with twine twine check dist/*
   ```

3. **Tag & release** (creates the PyPI publish)

   ```powershell
   git tag 2026.1.3
   git push origin main --tags
   # Then publish a GitHub Release for that tag (UI or gh):
   # gh release create 2026.1.3 --title "2026.1.3" --notes-file CHANGELOG.md
   ```

   The `Publish to PyPI` workflow builds the sdist/wheel and uploads to PyPI when
   the release is published. For a dry run, use **Actions → Publish to PyPI →
   Run workflow** with target `testpypi`.

4. **Verify** — [pypi.org/project/soniclab](https://pypi.org/project/soniclab/)
   shows the new version; `pip install -U soniclab` installs it.

Do not upload the same version twice to PyPI (versions are immutable).

## Changelog

See [CHANGELOG.md](CHANGELOG.md).

## License

See [LICENSE](LICENSE).
