Stereo fir issues

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;
}