Stakes. Every audioeffects class of the audioif tier that builds a
audiodynamics.Dynamics — Compressor, Limiter, Expander, NoiseGate,
DeEsser, TransientShaper, MultibandCompressor, DynamicEQ — cannot
release it on a board. The class gate's Tier 1 asks that deinit() release
every node the class built; on the two native builds
_component.Component.deinit() walks the class's node list, looks for
deinit with getattr(node, "deinit", None), and finds nothing to call. A
host that builds and tears down effects in a session leaks every Dynamics
it ever made. audioroute.Splitter is the same shape and is #58; this is its
sibling and the wider one — Limiter alone builds two per instance.
What the surface is. Measured this session, audiocomponents at
AUDIOIF_PIN 2f6cbc3, 2026-09-07:
$ .venv/bin/python -c "import audiodynamics; n = audiodynamics.Dynamics(
audiodynamics.DYN_LIMIT, sample_rate=48000, channel_count=2)
print(hasattr(n, 'deinit'), sorted(x for x in dir(n) if not x.startswith('_')))"
True ['bits_per_sample', 'channel_count', 'deinit', 'gain_reduction_db',
'key', 'max_buffer_length', 'play', 'playing', 'sample_rate',
'samples_signed', 'set', 'single_buffer', 'stop']
$ MICROPYPATH=lib:../cmods/micropython/lib ../cmods/bin/micropython <same>
False ['bits_per_sample', 'channel_count', 'gain_reduction_db', 'key',
'play', 'sample_rate', 'set']
$ MICROPYPATH=lib:../cmods/micropython/lib ../cmods/bin/circuitpython-effects <same>
False ['bits_per_sample', 'channel_count', 'gain_reduction_db', 'key',
'play', 'sample_rate', 'set']
So the CPython shim has deinit, stop, playing, max_buffer_length and
single_buffer; the two native builds have none of them. The gap is the
native binding's, not the shared C's — audioif_dynamics.c has the state to
free.
What it costs the gate. docs/effects/Limiter-evidence.md §2 records six
partial cells for exactly this: the class's own surface still closes and
the borrowed source still renders, both measured, but "every node the class
built was released" is unmeasured on MicroPython and on
circuitpython-effects, because there is nothing to observe. Every
audioif-tier dynamics pack in Phase 2 carries the same six cells, and the
gate audit rules them Y* with the cause below the class
(audiocomponents/docs/effects-phase2-gate-audit.md §3, G2).
Ask. deinit() on the native audiodynamics.Dynamics binding, with the
deinitialised guard #59 asks for generally. stop() and playing would
close the rest of the shim/native divergence, but deinit is the one a gate
item turns on.
Stakes. Every
audioeffectsclass of the audioif tier that builds aaudiodynamics.Dynamics—Compressor,Limiter,Expander,NoiseGate,DeEsser,TransientShaper,MultibandCompressor,DynamicEQ— cannotrelease it on a board. The class gate's Tier 1 asks that
deinit()releaseevery node the class built; on the two native builds
_component.Component.deinit()walks the class's node list, looks fordeinitwithgetattr(node, "deinit", None), and finds nothing to call. Ahost that builds and tears down effects in a session leaks every
Dynamicsit ever made.
audioroute.Splitteris the same shape and is #58; this is itssibling and the wider one —
Limiteralone builds two per instance.What the surface is. Measured this session,
audiocomponentsatAUDIOIF_PIN2f6cbc3, 2026-09-07:So the CPython shim has
deinit,stop,playing,max_buffer_lengthandsingle_buffer; the two native builds have none of them. The gap is thenative binding's, not the shared C's —
audioif_dynamics.chas the state tofree.
What it costs the gate.
docs/effects/Limiter-evidence.md§2 records sixpartialcells for exactly this: the class's own surface still closes andthe borrowed source still renders, both measured, but "every node the class
built was released" is unmeasured on MicroPython and on
circuitpython-effects, because there is nothing to observe. Everyaudioif-tier dynamics pack in Phase 2 carries the same six cells, and the
gate audit rules them
Y*with the cause below the class(
audiocomponents/docs/effects-phase2-gate-audit.md§3, G2).Ask.
deinit()on the nativeaudiodynamics.Dynamicsbinding, with thedeinitialised guard #59 asks for generally.
stop()andplayingwouldclose the rest of the shim/native divergence, but
deinitis the one a gateitem turns on.