diff --git a/src/Makefile b/src/Makefile index 391a26b..7ac97f7 100644 --- a/src/Makefile +++ b/src/Makefile @@ -10,7 +10,8 @@ OBJS = main.o kernel.o minidexed.o config.o userinterface.o uimenu.o \ mididevice.o midikeyboard.o serialmididevice.o pckeyboard.o \ sysexfileloader.o performanceconfig.o perftimer.o \ effect_compressor.o effect_platervbstereo.o \ - fx.o fx_tube.o fx_chorus.o fx_flanger.o fx_orbitone.o fx_phaser.o \ + fx.o fx_components.o \ + fx_svf.o fx_tube.o fx_chorus.o fx_flanger.o fx_orbitone.o fx_phaser.o \ fx_tape_delay.o fx_shimmer_reverb.o fx_rack.o \ uibuttons.o midipin.o diff --git a/src/fx.h b/src/fx.h index c8d9203..93e86d0 100644 --- a/src/fx.h +++ b/src/fx.h @@ -22,6 +22,9 @@ #include #include "common.h" +#include +using namespace std; + #define DISALLOW_COPY_AND_ASSIGN(TypeName) \ TypeName(const TypeName&) = delete; \ void operator=(const TypeName&) = delete diff --git a/src/fx_chorus.cpp b/src/fx_chorus.cpp index 2ede3e7..c19497b 100644 --- a/src/fx_chorus.cpp +++ b/src/fx_chorus.cpp @@ -4,11 +4,11 @@ #define CHORUS_BUFFER_SIZE 8192 -Chorus::Chorus(float32_t sampling_rate, unsigned voices, float32_t depth, float32_t rate, float32_t feedback) : +Chorus::Chorus(float32_t sampling_rate, unsigned voices, float32_t rate, float32_t depth, float32_t feedback) : FXElement(sampling_rate), NumVoices(voices), sample_position_ratio_(sampling_rate / 1000.0f), - lfo_phase_(0.0f) + lfo_(sampling_rate, LFO::Waveform::Sine, 0.01f, 1.0f) { this->delay_buffersL_ = new float32_t*[this->NumVoices]; this->delay_buffersR_ = new float32_t*[this->NumVoices]; @@ -21,6 +21,10 @@ Chorus::Chorus(float32_t sampling_rate, unsigned voices, float32_t depth, float3 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() @@ -38,13 +42,11 @@ Chorus::~Chorus() 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_); + float32_t delay = this->getDepth() * this->lfo_.process(); // Convert the delay time to samples unsigned delay_samples = static_cast(delay * this->sample_position_ratio_); @@ -62,12 +64,6 @@ void Chorus::processSample(float32_t inL, float32_t inR, float32_t& outL, float3 // 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) @@ -82,13 +78,12 @@ float32_t Chorus::getDepth() const 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(); + this->lfo_.setNormalizedFrequency(rate); } float32_t Chorus::getRate() const { - return this->rate_; + return this->lfo_.getNormalizedFrequency(); } void Chorus::setFeedback(float32_t feedback) diff --git a/src/fx_chorus.h b/src/fx_chorus.h index 05456f1..a4104f5 100644 --- a/src/fx_chorus.h +++ b/src/fx_chorus.h @@ -18,14 +18,14 @@ // #pragma once -#include "fx.h" +#include "fx_components.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); + Chorus(float32_t sampling_rate, unsigned voices = 4, float32_t rate = 0.1f, float32_t depth = 5.0f, float32_t feedback = 0.5f); virtual ~Chorus(); virtual void processSample(float32_t inL, float32_t inR, float32_t& outL, float32_t& outR) override; @@ -46,10 +46,7 @@ private: float32_t** delay_buffersR_; unsigned* delay_buffer_indices_; - float32_t lfo_phase_; - float32_t lfo_phase_increment_; - + LFO lfo_; 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_components.cpp b/src/fx_components.cpp new file mode 100644 index 0000000..8801074 --- /dev/null +++ b/src/fx_components.cpp @@ -0,0 +1,108 @@ +#include "fx_components.h" + +#include + +const float32_t Constants::M2PI = 2.0f * PI; +const float32_t Constants::M1_PI = 1.0f / PI; + +LFO::LFO(float32_t sampling_rate, Waveform waveform, float32_t min_frequency, float32_t max_frequency) : + FXBase(sampling_rate), + min_frequency_(min_frequency), + max_frequency_(max_frequency), + phase_(0.0f), + last_sample_(0.0f), + new_phase_(true), + rnd_generator_(rnd_device_()), + rnd_distribution_(-1.0f, 1.0f) +{ + this->setWaveform(waveform); + this->setFrequency(this->min_frequency_); +} + +LFO::~LFO() +{ +} + +void LFO::setWaveform(Waveform waveform) +{ + this->waveform_ = waveform; +} + +LFO::Waveform LFO::getWaveform() const +{ + return this->waveform_; +} + +void LFO::setNormalizedFrequency(float32_t normalized_frequency) +{ + normalized_frequency = constrain(normalized_frequency, 0.0f, 1.0f); + if(this->normalized_frequency_ != normalized_frequency) + { + float32_t frequency = mapfloat(normalized_frequency, 0.0f, 1.0f, this->min_frequency_, this->max_frequency_); + this->normalized_frequency_ = normalized_frequency; + this->frequency_ = frequency; + this->phase_increment_ = Constants::M2PI * this->frequency_ / this->getSamplingRate(); + } +} + +float32_t LFO::getNormalizedFrequency() const +{ + return this->frequency_; +} + +void LFO::setFrequency(float32_t frequency) +{ + frequency = constrain(frequency, this->min_frequency_, this->max_frequency_); + if(this->frequency_ != frequency) + { + float32_t normalized_frequency = mapfloat(frequency, this->min_frequency_, this->max_frequency_, 0.0f, 1.0f); + this->normalized_frequency_ = normalized_frequency; + this->frequency_ = frequency; + this->phase_increment_ = Constants::M2PI * this->frequency_ / this->getSamplingRate(); + } +} + +float32_t LFO::process() +{ + float32_t out = 0.0f; + switch(this->waveform_) + { + case Waveform::Sine: + out = std::sin(this->phase_); + break; + case Waveform::Saw: + out = Constants::M1_PI * this->phase_ - 1.0f; + break; + case Waveform::Square: + out = this->phase_ < PI ? 1.0 : -1.0; + break; + case Waveform::SH: + if(this->new_phase_) + { + out = this->rnd_distribution_(this->rnd_generator_); + } + else + { + out = this->last_sample_; + } + break; + case Waveform::Noise: + out = this->rnd_distribution_(this->rnd_generator_); + break; + } + + this->last_sample_ = out; + + this->phase_ += this->phase_increment_; + if(this->phase_ >= Constants::M2PI) + { + this->phase_ -= Constants::M2PI; + this->new_phase_ = true; + } + else + { + this->new_phase_ = false; + } + + return out; +} diff --git a/src/fx_components.h b/src/fx_components.h new file mode 100644 index 0000000..9ac3798 --- /dev/null +++ b/src/fx_components.h @@ -0,0 +1,109 @@ +// 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_components.h +// +// Several tools and components used in the implemlentation of FX +// +#pragma once + +#include "fx.h" + +#include + +struct Constants +{ + const static float32_t M2PI; // 2 * PI + const static float32_t M1_PI; // 1 / PI +}; + +class LFO : public FXBase +{ + DISALLOW_COPY_AND_ASSIGN(LFO); + +public: + typedef enum { + Sine, + Saw, + Square, + SH, + Noise + } Waveform; + + LFO(float32_t sampling_rate, Waveform waveform = Waveform::Sine, float32_t min_frequency = 0.01f, float32_t max_frequency = 10.0f); + ~LFO(); + + void setWaveform(Waveform waveform); + Waveform getWaveform() const; + + void setNormalizedFrequency(float32_t normalized_frequency); + float32_t getNormalizedFrequency() const; + + void setFrequency(float32_t frequency); + float32_t getFrequency() const; + + float32_t process(); + +private: + const float32_t min_frequency_; + const float32_t max_frequency_; + Waveform waveform_; + float32_t normalized_frequency_; + float32_t frequency_; + float32_t phase_; + float32_t phase_increment_; + float32_t last_sample_; + bool new_phase_; + std::random_device rnd_device_; + std::mt19937 rnd_generator_; + std::uniform_real_distribution rnd_distribution_; +}; + +template +class Buffer +{ + DISALLOW_COPY_AND_ASSIGN(Buffer); + +public: + Buffer(unsigned size) : + size_(size) + { + this->values_ = new T[size]; + this->reset(); + } + + virtual ~Buffer() + { + delete[] this->values_; + } + + void reset() + { + memset(this->values_, 0, this->size_ * sizeof(T)); + } + + float32_t& operator[](unsigned index) + { + return this->values_[index]; + } + + unsigned getSize() const + { + return this->size_; + } + +private: + const unsigned size_; + T* values_; +}; diff --git a/src/fx_flanger.cpp b/src/fx_flanger.cpp index e9389a7..e4b1607 100644 --- a/src/fx_flanger.cpp +++ b/src/fx_flanger.cpp @@ -8,7 +8,7 @@ Flanger::Flanger(float32_t sampling_rate, float32_t delay_time, float32_t freque FXElement(sampling_rate), MaxDelayLineSize(static_cast(2.0f * MAX_FLANGER_DELAY * sampling_rate / 1000.0f)), delay_line_index_(0), - lfo_phase_(0.0f) + lfo_(sampling_rate, LFO::Waveform::Sine, 0.1f, 10.0f) { this->delay_lineL_ = new float32_t[this->MaxDelayLineSize]; this->delay_lineR_ = new float32_t[this->MaxDelayLineSize]; @@ -27,10 +27,8 @@ Flanger::~Flanger() void Flanger::processSample(float32_t inL, float32_t inR, float32_t& outL, float32_t& outR) { - static float32_t M2PI = 2.0f * PI; - // Calculate the delay time based on the depth and rate parameters - float32_t delay = this->getDelayTime() + this->getDepth() * std::sin(this->lfo_phase_); + float32_t delay = this->getDelayTime() + this->getDepth() * this->lfo_.process(); // Convert the delay time to samples unsigned delay_samples = static_cast(delay * this->getSamplingRate() / 1000.0f); @@ -44,12 +42,6 @@ void Flanger::processSample(float32_t inL, float32_t inR, float32_t& outL, float this->delay_lineR_[this->delay_line_index_] = inR + outR * this->getFeedback(); this->delay_line_index_ = (this->delay_line_index_ + 1) % this->delay_line_size_; - - this->lfo_phase_ += this->lfo_phase_increment_; - if(this->lfo_phase_ > M2PI) - { - this->lfo_phase_ -= M2PI; - } } void Flanger::setDelayTime(float32_t delayMS) @@ -65,14 +57,12 @@ float32_t Flanger::getDelayTime() const void Flanger::setFrequency(float32_t frequency) { - frequency = constrain(frequency, 0.1f, 1.0f); - this->frequency_ = frequency; - this->lfo_phase_increment_ = 2.0f * PI * frequency / this->getSamplingRate(); + this->lfo_.setNormalizedFrequency(frequency); } float32_t Flanger::getFrequency() const { - return this->frequency_; + return this->lfo_.getNormalizedFrequency(); } void Flanger::setDepth(float32_t depth) diff --git a/src/fx_flanger.h b/src/fx_flanger.h index 8edcd6e..56fafc4 100644 --- a/src/fx_flanger.h +++ b/src/fx_flanger.h @@ -18,14 +18,14 @@ // #pragma once -#include "fx.h" +#include "fx_components.h" class Flanger : public FXElement { DISALLOW_COPY_AND_ASSIGN(Flanger); public: - Flanger(float32_t sampling_rate, float32_t delay_time = 5.0f, float32_t frequency = 0.5f, float32_t depth = 1.0f, float32_t feedback = 0.5f); + Flanger(float32_t sampling_rate, float32_t delay_time = 5.0f, float32_t frequency = 0.05f, float32_t depth = 1.0f, float32_t feedback = 0.25f); virtual ~Flanger(); virtual void processSample(float32_t inL, float32_t inR, float32_t& outL, float32_t& outR) override; @@ -51,11 +51,8 @@ private: float32_t* delay_lineL_; float32_t* delay_lineR_; - float32_t lfo_phase_; - float32_t lfo_phase_increment_; - float32_t delay_time_ms_; // Delay time in milliseconds (0.0 - 10.0) - float32_t frequency_; // LFO frequency in HZ (0.1 - 10.0) + LFO lfo_; float32_t depth_; // Depth of the flanger effect in milliseconds (0.0 - 10.0) float32_t feedback_; // Amount of feedback to apply to the delay line }; \ No newline at end of file diff --git a/src/fx_orbitone.cpp b/src/fx_orbitone.cpp index d189582..4aa6571 100644 --- a/src/fx_orbitone.cpp +++ b/src/fx_orbitone.cpp @@ -25,29 +25,29 @@ inline float32_t OrbitoneParameter::getFeedback() const // OrbitoneStage implementation -OrbitoneStage::OrbitoneStage(float32_t sampling_rate, OrbitoneParameter* params, float32_t frequency) : +OrbitoneStage::OrbitoneStage(float32_t sampling_rate, OrbitoneParameter* params, float32_t frequency, float32_t level) : FXElement(sampling_rate), params_(params), - frequency_(frequency), - phase_(0.0f) + lfo_(sampling_rate, LFO::Waveform::Sine, 0.0f, 20000.0f), + level_(level) +{ + this->lfo_.setFrequency(frequency); + + this->x_[0] = this->x_[1] = 0.0f; +} + +OrbitoneStage::~OrbitoneStage() { - this->phase_increment_ = 2.0f * PI * frequency / this->getSamplingRate(); } void OrbitoneStage::processSample(float32_t inL, float32_t inR, float32_t& outL, float32_t& outR) { // Generate a sine wave using the stage's oscillator - float32_t osc = sin(this->phase_); - - // Update the phase of the oscillator - this->phase_ += this->phase_increment_; - if(this->phase_ > 2.0f * PI) { - this->phase_ -= 2.0f * PI; - } + float32_t osc = this->level_ * this->lfo_.process(); // Apply feedback to the stage's input - outL = inL + osc * this->params_->getFeedback(); - outR = inR + osc * this->params_->getFeedback(); + outL = inL + inL * osc + this->params_->getFeedback() * this->x_[0]; + outR = inR + inR * osc + this->params_->getFeedback() * this->x_[1]; } @@ -57,10 +57,12 @@ Orbitone::Orbitone(float32_t sampling_rate, float32_t feedback) : FXElement(sampling_rate), params_(sampling_rate, feedback) { + float32_t level = 1.0f; for(unsigned i = 0; i < NUM_ORBITONR_STAGES; ++i) { float32_t frequency = 440.0 * pow(2.0f, (i - 1) / 12.0f); // Sets the frequency of each stage to be a multiple of 440 Hz - this->stages_[i] = new OrbitoneStage(sampling_rate, &this->params_, frequency); + level /= 2.0f; + this->stages_[i] = new OrbitoneStage(sampling_rate, &this->params_, frequency, level); } } diff --git a/src/fx_orbitone.h b/src/fx_orbitone.h index 7a9a4de..0a54894 100644 --- a/src/fx_orbitone.h +++ b/src/fx_orbitone.h @@ -18,7 +18,7 @@ // #pragma once -#include "fx.h" +#include "fx_components.h" class OrbitoneStage; @@ -43,15 +43,16 @@ class OrbitoneStage : public FXElement DISALLOW_COPY_AND_ASSIGN(OrbitoneStage); public: - OrbitoneStage(float32_t sampling_rate, OrbitoneParameter* params, float32_t frequency); + OrbitoneStage(float32_t sampling_rate, OrbitoneParameter* params, float32_t frequency, float32_t level); + virtual ~OrbitoneStage(); virtual void processSample(float32_t inL, float32_t inR, float32_t& outL, float32_t& outR) override; private: OrbitoneParameter* params_; - float32_t frequency_; // Frequency of the stage oscillator in Hz - float32_t phase_; // Phase of the stage's oscillator - float32_t phase_increment_; // Amount to increment the phase at each sample + LFO lfo_; + float32_t level_; + float32_t x_[2]; }; #define NUM_ORBITONR_STAGES 4 diff --git a/src/fx_phaser.cpp b/src/fx_phaser.cpp index 8f94183..faab36a 100644 --- a/src/fx_phaser.cpp +++ b/src/fx_phaser.cpp @@ -75,10 +75,8 @@ void PhaserStage::processSample(float32_t inL, float32_t inR, float32_t& outL, f Phaser::Phaser(float32_t sampling_rate, float32_t frequency, float32_t q) : FXElement(sampling_rate), params_(sampling_rate, frequency, q), - phase_(0.0f), - phase_increment_(0.0f) + lfo_(sampling_rate, LFO::Waveform::Sine, 0.1f, 10.0f) { - this->phase_increment_ = 2.0f * PI * frequency / this->getSamplingRate(); for(unsigned i = 0; i < NUM_PHASER_STAGES; ++i) { this->stages_[i] = new PhaserStage(sampling_rate, &this->params_); @@ -104,20 +102,15 @@ void Phaser::processSample(float32_t inL, float32_t inR, float32_t& outL, float3 } // Modulate the output of the phaser using the LFO - outL = sampleL * (0.5f + 0.5f * cos(this->phase_)); - outR = sampleR * (0.5f + 0.5f * cos(this->phase_));; - - // Update the phase of the LFO - this->phase_ += this->phase_increment_; - if(this->phase_ > 2.0f * PI) { - this->phase_ -= 2.0 * PI; - } + float32_t lfo = this->lfo_.process(); + outR = sampleR * (0.5f + 0.5f * lfo); + outL = sampleL * (0.5f + 0.5f * lfo); } void Phaser::setFrequency(float32_t frequency) { this->params_.setFrequency(frequency); - this->phase_increment_ = 2.0f * PI * frequency / this->getSamplingRate(); + this->lfo_.setNormalizedFrequency(frequency); } inline float32_t Phaser::getFrequency() const diff --git a/src/fx_phaser.h b/src/fx_phaser.h index 737d4d7..735bcf9 100644 --- a/src/fx_phaser.h +++ b/src/fx_phaser.h @@ -18,7 +18,7 @@ // #pragma once -#include "fx.h" +#include "fx_components.h" class PhaserStage; @@ -80,7 +80,6 @@ public: private: PhaserParameter params_; - float32_t phase_; // Current phase of the LFO (0.01 - 5.0) - float32_t phase_increment_; // Amount to increment the phase at each sample + LFO lfo_; PhaserStage* stages_[NUM_PHASER_STAGES]; }; \ No newline at end of file diff --git a/src/fx_shimmer_reverb.cpp b/src/fx_shimmer_reverb.cpp index 914cb8f..ed5642c 100644 --- a/src/fx_shimmer_reverb.cpp +++ b/src/fx_shimmer_reverb.cpp @@ -3,6 +3,8 @@ #include #include +#define SHIMMER_MAX_DELAY_TIME 2.0f + ShimmerReverb::ShimmerReverb(float32_t sampling_rate, float32_t left_delay_time, float32_t right_delay_time, @@ -35,9 +37,11 @@ ShimmerReverb::~ShimmerReverb() void ShimmerReverb::processSample(float32_t inL, float32_t inR, float32_t& outL, float32_t& outR) { + const static float32_t M2PI = 2.0f * PI; + // Calculate shimmer offset based on current phase - float32_t shimmerOffsetL = this->getShimmerAmplitude() * sin(this->shimmer_phase_ * 2.0f * PI); - float32_t shimmerOffsetR = this->getShimmerAmplitude() * cos(this->shimmer_phase_ * 2.0f * PI); + float32_t shimmerOffsetL = this->getShimmerAmplitude() * sin(this->shimmer_phase_); + float32_t shimmerOffsetR = this->getShimmerAmplitude() * cos(this->shimmer_phase_); // Calculate read position for left and right channel delay lines unsigned readPosL = static_cast(this->DelayLineLength + this->write_pos_L_ - (this->delay_time_L_ + shimmerOffsetL) * this->getSamplingRate()) % this->DelayLineLength; @@ -63,13 +67,16 @@ void ShimmerReverb::processSample(float32_t inL, float32_t inR, float32_t& outL, this->write_pos_R_ = (this->write_pos_R_ + 1) % this->DelayLineLength; // Increment shimmer phase - this->shimmer_phase_ += this->getShimmerFrequency() / this->getSamplingRate(); - if(this->shimmer_phase_ > 1.0f) this->shimmer_phase_ -= 1.0f; + this->shimmer_phase_ += this->shimmer_phase_increment_; + if(this->shimmer_phase_ > M2PI) + { + this->shimmer_phase_ -= M2PI; + } } void ShimmerReverb::setLeftDelayTime(float32_t delay_time_L) { - this->delay_time_L_ = constrain(delay_time_L, 0.0f, SHIMMER_MAX_DELAY_TIME); + this->delay_time_L_ = constrain(delay_time_L, 0.0f, 1.0f); } float32_t ShimmerReverb::getLeftDelayTime() const @@ -79,7 +86,7 @@ float32_t ShimmerReverb::getLeftDelayTime() const void ShimmerReverb::setRightDelayTime(float32_t delay_time_R) { - this->delay_time_R_ = constrain(delay_time_R, 0.0f, SHIMMER_MAX_DELAY_TIME); + this->delay_time_R_ = constrain(delay_time_R, 0.0f, 1.0f); } float32_t ShimmerReverb::getRightDelayTime() const @@ -89,7 +96,9 @@ float32_t ShimmerReverb::getRightDelayTime() const void ShimmerReverb::setShimmerFrequency(float32_t frequency) { - this->shimmer_frequency_ = constrain(frequency, 0.0f, this->getSamplingRate() / 2.0f); + const static float32_t M2PI = 2.0f * PI; + this->shimmer_frequency_ = constrain(frequency, 0.0f, 1.0f); + this->shimmer_phase_increment_ = M2PI * mapfloat(this->shimmer_frequency_, 0.0f, 1.0f, 20.0f, 20000.0f) / this->getSamplingRate(); } float32_t ShimmerReverb::getShimmerFrequency() const @@ -109,7 +118,7 @@ float32_t ShimmerReverb::getShimmerAmplitude() const void ShimmerReverb::setDecayTime(float32_t decay_time) { - this->decay_time_ = constrain(decay_time, 0.0f, SHIMMER_MAX_DELAY_TIME); + this->decay_time_ = constrain(decay_time, 0.0f, 1.0f); } float32_t ShimmerReverb::getDecayTime() const diff --git a/src/fx_shimmer_reverb.h b/src/fx_shimmer_reverb.h index 57031fa..6263d97 100644 --- a/src/fx_shimmer_reverb.h +++ b/src/fx_shimmer_reverb.h @@ -21,8 +21,6 @@ #include "fx.h" -#define SHIMMER_MAX_DELAY_TIME 2.0f - class ShimmerReverb : public FXElement { DISALLOW_COPY_AND_ASSIGN(ShimmerReverb); @@ -62,11 +60,12 @@ private: // Current write position for left and right channel delay lines unsigned write_pos_L_; unsigned write_pos_R_; - float32_t shimmer_phase_; // Current shimmer phase (0.0 - 1.0) + float32_t shimmer_phase_; + float32_t shimmer_phase_increment_; - float32_t delay_time_L_; // Left channel delay time in seconds - float32_t delay_time_R_; // Right channel delay time in seconds - float32_t shimmer_frequency_; // Shimmer frequency in Hz - float32_t shimmer_amplitude_; // Shimmer amplitude (0.0 - 1.0) - float32_t decay_time_; // Reverb decay time in seconds + float32_t delay_time_L_; // Left channel delay time in seconds + float32_t delay_time_R_; // Right channel delay time in seconds + float32_t shimmer_frequency_; // Shimmer frequency parameter in Hz (0.0 === 20Hz - 1.0 === 20kHz) + float32_t shimmer_amplitude_; // Shimmer amplitude (0.0 - 1.0) + float32_t decay_time_; // Reverb decay time in seconds }; \ No newline at end of file diff --git a/src/fx_svf.cpp b/src/fx_svf.cpp new file mode 100644 index 0000000..b786f54 --- /dev/null +++ b/src/fx_svf.cpp @@ -0,0 +1,182 @@ +#include "fx_svf.h" + +#include + +StateVariableFilter::StateVariableFilter(float32_t sampling_rate, Type type, float32_t cutoff) : + FXElement(sampling_rate), + type_(type), + cutoff_(0.0f) +{ + memset(this->z1_, 0, 2 * sizeof(float32_t)); + memset(this->z2_, 0, 2 * sizeof(float32_t)); + + this->setCutoff(cutoff); + this->setResonance(0.0f); +} + +StateVariableFilter::~StateVariableFilter() +{ +} + +void StateVariableFilter::setFilterType(Type type) +{ + if(this->type_ != type) + { + this->type_ = type; + this->updateCoefficients(); + } +} + +void StateVariableFilter::setCutoff(float32_t cutoff) +{ + cutoff = constrain(cutoff, 1.0f, this->getSamplingRate() / 2.0f); + if(this->cutoff_ != cutoff) + { + this->cutoff_ = cutoff; + this->updateCoefficients(); + } +} + +void StateVariableFilter::setResonance(float32_t resonance) +{ + resonance = constrain(resonance, 0.005f, 1.0f); + if(this->resonance_ != resonance) + { + this->resonance_ = resonance; + this->updateCoefficients(); + } +} + +void StateVariableFilter::setPeakGainDB(float32_t gain) +{ + if(this->peak_gain_ != gain) + { + this->peak_gain_ = gain; + this->updateCoefficients(); + } +} + +void StateVariableFilter::updateCoefficients() +{ + // Compute the filter coefficients based on the current parameter values + float32_t w0 = PI * this->cutoff_ / this->getSamplingRate(); + float32_t V = pow(10, fabs(this->peak_gain_) / 20.0f); + float32_t K = std::tan(w0); + float32_t K2 = K * K; + float32_t norm; + + switch(this->type_) + { + case Type::LPF: + norm = 1.0f / (1.0f + K / this->resonance_ + K2); + this->a0_ = K2 * norm; + this->a1_ = 2.0f * this->a0_; + this->a2_ = this->a0_; + this->b1_ = 2.0f * (K2 - 1.0f) * norm; + this->b2_ = (1.0f - K / this->resonance_ + K2) * norm; + break; + case Type::HPF: + norm = 1.0f / (1.0f + K / this->resonance_ + K2); + this->a0_ = norm; + this->a1_ = -2.0f * this->a0_; + this->a2_ = this->a0_; + this->b1_ = 2.0f * (K2 - 1.0f) * norm; + this->b2_ = (1.0f - K / this->resonance_ + K2) * norm; + break; + case Type::BPF: + norm = 1.0f / (1.0f + K / this->resonance_ + K2); + this->a0_ = K / this->resonance_ * norm; + this->a1_ = 0.0f; + this->a2_ = -this->a0_; + this->b1_ = 2.0f * (K2 - 1.0f) * norm; + this->b2_ = (1.0f - K / this->resonance_ + K2) * norm; + break; + case Type::NOTCH: + norm = 1.0f / (1.0f + K / this->resonance_ + K2); + this->a0_ = (1.0f + K2) * norm; + this->a1_ = 2.0f * (K2 - 1.0f) * norm; + this->a2_ = this->a0_; + this->b1_ = 2.0f * (K2 - 1.0f) * norm; + this->b2_ = (1.0f - K / this->resonance_ + K2) * norm; + break; + case Type::PEQ: + if(this->peak_gain_ >= 0) + { + // boost + norm = 1.0f / (1.0f + 1.0f / this->resonance_ * K + K2); + this->a0_ = (1.0f + V / this->resonance_ * K + K2) * norm; + this->a1_ = 2.0f * (K2 - 1) * norm; + this->a2_ = (1.0f - V / this->resonance_ * K + K2) * norm; + this->b1_ = this->a1_; + this->b2_ = (1.0f - 1.0f / this->resonance_ * K + K2) * norm; + } + else + { + // cut + norm = 1.0f / (1 + V / this->resonance_ * K + K2); + this->a0_ = (1.0f + 1.0f / this->resonance_ * K + K2) * norm; + this->a1_ = 2.0f * (K2 - 1) * norm; + this->a2_ = (1.0f - 1.0f / this->resonance_ * K + K2) * norm; + this->b1_ = this->a1_; + this->b2_ = (1.0f - V / this->resonance_ * K + K2) * norm; + } + break; + case Type::LSH: + if(this->peak_gain_ >= 0) + { + // boost + norm = 1 / (1 + std::sqrt(2) * K + K2); + this->a0_ = (1.0f + std::sqrt(2.0f * V) * K + V * K2) * norm; + this->a1_ = 2.0f * (V * K2 - 1.0f) * norm; + this->a2_ = (1.0f - std::sqrt(2.0f * V) * K + V * K2) * norm; + this->b1_ = 2.0f * (K2 - 1.0f) * norm; + this->b2_ = (1.0f - std::sqrt(2.0f) * K + K2) * norm; + } + else + { + // cutK * K + norm = 1.0f / (1.0f + std::sqrt(2.0f * V) * K + V * K2); + this->a0_ = (1.0f + std::sqrt(2.0f) * K + K2) * norm; + this->a1_ = 2.0f * (K2 - 1.0f) * norm; + this->a2_ = (1.0f - std::sqrt(2.0f) * K + K2) * norm; + this->b1_ = 2.0f * (V * K2 - 1.0f) * norm; + this->b2_ = (1.0f - std::sqrt(2.0f * V) * K + V * K2) * norm; + } + break; + case Type::HSH: + if(this->peak_gain_ >= 0) + { + // boost + norm = 1.0f / (1.0f + std::sqrt(2.0f) * K + K2); + this->a0_ = (V + std::sqrt(2.0f * V) * K + K2) * norm; + this->a1_ = 2.0f * (K2 - V) * norm; + this->a2_ = (V - std::sqrt(2.0f * V) * K + K2) * norm; + this->b1_ = 2.0f * (K2 - 1.0f) * norm; + this->b2_ = (1.0f - std::sqrt(2.0f) * K + K2) * norm; + } + else + { + // cut + norm = 1.0f / (V + std::sqrt(2.0f * V) * K + K2); + this->a0_ = (1.0f + std::sqrt(2.0f) * K + K2) * norm; + this->a1_ = 2.0f * (K2 - 1.0f) * norm; + this->a2_ = (1.0f - std::sqrt(2.0f) * K + K2) * norm; + this->b1_ = 2.0f * (K2 - V) * norm; + this->b2_ = (V - std::sqrt(2.0f * V) * K + K2) * norm; + } + break; + } +} + +void StateVariableFilter::processSample(float32_t inL, float32_t inR, float32_t& outL, float32_t& outR) +{ + const float32_t gain = 10.0f; + + outL = (inL * this->a0_ + this->z1_[0]) * gain; + this->z1_[0] = inL * this->a1_ + this->z2_[0] - this->b1_ * outL; + this->z2_[0] = inL * this->a2_ - this->b2_ * outL; + + outR = (inR * this->a0_ + this->z1_[1]) * gain; + this->z1_[0] = inR * this->a1_ + this->z2_[1] - this->b1_ * outR; + this->z2_[0] = inR * this->a2_ - this->b2_ * outR; +} diff --git a/src/fx_svf.h b/src/fx_svf.h new file mode 100644 index 0000000..cf81adf --- /dev/null +++ b/src/fx_svf.h @@ -0,0 +1,63 @@ +// 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_svf.h +// +// State Variable Filter used in Tape Delay +// + +#include "fx.h" + +class StateVariableFilter : public FXElement +{ + DISALLOW_COPY_AND_ASSIGN(StateVariableFilter); + +public: + typedef enum + { + LPF, // Low pass filter + HPF, // High pass filter + BPF, // Band pass filter + NOTCH, // Notch Filter + PEQ, // Peaking band EQ filter + LSH, // Low shelf filter + HSH // High shelf filter + } Type; + + StateVariableFilter(float32_t sampling_rate, Type type, float32_t cutoff); + virtual ~StateVariableFilter(); + + void setFilterType(Type type); + void setCutoff(float32_t cutoff); + void setResonance(float32_t resonance); + void setPeakGainDB(float32_t gainDB); + + virtual void processSample(float32_t inL, float32_t inR, float32_t& outL, float32_t& outR) override; + +private: + void updateCoefficients(); + + Type type_; + float32_t cutoff_; + float32_t resonance_; + float32_t peak_gain_; + float32_t a0_; + float32_t a1_; + float32_t a2_; + float32_t b1_; + float32_t b2_; + float32_t z1_[2]; + float32_t z2_[2]; +}; \ No newline at end of file diff --git a/src/fx_tape_delay.cpp b/src/fx_tape_delay.cpp index f5c65ea..550dce1 100644 --- a/src/fx_tape_delay.cpp +++ b/src/fx_tape_delay.cpp @@ -1,18 +1,53 @@ #include "fx_tape_delay.h" #include -#include -#include +#define MAX_DELAY_TIME 1.0f +#define MAX_FLUTTER_DELAY_TIME 0.1f + +#define LPF_CUTOFF_REF 14000.0f +#define HPF_CUTOFF_REF 60.0f + +TapeDelay::LowHighPassFilter::LowHighPassFilter(float32_t sampling_rate) : + FXElement(sampling_rate), + lpf_(sampling_rate, StateVariableFilter::Type::LPF, LPF_CUTOFF_REF), + hpf_(sampling_rate, StateVariableFilter::Type::HPF, HPF_CUTOFF_REF) +{ + this->setCutoffChangeRatio(0.0f); +} + +TapeDelay::LowHighPassFilter::~LowHighPassFilter() +{ +} + +void TapeDelay::LowHighPassFilter::setCutoffChangeRatio(float32_t ratio) +{ + ratio += 1.0f; + + this->lpf_.setCutoff(LPF_CUTOFF_REF * ratio); + this->hpf_.setCutoff(HPF_CUTOFF_REF * ratio); +} + +void TapeDelay::LowHighPassFilter::processSample(float32_t inL, float32_t inR, float32_t& outL, float32_t& outR) +{ + this->lpf_.processSample(inL, inR, outL, outR); + this->hpf_.processSample(outL, outR, outL, outR); +} TapeDelay::TapeDelay(const float32_t sampling_rate, float32_t default_delay_time, float32_t default_flutter_level, float32_t default_feedback_level) : FXElement(sampling_rate), - MaxSampleDelayTime(2.0f * sampling_rate * MAX_DELAY_TIME), - left_read_pos_(0), - right_read_pos_(0) + MaxSampleDelayTime((MAX_DELAY_TIME + MAX_FLUTTER_DELAY_TIME) * sampling_rate * MAX_DELAY_TIME), + read_pos_L_(0), + read_pos_R_(0), + filter_(sampling_rate), + rnd_generator_(rnd_device_()), + rnd_distribution_(-1.0f, 1.0f) { - this->left_buffer_ = new float32_t[this->MaxSampleDelayTime]; - this->right_buffer_ = new float32_t[this->MaxSampleDelayTime]; + this->buffer_L_ = new float32_t[this->MaxSampleDelayTime]; + this->buffer_R_ = new float32_t[this->MaxSampleDelayTime]; + + memset(this->buffer_L_, 0, this->MaxSampleDelayTime * sizeof(float32_t)); + memset(this->buffer_R_, 0, this->MaxSampleDelayTime * sizeof(float32_t)); this->setLeftDelayTime(default_delay_time); this->setRightDelayTime(default_delay_time); @@ -22,69 +57,85 @@ TapeDelay::TapeDelay(const float32_t sampling_rate, float32_t default_delay_time TapeDelay::~TapeDelay() { - delete[] this->left_buffer_; - delete[] this->right_buffer_; + delete[] this->buffer_L_; + delete[] this->buffer_R_; } void TapeDelay::processSample(float32_t inL, float32_t inR, float32_t& outL, float32_t& outR) { -std::cout << "Processing effect: " << typeid(this).name() << std::endl; - // calculate the fluttered delay time - float32_t fluttered_delay_time_L = (MAX_DELAY_TIME * this->getLeftDelayTime() + this->getFlutteredDelayTime()) * this->getSamplingRate(); - // Calculate write positions - int left_write_pos = (this->MaxSampleDelayTime + this->left_read_pos_ - static_cast(fluttered_delay_time_L)) % this->MaxSampleDelayTime; +int step = 0; +cout << "Delay processing" << endl; + +cout << "Processing #" << (++step) << ": Calculate the fluttered delay time" << endl; + // Calculate the fluttered delay time + float32_t fluttered_delay_time = this->getFlutteredDelayTime(); + this->filter_.setCutoffChangeRatio(fluttered_delay_time); - // calculate the fluttered delay time - float32_t fluttered_delay_time_R = (MAX_DELAY_TIME * this->getRightDelayTime() + this->getFlutteredDelayTime()) * this->getSamplingRate(); + float32_t fluttered_delay_time_L = (MAX_DELAY_TIME * this->getLeftDelayTime() + fluttered_delay_time) * this->getSamplingRate(); + float32_t fluttered_delay_time_R = (MAX_DELAY_TIME * this->getRightDelayTime() + fluttered_delay_time) * this->getSamplingRate(); + +cout << "Processing #" << (++step) << ": Calculate write positions" << endl; // Calculate write positions - int right_write_pos = (this->MaxSampleDelayTime + this->right_read_pos_ - static_cast(fluttered_delay_time_R)) % this->MaxSampleDelayTime; + unsigned write_pos_L = static_cast(this->MaxSampleDelayTime + this->read_pos_L_ + fluttered_delay_time_L) % this->MaxSampleDelayTime; + unsigned write_pos_R = static_cast(this->MaxSampleDelayTime + this->read_pos_R_ + fluttered_delay_time_R) % this->MaxSampleDelayTime; +cout << "Processing #" << (++step) << ": Write input to delay buffers" << endl; // Write input to delay buffers - this->left_buffer_[left_write_pos] = inL; - this->right_buffer_[right_write_pos] = inR; + this->buffer_L_[write_pos_L] = inL; + this->buffer_R_[write_pos_R] = inR; +cout << "Processing #" << (++step) << ": Read from delay buffers and apply feedback" << endl; // Read from delay buffers and apply feedback - outL = this->left_buffer_[this->left_read_pos_]; - outR = this->right_buffer_[this->right_read_pos_]; - this->left_buffer_[left_write_pos] += outL * this->getFeedbackLevel(); - this->right_buffer_[right_write_pos] += outR * this->getFeedbackLevel(); - + this->filter_.processSample( + this->buffer_L_[this->read_pos_L_], + this->buffer_R_[this->read_pos_R_], + outL, + outR + ); + // outL = this->buffer_L_[this->read_pos_L_]; + // outR = this->buffer_R_[this->read_pos_R_]; + + this->buffer_L_[write_pos_L] += outL * this->getFeedbackLevel(); + this->buffer_R_[write_pos_R] += outR * this->getFeedbackLevel(); + +cout << "Processing #" << (++step) << ": Increment read positions" << endl; // Increment read positions - ++this->left_read_pos_; - if(this->left_read_pos_ >= this->MaxSampleDelayTime) + ++this->read_pos_L_; + if(this->read_pos_L_ >= this->MaxSampleDelayTime) { - this->left_read_pos_ -= this->MaxSampleDelayTime; + this->read_pos_L_ -= this->MaxSampleDelayTime; } - ++this->right_read_pos_; - if(this->right_read_pos_ >= this->MaxSampleDelayTime) + ++this->read_pos_R_; + if(this->read_pos_R_ >= this->MaxSampleDelayTime) { - this->right_read_pos_ -= this->MaxSampleDelayTime; + this->read_pos_R_ -= this->MaxSampleDelayTime; } +cout << "Processing #" << (++step) << ": Completed" << endl; } void TapeDelay::setLeftDelayTime(float32_t delay_time) { - this->left_delay_time_ = constrain(delay_time, 0.0f, 1.0f); + this->delay_time_L_ = constrain(delay_time, 0.0f, 1.0f); } float32_t TapeDelay::getLeftDelayTime() const { - return this->left_delay_time_; + return this->delay_time_L_; } void TapeDelay::setRightDelayTime(float32_t delay_time) { - this->right_delay_time_ = constrain(delay_time, 0.0f, 1.0f); + this->delay_time_R_ = constrain(delay_time, 0.0f, 1.0f); } float32_t TapeDelay::getRightDelayTime() const { - return this->right_delay_time_; + return this->delay_time_R_; } void TapeDelay::setFlutterLevel(float32_t flutter_level) { - this->flutter_level_ = constrain(flutter_level, 0.0f, 0.1f); + this->flutter_level_ = constrain(flutter_level, 0.0f, 1.0f); } float32_t TapeDelay::getFlutterLevel() const @@ -105,10 +156,8 @@ float32_t TapeDelay::getFeedbackLevel() const float32_t TapeDelay::getFlutteredDelayTime() { // Genarate a random number in the range [-1.0 , 1.0] - float32_t r = - static_cast(this->random_generator_()) / - static_cast(this->random_generator_.max()); + float32_t r = this->rnd_distribution_(this->rnd_generator_); // Scale and bias the random number to the desired flutter range - return r * this->getFlutterLevel(); + return MAX_FLUTTER_DELAY_TIME * r * this->getFlutterLevel(); } diff --git a/src/fx_tape_delay.h b/src/fx_tape_delay.h index 5978514..78ccea0 100644 --- a/src/fx_tape_delay.h +++ b/src/fx_tape_delay.h @@ -20,17 +20,58 @@ #pragma once #include "fx.h" +#include "fx_svf.h" #include -#define MAX_DELAY_TIME 2.0f - class TapeDelay : public FXElement { DISALLOW_COPY_AND_ASSIGN(TapeDelay); - + + class LowHighPassFilter : public FXElement + { + DISALLOW_COPY_AND_ASSIGN(LowHighPassFilter); + + public: + LowHighPassFilter(float32_t sampling_rate); + virtual ~LowHighPassFilter(); + + virtual void processSample(float32_t inL, float32_t inR, float32_t& outL, float32_t& outR) override; + + void setCutoffChangeRatio(float32_t ratio); + + private: + // void processSampleLPF(float32_t inL, float32_t inR, float32_t& outL, float32_t& outR); + // void processSampleHPF(float32_t inL, float32_t inR, float32_t& outL, float32_t& outR); + + + // float32_t a0_lpf_; + // float32_t a1_lpf_; + // float32_t a2_lpf_; + // float32_t b1_lpf_; + // float32_t b2_lpf_; + // float32_t x1_lpf_[2]; + // float32_t x2_lpf_[2]; + // float32_t y1_lpf_[2]; + // float32_t y2_lpf_[2]; + + // float32_t a0_hpf_; + // float32_t a1_hpf_; + // float32_t a2_hpf_; + // float32_t b1_hpf_; + // float32_t b2_hpf_; + // float32_t x1_hpf_[2]; + // float32_t x2_hpf_[2]; + // float32_t y1_hpf_[2]; + // float32_t y2_hpf_[2]; + + StateVariableFilter lpf_; + StateVariableFilter hpf_; + }; + + public: - TapeDelay(const float32_t sampling_rate, float32_t default_delay_time = 0.25f, float32_t default_flutter_level = 0.05f, float32_t default_wet_level = 0.5f); + TapeDelay(const float32_t sampling_rate, float32_t default_delay_time = 0.25f, float32_t default_flutter_level = 1.0f, float32_t default_wet_level = 0.5f); virtual ~TapeDelay(); virtual void processSample(float32_t inL, float32_t inR, float32_t& outL, float32_t& outR) override; @@ -52,13 +93,18 @@ private: private: const size_t MaxSampleDelayTime; - size_t left_read_pos_; - size_t right_read_pos_; - float32_t* left_buffer_; - float32_t* right_buffer_; - float32_t left_delay_time_; // Left delay time in seconds (0.0 - 2.0) - float32_t right_delay_time_; // Right delay time in seconds (0.0 - 2.0) - float32_t flutter_level_; // Flutter level (0.0 - 0.1) - float32_t feedback_; // Feedback (0.0 - 1.0) - std::mt19937 random_generator_; + size_t read_pos_L_; + size_t read_pos_R_; + float32_t* buffer_L_; + float32_t* buffer_R_; + float32_t delay_time_L_; // Left delay time in seconds (0.0 - 2.0) + float32_t delay_time_R_; // Right delay time in seconds (0.0 - 2.0) + float32_t flutter_level_; // Flutter level (0.0 - 0.1) + float32_t feedback_; // Feedback (0.0 - 1.0) + + LowHighPassFilter filter_; + + std::random_device rnd_device_; + std::mt19937 rnd_generator_; + std::uniform_real_distribution rnd_distribution_; }; diff --git a/src/minidexed.cpp b/src/minidexed.cpp index 6d47027..34d7f21 100644 --- a/src/minidexed.cpp +++ b/src/minidexed.cpp @@ -923,7 +923,7 @@ void CMiniDexed::SetParameter (TParameter Parameter, int nValue) case ParameterFXChainTapeDelayFlutter: nValue = constrain((int)nValue, 0, 99); this->m_FXSpinLock.Acquire(); - this->setFXChainTapeDelayFlutter(nValue / 990.0f); + this->setFXChainTapeDelayFlutter(nValue / 99.0f); this->m_FXSpinLock.Release(); break; case ParameterFXChainTapeDelayFeedback: @@ -949,19 +949,19 @@ void CMiniDexed::SetParameter (TParameter Parameter, int nValue) case ParameterFXChainShimmerReverbDelayTimeLeft: nValue = constrain((int)nValue, 0, 99); this->m_FXSpinLock.Acquire(); - this->setFXChainShimmerReverbDelayTimeLeft(2.0f * nValue / 99.0f); + this->setFXChainShimmerReverbDelayTimeLeft(nValue / 99.0f); this->m_FXSpinLock.Release(); break; case ParameterFXChainShimmerReverbDelayTimeRight: nValue = constrain((int)nValue, 0, 99); this->m_FXSpinLock.Acquire(); - this->setFXChainShimmerReverbDelayTimeRight(2.0f * nValue / 99.0f); + this->setFXChainShimmerReverbDelayTimeRight(nValue / 99.0f); this->m_FXSpinLock.Release(); break; case ParameterFXChainShimmerReverbFrequency: nValue = constrain((int)nValue, 0, 99); this->m_FXSpinLock.Acquire(); - this->setFXChainShimmerReverbFrequency(2.0f * nValue / static_cast(this->m_pConfig->GetSampleRate())); + this->setFXChainShimmerReverbFrequency(nValue / 99.0f); this->m_FXSpinLock.Release(); break; case ParameterFXChainShimmerReverbAmplitude: @@ -973,7 +973,7 @@ void CMiniDexed::SetParameter (TParameter Parameter, int nValue) case ParameterFXChainShimmerReverbDecayTime: nValue = constrain((int)nValue, 0, 99); this->m_FXSpinLock.Acquire(); - this->setFXChainShimmerReverbDecayTime(2.0f * nValue / 99.0f); + this->setFXChainShimmerReverbDecayTime(nValue / 99.0f); this->m_FXSpinLock.Release(); break; #endif