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