diff --git a/Makefile b/Makefile index 314dbffd..b5412355 100644 --- a/Makefile +++ b/Makefile @@ -19,6 +19,7 @@ compressor \ crossfade \ dcblock \ decimator \ +diffuser \ drip \ fm2 \ fold \ diff --git a/daisysp.h b/daisysp.h index 61e3c47c..f932a210 100644 --- a/daisysp.h +++ b/daisysp.h @@ -34,6 +34,7 @@ #include "modules/dcblock.h" #include "modules/decimator.h" #include "modules/delayline.h" +#include "modules/diffuser.h" #include "modules/drip.h" #include "modules/dsp.h" #include "modules/dust.h" @@ -41,6 +42,7 @@ #include "modules/fold.h" #include "modules/formantosc.h" #include "modules/fractal_noise.h" +#include "modules/fx_engine.h" #include "modules/grainlet.h" #include "modules/harmonic_osc.h" #include "modules/hihat.h" diff --git a/modules/diffuser.cpp b/modules/diffuser.cpp new file mode 100644 index 00000000..68335888 --- /dev/null +++ b/modules/diffuser.cpp @@ -0,0 +1,64 @@ +#include "dsp.h" +#include "diffuser.h" +#include + +using namespace daisysp; + +void Diffuser::Init() +{ + engine_.Init(buffer_); + engine_.SetLFOFrequency(LFO_1, 0.3f / 48000.0f); + lp_decay_ = 0.0f; + + SetAmount(1.f); + SetTime(.5f); +} + +float Diffuser::Process(float in) +{ + typedef E::Reserve< + 126, + E::Reserve< + 180, + E::Reserve< + 269, + E::Reserve< + 444, + E::Reserve<1653, E::Reserve<2010, E::Reserve<3411>>>>>>> + Memory; + E::DelayLine ap1; + E::DelayLine ap2; + E::DelayLine ap3; + E::DelayLine ap4; + E::DelayLine dapa; + E::DelayLine dapb; + E::DelayLine del; + E::Context c; + const float kap = 0.625f; + const float klp = 0.75f; + float lp = lp_decay_; + + float wet; + engine_.Start(&c); + c.Read(in); + c.Read(ap1 TAIL, kap); + c.WriteAllPass(ap1, -kap); + c.Read(ap2 TAIL, kap); + c.WriteAllPass(ap2, -kap); + c.Read(ap3 TAIL, kap); + c.WriteAllPass(ap3, -kap); + c.Interpolate(ap4, 400.0f, LFO_1, 43.0f, kap); + c.WriteAllPass(ap4, -kap); + c.Interpolate(del, 3070.0f, LFO_1, 340.0f, rt_); + c.Lp(lp, klp); + c.Read(dapa TAIL, -kap); + c.WriteAllPass(dapa, kap); + c.Read(dapb TAIL, kap); + c.WriteAllPass(dapb, -kap); + c.Write(del, 2.0f); + c.Write(wet, 0.0f); + + lp_decay_ = lp; + + return .1f * amount_ * (wet - in); +} \ No newline at end of file diff --git a/modules/diffuser.h b/modules/diffuser.h new file mode 100644 index 00000000..66649869 --- /dev/null +++ b/modules/diffuser.h @@ -0,0 +1,60 @@ +#pragma once +#ifndef DSY_DIFFUSER_H +#define DSY_DIFFUSER_H + +#include "modules/fx_engine.h" + +#include +#ifdef __cplusplus + +/** @file diffuser.h */ + +namespace daisysp +{ +/** + @brief Granular Diffuser + @author Ben Sergentanis + @date Jan 2021 + Ported from pichenettes/eurorack/plaits/dsp/fx/diffuser.h \n + to an independent module. \n + Original code written by Emilie Gillet in 2016. \n +*/ +class Diffuser +{ + public: + Diffuser() {} + ~Diffuser() {} + + /** Initialize the module + */ + void Init(); + + /** Reset the reverb */ + void Clear() { engine_.Clear(); } + + /** Get the next sample + \param in Next sample to process + */ + float Process(float in); + + /** Set the reverb tail length + \param rt Works 0-1. + */ + void SetTime(float rt) { rt_ = fclamp(rt, 0.f, 1.f); } + + /** Set the amount of reverb. + \param amount Works 0-1. + */ + void SetAmount(float amount) { amount_ = fclamp(amount, 0.f, 1.f); }; + + private: + float rt_, amount_; + + uint16_t buffer_[8192]; + typedef FxEngine<8192, FORMAT_12_BIT> E; + E engine_; + float lp_decay_; +}; +} // namespace daisysp +#endif +#endif \ No newline at end of file diff --git a/modules/fx_engine.h b/modules/fx_engine.h new file mode 100644 index 00000000..e18c2a59 --- /dev/null +++ b/modules/fx_engine.h @@ -0,0 +1,398 @@ +#pragma once +#ifndef DSY_FX_ENGINE_H +#define DSY_FX_ENGINE_H + +#include +#include +#ifdef __cplusplus + +#define JOIN(lhs, rhs) JOIN_1(lhs, rhs) +#define JOIN_1(lhs, rhs) JOIN_2(lhs, rhs) +#define JOIN_2(lhs, rhs) lhs##rhs + +#define STATIC_ASSERT(expression, message) \ + struct JOIN(__static_assertion_at_line_, __LINE__) \ + { \ + impl::StaticAssertion((expression))> \ + JOIN(JOIN(JOIN(STATIC_ASSERTION_FAILED_AT_LINE_, __LINE__), _), \ + message); \ + }; + +/** @file fx_engine.h */ +namespace impl +{ +template +struct StaticAssertion; + +template <> +struct StaticAssertion +{ +}; // StaticAssertion + +template +struct StaticAssertionTest +{ +}; // StaticAssertionTest + +} // namespace impl + +namespace daisysp +{ +#define TAIL , -1 + +enum Format +{ + FORMAT_12_BIT, + FORMAT_16_BIT, + FORMAT_32_BIT +}; + +enum LFOIndex +{ + LFO_1, + LFO_2 +}; + +static inline int32_t Clip16(int32_t x) +{ + if(x < -32768) + { + return -32768; + } + else if(x > 32767) + { + return 32767; + } + else + { + return x; + } +} + +template +struct DataType +{ +}; + +template <> +struct DataType +{ + typedef uint16_t T; + + static inline float Decompress(T value) + { + return static_cast(static_cast(value)) / 4096.0f; + } + + static inline T Compress(float value) + { + return static_cast( + Clip16(static_cast(value * 4096.0f))); + } +}; + +template <> +struct DataType +{ + typedef uint16_t T; + + static inline float Decompress(T value) + { + return static_cast(static_cast(value)) / 32768.0f; + } + + static inline T Compress(float value) + { + return static_cast( + Clip16(static_cast(value * 32768.0f))); + } +}; + +template <> +struct DataType +{ + typedef float T; + + static inline float Decompress(T value) + { + return value; + ; + } + + static inline T Compress(float value) { return value; } +}; + +/** + @brief Base Class for building reverbs + @author Ben Sergentanis + @date Jan 2021 + Ported from pichenettes/eurorack/plaits/dsp/fx/fx_engine.h \n + to an independent module. \n + Original code written by Emilie Gillet in 2014. \n +*/ +template +class FxEngine +{ + public: + typedef typename DataType::T T; + FxEngine() {} + ~FxEngine() {} + + void Init(T* buffer) + { + buffer_ = buffer; + lfo_phase_[0] = 0.f; + lfo_phase_[1] = 0.f; + + lfo_freq_[0] = 0.f; + lfo_freq_[1] = 0.f; + } + + void Clear() + { + std::fill(&buffer_[0], &buffer_[size], 0); + write_ptr_ = 0; + } + + struct Empty + { + }; + + template + struct Reserve + { + typedef T Tail; + enum + { + length = l + }; + }; + + template + struct DelayLine + { + enum + { + length = DelayLine::length, + base = DelayLine::base + + DelayLine::length + 1 + }; + }; + + template + struct DelayLine + { + enum + { + length = Memory::length, + base = 0 + }; + }; + + class Context + { + friend class FxEngine; + + public: + Context() {} + ~Context() {} + + inline void Load(float value) { accumulator_ = value; } + + inline void Read(float value, float scale) + { + accumulator_ += value * scale; + } + + inline void Read(float value) { accumulator_ += value; } + + inline void Write(float& value) { value = accumulator_; } + + inline void Write(float& value, float scale) + { + value = accumulator_; + accumulator_ *= scale; + } + + template + inline void Write(D& d, int32_t offset, float scale) + { + STATIC_ASSERT(D::base + D::length <= size, delay_memory_full); + T w = DataType::Compress(accumulator_); + if(offset == -1) + { + buffer_[(write_ptr_ + D::base + D::length - 1) & MASK] = w; + } + else + { + buffer_[(write_ptr_ + D::base + offset) & MASK] = w; + } + accumulator_ *= scale; + } + + template + inline void Write(D& d, float scale) + { + Write(d, 0, scale); + } + + template + inline void WriteAllPass(D& d, int32_t offset, float scale) + { + Write(d, offset, scale); + accumulator_ += previous_read_; + } + + template + inline void WriteAllPass(D& d, float scale) + { + WriteAllPass(d, 0, scale); + } + + template + inline void Read(D& d, int32_t offset, float scale) + { + STATIC_ASSERT(D::base + D::length <= size, delay_memory_full); + T r; + if(offset == -1) + { + r = buffer_[(write_ptr_ + D::base + D::length - 1) & MASK]; + } + else + { + r = buffer_[(write_ptr_ + D::base + offset) & MASK]; + } + float r_f = DataType::Decompress(r); + previous_read_ = r_f; + accumulator_ += r_f * scale; + } + + template + inline void Read(D& d, float scale) + { + Read(d, 0, scale); + } + + inline void Lp(float& state, float coefficient) + { + state += coefficient * (accumulator_ - state); + accumulator_ = state; + } + + inline void Hp(float& state, float coefficient) + { + state += coefficient * (accumulator_ - state); + accumulator_ -= state; + } + + template + inline void Interpolate(D& d, float offset, float scale) + { + STATIC_ASSERT(D::base + D::length <= size, delay_memory_full); + + int32_t offset_integral = static_cast(offset); + float offset_fractional + = offset - static_cast(offset_integral); + + float a = DataType::Decompress( + buffer_[(write_ptr_ + offset_integral + D::base) & MASK]); + float b = DataType::Decompress( + buffer_[(write_ptr_ + offset_integral + D::base + 1) & MASK]); + float x = a + (b - a) * offset_fractional; + previous_read_ = x; + accumulator_ += x * scale; + } + + template + inline void Interpolate(D& d, + float offset, + LFOIndex index, + float amplitude, + float scale) + { + STATIC_ASSERT(D::base + D::length <= size, delay_memory_full); + lfo_phase_[index] += lfo_freq_[index]; + lfo_phase_[index] = lfo_phase_[index] >= 1.f + ? lfo_phase_[index] - 1.f + : lfo_phase_[index]; + + offset += amplitude * cosf(lfo_phase_[index] * TWOPI_F); + + int32_t offset_integral = static_cast(offset); + float offset_fractional + = offset - static_cast(offset_integral); + + float a = DataType::Decompress( + buffer_[(write_ptr_ + offset_integral + D::base) & MASK]); + float b = DataType::Decompress( + buffer_[(write_ptr_ + offset_integral + D::base + 1) & MASK]); + float x = a + (b - a) * offset_fractional; + previous_read_ = x; + accumulator_ += x * scale; + } + + private: + float accumulator_; + float previous_read_; + float lfo_value_[2]; + T* buffer_; + int32_t write_ptr_; + + float* lfo_phase_; + float* lfo_freq_; + }; + + inline void SetLFOFrequency(LFOIndex index, float frequency) + { + lfo_freq_[index] = frequency; //* 32.f ? + } + + inline void Start(Context* c) + { + --write_ptr_; + if(write_ptr_ < 0) + { + write_ptr_ += size; + } + c->accumulator_ = 0.0f; + c->previous_read_ = 0.0f; + c->buffer_ = buffer_; + c->write_ptr_ = write_ptr_; + + c->lfo_phase_ = lfo_phase_; + c->lfo_freq_ = lfo_freq_; + + if((write_ptr_ & 31) == 0) + { + lfo_phase_[0] += lfo_freq_[0]; + lfo_phase_[0] + = lfo_phase_[0] >= 1.f ? lfo_phase_[0] - 1.f : lfo_phase_[0]; + + lfo_phase_[1] += lfo_freq_[1]; + lfo_phase_[1] + = lfo_phase_[1] >= 1.f ? lfo_phase_[1] - 1.f : lfo_phase_[1]; + + c->lfo_value_[0] = cosf(TWOPI_F * lfo_phase_[0]); + c->lfo_value_[1] = cosf(TWOPI_F * lfo_phase_[1]); + } + else + { + c->lfo_value_[0] = cosf(TWOPI_F * lfo_phase_[0]); + c->lfo_value_[1] = cosf(TWOPI_F * lfo_phase_[1]); + } + } + + private: + enum + { + MASK = size - 1 + }; + + int32_t write_ptr_; + T* buffer_; + float lfo_phase_[2]; + float lfo_freq_[2]; +}; +} // namespace daisysp +#endif +#endif \ No newline at end of file