Stakes. Ten of the sixteen Phase 2 audioeffects classes are audioif
tier because they build audiobiquad.Biquad sections — BandPass,
CombFilter, DeEsser, DynamicEQ, GraphicEQ, HighPass, LowPass,
MultibandCompressor, Notch, ParametricEQ — and none of them can
release one 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 Biquad it
ever made — GraphicEQ makes ten per instance, ParametricEQ eight,
LowPass three. Same shape as #58 (audioroute.Splitter) and #60
(audiodynamics.Dynamics); this is the widest of the three.
What the surface is. Measured this session, audiocomponents at
AUDIOIF_PIN 2f6cbc3, 2026-09-07:
$ .venv/bin/python -c "import audiobiquad; n = audiobiquad.Biquad(
mode=audiobiquad.LOW_PASS, frequency=1000.0, Q=0.7071, gain_db=0.0,
mix=0.0, sample_rate=48000, channel_count=2)
print(hasattr(n,'deinit'), sorted(x for x in dir(n) if not x.startswith('_')))"
True ['Q', 'bits_per_sample', 'channel_count', 'clear', 'coefficients',
'deinit', 'frequency', 'gain_db', 'max_buffer_length', 'mix', 'mode',
'play', 'playing', 'sample_rate', 'samples_signed', 'single_buffer',
'stop']
$ cmods/bin/micropython -c "<the same>"
False ['Q', 'bits_per_sample', 'channel_count', 'clear', 'coefficients',
'frequency', 'gain_db', 'mix', 'mode', 'play', 'playing',
'sample_rate', 'stop']
$ cmods/bin/circuitpython-effects -c "<the same>"
False ['Q', 'bits_per_sample', 'channel_count', 'clear', 'coefficients',
'frequency', 'gain_db', 'mix', 'mode', 'play', 'playing',
'sample_rate', 'stop']
deinit, max_buffer_length, samples_signed and single_buffer exist on
the CPython target and on neither native build.
Why it is worse than a leak: it makes a gate row unfalsifiable. With no
deinit() to call, _deinited is never set, so the class's clean run and its
planted fault — a node deliberately left out of the deinit walk — give the
same answer, "0 live". Measured on LowPass
(tools/phase2_probes/lowpass_tier1_portable.py faults): the fault is RED on
CPython (1 of 3 nodes live) and GREEN on both native builds. A pack that
reported that green as a pass would be reporting absence as agreement.
LowPass's probe now prints n/a with this cause instead, and its evidence
pack carries the row as not met.
What would close it. audiobiquad.Biquad gaining deinit() on the
MicroPython and CircuitPython builds, with the same semantics as the CPython
target's: release the node's buffers, mark it deinitialised, and make a
subsequent pull raise rather than dump core (#59 is that second half).
Where this is recorded. docs/effects-phase2-gate-audit.md §3's G2 row
names it for five classes and says an issue is owed;
audiocomponents/docs/effects/LowPass-evidence.md §2 note ², §8 G2 and §11
carry the measurement above.
Stakes. Ten of the sixteen Phase 2
audioeffectsclasses are audioiftier because they build
audiobiquad.Biquadsections —BandPass,CombFilter,DeEsser,DynamicEQ,GraphicEQ,HighPass,LowPass,MultibandCompressor,Notch,ParametricEQ— and none of them canrelease one 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
Biquaditever made —
GraphicEQmakes ten per instance,ParametricEQeight,LowPassthree. Same shape as #58 (audioroute.Splitter) and #60(
audiodynamics.Dynamics); this is the widest of the three.What the surface is. Measured this session,
audiocomponentsatAUDIOIF_PIN2f6cbc3, 2026-09-07:deinit,max_buffer_length,samples_signedandsingle_bufferexist onthe CPython target and on neither native build.
Why it is worse than a leak: it makes a gate row unfalsifiable. With no
deinit()to call,_deinitedis never set, so the class's clean run and itsplanted fault — a node deliberately left out of the deinit walk — give the
same answer, "0 live". Measured on
LowPass(
tools/phase2_probes/lowpass_tier1_portable.py faults): the fault is RED onCPython (1 of 3 nodes live) and GREEN on both native builds. A pack that
reported that green as a pass would be reporting absence as agreement.
LowPass's probe now printsn/awith this cause instead, and its evidencepack carries the row as not met.
What would close it.
audiobiquad.Biquadgainingdeinit()on theMicroPython and CircuitPython builds, with the same semantics as the CPython
target's: release the node's buffers, mark it deinitialised, and make a
subsequent pull raise rather than dump core (#59 is that second half).
Where this is recorded.
docs/effects-phase2-gate-audit.md§3's G2 rownames it for five classes and says an issue is owed;
audiocomponents/docs/effects/LowPass-evidence.md§2 note ², §8 G2 and §11carry the measurement above.