Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 39 additions & 0 deletions .github/workflows/docs.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# The docs site as Read the Docs builds it: from the repository root, with
# docs/requirements.txt, and every warning a failure. The module reference is
# written from the type stubs, so a stub that does not render fails here.
name: Docs

on:
push:
branches: [main]
paths:
- 'docs/**'
- 'src/**/*.pyi'
- 'VERSION'
- '.readthedocs.yaml'
- '.github/workflows/docs.yml'
pull_request:
paths:
- 'docs/**'
- 'src/**/*.pyi'
- 'VERSION'
- '.readthedocs.yaml'
- '.github/workflows/docs.yml'
workflow_dispatch:

permissions:
contents: read

jobs:
docs:
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- uses: actions/checkout@v7
- uses: actions/setup-python@v7
with:
python-version: '3.13'
- name: Install docs requirements
run: pip install -r docs/requirements.txt
- name: Build docs
run: sphinx-build -W --keep-going -b html docs _site
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -26,3 +26,6 @@ dist/
*.o
*.a
.deps/

# Built documentation
docs/_build/
14 changes: 14 additions & 0 deletions .readthedocs.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
version: 2

build:
os: ubuntu-24.04
tools:
python: "3.13"

sphinx:
configuration: docs/conf.py
fail_on_warning: true

python:
install:
- requirements: docs/requirements.txt
2 changes: 1 addition & 1 deletion docs/changelog.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
# Changelog

Every release's changes are listed in [CHANGELOG.md](../CHANGELOG.md), newest first. What is planned next is in [ROADMAP.md](../ROADMAP.md).
Every release's changes are listed in [CHANGELOG.md](https://github.com/PyDevices/audiodsp/blob/main/CHANGELOG.md), newest first. What is planned next is in [ROADMAP.md](https://github.com/PyDevices/audiodsp/blob/main/ROADMAP.md).
55 changes: 55 additions & 0 deletions docs/conf.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
# SPDX-FileCopyrightText: 2026 Brad Barnett
#
# SPDX-License-Identifier: MIT

"""Sphinx configuration for the audiodsp documentation.

Everything shared by the PyDevices documentation projects (theme, top bar,
Markdown and docstring setup) comes from pydevices-docs.

The module reference is written from the type stubs, ``src/<module>/<module>.pyi``,
which list every name each native module exports. The build copies each stub
into ``_build`` as an importable module, so autodoc reads the stubs and never
imports the compiled modules.
"""

import re
import sys
from pathlib import Path

from pydevices_docs import configure

globals().update(configure(
"audiodsp", __file__,
repo="PyDevices/audiodsp", edit_path="docs",
version_file="../VERSION",
))

_DOCS = Path(__file__).resolve().parent
_STUBS = _DOCS.parent / "src"
_STUB_ROOT = _DOCS / "_build" / "stubs"


def _importable(text):
"""A stub may name a class before it is defined, which a stub allows and
Python before 3.14 does not, so defer the annotations."""
return re.sub(r"^((?:from|import) )", r"from __future__ import annotations\n\1",
text, count=1, flags=re.M)


def _install_stubs(app):
"""Make every module's stub importable under the module's own name."""
_STUB_ROOT.mkdir(parents=True, exist_ok=True)
if str(_STUB_ROOT) not in sys.path:
sys.path.insert(0, str(_STUB_ROOT))
for stub in sorted(_STUBS.glob("*/*.pyi")):
target = _STUB_ROOT / (stub.stem + ".py")
text = _importable(stub.read_text(encoding="utf-8"))
if not target.exists() or target.read_text(encoding="utf-8") != text:
target.write_text(text, encoding="utf-8")
sys.modules.pop(stub.stem, None)


def setup(app):
app.connect("builder-inited", _install_stubs)
return {"version": "1", "parallel_read_safe": True, "parallel_write_safe": True}
2 changes: 1 addition & 1 deletion docs/guides/decode-vorbis.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ and `with` as `MP3Decoder`. Mono and stereo files play; `sample_rate` and
plays, so `audiopump` refuses it: feed the pump from the interpreter instead.

The decoder is Xiph's Tremor, integer-only, carried in
[third_party/tremor](../../third_party/tremor) under its own BSD-3-Clause licence.
[third_party/tremor](https://github.com/PyDevices/audiodsp/tree/main/third_party/tremor) under its own BSD-3-Clause licence.
It decodes to the same samples on every build, and within two steps of
ffmpeg's float decoder. Other C modules in the same firmware can decode a
stream through it too. [The C interface page](../reference/vorbis.md) has the C interface,
Expand Down
4 changes: 2 additions & 2 deletions docs/how-it-works/correctness-standard.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ matters is that no independent implementation exists to compare against today.

**1. Three-target agreement, with nothing stored.** The CPython extension,
desktop MicroPython, and the patched CircuitPython build must render a probe
identically. All three compile the same kernel under [`src/shared/`](../../src/shared/), so this is
identically. All three compile the same kernel under [`src/shared/`](https://github.com/PyDevices/audiodsp/tree/main/src/shared), so this is
a real check on the three compilations and on anything that is
architecture-dependent, undefined, or width-sensitive — the class of defect that
found the flanger overflow. It cannot be laundered, because there is no file to
Expand Down Expand Up @@ -102,7 +102,7 @@ decision that must be written there, or it is a bug.
## What a divergence means now

* **CP-shared node, we differ from CircuitPython.** Either a bug of ours, or a
bug of theirs worth an upstream report ([`docs/upstream-reports/`](../upstream-reports/)), or a
bug of theirs worth an upstream report ([`docs/upstream-reports/`](https://github.com/PyDevices/audiodsp/tree/main/docs/upstream-reports)), or a
deliberate departure that goes in [`docs/how-it-works/upstream-diff.md`](upstream-diff.md). Three outcomes, and
choosing between them is the work. What it is never is "re-record ours".
* **Our own node, the three targets disagree.** Ours, always. One of the three
Expand Down
16 changes: 8 additions & 8 deletions docs/how-it-works/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,14 +47,14 @@ For a new pump port, [the pump-port guide](pump-ports.md) is authoritative.

| Path | Purpose |
|---|---|
| [`src/audiocore/`](../../src/audiocore/), [`src/synthio/`](../../src/synthio/), [`src/audiomixer/`](../../src/audiomixer/) | Core CircuitPython-compatible source, synthesis, and mixing modules. |
| [`src/audiocore/`](https://github.com/PyDevices/audiodsp/tree/main/src/audiocore), [`src/synthio/`](https://github.com/PyDevices/audiodsp/tree/main/src/synthio), [`src/audiomixer/`](https://github.com/PyDevices/audiodsp/tree/main/src/audiomixer) | Core CircuitPython-compatible source, synthesis, and mixing modules. |
| `src/audio*/` | Effects, DSP, routing, and audio utility modules compiled into native targets. |
| [`src/audiopump/`](../../src/audiopump/) | Portable pump loop, rings, events, taps, and lifecycle guards. |
| [`src/shared/`](../../src/shared/) | DSP and sample-protocol C shared by the MicroPython and CPython targets. |
| [`src/cp_compat/`](../../src/cp_compat/) | Small CircuitPython compatibility shims needed by the MicroPython port. |
| [`src/cpython/`](../../src/cpython/) | `_audiodsp` extension plus top-level Python wrappers for CPython. |
| [`lib/audiodsp_util/`](../../lib/audiodsp_util/) | Cross-runtime `float32()` helpers for settings derived in Python. |
| [`lib/audiorender/`](../../lib/audiorender/) | Desktop-only, NumPy-based offline composition renderer. |
| [`src/audiopump/`](https://github.com/PyDevices/audiodsp/tree/main/src/audiopump) | Portable pump loop, rings, events, taps, and lifecycle guards. |
| [`src/shared/`](https://github.com/PyDevices/audiodsp/tree/main/src/shared) | DSP and sample-protocol C shared by the MicroPython and CPython targets. |
| [`src/cp_compat/`](https://github.com/PyDevices/audiodsp/tree/main/src/cp_compat) | Small CircuitPython compatibility shims needed by the MicroPython port. |
| [`src/cpython/`](https://github.com/PyDevices/audiodsp/tree/main/src/cpython) | `_audiodsp` extension plus top-level Python wrappers for CPython. |
| [`lib/audiodsp_util/`](https://github.com/PyDevices/audiodsp/tree/main/lib/audiodsp_util) | Cross-runtime `float32()` helpers for settings derived in Python. |
| [`lib/audiorender/`](https://github.com/PyDevices/audiodsp/tree/main/lib/audiorender) | Desktop-only, NumPy-based offline composition renderer. |
| `micropython.mk`, `micropython.cmake` | User-module build glue for Make and CMake MicroPython ports. |
| `tests/` | CPython fixtures, in-repository parity gates, and parity tools that need sibling checkouts. |
| `docs/` | Guides, reference, and these architecture pages. |
Expand Down Expand Up @@ -97,7 +97,7 @@ Most deep parity checks need the sibling repositories checked out beside
this one, built interpreters, and the fixed CircuitPython oracle. They are
not a standalone contributor prerequisite. The repository's CI covers CPython
fixture tests and committed in-repository parity gates; `python -m flake8` is
the local lint gate. [CONTRIBUTING.md](../../CONTRIBUTING.md) has the rules:
the local lint gate. [CONTRIBUTING.md](https://github.com/PyDevices/audiodsp/blob/main/CONTRIBUTING.md) has the rules:
don't modify the CircuitPython oracle, don't casually update parity goldens,
and don't move the dependency pins.

Expand Down
12 changes: 6 additions & 6 deletions docs/how-it-works/upstream-diff.md
Original file line number Diff line number Diff line change
Expand Up @@ -747,7 +747,7 @@ Goal: keep this list short. Python-level API and behavior match CP unless
noted here.

Six of the entries below are upstream bugs rather than divergences, written up
in [upstream-reports/](../upstream-reports/). **Five have been filed with
in [upstream-reports/](https://github.com/PyDevices/audiodsp/tree/main/docs/upstream-reports). **Five have been filed with
CircuitPython and four are merged** (see that directory's README for the
issue and PR numbers): peaking-eq-sign, dds-oscillator-off-by-one,
distortion-soft-clip-union and biquad-reset are fixed upstream;
Expand Down Expand Up @@ -958,7 +958,7 @@ MicroPython, unrelated to any ported module).

## Phase 7: `synthtools` end-to-end acceptance test

[`tests/vendor/synthtools`](../../tests/vendor/synthtools/) is todbot's
[`tests/vendor/synthtools`](https://github.com/PyDevices/audiodsp/tree/main/tests/vendor/synthtools) is todbot's
[`CircuitPython_SynthTools`](https://github.com/todbot/CircuitPython_SynthTools)
(MIT, commit recorded in [`tests/vendor/SYNTHTOOLS_COMMIT.txt`](../../tests/vendor/SYNTHTOOLS_COMMIT.txt)), vendored
unmodified -- the `synthtools/` package only, not `examples/`/`tests/`/
Expand Down Expand Up @@ -3555,7 +3555,7 @@ group delay 0 samples at x1, 2.2 (46 us) at x2, 3.3 (69 us) at x4, 3.9 (80
us) at x8, with the magnitude response flat to within 0.001 dB from 200 Hz to
18 kHz. It is small -- a linear-phase FIR half-band steep enough for this
transition would be several times that -- but it is not zero and it is not an
integer, so `audioshaper.GROUP_DELAY_SAMPLES` carries the table and a
integer, so `audioshaper.GROUP_DELAY_SAMPLES` (a constant of the CPython build only) carries the table and a
component reporting `latency_samples` reports the entry for the factor it
built with.

Expand Down Expand Up @@ -3606,7 +3606,7 @@ Phase 0 sketch carried would have been a byte of state nothing reads.
`audiomixer.Mixer` does sample by sample with a saturating signed add
(`src/audiomixer/Mixer.c:216-230`). No surviving claimant needs a stream
here, and keeping the kernel free of `mp_obj` is what lets one golden hash
cover every interpreter -- the arithmetic all lives in [`src/shared/`](../../src/shared/), so
cover every interpreter -- the arithmetic all lives in [`src/shared/`](https://github.com/PyDevices/audiodsp/tree/main/src/shared), so
two interpreters disagreeing would itself be the finding.
- **There is no `pre_filter_hz`.** Vision section 6 says the input filter is
the class's to compose, and `audiofilters.Filter` with a `synthio.Biquad`
Expand Down Expand Up @@ -4274,7 +4274,7 @@ the routes `play()` cannot see. **Upstream still spins**, which is why
[`tests/parity/mixer_short_loop_probe.py`](../../tests/parity/mixer_short_loop_probe.py) is not in `verify_dsp.py`'s `PROBES`:
the oracle would hang the three-way rather than report a difference.
`audiomixer` is a stock CircuitPython module and not part of
[`src/circuitpython_spike/`](../../src/circuitpython_spike/), so per this repository's rule the fix lands on the
[`src/circuitpython_spike/`](https://github.com/PyDevices/audiodsp/tree/main/src/circuitpython_spike), so per this repository's rule the fix lands on the
MicroPython and CPython targets only and the oracle is left alone.

### Refused rather than padded
Expand Down Expand Up @@ -4540,7 +4540,7 @@ public surface, and is written down here rather than assumed away.
does not need one: its playback layer already pulls the graph natively, from
its own audio thread, so a second pull loop would be a second owner of the
same nodes. The module is therefore **absent from a CircuitPython build** —
`copy_manifest.txt` names none of [`src/audiopump/`](../../src/audiopump/) — and absent from the
`copy_manifest.txt` names none of [`src/audiopump/`](https://github.com/PyDevices/audiodsp/tree/main/src/audiopump) — and absent from the
**CPython wheel**, which excludes it rather than stubbing it, because it is a
MicroPython C module through and through (`MP_REGISTER_MODULE`, `mp_obj_t`, a
VM root pointer array) and a desktop Python program has threads of its own.
Expand Down
6 changes: 3 additions & 3 deletions docs/how-it-works/upstream-sync.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ to turn up.
3. For each commit, find the same code here. A file under `src/<module>/`
holds upstream's `shared-module` and `shared-bindings` files merged, with
a `// --- from ...` line where each begins. Arithmetic that both bindings
run lives in [`src/shared/`](../../src/shared/), and the CPython target's copy of the module
run lives in [`src/shared/`](https://github.com/PyDevices/audiodsp/tree/main/src/shared), and the CPython target's copy of the module
is `src/cpython/<module>.py` beside `_audiodsp.c`. A fix usually lands in
two or three of those places.
4. Take each fix in upstream's own words where the code is still upstream's,
Expand All @@ -59,7 +59,7 @@ to turn up.
all, in both the native module and the CPython target. Leave out only what
the list of deliberate differences below says this port does otherwise,
and say which in the pull request.
5. Add a probe under [`tests/parity/`](../../tests/parity/) whose output changes with each fix or
5. Add a probe under [`tests/parity/`](https://github.com/PyDevices/audiodsp/tree/main/tests/parity) whose output changes with each fix or
addition, and add it to `verify_dsp.py`. A probe CPython can't run (no `audiomp3`, say)
can't be compared there, because CI has only CPython and MicroPython; have
it check itself against the oracle's answers and run it in
Expand Down Expand Up @@ -211,7 +211,7 @@ a resync should take upstream's version. Each entry links to its reasons.
`shared-bindings` files for that class merged into one, docstrings (`//|`)
dropped. The per-sample loops of the effects live in `src/shared/` so the
CPython target runs the same C.
- **The CPython target** ([`src/cpython/`](../../src/cpython/)) is a second implementation, held to
- **The CPython target** ([`src/cpython/`](https://github.com/PyDevices/audiodsp/tree/main/src/cpython)) is a second implementation, held to
the native bytes by `verify_dsp.py` rather than by sharing upstream's code.
- **`attr, cp_compat_attr`** on every type, because mainline MicroPython
needs it to call a property
Expand Down
4 changes: 2 additions & 2 deletions docs/how-it-works/wheels-and-platforms.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

What a release builds, which platforms are proven by CI and which aren't,
and why. The steps to build and release are in
[CONTRIBUTING.md](../../CONTRIBUTING.md#build-and-release-wheels).
[CONTRIBUTING.md](https://github.com/PyDevices/audiodsp/blob/main/CONTRIBUTING.md#build-and-release-wheels).

## What a release builds

Expand Down Expand Up @@ -71,7 +71,7 @@ aarch64 is the example. At first, `verify_effects` differed on `multitap` and
`pitchshift`, the two effects that interpolate along a delay line: a
compiler's fused multiply-add changes the rounding, and AArch64 fuses by
baseline where x86-64 doesn't without `-mfma`. Forbidding contraction in
every [`src/shared/`](../../src/shared/) file that computes in float closed it (issue
every [`src/shared/`](https://github.com/PyDevices/audiodsp/tree/main/src/shared) file that computes in float closed it (issue
[#79](https://github.com/PyDevices/audiodsp/issues/79)), so aarch64's own
baseline was removed from [`tests/parity/golden/effects_component.json`](../../tests/parity/golden/effects_component.json): it is
held to the oracle hash itself, the same one x86_64 is held to.
Expand Down
65 changes: 61 additions & 4 deletions docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,12 @@ have. Start with install, then your first program.
## Reference

- [The modules](reference/modules.md): what ships, and where each is documented.
- [Filters and effects](reference/filters-and-effects.md): `audiobiquad`, `audiodynamics`, `audioecho`, `audioshaper`, `audioladder`, `audioverb`.
- [Sources and synthesis](reference/sources-and-synthesis.md): `audiooscillator`, `audiomodal`, `audiograph`.
- [Sources and synthesis](reference/sources-and-synthesis.md): `audiocore`, `audiomp3`, `synthio`, `audiooscillator`, `audiomodal`, `audiosampler`, `audiograph`.
- [Filters and effects](reference/filters-and-effects.md): `audiobiquad`, `audiodynamics`, `audioecho`, `audioshaper`, `audioladder`, `audioverb`, `audiofilters`, `audiodelays`, `audiofreeverb`, `audiospeed`, `audioconvolve`.
- [Mixing and routing](reference/mixing-and-routing.md): `audiomixer`, `audioroute`.
- [Analysis and utilities](reference/analysis-and-utilities.md): `audiometer`, `audiofeatures`, `audiomath`.
- [audiopump](reference/audiopump.md): status, builds, sources.
- [Vorbis](reference/vorbis.md): the C interface, memory and tests.
- [Vorbis](reference/vorbis.md): `audiovorbis`, the C interface, memory and tests.

## How it works

Expand All @@ -50,4 +52,59 @@ have. Start with install, then your first program.

- [Changelog](changelog.md): what changed in each release.

[Roadmap](../ROADMAP.md) · [Contributing](../CONTRIBUTING.md)
[Roadmap](https://github.com/PyDevices/audiodsp/blob/main/ROADMAP.md) · [Contributing](https://github.com/PyDevices/audiodsp/blob/main/CONTRIBUTING.md)

```{toctree}
:hidden:

changelog
get-started/first-program
get-started/install
guides/change-without-a-click
guides/decode-vorbis
guides/filter-a-sound
guides/meter-levels
guides/play-computed-audio
guides/play-recorded-notes
guides/run-the-audio-pump
guides/speech-features
guides/swap-what-plays
guides/use-threads
how-it-works/additions
how-it-works/audiograph
how-it-works/correctness-standard
how-it-works/drift-resampler
how-it-works/index
how-it-works/parity-gates
how-it-works/pump-ports
how-it-works/sound-stability
how-it-works/upstream-diff
how-it-works/upstream-sync
how-it-works/wheels-and-platforms
porting-plan
reference/analysis-and-utilities
reference/audiopump
reference/filters-and-effects
reference/mixing-and-routing
reference/modules
reference/sources-and-synthesis
reference/vorbis
upstream-reports/README
upstream-reports/biquad-band-edges
upstream-reports/biquad-reset
upstream-reports/dds-oscillator-off-by-one
upstream-reports/distortion-overdrive-drive
upstream-reports/distortion-soft-clip-union
upstream-reports/peaking-eq-sign
upstream-reports/prs/biquad-reset/PR
upstream-reports/prs/dds-oscillator-off-by-one/PR
upstream-reports/prs/distortion-soft-clip-union/PR
upstream-reports/prs/flanger-int32-overflow/PR
upstream-reports/prs/peaking-eq-sign/PR
upstream-reports/prs/speedchanger-phase-carry/PR
upstream-reports/prs/speedchanger-rate-rounding/PR
upstream-reports/prs/synthio-note-repress-zero-level/PR
upstream-reports/prs/synthio-note-repress-zero-level/review-reply-relic-se
upstream-reports/speedchanger-phase-carry
upstream-reports/speedchanger-rate-rounding
```
Loading
Loading