wip - linear parameter automation is now working

pull/1/head
Steve Lascos 7 years ago
parent ec4a6b26d5
commit 7c2e4d4e90
  1. 13
      src/AudioEffectSOS.h
  2. 8
      src/BAHardware.h
  3. 16
      src/LibBasicFunctions.h
  4. 10
      src/common/AudioDelay.cpp
  5. 8
      src/common/ExternalSramManager.cpp
  6. 100
      src/common/ParameterAutomation.cpp
  7. 5
      src/effects/AudioEffectAnalogDelay.cpp
  8. 72
      src/effects/AudioEffectSOS.cpp

@ -46,6 +46,8 @@ public:
// *** CONSTRUCTORS ***
AudioEffectSOS() = delete;
AudioEffectSOS(float maxDelayMs);
AudioEffectSOS(size_t numSamples);
/// 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.
@ -66,6 +68,9 @@ public:
/// Note that audio still passes through when bypass is enabled.
void bypass(bool byp) { m_bypass = byp; }
/// Activate the gate automation. Input gate will open, then close.
void trigger() { m_inputGateAuto.trigger(); }
/// 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
@ -74,7 +79,7 @@ public:
// ** ENABLE / DISABLE **
/// Enables audio processing. Note: when not enabled, CPU load is nearly zero.
void enable() { m_enable = true; }
void enable();
/// Disables audio process. When disabled, CPU load is nearly zero.
void disable() { m_enable = false; }
@ -123,12 +128,12 @@ private:
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);
BALibrary::ParameterAutomationSequence<float> m_inputGateAuto =
BALibrary::ParameterAutomationSequence<float>(3);
// 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);
void m_postProcessing(audio_block_t *out, audio_block_t *input);
};
}

@ -74,11 +74,17 @@ constexpr size_t MEM_MAX_ADDR[NUM_MEM_SLOTS] = { 131071, 131071 };
/**************************************************************************//**
* General Purpose SPI Interfaces
*****************************************************************************/
enum SpiDeviceId : unsigned {
enum class SpiDeviceId : unsigned {
SPI_DEVICE0 = 0, ///< Arduino SPI device
SPI_DEVICE1 = 1 ///< Arduino SPI1 device
};
constexpr int SPI_MAX_ADDR = 131071; ///< Max address size per chip
constexpr size_t SPI_MEM0_SIZE_BYTES = 131072;
constexpr size_t SPI_MEM0_MAX_AUDIO_SAMPLES = SPI_MEM0_SIZE_BYTES/sizeof(int16_t);
constexpr size_t SPI_MEM1_SIZE_BYTES = 131072;
constexpr size_t SPI_MEM1_MAX_AUDIO_SAMPLES = SPI_MEM1_SIZE_BYTES/sizeof(int16_t);
#else

@ -153,6 +153,12 @@ public:
/// @returns a pointer to the requested audio_block_t
audio_block_t *getBlock(size_t index);
/// Returns the max possible delay samples. For INTERNAL memory, the delay can be equal to
/// the full maxValue specified. For EXTERNAL memory, the max delay is actually one audio
/// block less then the full size to prevent wrapping.
/// @returns the maximum delay offset in units of samples.
size_t getMaxDelaySamples();
/// Retrieve an audio block (or samples) from the buffer.
/// @details when using INTERNAL memory, only supported size is AUDIO_BLOCK_SAMPLES. When using
/// EXTERNAL, a size smaller than AUDIO_BLOCK_SAMPLES can be requested.
@ -167,6 +173,8 @@ public:
/// @returns pointer to the underlying ExtMemSlot.
ExtMemSlot *getSlot() const { return m_slot; }
/// Ween using INTERNAL memory, thsi function can return a pointer to the underlying RingBuffer that contains
/// audio_block_t * pointers.
/// @returns pointer to the underlying RingBuffer
@ -183,6 +191,7 @@ private:
MemType m_type; ///< when 0, INTERNAL memory, when 1, external MEMORY.
RingBuffer<audio_block_t *> *m_ringBuffer = nullptr; ///< When using INTERNAL memory, a RingBuffer will be created.
ExtMemSlot *m_slot = nullptr; ///< When using EXTERNAL memory, an ExtMemSlot must be provided.
size_t m_maxDelaySamples = 0; ///< stores the number of audio samples in the AudioDelay.
};
/**************************************************************************//**
@ -317,6 +326,7 @@ class ParameterAutomation
public:
enum class Function : unsigned {
NOT_CONFIGURED = 0, ///< Initial, unconfigured stage
HOLD, ///< f(x) = constant
LINEAR, ///< f(x) = x
EXPONENTIAL, ///< f(x) = e^x
LOGARITHMIC, ///< f(x) = ln(x)
@ -350,9 +360,10 @@ private:
T m_startValue;
T m_endValue;
bool m_running = false;
T m_currentValueX; ///< the current value of x in f(x)
float m_currentValueX; ///< the current value of x in f(x)
size_t m_duration;
T m_coeffs[3]; ///< some general coefficient storage
float m_coeffs[3]; ///< some general coefficient storage
bool m_positiveSlope = true;
};
@ -380,6 +391,7 @@ private:
ParameterAutomation<T> *m_paramArray[MAX_PARAMETER_SEQUENCES];
int m_currentIndex = 0;
int m_numStages = 0;
bool m_running = false;
};
} // BALibrary

@ -34,6 +34,7 @@ AudioDelay::AudioDelay(size_t maxSamples)
// INTERNAL memory consisting of audio_block_t data structures.
QueuePosition pos = calcQueuePosition(maxSamples);
m_ringBuffer = new RingBuffer<audio_block_t *>(pos.index+2); // If the delay is in queue x, we need to overflow into x+1, thus x+2 total buffers.
m_maxDelaySamples = maxSamples;
}
AudioDelay::AudioDelay(float maxDelayTimeMs)
@ -46,6 +47,7 @@ AudioDelay::AudioDelay(ExtMemSlot *slot)
{
m_type = (MemType::MEM_EXTERNAL);
m_slot = slot;
m_maxDelaySamples = (slot->size() / sizeof(int16_t)) - AUDIO_BLOCK_SAMPLES;
}
AudioDelay::~AudioDelay()
@ -93,6 +95,11 @@ audio_block_t* AudioDelay::getBlock(size_t index)
return ret;
}
size_t AudioDelay::getMaxDelaySamples()
{
return m_maxDelaySamples;
}
bool AudioDelay::getSamples(audio_block_t *dest, size_t offsetSamples, size_t numSamples)
{
if (!dest) {
@ -159,8 +166,9 @@ bool AudioDelay::getSamples(audio_block_t *dest, size_t offsetSamples, size_t nu
return true;
} else {
// numSampmles is > than total slot size
// numSamples is > than total slot size
Serial.println("getSamples(): ERROR numSamples > total slot size");
Serial.println(numSamples + String(" > ") + m_slot->size());
return false;
}
}

@ -37,8 +37,8 @@ ExternalSramManager::ExternalSramManager(unsigned numMemories)
// Initialize the static memory configuration structs
if (!m_configured) {
for (unsigned i=0; i < NUM_MEM_SLOTS; i++) {
m_memConfig[i].size = MEM_MAX_ADDR[i];
m_memConfig[i].totalAvailable = MEM_MAX_ADDR[i];
m_memConfig[i].size = MEM_MAX_ADDR[i]+1;
m_memConfig[i].totalAvailable = MEM_MAX_ADDR[i]+1;
m_memConfig[i].nextAvailable = 0;
m_memConfig[i].m_spi = nullptr;
@ -111,7 +111,9 @@ bool ExternalSramManager::requestMemory(ExtMemSlot *slot, size_t sizeBytes, BAGu
return true;
} else {
// there is not enough memory available for the request
Serial.println(String("ExternalSramManager::requestMemory(): Insufficient memory in slot, request/available: ")
+ sizeBytes + String(" : ")
+ m_memConfig[mem].totalAvailable);
return false;
}
}

@ -28,6 +28,7 @@ namespace BALibrary {
// ParameterAutomation
///////////////////////////////////////////////////////////////////////////////
constexpr int LINEAR_SLOPE = 0;
template <class T>
ParameterAutomation<T>::ParameterAutomation()
{
@ -64,10 +65,20 @@ void ParameterAutomation<T>::reconfigure(T startValue, T endValue, size_t durati
m_function = function;
m_startValue = startValue;
m_endValue = endValue;
m_currentValueX = startValue;
m_currentValueX = static_cast<float>(startValue);
m_duration = durationSamples;
m_running = false;
if (endValue >= startValue) {
// value is increasing
m_positiveSlope = true;
} else {
// value is decreasing
m_positiveSlope = false;
}
float duration = m_duration / static_cast<float>(AUDIO_BLOCK_SAMPLES);
// Pre-compute any necessary coefficients
switch(m_function) {
case Function::EXPONENTIAL :
@ -80,9 +91,14 @@ void ParameterAutomation<T>::reconfigure(T startValue, T endValue, size_t durati
break;
// Default will be same as LINEAR
case Function::HOLD :
m_coeffs[LINEAR_SLOPE] = (1.0f / static_cast<float>(duration)); // convert duration from ms to sec
break;
case Function::LINEAR :
default :
m_coeffs[LINEAR_SLOPE] = (endValue - startValue) / static_cast<T>(m_duration);
// The number of parameter updates will be duration in samples divided by audio sample block size since
// we only update once per block.
m_coeffs[LINEAR_SLOPE] = static_cast<float>(endValue - startValue) / duration; // convert duration from ms to sec
break;
}
}
@ -91,13 +107,33 @@ void ParameterAutomation<T>::reconfigure(T startValue, T endValue, size_t durati
template <class T>
void ParameterAutomation<T>::trigger()
{
m_currentValueX = m_startValue;
if (m_function == Function::HOLD) {
// The HOLD function will move currentValueX from 0 to 1.0 over the desired duration,
// but will always return the startValue.
m_currentValueX = 0.0f;
} else {
m_currentValueX = static_cast<float>(m_startValue);
}
m_running = true;
//Serial.println("ParameterAutomation<T>::trigger() called");
}
template <class T>
T ParameterAutomation<T>::getNextValue()
{
if (m_running == false) {
return m_startValue;
}
if (m_function == Function::HOLD) {
// HOLD is treated as a special case
m_currentValueX += m_coeffs[LINEAR_SLOPE];
if (m_currentValueX >= 1.0) {
m_running = false;
}
return m_startValue;
}
switch(m_function) {
case Function::EXPONENTIAL :
break;
@ -107,24 +143,28 @@ T ParameterAutomation<T>::getNextValue()
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;
// Check if the automation is finished.
if ( ( m_positiveSlope && (m_currentValueX >= m_endValue)) ||
(!m_positiveSlope && (m_currentValueX <= m_endValue)) ) {
m_running = false;
return m_endValue;
} else {
return static_cast<T>(m_currentValueX);
}
}
// Template instantiation
//template class MyStack<int, 6>;
template class ParameterAutomation<float>;
template class ParameterAutomation<int>;
template class ParameterAutomation<unsigned>;
///////////////////////////////////////////////////////////////////////////////
// ParameterAutomationSequence
@ -132,7 +172,6 @@ template class ParameterAutomation<float>;
template <class T>
ParameterAutomationSequence<T>::ParameterAutomationSequence(int numStages)
{
//m_paramArray = malloc(sizeof(ParameterAutomation<T>*) * numStages);
if (numStages < MAX_PARAMETER_SEQUENCES) {
for (int i=0; i<numStages; i++) {
m_paramArray[i] = new ParameterAutomation<T>();
@ -147,37 +186,65 @@ ParameterAutomationSequence<T>::ParameterAutomationSequence(int numStages)
template <class T>
ParameterAutomationSequence<T>::~ParameterAutomationSequence()
{
//if (m_paramArray) {
for (int i=0; i<m_numStages; i++) {
if (m_paramArray[i]) {
delete m_paramArray[i];
}
}
// delete m_paramArray;
//}
}
template <class T>
void ParameterAutomationSequence<T>::setupParameter(int index, T startValue, T endValue, size_t durationSamples, typename ParameterAutomation<T>::Function function)
{
m_paramArray[index]->reconfigure(startValue, endValue, durationSamples, function);
m_currentIndex = 0;
}
template <class T>
void ParameterAutomationSequence<T>::setupParameter(int index, T startValue, T endValue, float durationMilliseconds, typename ParameterAutomation<T>::Function function)
{
m_paramArray[index]->reconfigure(startValue, endValue, durationMilliseconds, function);
m_currentIndex = 0;
}
template <class T>
void ParameterAutomationSequence<T>::trigger(void)
{
m_currentIndex = 0;
for (int i=0; i<m_numStages; i++) {
m_paramArray[i]->trigger();
m_paramArray[0]->trigger();
m_running = true;
//Serial.println("ParameterAutomationSequence<T>::trigger() called");
}
template <class T>
T ParameterAutomationSequence<T>::getNextValue()
{
// Get the next value
T nextValue = m_paramArray[m_currentIndex]->getNextValue();
if (m_running) {
//Serial.println(String("ParameterAutomationSequence<T>::getNextValue() is ") + nextValue
// + String(" from stage ") + m_currentIndex);
// If current stage is done, trigger the next
if (m_paramArray[m_currentIndex]->isFinished()) {
Serial.println(String("Finished stage ") + m_currentIndex);
m_currentIndex++;
if (m_currentIndex >= m_numStages) {
// Last stage already finished
m_running = false;
m_currentIndex = 0;
} else {
// trigger the next stage
m_paramArray[m_currentIndex]->trigger();
}
}
}
return nextValue;
}
template <class T>
bool ParameterAutomationSequence<T>::isFinished()
{
@ -188,6 +255,7 @@ bool ParameterAutomationSequence<T>::isFinished()
break;
}
}
m_running = !finished;
return finished;
}

@ -166,6 +166,11 @@ void AudioEffectAnalogDelay::delay(float milliseconds)
{
size_t delaySamples = calcAudioSamples(milliseconds);
if (delaySamples > m_memory->getMaxDelaySamples()) {
// this exceeds max delay value, limit it.
delaySamples = m_memory->getMaxDelaySamples();
}
if (!m_memory) { Serial.println("delay(): m_memory is not valid"); }
if (!m_externalMemory) {

@ -19,18 +19,51 @@ constexpr int MIDI_CONTROL = 1;
constexpr float MAX_GATE_OPEN_TIME_MS = 3000.0f;
constexpr float MAX_GATE_CLOSE_TIME_MS = 3000.0f;
constexpr int GATE_OPEN_STAGE = 0;
constexpr int GATE_HOLD_STAGE = 1;
constexpr int GATE_CLOSE_STAGE = 2;
AudioEffectSOS::AudioEffectSOS(float maxDelayMs)
: AudioStream(1, m_inputQueueArray)
{
m_memory = new AudioDelay(maxDelayMs);
m_maxDelaySamples = calcAudioSamples(maxDelayMs);
m_externalMemory = false;
}
AudioEffectSOS::AudioEffectSOS(size_t numSamples)
: AudioStream(1, m_inputQueueArray)
{
m_memory = new AudioDelay(numSamples);
m_maxDelaySamples = numSamples;
m_externalMemory = false;
}
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()
{
if (m_memory) delete m_memory;
}
void AudioEffectSOS::enable(void)
{
m_enable = true;
if (m_externalMemory) {
// Because we hold the previous output buffer for an update cycle, the maximum delay is actually
// 1 audio block mess then the max delay returnable from the memory.
m_maxDelaySamples = m_memory->getMaxDelaySamples();
Serial.println(String("SOS Enabled with delay length ") + m_maxDelaySamples + String(" samples"));
}
m_delaySamples = m_maxDelaySamples;
m_inputGateAuto.setupParameter(GATE_OPEN_STAGE, 0.0f, 1.0f, 1000.0f, ParameterAutomation<float>::Function::LINEAR);
m_inputGateAuto.setupParameter(GATE_HOLD_STAGE, 1.0f, 1.0f, 1000.0f, ParameterAutomation<float>::Function::HOLD);
m_inputGateAuto.setupParameter(GATE_CLOSE_STAGE, 1.0f, 0.0f, 1000.0f, ParameterAutomation<float>::Function::LINEAR);
}
void AudioEffectSOS::update(void)
@ -58,7 +91,7 @@ void AudioEffectSOS::update(void)
}
// Check is block is bypassed, if so either transmit input directly or create silence
if (m_bypass == true) {
if ( (m_bypass == true) || (!inputAudioBlock) ) {
// transmit the input directly
if (!inputAudioBlock) {
// create silence
@ -73,6 +106,8 @@ void AudioEffectSOS::update(void)
return;
}
if (!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.
@ -83,6 +118,8 @@ void AudioEffectSOS::update(void)
// get the data. If using external memory with DMA, this won't be filled until
// later.
m_memory->getSamples(blockToOutput, m_delaySamples);
//Serial.println(String("Delay samples:") + m_delaySamples);
//Serial.println(String("Use dma: ") + m_memory->getSlot()->isUseDma());
// If using DMA, we need something else to do while that read executes, so
// move on to input preprocessing
@ -95,6 +132,8 @@ void AudioEffectSOS::update(void)
// 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);
//audio_block_t *blockToRelease = m_memory->addBlock(inputAudioBlock);
//Serial.println("Done adding new block");
// BACK TO OUTPUT PROCESSING
@ -105,13 +144,19 @@ void AudioEffectSOS::update(void)
}
// perform the wet/dry mix mix
//m_postProcessing(blockToOutput, inputAudioBlock, blockToOutput);
m_postProcessing(blockToOutput, blockToOutput);
transmit(blockToOutput);
release(inputAudioBlock);
if (m_previousBlock)
release(m_previousBlock);
m_previousBlock = blockToOutput;
if (m_blockToRelease == m_previousBlock) {
Serial.println("ERROR: POINTER COLLISION");
}
if (m_blockToRelease) release(m_blockToRelease);
m_blockToRelease = blockToRelease;
}
@ -121,12 +166,13 @@ void AudioEffectSOS::gateOpenTime(float milliseconds)
{
// TODO - change the paramter automation to an automation sequence
m_openTimeMs = milliseconds;
//m_inputGateAuto.reconfigure();
m_inputGateAuto.setupParameter(GATE_OPEN_STAGE, 0.0f, 1.0f, m_openTimeMs, ParameterAutomation<float>::Function::LINEAR);
}
void AudioEffectSOS::gateCloseTime(float milliseconds)
{
m_closeTimeMs = milliseconds;
m_inputGateAuto.setupParameter(GATE_CLOSE_STAGE, 1.0f, 0.0f, m_closeTimeMs, ParameterAutomation<float>::Function::LINEAR);
}
////////////////////////////////////////////////////////////////////////
@ -149,7 +195,7 @@ void AudioEffectSOS::processMidi(int channel, int control, int value)
(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);
Serial.println(String("AudioEffectSOS::gate close time (ms): ") + m_closeTimeMs);
return;
}
@ -180,8 +226,8 @@ void AudioEffectSOS::processMidi(int channel, int control, int value)
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!"));
m_inputGateAuto.trigger();
return;
}
}
@ -203,17 +249,25 @@ void AudioEffectSOS::m_preProcessing (audio_block_t *out, audio_block_t *input,
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();
//float gateVol = 1.0f;
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
gainAdjust(&tempAudioBuffer, delayedSignal, m_feedback, 0); // last parameter is coeff shift, 0 bits
combine(out, out, &tempAudioBuffer);
} else if (input) {
memcpy(out->data, input->data, sizeof(int16_t) * AUDIO_BLOCK_SAMPLES);
}
}
void AudioEffectSOS::m_postProcessing(audio_block_t *out, audio_block_t *in)
{
gainAdjust(out, out, m_volume, 0);
}
} // namespace BAEffects

Loading…
Cancel
Save