diff --git a/plugin/Makefile b/plugin/Makefile index 2797adf..47b51bc 100644 --- a/plugin/Makefile +++ b/plugin/Makefile @@ -15,6 +15,8 @@ FILES_DSP = \ voice.cpp \ tables.cpp \ parameters.cpp \ + svf.cpp \ + chorus.cpp \ peacock.cpp FILES_UI = \ diff --git a/plugin/chorus.cpp b/plugin/chorus.cpp new file mode 100644 index 0000000..682843d --- /dev/null +++ b/plugin/chorus.cpp @@ -0,0 +1,129 @@ +/* + sonnenlicht poly ensemble + + Copyright 2025 Gordon JC Pearce + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION + OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ + +#include "chorus.hpp" + +#include +#include + +#include + +//extern double sampleRate; +//extern uint32_t bufferSize; + +double sampleRate = 48000; +uint32_t bufferSize = 1024; +Chorus::Chorus() { // no parameters, programs, or states + + lpfOut1 = new float[bufferSize]; + lpfOut2 = new float[bufferSize]; + ram = new float[DELAYSIZE]; // probably needs to be calculated based on sample rate + + fastPhase = 0; + slowPhase = 0; + + postFilter1l = new SVF(POSTCUTOFF, .546); + postFilter2l = new SVF(POSTCUTOFF, 1.324); + postFilter1r = new SVF(POSTCUTOFF, .546); + postFilter2r = new SVF(POSTCUTOFF, 1.324); + + // lfo values taken from a rough simulation + fastOmega = 6.283 * 5.7 / sampleRate; // approximate, can be adjusted + slowOmega = 6.283 * 0.7 / sampleRate; // again approximate + + // zero out the delay buffer + memset(ram, 0, sizeof(float) * DELAYSIZE); + memset(lpfOut1, 0, sizeof(float) * bufferSize); + memset(lpfOut2, 0, sizeof(float) * bufferSize); +} + +Chorus::~Chorus() { + delete lpfOut1; + delete lpfOut2; + delete ram; + delete postFilter1l; + delete postFilter2l; + delete postFilter1r; + delete postFilter2r; +} + +void Chorus::run(const float *input, float **outputs, uint32_t frames) { + // actual effects here + + // now run the DSP + float out0 = 0, out120 = 0, out240 = 0, s0 = 0, s1 = 0; + float lfoMod, dly1, frac; + uint16_t tap, delay; + + for (uint32_t i = 0; i < frames; i++) { + // run a step of LFO + fastPhase += fastOmega; + if (fastPhase > 6.283) fastPhase -= 6.283; + slowPhase += slowOmega; + if (slowPhase > 6.283) slowPhase -= 6.283; + + ram[delayptr] = input[i]; + +#define BASE 0.05 +#define AMT 0.00175 + + // 0 degree delay line + lfoMod = 0.203 * sin(fastPhase) + 0.835 * sin(slowPhase); + dly1 = (BASE + (AMT * lfoMod)) * sampleRate; + delay = (int)dly1; + frac = dly1 - delay; + + tap = delayptr - delay; + s1 = ram[(tap - 1) & 0x3ff]; + s0 = ram[tap & 0x3ff]; + out0 = ((s1 - s0) * frac) + s0; + + // 120 degree delay line + lfoMod = 0.248 * sin(fastPhase + 2.09) + 0.745 * sin(slowPhase + 2.09); + dly1 = (BASE + (AMT * lfoMod)) * sampleRate; + delay = (int)dly1; + frac = dly1 - delay; + + tap = delayptr - delay; + s1 = ram[(tap - 1) & 0x3ff]; + s0 = ram[tap & 0x3ff]; + out120 = ((s1 - s0) * frac) + s0; + + // 240 degree delay line + lfoMod = 0.252 * sin(fastPhase + 4.18) + 0.809 * sin(slowPhase + 4.18); + dly1 = (BASE + (AMT * lfoMod)) * sampleRate; + delay = (int)dly1; + frac = dly1 - delay; + + tap = delayptr - delay; + s1 = ram[(tap - 1) & 0x3ff]; + s0 = ram[tap & 0x3ff]; + out240 = ((s1 - s0) * frac) + s0; + + lpfOut1[i] = (out0 + (out120 * 0.66) + (out240 * 0.33)); + lpfOut2[i] = (out0 + (out120 * 0.33) + (out240 * 0.66)); + + delayptr++; + delayptr &= 0x3ff; + } + + postFilter1l->runSVF(lpfOut1, lpfOut1, frames); + postFilter2l->runSVF(lpfOut1, outputs[0], frames); + postFilter1r->runSVF(lpfOut2, lpfOut2, frames); + postFilter2r->runSVF(lpfOut2, outputs[1], frames); +} diff --git a/plugin/chorus.hpp b/plugin/chorus.hpp new file mode 100644 index 0000000..657de58 --- /dev/null +++ b/plugin/chorus.hpp @@ -0,0 +1,48 @@ +/* + sonnenlicht poly ensemble + + Copyright 2025 Gordon JC Pearce + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION + OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ + +#ifndef Chorus_HPP +#define Chorus_HPP + +#include "svf.hpp" + +// total size of delay line buffer +#define DELAYSIZE 1028 + +#define POSTCUTOFF 10000 + +class Chorus { + public: + Chorus(); + ~Chorus(); + void run(const float *input, float **outputs, uint32_t frames); + + private: + double fastPhase, fastOmega; + double slowPhase, slowOmega; + double fastLfo, slowLfo; + + uint16_t delayptr; + + float *ram; + float *lpfIn; + float *lpfOut1, *lpfOut2; + + SVF *postFilter1l, *postFilter2l, *postFilter1r, *postFilter2r; +}; +#endif diff --git a/plugin/peacock.cpp b/plugin/peacock.cpp index 174b0ca..173ee25 100644 --- a/plugin/peacock.cpp +++ b/plugin/peacock.cpp @@ -26,11 +26,14 @@ Peacock::Peacock() : Plugin(parameterCount, 0, 0) { m = new Module; ic1 = new Assigner; ic1->voice = voice; + chorus = new Chorus; } Peacock::~Peacock() { free(m); + free(ic1); + free(chorus); } void Peacock::initAudioPort(bool input, uint32_t index, AudioPort& port) { @@ -93,7 +96,9 @@ void Peacock::run(const float**, float** outputs, uint32_t frames, const MidiEve // now we've assembled a full chunk of audio // we'd apply the highpass filter and chorus here // for now just copy left to right - memcpy(outputs[1], outputs[0], sizeof(float) * frames); + //memcpy(outputs[1], outputs[0], sizeof(float) * frames); + chorus->run(outputs[0], outputs, frames); + //outputs[0][0]=1; } diff --git a/plugin/peacock.hpp b/plugin/peacock.hpp index a3a04cb..72654ed 100644 --- a/plugin/peacock.hpp +++ b/plugin/peacock.hpp @@ -22,6 +22,8 @@ #include "DistrhoPlugin.hpp" #include "assigner.hpp" #include "module.hpp" +#include "chorus.hpp" + START_NAMESPACE_DISTRHO class Peacock : public Plugin { @@ -55,6 +57,7 @@ class Peacock : public Plugin { private: Assigner* ic1; Module* m; + Chorus* chorus; uint32_t sampleRate; diff --git a/plugin/svf.cpp b/plugin/svf.cpp new file mode 100644 index 0000000..14c3684 --- /dev/null +++ b/plugin/svf.cpp @@ -0,0 +1,55 @@ +/* + State Variable Filter + + Copyright 2025 Gordon JC Pearce + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION + OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ + +#include "svf.hpp" + +#include + +#include + +SVF::SVF(float cutoff, float Q) { + // zero out all values + z1 = 0; + z2 = 0; + setCutoff(cutoff, Q); +} + +void SVF::setCutoff(float cutoff, float Q) { + float F = cutoff / sampleRate; + float w = 2 * tan(3.14159 * F); + float a = w / Q; + float b = w * w; + + // "corrected" SVF params, per Fons Adriaensen + c1 = (a + b) / (1 + a / 2 + b / 4); + c2 = b / (a + b); + d0 = c1 * c2 / 4; +} + +void SVF::runSVF(const float *input, float *output, uint32_t frames) { + float x; + + for (uint32_t i = 0; i < frames; i++) { + // lowpass filter + x = input[i] - z1 - z2; + z2 += c2 * z1; + z1 += c1 * x; + output[i] = d0 * x + z2; + } + // printf("%f\n", x); +} \ No newline at end of file diff --git a/plugin/svf.hpp b/plugin/svf.hpp new file mode 100644 index 0000000..cc05f72 --- /dev/null +++ b/plugin/svf.hpp @@ -0,0 +1,39 @@ +/* + State Variable Filter + + Copyright 2025 Gordon JC Pearce + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION + OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ + +#ifndef SVF_HPP +#define SVF_HPP + +#include + +extern double sampleRate; + +class SVF { + public: + SVF(float cutoff=1000, float Q=0.707); + void setCutoff(float cutoff, float Q); + + void runSVF(const float *input, float *output, uint32_t frames); + + protected: + private: + float c1, c2, d0; + float z1, z2; +}; + +#endif