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
26 changes: 23 additions & 3 deletions .github/workflows/clean-build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -219,9 +219,9 @@ jobs:
"$MP" "$PROBE" audiodelays | tee /tmp/pitchshift_freeze.txt
grep -q "PITCHSHIFT FREEZE PASS" /tmp/pitchshift_freeze.txt

# The graph runner: one C call over the kernels above. MicroPython only,
# so this is the only place it runs. Each planted fault is a wiring or a
# parameter mistake the runner must not hide, and must FAIL first.
# The graph runner: one C call over the kernels above. Each planted
# fault is a wiring or a parameter mistake the runner must not hide, and
# must FAIL first.
- name: The graph runner, and proof its probe can fail
run: |
MP=./micropython/ports/unix/build-standard/micropython
Expand All @@ -235,6 +235,26 @@ jobs:
"$MP" "$PROBE" | tee /tmp/graph.txt
grep -q "GRAPH PASS" /tmp/graph.txt

# The same graph on CPython (the wheel installed above) and on this
# MicroPython, compared sample for sample with a tolerance of 0 counts.
# Each planted fault (one input count off, one set() a block late) goes
# into the MicroPython render only, and the comparison must FAIL first.
# The three-way with the CircuitPython unix build is a local run, as
# for verify_dsp.
- name: A graph renders the same samples on CPython and MicroPython
run: |
MP=$GITHUB_WORKSPACE/micropython/ports/unix/build-standard/micropython
PY=$GITHUB_WORKSPACE/audiodsp/.ci-venv/bin/python
cd audiodsp
for planted in lsb late; do
if "$PY" tests/parity/verify_graph.py --micropython "$MP" --fault $planted > /dev/null; then
echo "the planted fault passed, so this comparison proves nothing: $planted" >&2
exit 1
fi
done
"$PY" tests/parity/verify_graph.py --micropython "$MP" | tee /tmp/graph_parity.txt
grep -q "GRAPH RENDERS AGREE" /tmp/graph_parity.txt

# The pump. Everything above proves the DSP nodes; these prove the
# thing that pulls them, and they can only run here, because they
# need a MicroPython build and this is the job that makes one.
Expand Down
9 changes: 5 additions & 4 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,10 +40,11 @@ for source compatibility; only this repo's own name differs.
- `circuitpython.mk` + `src/circuitpython_spike/` — build `audiodynamics`,
`audioroute`, `audiomath`, `audioecho`, `audioshaper`, `audioladder`,
`audioconvolve`, `audiobiquad`, `audioverb`, `audiomodal`, `audiosampler`,
`audiooscillator` and `audiovorbis` into CircuitPython 11 as a user C module
(`micropython.mk` hands a CircuitPython tree to `circuitpython.mk`). None
of the thirteen is a CircuitPython port: the first two come from
micropython-vst3's `vstaudio` engine and the last eleven are audiodsp's own,
`audiooscillator`, `audiovorbis`, `audiopump` and `audiograph` into
CircuitPython 11 as a user C module (`micropython.mk` hands a CircuitPython
tree to `circuitpython.mk`). None of the fifteen is a CircuitPython port: the
first two come from micropython-vst3's `vstaudio` engine and the last
thirteen are audiodsp's own,
so CircuitPython gains them
here rather than the other way round.
- `docs/porting-plan.md` — the full phased porting history, architecture,
Expand Down
8 changes: 5 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -77,8 +77,8 @@ This gets you `audiocore`, `synthio`, `audiomixer`, `audiofilters`,
`audiodelays`, `audiofreeverb`, `audiospeed`, `audiodynamics`, `audioroute`,
`audiomath`, `audioecho`, `audioshaper`, `audioladder`, `audiooscillator`,
`audioconvolve`, `audiobiquad`, `audioverb`, `audiomodal`, `audiosampler`, `audiometer`,
`audiopump`, `audiovorbis`, and the `audiorender` package.
`audiomp3` and `audiograph` remain firmware-only. The distribution declares no *required*
`audiopump`, `audiovorbis`, `audiograph`, and the `audiorender` package.
`audiomp3` remains firmware-only. The distribution declares no *required*
runtime dependencies and does not itself publish an `audiodsp` import; its
version is the `VERSION` file, which is also what `_audiodsp.__version__`
reports.
Expand Down Expand Up @@ -495,7 +495,9 @@ delay) and a few small ones a voice needs (an ADSR, an LFO, noise, a mixer
with ramped gains, a VCA), wire them through the graph's own block buffers,
put each voice in a group, and call `run()` once a block; nothing in Python
runs between the nodes. That is what lets a six-voice polysynth play in real
time on an ESP32-P4. MicroPython only for now:
time on an ESP32-P4. The same graph renders the same samples on CPython,
MicroPython and CircuitPython, so a patch you build on the desktop sounds
there as it will on the board:
[docs/audiograph.md](docs/audiograph.md) has the nodes, the wire and the
limits.

Expand Down
3 changes: 0 additions & 3 deletions ROADMAP.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,6 @@ audio pump holding up under live use such as a stompbox or a stage rig.
[docs/pump-ports.md](docs/pump-ports.md) for how a port plugs in.
- An LC3 codec module beside `audiomp3`, frame by frame: encode PCM to one
frame, decode a frame (or a lost one) to PCM. Leaves room for LE Audio later.
- `audiograph` on CPython and CircuitPython: the graph runner's core is
runtime-neutral C, so each is a shim over the same calls, and parity tests
across the three can then pin a graph's render on the desktop to the board's.
- A level ramp on `audiomixer` voices: a level move ramps over a set number of
frames instead of stepping, so an effect can fade its wet path in or out
without a click when you switch it in mid-note.
Expand Down
3 changes: 2 additions & 1 deletion circuitpython.mk
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ CFLAGS_USERMOD += -I$(AUDIODSP_CP_SPIKE) -I$(AUDIODSP_CP_SRC)

AUDIODSP_CP_MODULES := audiodynamics audioroute audiomath audioecho audioshaper \
audioladder audiooscillator audioconvolve audiobiquad audioverb audiomodal \
audiosampler audiovorbis audiopump
audiosampler audiovorbis audiopump audiograph

AUDIODSP_CP_SOURCES := \
$(foreach m,$(AUDIODSP_CP_MODULES),$(sort $(wildcard $(AUDIODSP_CP_SPIKE)/shared-bindings/$(m)/*.c))) \
Expand All @@ -59,6 +59,7 @@ AUDIODSP_CP_SOURCES += $(addprefix $(AUDIODSP_CP_SRC)/shared/, \
audiodsp_sampler.c \
audiodsp_vorbis.c \
audiodsp_port.c \
audiodsp_graph.c \
)

SRC_USERMOD_C += $(AUDIODSP_CP_SOURCES)
Expand Down
16 changes: 14 additions & 2 deletions docs/audiograph.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,10 @@ ones a voice needs (an envelope, an LFO, noise, a mixer, a VCA), then call
lets a six-voice polysynth built from these kernels play in real time on an
ESP32-P4, where the same patch run node by node from Python could not.

It is MicroPython only for now; the CPython and CircuitPython bindings are
on the [roadmap](../ROADMAP.md).
It is the same module on CPython (in the `pydevices-audiodsp` wheel),
MicroPython and CircuitPython, and the same C underneath, so a graph renders
the same samples on all three: you can build and listen to a patch on the
desktop and get the board's render, sample for sample.

```python
import audiograph as ag
Expand Down Expand Up @@ -115,3 +117,13 @@ sounds and a masked group goes silent at the mixer), to the mixer's gain
ramp and to the output node. Each planted fault, a mixer added before the
node it reads, a filter left out of the chain, a cutoff sent to a node no
output reads, makes it fail; CI runs the faults first.

`tests/parity/verify_graph.py` renders one patch with every node kind in it
(two voices with their groups parking, a fed buffer, moved gains and
cutoffs, a delay reset mid-tail, a mono graph) on CPython and on each
interpreter you name, and compares every output sample. The tolerance is 0
counts: the three compile the same C and are handed the same floats, so any
difference is a defect rather than rounding. Its two planted faults, one
input sample one count off and one `set()` a block late, each make it fail.
CI runs it against MicroPython in both float widths; the three-way with the
CircuitPython unix build is a local run.
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ Repository = "https://github.com/PyDevices/audiodsp"
Issues = "https://github.com/PyDevices/audiodsp/issues"

[tool.setuptools]
py-modules = ["audiocore", "synthio", "audiomixer", "audiofilters", "audiodelays", "audiofreeverb", "audiospeed", "audiodynamics", "audioroute", "audiomath", "audioecho", "audioconvolve", "audiobiquad", "audioshaper", "audioladder", "audiooscillator", "audioverb", "audiomodal", "audiosampler", "audiometer", "audiopump", "audiovorbis"]
py-modules = ["audiocore", "synthio", "audiomixer", "audiofilters", "audiodelays", "audiofreeverb", "audiospeed", "audiodynamics", "audioroute", "audiomath", "audioecho", "audioconvolve", "audiobiquad", "audioshaper", "audioladder", "audiooscillator", "audioverb", "audiomodal", "audiosampler", "audiometer", "audiopump", "audiovorbis", "audiograph"]
# Keep py-modules explicit: src/cpython is the root package directory now, and
# an automatic scan there would also pick up anything else that lands in it.
packages = ["audiorender", "audiodsp_util"]
Expand Down
1 change: 1 addition & 0 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@
"src/shared/audiodsp_modal.c",
"src/shared/audiodsp_sampler.c",
"src/shared/audiodsp_meter.c",
"src/shared/audiodsp_graph.c",
# Xiph's Tremor, included into this one file from
# third_party/tremor; docs/vorbis.md.
"src/shared/audiodsp_vorbis.c",
Expand Down
5 changes: 3 additions & 2 deletions src/audiograph/Graph.h
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
// audiograph.Graph -- MicroPython binding over shared/audiodsp_graph.c.
//
// The runner that renders a whole patch in one call. MicroPython only for
// now; the CPython and CircuitPython bindings are on the roadmap.
// The runner that renders a whole patch in one call. The CircuitPython twin
// is src/circuitpython_spike/shared-bindings/audiograph/, the CPython one the
// Graph type in src/cpython/_audiodsp.c; the three take the same arguments.
//
// SPDX-License-Identifier: MIT

Expand Down
Loading
Loading