Fixing the Chorus and enhancing the FXEngine to disable internal LFO

pull/409/head
abscisys 2 years ago
parent acc96b1291
commit a376b41e72
  1. 118
      src/fx_chorus.cpp
  2. 24
      src/fx_chorus.h
  3. 5
      src/fx_components.cpp
  4. 3
      src/fx_components.h
  5. 52
      src/fx_engine.hpp
  6. 8
      src/test/fxrack_test.cpp

@ -4,89 +4,92 @@
#define CHORUS_BUFFER_SIZE 8192 #define CHORUS_BUFFER_SIZE 8192
#define LFO_MIN_FREQ 0.0f #define LFO1_MAX_FREQ 0.25f
#define LFO_MAX_FREQ 1.0f #define LFO2_MAX_FREQ 0.35f
Chorus::Chorus(float32_t sampling_rate, unsigned voices, float32_t rate, float32_t depth, float32_t feedback) : #define FULLSCALE_DEPTH_RATIO 384.0f
Chorus::Chorus(float32_t sampling_rate) :
FXElement(sampling_rate), FXElement(sampling_rate),
NumVoices(voices), engine_(sampling_rate, LFO1_MAX_FREQ, LFO2_MAX_FREQ),
sample_position_ratio_(sampling_rate / 1000.0f), rate_(0.0f),
lfo_(sampling_rate, LFO::Waveform::Sine, LFO_MIN_FREQ, LFO_MAX_FREQ) depth_(0.0f),
fullscale_depth_(0.0f),
feedback_(0.0f)
{ {
this->delay_buffersL_ = new float32_t*[this->NumVoices]; this->lfo_[LFO_Index::Sin1] = new LFO(sampling_rate, LFO::Waveform::Sine, 0.0f, LFO1_MAX_FREQ);
this->delay_buffersR_ = new float32_t*[this->NumVoices]; this->lfo_[LFO_Index::Cos1] = new LFO(sampling_rate, LFO::Waveform::Sine, 0.0f, LFO1_MAX_FREQ, Constants::MPI_2);
this->lfo_[LFO_Index::Sin2] = new LFO(sampling_rate, LFO::Waveform::Sine, 0.0f, LFO2_MAX_FREQ);
for(unsigned i = 0; i < this->NumVoices; ++i) this->lfo_[LFO_Index::Cos2] = new LFO(sampling_rate, LFO::Waveform::Sine, 0.0f, LFO2_MAX_FREQ, Constants::MPI_2);
{
this->delay_buffersL_[i] = new float32_t[CHORUS_BUFFER_SIZE]; this->setRate(0.1f);
this->delay_buffersR_[i] = new float32_t[CHORUS_BUFFER_SIZE]; this->setDepth(0.15f);
} this->setFeedback(0.15f);
this->delay_buffer_indices_ = new unsigned[this->NumVoices];
memset(this->delay_buffer_indices_, 0, this->NumVoices * sizeof(float32_t));
this->setRate(rate);
this->setDepth(depth);
this->setFeedback(feedback);
} }
Chorus::~Chorus() Chorus::~Chorus()
{ {
for(unsigned i = 0; i < this->NumVoices; ++i) for(unsigned i = 0; i < 4; ++i)
{ {
delete[] this->delay_buffersL_[i]; delete this->lfo_[i];
delete[] this->delay_buffersR_[i];
} }
delete[] this->delay_buffersL_;
delete[] this->delay_buffersR_;
delete[] this->delay_buffer_indices_;
} }
void Chorus::processSample(float32_t inL, float32_t inR, float32_t& outL, float32_t& outR) void Chorus::processSample(float32_t inL, float32_t inR, float32_t& outL, float32_t& outR)
{ {
float32_t sumL = 0.0f; typedef Engine::Reserve<2047> Memory;
float32_t sumR = 0.0f; Engine::DelayLine<Memory, 0> line;
for(unsigned i = 0; i < this->NumVoices; ++i) { Engine::Context c;
// Calculate the delay time based on the depth and rate parameters
float32_t delay = this->getDepth() * this->lfo_.process(); engine_.start(&c);
// Convert the delay time to samples // Update LFO.
unsigned delay_samples = static_cast<unsigned>(delay * this->sample_position_ratio_); float32_t sin_1 = this->lfo_[LFO_Index::Sin1]->process() * 4.0f;
float32_t cos_1 = this->lfo_[LFO_Index::Cos1]->process() * 4.0f;
// Mix the input audio with the delayed audio float32_t sin_2 = this->lfo_[LFO_Index::Sin2]->process() * 4.0f;
sumL += inL + this->delay_buffersL_[i][(CHORUS_BUFFER_SIZE + this->delay_buffer_indices_[i] - delay_samples) % CHORUS_BUFFER_SIZE]; float32_t cos_2 = this->lfo_[LFO_Index::Cos2]->process() * 4.0f;
sumR += inR + this->delay_buffersR_[i][(CHORUS_BUFFER_SIZE + this->delay_buffer_indices_[i] - delay_samples) % CHORUS_BUFFER_SIZE];
float32_t wet;
// Update the delay buffer for this voice
this->delay_buffersL_[i][this->delay_buffer_indices_[i]] = inL + sumL * this->getFeedback() / static_cast<float32_t>(i + 1); // Sum L & R channel to send to chorus line.
this->delay_buffersR_[i][this->delay_buffer_indices_[i]] = inR + sumR * this->getFeedback() / static_cast<float32_t>(i + 1); c.read(inL, 0.5f);
this->delay_buffer_indices_[i] = (delay_buffer_indices_[i] + 1) % CHORUS_BUFFER_SIZE; c.read(inR, 0.5f);
} c.write(line, 0.0f);
// Average the mixed audio from all voices to create the output c.interpolate(line, sin_1 * this->fullscale_depth_ + 1200, 0.5f);
outL = sumL / static_cast<float32_t>(this->NumVoices); c.interpolate(line, sin_2 * this->fullscale_depth_ + 800, 0.5f);
outR = sumR / static_cast<float32_t>(this->NumVoices); c.write(wet, 0.0f);
outL = wet;
c.interpolate(line, cos_1 * this->fullscale_depth_ + 800, 0.5f);
c.interpolate(line, cos_2 * this->fullscale_depth_ + 1200, 0.5f);
c.write(wet, 0.0f);
outR = wet;
} }
void Chorus::setDepth(float32_t depth) void Chorus::setRate(float32_t rate)
{ {
this->depth_ = constrain(depth, 0.0f, 10.0f); this->rate_ = constrain(rate, 0.0f, 1.0f);
for(unsigned i = 0; i < 4; ++i)
{
this->lfo_[i]->setNormalizedFrequency(this->rate_);
}
} }
float32_t Chorus::getDepth() const float32_t Chorus::getRate() const
{ {
return this->depth_; return this->depth_;
} }
void Chorus::setRate(float32_t rate) void Chorus::setDepth(float32_t depth)
{ {
this->lfo_.setNormalizedFrequency(rate); this->depth_ = constrain(depth, 0.0f, 1.0f);
this->fullscale_depth_ = this->depth_ * FULLSCALE_DEPTH_RATIO;
} }
float32_t Chorus::getRate() const float32_t Chorus::getDepth() const
{ {
return this->lfo_.getNormalizedFrequency(); return this->depth_;
} }
void Chorus::setFeedback(float32_t feedback) void Chorus::setFeedback(float32_t feedback)
@ -98,4 +101,3 @@ float32_t Chorus::getFeedback() const
{ {
return this->feedback_; return this->feedback_;
} }

@ -15,17 +15,27 @@
// fx_chorus.h // fx_chorus.h
// //
// Stereo Chorus audio effects proposed in the context of the MiniDexed project // Stereo Chorus audio effects proposed in the context of the MiniDexed project
// This implemelntation is based on the Chorus FX from the Rings Eurorack module from Mutable Instruments
// //
#pragma once #pragma once
#include "fx_components.h" #include "fx_components.h"
#include "fx_engine.hpp"
class Chorus : public FXElement class Chorus : public FXElement
{ {
DISALLOW_COPY_AND_ASSIGN(Chorus); DISALLOW_COPY_AND_ASSIGN(Chorus);
public: public:
Chorus(float32_t sampling_rate, unsigned voices = 4, float32_t rate = 0.1f, float32_t depth = 5.0f, float32_t feedback = 0.5f); enum LFO_Index
{
Sin1 = 0,
Sin2,
Cos1,
Cos2
};
Chorus(float32_t sampling_rate);
virtual ~Chorus(); virtual ~Chorus();
virtual void processSample(float32_t inL, float32_t inR, float32_t& outL, float32_t& outR) override; virtual void processSample(float32_t inL, float32_t inR, float32_t& outL, float32_t& outR) override;
@ -40,13 +50,13 @@ public:
float32_t getFeedback() const; float32_t getFeedback() const;
private: private:
const unsigned NumVoices; // Number of voices in the chorus typedef FxEngine<2048, FORMAT_16_BIT, false> Engine;
const float32_t sample_position_ratio_; Engine engine_;
float32_t** delay_buffersL_;
float32_t** delay_buffersR_;
unsigned* delay_buffer_indices_;
LFO lfo_; float32_t rate_; // Normalized frequency for the 2 LFOs frequencies (0.0 - 10.0)
float32_t depth_; // Depth of the chorus in milliseconds (0.0 - 10.0) float32_t depth_; // Depth of the chorus in milliseconds (0.0 - 10.0)
float32_t fullscale_depth_; // Equivalent to depth_ but in the range of (0.0 - 384.0)
float32_t feedback_; // Feedback level of the chorus (0.0 - 1.0) float32_t feedback_; // Feedback level of the chorus (0.0 - 1.0)
LFO* lfo_[4];
}; };

@ -6,19 +6,20 @@
// Constants implemlentation // // Constants implemlentation //
/////////////////////////////// ///////////////////////////////
const float32_t Constants::M2PI = 2.0f * PI; const float32_t Constants::M2PI = 2.0f * PI;
const float32_t Constants::MPI_2 = PI / 2.0f;
const float32_t Constants::M1_PI = 1.0f / PI; const float32_t Constants::M1_PI = 1.0f / PI;
///////////////////////// /////////////////////////
// LFO implemlentation // // LFO implemlentation //
///////////////////////// /////////////////////////
LFO::LFO(float32_t sampling_rate, Waveform waveform, float32_t min_frequency, float32_t max_frequency) : LFO::LFO(float32_t sampling_rate, Waveform waveform, float32_t min_frequency, float32_t max_frequency, float32_t initial_phase) :
FXBase(sampling_rate), FXBase(sampling_rate),
min_frequency_(min_frequency), min_frequency_(min_frequency),
max_frequency_(max_frequency), max_frequency_(max_frequency),
waveform_(waveform), waveform_(waveform),
normalized_frequency_(-1.0f), normalized_frequency_(-1.0f),
frequency_(0.0f), frequency_(0.0f),
phase_(0.0f), phase_(initial_phase),
phase_increment_(0.0f), phase_increment_(0.0f),
current_sample_(0.0f), current_sample_(0.0f),
new_phase_(true), new_phase_(true),

@ -25,6 +25,7 @@
struct Constants struct Constants
{ {
const static float32_t M2PI; // 2 * PI const static float32_t M2PI; // 2 * PI
const static float32_t MPI_2; // PI / 2
const static float32_t M1_PI; // 1 / PI const static float32_t M1_PI; // 1 / PI
}; };
@ -41,7 +42,7 @@ public:
Noise Noise
} Waveform; } Waveform;
LFO(float32_t sampling_rate, Waveform waveform = Waveform::Sine, float32_t min_frequency = 0.01f, float32_t max_frequency = 10.0f); LFO(float32_t sampling_rate, Waveform waveform = Waveform::Sine, float32_t min_frequency = 0.01f, float32_t max_frequency = 10.0f, float32_t initial_phase = 0.0f);
~LFO(); ~LFO();
void setWaveform(Waveform waveform); void setWaveform(Waveform waveform);

@ -61,7 +61,8 @@ struct DataType<FORMAT_16_BIT>
template < template <
size_t size, size_t size,
Format format = FORMAT_16_BIT> Format format = FORMAT_16_BIT,
bool enable_lfo = true>
class FxEngine : public FXBase class FxEngine : public FXBase
{ {
DISALLOW_COPY_AND_ASSIGN(FxEngine); DISALLOW_COPY_AND_ASSIGN(FxEngine);
@ -69,21 +70,24 @@ class FxEngine : public FXBase
public: public:
typedef typename DataType<format>::T T; typedef typename DataType<format>::T T;
FxEngine(float32_t sampling_rate, float32_t max_lfo_frequency = 1.0f) : FxEngine(float32_t sampling_rate, float32_t max_lfo1_frequency = 1.0f, float32_t max_lfo2_frequency = 1.0f) :
FXBase(sampling_rate) FXBase(sampling_rate)
{ {
this->buffer_ = new uint16_t[size]; this->buffer_ = new uint16_t[size];
this->lfo_[LFO_1] = new LFO(sampling_rate, LFO::Waveform::Sine, 0.0f, max_lfo_frequency); this->lfo_[LFO_1] = enable_lfo ? new LFO(sampling_rate, LFO::Waveform::Sine, 0.0f, max_lfo1_frequency) : nullptr;
this->lfo_[LFO_2] = new LFO(sampling_rate, LFO::Waveform::Sine, 0.0f, max_lfo_frequency); this->lfo_[LFO_2] = enable_lfo ? new LFO(sampling_rate, LFO::Waveform::Sine, 0.0f, max_lfo2_frequency) : nullptr;
this->clear(); this->clear();
} }
~FxEngine() ~FxEngine()
{ {
delete[] this->buffer_; delete[] this->buffer_;
if(enable_lfo)
{
delete this->lfo_[LFO_1]; delete this->lfo_[LFO_1];
delete this->lfo_[LFO_2]; delete this->lfo_[LFO_2];
} }
}
void clear() void clear()
{ {
@ -159,19 +163,19 @@ public:
this->accumulator_ += value; this->accumulator_ += value;
} }
inline void write(float32_t &value) inline void write(float32_t& value)
{ {
value = this->accumulator_; value = this->accumulator_;
} }
inline void write(float32_t &value, float32_t scale) inline void write(float32_t& value, float32_t scale)
{ {
value = this->accumulator_; value = this->accumulator_;
this->accumulator_ *= scale; this->accumulator_ *= scale;
} }
template <typename D> template <typename D>
inline void write(D &d, int32_t offset, float32_t scale) inline void write(D& d, int32_t offset, float32_t scale)
{ {
assert(D::base + D::length <= size); assert(D::base + D::length <= size);
T w = DataType<format>::compress(this->accumulator_); T w = DataType<format>::compress(this->accumulator_);
@ -187,26 +191,26 @@ public:
} }
template <typename D> template <typename D>
inline void write(D &d, float32_t scale) inline void write(D& d, float32_t scale)
{ {
this->write(d, 0, scale); this->write(d, 0, scale);
} }
template <typename D> template <typename D>
inline void writeAllPass(D &d, int32_t offset, float32_t scale) inline void writeAllPass(D& d, int32_t offset, float32_t scale)
{ {
this->write(d, offset, scale); this->write(d, offset, scale);
this->accumulator_ += this->previous_read_; this->accumulator_ += this->previous_read_;
} }
template <typename D> template <typename D>
inline void writeAllPass(D &d, float32_t scale) inline void writeAllPass(D& d, float32_t scale)
{ {
this->writeAllPass(d, 0, scale); this->writeAllPass(d, 0, scale);
} }
template <typename D> template <typename D>
inline void read(D &d, int32_t offset, float32_t scale) inline void read(D& d, int32_t offset, float32_t scale)
{ {
assert(D::base + D::length <= size); assert(D::base + D::length <= size);
T r; T r;
@ -224,25 +228,25 @@ public:
} }
template <typename D> template <typename D>
inline void read(D &d, float32_t scale) inline void read(D& d, float32_t scale)
{ {
this->read(d, 0, scale); this->read(d, 0, scale);
} }
inline void lp(float32_t &state, float32_t coefficient) inline void lp(float32_t& state, float32_t coefficient)
{ {
state += coefficient * (this->accumulator_ - state); state += coefficient * (this->accumulator_ - state);
this->accumulator_ = state; this->accumulator_ = state;
} }
inline void hp(float32_t &state, float32_t coefficient) inline void hp(float32_t& state, float32_t coefficient)
{ {
state += coefficient * (this->accumulator_ - state); state += coefficient * (this->accumulator_ - state);
this->accumulator_ -= state; this->accumulator_ -= state;
} }
template <typename D> template <typename D>
inline void interpolate(D &d, float32_t offset, float32_t scale) inline void interpolate(D& d, float32_t offset, float32_t scale)
{ {
assert(D::base + D::length <= size); assert(D::base + D::length <= size);
MAKE_INTEGRAL_FRACTIONAL(offset); MAKE_INTEGRAL_FRACTIONAL(offset);
@ -254,10 +258,10 @@ public:
} }
template <typename D> template <typename D>
inline void interpolate(D &d, float32_t offset, LFOIndex index, float32_t amplitude, float32_t scale) inline void interpolate(D& d, float32_t offset, LFOIndex index, float32_t amplitude, float32_t scale)
{ {
assert(D::base + D::length <= size); assert(D::base + D::length <= size);
offset += amplitude * lfo_value_[index]; offset += amplitude * this->lfo_value_[index];
MAKE_INTEGRAL_FRACTIONAL(offset); MAKE_INTEGRAL_FRACTIONAL(offset);
float32_t a = DataType<format>::decompress(this->buffer_[(this->write_ptr_ + offset_integral + D::base) & MASK]); float32_t a = DataType<format>::decompress(this->buffer_[(this->write_ptr_ + offset_integral + D::base) & MASK]);
float32_t b = DataType<format>::decompress(this->buffer_[(this->write_ptr_ + offset_integral + D::base + 1) & MASK]); float32_t b = DataType<format>::decompress(this->buffer_[(this->write_ptr_ + offset_integral + D::base + 1) & MASK]);
@ -275,9 +279,20 @@ public:
}; };
inline void setLFOFrequency(LFOIndex index, float32_t frequency) inline void setLFOFrequency(LFOIndex index, float32_t frequency)
{
if(enable_lfo)
{ {
this->lfo_[index]->setFrequency(frequency); this->lfo_[index]->setFrequency(frequency);
} }
}
inline void setLFONormalizedFrequency(LFOIndex index, float32_t normalized_frequency)
{
if(enable_lfo)
{
this->lfo_[index]->setNormalizedFrequency(normalized_frequency);
}
}
inline void start(Context *c) inline void start(Context *c)
{ {
@ -290,9 +305,12 @@ public:
c->previous_read_ = 0.0f; c->previous_read_ = 0.0f;
c->buffer_ = buffer_; c->buffer_ = buffer_;
c->write_ptr_ = write_ptr_; c->write_ptr_ = write_ptr_;
if(enable_lfo)
{
c->lfo_value_[LFO_1] = this->lfo_[LFO_1]->process(); c->lfo_value_[LFO_1] = this->lfo_[LFO_1]->process();
c->lfo_value_[LFO_2] = this->lfo_[LFO_2]->process(); c->lfo_value_[LFO_2] = this->lfo_[LFO_2]->process();
} }
}
private: private:
enum enum

@ -189,10 +189,10 @@ int main()
rack->getTube()->setWetLevel(1.0f); rack->getTube()->setWetLevel(1.0f);
rack->getTube()->setOverdrive(1.0f); rack->getTube()->setOverdrive(1.0f);
rack->getChorus()->setEnable(false); rack->getChorus()->setEnable(true);
rack->getChorus()->setWetLevel(0.5f); rack->getChorus()->setWetLevel(0.5f);
rack->getChorus()->setRate(0.2f); rack->getChorus()->setRate(0.5f);
rack->getChorus()->setDepth(0.15f); rack->getChorus()->setDepth(0.5f);
rack->getChorus()->setFeedback(0.15f); rack->getChorus()->setFeedback(0.15f);
rack->getPhaser()->setEnable(false); rack->getPhaser()->setEnable(false);
@ -210,7 +210,7 @@ int main()
rack->getTapeDelay()->setFlutterLevel(0.0f); rack->getTapeDelay()->setFlutterLevel(0.0f);
rack->getTapeDelay()->setFeedbak(0.5f); rack->getTapeDelay()->setFeedbak(0.5f);
rack->getShimmerReverb()->setEnable(true); rack->getShimmerReverb()->setEnable(false);
rack->getShimmerReverb()->setWetLevel(0.7f); rack->getShimmerReverb()->setWetLevel(0.7f);
rack->getShimmerReverb()->setInputGain(0.45f); rack->getShimmerReverb()->setInputGain(0.45f);
rack->getShimmerReverb()->setTime(0.89f); rack->getShimmerReverb()->setTime(0.89f);

Loading…
Cancel
Save