Added user control of AudioEffectAnalogDelay filters

master
Steve Lascos 7 years ago
parent 604b3e8792
commit 4f528ebff7
  1. 27
      src/AudioEffectAnalogDelay.h
  2. 26
      src/LibBasicFunctions.h
  3. 73
      src/common/IirBiquadFilter.cpp
  4. 23
      src/effects/AudioEffectAnalogDelay.cpp

@ -30,6 +30,10 @@
namespace BAGuitar { 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 * AudioEffectAnalogDelay models BBD based analog delays. It provides controls
* for delay, feedback (or regen), mix and output level. All parameters can be * 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 NUM_CONTROLS ///< this can be used as an alias for the number of MIDI controls
}; };
AudioEffectAnalogDelay() = delete;
// *** CONSTRUCTORS *** // *** CONSTRUCTORS ***
AudioEffectAnalogDelay() = delete;
/// Construct an analog delay using internal memory by specifying the maximum /// Construct an analog delay using internal memory by specifying the maximum
/// delay in milliseconds. /// delay in milliseconds.
@ -130,6 +133,18 @@ public:
/// @param value the CC value from 0 to 127 /// @param value the CC value from 0 to 127
void processMidi(int channel, int midiCC, int value); 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. <br>
/// 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 virtual void update(void); ///< update automatically called by the Teesny Audio Library
private: private:
@ -154,7 +169,13 @@ private:
void m_preProcessing(audio_block_t *out, audio_block_t *dry, audio_block_t *wet); 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); 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;
}; };
} }

@ -199,6 +199,13 @@ public:
IirBiQuadFilter(unsigned numStages, const int32_t *coeffs, int coeffShift = 0); IirBiQuadFilter(unsigned numStages, const int32_t *coeffs, int coeffShift = 0);
virtual ~IirBiQuadFilter(); 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 /// Process the data using the configured IIR filter
/// @details output and input can be the same pointer if in-place modification is desired /// @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 /// @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 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 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 /// @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(); 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 /// Process the data using the configured IIR filter
/// @details output and input can be the same pointer if in-place modification is desired /// @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 /// @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 /// Construct a Biquad filter with specified number of stages and coefficients
/// @details See CMSIS-DSP documentation for more details /// @details See CMSIS-DSP documentation for more details
/// @param numStages number of biquad stages. Each stage has 5 coefficients. /// @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 coeffs pointer to an array of single-precision floating-point coefficients
IirBiQuadFilterFloat(unsigned numStages, const float *coeffs); IirBiQuadFilterFloat(unsigned maxNumStages, const float *coeffs);
virtual ~IirBiQuadFilterFloat(); 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 /// Process the data using the configured IIR filter
/// @details output and input can be the same pointer if in-place modification is desired /// @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 /// @param output pointer to where the output results will be written

@ -26,14 +26,17 @@ namespace BAGuitar {
//////////////////////////////////////////////////// ////////////////////////////////////////////////////
// IirBiQuadFilter // IirBiQuadFilter
//////////////////////////////////////////////////// ////////////////////////////////////////////////////
IirBiQuadFilter::IirBiQuadFilter(unsigned numStages, const int32_t *coeffs, int coeffShift) constexpr int NUM_COEFFS_PER_STAGE = 5;
: NUM_STAGES(numStages) 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]; m_coeffs = new int32_t[NUM_COEFFS_PER_STAGE*maxNumStages];
memcpy(m_coeffs, coeffs, 5*numStages * sizeof(int32_t)); //memcpy(m_coeffs, coeffs, 5*numStages * sizeof(int32_t));
m_state = new int32_t[4*numStages]; m_state = new int32_t[NUM_STATES_PER_STAGE*maxNumStages];
arm_biquad_cascade_df1_init_q31(&m_iirCfg, numStages, m_coeffs, m_state, coeffShift); //arm_biquad_cascade_df1_init_q31(&m_iirCfg, numStages, m_coeffs, m_state, coeffShift);
changeFilterCoeffs(maxNumStages, coeffs, coeffShift);
} }
IirBiQuadFilter::~IirBiQuadFilter() IirBiQuadFilter::~IirBiQuadFilter()
@ -42,6 +45,15 @@ IirBiQuadFilter::~IirBiQuadFilter()
if (m_state) delete [] m_state; 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) 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; return true;
} }
///////////////////////////////////
// HIGH QUALITY // 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]; m_coeffs = new int32_t[NUM_COEFFS_PER_STAGE*maxNumStages];
memcpy(m_coeffs, coeffs, 5*numStages * sizeof(int32_t)); //memcpy(m_coeffs, coeffs, 5*numStages * sizeof(int32_t));
m_state = new int64_t[4*numStages];; 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); //arm_biquad_cas_df1_32x64_init_q31(&m_iirCfg, numStages, m_coeffs, m_state, coeffShift);
changeFilterCoeffs(maxNumStages, coeffs, coeffShift);
} }
IirBiQuadFilterHQ::~IirBiQuadFilterHQ() IirBiQuadFilterHQ::~IirBiQuadFilterHQ()
@ -84,6 +99,15 @@ IirBiQuadFilterHQ::~IirBiQuadFilterHQ()
if (m_state) delete [] m_state; 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) 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; return true;
} }
///////////////////////
// FLOAT // 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]; m_coeffs = new float[NUM_COEFFS_PER_STAGE*maxNumStages];
memcpy(m_coeffs, coeffs, 5*numStages * sizeof(float)); //memcpy(m_coeffs, coeffs, NUM_COEFFS_PER_STAGE*maxNumStages * sizeof(float));
m_state = new float[4*numStages];; m_state = new float[NUM_STATES_PER_STAGE*maxNumStages];;
arm_biquad_cascade_df2T_init_f32(&m_iirCfg, numStages, m_coeffs, m_state); //arm_biquad_cascade_df2T_init_f32(&m_iirCfg, maxNumStages, m_coeffs, m_state);
changeFilterCoeffs(maxNumStages, coeffs);
} }
IirBiQuadFilterFloat::~IirBiQuadFilterFloat() 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) bool IirBiQuadFilterFloat::process(float *output, float *input, size_t numSamples)
{ {
if (!output) return false; if (!output) return false;

@ -13,9 +13,8 @@ constexpr int MIDI_CHANNEL = 0;
constexpr int MIDI_CONTROL = 1; constexpr int MIDI_CONTROL = 1;
// BOSS DM-3 Filters // BOSS DM-3 Filters
constexpr unsigned NUM_IIR_STAGES = 4; constexpr unsigned DM3_COEFF_SHIFT = 2;
constexpr unsigned IIR_COEFF_SHIFT = 2; constexpr int32_t DM3[5*MAX_NUM_FILTER_STAGES] = {
constexpr int32_t DEFAULT_COEFFS[5*NUM_IIR_STAGES] = {
536870912, 988616936, 455608573, 834606945, -482959709, 536870912, 988616936, 455608573, 834606945, -482959709,
536870912, 1031466345, 498793368, 965834205, -467402235, 536870912, 1031466345, 498793368, 965834205, -467402235,
536870912, 1105821939, 573646688, 928470657, -448083489, 536870912, 1105821939, 573646688, 928470657, -448083489,
@ -28,7 +27,7 @@ AudioEffectAnalogDelay::AudioEffectAnalogDelay(float maxDelayMs)
{ {
m_memory = new AudioDelay(maxDelayMs); m_memory = new AudioDelay(maxDelayMs);
m_maxDelaySamples = calcAudioSamples(maxDelayMs); m_maxDelaySamples = calcAudioSamples(maxDelayMs);
m_iir = new IirBiQuadFilterHQ(NUM_IIR_STAGES, reinterpret_cast<const int32_t *>(&DEFAULT_COEFFS), IIR_COEFF_SHIFT); m_constructFilter();
} }
AudioEffectAnalogDelay::AudioEffectAnalogDelay(size_t numSamples) AudioEffectAnalogDelay::AudioEffectAnalogDelay(size_t numSamples)
@ -36,7 +35,7 @@ AudioEffectAnalogDelay::AudioEffectAnalogDelay(size_t numSamples)
{ {
m_memory = new AudioDelay(numSamples); m_memory = new AudioDelay(numSamples);
m_maxDelaySamples = numSamples; m_maxDelaySamples = numSamples;
m_iir = new IirBiQuadFilterHQ(NUM_IIR_STAGES, reinterpret_cast<const int32_t *>(&DEFAULT_COEFFS), IIR_COEFF_SHIFT); m_constructFilter();
} }
// requires preallocated memory large enough // requires preallocated memory large enough
@ -46,7 +45,7 @@ AudioEffectAnalogDelay::AudioEffectAnalogDelay(ExtMemSlot *slot)
m_memory = new AudioDelay(slot); m_memory = new AudioDelay(slot);
m_maxDelaySamples = (slot->size() / sizeof(int16_t)); m_maxDelaySamples = (slot->size() / sizeof(int16_t));
m_externalMemory = true; m_externalMemory = true;
m_iir = new IirBiQuadFilterHQ(NUM_IIR_STAGES, reinterpret_cast<const int32_t *>(&DEFAULT_COEFFS), IIR_COEFF_SHIFT); m_constructFilter();
} }
AudioEffectAnalogDelay::~AudioEffectAnalogDelay() AudioEffectAnalogDelay::~AudioEffectAnalogDelay()
@ -55,6 +54,18 @@ AudioEffectAnalogDelay::~AudioEffectAnalogDelay()
if (m_iir) delete m_iir; 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<const int32_t *>(&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) void AudioEffectAnalogDelay::update(void)
{ {
audio_block_t *inputAudioBlock = receiveReadOnly(); // get the next block of input samples audio_block_t *inputAudioBlock = receiveReadOnly(); // get the next block of input samples

Loading…
Cancel
Save