Skip to content

Add Biquad Filters to audiodelays.Echo and audiofreeverb.Freeverb - #11196

Merged
tannewt merged 7 commits into
adafruit:mainfrom
relic-se:audiodelays-echo-biquads
Aug 19, 2026
Merged

tannewt merged 7 commits into
adafruit:mainfrom
relic-se:audiodelays-echo-biquads

Conversation

@relic-se

Copy link
Copy Markdown

This update expands the capabilities of audiodelays.Echo and audiofreeverb.Freeverb by adding synthio.Biquad filter support internally to their audio processing chain.

Echo

The audiodelays.Echo.filter property filters samples before they are stored in the echo buffer to be recalled later. This can produce a "tape-like" effect when repeated echos are processed through the filter on each iteration.

Freeverb

The audiofreeverb.Freeverb.pre_filter and audiofreeverb.Freeverb.post_filter properties allow the incoming sample to be filtered going into and out of the reverb algorithm. These new "Pre-EQ" and "Post-EQ" allow deeper control and shaping of the reverb effect while maintaining the integrity of the original sample.

Demonstration

# boot.py
import usb_audio
usb_audio.enable(sample_rate=8000, channel_count=1)
# code.py
from audiodelays import Echo
from audiofilters import Filter
from audiofreeverb import Freeverb
import synthio
import time
import ulab.numpy as np
from usb_audio import usb_microphone

FILTER = False

synth = synthio.Synthesizer(
    envelope=synthio.Envelope(
        attack_time=0.05,
        release_time=0.05,
        attack_level=0.7,
        sustain_level=0.7,
    ),
    
    sample_rate=8000,
    channel_count=1,
)

effect_echo = Echo(
    max_delay_ms=200,
    delay_ms=200,
    decay=0.7,
    filter=synthio.Biquad(synthio.FilterMode.LOW_PASS, synthio.LFO(offset=1000, scale=800, rate=0.3), 1.5) if FILTER else None,
    mix=0.5,
    freq_shift=True,
    
    sample_rate=synth.sample_rate,
    channel_count=synth.channel_count,
)

effect_reverb = Freeverb(
    pre_filter=synthio.Biquad(synthio.FilterMode.HIGH_PASS, 200) if FILTER else None,
    post_filter=synthio.Biquad(synthio.FilterMode.LOW_PASS, 1000) if FILTER else None,
    mix=0.5,
    
    sample_rate=synth.sample_rate,
    channel_count=synth.channel_count,
)

# Select effect type
effect = effect_echo

# Audio Chain
effect.play(synth)
usb_microphone.play(effect, loop=True)

# Play through scale
root_note = 60  # middle C
major_scale = [0, 2, 4, 5, 7, 9, 11, 12]
try:
    for note in major_scale:
        synth.press(root_note + note)
        time.sleep(0.1)
        synth.release(root_note + note)
        time.sleep(2)
    time.sleep(2)
except KeyboardInterrupt:
    pass
usb_microphone.stop()

Results

This above program was recorded using Audacity over USB Audio under 4 conditions: dry echo, filtered echo, dry reverb, and filtered reverb. filter-tests.zip

All tests were run by a Waveshare RP2040 Zero. In some tests, audible popping can be heard. This is likely due to buffer underflows caused by the slower processor and the computational requirements of these effects or the experimental nature of usb_audio in its current state.

Notes

  • Originally, I was just going to implement either pre or post filtering on audiofreeverb.Freeverb. After using the effect under each circumstance, I determined that it would be useful to have both options. "Pre-EQ" does a good job of "taming" the reverb by reducing the effect of sudden pops which can cause the reverb to act abruptly. "Post-EQ" is more artistic in the way that it shapes the reverb output.
  • I decided to share much of the filter object handling within a centralized location (audio-module/audiofilters/__init__.c). This has definitely helped simplify implementation and reduce flash usage, but there is likely room for improvement with performance. I'd appreciate direction on how best to share these methods.

@tannewt tannewt left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A couple suggestions. Overall I'm excited for more synth improvements!

Comment thread shared-module/audiofilters/__init__.c Outdated
Comment thread shared-module/audiofilters/__init__.c Outdated
@relic-se

Copy link
Copy Markdown
Author

@tannewt All up-to-date now. Let me know if you'd like to see any other changes.

@tannewt tannewt left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks great! Nice and clean. Thanks!

@tannewt
tannewt merged commit 1b3c2bd into adafruit:main Aug 19, 2026
12 checks passed
@relic-se
relic-se deleted the audiodelays-echo-biquads branch August 19, 2026 22:55
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants