diff --git a/.gitignore b/.gitignore index 6303c46..188d044 100644 --- a/.gitignore +++ b/.gitignore @@ -45,3 +45,4 @@ sdcard # Editor related files *.swp *.swo +.vscode/ diff --git a/src/Makefile b/src/Makefile index 540ae68..40d59c7 100644 --- a/src/Makefile +++ b/src/Makefile @@ -9,7 +9,9 @@ CMSIS_DIR = ../CMSIS_5/CMSIS 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 uibuttons.o midipin.o + effect_compressor.o effect_platervbstereo.o \ + fx.o fx_tape_delay.o fx_shimmer_reverb.o fx_rack.o \ + uibuttons.o midipin.o OPTIMIZE = -O3 diff --git a/src/fx.cpp b/src/fx.cpp new file mode 100644 index 0000000..c23efe7 --- /dev/null +++ b/src/fx.cpp @@ -0,0 +1,24 @@ +#include "fx.h" + +FXBase::FXBase(float32_t sampling_rate) : + SamplingRate(sampling_rate) +{ +} + +FXBase::~FXBase() +{ +} + +float32_t FXBase::getSamplingRate() const +{ + return this->SamplingRate; +} + +FX::FX(float32_t sampling_rate) : + FXBase(sampling_rate) +{ +} + +FX::~FX() +{ +} diff --git a/src/fx.h b/src/fx.h new file mode 100644 index 0000000..94387c6 --- /dev/null +++ b/src/fx.h @@ -0,0 +1,54 @@ +// 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.h +// +// Base class for Stereo audio effects proposed in the context of the MiniDexed project +// +#pragma once + +#include +#include +#include "common.h" + +#define DISALLOW_COPY_AND_ASSIGN(TypeName) \ + TypeName(const TypeName&) = delete; \ + void operator=(const TypeName&) = delete + +class FXBase +{ + DISALLOW_COPY_AND_ASSIGN(FXBase); + +protected: + FXBase(float32_t sampling_rate); + virtual ~FXBase(); + +public: + float32_t getSamplingRate() const; + +private: + const float32_t SamplingRate; +}; + +class FX : public FXBase +{ + DISALLOW_COPY_AND_ASSIGN(FX); + +protected: + FX(float32_t sampling_rate); + virtual ~FX(); + +public: + virtual void process(float32_t* left_input, float32_t* right_input, float32_t* left_output, float32_t* right_output, size_t nSamples) = 0; +}; diff --git a/src/fx_phaser.cpp b/src/fx_phaser.cpp new file mode 100644 index 0000000..1ff06e3 --- /dev/null +++ b/src/fx_phaser.cpp @@ -0,0 +1,58 @@ +#include "fx_phaser.h" + +#include + +PhaserStage::PhaserStage(float32_t sampling_rate, float32_t frequency, float32_t q) : + FXBase(sampling_rate), + frequency_(frequency), + q_(q) +{ + memset(this->z1, 0, 2 * sizeof(float32_t)); + memset(this->z2, 0, 2 * sizeof(float32_t)); + this->computeCoefficients(); +} + +void PhaserStage::computeCoefficients() +{ + float32_t w0 = 2.0f * PI * this->getFrequency() / this->getSamplingRate(); + float32_t alpha = sin(w0) / (2.0f * this->q_); + this->a0 = 1.0f + alpha; + this->a1 = -2.0f * cos(w0); + this->a2 = 1.0f - alpha; + this->b1 = this->a1; + this->b2 = this->a2; +} + +void PhaserStage::process(float32_t inL, float32_t inR, float32_t& outL, float32_t& outR) +{ + outL = (this->a0 * inL + this->a1 * this->z1[0] + this->a2 * this->z2[0]) / this->a0; + this->z2[0] = this->z1[0]; + this->z2[0] = inL; + + outR = (this->a0 * inR + this->a1 * this->z1[1] + this->a2 * this->z2[1]) / this->a0; + this->z2[1] = this->z1[1]; + this->z2[1] = inR; +} + +void PhaserStage::setFrequency(float32_t frequency) +{ + this->frequency_ = constrain(frequency, 0.0, 10.0); + this->computeCoefficients(); +} + +float32_t PhaserStage::getFrequency() const +{ + return this->frequency_; +} + +void PhaserStage::setQ(float32_t q) +{ + this->q_ = constrain(q, 0.1f, 1.0f); + this->computeCoefficients(); +} + +float32_t PhaserStage::getQ() const +{ + return this->q_; +} + diff --git a/src/fx_phaser.h b/src/fx_phaser.h new file mode 100644 index 0000000..657a79f --- /dev/null +++ b/src/fx_phaser.h @@ -0,0 +1,62 @@ +// 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_phaser.h +// +// Stereo Phaser audio effects proposed in the context of the MiniDexed project +// +#pragma once + +#include "fx.h" + +class PhaserStage : public FXBase +{ + DISALLOW_COPY_AND_ASSIGN(PhaserStage); + +public: + PhaserStage(float32_t sampling_rate, float32_t frequency = 0.5f, float32_t q = 1.0f); + + void process(float32_t inL, float32_t inR, float32_t& outL, float32_t& outR); + + void setFrequency(float32_t frequency); + inline float32_t getFrequency() const; + + void setQ(float32_t q); + inline float32_t getQ() const; + +private: + void computeCoefficients(); + + float32_t frequency_; // LFO frequency in Hz + float32_t q_; // Q factor for the filters + + float32_t a0, a1, a2, b1, b2; // Coefficients for the stage's filter + float32_t z1[2], z2[2]; // State variables for the stage's filter +}; + +#define NUM_PHASER_STAGES 6 + +class Phaser : public FX +{ + DISALLOW_COPY_AND_ASSIGN(Phaser); + +public: + Phaser(float32_t sampling_rate, float32_t frequency, float32_t q); + virtual ~Phaser(); + + virtual void process(float32_t* left_input, float32_t* right_input, float32_t* left_output, float32_t* right_output, size_t nSamples) override; + +private: + PhaserStage stages_[NUM_PHASER_STAGES]; +}; \ No newline at end of file diff --git a/src/fx_rack.cpp b/src/fx_rack.cpp new file mode 100644 index 0000000..bab4864 --- /dev/null +++ b/src/fx_rack.cpp @@ -0,0 +1,75 @@ +#include "fx_rack.h" + +FXUnit::FXUnit(float32_t sampling_rate, FX& fx, float32_t wet_level) : + FX(sampling_rate), + fx_(fx) +{ + this->setWetLevel(wet_level); +} + +FXUnit::~FXUnit() +{ +} + +void FXUnit::process(float32_t* left_input, float32_t* right_input, float32_t* left_output, float32_t* right_output, size_t nSamples) +{ + this->fx_.process(left_input, right_input, left_output, right_output, nSamples); + + for(unsigned i = 0; i < nSamples; ++i) + { + // Mix wet and dry signals + *left_output = this->getWetLevel() * *left_output + (1.0f - this->getWetLevel()) * *left_input; + *right_output = this->getWetLevel() * *right_output + (1.0f - this->getWetLevel()) * *left_input; + + // Move to next input sample + ++left_input; + ++right_input; + + // Move to next output sample + ++left_output; + ++right_output; + } +} + +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 TapeDelay(sampling_rate)); + this->registerFX(new ShimmerReverb(sampling_rate)); +} + +FXRack::~FXRack() +{ + for(FXChain::iterator it = this->fx_chain_.begin(); it != this->fx_chain_.end(); it++) + { + delete *it; + } + this->fx_chain_.clear(); +} + +void FXRack::process(float32_t* left_input, float32_t* right_input, float32_t* left_output, float32_t* right_output, size_t nSamples) +{ + for(FXChain::iterator it = this->fx_chain_.begin(); it != this->fx_chain_.end(); it++) + { + (*it)->process(left_input, right_input, left_output, right_output, nSamples); + + left_input = left_output; + right_input = right_output; + } +} + +void FXRack::registerFX(FX* fx) +{ + this->fx_chain_.push_back(new FXUnit(this->getSamplingRate(), *fx)); +} diff --git a/src/fx_rack.h b/src/fx_rack.h new file mode 100644 index 0000000..74fe77d --- /dev/null +++ b/src/fx_rack.h @@ -0,0 +1,62 @@ +// 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_rack.h +// +// Rack of audio effects proposed in the context of the MiniDexed project +// + +#pragma once + +#include "fx.h" +#include "fx_tape_delay.h" +#include "fx_shimmer_reverb.h" + +#include + +class FXUnit : public FX +{ + DISALLOW_COPY_AND_ASSIGN(FXUnit); + +public: + FXUnit(float32_t sampling_rate, FX& fx, float32_t wet_level = 0.5f); + virtual ~FXUnit(); + + virtual void process(float32_t* left_input, float32_t* right_input, float32_t* left_output, float32_t* right_output, size_t nSamples) override; + + void setWetLevel(float32_t wet_level); + inline float32_t getWetLevel() const; + +private: + FX& 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; + +class FXRack : public FX +{ + DISALLOW_COPY_AND_ASSIGN(FXRack); + +public: + FXRack(float32_t sampling_rate); + virtual ~FXRack(); + + virtual void process(float32_t* left_input, float32_t* right_input, float32_t* left_output, float32_t* right_output, size_t nSamples) override; + +private: + void registerFX(FX* fx); + + FXChain fx_chain_; +}; \ No newline at end of file diff --git a/src/fx_shimmer_reverb.cpp b/src/fx_shimmer_reverb.cpp new file mode 100644 index 0000000..651f657 --- /dev/null +++ b/src/fx_shimmer_reverb.cpp @@ -0,0 +1,134 @@ +#include "fx_shimmer_reverb.h" + +#include +#include + +ShimmerReverb::ShimmerReverb(float32_t sampling_rate, + float32_t left_delay_time, + float32_t right_delay_time, + float32_t shimmer_frequency, + float32_t shimmer_amplitude, + float32_t decay_time) : FX(sampling_rate), + DelayLineLength(static_cast(SHIMMER_MAX_DELAY_TIME * sampling_rate)), + write_pos_L_(0), + write_pos_R_(0), + shimmer_phase_(0.0f) +{ + this->delay_line_L_ = new float32_t[this->DelayLineLength]; + this->delay_line_R_ = new float32_t[this->DelayLineLength]; + + memset(this->delay_line_L_, 0, this->DelayLineLength * sizeof(float32_t)); + memset(this->delay_line_R_, 0, this->DelayLineLength * sizeof(float32_t)); + + this->setLeftDelayTime(left_delay_time); + this->setRightDelayTime(right_delay_time); + this->setShimmerFrequency(shimmer_frequency); + this->setShimmerAmplitude(shimmer_amplitude); + this->setDecayTime(decay_time); +} + +ShimmerReverb::~ShimmerReverb() +{ + delete[] this->delay_line_L_; + delete[] this->delay_line_R_; +} + +void ShimmerReverb::process(float32_t* left_input, float32_t* right_input, float32_t* left_output, float32_t* right_output, size_t nSamples) +{ + for(unsigned i = 0; i < nSamples; ++i) + { + // 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); + + // Calculate read position for left and right channel delay lines + int readPosL = this->write_pos_L_ - (int)(this->delay_time_L_ * this->getSamplingRate()) - (int)(shimmerOffsetL * this->getSamplingRate()); + int readPosR = this->write_pos_R_ - (int)(this->delay_time_R_ * this->getSamplingRate()) - (int)(shimmerOffsetR * this->getSamplingRate()); + + // Wrap read position around the end of the delay line if necessary + if(readPosL < 0) readPosL += this->DelayLineLength; + if(readPosR < 0) readPosR += this->DelayLineLength; + + // Read32_t left and right channel delay line samples + float32_t delaySampleL = this->delay_line_L_[readPosL]; + float32_t delaySampleR = this->delay_line_R_[readPosR]; + + // Calculate reverb decay factor + float32_t decay = std::pow(0.001f, 1.0f / (this->decay_time_ * this->getSamplingRate())); + + // Calculate output samples + *left_output = *left_input + delaySampleL * decay; + *right_output = *right_input + delaySampleR * decay; + + // Write input samples to delay lines + this->delay_line_L_[this->write_pos_L_] = *left_input; + this->delay_line_R_[this->write_pos_R_] = *right_input; + + // Increment write position and wrap around the end of the delay line if necessary + this->write_pos_L_ = (this->write_pos_L_ + 1) % this->DelayLineLength; + 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; + + // Move to next input sample + ++left_input; + ++right_input; + + // Move to next output sample + ++left_output; + ++right_output; + } +} + +void ShimmerReverb::setLeftDelayTime(float32_t delay_time_L) +{ + this->delay_time_L_ = constrain(delay_time_L, 0.0f, SHIMMER_MAX_DELAY_TIME); +} + +float32_t ShimmerReverb::getLeftDelayTime() const +{ + return this->delay_time_L_; +} + +void ShimmerReverb::setRightDelayTime(float32_t delay_time_R) +{ + this->delay_time_R_ = constrain(delay_time_R, 0.0f, SHIMMER_MAX_DELAY_TIME); +} + +float32_t ShimmerReverb::getRightDelayTime() const +{ + return this->delay_time_R_; +} + +void ShimmerReverb::setShimmerFrequency(float32_t frequency) +{ + this->shimmer_frequency_ = constrain(frequency, 0.0f, this->getSamplingRate() / 2.0f); +} + +float32_t ShimmerReverb::getShimmerFrequency() const +{ + return this->shimmer_frequency_; +} + +void ShimmerReverb::setShimmerAmplitude(float32_t amplitude) +{ + this->shimmer_amplitude_ = constrain(amplitude, 0.0f, 1.0f); +} + +float32_t ShimmerReverb::getShimmerAmplitude() const +{ + return this->shimmer_amplitude_; +} + +void ShimmerReverb::setDecayTime(float32_t decay_time) +{ + this->decay_time_ = constrain(decay_time, 0.0f, SHIMMER_MAX_DELAY_TIME); +} + +float32_t ShimmerReverb::getDecayTime() const +{ + return this->decay_time_; +} + diff --git a/src/fx_shimmer_reverb.h b/src/fx_shimmer_reverb.h new file mode 100644 index 0000000..a7b8129 --- /dev/null +++ b/src/fx_shimmer_reverb.h @@ -0,0 +1,72 @@ +// 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_shimmer_reverb.h +// +// Stereo Shimmer reverb proposed in the context of the MiniDexed project +// +#pragma once + +#include "fx.h" + +#define SHIMMER_MAX_DELAY_TIME 2.0f + +class ShimmerReverb : public FX +{ + DISALLOW_COPY_AND_ASSIGN(ShimmerReverb); + +public: + ShimmerReverb( float32_t sampling_rate, + float32_t left_delay_time = 0.5f, + float32_t right_delay_time = 0.6f, + float32_t shimmer_frequency = 2.0f, + float32_t shimmer_amplitude = 0.5f, + float32_t decay_time = 2.0f); + + virtual ~ShimmerReverb(); + + virtual void process(float32_t* left_input, float32_t* right_input, float32_t* left_output, float32_t* right_output, size_t nSamples) override; + + void setLeftDelayTime(float32_t delay_time_L); + inline float32_t getLeftDelayTime() const; + + void setRightDelayTime(float32_t delay_time_R); + inline float32_t getRightDelayTime() const; + + void setShimmerFrequency(float32_t frequency); + inline float32_t getShimmerFrequency() const; + + void setShimmerAmplitude(float32_t amplitude); + inline float32_t getShimmerAmplitude() const; + + void setDecayTime(float32_t decay_time); + inline float32_t getDecayTime() const; + +private: + const unsigned DelayLineLength; + float32_t* delay_line_L_; + float32_t* delay_line_R_; + + // 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 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 +}; \ No newline at end of file diff --git a/src/fx_tape_delay.cpp b/src/fx_tape_delay.cpp new file mode 100644 index 0000000..faccabe --- /dev/null +++ b/src/fx_tape_delay.cpp @@ -0,0 +1,117 @@ +#include "fx_tape_delay.h" + +#include +#include + +TapeDelay::TapeDelay(const float32_t sampling_rate, float32_t default_delay_time, float32_t default_flutter_level, float32_t default_feedback_level) : + FX(sampling_rate), + MaxSampleDelayTime(sampling_rate * MAX_DELAY_TIME), + left_read_pos_(0), + right_read_pos_(0) +{ + this->left_buffer_ = new float32_t[this->MaxSampleDelayTime]; + this->right_buffer_ = new float32_t[this->MaxSampleDelayTime]; + + this->setDelayTime(default_delay_time); + this->setFlutterLevel(default_flutter_level); + this->setFeedbakLevel(default_feedback_level); +} + +TapeDelay::~TapeDelay() +{ + delete[] this->left_buffer_; + delete[] this->right_buffer_; +} + +void TapeDelay::process(float32_t* left_input, float32_t* right_input, float32_t* left_output, float32_t* right_output, size_t nSamples) +{ + for(size_t i = 0; i < nSamples; ++i) + { + // calculate the fluttered delay time + float32_t fluttered_delay_time = this->getDelayTime() + this->getFlutteredDelayTime(); + + // Calculate write positions + int left_write_pos = this->left_read_pos_ - static_cast(fluttered_delay_time); + while(left_write_pos < 0) + { + left_write_pos += this->MaxSampleDelayTime; + } + + int right_write_pos = this->right_read_pos_ - static_cast(fluttered_delay_time); + while(right_write_pos < 0) + { + right_write_pos += this->MaxSampleDelayTime; + } + + // Write input to delay buffers + this->left_buffer_[left_write_pos] = *left_input; + this->right_buffer_[right_write_pos] = *right_input; + + // Read from delay buffers and apply feedback + *left_output = this->left_buffer_[this->left_read_pos_]; + *right_output = this->right_buffer_[this->right_read_pos_]; + this->left_buffer_[left_write_pos] += *left_output * this->getFeedbackLevel(); + this->right_buffer_[right_write_pos] += *right_output * this->getFeedbackLevel(); + + // Increment read positions + ++this->left_read_pos_; + if(this->left_read_pos_ >= this->MaxSampleDelayTime) + { + this->left_read_pos_ -= this->MaxSampleDelayTime; + } + ++this->right_read_pos_; + if(this->right_read_pos_ >= this->MaxSampleDelayTime) + { + this->right_read_pos_ -= this->MaxSampleDelayTime; + } + + // Move to next input sample + ++left_input; + ++right_input; + + // Move to next output sample + ++left_output; + ++right_output; + } +} + +void TapeDelay::setDelayTime(float32_t delay_time) +{ + this->delay_time_ = constrain(delay_time, 0.0f, 1.0f); +} + +inline float32_t TapeDelay::getDelayTime() const +{ + return this->delay_time_; +} + +void TapeDelay::setFlutterLevel(float32_t flutter_level) +{ + this->flutter_level_ = constrain(flutter_level, 0.0f, 1.0f); +} + +inline float32_t TapeDelay::getFlutterLevel() const +{ + return this->flutter_level_; +} + +void TapeDelay::setFeedbakLevel(float32_t feedback) +{ + this->feedback_ = constrain(feedback, 0.0, 1.0); +} + +inline float32_t TapeDelay::getFeedbackLevel() const +{ + return this->feedback_; +} + +inline 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()); + + // Scale and bias the random number to the desired flutter range + return r * this->getFlutterLevel(); +} diff --git a/src/fx_tape_delay.h b/src/fx_tape_delay.h new file mode 100644 index 0000000..a8caf89 --- /dev/null +++ b/src/fx_tape_delay.h @@ -0,0 +1,60 @@ +// 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_tape_delay.h +// +// Stereo Tape Delay proposed in the context of the MiniDexed project +// +#pragma once + +#include "fx.h" + +#include + +#define MAX_DELAY_TIME 2 + +class TapeDelay : public FX +{ + DISALLOW_COPY_AND_ASSIGN(TapeDelay); + +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); + virtual ~TapeDelay(); + + virtual void process(float32_t* left_input, float32_t* right_input, float32_t* left_output, float32_t* right_output, size_t nSamples) override; + + void setDelayTime(float32_t delay_time); + inline float32_t getDelayTime() const; + + void setFlutterLevel(float32_t flutter_level); + inline float32_t getFlutterLevel() const; + + void setFeedbakLevel(float32_t feedback); + inline float32_t getFeedbackLevel() const; + +private: + inline float32_t getFlutteredDelayTime(); + +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 delay_time_; + float32_t flutter_level_; + float32_t feedback_; + std::mt19937 random_generator_; +};