diff --git a/src/fx_chorus.cpp b/src/fx_chorus.cpp index 619b120..a0a0291 100644 --- a/src/fx_chorus.cpp +++ b/src/fx_chorus.cpp @@ -4,89 +4,92 @@ #define CHORUS_BUFFER_SIZE 8192 -#define LFO_MIN_FREQ 0.0f -#define LFO_MAX_FREQ 1.0f +#define LFO1_MAX_FREQ 0.25f +#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), - NumVoices(voices), - sample_position_ratio_(sampling_rate / 1000.0f), - lfo_(sampling_rate, LFO::Waveform::Sine, LFO_MIN_FREQ, LFO_MAX_FREQ) + engine_(sampling_rate, LFO1_MAX_FREQ, LFO2_MAX_FREQ), + rate_(0.0f), + depth_(0.0f), + fullscale_depth_(0.0f), + feedback_(0.0f) { - this->delay_buffersL_ = new float32_t*[this->NumVoices]; - this->delay_buffersR_ = new float32_t*[this->NumVoices]; - - for(unsigned i = 0; i < this->NumVoices; ++i) - { - this->delay_buffersL_[i] = new float32_t[CHORUS_BUFFER_SIZE]; - this->delay_buffersR_[i] = new float32_t[CHORUS_BUFFER_SIZE]; - } - - 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); + this->lfo_[LFO_Index::Sin1] = new LFO(sampling_rate, LFO::Waveform::Sine, 0.0f, LFO1_MAX_FREQ); + 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); + this->lfo_[LFO_Index::Cos2] = new LFO(sampling_rate, LFO::Waveform::Sine, 0.0f, LFO2_MAX_FREQ, Constants::MPI_2); + + this->setRate(0.1f); + this->setDepth(0.15f); + this->setFeedback(0.15f); } Chorus::~Chorus() { - for(unsigned i = 0; i < this->NumVoices; ++i) + for(unsigned i = 0; i < 4; ++i) { - delete[] this->delay_buffersL_[i]; - delete[] this->delay_buffersR_[i]; + delete this->lfo_[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) { - float32_t sumL = 0.0f; - float32_t sumR = 0.0f; - for(unsigned i = 0; i < this->NumVoices; ++i) { - // Calculate the delay time based on the depth and rate parameters - float32_t delay = this->getDepth() * this->lfo_.process(); - - // Convert the delay time to samples - unsigned delay_samples = static_cast(delay * this->sample_position_ratio_); - - // Mix the input audio with the delayed audio - sumL += inL + this->delay_buffersL_[i][(CHORUS_BUFFER_SIZE + this->delay_buffer_indices_[i] - delay_samples) % CHORUS_BUFFER_SIZE]; - sumR += inR + this->delay_buffersR_[i][(CHORUS_BUFFER_SIZE + this->delay_buffer_indices_[i] - delay_samples) % CHORUS_BUFFER_SIZE]; - - // Update the delay buffer for this voice - this->delay_buffersL_[i][this->delay_buffer_indices_[i]] = inL + sumL * this->getFeedback() / static_cast(i + 1); - this->delay_buffersR_[i][this->delay_buffer_indices_[i]] = inR + sumR * this->getFeedback() / static_cast(i + 1); - this->delay_buffer_indices_[i] = (delay_buffer_indices_[i] + 1) % CHORUS_BUFFER_SIZE; - } - - // Average the mixed audio from all voices to create the output - outL = sumL / static_cast(this->NumVoices); - outR = sumR / static_cast(this->NumVoices); + typedef Engine::Reserve<2047> Memory; + Engine::DelayLine line; + Engine::Context c; + + engine_.start(&c); + + // Update LFO. + float32_t sin_1 = this->lfo_[LFO_Index::Sin1]->process() * 4.0f; + float32_t cos_1 = this->lfo_[LFO_Index::Cos1]->process() * 4.0f; + float32_t sin_2 = this->lfo_[LFO_Index::Sin2]->process() * 4.0f; + float32_t cos_2 = this->lfo_[LFO_Index::Cos2]->process() * 4.0f; + + float32_t wet; + + // Sum L & R channel to send to chorus line. + c.read(inL, 0.5f); + c.read(inR, 0.5f); + c.write(line, 0.0f); + + c.interpolate(line, sin_1 * this->fullscale_depth_ + 1200, 0.5f); + c.interpolate(line, sin_2 * this->fullscale_depth_ + 800, 0.5f); + 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_; } -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) @@ -98,4 +101,3 @@ float32_t Chorus::getFeedback() const { return this->feedback_; } - diff --git a/src/fx_chorus.h b/src/fx_chorus.h index a4104f5..4ca6c29 100644 --- a/src/fx_chorus.h +++ b/src/fx_chorus.h @@ -15,17 +15,27 @@ // fx_chorus.h // // 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 #include "fx_components.h" +#include "fx_engine.hpp" class Chorus : public FXElement { DISALLOW_COPY_AND_ASSIGN(Chorus); 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 void processSample(float32_t inL, float32_t inR, float32_t& outL, float32_t& outR) override; @@ -40,13 +50,13 @@ public: float32_t getFeedback() const; private: - const unsigned NumVoices; // Number of voices in the chorus - const float32_t sample_position_ratio_; - float32_t** delay_buffersL_; - float32_t** delay_buffersR_; - unsigned* delay_buffer_indices_; + typedef FxEngine<2048, FORMAT_16_BIT, false> Engine; + Engine engine_; - 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 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) + + LFO* lfo_[4]; }; diff --git a/src/fx_components.cpp b/src/fx_components.cpp index d00d822..fe03a3c 100644 --- a/src/fx_components.cpp +++ b/src/fx_components.cpp @@ -6,19 +6,20 @@ // Constants implemlentation // /////////////////////////////// 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; ///////////////////////// // 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), min_frequency_(min_frequency), max_frequency_(max_frequency), waveform_(waveform), normalized_frequency_(-1.0f), frequency_(0.0f), - phase_(0.0f), + phase_(initial_phase), phase_increment_(0.0f), current_sample_(0.0f), new_phase_(true), diff --git a/src/fx_components.h b/src/fx_components.h index 112f0af..f886b3b 100644 --- a/src/fx_components.h +++ b/src/fx_components.h @@ -25,6 +25,7 @@ struct Constants { const static float32_t M2PI; // 2 * PI + const static float32_t MPI_2; // PI / 2 const static float32_t M1_PI; // 1 / PI }; @@ -41,7 +42,7 @@ public: Noise } 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(); void setWaveform(Waveform waveform); diff --git a/src/fx_engine.hpp b/src/fx_engine.hpp index 424d92b..10c6a72 100644 --- a/src/fx_engine.hpp +++ b/src/fx_engine.hpp @@ -61,7 +61,8 @@ struct DataType template < size_t size, - Format format = FORMAT_16_BIT> + Format format = FORMAT_16_BIT, + bool enable_lfo = true> class FxEngine : public FXBase { DISALLOW_COPY_AND_ASSIGN(FxEngine); @@ -69,20 +70,23 @@ class FxEngine : public FXBase public: typedef typename DataType::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) { 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_2] = 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] = enable_lfo ? new LFO(sampling_rate, LFO::Waveform::Sine, 0.0f, max_lfo2_frequency) : nullptr; this->clear(); } ~FxEngine() { delete[] this->buffer_; - delete this->lfo_[LFO_1]; - delete this->lfo_[LFO_2]; + if(enable_lfo) + { + delete this->lfo_[LFO_1]; + delete this->lfo_[LFO_2]; + } } void clear() @@ -159,19 +163,19 @@ public: this->accumulator_ += value; } - inline void write(float32_t &value) + inline void write(float32_t& value) { value = this->accumulator_; } - inline void write(float32_t &value, float32_t scale) + inline void write(float32_t& value, float32_t scale) { value = this->accumulator_; this->accumulator_ *= scale; } template - 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); T w = DataType::compress(this->accumulator_); @@ -187,26 +191,26 @@ public: } template - inline void write(D &d, float32_t scale) + inline void write(D& d, float32_t scale) { this->write(d, 0, scale); } template - 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->accumulator_ += this->previous_read_; } template - inline void writeAllPass(D &d, float32_t scale) + inline void writeAllPass(D& d, float32_t scale) { this->writeAllPass(d, 0, scale); } template - 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); T r; @@ -224,25 +228,25 @@ public: } template - inline void read(D &d, float32_t scale) + inline void read(D& d, float32_t 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); 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); this->accumulator_ -= state; } template - 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); MAKE_INTEGRAL_FRACTIONAL(offset); @@ -254,10 +258,10 @@ public: } template - 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); - offset += amplitude * lfo_value_[index]; + offset += amplitude * this->lfo_value_[index]; MAKE_INTEGRAL_FRACTIONAL(offset); float32_t a = DataType::decompress(this->buffer_[(this->write_ptr_ + offset_integral + D::base) & MASK]); float32_t b = DataType::decompress(this->buffer_[(this->write_ptr_ + offset_integral + D::base + 1) & MASK]); @@ -276,7 +280,18 @@ public: inline void setLFOFrequency(LFOIndex index, float32_t frequency) { - this->lfo_[index]->setFrequency(frequency); + if(enable_lfo) + { + 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) @@ -290,8 +305,11 @@ public: c->previous_read_ = 0.0f; c->buffer_ = buffer_; c->write_ptr_ = write_ptr_; - c->lfo_value_[LFO_1] = this->lfo_[LFO_1]->process(); - c->lfo_value_[LFO_2] = this->lfo_[LFO_2]->process(); + if(enable_lfo) + { + c->lfo_value_[LFO_1] = this->lfo_[LFO_1]->process(); + c->lfo_value_[LFO_2] = this->lfo_[LFO_2]->process(); + } } private: diff --git a/src/test/fxrack_test.cpp b/src/test/fxrack_test.cpp index 8ce1852..c991cdf 100644 --- a/src/test/fxrack_test.cpp +++ b/src/test/fxrack_test.cpp @@ -189,10 +189,10 @@ int main() rack->getTube()->setWetLevel(1.0f); rack->getTube()->setOverdrive(1.0f); - rack->getChorus()->setEnable(false); + rack->getChorus()->setEnable(true); rack->getChorus()->setWetLevel(0.5f); - rack->getChorus()->setRate(0.2f); - rack->getChorus()->setDepth(0.15f); + rack->getChorus()->setRate(0.5f); + rack->getChorus()->setDepth(0.5f); rack->getChorus()->setFeedback(0.15f); rack->getPhaser()->setEnable(false); @@ -210,7 +210,7 @@ int main() rack->getTapeDelay()->setFlutterLevel(0.0f); rack->getTapeDelay()->setFeedbak(0.5f); - rack->getShimmerReverb()->setEnable(true); + rack->getShimmerReverb()->setEnable(false); rack->getShimmerReverb()->setWetLevel(0.7f); rack->getShimmerReverb()->setInputGain(0.45f); rack->getShimmerReverb()->setTime(0.89f);