Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Audio effects: Filters #9744

Merged
merged 6 commits into from
Oct 23, 2024
Merged

Add Audio effects: Filters #9744

merged 6 commits into from
Oct 23, 2024

Conversation

relic-se
Copy link

@relic-se relic-se commented Oct 22, 2024

This is an expansion of the work on #9640 to begin creating "filter-based" audio effects (original issue: #8974).

The only effect added to this module at the moment is the standard Filter effect which utilizes the Biquad filter system borrowed from synthio so that it can be processed on other audio sources (ie: wave file, microphone, etc). The downside with sharing this processing with synthio is that a synthio.Synthesizer object needs to be created in order to generate Biquad objects using the low_pass_filter, high_pass_filter, and band_pass_filter methods. A synthio.Biquad object can be directly created, but that isn't ideal.

To do:

  • Create additional filter effects (tbd)
  • Validate documentation
  • Test effects against varying audio sources (8-bit, unsigned, etc)

A simple example using audio generated with synthio.Note is included in the documentation for audiofilters.Filter, but it is not a good demonstration of the utility of this effect. Here is a better example using a different audio source (the "StreetChicken.wav" file provided in https://learn.adafruit.com/circuitpython-essentials/circuitpython-audio-out):

import board
import audiobusio
import synthio
import audiofilters
import audiocore
import digitalio
import adafruit_debouncer

Q = 2.0

audio = audiobusio.I2SOut(bit_clock=board.GP2, word_select=board.GP3, data=board.GP4)

# Load audio file
wave_file = open("StreetChicken.wav", "rb")
wave = audiocore.WaveFile(wave_file)

# Used to generate Biquad objects
synth = synthio.Synthesizer(channel_count=wave.channel_count, sample_rate=wave.sample_rate)

# Create filter effect (`filter=None` upon initialization)
filter = audiofilters.Filter(buffer_size=1024, channel_count=wave.channel_count, sample_rate=wave.sample_rate, mix=1.0)
filter.play(wave, loop=True)
audio.play(filter)

# Buttons to control filter frequency
button_up_pin = digitalio.DigitalInOut(board.GP0)
button_up_pin.direction = digitalio.Direction.INPUT
#button_up_pin.pull = digitalio.Pull.UP # pull-up resistor on board
button_up = adafruit_debouncer.Debouncer(button_up_pin)

button_down_pin = digitalio.DigitalInOut(board.GP1)
button_down_pin.direction = digitalio.Direction.INPUT
#button_down_pin.pull = digitalio.Pull.UP # pull-up resistor on board
button_down = adafruit_debouncer.Debouncer(button_down_pin)

position = 1.0 # Relative frequency position
def update_filter():
    # Frequency is exponential based on relative position
    frequency = (min(max(position, 0.0), 1.0) ** 2) * (wave.sample_rate / 2 - 25) + 25
    print("{}hz".format(frequency))
    filter.filter = synth.low_pass_filter(frequency, Q)
update_filter() # Set initial filter state

while True:
    button_up.update()
    if button_up.rose:
        position = min(position + 0.025, 1.0)
        update_filter()
        
    button_down.update()
    if button_down.rose:
        position = max(position - 0.025, 0.0)
        update_filter()

I'd appreciate any feedback on the implementation of this effect and what other filter-based effects to begin development on.

Thanks to @gamblor21 for setting down the foundation to get this effects system up and running. It was surprisingly easy to get this new module added based on his work on audiodelays.

@relic-se
Copy link
Author

Here's a video to provide a quick explanation of the module and a demonstration of the effect: https://www.youtube.com/watch?v=PoQpTWsgTkw

@dhalbert
Copy link
Collaborator

This error occurred in the doc build:

audiofilters/__init__.pyi:17: error: Incompatible default for argument "filter" (default has type "None", argument has type "Biquad")  [assignment]
audiofilters/__init__.pyi:17: note: PEP 484 prohibits implicit Optional. Accordingly, mypy has changed its default to no_implicit_optional=True
audiofilters/__init__.pyi:17: note: Use https://github.com/hauntsaninja/no_implicit_optional to automatically upgrade your codebase
Found 1 error in 1 file (checked 111 source files)

@relic-se
Copy link
Author

Thanks for catching that. I hadn't built the docs yet myself. I'll have it fixed today.

Copy link
Member

@gamblor21 gamblor21 left a comment

Choose a reason for hiding this comment

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

Thanks, looks good! I added a couple of comments.
I tested it and worked for me.

shared-module/audiofilters/Filter.c Outdated Show resolved Hide resolved
shared-module/audiofilters/Filter.c Outdated Show resolved Hide resolved
@relic-se
Copy link
Author

@dhalbert The documentation should be fixed. It's fairly straightforward for now, so let me know if there's anything else that needs to be updated within the docs.

@tannewt tannewt requested a review from dhalbert October 23, 2024 16:58
@dhalbert
Copy link
Collaborator

@dcooperdalrymple This looks fine, but it has three unchecked boxes in the task list in the first post. Is there further testing and documentation to do? If so, maybe we should make this draft? Or are you done?

@dhalbert
Copy link
Collaborator

dhalbert commented Oct 23, 2024

After talking with @tannewt, I'll merge this for 9.2.0, but label both audiodelays and audioeffectsaudiofilters as "experimental: API may change". So people can try them but also we can feel free to modify the API.

Copy link
Collaborator

@dhalbert dhalbert left a comment

Choose a reason for hiding this comment

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

Thank you!

@dhalbert dhalbert merged commit 38779cd into adafruit:main Oct 23, 2024
30 checks passed
@relic-se
Copy link
Author

relic-se commented Oct 23, 2024

Thanks for the merge, @dhalbert !

@dcooperdalrymple This looks fine, but it has three unchecked boxes in the task list in the first post. Is there further testing and documentation to do? If so, maybe we should make this draft? Or are you done?

The work listed in the original checklist is mostly done. I think it's best to keep additional effects within this module to separate PRs.

I do have a minor modification in mind to the audiofilters.Filter class (removing reliance on synthio.Synthesizer to generate synthio.Biquad objects), but it shouldn't affect the existing implementation. I'll draft that up as a new PR in the near future.

After talking with @tannewt, I'll merge this for 9.2.0, but label both audiodelays and audioeffects`audiofilters as "experimental: API may change". So people can try them but also we can feel free to modify the API.

Sounds great! Minor typo to change audioeffects to audiofilters.

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.

3 participants