From 442f871e014d56e01f5da6464b73f910f805d37d Mon Sep 17 00:00:00 2001 From: Steve Lascos Date: Sat, 3 Feb 2018 16:51:46 -0500 Subject: [PATCH] Added low pass filters in the delay path --- src/AudioEffectAnalogDelay.cpp | 84 +++++++++++--------- src/AudioEffectAnalogDelay.h | 16 ++-- src/LibBasicFunctions.cpp | 139 ++++++++++++++++++++++++++++++++- src/LibBasicFunctions.h | 60 ++++++++++++++ 4 files changed, 249 insertions(+), 50 deletions(-) diff --git a/src/AudioEffectAnalogDelay.cpp b/src/AudioEffectAnalogDelay.cpp index c570d03..d074afc 100644 --- a/src/AudioEffectAnalogDelay.cpp +++ b/src/AudioEffectAnalogDelay.cpp @@ -13,17 +13,28 @@ constexpr int MIDI_NUM_PARAMS = 4; constexpr int MIDI_CHANNEL = 0; constexpr int MIDI_CONTROL = 1; -constexpr int MIDI_ENABLE = 0; +constexpr int MIDI_BYPASS = 0; constexpr int MIDI_DELAY = 1; constexpr int MIDI_FEEDBACK = 2; constexpr int MIDI_MIX = 3; +// 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] = { + 536870912, 988616936, 455608573, 834606945, -482959709, + 536870912, 1031466345, 498793368, 965834205, -467402235, + 536870912, 1105821939, 573646688, 928470657, -448083489, + 2339, 5093, 2776, 302068995, 4412722 +}; + AudioEffectAnalogDelay::AudioEffectAnalogDelay(float maxDelay) : AudioStream(1, m_inputQueueArray) { m_memory = new AudioDelay(maxDelay); m_maxDelaySamples = calcAudioSamples(maxDelay); + m_iir = new IirBiQuadFilterHQ(NUM_IIR_STAGES, reinterpret_cast(&DEFAULT_COEFFS), IIR_COEFF_SHIFT); } AudioEffectAnalogDelay::AudioEffectAnalogDelay(size_t numSamples) @@ -31,6 +42,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); } // requires preallocated memory large enough @@ -40,68 +52,64 @@ AudioEffectAnalogDelay::AudioEffectAnalogDelay(ExtMemSlot *slot) m_memory = new AudioDelay(slot); m_maxDelaySamples = slot->size(); m_externalMemory = true; + m_iir = new IirBiQuadFilterHQ(NUM_IIR_STAGES, reinterpret_cast(&DEFAULT_COEFFS), IIR_COEFF_SHIFT); } AudioEffectAnalogDelay::~AudioEffectAnalogDelay() { if (m_memory) delete m_memory; + if (m_iir) delete m_iir; } void AudioEffectAnalogDelay::update(void) { audio_block_t *inputAudioBlock = receiveReadOnly(); // get the next block of input samples - if (!inputAudioBlock) { - // create silence - inputAudioBlock = allocate(); - if (!inputAudioBlock) { return; } // failed to allocate - else { - clearAudioBlock(inputAudioBlock); - } - } - + // Check is block is disabled if (m_enable == false) { - // release all held memory resources - transmit(inputAudioBlock); - release(inputAudioBlock); inputAudioBlock = nullptr; + // do not transmit or process any audio, return as quickly as possible. + if (inputAudioBlock) release(inputAudioBlock); + // release all held memory resources if (m_previousBlock) { release(m_previousBlock); m_previousBlock = nullptr; } if (!m_externalMemory) { + // when using internal memory we have to release all references in the ring buffer while (m_memory->getRingBuffer()->size() > 0) { audio_block_t *releaseBlock = m_memory->getRingBuffer()->front(); m_memory->getRingBuffer()->pop_front(); if (releaseBlock) release(releaseBlock); } } + return; } - if (m_callCount < 1024) { - if (inputAudioBlock) { - transmit(inputAudioBlock, 0); - release(inputAudioBlock); + // Check is block is bypassed, if so either transmit input directly or create silence + if (m_bypass == true) { + // transmit the input directly + if (!inputAudioBlock) { + // create silence + inputAudioBlock = allocate(); + if (!inputAudioBlock) { return; } // failed to allocate + else { + clearAudioBlock(inputAudioBlock); + } } - m_callCount++; return; + transmit(inputAudioBlock, 0); + release(inputAudioBlock); + return; } - - m_callCount++; - //Serial.println(String("AudioEffectAnalgDelay::update: ") + m_callCount); - + // Otherwise perform normal processing // Preprocessing audio_block_t *preProcessed = allocate(); + // mix the input with the feedback path in the pre-processing stage m_preProcessing(preProcessed, inputAudioBlock, m_previousBlock); audio_block_t *blockToRelease = m_memory->addBlock(preProcessed); if (blockToRelease) release(blockToRelease); -// if (inputAudioBlock) { -// transmit(inputAudioBlock, 0); -// release(inputAudioBlock); -// } -// return; - // OUTPUT PROCESSING audio_block_t *blockToOutput = nullptr; blockToOutput = allocate(); @@ -110,7 +118,7 @@ void AudioEffectAnalogDelay::update(void) if (!blockToOutput) return; // skip this time due to failure // copy over data m_memory->getSamples(blockToOutput, m_delaySamples); - // perform the mix + // perform the wet/dry mix mix m_postProcessing(blockToOutput, inputAudioBlock, blockToOutput); transmit(blockToOutput); @@ -177,11 +185,11 @@ void AudioEffectAnalogDelay::processMidi(int channel, int control, int value) return; } - if ((m_midiConfig[MIDI_ENABLE][MIDI_CHANNEL] == channel) && - (m_midiConfig[MIDI_ENABLE][MIDI_CONTROL] == control)) { - // Enable - if (value >= 65) { enable(); Serial.println(String("AudioEffectAnalogDelay::enable: ON") + value); } - else { disable(); Serial.println(String("AudioEffectAnalogDelay::enable: OFF") + value); } + if ((m_midiConfig[MIDI_BYPASS][MIDI_CHANNEL] == channel) && + (m_midiConfig[MIDI_BYPASS][MIDI_CONTROL] == control)) { + // Bypass + if (value >= 65) { bypass(false); Serial.println(String("AudioEffectAnalogDelay::not bypassed -> ON") + value); } + else { bypass(true); Serial.println(String("AudioEffectAnalogDelay::bypassed -> OFF") + value); } return; } @@ -208,10 +216,10 @@ void AudioEffectAnalogDelay::mapMidiDelay(int control, int channel) m_midiConfig[MIDI_DELAY][MIDI_CONTROL] = control; } -void AudioEffectAnalogDelay::mapMidiEnable(int control, int channel) +void AudioEffectAnalogDelay::mapMidiBypass(int control, int channel) { - m_midiConfig[MIDI_ENABLE][MIDI_CHANNEL] = channel; - m_midiConfig[MIDI_ENABLE][MIDI_CONTROL] = control; + m_midiConfig[MIDI_BYPASS][MIDI_CHANNEL] = channel; + m_midiConfig[MIDI_BYPASS][MIDI_CONTROL] = control; } void AudioEffectAnalogDelay::mapMidiFeedback(int control, int channel) @@ -239,6 +247,8 @@ void AudioEffectAnalogDelay::m_preProcessing(audio_block_t *out, audio_block_t * void AudioEffectAnalogDelay::m_postProcessing(audio_block_t *out, audio_block_t *dry, audio_block_t *wet) { if ( out && dry && wet) { + // Simulate the LPF IIR nature of the analog systems + m_iir->process(wet->data, wet->data, AUDIO_BLOCK_SAMPLES); alphaBlend(out, dry, wet, m_mix); } else if (dry) { memcpy(out->data, dry->data, sizeof(int16_t) * AUDIO_BLOCK_SAMPLES); diff --git a/src/AudioEffectAnalogDelay.h b/src/AudioEffectAnalogDelay.h index b354dc3..b4220fa 100644 --- a/src/AudioEffectAnalogDelay.h +++ b/src/AudioEffectAnalogDelay.h @@ -8,8 +8,6 @@ #ifndef SRC_AUDIOEFFECTANALOGDELAY_H_ #define SRC_AUDIOEFFECTANALOGDELAY_H_ -//#include - #include #include "LibBasicFunctions.h" @@ -17,8 +15,6 @@ namespace BAGuitar { class AudioEffectAnalogDelay : public AudioStream { public: - static constexpr int MAX_DELAY_CHANNELS = 8; - AudioEffectAnalogDelay() = delete; AudioEffectAnalogDelay(float maxDelay); AudioEffectAnalogDelay(size_t numSamples); @@ -28,35 +24,35 @@ public: virtual void update(void); void delay(float milliseconds); void delay(size_t delaySamples); + + void bypass(bool byp) { m_bypass = byp; } void feedback(float feedback) { m_feedback = feedback; } void mix(float mix) { m_mix = mix; } void enable() { m_enable = true; } void disable() { m_enable = false; } void processMidi(int channel, int control, int value); - void mapMidiEnable(int control, int channel = 0); + void mapMidiBypass(int control, int channel = 0); void mapMidiDelay(int control, int channel = 0); void mapMidiFeedback(int control, int channel = 0); void mapMidiMix(int control, int channel = 0); private: audio_block_t *m_inputQueueArray[1]; + bool m_bypass = true; bool m_enable = false; bool m_externalMemory = false; AudioDelay *m_memory = nullptr; size_t m_maxDelaySamples = 0; + audio_block_t *m_previousBlock = nullptr; + IirBiQuadFilterHQ *m_iir = nullptr; // Controls int m_midiConfig[4][2]; - //int m_midiEnable[2] = {0,16}; size_t m_delaySamples = 0; - //int m_midiDelay[2] = {0,20}; float m_feedback = 0.0f; - //int m_midiFeedback[2] = {0,21}; float m_mix = 0.0f; - //int m_midiMix[2] = {0,22}; - audio_block_t *m_previousBlock = nullptr; 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); diff --git a/src/LibBasicFunctions.cpp b/src/LibBasicFunctions.cpp index 67debd6..305ff20 100644 --- a/src/LibBasicFunctions.cpp +++ b/src/LibBasicFunctions.cpp @@ -34,9 +34,21 @@ size_t calcOffset(QueuePosition position) void alphaBlend(audio_block_t *out, audio_block_t *dry, audio_block_t* wet, float mix) { - for (int i=0; i< AUDIO_BLOCK_SAMPLES; i++) { - out->data[i] = (dry->data[i] * (1 - mix)) + (wet->data[i] * mix); - } + //Non-optimized version for illustrative purposes +// for (int i=0; i< AUDIO_BLOCK_SAMPLES; i++) { +// out->data[i] = (dry->data[i] * (1 - mix)) + (wet->data[i] * mix); +// } +// return; + + // ARM DSP optimized + int16_t wetBuffer[AUDIO_BLOCK_SAMPLES]; + int16_t dryBuffer[AUDIO_BLOCK_SAMPLES]; + int16_t scaleFractWet = (int16_t)(mix * 32767.0f); + int16_t scaleFractDry = 32767-scaleFractWet; + + arm_scale_q15(dry->data, scaleFractDry, 0, dryBuffer, AUDIO_BLOCK_SAMPLES); + arm_scale_q15(wet->data, scaleFractWet, 0, wetBuffer, AUDIO_BLOCK_SAMPLES); + arm_add_q15(wetBuffer, dryBuffer, out->data, AUDIO_BLOCK_SAMPLES); } void clearAudioBlock(audio_block_t *block) @@ -45,6 +57,9 @@ void clearAudioBlock(audio_block_t *block) } +//////////////////////////////////////////////////// +// AudioDelay +//////////////////////////////////////////////////// AudioDelay::AudioDelay(size_t maxSamples) : m_slot(nullptr) { @@ -210,5 +225,123 @@ bool AudioDelay::getSamples(audio_block_t *dest, size_t offset, size_t numSample } +//////////////////////////////////////////////////// +// IirBiQuadFilter +//////////////////////////////////////////////////// +IirBiQuadFilter::IirBiQuadFilter(unsigned numStages, const int32_t *coeffs, int coeffShift) +: NUM_STAGES(numStages) +{ + m_coeffs = new int32_t[5*numStages]; + 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); +} + +IirBiQuadFilter::~IirBiQuadFilter() +{ + if (m_coeffs) delete [] m_coeffs; + if (m_state) delete [] m_state; +} + + +bool IirBiQuadFilter::process(int16_t *output, int16_t *input, size_t numSamples) +{ + if (!output) return false; + if (!input) { + // send zeros + memset(output, 0, numSamples * sizeof(int16_t)); + } else { + + // create convertion buffers on teh stack + int32_t input32[numSamples]; + int32_t output32[numSamples]; + for (int i=0; i #include +#include #include "Arduino.h" #include "Audio.h" @@ -153,6 +154,65 @@ private: ExtMemSlot *m_slot = nullptr; ///< When using EXTERNAL memory, an ExtMemSlot must be provided. }; +/**************************************************************************//** + * IIR BiQuad Filter - Direct Form I
+ * y[n] = b0 * x[n] + b1 * x[n-1] + b2 * x[n-2] + a1 * y[n-1] + a2 * y[n-2]
+ * Some design tools (like Matlab assume the feedback coefficients 'a' are negated. You + * may have to negate your 'a' coefficients. + * @details Note that the ARM CMSIS-DSP library requires an extra zero between first + * and second 'b' coefficients. E.g.
+ * {b10, 0, b11, b12, a11, a12, b20, 0, b21, b22, a21, a22, ...} + *****************************************************************************/ +class IirBiQuadFilter { +public: + IirBiQuadFilter() = delete; + IirBiQuadFilter(unsigned numStages, const int32_t *coeffs, int coeffShift = 0); + virtual ~IirBiQuadFilter(); + bool process(int16_t *output, int16_t *input, size_t numSamples); +private: + const unsigned NUM_STAGES; + int32_t *m_coeffs = nullptr; + + // ARM DSP Math library filter instance + arm_biquad_casd_df1_inst_q31 m_iirCfg; + int32_t *m_state = nullptr; +}; + + +class IirBiQuadFilterHQ { +public: + IirBiQuadFilterHQ() = delete; + IirBiQuadFilterHQ(unsigned numStages, const int32_t *coeffs, int coeffShift = 0); + virtual ~IirBiQuadFilterHQ(); + bool process(int16_t *output, int16_t *input, size_t numSamples); +private: + + + const unsigned NUM_STAGES; + int32_t *m_coeffs = nullptr; + + // ARM DSP Math library filter instance + arm_biquad_cas_df1_32x64_ins_q31 m_iirCfg; + int64_t *m_state = nullptr; + +}; + +class IirBiQuadFilterFloat { +public: + IirBiQuadFilterFloat() = delete; + IirBiQuadFilterFloat(unsigned numStages, const float *coeffs); + virtual ~IirBiQuadFilterFloat(); + bool process(float *output, float *input, size_t numSamples); +private: + const unsigned NUM_STAGES; + float *m_coeffs = nullptr; + + // ARM DSP Math library filter instance + arm_biquad_cascade_df2T_instance_f32 m_iirCfg; + float *m_state = nullptr; + +}; + /**************************************************************************//** * Customer RingBuffer with random access *****************************************************************************/