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_;
+};