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 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<unsigned>(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<float32_t>(i + 1);
this->delay_buffersR_[i][this->delay_buffer_indices_[i]] = inR + sumR * this->getFeedback() / static_cast<float32_t>(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<float32_t>(this->NumVoices);
outR = sumR / static_cast<float32_t>(this->NumVoices);
typedef Engine::Reserve<2047> Memory;
Engine::DelayLine<Memory, 0> 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_;
}

@ -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];
};

@ -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),

@ -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);

@ -61,7 +61,8 @@ struct DataType<FORMAT_16_BIT>
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,21 +70,24 @@ class FxEngine : public FXBase
public:
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)
{
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_;
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 <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);
T w = DataType<format>::compress(this->accumulator_);
@ -187,26 +191,26 @@ public:
}
template <typename D>
inline void write(D &d, float32_t scale)
inline void write(D& d, float32_t scale)
{
this->write(d, 0, scale);
}
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->accumulator_ += this->previous_read_;
}
template <typename D>
inline void writeAllPass(D &d, float32_t scale)
inline void writeAllPass(D& d, float32_t scale)
{
this->writeAllPass(d, 0, scale);
}
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);
T r;
@ -224,25 +228,25 @@ public:
}
template <typename D>
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 <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);
MAKE_INTEGRAL_FRACTIONAL(offset);
@ -254,10 +258,10 @@ public:
}
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);
offset += amplitude * lfo_value_[index];
offset += amplitude * this->lfo_value_[index];
MAKE_INTEGRAL_FRACTIONAL(offset);
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]);
@ -275,9 +279,20 @@ public:
};
inline void setLFOFrequency(LFOIndex index, float32_t 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,9 +305,12 @@ public:
c->previous_read_ = 0.0f;
c->buffer_ = buffer_;
c->write_ptr_ = write_ptr_;
if(enable_lfo)
{
c->lfo_value_[LFO_1] = this->lfo_[LFO_1]->process();
c->lfo_value_[LFO_2] = this->lfo_[LFO_2]->process();
}
}
private:
enum

@ -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);

Loading…
Cancel
Save