I’ve written some code I thought I would share. I figured some people starting out might find it helpful. I’ve seen some similar topics discussed in this forum. I’m quite new to this type of programming myself so feedback from the more experienced is very welcome.
The objective is to control the frequency of an LFO with either knob 1 on the Pod or by tapping in the tempo with button 2. This is a feature seen on many guitar pedals, which allow a user to dial in a new setting by tapping with their foot (I’m awaiting the delivery of my terrarium pcb). In this example, the LFO is used to flash LED 2 at the given frequency.
#include "daisy_pod.h"
#include "daisysp.h"
using namespace daisy;
using namespace daisysp;
DaisyPod hw;
const float MAX_FREQ = 35; //30ms period
const float MIN_FREQ = 0.333; //3s period
int count, MAX_COUNT, MIN_COUNT; //count between taps. MAX and MIN value
bool tapping = false; //True when user is tapping
bool averaging = false; //True after 2nd tap
float BPS; //Audio blocks per second
float freq_tt; //Frequency read from tap tempo
Parameter freq_k; //Frequency read from knob
float prev_k1 = -1.0f; //Previous knob position. Used to detect change
bool use_tt = false; //use tap tempo. Knob values arent read when true
float lfo_out;
Oscillator lfo;
void tap_tempo();
void UpdateLeds();
void UpdateKnobs();
void AudioCallback(AudioHandle::InputBuffer in, AudioHandle::OutputBuffer out, size_t size)
{
hw.ProcessAllControls();
tap_tempo();
UpdateKnobs();
UpdateLeds();
for (size_t i = 0; i < size; i++)
{
out[0][i] = in[0][i];
out[1][i] = in[1][i];
lfo_out = lfo.Process();
}
}
void tap_tempo() {
if(tapping) {
count++;
if(hw.button2.RisingEdge() && count > MIN_COUNT) {
if (averaging) //3rd plus tap
freq_tt = 0.6*(freq_tt) + 0.4*(BPS/count); // Weighted Averaging
else {
//2nd tap
freq_tt = BPS/count; //frequency = BPS/count
averaging = true;
}
use_tt = true;
lfo.SetFreq(freq_tt);
count = 0;
}
else if(count == MAX_COUNT) { // After 1/MIN_FREQ seconds no tap, reset values
count = 0;
tapping = false;
averaging = false;
}
}
else if(hw.button2.RisingEdge()) //1st tap
tapping = true;
}
void UpdateKnobs()
{
float new_k1 = hw.knob1.Process();
if(!use_tt){
lfo.SetFreq(freq_k.Process());
prev_k1 = new_k1;
}
else if (new_k1 > prev_k1+0.05 || new_k1 < prev_k1-0.05) // When the knob value is changed set use_tt to false
use_tt = false;
}
void UpdateLeds() {
if(tapping)
hw.led1.Set(0, 0, 1); // blue
else
hw.led1.Set(0, 0, 0); // off
if(lfo_out > 0)
hw.led2.Set(1, 0, 0); // red
else
hw.led2.Set(0, 0, 0); // off
hw.UpdateLeds();
}
int main(void)
{
hw.Init();
hw.SetAudioBlockSize(4); // number of samples handled per callback
hw.SetAudioSampleRate(SaiHandle::Config::SampleRate::SAI_48KHZ);
BPS = hw.AudioSampleRate()/hw.AudioBlockSize(); // Blocks per second = sample_rate/block_size
MAX_COUNT = round(BPS/MIN_FREQ); // count = BPS/frequency
MIN_COUNT = round(BPS/MAX_FREQ);
freq_k.Init(hw.knob1, MIN_FREQ, MAX_FREQ, freq_k.LOGARITHMIC); //Mapping knob 1 values to freq_k logarithmically
//LFO Settings
lfo.Init(hw.AudioSampleRate());
lfo.SetWaveform(lfo.WAVE_SQUARE);
lfo.SetPw(0.25);
lfo.SetFreq(1.0f);
hw.StartAdc();
hw.StartAudio(AudioCallback);
while(1) {}
}
The program is pretty straight forward. When the user starts tapping the button, a count is started which is incremented once every call to the audio processing block. LED 1 lights up Blue at this point. This count is saved when the user taps again, and then is used to calculate the new frequency value.
When the user continues tapping, a weighted average is used to optimise the calculated frequency. Here BPS stands for audio Blocks Per Second. 0.6 and 0.4 are the weightings i chose.
if (averaging) //3rd plus tap
freq_tt = 0.6*(freq_tt) + 0.4*(BPS/count); // Weighted Averaging
This weighted averaging ensures that later intervals are favoured over previously entered ones. The belief being that the users beats will become more steady. I landed on this value of 60/40 after trying a few but 50/50 also works ok.
If the user moves knob1 a little, the program will revert to using that to set the frequency of the lfo. This is controlled by the bool use_tt, which is true when the program is using the tap tempo value
void UpdateKnobs()
{
float new_k1 = hw.knob1.Process();
if(!use_tt){
lfo.SetFreq(freq_k.Process());
prev_k1 = new_k1;
}
else if (new_k1 > prev_k1+0.05 || new_k1 < prev_k1-0.05) // When the knob value is changed set use_tt to false
use_tt = false;
}
In this example, the audio is simply passed through as it entered, but the tap tempo controlled lfo could be hooked up to any of the many different effects that can be modulated this way.
Let me know what you think or of any possible improvements I could make