|
|
|
@ -5,24 +5,30 @@ |
|
|
|
|
* Author: slascos |
|
|
|
|
*/ |
|
|
|
|
#include <new> |
|
|
|
|
#include <cmath> |
|
|
|
|
#include "AudioEffectAnalogChorusFilters.h" |
|
|
|
|
#include "AudioEffectAnalogChorus.h" |
|
|
|
|
|
|
|
|
|
using namespace BALibrary; |
|
|
|
|
|
|
|
|
|
//#define INTERPOLATED_DELAY Uncomment this line to test the inteprolated delay which adds 1/10th of a sample
|
|
|
|
|
|
|
|
|
|
namespace BAEffects { |
|
|
|
|
|
|
|
|
|
constexpr int MIDI_CHANNEL = 0; |
|
|
|
|
constexpr int MIDI_CONTROL = 1; |
|
|
|
|
|
|
|
|
|
constexpr float DELAY_REFERENCE_F = static_cast<float>(AUDIO_BLOCK_SAMPLES/2); |
|
|
|
|
|
|
|
|
|
AudioEffectAnalogChorus::AudioEffectAnalogChorus() |
|
|
|
|
: AudioStream(1, m_inputQueueArray) |
|
|
|
|
{ |
|
|
|
|
m_memory = new AudioDelay(m_DEFAULT_DELAY_MS + m_DELAY_RANGE); |
|
|
|
|
m_maxDelaySamples = calcAudioSamples(m_DEFAULT_DELAY_MS + m_DELAY_RANGE); |
|
|
|
|
m_memory = new AudioDelay(m_DEFAULT_AVERAGE_DELAY_MS + m_DELAY_RANGE); |
|
|
|
|
m_maxDelaySamples = calcAudioSamples(m_DEFAULT_AVERAGE_DELAY_MS + m_DELAY_RANGE); |
|
|
|
|
m_averageDelaySamples = static_cast<float>(calcAudioSamples(m_DEFAULT_AVERAGE_DELAY_MS)); |
|
|
|
|
m_delayRange = static_cast<float>(calcAudioSamples(m_DELAY_RANGE)); |
|
|
|
|
|
|
|
|
|
m_constructFilter(); |
|
|
|
|
m_lfo.setWaveform(Waveform::TRIANGLE); |
|
|
|
|
m_lfo.setRateAudio(4.0f); // Default to 4 Hz
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// requires preallocated memory large enough
|
|
|
|
@ -31,8 +37,13 @@ AudioEffectAnalogChorus::AudioEffectAnalogChorus(ExtMemSlot *slot) |
|
|
|
|
{ |
|
|
|
|
m_memory = new AudioDelay(slot); |
|
|
|
|
m_maxDelaySamples = (slot->size() / sizeof(int16_t)); |
|
|
|
|
m_averageDelaySamples = static_cast<float>(calcAudioSamples(m_DEFAULT_AVERAGE_DELAY_MS)); |
|
|
|
|
m_delayRange = static_cast<float>(calcAudioSamples(m_DELAY_RANGE)); |
|
|
|
|
|
|
|
|
|
m_externalMemory = true; |
|
|
|
|
m_constructFilter(); |
|
|
|
|
m_lfo.setWaveform(Waveform::TRIANGLE); |
|
|
|
|
m_lfo.setRateAudio(4.0f); // Default to 4 Hz
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
AudioEffectAnalogChorus::~AudioEffectAnalogChorus() |
|
|
|
@ -48,6 +59,19 @@ void AudioEffectAnalogChorus::m_constructFilter(void) |
|
|
|
|
m_iir = new IirBiQuadFilterHQ(CE2_NUM_STAGES, reinterpret_cast<const int32_t *>(&CE2), CE2_COEFF_SHIFT); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
void AudioEffectAnalogChorus::setWaveform(BALibrary::Waveform waveform) |
|
|
|
|
{ |
|
|
|
|
switch(waveform) { |
|
|
|
|
case Waveform::SINE : |
|
|
|
|
case Waveform::TRIANGLE : |
|
|
|
|
case Waveform::SAWTOOTH : |
|
|
|
|
m_lfo.setWaveform(waveform); |
|
|
|
|
break; |
|
|
|
|
default : |
|
|
|
|
Serial.println("AudioEffectAnalogChorus::setWaveform: Unsupported Waveform"); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
void AudioEffectAnalogChorus::setFilterCoeffs(int numStages, const int32_t *coeffs, int coeffShift) |
|
|
|
|
{ |
|
|
|
|
m_iir->changeFilterCoeffs(numStages, coeffs, coeffShift); |
|
|
|
@ -75,7 +99,7 @@ void AudioEffectAnalogChorus::update(void) |
|
|
|
|
|
|
|
|
|
// Check is block is disabled
|
|
|
|
|
if (m_enable == false) { |
|
|
|
|
// do not transmit or process any audio, return as quickly as possible.
|
|
|
|
|
// do not transmit or proess any audio, return as quickly as possible.
|
|
|
|
|
if (inputAudioBlock) release(inputAudioBlock); |
|
|
|
|
|
|
|
|
|
// release all held memory resources
|
|
|
|
@ -118,14 +142,29 @@ void AudioEffectAnalogChorus::update(void) |
|
|
|
|
|
|
|
|
|
// get the data. If using external memory with DMA, this won't be filled until
|
|
|
|
|
// later.
|
|
|
|
|
#ifdef INTERPOLATED_DELAY |
|
|
|
|
int16_t extendedBuffer[AUDIO_BLOCK_SAMPLES+1]; // need one more sample for intepolating between 128th and 129th (last sample)
|
|
|
|
|
m_memory->getSamples(extendedBuffer, m_delaySamples, AUDIO_BLOCK_SAMPLES+1); |
|
|
|
|
#else |
|
|
|
|
m_memory->getSamples(blockToOutput, m_delaySamples); |
|
|
|
|
#endif |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// We need to grab two blocks of audio since the modulating delay value from the LFO
|
|
|
|
|
// can exceed the length of one audio block during the time frame of one audio block.
|
|
|
|
|
int16_t extendedBuffer[(2*AUDIO_BLOCK_SAMPLES)]; // need one more sample for interpolating between 128th and 129th (last sample)
|
|
|
|
|
|
|
|
|
|
// Get next vector of lfo values, they will range range from -1.0 to +1.0f.
|
|
|
|
|
float *lfoValues = m_lfo.getNextVector(); |
|
|
|
|
//float lfoValues[128];
|
|
|
|
|
for (int i=0; i<128; i++) { lfoValues[i] = lfoValues[i] * m_lfoDepth; } |
|
|
|
|
|
|
|
|
|
// Calculate the starting delay from the first lfo sample. This will represent the 'reference' delay
|
|
|
|
|
// for this output block
|
|
|
|
|
float referenceDelay = m_averageDelaySamples + (lfoValues[0] * m_delayRange); |
|
|
|
|
unsigned delaySamples = static_cast<unsigned>(referenceDelay); // round down to the nearest audio sample for indexing into AudioDelay class
|
|
|
|
|
|
|
|
|
|
// From a given current delay value, while reading out the next 128, the delay could slew up or down
|
|
|
|
|
// AUDIO_BLOCK_SAMPLES/2 cycles of delay. For example...
|
|
|
|
|
// Pitching up : current + 128 + 64
|
|
|
|
|
// Pitching down: current - 64 + 128
|
|
|
|
|
// We need to grab 2*AUDIO_BLOCK_SAMPLES. Be aware that audio samples are stored BACKWARDS in the buffers.
|
|
|
|
|
// m_memory->getSamples(extendedBuffer , delaySamples - (AUDIO_BLOCK_SAMPLES/2), AUDIO_BLOCK_SAMPLES);
|
|
|
|
|
// m_memory->getSamples(extendedBuffer + AUDIO_BLOCK_SAMPLES, delaySamples +( AUDIO_BLOCK_SAMPLES/2), AUDIO_BLOCK_SAMPLES);
|
|
|
|
|
m_memory->getSamples(extendedBuffer + AUDIO_BLOCK_SAMPLES, delaySamples - (AUDIO_BLOCK_SAMPLES/2), AUDIO_BLOCK_SAMPLES); |
|
|
|
|
m_memory->getSamples(extendedBuffer , delaySamples +( AUDIO_BLOCK_SAMPLES/2), AUDIO_BLOCK_SAMPLES); |
|
|
|
|
// If using DMA, we need something else to do while that read executes, so
|
|
|
|
|
// move on to input preprocessing
|
|
|
|
|
|
|
|
|
@ -142,16 +181,39 @@ void AudioEffectAnalogChorus::update(void) |
|
|
|
|
// 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
|
|
|
|
|
// Using DMA so we have to busy-wait here until DMA is done
|
|
|
|
|
while (m_memory->getSlot()->isReadBusy()) {} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
#ifdef INTERPOLATED_DELAY |
|
|
|
|
// TODO: partial delay testing
|
|
|
|
|
// extendedBuffer is oversized
|
|
|
|
|
//memcpy(blockToOutput->data, &extendedBuffer[1], sizeof(int16_t)*AUDIO_BLOCK_SAMPLES);
|
|
|
|
|
m_memory->interpolateDelay(extendedBuffer, blockToOutput->data, 0.1f, AUDIO_BLOCK_SAMPLES); |
|
|
|
|
#endif |
|
|
|
|
double bufferIndexFloat; |
|
|
|
|
int delayIndex; |
|
|
|
|
for (int i=0, j=AUDIO_BLOCK_SAMPLES-1; i<AUDIO_BLOCK_SAMPLES; i++,j--) { |
|
|
|
|
// each output sample will be an interpolated value between two samples
|
|
|
|
|
// the precise delay value will be based on the LFO vector values.
|
|
|
|
|
// For each output sample, calculate the floating point delay offset from the reference delay.
|
|
|
|
|
// This will be an offset from location AUDIO_BLOCK_SAMPLES/2 (e.g. 64) in the buffer.
|
|
|
|
|
float offsetDelayFromRef = m_averageDelaySamples + (lfoValues[i] * m_delayRange) - referenceDelay; |
|
|
|
|
float bufferPosition = DELAY_REFERENCE_F + offsetDelayFromRef; |
|
|
|
|
|
|
|
|
|
// Get the interpolation coefficients from the fractional part of the buffer position
|
|
|
|
|
float fraction1 = modf(bufferPosition, &bufferIndexFloat); |
|
|
|
|
float fraction2 = 1.0f - fraction1; |
|
|
|
|
//fraction1 = 0.5f;
|
|
|
|
|
//fraction2 = 0.5f;
|
|
|
|
|
delayIndex = static_cast<unsigned>(bufferIndexFloat); |
|
|
|
|
if ( (delayIndex < 0) || (delayIndex > 256) ) { |
|
|
|
|
Serial.println(String("lfoValues[") + i + String("]:") + lfoValues[i] + |
|
|
|
|
String(" referenceDelay:") + referenceDelay + |
|
|
|
|
String(" bufferPosition:") + bufferPosition + |
|
|
|
|
String(" delayIndex:") + delayIndex) ; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
//delayIndex = 64+i;
|
|
|
|
|
blockToOutput->data[j] = static_cast<int16_t>( |
|
|
|
|
(static_cast<float>(extendedBuffer[j+delayIndex]) * fraction1) + |
|
|
|
|
(static_cast<float>(extendedBuffer[j+delayIndex+1]) * fraction2) ); |
|
|
|
|
//blockToOutput->data[i] = extendedBuffer[64+i];
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// perform the wet/dry mix mix
|
|
|
|
|
m_postProcessing(blockToOutput, inputAudioBlock, blockToOutput); |
|
|
|
@ -174,6 +236,7 @@ void AudioEffectAnalogChorus::update(void) |
|
|
|
|
void AudioEffectAnalogChorus::m_preProcessing(audio_block_t *out, audio_block_t *dry, audio_block_t *wet) |
|
|
|
|
{ |
|
|
|
|
memcpy(out->data, dry->data, sizeof(int16_t) * AUDIO_BLOCK_SAMPLES); |
|
|
|
|
// TODO: Clean this up with proper preprocessing
|
|
|
|
|
// if ( out && dry && wet) {
|
|
|
|
|
// alphaBlend(out, dry, wet, m_feedback);
|
|
|
|
|
// m_iir->process(out->data, out->data, AUDIO_BLOCK_SAMPLES);
|
|
|
|
@ -200,45 +263,25 @@ void AudioEffectAnalogChorus::m_postProcessing(audio_block_t *out, audio_block_t |
|
|
|
|
|
|
|
|
|
void AudioEffectAnalogChorus::setDelayConfig(float averageDelayMs, float delayRangeMs) |
|
|
|
|
{ |
|
|
|
|
size_t delaySamples = calcAudioSamples(averageDelayMs + delayRangeMs); |
|
|
|
|
|
|
|
|
|
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) { |
|
|
|
|
// internal memory
|
|
|
|
|
// Do nothing
|
|
|
|
|
} else { |
|
|
|
|
// external memory
|
|
|
|
|
ExtMemSlot *slot = m_memory->getSlot(); |
|
|
|
|
|
|
|
|
|
if (!slot) { Serial.println("ERROR: slot ptr is not valid"); } |
|
|
|
|
if (!slot->isEnabled()) { |
|
|
|
|
slot->enable(); |
|
|
|
|
Serial.println("WEIRD: slot was not enabled"); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
setDelayConfig(calcAudioSamples(averageDelayMs), calcAudioSamples(delayRangeMs)); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
void AudioEffectAnalogChorus::setDelayConfig(size_t averageDelayNumSamples, size_t delayRangeNumSamples) |
|
|
|
|
{ |
|
|
|
|
size_t delaySamples = averageDelayNumSamples + delayRangeNumSamples; |
|
|
|
|
m_averageDelaySamples = averageDelayNumSamples; |
|
|
|
|
m_delayRange = delayRangeNumSamples; |
|
|
|
|
|
|
|
|
|
if (delaySamples > m_memory->getMaxDelaySamples()) { |
|
|
|
|
// this exceeds max delay value, limit it.
|
|
|
|
|
delaySamples = m_memory->getMaxDelaySamples(); |
|
|
|
|
m_averageDelaySamples = delaySamples/2; |
|
|
|
|
m_delayRange = delaySamples/2; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
if (!m_memory) { Serial.println("delay(): m_memory is not valid"); } |
|
|
|
|
|
|
|
|
|
if (!m_externalMemory) { |
|
|
|
|
// internal memory
|
|
|
|
|
// Do nothing
|
|
|
|
|
} else { |
|
|
|
|
if (m_externalMemory) { |
|
|
|
|
// external memory
|
|
|
|
|
ExtMemSlot *slot = m_memory->getSlot(); |
|
|
|
|
|
|
|
|
|