I’ve been trying to use the fir module of daisysp to run a guitar cab sim on a daisy pod, but whenever i try to run it in stereo, with small tweaks to the example code, I get a strange crackly/bitcrushy sound, as seen below.
After separating it into it’s own file so that I could attempt to run each channel as it’s own instance (assuming that the second channel was interfering with the first), it didn’t resolve the issue. Switching back to mono works, but I don’t have enough experience with c++ or audio processing to pinpoint a cause. Any help or pointers would be appreciated.
main.cpp:
#include "daisysp.h"
#include "daisy_seed.h"
#include "ir.h"
ir filterR;
ir filterL;
using namespace daisysp;
using namespace daisy;
static DaisySeed hw;
static void AudioCallback(AudioHandle::InterleavingInputBuffer in,
AudioHandle::InterleavingOutputBuffer out,
size_t size)
{
/* Copy generated coefficients to active buffer */
float inl, inr;
filterL.ApplyFilter();
filterR.ApplyFilter();
for(size_t i = 0; i < size; i += 2)
{
inl = in[i];
inr = in[i + 1];
out[i] = filterL.process(inl); /*< Left */
out[i + 1] = filterR.process(inr); /*< Right */
}
filterL.callback_count++;
filterR.callback_count++;
}
int main(void)
{
/* initialize seed hardware and daisysp modules */
hw.Configure();
hw.Init();
hw.SetAudioBlockSize(4);
hw.StartLog(false);
filterL.InitWindow();
filterL.init(hw);
filterR.InitWindow();
filterR.init(hw);
hw.StartAudio(AudioCallback);
while (1)
{
filterL.filterUpdate(hw);
filterR.filterUpdate(hw);
}
}
ir.h:
#pragma once
#include "daisysp.h"
#include "daisy_seed.h"
class ir
{
private:
daisysp::Oscillator lfo;
daisysp::WhiteNoise osc;
daisysp::FIR<FIRFILTER_USER_MEMORY> flt;
public:
ir();
~ir();
void InitWindow();
void ApplyFilter();
void UpdateFilter(float freq);
void init(daisy::DaisySeed &hw);
void filterUpdate(daisy::DaisySeed &hw);
float process(float input);
static constexpr size_t flt_size = 512; /*< FIR filter length */
static constexpr int upd_rate_hz = 1000; /*< FIR recalculation rate */
float ir_front[flt_size];
float ir_back[flt_size + 1];
float wnd[flt_size / 2];
bool ir_update_pending;
int update_count;
int callback_count;
float flt_state[flt_size + 1];
uint32_t next_update;
int callback_rate;
uint32_t tick_rate;
uint32_t update_period;
};
ir.cpp:
#include "daisysp.h"
#include "daisy_seed.h"
#include "ir.h"
using namespace daisysp;
using namespace daisy;
ir::ir() {
}
ir::~ir(){
}
/* Blackman-Harris window function */
void ir::InitWindow()
{
float scale = TWOPI_F / flt_size;
const float a0 = 0.35875f;
const float a1 = 0.48829f;
const float a2 = 0.14128f;
const float a3 = 0.01168f;
for(size_t i = 0; i < flt_size / 2; i++)
{
const float wind_arg = scale * i;
wnd[i] = a0 - a1 * cosf(wind_arg) + a2 * cosf(wind_arg * 2)
- a3 * cosf(wind_arg * 3);
}
}
float ir::process(float input) {
return flt.Process(input);
}
/* Windowed Sinc filter design */
void ir::UpdateFilter(float freq)
{
/* Only proceed if the previous filter was already applied */
if(false == ir_update_pending)
{
constexpr int half = flt_size / 2;
const float scale = 2.0f * freq;
/* Symmetric filter shape */
for(int i = 0; i < half; i++)
{
const float sinc_arg = PI_F * (i - half);
const float sinc = sinf(sinc_arg * scale) / sinc_arg;
const float filt = sinc * wnd[i];
ir_back[i] = filt;
ir_back[flt_size - i] = filt; /* may access ir_back[flt_size] !! */
}
ir_back[half] = scale;
ir_update_pending = true;
}
}
void ir::ApplyFilter()
{
if(true == ir_update_pending)
{
for(size_t i = 0; i < flt_size; i++)
{
ir_front[i] = ir_back[i];
}
ir_update_pending = false;
}
}
void ir::init(DaisySeed &hw) {
callback_rate = (int)hw.AudioCallbackRate();
tick_rate = hw.system.GetPClk1Freq() * 2;
update_period = tick_rate / upd_rate_hz;
/** initialize FIR filter and set parameters
* Note: in this mode (user-managed memory) the coefficients
* are NOT copied, so we may update them at runtime
*/
flt.SetStateBuffer(flt_state, DSY_COUNTOF(flt_state));
flt.Init(ir_front, flt_size, false);
InitWindow();
UpdateFilter(0.5f);
/* set parameters for the control oscillator object */
lfo.Init((float)upd_rate_hz);
lfo.SetWaveform(Oscillator::WAVE_TRI);
lfo.SetAmp(1.0f);
lfo.SetFreq(.1f); /* 10 seconds period */
/* set parameters for the source oscillator object */
osc.Init();
osc.SetAmp(0.5f);
uint32_t next_update = hw.system.GetTick();
}
void ir::filterUpdate(DaisySeed &hw) {
const float cutoff_freq = 0.01f + 0.15f * (1.0f + lfo.Process());
UpdateFilter(cutoff_freq);
update_count++;
/* estimate average filter update rate once per second */
if(callback_count % callback_rate == (callback_rate - 1))
{
const float rate
= (float)update_count * callback_rate / callback_count;
hw.PrintLine("Average = " FLT_FMT3 "upd/sec", FLT_VAR3(rate));
}
/** delay to roughly maintain the requested update rate
* Note: this is better than using Delay* functions,
* because the error doesn't accumulate
*/
while(next_update - hw.system.GetTick() < update_period) {}
next_update += update_period;
}