Simple Tap Tempo example for Pod

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

3 Likes

Thank you so much for sharing! This looks like a lot of fun to play with :slight_smile:

I have my Minilogue connected to my Pod right now, so I would love to try this out as soon as I can. I will keep you posted!
I think I’ll map the LFO to the Moog Ladder filter emulation…

1 Like