Skip to content

audiobiquad.Biquad has no deinit() on the native builds, so ten audioif-tier filter and EQ classes cannot release one #63

Description

@bdbarnett

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.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions