Stakes. On the desktop MicroPython and the patched CircuitPython builds,
using an audio node after deinit() is undefined behaviour rather than an
error, and for audiomixer.Mixer it is a segmentation fault. A host that
tears an effect down and pulls a stale reference — a UI holding the old
node, a chain rebuilt while a render is in flight — takes the interpreter
down with it, on hardware where there is no core file to read.
Measured 2026-09-07 against AUDIOIF_PIN 2f6cbc3, one node per process
(audiocomponents/tools/compressor_deinit_use.py, written for exactly this
because a battery that walked all six killed the interpreter):
$ .venv/bin/python tools/compressor_deinit_use.py 3
node 3 Dynamics RuntimeError: Object has been deinitialized and can no longer be used
$ cmods/bin/micropython ... tools/compressor_deinit_use.py 3
node 3 Dynamics pulled 1024 bytes
$ cmods/bin/micropython ... tools/compressor_deinit_use.py 5
node 5 Mixer (SIGSEGV, core dumped)
$ cmods/bin/circuitpython-effects ... tools/compressor_deinit_use.py 5
node 5 Mixer (SIGSEGV, core dumped)
Indices 1–4 (audioroute.SplitterTap ×2, audiodynamics.Dynamics ×2)
render on as if nothing had happened on both native builds and return the
same bytes they would have returned before the deinit(). Index 5,
audiomixer.Mixer, crashes: its deinit frees the voice buffers and
get_buffer reads them.
The CPython target is the one that behaves: every released node raises
RuntimeError: Object has been deinitialized and can no longer be used, so
the check exists in the shim and not in the native modules.
Ask. The native builds get the same deinitialised guard the CPython
target has, on get_buffer at minimum — an exception, not a crash. The
Mixer is the urgent one because it is the only node measured to fault, but
a tap and a Dynamics returning stale audio silently is the same bug with a
quieter symptom.
Found while closing the Tier 1 deinit() row for Compressor
(audiocomponents/docs/effects/Compressor-evidence.md §2).
Stakes. On the desktop MicroPython and the patched CircuitPython builds,
using an audio node after
deinit()is undefined behaviour rather than anerror, and for
audiomixer.Mixerit is a segmentation fault. A host thattears an effect down and pulls a stale reference — a UI holding the old
node, a chain rebuilt while a render is in flight — takes the interpreter
down with it, on hardware where there is no core file to read.
Measured 2026-09-07 against
AUDIOIF_PIN2f6cbc3, one node per process(
audiocomponents/tools/compressor_deinit_use.py, written for exactly thisbecause a battery that walked all six killed the interpreter):
Indices 1–4 (
audioroute.SplitterTap×2,audiodynamics.Dynamics×2)render on as if nothing had happened on both native builds and return the
same bytes they would have returned before the
deinit(). Index 5,audiomixer.Mixer, crashes: itsdeinitfrees the voice buffers andget_bufferreads them.The CPython target is the one that behaves: every released node raises
RuntimeError: Object has been deinitialized and can no longer be used, sothe check exists in the shim and not in the native modules.
Ask. The native builds get the same deinitialised guard the CPython
target has, on
get_bufferat minimum — an exception, not a crash. TheMixeris the urgent one because it is the only node measured to fault, buta tap and a
Dynamicsreturning stale audio silently is the same bug with aquieter symptom.
Found while closing the Tier 1
deinit()row forCompressor(
audiocomponents/docs/effects/Compressor-evidence.md§2).