From 429c37f8aca0b5d88b86a75606cecbc156a183c8 Mon Sep 17 00:00:00 2001 From: Steve Lascos Date: Tue, 20 Feb 2018 20:58:48 -0500 Subject: [PATCH] Added volume control, some code cleanup --- .../Delay/AnalogDelayDemo/AnalogDelayDemo.ino | 65 ++++++----- src/AudioEffectAnalogDelay.h | 53 ++++++++- src/LibBasicFunctions.h | 17 ++- src/common/AudioHelpers.cpp | 12 +- src/effects/AudioEffectAnalogDelay.cpp | 109 ++++++++---------- 5 files changed, 159 insertions(+), 97 deletions(-) diff --git a/examples/Delay/AnalogDelayDemo/AnalogDelayDemo.ino b/examples/Delay/AnalogDelayDemo/AnalogDelayDemo.ino index 1c4b657..fb452fb 100644 --- a/examples/Delay/AnalogDelayDemo/AnalogDelayDemo.ino +++ b/examples/Delay/AnalogDelayDemo/AnalogDelayDemo.ino @@ -1,7 +1,8 @@ -//#include +#include #include "BAGuitar.h" using namespace BAGuitar; + AudioInputI2S i2sIn; AudioOutputI2S i2sOut; BAAudioControlWM8731 codec; @@ -9,22 +10,35 @@ BAAudioControlWM8731 codec; #define USE_EXT #ifdef USE_EXT -ExternalSramManager externalSram(1); // Manage both SRAMs -ExtMemSlot delaySlot; // For the external memory +// If using external SPI memory, we will instantiance an SRAM +// manager and create an external memory slot to use as the memory +// for our audio delay +ExternalSramManager externalSram(1); // Manage only one SRAM. +ExtMemSlot delaySlot; // Declare an external memory slot. + +// Instantiate the AudioEffectAnalogDelay to use external memory by +/// passing it the delay slot. AudioEffectAnalogDelay myDelay(&delaySlot); #else -AudioEffectAnalogDelay myDelay(200.0f); +// If using internal memory, we will instantiate the AudioEffectAnalogDelay +// by passing it the maximum amount of delay we will use in millseconds. Note that +// audio delay lengths are very limited when using internal memory due to limited +// internal RAM size. +AudioEffectAnalogDelay myDelay(200.0f); // max delay of 200 ms. #endif -AudioMixer4 mixer; // Used to mix the original dry with the wet (effects) path. +//AudioMixer4 mixer; // Used to mix the original dry with the wet (effects) path. +// +//AudioConnection patch0(i2sIn,0, myDelay,0); +//AudioConnection mixerDry(i2sIn,0, mixer,0); +//AudioConnection mixerWet(myDelay,0, mixer,1); + +AudioConnection input(i2sIn,0, myDelay,0); +AudioConnection leftOut(myDelay,0, i2sOut, 0); -AudioConnection patch0(i2sIn,0, myDelay,0); -AudioConnection mixerDry(i2sIn,0, mixer,0); -AudioConnection mixerWet(myDelay,0, mixer,1); -AudioConnection leftOut(mixer,0, i2sOut, 0); -AudioConnection rightOut(mixer,0, i2sOut, 1); +int loopCount = 0; -unsigned loopCount = 0; +AudioConnection rightOut(myDelay,0, i2sOut, 1); void setup() { delay(100); @@ -43,32 +57,31 @@ void setup() { #ifdef USE_EXT Serial.println("Using EXTERNAL memory"); - //externalSram.requestMemory(&delaySlot, 1400.0f); - //externalSram.requestMemory(&delaySlot, 1400.0f, MemSelect::MEM0, true); + // We have to request memory be allocated to our slot. externalSram.requestMemory(&delaySlot, 1400.0f, MemSelect::MEM1, true); #else Serial.println("Using INTERNAL memory"); #endif - myDelay.delay(200.0f); - //myDelay.delay( 128.0f/44100.0f*1000.0f); - //myDelay.delay(0, 0.0f); - //myDelay.delay((size_t)8192); - - + // Configure which MIDI CC's will control the effects + myDelay.mapMidiControl(AudioEffectAnalogDelay::BYPASS,16); + myDelay.mapMidiControl(AudioEffectAnalogDelay::DELAY,20); + myDelay.mapMidiControl(AudioEffectAnalogDelay::FEEDBACK,21); + myDelay.mapMidiControl(AudioEffectAnalogDelay::MIX,22); + myDelay.mapMidiControl(AudioEffectAnalogDelay::VOLUME,23); - myDelay.mapMidiBypass(16); - myDelay.mapMidiDelay(20); - myDelay.mapMidiFeedback(21); - myDelay.mapMidiMix(22); + // Besure to enable the delay, by default it's processing is off. + myDelay.enable(); - myDelay.enable(); + // Set some default values. They can be changed by sending MIDI CC messages + // over the USB. + myDelay.delay(200.0f); myDelay.bypass(false); myDelay.mix(1.0f); myDelay.feedback(0.0f); - mixer.gain(0, 0.0f); // unity gain on the dry - mixer.gain(1, 1.0f); // unity gain on the wet +// mixer.gain(0, 0.0f); // unity gain on the dry +// mixer.gain(1, 1.0f); // unity gain on the wet } diff --git a/src/AudioEffectAnalogDelay.h b/src/AudioEffectAnalogDelay.h index 0ff2d2f..7fd2475 100644 --- a/src/AudioEffectAnalogDelay.h +++ b/src/AudioEffectAnalogDelay.h @@ -39,7 +39,21 @@ namespace BAGuitar { *****************************************************************************/ class AudioEffectAnalogDelay : public AudioStream { public: + + ///< List of AudioEffectAnalogDelay MIDI controllable parameters + enum { + BYPASS = 0, ///< controls effect bypass + DELAY, ///< controls the amount of delay + FEEDBACK, ///< controls the amount of echo feedback (regen) + MIX, ///< controls the the mix of input and echo signals + VOLUME, ///< controls the output volume level + NUM_CONTROLS ///< this can be used as an alias for the number of MIDI controls + }; + AudioEffectAnalogDelay() = delete; + + // *** CONSTRUCTORS *** + /// Construct an analog delay using internal memory by specifying the maximum /// delay in milliseconds. /// @param maxDelayMs maximum delay in milliseconds. Larger delays use more memory. @@ -57,6 +71,8 @@ public: virtual ~AudioEffectAnalogDelay(); ///< Destructor + // *** PARAMETERS *** + /// Set the delay in milliseconds. /// @param milliseconds the request delay in milliseconds. Must be less than max delay. void delay(float milliseconds); @@ -79,22 +95,46 @@ public: /// 0.5, output is 50% Dry, 50% Wet. void mix(float mix) { m_mix = mix; } + /// Set the output volume. This affect both the wet and dry signals. + /// @details The default is 1.0. + /// @param vol Sets the output volume between -1.0 and +1.0 + void volume(float vol) {m_volume = vol; } + + // ** ENABLE / DISABLE ** + /// Enables audio processing. Note: when not enabled, CPU load is nearly zero. void enable() { m_enable = true; } /// Disables audio process. When disabled, CPU load is nearly zero. void disable() { m_enable = false; } - void processMidi(int channel, int control, int value); - 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); + // ** MIDI ** + + /// Sets whether MIDI OMNI channel is processig on or off. When on, + /// all midi channels are used for matching CCs. + /// @param isOmni when true, all channels are processed, when false, channel + /// must match configured value. + void setMidiOmni(bool isOmni) { m_isOmni = isOmni; } + + /// Configure an effect parameter to be controlled by a MIDI CC + /// number on a particular channel. + /// @param parameter one of the parameter names in the class enum + /// @param midiCC the CC number from 0 to 127 + /// @param midiChannel the effect will only response to the CC on this channel + /// when OMNI mode is off. + void mapMidiControl(int parameter, int midiCC, int midiChannel = 0); + + /// process a MIDI Continous-Controller (CC) message + /// @param channel the MIDI channel from 0 to 15) + /// @param midiCC the CC number from 0 to 127 + /// @param value the CC value from 0 to 127 + void processMidi(int channel, int midiCC, int value); virtual void update(void); ///< update automatically called by the Teesny Audio Library private: audio_block_t *m_inputQueueArray[1]; + bool m_isOmni = false; bool m_bypass = true; bool m_enable = false; bool m_externalMemory = false; @@ -105,10 +145,11 @@ private: IirBiQuadFilterHQ *m_iir = nullptr; // Controls - int m_midiConfig[4][2]; + int m_midiConfig[NUM_CONTROLS][2]; // stores the midi parameter mapping size_t m_delaySamples = 0; float m_feedback = 0.0f; float m_mix = 0.0f; + float m_volume = 1.0f; 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.h b/src/LibBasicFunctions.h index a335ac0..802bc5c 100644 --- a/src/LibBasicFunctions.h +++ b/src/LibBasicFunctions.h @@ -70,11 +70,26 @@ size_t calcAudioSamples(float milliseconds); /// specified position. size_t calcOffset(QueuePosition position); +/// Clear the contents of an audio block to zero +/// @param block pointer to the audio block to clear void clearAudioBlock(audio_block_t *block); - +/// Perform an alpha blend between to audio blocks. Performs
+/// out = dry*(1-mix) + wet*(mix) +/// @param out pointer to the destination audio block +/// @param dry pointer to the dry audio +/// @param wet pointer to the wet audio +/// @param mix float between 0.0 and 1.0. void alphaBlend(audio_block_t *out, audio_block_t *dry, audio_block_t* wet, float mix); +/// Applies a gain to the audio via fixed-point scaling accoring to
+/// out = int * (vol * 2^coeffShift) +/// @param out pointer to output audio block +/// @param in pointer to input audio block +/// @param vol volume cofficient between -1.0 and +1.0 +/// @param coeffShift number of bits to shiftt the coefficient +void gainAdjust(audio_block_t *out, audio_block_t *in, float vol, int coeffShift = 0); + template class RingBuffer; // forward declare so AudioDelay can use it. diff --git a/src/common/AudioHelpers.cpp b/src/common/AudioHelpers.cpp index ca3a02c..ee09395 100644 --- a/src/common/AudioHelpers.cpp +++ b/src/common/AudioHelpers.cpp @@ -47,12 +47,6 @@ size_t calcOffset(QueuePosition position) void alphaBlend(audio_block_t *out, audio_block_t *dry, audio_block_t* wet, float 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]; @@ -64,6 +58,12 @@ void alphaBlend(audio_block_t *out, audio_block_t *dry, audio_block_t* wet, floa arm_add_q15(wetBuffer, dryBuffer, out->data, AUDIO_BLOCK_SAMPLES); } +void gainAdjust(audio_block_t *out, audio_block_t *in, float vol, int coeffShift) +{ + int16_t scale = (int16_t)(vol * 32767.0f); + arm_scale_q15(in->data, scale, coeffShift, out->data, AUDIO_BLOCK_SAMPLES); +} + void clearAudioBlock(audio_block_t *block) { memset(block->data, 0, sizeof(int16_t)*AUDIO_BLOCK_SAMPLES); diff --git a/src/effects/AudioEffectAnalogDelay.cpp b/src/effects/AudioEffectAnalogDelay.cpp index d7b1a0f..3881e57 100644 --- a/src/effects/AudioEffectAnalogDelay.cpp +++ b/src/effects/AudioEffectAnalogDelay.cpp @@ -9,23 +9,17 @@ namespace BAGuitar { -constexpr int MIDI_NUM_PARAMS = 4; constexpr int MIDI_CHANNEL = 0; constexpr int MIDI_CONTROL = 1; -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 + 536870912, 988616936, 455608573, 834606945, -482959709, + 536870912, 1031466345, 498793368, 965834205, -467402235, + 536870912, 1105821939, 573646688, 928470657, -448083489, + 2339, 5093, 2776, 302068995, 4412722 }; @@ -200,13 +194,40 @@ void AudioEffectAnalogDelay::delay(size_t delaySamples) m_delaySamples= delaySamples; } +void AudioEffectAnalogDelay::m_preProcessing(audio_block_t *out, audio_block_t *dry, audio_block_t *wet) +{ + if ( out && dry && wet) { + alphaBlend(out, dry, wet, m_feedback); + m_iir->process(out->data, out->data, AUDIO_BLOCK_SAMPLES); + } else if (dry) { + memcpy(out->data, dry->data, sizeof(int16_t) * AUDIO_BLOCK_SAMPLES); + } +} + +void AudioEffectAnalogDelay::m_postProcessing(audio_block_t *out, audio_block_t *dry, audio_block_t *wet) +{ + if (!out) return; // no valid output buffer + + 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); + } + // Set the output volume + gainAdjust(out, out, m_volume, 1); + +} + void AudioEffectAnalogDelay::processMidi(int channel, int control, int value) { + float val = (float)value / 127.0f; - if ((m_midiConfig[MIDI_DELAY][MIDI_CHANNEL] == channel) && - (m_midiConfig[MIDI_DELAY][MIDI_CONTROL] == control)) { + if ((m_midiConfig[DELAY][MIDI_CHANNEL] == channel) && + (m_midiConfig[DELAY][MIDI_CONTROL] == control)) { // Delay m_maxDelaySamples = m_memory->getSlot()->size(); Serial.println(String("AudioEffectAnalogDelay::delay: ") + val + String(" out of ") + m_maxDelaySamples); @@ -214,75 +235,47 @@ void AudioEffectAnalogDelay::processMidi(int channel, int control, int value) return; } - if ((m_midiConfig[MIDI_BYPASS][MIDI_CHANNEL] == channel) && - (m_midiConfig[MIDI_BYPASS][MIDI_CONTROL] == control)) { + if ((m_midiConfig[BYPASS][MIDI_CHANNEL] == channel) && + (m_midiConfig[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; } - if ((m_midiConfig[MIDI_FEEDBACK][MIDI_CHANNEL] == channel) && - (m_midiConfig[MIDI_FEEDBACK][MIDI_CONTROL] == control)) { + if ((m_midiConfig[FEEDBACK][MIDI_CHANNEL] == channel) && + (m_midiConfig[FEEDBACK][MIDI_CONTROL] == control)) { // Feedback Serial.println(String("AudioEffectAnalogDelay::feedback: ") + val); feedback(val); return; } - if ((m_midiConfig[MIDI_MIX][MIDI_CHANNEL] == channel) && - (m_midiConfig[MIDI_MIX][MIDI_CONTROL] == control)) { + if ((m_midiConfig[MIX][MIDI_CHANNEL] == channel) && + (m_midiConfig[MIX][MIDI_CONTROL] == control)) { // Mix Serial.println(String("AudioEffectAnalogDelay::mix: ") + val); mix(val); return; } -} -void AudioEffectAnalogDelay::mapMidiDelay(int control, int channel) -{ - m_midiConfig[MIDI_DELAY][MIDI_CHANNEL] = channel; - m_midiConfig[MIDI_DELAY][MIDI_CONTROL] = control; -} - -void AudioEffectAnalogDelay::mapMidiBypass(int control, int channel) -{ - m_midiConfig[MIDI_BYPASS][MIDI_CHANNEL] = channel; - m_midiConfig[MIDI_BYPASS][MIDI_CONTROL] = control; -} - -void AudioEffectAnalogDelay::mapMidiFeedback(int control, int channel) -{ - m_midiConfig[MIDI_FEEDBACK][MIDI_CHANNEL] = channel; - m_midiConfig[MIDI_FEEDBACK][MIDI_CONTROL] = control; -} - -void AudioEffectAnalogDelay::mapMidiMix(int control, int channel) -{ - m_midiConfig[MIDI_MIX][MIDI_CHANNEL] = channel; - m_midiConfig[MIDI_MIX][MIDI_CONTROL] = control; -} - - -void AudioEffectAnalogDelay::m_preProcessing(audio_block_t *out, audio_block_t *dry, audio_block_t *wet) -{ - if ( out && dry && wet) { - alphaBlend(out, dry, wet, m_feedback); - m_iir->process(out->data, out->data, AUDIO_BLOCK_SAMPLES); - } else if (dry) { - memcpy(out->data, dry->data, sizeof(int16_t) * AUDIO_BLOCK_SAMPLES); + if ((m_midiConfig[VOLUME][MIDI_CHANNEL] == channel) && + (m_midiConfig[VOLUME][MIDI_CONTROL] == control)) { + // Volume + Serial.println(String("AudioEffectAnalogDelay::volume: ") + val); + volume(val); + return; } + } -void AudioEffectAnalogDelay::m_postProcessing(audio_block_t *out, audio_block_t *dry, audio_block_t *wet) +void AudioEffectAnalogDelay::mapMidiControl(int parameter, int midiCC, int midiChannel) { - 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); + if (parameter >= NUM_CONTROLS) { + return ; // Invalid midi parameter } + m_midiConfig[parameter][MIDI_CHANNEL] = midiChannel; + m_midiConfig[parameter][MIDI_CONTROL] = midiCC; } }