diff --git a/src/AudioEffectAnalogDelay.h b/src/AudioEffectAnalogDelay.h index 7fd2475..d84ad79 100644 --- a/src/AudioEffectAnalogDelay.h +++ b/src/AudioEffectAnalogDelay.h @@ -30,6 +30,10 @@ namespace BAGuitar { +/// The number of stages in the analog-response Biquad filter +constexpr unsigned MAX_NUM_FILTER_STAGES = 4; +constexpr unsigned NUM_COEFFS_PER_STAGE = 5; + /**************************************************************************//** * AudioEffectAnalogDelay models BBD based analog delays. It provides controls * for delay, feedback (or regen), mix and output level. All parameters can be @@ -50,9 +54,8 @@ public: NUM_CONTROLS ///< this can be used as an alias for the number of MIDI controls }; - AudioEffectAnalogDelay() = delete; - // *** CONSTRUCTORS *** + AudioEffectAnalogDelay() = delete; /// Construct an analog delay using internal memory by specifying the maximum /// delay in milliseconds. @@ -130,6 +133,18 @@ public: /// @param value the CC value from 0 to 127 void processMidi(int channel, int midiCC, int value); + /// Override the default coefficients with your own. The number of filters stages affects how + /// much CPU is consumed. + /// @details The effect uses the CMSIS-DSP library for biquads which requires coefficents + /// be in q31 format, which means they are 32-bit signed integers representing -1.0 to slightly + /// less than +1.0. The coeffShift parameter effectively multiplies the coefficients by 2^shift.
+ /// Example: If you really want +1.5, must instead use +0.75 * 2^1, thus 0.75 in q31 format is + /// (0.75 * 2^31) = 1610612736 and coeffShift = 1. + /// @param numStages the actual number of filter stages you want to use. Must be <= MAX_NUM_FILTER_STAGES. + /// @param coeffs pointer to an integer array of coefficients in q31 format. + /// @param coeffShift Coefficient scaling factor = 2^coeffShift. + void setFilterCoeffs(int numStages, const int32_t *coeffs, int coeffShift); + virtual void update(void); ///< update automatically called by the Teesny Audio Library private: @@ -154,7 +169,13 @@ private: void m_preProcessing(audio_block_t *out, audio_block_t *dry, audio_block_t *wet); void m_postProcessing(audio_block_t *out, audio_block_t *dry, audio_block_t *wet); - size_t m_callCount = 0; + // Coefficients + void m_constructFilter(void); +// int m_numStages; +// int m_coeffShift; +// int m_coeffs[MAX_NUM_FILTER_STAGES*NUM_COEFFS_PER_STAGE] = {}; + + //size_t m_callCount = 0; }; } diff --git a/src/LibBasicFunctions.h b/src/LibBasicFunctions.h index b035031..0507e43 100644 --- a/src/LibBasicFunctions.h +++ b/src/LibBasicFunctions.h @@ -199,6 +199,13 @@ public: IirBiQuadFilter(unsigned numStages, const int32_t *coeffs, int coeffShift = 0); virtual ~IirBiQuadFilter(); + /// Reconfigure the filter coefficients. + /// @details See CMSIS-DSP documentation for more details + /// @param numStages number of biquad stages. Each stage has 5 coefficients. + /// @param coeffs pointer to an array of Q31 fixed-point coefficients (range -1 to +0.999...) + /// @param coeffShift coeffs are multiplied by 2^coeffShift to support coefficient range scaling + void changeFilterCoeffs(unsigned numStages, const int32_t *coeffs, int coeffShift = 0); + /// Process the data using the configured IIR filter /// @details output and input can be the same pointer if in-place modification is desired /// @param output pointer to where the output results will be written @@ -227,9 +234,16 @@ public: /// @param numStages number of biquad stages. Each stage has 5 coefficients. /// @param coeffs pointer to an array of Q31 fixed-point coefficients (range -1 to +0.999...) /// @param coeffShift coeffs are multiplied by 2^coeffShift to support coefficient range scaling - IirBiQuadFilterHQ(unsigned numStages, const int32_t *coeffs, int coeffShift = 0); + IirBiQuadFilterHQ(unsigned maxNumStages, const int32_t *coeffs, int coeffShift = 0); virtual ~IirBiQuadFilterHQ(); + /// Reconfigure the filter coefficients. + /// @details See CMSIS-DSP documentation for more details + /// @param numStages number of biquad stages. Each stage has 5 coefficients. + /// @param coeffs pointer to an array of Q31 fixed-point coefficients (range -1 to +0.999...) + /// @param coeffShift coeffs are multiplied by 2^coeffShift to support coefficient range scaling + void changeFilterCoeffs(unsigned numStages, const int32_t *coeffs, int coeffShift = 0); + /// Process the data using the configured IIR filter /// @details output and input can be the same pointer if in-place modification is desired /// @param output pointer to where the output results will be written @@ -257,10 +271,16 @@ public: /// Construct a Biquad filter with specified number of stages and coefficients /// @details See CMSIS-DSP documentation for more details /// @param numStages number of biquad stages. Each stage has 5 coefficients. - /// @param coeffs pointer to an array of Q31 fixed-point coefficients (range -1 to +0.999...) - IirBiQuadFilterFloat(unsigned numStages, const float *coeffs); + /// @param coeffs pointer to an array of single-precision floating-point coefficients + IirBiQuadFilterFloat(unsigned maxNumStages, const float *coeffs); virtual ~IirBiQuadFilterFloat(); + /// Reconfigure the filter coefficients. + /// @details See CMSIS-DSP documentation for more details + /// @param numStages number of biquad stages. Each stage has 5 coefficients. + /// @param coeffs pointer to an array of single-precision floating-point coefficients + void changeFilterCoeffs(unsigned numStages, const float *coeffs); + /// Process the data using the configured IIR filter /// @details output and input can be the same pointer if in-place modification is desired /// @param output pointer to where the output results will be written diff --git a/src/common/IirBiquadFilter.cpp b/src/common/IirBiquadFilter.cpp index 59b5432..f2ae1c7 100644 --- a/src/common/IirBiquadFilter.cpp +++ b/src/common/IirBiquadFilter.cpp @@ -26,14 +26,17 @@ namespace BAGuitar { //////////////////////////////////////////////////// // IirBiQuadFilter //////////////////////////////////////////////////// -IirBiQuadFilter::IirBiQuadFilter(unsigned numStages, const int32_t *coeffs, int coeffShift) -: NUM_STAGES(numStages) +constexpr int NUM_COEFFS_PER_STAGE = 5; +constexpr int NUM_STATES_PER_STAGE = 4; +IirBiQuadFilter::IirBiQuadFilter(unsigned maxNumStages, const int32_t *coeffs, int coeffShift) +: NUM_STAGES(maxNumStages) { - m_coeffs = new int32_t[5*numStages]; - memcpy(m_coeffs, coeffs, 5*numStages * sizeof(int32_t)); + m_coeffs = new int32_t[NUM_COEFFS_PER_STAGE*maxNumStages]; + //memcpy(m_coeffs, coeffs, 5*numStages * sizeof(int32_t)); - m_state = new int32_t[4*numStages]; - arm_biquad_cascade_df1_init_q31(&m_iirCfg, numStages, m_coeffs, m_state, coeffShift); + m_state = new int32_t[NUM_STATES_PER_STAGE*maxNumStages]; + //arm_biquad_cascade_df1_init_q31(&m_iirCfg, numStages, m_coeffs, m_state, coeffShift); + changeFilterCoeffs(maxNumStages, coeffs, coeffShift); } IirBiQuadFilter::~IirBiQuadFilter() @@ -42,6 +45,15 @@ IirBiQuadFilter::~IirBiQuadFilter() if (m_state) delete [] m_state; } +void IirBiQuadFilter::changeFilterCoeffs(unsigned numStages, const int32_t *coeffs, int coeffShift) +{ + // clear the state + memset(m_state, 0, sizeof(int32_t) * NUM_COEFFS_PER_STAGE * numStages); + // copy the coeffs + memcpy(m_coeffs, coeffs, NUM_COEFFS_PER_STAGE*numStages * sizeof(int32_t)); + arm_biquad_cascade_df1_init_q31(&m_iirCfg, numStages, m_coeffs, m_state, coeffShift); +} + bool IirBiQuadFilter::process(int16_t *output, int16_t *input, size_t numSamples) { @@ -67,15 +79,18 @@ bool IirBiQuadFilter::process(int16_t *output, int16_t *input, size_t numSamples return true; } +/////////////////////////////////// // HIGH QUALITY -IirBiQuadFilterHQ::IirBiQuadFilterHQ(unsigned numStages, const int32_t *coeffs, int coeffShift) -: NUM_STAGES(numStages) +/////////////////////////////////// +IirBiQuadFilterHQ::IirBiQuadFilterHQ(unsigned maxNumStages, const int32_t *coeffs, int coeffShift) +: NUM_STAGES(maxNumStages) { - m_coeffs = new int32_t[5*numStages]; - memcpy(m_coeffs, coeffs, 5*numStages * sizeof(int32_t)); + m_coeffs = new int32_t[NUM_COEFFS_PER_STAGE*maxNumStages]; + //memcpy(m_coeffs, coeffs, 5*numStages * sizeof(int32_t)); - m_state = new int64_t[4*numStages];; - arm_biquad_cas_df1_32x64_init_q31(&m_iirCfg, numStages, m_coeffs, m_state, coeffShift); + m_state = new int64_t[NUM_STATES_PER_STAGE*maxNumStages];; + //arm_biquad_cas_df1_32x64_init_q31(&m_iirCfg, numStages, m_coeffs, m_state, coeffShift); + changeFilterCoeffs(maxNumStages, coeffs, coeffShift); } IirBiQuadFilterHQ::~IirBiQuadFilterHQ() @@ -84,6 +99,15 @@ IirBiQuadFilterHQ::~IirBiQuadFilterHQ() if (m_state) delete [] m_state; } +void IirBiQuadFilterHQ::changeFilterCoeffs(unsigned numStages, const int32_t *coeffs, int coeffShift) +{ + // clear the state + memset(m_state, 0, sizeof(int32_t) * NUM_COEFFS_PER_STAGE * numStages); + // copy the coeffs + memcpy(m_coeffs, coeffs, NUM_COEFFS_PER_STAGE*numStages * sizeof(int32_t)); + arm_biquad_cas_df1_32x64_init_q31(&m_iirCfg, numStages, m_coeffs, m_state, coeffShift); +} + bool IirBiQuadFilterHQ::process(int16_t *output, int16_t *input, size_t numSamples) { @@ -109,15 +133,18 @@ bool IirBiQuadFilterHQ::process(int16_t *output, int16_t *input, size_t numSampl return true; } +/////////////////////// // FLOAT -IirBiQuadFilterFloat::IirBiQuadFilterFloat(unsigned numStages, const float *coeffs) -: NUM_STAGES(numStages) +/////////////////////// +IirBiQuadFilterFloat::IirBiQuadFilterFloat(unsigned maxNumStages, const float *coeffs) +: NUM_STAGES(maxNumStages) { - m_coeffs = new float[5*numStages]; - memcpy(m_coeffs, coeffs, 5*numStages * sizeof(float)); + m_coeffs = new float[NUM_COEFFS_PER_STAGE*maxNumStages]; + //memcpy(m_coeffs, coeffs, NUM_COEFFS_PER_STAGE*maxNumStages * sizeof(float)); - m_state = new float[4*numStages];; - arm_biquad_cascade_df2T_init_f32(&m_iirCfg, numStages, m_coeffs, m_state); + m_state = new float[NUM_STATES_PER_STAGE*maxNumStages];; + //arm_biquad_cascade_df2T_init_f32(&m_iirCfg, maxNumStages, m_coeffs, m_state); + changeFilterCoeffs(maxNumStages, coeffs); } IirBiQuadFilterFloat::~IirBiQuadFilterFloat() @@ -127,6 +154,16 @@ IirBiQuadFilterFloat::~IirBiQuadFilterFloat() } +void IirBiQuadFilterFloat::changeFilterCoeffs(unsigned numStages, const float *coeffs) +{ + // clear the state + memset(m_state, 0, sizeof(float) * NUM_COEFFS_PER_STAGE * numStages); + // copy the coeffs + memcpy(m_coeffs, coeffs, NUM_COEFFS_PER_STAGE*numStages * sizeof(float)); + arm_biquad_cascade_df2T_init_f32(&m_iirCfg, numStages, m_coeffs, m_state); +} + + bool IirBiQuadFilterFloat::process(float *output, float *input, size_t numSamples) { if (!output) return false; diff --git a/src/effects/AudioEffectAnalogDelay.cpp b/src/effects/AudioEffectAnalogDelay.cpp index 69b4c55..f822a6e 100644 --- a/src/effects/AudioEffectAnalogDelay.cpp +++ b/src/effects/AudioEffectAnalogDelay.cpp @@ -13,9 +13,8 @@ constexpr int MIDI_CHANNEL = 0; constexpr int MIDI_CONTROL = 1; // BOSS DM-3 Filters -constexpr unsigned NUM_IIR_STAGES = 4; -constexpr unsigned IIR_COEFF_SHIFT = 2; -constexpr int32_t DEFAULT_COEFFS[5*NUM_IIR_STAGES] = { +constexpr unsigned DM3_COEFF_SHIFT = 2; +constexpr int32_t DM3[5*MAX_NUM_FILTER_STAGES] = { 536870912, 988616936, 455608573, 834606945, -482959709, 536870912, 1031466345, 498793368, 965834205, -467402235, 536870912, 1105821939, 573646688, 928470657, -448083489, @@ -28,7 +27,7 @@ AudioEffectAnalogDelay::AudioEffectAnalogDelay(float maxDelayMs) { m_memory = new AudioDelay(maxDelayMs); m_maxDelaySamples = calcAudioSamples(maxDelayMs); - m_iir = new IirBiQuadFilterHQ(NUM_IIR_STAGES, reinterpret_cast(&DEFAULT_COEFFS), IIR_COEFF_SHIFT); + m_constructFilter(); } AudioEffectAnalogDelay::AudioEffectAnalogDelay(size_t numSamples) @@ -36,7 +35,7 @@ AudioEffectAnalogDelay::AudioEffectAnalogDelay(size_t numSamples) { m_memory = new AudioDelay(numSamples); m_maxDelaySamples = numSamples; - m_iir = new IirBiQuadFilterHQ(NUM_IIR_STAGES, reinterpret_cast(&DEFAULT_COEFFS), IIR_COEFF_SHIFT); + m_constructFilter(); } // requires preallocated memory large enough @@ -46,7 +45,7 @@ AudioEffectAnalogDelay::AudioEffectAnalogDelay(ExtMemSlot *slot) m_memory = new AudioDelay(slot); m_maxDelaySamples = (slot->size() / sizeof(int16_t)); m_externalMemory = true; - m_iir = new IirBiQuadFilterHQ(NUM_IIR_STAGES, reinterpret_cast(&DEFAULT_COEFFS), IIR_COEFF_SHIFT); + m_constructFilter(); } AudioEffectAnalogDelay::~AudioEffectAnalogDelay() @@ -55,6 +54,18 @@ AudioEffectAnalogDelay::~AudioEffectAnalogDelay() if (m_iir) delete m_iir; } +// This function just sets up the default filter and coefficients +void AudioEffectAnalogDelay::m_constructFilter(void) +{ + // Use DM3 coefficients by default + m_iir = new IirBiQuadFilterHQ(MAX_NUM_FILTER_STAGES, reinterpret_cast(&DM3), DM3_COEFF_SHIFT); +} + +void AudioEffectAnalogDelay::setFilterCoeffs(int numStages, const int32_t *coeffs, int coeffShift) +{ + m_iir->changeFilterCoeffs(numStages, coeffs, coeffShift); +} + void AudioEffectAnalogDelay::update(void) { audio_block_t *inputAudioBlock = receiveReadOnly(); // get the next block of input samples