diff --git a/src/effect_base.h b/src/effect_base.h index ba44d7f..1e720da 100644 --- a/src/effect_base.h +++ b/src/effect_base.h @@ -7,6 +7,7 @@ #define EFFECT_NONE 0 #define EFFECT_CHORUS 1 #define EFFECT_DELAY 2 +#define EFFECT_LPF 3 class AudioEffect { diff --git a/src/effect_delay.cpp b/src/effect_delay.cpp index a79536d..228ce62 100644 --- a/src/effect_delay.cpp +++ b/src/effect_delay.cpp @@ -1,30 +1,30 @@ #include #include "effect_delay.h" -LOGMODULE ("fx chorus"); +LOGMODULE ("fx delay"); AudioEffectDelay::AudioEffectDelay(float32_t samplerate) : AudioEffect(samplerate) { - bufferSize = (int) samplerate * MAX_DELAY_TIME; - bufferL = new float32_t[this->bufferSize]; - bufferR = new float32_t[this->bufferSize]; - index = 0; + this->bufferSize = (int) samplerate * MAX_DELAY_TIME; + this->bufferL = new float32_t[this->bufferSize]; + this->bufferR = new float32_t[this->bufferSize]; + this->index = 0; - for (size_t i = 0; i < bufferSize; i++) + for (size_t i = 0; i < this->bufferSize; i++) { - bufferL[i] = 0.0f; - bufferR[i] = 0.0f; + this->bufferL[i] = 0.0f; + this->bufferR[i] = 0.0f; } - timeL = 0.36f; - timeR = 0.36f; - feedback = 0.3f; + this->timeL = 0.36f; + this->timeR = 0.36f; + this->feedback = 0.3f; } AudioEffectDelay::~AudioEffectDelay() { - delete bufferL; - delete bufferR; + delete this->bufferL; + delete this->bufferR; } unsigned AudioEffectDelay::getId() @@ -37,30 +37,30 @@ void AudioEffectDelay::doProcess(const float32_t* inblockL, const float32_t* inb for (uint16_t i=0; i < len; i++) { // Update buffers - bufferL[index] = inblockL[i]; - bufferR[index] = inblockR[i]; + this->bufferL[index] = inblockL[i]; + this->bufferR[index] = inblockR[i]; // Calculate offsets - int offsetL = index - (timeL * samplerate); + int offsetL = this->index - (this->timeL * this->samplerate); if (offsetL < 0) { - offsetL = bufferSize + offsetL; + offsetL = this->bufferSize + offsetL; } - int offsetR = index - (timeR * samplerate); + int offsetR = this->index - (this->timeR * this->samplerate); if (offsetR < 0) { - offsetR = bufferSize + offsetR; + offsetR = this->bufferSize + offsetR; } - bufferL[index] += bufferL[offsetL] * feedback; - bufferR[index] += bufferR[offsetR] * feedback; + this->bufferL[index] += this->bufferL[offsetL] * this->feedback; + this->bufferR[index] += this->bufferR[offsetR] * this->feedback; - outblockL[i] = bufferL[index]; - outblockR[i] = bufferR[index]; + outblockL[i] = this->bufferL[index]; + outblockR[i] = this->bufferR[index]; // Update index - index++; - if (index >= bufferSize) + this->index++; + if (this->index >= this->bufferSize) { - index = 0; + this->index = 0; } } } diff --git a/src/effect_lpf.h b/src/effect_lpf.h new file mode 100644 index 0000000..cf4bf63 --- /dev/null +++ b/src/effect_lpf.h @@ -0,0 +1,117 @@ +#ifndef _EFFECT_LPF_H +#define _EFFECT_LPF_H + +#include "effect_base.h" + +class AudioEffectLPF : public AudioEffect +{ +public: + static constexpr float32_t MIN_CUTOFF = 0.00001f; + static constexpr float32_t MAX_CUTOFF = 8000.0f; + static constexpr float32_t MIN_RES = 0.0f; + static constexpr float32_t MAX_RES = 1.0f; + + AudioEffectLPF(float32_t samplerate) : AudioEffect(samplerate) + { + // Setup init values + this->setCutoff(2000.0f); + this->setResonance(MIN_RES); + } + virtual ~AudioEffectLPF() + { + } + + virtual unsigned getId() + { + return EFFECT_LPF; + } + + /** + * Set the static cutoff frequency of the filter. + * Cutoff frequency must be between MIN_CUTOFF and MAX_CUTOFF. + * Envelope signal varies the cutoff frequency from this static value. + */ + void setCutoff(float32_t cutoff) + { + // Check limits + cutoff = (cutoff < MIN_CUTOFF) ? MIN_CUTOFF : cutoff; + cutoff = (cutoff > MAX_CUTOFF) ? MAX_CUTOFF : cutoff; + + this->cutoff = cutoff; + recalculate(); + } + + /** + * Set the resonance of the filter. + * Valid values are between 0.0 and 1.0 where + * 0.0 is no resonance and 1.0 is full resonance or oscillation. + */ + void setResonance(float32_t resonance) + { + // Check limits + resonance = (resonance < MIN_RES) ? MIN_RES : resonance; + resonance = (resonance > MAX_RES) ? MAX_RES : resonance; + + this->resonance = resonance; + recalculate(); + } + +protected: + virtual void doProcess(const float32_t* inblockL, const float32_t* inblockR, float32_t* outblockL, float32_t* outblockR, uint16_t len) + { + for (int i = 0; i < len; i++) { + // Get a sample to process + float32_t s = inblockL[i]; + + // Return processed sample from filter + outblockL[i] = processSample(s); + } + } + +private: + float32_t cutoff; + float32_t resonance; + float32_t x, r, p, k, y1, y2, y3, y4, oldx, oldy1, oldy2, oldy3; + + /** + * Recalculate filter parameters on changes to cutoff or resonance + */ + void recalculate() + { + float32_t f = (cutoff + cutoff) / this->samplerate; + p = f * (1.8 - (0.8 * f)); + k = p + p - 1.0; + + float32_t t = (1.0 - p) * 1.386249; + float32_t t2 = 12.0 + t * t; + r = resonance * (t2 + 6.0 * t) / (t2 - 6.0 * t); + } + + /** + * Process a single sample through the filter + */ + float32_t processSample(float32_t input) + { + // Process input + //x = ((float32_t) input/ F32_MAX) - r * y4; + x = input - r * y4; + + // Four cascaded one pole filters (bilinear transform) + y1 = x * p + oldx * p - k * y1; + y2 = y1 * p + oldy1 * p - k * y2; + y3 = y2 * p + oldy2 * p - k * y3; + y4 = y3 * p + oldy3 * p - k * y4; + + // Clipper band limited sigmoid + y4 -= (y4 * y4 * y4) / 6.0; + + oldx = x; + oldy1 = y1; + oldy2 = y2; + oldy3 = y3; + //return (float32_t) (y4 * F32_MAX); + return y4; + } +}; + +#endif // _EFFECT_LPF_H \ No newline at end of file diff --git a/src/effects.h b/src/effects.h index 614a60c..9083043 100644 --- a/src/effects.h +++ b/src/effects.h @@ -1,8 +1,38 @@ #ifndef _EFFECTS_H #define _EFFECTS_H +#include #include "effect_base.h" #include "effect_chorus.h" #include "effect_delay.h" +#include "effect_lpf.h" + +inline AudioEffect* newAudioEffect(unsigned type, float32_t samplerate) +{ + switch (type) + { + case EFFECT_CHORUS: + return new AudioEffectChorus(samplerate); + case EFFECT_DELAY: + return new AudioEffectDelay(samplerate); + case EFFECT_LPF: + return new AudioEffectLPF(samplerate); + case EFFECT_NONE: + default: + return new AudioEffectNone(samplerate); + } +} + +inline std::string getFXTypeName(int nValue) +{ + switch (nValue) + { + case EFFECT_CHORUS: return "Juno Chorus"; + case EFFECT_DELAY: return "Delay"; + case EFFECT_LPF: return "LP Filter"; + case EFFECT_NONE: + default: return "None"; + } +} #endif // _EFFECTS_H \ No newline at end of file diff --git a/src/minidexed.cpp b/src/minidexed.cpp index 0650ff1..73e4048 100644 --- a/src/minidexed.cpp +++ b/src/minidexed.cpp @@ -591,28 +591,16 @@ void CMiniDexed::SetPan (unsigned nPan, unsigned nTG) void CMiniDexed::setInsertFXType (unsigned nType, unsigned nTG) { - nType=constrain((int) nType, 0, 2); - assert (nTG < CConfig::ToneGenerators); - m_InsertFXSpinLock[nTG]->Acquire(); - - delete m_InsertFX[nTG]; - - switch (nType) - { - case EFFECT_CHORUS: - m_InsertFX[nTG] = new AudioEffectChorus(m_pConfig->GetSampleRate()); - break; - case EFFECT_DELAY: - m_InsertFX[nTG] = new AudioEffectDelay(m_pConfig->GetSampleRate()); - break; - case EFFECT_NONE: - default: - m_InsertFX[nTG] = new AudioEffectNone(m_pConfig->GetSampleRate()); - break; + // If the effect type is already set just return + if (m_InsertFX[nTG]->getId() == nType) { + return; } + m_InsertFXSpinLock[nTG]->Acquire(); + delete m_InsertFX[nTG]; + m_InsertFX[nTG] = newAudioEffect(nType, m_pConfig->GetSampleRate()); m_InsertFXSpinLock[nTG]->Release(); m_UI.ParameterChanged (); diff --git a/src/uimenu.cpp b/src/uimenu.cpp index 3d8652d..55c0978 100644 --- a/src/uimenu.cpp +++ b/src/uimenu.cpp @@ -250,7 +250,7 @@ const CUIMenu::TParameter CUIMenu::s_TGParameter[CMiniDexed::TGParameterUnknown] {0, CSysExFileLoader::VoicesPerBank-1, 1}, // TGParameterProgram {0, 127, 8, ToVolume}, // TGParameterVolume {0, 127, 8, ToPan}, // TGParameterPan - {0, 2, 1, ToFXType}, // TGParameterInsertFXType + {0, 3, 1, ToFXType}, // TGParameterInsertFXType {-99, 99, 1}, // TGParameterMasterTune {0, 99, 1}, // TGParameterCutoff {0, 99, 1}, // TGParameterResonance @@ -1252,13 +1252,7 @@ string CUIMenu::ToPolyMono (int nValue) string CUIMenu::ToFXType (int nValue) { - switch (nValue) - { - case EFFECT_CHORUS: return "Juno Chorus"; - case EFFECT_DELAY: return "Delay"; - case EFFECT_NONE: - default: return "None"; - } + return getFXTypeName(nValue); } void CUIMenu::TGShortcutHandler (TMenuEvent Event)