forked from wirtz/BALibrary
parent
2ca703cc44
commit
808aedf8c0
@ -0,0 +1,136 @@ |
|||||||
|
/**************************************************************************//**
|
||||||
|
* @file |
||||||
|
* @author Steve Lascos |
||||||
|
* @company Blackaddr Audio |
||||||
|
* |
||||||
|
* AudioEffectSOS is a class f |
||||||
|
* |
||||||
|
* @copyright This program is free software: you can redistribute it and/or modify |
||||||
|
* it under the terms of the GNU General Public License as published by |
||||||
|
* the Free Software Foundation, either version 3 of the License, or |
||||||
|
* (at your option) any later version.* |
||||||
|
* |
||||||
|
* This program is distributed in the hope that it will be useful, |
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||||
|
* GNU General Public License for more details. |
||||||
|
* |
||||||
|
* You should have received a copy of the GNU General Public License |
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*****************************************************************************/ |
||||||
|
|
||||||
|
#ifndef __BAGUITAR_BAAUDIOEFFECTSOS_H |
||||||
|
#define __BAGUITAR_BAAUDIOEFFECTSOS_H |
||||||
|
|
||||||
|
#include <Audio.h> |
||||||
|
#include "LibBasicFunctions.h" |
||||||
|
|
||||||
|
namespace BAEffects { |
||||||
|
|
||||||
|
/**************************************************************************//**
|
||||||
|
* AudioEffectSOS |
||||||
|
*****************************************************************************/ |
||||||
|
class AudioEffectSOS : public AudioStream { |
||||||
|
public: |
||||||
|
|
||||||
|
///< List of AudioEffectAnalogDelay MIDI controllable parameters
|
||||||
|
enum { |
||||||
|
BYPASS = 0, ///< controls effect bypass
|
||||||
|
GATE_TRIGGER, ///< begins the gate sequence
|
||||||
|
GATE_OPEN_TIME, ///< controls how long it takes to open the gate
|
||||||
|
GATE_CLOSE_TIME, ///< controls how long it takes to close the gate (release)
|
||||||
|
FEEDBACK, ///< controls the amount of feedback, more gives longer SOS sustain
|
||||||
|
VOLUME, ///< controls the output volume level
|
||||||
|
NUM_CONTROLS ///< this can be used as an alias for the number of MIDI controls
|
||||||
|
}; |
||||||
|
|
||||||
|
// *** CONSTRUCTORS ***
|
||||||
|
AudioEffectSOS() = delete; |
||||||
|
|
||||||
|
/// Construct an analog delay using external SPI via an ExtMemSlot. The amount of
|
||||||
|
/// delay will be determined by the amount of memory in the slot.
|
||||||
|
/// @param slot A pointer to the ExtMemSlot to use for the delay.
|
||||||
|
AudioEffectSOS(BAGuitar::ExtMemSlot *slot); // requires sufficiently sized pre-allocated memory
|
||||||
|
|
||||||
|
virtual ~AudioEffectSOS(); ///< Destructor
|
||||||
|
|
||||||
|
// *** PARAMETERS ***
|
||||||
|
void gateOpenTime(float milliseconds); |
||||||
|
|
||||||
|
void gateCloseTime(float milliseconds); |
||||||
|
|
||||||
|
void feedback(float feedback) { m_feedback = feedback; } |
||||||
|
|
||||||
|
/// Bypass the effect.
|
||||||
|
/// @param byp when true, bypass wil disable the effect, when false, effect is enabled.
|
||||||
|
/// Note that audio still passes through when bypass is enabled.
|
||||||
|
void bypass(bool byp) { m_bypass = byp; } |
||||||
|
|
||||||
|
/// 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; } |
||||||
|
|
||||||
|
// ** 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; |
||||||
|
BAGuitar::AudioDelay *m_memory = nullptr; |
||||||
|
bool m_externalMemory = true; |
||||||
|
audio_block_t *m_previousBlock = nullptr; |
||||||
|
audio_block_t *m_blockToRelease = nullptr; |
||||||
|
size_t m_maxDelaySamples = 0; |
||||||
|
|
||||||
|
// Controls
|
||||||
|
int m_midiConfig[NUM_CONTROLS][2]; // stores the midi parameter mapping
|
||||||
|
size_t m_delaySamples = 0; |
||||||
|
float m_openTimeMs = 0.0f; |
||||||
|
float m_closeTimeMs = 0.0f; |
||||||
|
float m_feedback = 0.0f; |
||||||
|
float m_volume = 1.0f; |
||||||
|
|
||||||
|
// Automated Controls
|
||||||
|
BALibrary::ParameterAutomation<float> m_inputGateAuto = |
||||||
|
BALibrary::ParameterAutomation<float>(0.0f, 1.0f, 0.0f, BALibrary::ParameterAutomation<float>::Function::LINEAR); |
||||||
|
|
||||||
|
// Private functions
|
||||||
|
void m_preProcessing (audio_block_t *out, audio_block_t *input, audio_block_t *delayedSignal); |
||||||
|
//void m_postProcessing(audio_block_t *out, audio_block_t *dry, audio_block_t *wet);
|
||||||
|
}; |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
#endif /* __BAGUITAR_BAAUDIOEFFECTANALOGDELAY_H */ |
@ -0,0 +1,110 @@ |
|||||||
|
/*
|
||||||
|
* ParameterAutomation.cpp |
||||||
|
* |
||||||
|
* Created on: April 14, 2018 |
||||||
|
* Author: slascos |
||||||
|
* |
||||||
|
* This program is free software: you can redistribute it and/or modify |
||||||
|
* it under the terms of the GNU General Public License as published by |
||||||
|
* the Free Software Foundation, either version 3 of the License, or |
||||||
|
* (at your option) any later version.* |
||||||
|
* |
||||||
|
* This program is distributed in the hope that it will be useful, |
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||||
|
* GNU General Public License for more details. |
||||||
|
* |
||||||
|
* You should have received a copy of the GNU General Public License |
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/ |
||||||
|
|
||||||
|
#include "LibBasicFunctions.h" |
||||||
|
|
||||||
|
using namespace BAGuitar; |
||||||
|
|
||||||
|
namespace BALibrary { |
||||||
|
|
||||||
|
constexpr int LINEAR_SLOPE = 0; |
||||||
|
|
||||||
|
template <class T> |
||||||
|
ParameterAutomation<T>::ParameterAutomation(T startValue, T endValue, float durationMilliseconds, Function function) |
||||||
|
{ |
||||||
|
reconfigure(startValue, endValue, calcAudioSamples(durationMilliseconds), function); |
||||||
|
} |
||||||
|
|
||||||
|
template <class T> |
||||||
|
ParameterAutomation<T>::ParameterAutomation(T startValue, T endValue, size_t durationSamples, Function function) |
||||||
|
{ |
||||||
|
reconfigure(startValue, endValue, durationSamples, function); |
||||||
|
} |
||||||
|
|
||||||
|
template <class T> |
||||||
|
void ParameterAutomation<T>::reconfigure(T startValue, T endValue, float durationMilliseconds, Function function) |
||||||
|
{ |
||||||
|
reconfigure(startValue, endValue, calcAudioSamples(durationMilliseconds), function); |
||||||
|
} |
||||||
|
|
||||||
|
template <class T> |
||||||
|
void ParameterAutomation<T>::reconfigure(T startValue, T endValue, size_t durationSamples, Function function) |
||||||
|
{ |
||||||
|
m_function = function; |
||||||
|
m_startValue = startValue; |
||||||
|
m_endValue = endValue; |
||||||
|
m_currentValueX = startValue; |
||||||
|
m_duration = durationSamples; |
||||||
|
|
||||||
|
// Pre-compute any necessary coefficients
|
||||||
|
switch(m_function) { |
||||||
|
case Function::EXPONENTIAL : |
||||||
|
break; |
||||||
|
case Function::LOGARITHMIC : |
||||||
|
break; |
||||||
|
case Function::PARABOLIC : |
||||||
|
break; |
||||||
|
case Function::LOOKUP_TABLE : |
||||||
|
break; |
||||||
|
|
||||||
|
// Default will be same as LINEAR
|
||||||
|
case Function::LINEAR : |
||||||
|
default : |
||||||
|
m_coeffs[LINEAR_SLOPE] = (endValue - startValue) / static_cast<T>(m_duration); |
||||||
|
break; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
template <class T> |
||||||
|
void ParameterAutomation<T>::trigger() |
||||||
|
{ |
||||||
|
m_currentValueX = m_startValue; |
||||||
|
m_running = true; |
||||||
|
} |
||||||
|
|
||||||
|
template <class T> |
||||||
|
T ParameterAutomation<T>::getNextValue() |
||||||
|
{ |
||||||
|
switch(m_function) { |
||||||
|
case Function::EXPONENTIAL : |
||||||
|
break; |
||||||
|
case Function::LOGARITHMIC : |
||||||
|
break; |
||||||
|
case Function::PARABOLIC : |
||||||
|
break; |
||||||
|
case Function::LOOKUP_TABLE : |
||||||
|
break; |
||||||
|
|
||||||
|
// Default will be same as LINEAR
|
||||||
|
case Function::LINEAR : |
||||||
|
default : |
||||||
|
// output = m_currentValueX + slope
|
||||||
|
m_currentValueX += m_coeffs[LINEAR_SLOPE]; |
||||||
|
if (m_currentValueX >= m_endValue) { |
||||||
|
m_currentValueX = m_endValue; |
||||||
|
m_running = false; |
||||||
|
} |
||||||
|
break; |
||||||
|
} |
||||||
|
return m_currentValueX; |
||||||
|
} |
||||||
|
|
||||||
|
} |
@ -0,0 +1,221 @@ |
|||||||
|
/*
|
||||||
|
* AudioEffectSOS.cpp |
||||||
|
* |
||||||
|
* Created on: Apr 14, 2018 |
||||||
|
* Author: blackaddr |
||||||
|
*/ |
||||||
|
|
||||||
|
#include "AudioEffectSOS.h" |
||||||
|
#include "LibBasicFunctions.h" |
||||||
|
|
||||||
|
using namespace BAGuitar; |
||||||
|
using namespace BALibrary; |
||||||
|
|
||||||
|
namespace BAEffects { |
||||||
|
|
||||||
|
constexpr int MIDI_CHANNEL = 0; |
||||||
|
constexpr int MIDI_CONTROL = 1; |
||||||
|
|
||||||
|
constexpr float MAX_GATE_OPEN_TIME_MS = 3000.0f; |
||||||
|
constexpr float MAX_GATE_CLOSE_TIME_MS = 3000.0f; |
||||||
|
|
||||||
|
AudioEffectSOS::AudioEffectSOS(ExtMemSlot *slot) |
||||||
|
: AudioStream(1, m_inputQueueArray) |
||||||
|
{ |
||||||
|
m_memory = new AudioDelay(slot); |
||||||
|
m_maxDelaySamples = (slot->size() / sizeof(int16_t)); |
||||||
|
m_delaySamples = m_maxDelaySamples; |
||||||
|
m_externalMemory = true; |
||||||
|
} |
||||||
|
|
||||||
|
AudioEffectSOS::~AudioEffectSOS() |
||||||
|
{ |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
void AudioEffectSOS::update(void) |
||||||
|
{ |
||||||
|
audio_block_t *inputAudioBlock = receiveReadOnly(); // get the next block of input samples
|
||||||
|
|
||||||
|
// Check is block is disabled
|
||||||
|
if (m_enable == false) { |
||||||
|
// 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; |
||||||
|
} |
||||||
|
|
||||||
|
// 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); |
||||||
|
} |
||||||
|
} |
||||||
|
transmit(inputAudioBlock, 0); |
||||||
|
release(inputAudioBlock); |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
// Otherwise perform normal processing
|
||||||
|
// In order to make use of the SPI DMA, we need to request the read from memory first,
|
||||||
|
// then do other processing while it fills in the back.
|
||||||
|
audio_block_t *blockToOutput = nullptr; // this will hold the output audio
|
||||||
|
blockToOutput = allocate(); |
||||||
|
if (!blockToOutput) return; // skip this update cycle due to failure
|
||||||
|
|
||||||
|
// get the data. If using external memory with DMA, this won't be filled until
|
||||||
|
// later.
|
||||||
|
m_memory->getSamples(blockToOutput, m_delaySamples); |
||||||
|
|
||||||
|
// If using DMA, we need something else to do while that read executes, so
|
||||||
|
// move on to input preprocessing
|
||||||
|
|
||||||
|
// Preprocessing
|
||||||
|
audio_block_t *preProcessed = allocate(); |
||||||
|
// mix the input with the feedback path in the pre-processing stage
|
||||||
|
m_preProcessing(preProcessed, inputAudioBlock, m_previousBlock); |
||||||
|
|
||||||
|
// consider doing the BBD post processing here to use up more time while waiting
|
||||||
|
// for the read data to come back
|
||||||
|
audio_block_t *blockToRelease = m_memory->addBlock(preProcessed); |
||||||
|
|
||||||
|
|
||||||
|
// BACK TO OUTPUT PROCESSING
|
||||||
|
// Check if external DMA, if so, we need to be sure the read is completed
|
||||||
|
if (m_externalMemory && m_memory->getSlot()->isUseDma()) { |
||||||
|
// Using DMA
|
||||||
|
while (m_memory->getSlot()->isReadBusy()) {} |
||||||
|
} |
||||||
|
|
||||||
|
// perform the wet/dry mix mix
|
||||||
|
//m_postProcessing(blockToOutput, inputAudioBlock, blockToOutput);
|
||||||
|
transmit(blockToOutput); |
||||||
|
|
||||||
|
release(inputAudioBlock); |
||||||
|
release(m_previousBlock); |
||||||
|
m_previousBlock = blockToOutput; |
||||||
|
|
||||||
|
if (m_blockToRelease) release(m_blockToRelease); |
||||||
|
m_blockToRelease = blockToRelease; |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
void AudioEffectSOS::gateOpenTime(float milliseconds) |
||||||
|
{ |
||||||
|
// TODO - change the paramter automation to an automation sequence
|
||||||
|
m_openTimeMs = milliseconds; |
||||||
|
//m_inputGateAuto.reconfigure();
|
||||||
|
} |
||||||
|
|
||||||
|
void AudioEffectSOS::gateCloseTime(float milliseconds) |
||||||
|
{ |
||||||
|
m_closeTimeMs = milliseconds; |
||||||
|
} |
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////
|
||||||
|
// MIDI PROCESSING
|
||||||
|
////////////////////////////////////////////////////////////////////////
|
||||||
|
void AudioEffectSOS::processMidi(int channel, int control, int value) |
||||||
|
{ |
||||||
|
|
||||||
|
float val = (float)value / 127.0f; |
||||||
|
|
||||||
|
if ((m_midiConfig[GATE_OPEN_TIME][MIDI_CHANNEL] == channel) && |
||||||
|
(m_midiConfig[GATE_OPEN_TIME][MIDI_CONTROL] == control)) { |
||||||
|
// Gate Open Time
|
||||||
|
gateOpenTime(val * MAX_GATE_OPEN_TIME_MS); |
||||||
|
Serial.println(String("AudioEffectSOS::gate open time (ms): ") + m_openTimeMs); |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
if ((m_midiConfig[GATE_CLOSE_TIME][MIDI_CHANNEL] == channel) && |
||||||
|
(m_midiConfig[GATE_CLOSE_TIME][MIDI_CONTROL] == control)) { |
||||||
|
// Gate Close Time
|
||||||
|
gateCloseTime(val * MAX_GATE_CLOSE_TIME_MS); |
||||||
|
Serial.println(String("AudioEffectSOS::gate close time (ms): ") + m_openTimeMs); |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
if ((m_midiConfig[FEEDBACK][MIDI_CHANNEL] == channel) && |
||||||
|
(m_midiConfig[FEEDBACK][MIDI_CONTROL] == control)) { |
||||||
|
// Feedback
|
||||||
|
Serial.println(String("AudioEffectSOS::feedback: ") + 100*val + String("%")); |
||||||
|
feedback(val); |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
if ((m_midiConfig[VOLUME][MIDI_CHANNEL] == channel) && |
||||||
|
(m_midiConfig[VOLUME][MIDI_CONTROL] == control)) { |
||||||
|
// Volume
|
||||||
|
Serial.println(String("AudioEffectSOS::volume: ") + 100*val + String("%")); |
||||||
|
volume(val); |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
if ((m_midiConfig[BYPASS][MIDI_CHANNEL] == channel) && |
||||||
|
(m_midiConfig[BYPASS][MIDI_CONTROL] == control)) { |
||||||
|
// Bypass
|
||||||
|
if (value >= 65) { bypass(false); Serial.println(String("AudioEffectSOS::not bypassed -> ON") + value); } |
||||||
|
else { bypass(true); Serial.println(String("AudioEffectSOS::bypassed -> OFF") + value); } |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
if ((m_midiConfig[GATE_TRIGGER][MIDI_CHANNEL] == channel) && |
||||||
|
(m_midiConfig[GATE_TRIGGER][MIDI_CONTROL] == control)) { |
||||||
|
// The gate is trigged by any value
|
||||||
|
m_inputGateAuto.trigger(); |
||||||
|
Serial.println(String("AudioEffectSOS::Gate Triggered!")); |
||||||
|
return; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
void AudioEffectSOS::mapMidiControl(int parameter, int midiCC, int midiChannel) |
||||||
|
{ |
||||||
|
if (parameter >= NUM_CONTROLS) { |
||||||
|
return ; // Invalid midi parameter
|
||||||
|
} |
||||||
|
m_midiConfig[parameter][MIDI_CHANNEL] = midiChannel; |
||||||
|
m_midiConfig[parameter][MIDI_CONTROL] = midiCC; |
||||||
|
} |
||||||
|
|
||||||
|
//////////////////////////////////////////////////////////////////////
|
||||||
|
// PRIVATE FUNCTIONS
|
||||||
|
//////////////////////////////////////////////////////////////////////
|
||||||
|
void AudioEffectSOS::m_preProcessing (audio_block_t *out, audio_block_t *input, audio_block_t *delayedSignal) |
||||||
|
{ |
||||||
|
if ( out && input && delayedSignal) { |
||||||
|
// Multiply the input signal by the automated gate value
|
||||||
|
// Multiply the delayed signal by the user set feedback value
|
||||||
|
// then mix together.
|
||||||
|
float gateVol = m_inputGateAuto.getNextValue(); |
||||||
|
audio_block_t tempAudioBuffer; |
||||||
|
gainAdjust(out, input, gateVol, 0); // last paremeter is coeff shift, 0 bits
|
||||||
|
gainAdjust(&tempAudioBuffer, delayedSignal, m_feedback, 0); // last paremeter is coeff shift, 0 bits
|
||||||
|
combine(out, out, &tempAudioBuffer); |
||||||
|
} else if (input) { |
||||||
|
memcpy(out->data, input->data, sizeof(int16_t) * AUDIO_BLOCK_SAMPLES); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
} // namespace BAEffects
|
||||||
|
|
||||||
|
|
||||||
|
|
Loading…
Reference in new issue