Comb Filter

Status
Not open for further replies.

unicornpower

Well-known member
Hi,

I'm trying to code a very simple comb filter using Teensy and the Audio Shield. I'm trying to copy something like the Resonator in Ableton. I plan to use it as a guitar effect, so when I play certain notes they ring out more than others. I'm using the delay object included in the audio library, but setting the delay time so that it is tuned to resonate at a certain pitch, using this simple calculation.

Code:
float calculate_delay_time_ms( float resonant_frequency )
{
  return 1000.0f / resonant_frequency;
}

I have this setup so that I have both a feed forward, and a feed back comb filter. Only the feed forward one is currently enabled. I have added some simple test tone code. What I would expect to see, is that when the test tone matches the frequency that the comb filter is tuned to, the amplitude would essentially double. But depending on what note it's tuned to, the highest amplitude is often seen one tone up? I am seeing amplitude increase at the expected frequency, but not as much as 1 tone up. I suspect this is down to some error in my understanding of comb filters, but if someone more knowledgeable could point it out I'd be very appreciative! Full code included below for reference..

Code:
#include <Audio.h>
#include <Wire.h>
#include <SPI.h>
#include <SD.h>
#include <SerialFlash.h>

#define TEST_TONE

// Use these with the audio adaptor board
#define SDCARD_CS_PIN    10
#define SDCARD_MOSI_PIN  7
#define SDCARD_SCK_PIN   14

const int                DRY_SIGNAL_CHANNEL( 0 );
const int                FEED_FORWARD_CHANNEL( 1 );
const int                FEED_BACKWARD_CHANNEL( 2 );

const int                RESONANT_FREQ_PIN( 20 );
const int                RESONANCE_TIME_PIN( 17 );

/*
const float              FREQUENCIES[] =   {  65.41,            // C2
                                              69.30,            // C#2
                                              73.42,            // D2
                                              77.78,            // D#2
                                              82.41,            // E2
                                              87.31,            // F2
                                              92.50,            // F#2
                                              98.00,            // G2
                                              103.83,           // G#2
                                              110.00,           // A2
                                              116.54,           // A#2
                                              123.47,           // B2
                                            };
*/
const float              FREQUENCIES[] =   {  261.63,            // C4
                                              277.18,            // C#4
                                              293.66,            // D4
                                              311.13,            // D#4
                                              329.63,            // E4
                                              349.23,            // F4
                                              369.99,            // F#4
                                              392.00,            // G4
                                              415.30,           // G#4
                                              440.00,           // A4
                                              466.16,           // A#4
                                              493.88,           // B4
                                            };

#ifdef TEST_TONE

AudioSynthWaveformSine   test_tone;
AudioOutputI2S           audio_output;
AudioControlSGTL5000     sgtl5000_1;

AudioMixer4              delay_mixer;
AudioEffectDelay         feed_forward_delay;
AudioEffectDelay         feed_back_delay;

AudioConnection          patch_cord_1( test_tone, 0, delay_mixer, DRY_SIGNAL_CHANNEL );
AudioConnection          patch_cord_2( delay_mixer, 0, audio_output, 0 );
AudioConnection          patch_cord_3( test_tone, 0, feed_forward_delay, 0 );
AudioConnection          patch_cord_4( feed_forward_delay, 0, delay_mixer, FEED_FORWARD_CHANNEL );
AudioConnection          patch_cord_5( delay_mixer, 0, feed_back_delay, 0 );
AudioConnection          patch_cord_6( feed_back_delay, 0, delay_mixer, FEED_BACKWARD_CHANNEL );

#else // !TEST_TONE

AudioInputI2S            audio_input;
AudioOutputI2S           audio_output;
AudioControlSGTL5000     sgtl5000_1;

AudioMixer4              delay_mixer;
AudioEffectDelay         feed_forward_delay;
AudioEffectDelay         feed_back_delay;

AudioConnection          patch_cord_1( audio_input, 0, delay_mixer, DRY_SIGNAL_CHANNEL );
AudioConnection          patch_cord_2( delay_mixer, 0, audio_output, 0 );
AudioConnection          patch_cord_3( audio_input, 0, feed_forward_delay, 0 );
AudioConnection          patch_cord_4( feed_forward_delay, 0, delay_mixer, FEED_FORWARD_CHANNEL );
AudioConnection          patch_cord_5( delay_mixer, 0, feed_back_delay, 0 );
AudioConnection          patch_cord_6( feed_back_delay, 0, delay_mixer, FEED_BACKWARD_CHANNEL );

#endif // !TEST_TONE

float calculate_delay_time_ms( float resonant_frequency )
{
  return 1000.0f / resonant_frequency;
}

float calculate_feedback_multiplier( float delay_time_ms, float resonance_time_ms )
{
  const float p = delay_time_ms / resonance_time_ms;
  return powf( 0.001, p );
}

void setup()
{
  AudioMemory( 128 );

  sgtl5000_1.enable();
  sgtl5000_1.volume(0.8f);

  sgtl5000_1.lineInLevel( 10 );  // 0.56volts p-p
  sgtl5000_1.lineOutLevel( 13 );  // 3.16volts p-p

  Serial.begin(9600);
  delay( 1000 );
  
 #ifdef TEST_TONE
  test_tone.amplitude( 0.25f );
#endif

  Serial.print("Setup started!\n");

  const float delay_time_ms       = calculate_delay_time_ms( FREQUENCIES[2] ); // tuned to D2
  const float feedback_mult       = calculate_feedback_multiplier( delay_time_ms, 1000.0f );

  feed_forward_delay.delay( 0, delay_time_ms );
  feed_back_delay.delay( 0, delay_time_ms );

  delay_mixer.gain( DRY_SIGNAL_CHANNEL, 0.5f );
  delay_mixer.gain( FEED_BACKWARD_CHANNEL, feedback_mult * 0.0f );
  delay_mixer.gain( FEED_FORWARD_CHANNEL, feedback_mult * 0.5f );

  Serial.print("Delay time ms:");
  Serial.print(delay_time_ms);
  Serial.print(" feedback mult:");
  Serial.print(feedback_mult);
  Serial.print("\nSetup finished!\n");
}

void loop()
{
#ifdef TEST_TONE

    static int current_tone_index = 0;

    test_tone.frequency( FREQUENCIES[current_tone_index] );

    Serial.print("Tone index:");
    Serial.print( current_tone_index );
    Serial.print(" Freq:");
    Serial.println( FREQUENCIES[current_tone_index] );
    
    delay( 2000 );
    current_tone_index = ( current_tone_index + 1 ) % 12;

#endif
}
 
Thanks, Paul. From what I can see from the code, this defines LBCF, which is a feedback comb filter which also includes a low-pass filter on the feedback loop?

I wanted to start very simply with my resonator. My understanding was that a simple comb filter is just a delay line which either feeds forwards or backwards, and mixes with the original signal. The delay time is set such that, to filter out a frequency, you would set the delay line to half the wavelength of the frequency you want to filter. So the delay is 180 degrees out of phase with the original signal, and the desired frequency is cancelled out. If you want to resonate, you set the delay time to the wavelength of the frequency you want to resonate at, so the waves are in phase, thus doubling the amplitude. Something is definitely wrong with my understanding though, as it doesn't appear to work like that. :)
 
Yeah, but maybe the code can at least save you some time, rather than starting from scratch?

On my long-term todo list is adding general purpose comb and allpass filters. Truth is, I really don't know much about their applications outside of reverb and echo effects. I can't promise I'll get to this quickly, but I'm curious to hear any input about features I should consider for someday adding such filters.
 
Yeah, but maybe the code can at least save you some time, rather than starting from scratch?

On my long-term todo list is adding general purpose comb and allpass filters. Truth is, I really don't know much about their applications outside of reverb and echo effects. I can't promise I'll get to this quickly, but I'm curious to hear any input about features I should consider for someday adding such filters.

Just saw this and thought I'd respond. I know this is an extremely old thread but none-the-less: There are a few great uses for comb filters outside of reverbs and echo effects! The primary use case outside of effects is for physical modeling synthesis. For example, if I wanted to create an extremely realistic flute sound, I could use a comb filter on white noise to provide a pitched noise effect similar to how blowing air into a flute would create a pitched resonance.
Here's an example: https://www.youtube.com/watch?v=wVul3xKs7xA
To physical model any resonating instruments, comb filters are a must. Even outside of resonating instruments, comb filters are used in basically every facet of physical modeling, just to a lesser extent. I'm currently in the process of creating one with pitch and resonance control for the audio library, however, I am far from a pro when it comes to DSP (and coding in general). Regardless, Ill make a new post in a few days sharing my progress if I get anywhere close to coding a usable comb.

All the best,
Noah
 
The problem the OP had was that AudioEffectDelay doesn't do sub-sampletime delays, only integer multiples of the
sampletime, so the comb frequencies available were quantized. Sub-sample delay requires inter-sample interpolation,
which for high quality will require oversampling, polynomial interpolation and finally decimation back to the incoming
sample rate.

If you care less about HF performance you can avoid oversampling/decimation making it much more efficient, but you'll
still need better than linear interpolation for low artifacts (aka LERP is not enough)
 
The problem the OP had was that AudioEffectDelay doesn't do sub-sampletime delays, only integer multiples of the
sampletime...

Wow, that explains a lot. I was experimenting yesterday and found that a feed forward/back delay line was causing a comb effect, just not in any harmonic ratio. Quantization due to the sample rate perfectly explains that.

https://www.dsprelated.com/freebooks/filters/Analysis_Digital_Comb_Filter.html

This article contains a section about a 1/3rd of the way down the page on impulse response comb filtering. I haven't done anything with FIRs before, so this is going to take a good bit of trial and error. Any reason why this method wouldn't work? The biggest downside I see is a lack of built in frequency control for the FIR filter in the audio library (again, I don't know anything about FIR yet, so I could be wrong here).
 
Digital filters don't have a frequency control, the filter locks the frequencies into the impulse response. You have to generate different filters for different frequencies - not convenient for a swept comb filter like a flanger or phaser. A comb filter is just a filter, any method of filter implementation can work, the
poles and zeroes are the same whatever the method. But using a delay + interpolation is very powerful if you want to time-vary the response of a comb.
 
Status
Not open for further replies.
Back
Top