How to use LFO for filter cutoff, can’t make it work

Hi, got an effect to a stage i like and it’s working. I’m trying to use an LFO as found in this example, but removing the oscillator obviously:
Screenshot 2024-02-28 at 13.17.18.png

Code:
#include <math.h>
 
   #include <Audio.h>
   #include <Wire.h>
   #include <SPI.h>
   #include <SD.h>
   #include <SerialFlash.h>
 
   // GUItool: begin automatically generated code
   AudioSynthWaveformDc     pitchAmt;            //xy=90,133
   AudioSynthWaveform       LFO;      //xy=90,194
   AudioSynthWaveformDc     filterAmt; //xy=90,255
   AudioEffectMultiply      multiply;      //xy=247,160
   AudioEffectMultiply      multiply1; //xy=250,233
   AudioSynthWaveformModulated oscillator;   //xy=396,166
   AudioFilterStateVariable filter;        //xy=541,224
   AudioOutputI2S           i2s;            //xy=707,208
   AudioConnection          patchCord1(pitchAmt, 0, multiply, 0);
   AudioConnection          patchCord2(LFO, 0, multiply, 1);
   AudioConnection          patchCord3(LFO, 0, multiply1, 0);
   AudioConnection          patchCord4(filterAmt, 0, multiply1, 1);
   AudioConnection          patchCord5(multiply, 0, oscillator, 0);
   AudioConnection          patchCord6(multiply1, 0, filter, 1);
   AudioConnection          patchCord7(oscillator, 0, filter, 0);
   AudioConnection          patchCord8(filter, 0, i2s, 0);
   AudioConnection          patchCord9(filter, 0, i2s, 1);
   AudioControlSGTL5000     sgtl5000;       //xy=618,140
   // GUItool: end automatically generated code
 
   #define PITCH_POT A0
   #define CUTOFF_POT A1
   #define RATE_POT A2
   #define AMOUNT_POT A3
 
   #define LED 3
   #define PITCH_FLT_SW 4
 
   int pitch = 220;
   int cutoff = 2000;
   int rate = 0;
   int amount = 0;
   bool isPitch = true;
 
   // pitch scales logarithmically
   float inputToPitch(int input)
   {
       int n = map(input, 0, 1023, 21, 108);
       return 440 * pow(2, (n - 69) / 12.0);
   }
 
   // input is from 0 to 127
   void setCutoff(int u)
   {
       // Use an exponential curve from 50Hz to about 12kHz
       float co = 50 * exp(5.481 * u / 127.0);
       filter.frequency(co);
       filter.octaveControl(log2f(12000.0 / (float)co));
   }
 
   void setLFORate(int u)
   {
   // convert log scale 0 to 127 to a linear rate
   // f(1) = 0.5Hz, f(127) = 40Hz
       float f = exp(u / 25.0) / 4.0;
       LFO.frequency(f);
   }
 
   void updateLFO()
   {
       digitalWrite(LED, isPitch);
       pitchAmt.amplitude(isPitch ? amount / 127.0 : 0);
       filterAmt.amplitude(isPitch ? 0 : amount / 127.0);
   }
 
   void setup()
   {
       // reserve some memory for the audio functions
       AudioMemory(20);
       // enable the audio control chip on the Audio Shield
       sgtl5000.enable();
       sgtl5000.volume(0.5);
 
       // setup the two pins for listening to the pitch and amplitude controls
       pinMode(PITCH_POT, INPUT);
       pinMode(CUTOFF_POT, INPUT);
       pinMode(RATE_POT, INPUT);
       pinMode(AMOUNT_POT, INPUT);
    
       // and the switch/LED pins
       pinMode(LED, OUTPUT);
       pinMode(PITCH_FLT_SW, INPUT_PULLUP);
       updateLFO();
 
       // configure and start the oscillator object
       oscillator.amplitude(0.5);
       oscillator.frequency(pitch);
       oscillator.begin(WAVEFORM_SAWTOOTH);
       oscillator.frequencyModulation(1);
 
       setLFORate(rate);
       LFO.amplitude(1);
       LFO.begin(WAVEFORM_TRIANGLE);
   }
 
   void loop()
   {
       static long lastpress = 0;
       // are we modulating pitch or filter?
       if ((digitalRead(PITCH_FLT_SW) == 0) && (millis() - lastpress > 200))  // switch is on (line pulled low)
       {
           lastpress = millis();
           isPitch = !isPitch;
           updateLFO();
       }
    
       // read the pitch pot position
       int newpitch = analogRead(PITCH_POT);
       // has it changed?
       if (newpitch != pitch)
       {
           // update if it has
           pitch = newpitch;
           oscillator.frequency(inputToPitch(newpitch));
       }
    
       // read the cutoff pot position
       int newcutoff = analogRead(CUTOFF_POT) >> 3;
       // has it changed?
       if (newcutoff != cutoff)
       {
           // update if it has
           cutoff = newcutoff;
           setCutoff(cutoff);
       }
    
       // read the LFO rate pot position
       int newrate = analogRead(RATE_POT) >> 3;
       // has it changed?
       if (newrate != rate)
       {
           // update if it has
           rate = newrate;
           setLFORate(rate);
       }
    
       // read the amount pot position
       int newamount = analogRead(AMOUNT_POT) >> 3;
       // has it changed?
       if (newamount != amount)
       {
           // update if it has
           amount = newamount;
           updateLFO();
       }
   }

I’m trying to implement it in my code to modulate the state variable filter cutoff, however i’m not getting any output currently. I’ve manually ‘patched’ things in so perhaps it’s a mistake there or it’s a problem with how i’m trying to integrate the LFO to my code. In the above code i can sort of see how the LFO works and how it reads the pot values to update speed and depth but i can’t actually figure out what it’s doing with this output.

Anyway, here is where i’m at. I would be extremely grateful for any thoughts or pointers. Apologies if the code is an absolute casserole.
Code:
#define LED 3
#include <Bounce.h>
#include <ResponsiveAnalogRead.h>
#include <vibrato.h>
#include "effect_delay10tap.h"
#include <math.h>

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

// GUItool: begin automatically generated code
//LFO BITS
AudioSynthWaveform       LFO;      //xy=90,194
AudioSynthWaveformDc     filterAmt; //xy=90,255
AudioEffectMultiply      multiply; //xy=250,233

//FX BITS
AudioInputI2S            i2s1;           //xy=82,344
AudioMixer4              mixer1;         //xy=162,96
AudioEffectBitcrusher    bitcrusher1;    //xy=255,27
AudioEffectDelay10tap delay1;         //xy=307,220
AudioAmplifier           amp1;           //xy=409,29
AudioMixer4              mixer2;         //xy=462,224
AudioFilterStateVariable filter1;         //xy=535,37
AudioFilterBiquad        biquad1;
AudioOutputI2S           i2s2;           //xy=746,214
AudioEffect_Vibrato      vibrato1;

//PATCHING
AudioConnection          patchCord1(i2s1, 0, vibrato1, 0);
AudioConnection          patchCord2(vibrato1, 0, mixer1, 0);
AudioConnection          patchCord3(i2s1, 0, bitcrusher1, 0);
AudioConnection          patchCord4(i2s1, 0, mixer2, 1);
AudioConnection          patchCord5(mixer1, delay1);
AudioConnection          patchCord6(bitcrusher1, amp1);
AudioConnection          patchCord7(delay1, 0, mixer1, 1);
AudioConnection          patchCord8(delay1, 0, mixer2, 0);
AudioConnection          patchCord9(delay1, 1, mixer2, 2);
AudioConnection          patchCord10(amp1, biquad1);
AudioConnection          patchCord11(biquad1, filter1);
AudioConnection          patchCord12(mixer2, 0, i2s2, 0);
AudioConnection          patchCord13(filter1, 1, mixer1, 2);

AudioConnection          patchCord14(LFO, 0, multiply, 0);
AudioConnection          patchCord15(filterAmt, 0, multiply, 1);
AudioConnection          patchCord16(multiply, 0, filter1, 1);

AudioControlSGTL5000     sgtl5000_1;     //xy=767,328


#define RATE_POT A5
#define AMOUNT_POT A6
#define LED 3
#define PITCH_FLT_SW 4

int LFOrate = 0;
int amount = 0;
bool isPitch = true;

void setCutoff(int u)
{
  // Use an exponential curve from 50Hz to about 12kHz
  float co = 50 * exp(5.481 * u / 127.0);
  filter1.frequency(co);
  filter1.resonance (5);
  filter1.octaveControl(log2f(12000.0 / (float)co));
}

void setLFORate(int u)
{
  // convert log scale 0 to 127 to a linear rate
  // f(1) = 0.5Hz, f(127) = 40Hz
  float f = exp(u / 25.0) / 4.0;
  LFO.frequency(f);
}

void updateLFO()
{
  digitalWrite(LED, isPitch);

  filterAmt.amplitude(isPitch ? 0 : amount / 127.0);
}




// delay line
#define DELAYLINE_MAX_LEN 45159  // number of samples at 44100 samples a second
int16_t delay_line[DELAYLINE_MAX_LEN] = {};

// main timing loop
#define LOOP0_DURATION 20  // interval time in millis
elapsedMillis loop0_timer;
// pot to control delaytime
const int DELAY_TIME_KNOB_PIN = A4;  // A12 gain
ResponsiveAnalogRead delayTimeKnob(DELAY_TIME_KNOB_PIN, true);



/*
  Bounce footswitch = Bounce(0, 50);  // debounce the footswitch
  Bounce D1 = Bounce(1, 50);          // debounce the toggle switch
  Bounce D2 = Bounce(2, 50);          // "  "  "  "  "  "  "  "  "

  // this section includes the function to check the toggle position
  bool right;
  bool middle;
  bool left;
  void checkToggle () {               // this is our function to check toggle position...
  D1.update();  D2.update();          // check digital inputs connected to toggle (can delete I think)
  if (digitalRead(1) && !digitalRead(2))   {
    right = 1;  // toggle is right
    middle = 0;
    left = 0;
  }
  if (digitalRead(1) && digitalRead(2))  {
    right = 0;  // toggle is in the middle
    middle = 1;
    left = 0;
  }
  if (!digitalRead(1) && digitalRead(2))   {
    right = 0;  // toggle is left
    middle = 0;
    left = 1;
  }
  }
*/

byte bitdepth = 16;                 // used to set bit depth
int samplerate = 44100;             // used to set sample rate
float mix;                          // clean/delay mix
float mix1;                        // clean/delay mix
float feedback;                    // delay feedback
int bandpass;                      //bandpass cutoff frequency
int delaytime;





void setup() {

  pinMode(RATE_POT, INPUT);
  pinMode(AMOUNT_POT, INPUT);

  pinMode(LED, OUTPUT);
  pinMode(PITCH_FLT_SW, INPUT_PULLUP);
  updateLFO();

  setLFORate(LFOrate);
  LFO.amplitude(1);
  LFO.begin(WAVEFORM_TRIANGLE);

  AudioMemory(400); // the "40" represents how much internal memory (in the Teensy, not the external RAM chip) is allotted for audio recording. It is measured in sample blocks, each providing 2.9ms of audio.
  sgtl5000_1.enable();    // this turns on the SGTL5000, which is the audio codec on the audio board
  sgtl5000_1.volume(1);   // this sets the output volume (it can be between 0 and 1)
  sgtl5000_1.inputSelect(AUDIO_INPUT_LINEIN); // selects the audio input, we always use Line In
  // analogReadResolution(10); // configure the pots to give 12 bit readings
  pinMode(0, INPUT_PULLUP); // internal pull-up resistor for footswitch
  pinMode(1, INPUT_PULLUP); // internal pull-up resistor for toggle
  pinMode(2, INPUT_PULLUP); // internal pull-up resistor for toggle
  pinMode(3, OUTPUT);       // pin 3 (the LED) is an output;
  Serial.begin(115200);       // initiate the serial monitor. USB is always 12 Mbit/sec
  sgtl5000_1.audioPostProcessorEnable();
  //analogReadAveraging(10);



  // start up the effect and pass it an array to store the samples
  delay1.begin(delay_line, DELAYLINE_MAX_LEN);

}

float VibeRate = 1;
float VibeDepth = 10;



void loop() {

  static long lastpress = 0;
  // are we modulating pitch or filter?
  if ((digitalRead(PITCH_FLT_SW) == 0) && (millis() - lastpress > 200))  // switch is on (line pulled low)
  {
    lastpress = millis();
    isPitch = !isPitch;
    updateLFO();
  }



  // read the LFO rate pot position
  int newrate = analogRead(RATE_POT) >> 3;
  // has it changed?
  if (newrate != LFOrate)
  {
    // update if it has
    LFOrate = newrate;
    setLFORate(LFOrate);
  }

  // read the LFO amount pot position
  int newamount = analogRead(AMOUNT_POT) >> 3;
  // has it changed?
  if (newamount != amount)
  {
    // update if it has
    amount = newamount;
    updateLFO();
  }



  vibrato1.modulation(VibeRate, VibeDepth);

  // loop timer
  if (loop0_timer >= LOOP0_DURATION) {
    // update and check the pot
    delayTimeKnob.update();
    if (delayTimeKnob.hasChanged()) {
      //Serial.printf("Delay Time Knob:%d\n", delayTimeKnob.getValue());
      delay1.delaysmooth(0, delayTimeKnob.getValue());
    }
    loop0_timer = 0;
  }


  feedback = (float) analogRead (A2) / 1023; // delay feedback

  mix = (float) analogRead (A0) / 1023; // wet/dry mix
  //Serial.println (mix);
  mix1 = 1.0 - mix;

  amp1.gain(1);


  //Set mixer levels
  mixer1.gain (0, 0.5); // clean gtr in level
  mixer1.gain (1, feedback);
  mixer1.gain (2, 1);

  mixer2.gain (0, mix); // fx signal
  mixer2.gain (1, 0.5); // dry signal


  // do bitcrushing
  bitdepth = (16);
  bitcrusher1.bits(bitdepth);   // set bit depth

  samplerate = (analogRead(A1) << 1) + 1000;        // samplerate pot ranges from 1000 to 9190
  bitcrusher1.sampleRate(samplerate);               // set sample rate


  bandpass = (analogRead(A3) << 1) + 1000; // lpf pot ranges from 500 to 8240hz

  biquad1.setBandpass (0, 2000, 1);
  biquad1.setBandpass (1, 2000, 1);
  biquad1.setBandpass (2, 2000, 1);
  biquad1.setBandpass (3, 2000, 1);

}



For reference, here is the same code without the LFO which works fine:
Code:
#define LED 3
#include <Bounce.h>
#include <ResponsiveAnalogRead.h>
#include <vibrato.h>
#include "effect_delay10tap.h"


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

// GUItool: begin automatically generated code
//LFO BITS      


//FX BITS 
AudioInputI2S            i2s1;           //xy=82,344
AudioMixer4              mixer1;         //xy=162,96
AudioEffectBitcrusher    bitcrusher1;    //xy=255,27
AudioEffectDelay10tap delay1;         //xy=307,220
AudioAmplifier           amp1;           //xy=409,29
AudioMixer4              mixer2;         //xy=462,224
AudioFilterStateVariable filter1;         //xy=535,37
AudioFilterBiquad        biquad1;
AudioOutputI2S           i2s2;           //xy=746,214
AudioEffect_Vibrato      vibrato1;

//PATCHING
AudioConnection          patchCord1(i2s1, 0, vibrato1, 0);
AudioConnection          patchCord2(vibrato1, 0, mixer1, 0);
AudioConnection          patchCord3(i2s1, 0, bitcrusher1, 0);
AudioConnection          patchCord4(i2s1, 0, mixer2, 1);
AudioConnection          patchCord5(mixer1, delay1);
AudioConnection          patchCord6(bitcrusher1, amp1);
AudioConnection          patchCord7(delay1, 0, mixer1, 1);
AudioConnection          patchCord8(delay1, 0, mixer2, 0);
AudioConnection          patchCord9(delay1, 1, mixer2, 2);
AudioConnection          patchCord10(amp1, biquad1);
AudioConnection          patchCord11(biquad1, filter1);
AudioConnection          patchCord12(mixer2, 0, i2s2, 0);
AudioConnection          patchCord13(filter1, 1, mixer1, 2);




AudioControlSGTL5000     sgtl5000_1;     //xy=767,328
// GUItool: end automatically generated code

  


// delay line
#define DELAYLINE_MAX_LEN 45159  // number of samples at 44100 samples a second
int16_t delay_line[DELAYLINE_MAX_LEN] = {};

// main timing loop
#define LOOP0_DURATION 20  // interval time in millis
elapsedMillis loop0_timer;
// pot to control delaytime
const int DELAY_TIME_KNOB_PIN = A4;  // A12 gain
ResponsiveAnalogRead delayTimeKnob(DELAY_TIME_KNOB_PIN, true);



/*
  Bounce footswitch = Bounce(0, 50);  // debounce the footswitch
  Bounce D1 = Bounce(1, 50);          // debounce the toggle switch
  Bounce D2 = Bounce(2, 50);          // "  "  "  "  "  "  "  "  "

  // this section includes the function to check the toggle position
  bool right;
  bool middle;
  bool left;
  void checkToggle () {               // this is our function to check toggle position...
  D1.update();  D2.update();          // check digital inputs connected to toggle (can delete I think)
  if (digitalRead(1) && !digitalRead(2))   {
    right = 1;  // toggle is right
    middle = 0;
    left = 0;
  }
  if (digitalRead(1) && digitalRead(2))  {
    right = 0;  // toggle is in the middle
    middle = 1;
    left = 0;
  }
  if (!digitalRead(1) && digitalRead(2))   {
    right = 0;  // toggle is left
    middle = 0;
    left = 1;
  }
  }
*/

byte bitdepth = 16;                 // used to set bit depth
int samplerate = 44100;             // used to set sample rate
float mix;                          // clean/delay mix
float mix1;                        // clean/delay mix
float feedback;                    // delay feedback
int bandpass;                      //bandpass cutoff frequency
int delaytime;





void setup() {

  AudioMemory(400); // the "40" represents how much internal memory (in the Teensy, not the external RAM chip) is allotted for audio recording. It is measured in sample blocks, each providing 2.9ms of audio.
  sgtl5000_1.enable();    // this turns on the SGTL5000, which is the audio codec on the audio board
  sgtl5000_1.volume(1);   // this sets the output volume (it can be between 0 and 1)
  sgtl5000_1.inputSelect(AUDIO_INPUT_LINEIN); // selects the audio input, we always use Line In
 // analogReadResolution(10); // configure the pots to give 12 bit readings
  pinMode(0, INPUT_PULLUP); // internal pull-up resistor for footswitch
  pinMode(1, INPUT_PULLUP); // internal pull-up resistor for toggle
  pinMode(2, INPUT_PULLUP); // internal pull-up resistor for toggle
  pinMode(3, OUTPUT);       // pin 3 (the LED) is an output;
  Serial.begin(115200);       // initiate the serial monitor. USB is always 12 Mbit/sec
  sgtl5000_1.audioPostProcessorEnable();
  //analogReadAveraging(10);



 // start up the effect and pass it an array to store the samples
  delay1.begin(delay_line, DELAYLINE_MAX_LEN);
 
}

float VibeRate = 1;
float VibeDepth = 10;


void loop() {

  vibrato1.modulation(VibeRate, VibeDepth);

  // loop timer
  if (loop0_timer >= LOOP0_DURATION) {
    // update and check the pot
    delayTimeKnob.update();
    if (delayTimeKnob.hasChanged()) {
      //Serial.printf("Delay Time Knob:%d\n", delayTimeKnob.getValue());
      delay1.delaysmooth(0, delayTimeKnob.getValue());
    }
    loop0_timer = 0;
  }


  feedback = (float) analogRead (A2) / 1023; // delay feedback

  mix = (float) analogRead (A0) /1023; // wet/dry mix
  //Serial.println (mix);
  mix1 = 1.0 - mix;

  amp1.gain(1);


  //Set mixer levels
  mixer1.gain (0, 0.5); // clean gtr in level
  mixer1.gain (1, feedback);
  mixer1.gain (2, 1);

  mixer2.gain (0, mix); // fx signal
  mixer2.gain (1, 0.5); // dry signal


  // do bitcrushing
  bitdepth = (16);
  bitcrusher1.bits(bitdepth);   // set bit depth

  samplerate = (analogRead(A1) << 1) + 1000;        // samplerate pot ranges from 1000 to 9190
  bitcrusher1.sampleRate(samplerate);               // set sample rate


  bandpass = (analogRead(A3) << 1) + 1000; // lpf pot ranges from 500 to 8240hz
  
  biquad1.setBandpass (0, bandpass, 2);
  biquad1.setBandpass (1, bandpass, 2);
  biquad1.setBandpass (2, bandpass, 2);
  biquad1.setBandpass (3, bandpass, 2);


filter1.frequency (bandpass);
filter1.resonance (5);
filter1.octaveControl (5);


}
 
Last edited:
Got there in the end. In case it’s helpful for anyone else in the future here is how i edited that LFO code. This is just set up to take input, through the filter, then to output.

Code:
#include <math.h>

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

// GUItool: begin automatically generated code

AudioSynthWaveform       LFO;      //xy=90,194
AudioSynthWaveformDc     filterAmt; //xy=90,255
AudioEffectMultiply      multiply; //xy=250,233

AudioFilterStateVariable filter;        //xy=541,224
AudioInputI2S            i2s1;
AudioOutputI2S           i2s2;            //xy=707,208


AudioConnection          patchCord3(LFO, 0, multiply, 0);
AudioConnection          patchCord4(filterAmt, 0, multiply, 1);

AudioConnection          patchCord6(multiply, 0, filter, 1);
AudioConnection          patchCord7(i2s1, 0, filter, 0);
AudioConnection          patchCord8(filter, 1, i2s2, 0);

AudioControlSGTL5000     sgtl5000;       //xy=618,140
// GUItool: end automatically generated code


#define CUTOFF_POT A1
#define RATE_POT A2
#define AMOUNT_POT A3




int pitch = 220;
int cutoff = 2000;
int LFOrate = 0;
int amount = 0;





// input is from 0 to 127
void setCutoff(int u)
{
  // Use an exponential curve from 50Hz to about 12kHz
  // float co = 50 * exp(5.481 * u / 127.0);
  filter.frequency(1000);
  filter.resonance(3);
  filter.octaveControl(3);
}

void setLFORate(int u)
{
  // convert log scale 0 to 127 to a linear rate
  // f(1) = 0.5Hz, f(127) = 40Hz
  float f = exp(u / 25.0) / 16.0; // CHANGE 16.0 TO SPEED/SLOW LFO SPEED.
  LFO.frequency(f);
}

void updateLFO()
{

  filterAmt.amplitude(amount / 127.0);

}

void setup()
{
  // reserve some memory for the audio functions
  AudioMemory(20);
  // enable the audio control chip on the Audio Shield
  sgtl5000.enable();
  sgtl5000.volume(0.5);

  // setup the two pins for listening to the pitch and amplitude controls


  pinMode(RATE_POT, INPUT);
  pinMode(AMOUNT_POT, INPUT);

  updateLFO();


  setLFORate(LFOrate);
  LFO.amplitude(1);
  LFO.begin(WAVEFORM_TRIANGLE);
}

void loop()
{





  // read the cutoff pot position
  int newcutoff = analogRead(CUTOFF_POT) >> 3;
  // has it changed?
  if (newcutoff != cutoff)
  {
    // update if it has
    cutoff = newcutoff;
    setCutoff(cutoff);
  }

  // read the LFO rate pot position
  int newrate = analogRead(RATE_POT) >> 3;
  // has it changed?
  if (newrate != LFOrate)
  {
    // update if it has
    LFOrate = newrate;
    setLFORate(LFOrate);
  }

  // read the amount pot position
  int newamount = analogRead(AMOUNT_POT) >> 3;
  // has it changed?
  if (newamount != amount)
  {
    // update if it has
    amount = newamount;
    updateLFO();

  }
}
 
Back
Top