Adding first set of FX

pull/409/head
Vincent 2 years ago
parent d42b8afc58
commit 6ca3e3d340
  1. 1
      .gitignore
  2. 4
      src/Makefile
  3. 24
      src/fx.cpp
  4. 54
      src/fx.h
  5. 58
      src/fx_phaser.cpp
  6. 62
      src/fx_phaser.h
  7. 75
      src/fx_rack.cpp
  8. 62
      src/fx_rack.h
  9. 134
      src/fx_shimmer_reverb.cpp
  10. 72
      src/fx_shimmer_reverb.h
  11. 117
      src/fx_tape_delay.cpp
  12. 60
      src/fx_tape_delay.h

1
.gitignore vendored

@ -45,3 +45,4 @@ sdcard
# Editor related files
*.swp
*.swo
.vscode/

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

@ -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()
{
}

@ -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 <http://www.gnu.org/licenses/>.
//
// fx.h
//
// Base class for Stereo audio effects proposed in the context of the MiniDexed project
//
#pragma once
#include <stdint.h>
#include <arm_math.h>
#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;
};

@ -0,0 +1,58 @@
#include "fx_phaser.h"
#include <cmath>
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_;
}

@ -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 <http://www.gnu.org/licenses/>.
//
// 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];
};

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

@ -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 <http://www.gnu.org/licenses/>.
//
// 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 <vector>
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<FXUnit*> 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_;
};

@ -0,0 +1,134 @@
#include "fx_shimmer_reverb.h"
#include <cmath>
#include <algorithm>
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<unsigned>(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_;
}

@ -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 <http://www.gnu.org/licenses/>.
//
//
// 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
};

@ -0,0 +1,117 @@
#include "fx_tape_delay.h"
#include <cmath>
#include <algorithm>
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<int>(fluttered_delay_time);
while(left_write_pos < 0)
{
left_write_pos += this->MaxSampleDelayTime;
}
int right_write_pos = this->right_read_pos_ - static_cast<int>(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<float32_t>(this->random_generator_()) /
static_cast<float32_t>(this->random_generator_.max());
// Scale and bias the random number to the desired flutter range
return r * this->getFlutterLevel();
}

@ -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 <http://www.gnu.org/licenses/>.
//
//
// fx_tape_delay.h
//
// Stereo Tape Delay proposed in the context of the MiniDexed project
//
#pragma once
#include "fx.h"
#include <random>
#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_;
};
Loading…
Cancel
Save