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
Synthio: switch to per-note biquad filtering #8048
Conversation
the filter cannot be applied as yet.
.. by assigning a new band pass filter to the note every 2 frames
and I verified the filter coefficients against https://arachnoid.com/BiQuadDesigner/ |
this removes a marked DC offset and may cure the 'pops' problem.
Apply envelope & panning after biquad filtering. This may fix the weird popping problem. It also reduces the number of operations that are done "in stereo", so it could help performance. It also fixes a previously unnoticed problem where a ring-modulated waveform had 2x the amplitude of an un-modulated waveform. The test differences look large but it's because some values got changed in the LSB after the mathematical divisions were moved around.
This "survived" testing by @gamblor21 who used it to make some great percussion sounds by frequency-filtering a noise sample... so I dropped it into review status. There are still some potentially useful biquad filters to add, which should be entered as a feature request issue:
|
Hi! Sorry I've not been able to get to this until now. I am having a hard time using this. It seems to be geared to creating static filters that do not change. But filters in synths are usually modulated, a common sound being the low-pass filter sweep bwwwowwww sound. E.g. I would like to do something like the below, but I cannot access the function that goes from frequency & q_factor to biquad coeffs a1,a2,b0,b1,b2: top_f = 1600
q = 0.5
myfilter = synth.low_pass_filter(frequency=top_f, q_factor=q)
note = synthio.Note(frequency= 440, filter=myfilter)
synth.press(note)
while True:
myfilter.frequency = (myfilter.frequency - 10) % f
time.sleep(0.01) |
Two other things:
def adjust_filter(afilter,w0,Q): # f,q
s,c = math.sin(w0), math.cos(w0)
alpha = s / (2 * Q)
a0 = 1 + alpha
afilter.a1 = -2 * c / a0
afilter.a2 = 1 - alpha / a0
afilter.b0 = (1 - c) / 2 / a0
afilter.b1 = 1 - c / a0
afilter.b2 = (1 - c) / 2 / a0
|
Okay this works as expected*: # synthio_filter_test.py --
# 5 Jun 2023 - @todbot / Tod Kurt
import time, random, board, analogio, audiopwmio, synthio
knob1 = analogio.AnalogIn(board.GP26)
knob2 = analogio.AnalogIn(board.GP27)
audio = audiopwmio.PWMAudioOut(board.GP10)
synth = synthio.Synthesizer(sample_rate=22050)
audio.play(synth)
note1 = synthio.Note(frequency=110)
synth.press(note1)
while True:
lpf_f = 10 + knob1.value / 32
lpf_q = knob2.value / 20000
note1.filter = synth.low_pass_filter(lpf_f, lpf_q)
print(lpf_f, lpf_q)
time.sleep(0.1)
But otherwise from my hour or so of testing, I say "ship!" This is a really cool feature that's immediately useful. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As discussed tested manually. Code looks good to me. Really useful update! Thanks.
@jepler Any comments wrt todbot's feedback before I do a final review? |
I don't think my comments should hold this up. The code is behaving as intended, as this is a common issue with filters at high resonance/feedback values. I'm sure we can devise band-aids as needed later. |
Let's slip it in @tannewt ! |
Not tested on hardware yet, but gives plausible results for the new manual host computer test "synthio/note/biquad.py"
My main reference has been https://www.w3.org/TR/audio-eq-cookbook/
each note can have just 0 or 1 biquad filters, there's not a filter topology.
the
filter
property can in theory be dynamically updated but I didn't test it; I think there's not actually a sound mathematical theory of how changing the a/b coefficients dynamically affects things, since the internaly
state of the filter doesn't make sense when coefficients change. (w3 audio api notes that changing coefficients at runtime can "create unstable biquad filters")It's also not currently possible to hook up the biquad filter inputs from blocks/LFOs. This is because of laziness, but also because computing the filter coefficients involves a sin() and cos() call, which are costly. If this design is otherwise adequate, but automation of the biquad filters from LFOs is needed, we can find out whether sin/cos are fast enough (or whether a fixed point sin/cos would be adequate & fast enough).
@gamblor21 @todbot