Skip to content

synthio filters glitch at certain frequencies with Q > 1.41 #10200

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

Closed
todbot opened this issue Mar 29, 2025 · 9 comments
Closed

synthio filters glitch at certain frequencies with Q > 1.41 #10200

todbot opened this issue Mar 29, 2025 · 9 comments
Labels
audio bug rp2 Both RP2 microcontrollers
Milestone

Comments

@todbot
Copy link

todbot commented Mar 29, 2025

CircuitPython version and board name

Adafruit CircuitPython 9.2.6 on 2025-03-23; Raspberry Pi Pico 2 with rp2350a
Adafruit CircuitPython 9.2.6 on 2025-03-23; Raspberry Pi Pico with rp2040

Code/REPL

"""
filter Q over 1.41 causes glitches in the filter at certain frequency
"""

import time, board, synthio, audiobusio, audiomixer

SAMPLE_RATE = 44100
BUFFER_SIZE = 2048
i2s_bck_pin = board.GP20
i2s_lck_pin = board.GP21
i2s_dat_pin = board.GP22

audio = audiobusio.I2SOut(bit_clock=i2s_bck_pin, word_select=i2s_lck_pin, data=i2s_dat_pin)
mixer = audiomixer.Mixer(sample_rate=SAMPLE_RATE, channel_count=2, buffer_size=BUFFER_SIZE)
synth = synthio.Synthesizer(sample_rate=SAMPLE_RATE, channel_count=2)
audio.play(mixer)
mixer.voice[0].play(synth)

midi_notes = [ 49,  # glitch near 170 & 150 Hz
               37,  # glitch near 100, 70, & 60 Hz
               36,  # gitch near 70 & 60 Hz
               42,  # glitch near 100 Hz
               47,  # glitch near 120 Hz
              ]
while True:
    for n in midi_notes:
        midi_note_f = synthio.midi_to_hz(n)
        note = synthio.Note(midi_note_f)
        note.filter = synthio.BlockBiquad(synthio.FilterMode.LOW_PASS,
                                          frequency=200, Q=1.5)
        synth.press(note)
        for f in range(21):  # modulate the filter down by hand to find glitches
            filter_freq = 200 - (10*f)
            print("Q: %.2f filter freq: %3d note freq: %3.2f" %
                  (note.filter.Q, filter_freq, midi_note_f))
            note.filter.frequency = filter_freq
            time.sleep(0.3)        
        synth.release(note)

Behavior

When synthio.BlockBiquad is set to certain frequencies with Q>1.41, strange oscillations / "glitches" occur that should not. In general it seems to be around the pitch of the playing note and lower.

Description

Here's a video showing the above code running on a Pico2 RP2350 driving an I2S DAC.
The behavior is the same on a Pico RP2040 with I2S DAC. The behavior is also the same with PWMOut.
This behavior has existed since synthio has had filters, but with BlockBiquad it's more noticeable because we can easily modulate the filter frequency.

synthio_filter_glitch.mp4

Additional information

No response

@todbot todbot added the bug label Mar 29, 2025
@tannewt tannewt added audio rp2 Both RP2 microcontrollers labels Mar 31, 2025
@tannewt tannewt added this to the Long term milestone Mar 31, 2025
@gamblor21
Copy link
Member

Took a quick look, the waveform (in Audacity) looks like a overflow error. Quickly traced it back and in:

static void sum_with_loudness(int32_t *out_buffer32, int32_t *tmp_buffer32, int16_t loudness[2], size_t dur, int synth_chan) {

there is a likely overflow error (int32_t * int16_t into a int32_t) that Jeff noticed when we were discussing fixed point. I think this is the same error that was causing some random distortion I was getting with louder volumes and multiple voices.

@todbot
Copy link
Author

todbot commented Apr 21, 2025

That makes sense. At higher resonance, you can get large volume peaks. I think some other filter emulations handle this by having a rate-limiter or some other cheap way to "soft-saturate" the output.

I also hear distortion at times with many voices that I bet it's due to this. So if we find a good cheap fixed-point saturation algorithm, maybe we put it in sum_with_loudness().

@gamblor21
Copy link
Member

I also hear distortion at times with many voices that I bet it's due to this. So if we find a good cheap fixed-point saturation algorithm, maybe we put it in sum_with_loudness().

I use one already in Reverb so when I have a moment I'm going to look through synthio and try to catch places where we are letting things get too large. Probably should be its own issue but your report is easy to reproduce. I never could find a magic way.

@todbot
Copy link
Author

todbot commented Apr 21, 2025

I've not looked, but it seems that whatever audiofilters.Filter is doing is different. That is, by moving to using an audiofilter instead of a per-Note filter like below, the glitches are gone, but there are odd bumps in volume at the glitch frequencies.

""" .... set up audio system including a mixer and synth """

filter1 = audiofilters.Filter(buffer_size=1024,
                              channel_count=CHANNEL_COUNT,
                              sample_rate=SAMPLE_RATE,
                              mix=1.0)
filter1.filter = synthio.Biquad(synthio.FilterMode.LOW_PASS,
                         frequency=200, Q=1.5)
mixer.voice[0].play(filter1)
filter1.play(synth)

midi_notes = [ 49,  # glitch near 170 & 150 Hz
               37,  # loud near 100, 70, & 60 Hz
               36,  # loud near 70 & 60 Hz
               42,  # loud near 100 Hz
               47,  # loud near 120 Hz
              ]

while True:
    for n in midi_notes:
        midi_note_f = synthio.midi_to_hz(n)
        note = synthio.Note(midi_note_f)
        synth.press(note)
        for f in range(21):  # modulate the filter down by hand to find glitches
            filter_freq = 200 - (10*f)
            print("Q: %.2f filter freq: %3d note freq: %3.2f" %
                  (filter1.filter.Q, filter_freq, midi_note_f))
            filter1.filter.frequency = filter_freq
            time.sleep(0.3)        
        synth.release(note)

@gamblor21
Copy link
Member

I've not looked, but it seems that whatever audiofilters.Filter is doing is different. That is, by moving to using an audiofilter instead of a per-Note filter like below, the glitches are gone, but there are odd bumps in volume at the glitch frequencies.

audiofilters.Filter uses a different code (and we have discussed removing the synth specific but I can't remember the outcome). So the Filter class won't overflow the same way synthio filters are. I am still going to try to fix the underlying issue as it could effect more then just the filters. But using audiofilters.Filter is probably safer to use at the moment.

@relic-se
Copy link

Thank you for researching into this, @todbot! I think all of us synthio folks have been aware of this problem for a while. I'd love to push the filter harder, but it doesn't take long to run into this error. I'm sure that audiofilters.Filter is implementing summing slightly better, but I'd be very interested to see what your research within audiofreeverb.Freeverb can do for this issue, @gamblor21.

@gamblor21
Copy link
Member

#10288 fixed this for me @todbot if you have a chance I'd love you to give it a try.

@todbot
Copy link
Author

todbot commented Apr 25, 2025

Oh my goodness, yes! Thank you. The #10288 patch makes the filter distortion non-existent! Much easier to do chords other other multi-note stuff now too.

@gamblor21
Copy link
Member

Fixed by #10288

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
audio bug rp2 Both RP2 microcontrollers
Projects
None yet
Development

No branches or pull requests

4 participants