diff --git a/src/fx_chorus.cpp b/src/fx_chorus.cpp new file mode 100644 index 0000000..2ede3e7 --- /dev/null +++ b/src/fx_chorus.cpp @@ -0,0 +1,103 @@ +#include "fx_chorus.h" + +#include + +#define CHORUS_BUFFER_SIZE 8192 + +Chorus::Chorus(float32_t sampling_rate, unsigned voices, float32_t depth, float32_t rate, float32_t feedback) : + FXElement(sampling_rate), + NumVoices(voices), + sample_position_ratio_(sampling_rate / 1000.0f), + lfo_phase_(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)); +} + +Chorus::~Chorus() +{ + for(unsigned i = 0; i < this->NumVoices; ++i) + { + delete[] this->delay_buffersL_[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) +{ + static float32_t M2PI = 2.0f * PI; + + 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() * std::sin(this->lfo_phase_); + + // 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); + + this->lfo_phase_ += this->lfo_phase_increment_; + if(this->lfo_phase_ > M2PI) + { + this->lfo_phase_ -= M2PI; + } +} + +void Chorus::setDepth(float32_t depth) +{ + this->depth_ = constrain(depth, 0.0f, 10.0f); +} + +float32_t Chorus::getDepth() const +{ + return this->depth_; +} + +void Chorus::setRate(float32_t rate) +{ + this->rate_ = constrain(rate, 0.1f, 1.0f); + this->lfo_phase_increment_ = 2.0f * PI * this->rate_ / this->getSamplingRate(); +} + +float32_t Chorus::getRate() const +{ + return this->rate_; +} + +void Chorus::setFeedback(float32_t feedback) +{ + this->feedback_ = constrain(feedback, 0.0f, 1.0f); +} + +float32_t Chorus::getFeedback() const +{ + return this->feedback_; +} + diff --git a/src/fx_chorus.h b/src/fx_chorus.h new file mode 100644 index 0000000..dafc858 --- /dev/null +++ b/src/fx_chorus.h @@ -0,0 +1,55 @@ +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +// +// fx_chorus.h +// +// Stereo Chorus audio effects proposed in the context of the MiniDexed project +// +#pragma once + +#include "fx.h" + +class Chorus : public FXElement +{ + DISALLOW_COPY_AND_ASSIGN(Chorus); + +public: + Chorus(float32_t sampling_rate, unsigned voices = 4, float32_t depth = 5.0f, float32_t rate = 0.5f, float32_t feedback = 0.5f); + virtual ~Chorus(); + + virtual void processSample(float32_t inL, float32_t inR, float32_t& outL, float32_t& outR) override; + + void setDepth(float32_t depth); + inline float32_t getDepth() const; + + void setRate(float32_t rate); + inline float32_t getRate() const; + + void setFeedback(float32_t feedback); + inline 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_; + + float32_t lfo_phase_; + float32_t lfo_phase_increment_; + + float32_t depth_; // Depth of the chorus in milliseconds (0.0 - 10.0) + float32_t rate_; // Rate of the chorus in Hz (0.1 - 1.0) + float32_t feedback_; // Feedback level of the chorus (0.0 - 1.0) +}; diff --git a/src/fx_rack.cpp b/src/fx_rack.cpp index e35901d..102f5c1 100644 --- a/src/fx_rack.cpp +++ b/src/fx_rack.cpp @@ -1,42 +1,24 @@ #include "fx_rack.h" -FXUnit::FXUnit(float32_t sampling_rate, FXElement& fx, float32_t wet_level) : - FXElement(sampling_rate), - fx_(fx) -{ - this->setWetLevel(wet_level); -} - -FXUnit::~FXUnit() -{ -} - -void FXUnit::processSample(float32_t inL, float32_t inR, float32_t& outL, float32_t& outR) -{ - this->fx_.processSample(inL, inR, outL, outR); - - // Mix wet and dry signals - outL = this->getWetLevel() * outL + (1.0f - this->getWetLevel()) * inL; - outR = this->getWetLevel() * outR + (1.0f - this->getWetLevel()) * inR; -} - -void FXUnit::setWetLevel(float32_t wet_level) -{ - this->wet_level_ = constrain(wet_level, 0.0f, 1.0f); -} - -float32_t FXUnit::getWetLevel() const -{ - return this->wet_level_; -} - FXRack::FXRack(float32_t sampling_rate) : FX(sampling_rate), fx_chain_() { - this->registerFX(new Phaser(sampling_rate)); - this->registerFX(new TapeDelay(sampling_rate)); - this->registerFX(new ShimmerReverb(sampling_rate)); + this->fxTube_ = new FXUnit(sampling_rate); + this->fxChorus_ = new FXUnit(sampling_rate); + this->fxFlanger_ = new FXUnit(sampling_rate); + this->fxOrbitone_ = new FXUnit(sampling_rate); + this->fxPhaser_ = new FXUnit(sampling_rate); + this->fxTapeDelay_ = new FXUnit(sampling_rate); + this->fxShimmerReverb_ = new FXUnit(sampling_rate); + + this->registerFX(this->fxTube_); + this->registerFX(this->fxChorus_); + this->registerFX(this->fxFlanger_); + this->registerFX(this->fxOrbitone_); + this->registerFX(this->fxPhaser_); + this->registerFX(this->fxTapeDelay_); + this->registerFX(this->fxShimmerReverb_); } FXRack::~FXRack() @@ -46,6 +28,14 @@ FXRack::~FXRack() delete *it; } this->fx_chain_.clear(); + + delete this->fxTube_; + delete this->fxChorus_; + delete this->fxFlanger_; + delete this->fxOrbitone_; + delete this->fxPhaser_; + delete this->fxTapeDelay_; + delete this->fxShimmerReverb_; } void FXRack::process(float32_t* left_input, float32_t* right_input, float32_t* left_output, float32_t* right_output, size_t nSamples) @@ -85,5 +75,5 @@ void FXRack::process(float32_t* left_input, float32_t* right_input, float32_t* l void FXRack::registerFX(FXElement* fx) { - this->fx_chain_.push_back(new FXUnit(this->getSamplingRate(), *fx)); + this->fx_chain_.push_back(fx); } diff --git a/src/fx_rack.h b/src/fx_rack.h index 73dc586..2bbe483 100644 --- a/src/fx_rack.h +++ b/src/fx_rack.h @@ -20,31 +20,63 @@ #pragma once #include "fx.h" +#include "fx_tube.h" +#include "fx_chorus.h" +#include "fx_flanger.h" +#include "fx_orbitone.h" #include "fx_phaser.h" #include "fx_tape_delay.h" #include "fx_shimmer_reverb.h" #include -class FXUnit : public FXElement +template +class FXUnit : public virtual _FXElement { DISALLOW_COPY_AND_ASSIGN(FXUnit); public: - FXUnit(float32_t sampling_rate, FXElement& fx, float32_t wet_level = 0.5f); - virtual ~FXUnit(); + FXUnit(float32_t sampling_rate, float32_t wet_level = 0.5f) : + _FXElement(sampling_rate), + wet_level_(wet_level) + { + } - virtual void processSample(float32_t inL, float32_t inR, float32_t& outL, float32_t& outR) override; + virtual ~FXUnit() + { + } - void setWetLevel(float32_t wet_level); - inline float32_t getWetLevel() const; + void processSample(float32_t inL, float32_t inR, float32_t& outL, float32_t& outR) + { + if(this->getWetLevel() == 0.0f) + { + outL = inL; + outR = inR; + } + else + { + _FXElement::processSample(inL, inR, outL, outR); + float32_t dry = 1.0f - this->getWetLevel(); + outL = this->getWetLevel() * outL + dry * inL; + outR = this->getWetLevel() * outR + dry * inR; + } + } + + void setWetLevel(float32_t wet_level) + { + this->wet_level_ = constrain(wet_level, 0.0f, 1.0f); + } + + inline float32_t getWetLevel() const + { + return this->wet_level_; + } private: - FXElement& fx_; // Embedded FX float32_t wet_level_; // How much the signal is affected by the inner FX (0.0 - 1.0) }; -typedef std::vector FXChain; +typedef std::vector FXChain; class FXRack : public FX { @@ -60,4 +92,11 @@ private: void registerFX(FXElement* fx); FXChain fx_chain_; + FXUnit* fxTube_; + FXUnit* fxChorus_; + FXUnit* fxFlanger_; + FXUnit* fxOrbitone_; + FXUnit* fxPhaser_; + FXUnit* fxTapeDelay_; + FXUnit* fxShimmerReverb_; }; \ No newline at end of file diff --git a/src/fx_tube.cpp b/src/fx_tube.cpp new file mode 100644 index 0000000..8e8feba --- /dev/null +++ b/src/fx_tube.cpp @@ -0,0 +1,60 @@ +#include "fx_tube.h" + +#include + +Tube::Tube(float32_t samplingRate, float32_t curve, float32_t bias) : + FXElement(samplingRate), + TubeCurve(curve), + TubeBias(bias) +{ +} + +Tube::~Tube() +{ +} + +void Tube::processSample(float32_t inL, float32_t inR, float32_t& outL, float32_t& outR) +{ + float32_t absInL = abs(inL); + + float32_t coeff = this->TubeCurve + this->getOverdrive(); + + if(absInL > this->TubeBias) + { + outL = coeff * (absInL - this->TubeBias) / (1.0f - this->TubeBias); + } + else + { + outL = coeff * absInL / (1.0f + this->TubeBias * absInL); + } + + if(inL < 0.0f) + { + outL = -outL; + } + + float32_t absInR = abs(inR); + if(absInR > this->TubeBias) + { + outR = coeff * (absInR - this->TubeBias) / (1.0f - this->TubeBias); + } + else + { + outR = coeff * absInR / (1.0f + this->TubeBias * absInR); + } + + if(inR < 0.0f) + { + outR = -outR; + } +} + +void Tube::setOverdrive(float32_t overdrive) +{ + this->overdrive_ = constrain(overdrive, 0.0f, 1.0f); +} + +float32_t Tube::getOverdrive() const +{ + return this->overdrive_; +} diff --git a/src/fx_tube.h b/src/fx_tube.h new file mode 100644 index 0000000..c007e9f --- /dev/null +++ b/src/fx_tube.h @@ -0,0 +1,41 @@ +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +// +// fx_tube.h +// +// Stereo Tube overdrive audio effects proposed in the context of the MiniDexed project +// +#pragma once + +#include "fx.h" + +class Tube : public FXElement +{ + DISALLOW_COPY_AND_ASSIGN(Tube); + +public: + Tube(float32_t sampling_rate, float32_t curve = 2.0f, float32_t bias = 0.7f); + virtual ~Tube(); + + virtual void processSample(float32_t inL, float32_t inR, float32_t& outL, float32_t& outR) override; + + void setOverdrive(float32_t overdrive); + inline float32_t getOverdrive() const; + +private: + const float32_t TubeCurve; + const float32_t TubeBias; + + float32_t overdrive_; +}; \ No newline at end of file