Added volume control, some code cleanup

pull/1/head
Steve Lascos 7 years ago
parent b649550cb3
commit 429c37f8ac
  1. 65
      examples/Delay/AnalogDelayDemo/AnalogDelayDemo.ino
  2. 53
      src/AudioEffectAnalogDelay.h
  3. 17
      src/LibBasicFunctions.h
  4. 12
      src/common/AudioHelpers.cpp
  5. 99
      src/effects/AudioEffectAnalogDelay.cpp

@ -1,7 +1,8 @@
//#include <MIDI.h>
#include <MIDI.h>
#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);
myDelay.mapMidiBypass(16);
myDelay.mapMidiDelay(20);
myDelay.mapMidiFeedback(21);
myDelay.mapMidiMix(22);
// 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);
// Besure to enable the delay, by default it's processing is off.
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
}

@ -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);

@ -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 <br>
/// 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 <br>
/// 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 T>
class RingBuffer; // forward declare so AudioDelay can use it.

@ -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);

@ -9,15 +9,9 @@
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;
@ -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;
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_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)
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;
}
}

Loading…
Cancel
Save