diff --git a/src/Makefile b/src/Makefile index 66487bd..eed75d3 100644 --- a/src/Makefile +++ b/src/Makefile @@ -24,6 +24,8 @@ OBJS = main.o kernel.o minidexed.o config.o userinterface.o uimenu.o \ effect_audio/rkrlv2/EffectLFO.o \ effect_audio/rkrlv2/Phaser.o \ effect_audio/rkrlv2/APhaser.o \ + effect_audio/rkrlv2/delayline.o \ + effect_audio/rkrlv2/Chorus.o \ effect_midi/midi_arp.o \ effect_midi/modarpeggiator/common/clock.o \ effect_midi/modarpeggiator/common/midiHandler.o \ diff --git a/src/effect_audio/effect_flanger.h b/src/effect_audio/effect_flanger.h new file mode 100644 index 0000000..73353df --- /dev/null +++ b/src/effect_audio/effect_flanger.h @@ -0,0 +1,185 @@ +/* + * Flanger / Chorus Port + * Ported from https://github.com/ssj71/rkrlv2 + * Ported from https://github.com/zynaddsubfx/zynaddsubfx + * + * Javier Nonis (https://github.com/jnonis) - 2024 + */ +#ifndef _EFFECT_FLANGER_H +#define _EFFECT_FLANGER_H + +#include "effect_base.h" +#include "rkrlv2/Chorus.h" + +class AudioEffectFlanger : public AudioEffect +{ +public: + // ID must be unique for each AudioEffect + static const unsigned ID = 12; + static constexpr const char* NAME = "Flanger"; + + enum Param + { + BYPASS, + MIX, + PAN, + FL_FREQ, + FL_RND, + TYPE, + STDL, + FL_DEPTH, + DELAY, + FB, + LRCR, + MODE, + SUB, + AWESOME, + UNKNOWN + }; + + AudioEffectFlanger(float32_t samplerate) : AudioEffect(samplerate) + { + this->chorus = new RKRChorus(0, 0, (double) samplerate); + this->init_params = true; + + this->chorus->setpreset(5); + } + + virtual ~AudioEffectFlanger() + { + delete this->chorus; + } + + virtual unsigned getId() + { + return AudioEffectFlanger::ID; + } + + virtual std::string getName() + { + return AudioEffectFlanger::NAME; + } + + virtual void initializeSendFX() + { + this->setParameter(AudioEffectFlanger::Param::MIX, 127); + } + + virtual void setParameter(unsigned param, unsigned value) + { + switch (param) + { + case AudioEffectFlanger::Param::BYPASS: + this->setBypass(value == 1); + this->chorus->cleanup(); + break; + case AudioEffectFlanger::Param::MIX: + this->chorus->changepar(0, value); + break; + case AudioEffectFlanger::Param::PAN: + this->chorus->changepar(1, value); + break; + case AudioEffectFlanger::Param::FL_FREQ: + this->chorus->changepar(2, value); + break; + case AudioEffectFlanger::Param::FL_RND: + this->chorus->changepar(3, value); + break; + case AudioEffectFlanger::Param::TYPE: + this->chorus->changepar(4, value); + break; + case AudioEffectFlanger::Param::STDL: + this->chorus->changepar(5, value); + break; + case AudioEffectFlanger::Param::FL_DEPTH: + this->chorus->changepar(6, value); + break; + case AudioEffectFlanger::Param::DELAY: + this->chorus->changepar(7, value); + break; + case AudioEffectFlanger::Param::FB: + this->chorus->changepar(8, value); + break; + case AudioEffectFlanger::Param::LRCR: + this->chorus->changepar(9, value); + break; + case AudioEffectFlanger::Param::MODE: + this->chorus->changepar(10, value); + break; + case AudioEffectFlanger::Param::SUB: + this->chorus->changepar(11, value); + break; + case AudioEffectFlanger::Param::AWESOME: + this->chorus->changepar(12, value); + break; + default: + break; + } + } + + virtual unsigned getParameter(unsigned param) + { + switch (param) + { + case AudioEffectFlanger::Param::BYPASS: + return this->getBypass() ? 1 : 0; + case AudioEffectFlanger::Param::MIX: + return this->chorus->getpar(0); + case AudioEffectFlanger::Param::PAN: + return this->chorus->getpar(1); + case AudioEffectFlanger::Param::FL_FREQ: + return this->chorus->getpar(2); + case AudioEffectFlanger::Param::FL_RND: + return this->chorus->getpar(3); + case AudioEffectFlanger::Param::TYPE: + return this->chorus->getpar(4); + case AudioEffectFlanger::Param::STDL: + return this->chorus->getpar(5); + case AudioEffectFlanger::Param::FL_DEPTH: + return this->chorus->getpar(6); + case AudioEffectFlanger::Param::DELAY: + return this->chorus->getpar(7); + case AudioEffectFlanger::Param::FB: + return this->chorus->getpar(8); + case AudioEffectFlanger::Param::LRCR: + return this->chorus->getpar(9); + case AudioEffectFlanger::Param::MODE: + return this->chorus->getpar(10); + case AudioEffectFlanger::Param::SUB: + return this->chorus->getpar(11); + case AudioEffectFlanger::Param::AWESOME: + return this->chorus->getpar(12); + default: + return 0; + } + } + +protected: + virtual size_t getParametersSize() + { + return AudioEffectFlanger::Param::UNKNOWN; + } + + virtual void doProcess(const float32_t* inblockL, const float32_t* inblockR, float32_t* outblockL, float32_t* outblockR, uint16_t len) + { + // LFO effects require period be set before setting other params + if(this->init_params) + { + this->chorus->PERIOD = len; + this->init_params = false; // so we only do this once + } + + // now set out ports and global period size + this->chorus->efxoutl = outblockL; + this->chorus->efxoutr = outblockR; + + //now run + this->chorus->out((float*) inblockL, (float*) inblockR, len); + } + +private: + RKRChorus* chorus; + bool init_params; +}; + +#endif // _EFFECT_FLANGER_H \ No newline at end of file diff --git a/src/effect_audio/rkrlv2/Chorus.cpp b/src/effect_audio/rkrlv2/Chorus.cpp new file mode 100644 index 0000000..aab7bc3 --- /dev/null +++ b/src/effect_audio/rkrlv2/Chorus.cpp @@ -0,0 +1,402 @@ +/* + ZynAddSubFX - a software synthesizer + + Chorus.C - Chorus and Flange effects + Copyright (C) 2002-2005 Nasca Octavian Paul + Author: Nasca Octavian Paul + + Modified for rakarrack by Josep Andreu + + This program is free software; you can redistribute it and/or modify + it under the terms of version 2 of the GNU General Public License + as published by the Free Software Foundation. + + 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 (version 2) for more details. + + You should have received a copy of the GNU General Public License (version 2) + along with this program; if not, write to the Free Software Foundation, + Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +*/ + +#include +#include "Chorus.h" +#include + +RKRChorus::RKRChorus (float * efxoutl_, float * efxoutr_, double sample_rate) +{ + fSAMPLE_RATE = sample_rate; + efxoutl = efxoutl_; + efxoutr = efxoutr_; + dlk = 0; + drk = 0; + maxdelay = lrintf (MAX_CHORUS_DELAY / 1000.0 * (int)sample_rate); + delayl = new float[maxdelay]; + delayr = new float[maxdelay]; + lfo = new EffectLFO(sample_rate); + + float tmp = 0.08f; + ldelay = new delayline(tmp, 2, sample_rate); + rdelay = new delayline(tmp, 2, sample_rate); + ldelay -> set_averaging(0.005f); + rdelay -> set_averaging(0.005f); + ldelay->set_mix( 1.0f ); + rdelay->set_mix( 1.0f ); + + Ppreset = 0; + PERIOD = 256; //make our best guess for the initializing + setpreset (Ppreset); + + oldr = 0.0f; + oldl = 0.0f; + awesome_mode = 0; + + lfo->effectlfoout (&lfol, &lfor); + dl2 = getdelay (lfol); + dr2 = getdelay (lfor); + cleanup (); +}; + +RKRChorus::~RKRChorus () +{ + delete delayl; + delete delayr; + delete ldelay; + delete rdelay; + delete lfo; +}; + +/* + * get the delay value in samples; xlfo is the current lfo value + */ +float RKRChorus::getdelay (float xlfo) +{ + float + result; + if (Pflangemode == 0) { + result = (delay + xlfo * depth) * fSAMPLE_RATE; + } else + result = 0; + + //check if it is too big delay(caused bu errornous setdelay() and setdepth() + if ((result + 0.5) >= maxdelay) { + fprintf (stderr, "%s", + "WARNING: Chorus.C::getdelay(..) too big delay (see setdelay and setdepth funcs.)\n"); + printf ("%f %d\n", result, maxdelay); + result = (float) maxdelay - 1.0f; + }; + return (result); +}; + +/* + * Apply the effect + */ +void +RKRChorus::out (float * smpsl, float * smpsr, uint32_t period) +{ + unsigned int i; + float tmp; + float fPERIOD = period; + float outL, outR; + dl1 = dl2; + dr1 = dr2; + lfo->effectlfoout (&lfol, &lfor); + + float v1, v2; + if (outvolume < 0.5f) + { + v1 = 1.0f; + v2 = outvolume * 2.0f; + } + else + { + v1 = (1.0f - outvolume) * 2.0f; + v2 = 1.0f; + } + + if(awesome_mode) { //use interpolated delay line for better sound + float tmpsub; + + dl2 = delay + lfol * depth; + dr2 = delay + lfor * depth; + if (Poutsub != 0) tmpsub = -1.0f; + else tmpsub = 1.0f; + + for (i = 0; i < period; i++) { + //Left + mdel = (dl1 * (float)(period - i) + dl2 * (float)i) / fPERIOD; + tmp = smpsl[i] + oldl*fb; + outL = tmpsub*ldelay->delay(tmp, mdel, 0, 1, 0); + oldl = outL; + + //Right + mdel = (dr1 * (float)(period - i) + dr2 * (float)i) / fPERIOD; + tmp = smpsr[i] + oldr*fb; + outR = tmpsub*rdelay->delay(tmp, mdel, 0, 1, 0); + oldr = outR; + + outL *= panning; + outR *= (1.0f - panning); + + efxoutl[i] = smpsl[i] * v1 + outL * v2; + efxoutr[i] = smpsr[i] * v1 + outR * v2; + } + + } else { + + dl2 = getdelay (lfol); + dr2 = getdelay (lfor); + for (i = 0; i < period; i++) { + float inl = smpsl[i]; + float inr = smpsr[i]; + //LRcross + float l = inl; + float r = inr; + inl = l * (1.0f - lrcross) + r * lrcross; + inr = r * (1.0f - lrcross) + l * lrcross; + + //Left channel + + //compute the delay in samples using linear interpolation between the lfo delays + mdel = (dl1 * (float)(period - i) + dl2 * (float)i) / fPERIOD; + if (++dlk >= maxdelay) + dlk = 0; + tmp = (float) dlk - mdel + (float)maxdelay * 2.0f; //where should I get the sample from + + F2I (tmp, dlhi); + dlhi %= maxdelay; + + dlhi2 = (dlhi - 1 + maxdelay) % maxdelay; + dllo = 1.0f - fmodf (tmp, 1.0f); + outL = delayl[dlhi2] * dllo + delayl[dlhi] * (1.0f - dllo); + delayl[dlk] = inl + outL * fb; + + //Right channel + + //compute the delay in samples using linear interpolation between the lfo delays + mdel = (dr1 * (float)(period - i) + dr2 * (float)i) / fPERIOD; + if (++drk >= maxdelay) + drk = 0; + tmp = (float)drk - mdel + (float)maxdelay * 2.0f; //where should I get the sample from + + F2I (tmp, dlhi); + dlhi %= maxdelay; + + dlhi2 = (dlhi - 1 + maxdelay) % maxdelay; + dllo = 1.0f - fmodf (tmp, 1.0f); + outR = delayr[dlhi2] * dllo + delayr[dlhi] * (1.0f - dllo); + delayr[dlk] = inr + outR * fb; + + if (Poutsub != 0) { + outL *= -1.0f; + outR *= -1.0f; + } + + outL *= panning; + outR *= (1.0f - panning); + + efxoutl[i] = smpsl[i] * v1 + outL * v2; + efxoutr[i] = smpsr[i] * v1 + outR * v2; + } + } //end awesome_mode test +}; + +/* + * Cleanup the effect + */ +void +RKRChorus::cleanup () +{ + for (int i = 0; i < maxdelay; i++) { + delayl[i] = 0.0; + delayr[i] = 0.0; + }; + +}; + +/* + * Parameter control + */ +void +RKRChorus::setdepth (int Pdepth) +{ + this->Pdepth = Pdepth; + depth = (powf (8.0f, ((float)Pdepth / 127.0f) * 2.0f) - 1.0f) / 1000.0f; //seconds +}; + +void +RKRChorus::setdelay (int Pdelay) +{ + this->Pdelay = Pdelay; + delay = (powf (10.0f, ((float)Pdelay / 127.0f) * 2.0f) - 1.0f) / 1000.0f; //seconds +}; + +void +RKRChorus::setfb (int Pfb) +{ + this->Pfb = Pfb; + fb = ((float)Pfb - 64.0f) / 64.1f; +}; + +void +RKRChorus::setvolume (int Pvolume) +{ + this->Pvolume = Pvolume; + outvolume = (float)Pvolume / 127.0f; +}; + +void +RKRChorus::setpanning (int Ppanning) +{ + this->Ppanning = Ppanning; + panning = ((float)Ppanning +.5f) / 127.0f; +}; + +void +RKRChorus::setlrcross (int Plrcross) +{ + this->Plrcross = Plrcross; + lrcross = (float)Plrcross / 127.0f; +}; + +void +RKRChorus::setpreset (int npreset) +{ + const int PRESET_SIZE = 12; + const int NUM_PRESETS = 10; + int presets[NUM_PRESETS][PRESET_SIZE] = { + //Chorus1 + {64, 64, 33, 0, 0, 90, 40, 85, 64, 119, 0, 0}, + //Chorus2 + {64, 64, 19, 0, 0, 98, 56, 90, 64, 19, 0, 0}, + //Chorus3 + {64, 64, 7, 0, 1, 42, 97, 95, 90, 127, 0, 0}, + //Celeste1 + {64, 64, 1, 0, 0, 42, 115, 18, 90, 127, 0, 0}, + //Celeste2 + {64, 64, 7, 117, 0, 50, 115, 9, 31, 127, 0, 1}, + //Flange1 + {64, 64, 39, 0, 0, 60, 23, 3, 62, 0, 0, 0}, + //Flange2 + {64, 64, 9, 34, 1, 40, 35, 3, 109, 0, 0, 0}, + //Flange3 + {64, 64, 31, 34, 1, 94, 35, 3, 54, 0, 0, 1}, + //Flange4 + {64, 64, 14, 0, 1, 62, 12, 19, 97, 0, 0, 0}, + //Flange5 + {64, 64, 34, 105, 0, 24, 39, 19, 17, 0, 0, 1} + }; + + + for (int n = 0; n < PRESET_SIZE; n++) { + changepar (n, presets[npreset][n]); + } + Ppreset = npreset; +}; + + +void +RKRChorus::changepar (int npar, int value) +{ + switch (npar) { + case 0: + setvolume (value); + break; + case 1: + setpanning (value); + break; + case 2: + lfo->Pfreq = value; + lfo->updateparams (PERIOD); + break; + case 3: + lfo->Prandomness = value; + lfo->updateparams (PERIOD); + break; + case 4: + lfo->PLFOtype = value; + lfo->updateparams (PERIOD); + break; + case 5: + lfo->Pstereo = value; + lfo->updateparams (PERIOD); + break; + case 6: + setdepth (value); + break; + case 7: + setdelay (value); + break; + case 8: + setfb (value); + break; + case 9: + setlrcross (value); + break; + case 10: + if (value > 1) + value = 1; + Pflangemode = value; + break; + case 11: + if (value > 1) + value = 1; + Poutsub = value; + break; + case 12: + awesome_mode = value; + break; + }; +}; + +int +RKRChorus::getpar (int npar) +{ + switch (npar) { + case 0: + return (Pvolume); + break; + case 1: + return (Ppanning); + break; + case 2: + return (lfo->Pfreq); + break; + case 3: + return (lfo->Prandomness); + break; + case 4: + return (lfo->PLFOtype); + break; + case 5: + return (lfo->Pstereo); + break; + case 6: + return (Pdepth); + break; + case 7: + return (Pdelay); + break; + case 8: + return (Pfb); + break; + case 9: + return (Plrcross); + break; + case 10: + return (Pflangemode); + break; + case 11: + return (Poutsub); + break; + case 12: + return (awesome_mode); + break; + default: + return (0); + }; + +}; diff --git a/src/effect_audio/rkrlv2/Chorus.h b/src/effect_audio/rkrlv2/Chorus.h new file mode 100644 index 0000000..f384bcd --- /dev/null +++ b/src/effect_audio/rkrlv2/Chorus.h @@ -0,0 +1,89 @@ +/* + ZynAddSubFX - a software synthesizer + + Chorus.h - Chorus and Flange effects + Copyright (C) 2002-2005 Nasca Octavian Paul + Author: Nasca Octavian Paul + + Modified for rakarrack by Josep Andreu + + This program is free software; you can redistribute it and/or modify + it under the terms of version 2 of the GNU General Public License + as published by the Free Software Foundation. + + 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 (version 2) for more details. + + You should have received a copy of the GNU General Public License (version 2) + along with this program; if not, write to the Free Software Foundation, + Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +*/ + +#ifndef RKR_CHORUS_H +#define RKR_CHORUS_H +#include "global.h" +#include "EffectLFO.h" +#include "delayline.h" + +class RKRChorus +{ + +public: + RKRChorus (float * efxoutl_, float * efxoutr_, double sample_rate); + ~RKRChorus (); + void out (float * smpsl, float * smpsr, uint32_t period); + void setpreset (int npreset); + void changepar (int npar, int value); + int getpar (int npar); + void cleanup (); + + + int Ppreset; + float *efxoutl; + float *efxoutr; + float outvolume; //this is the volume of effect and is public because need it in system effect. The out volume of s + + uint32_t PERIOD; + +private: + //Parametrii Chorus + EffectLFO* lfo; //lfo-ul chorus + int Pvolume; + int Ppanning; + int Pdepth; //the depth of the Chorus(ms) + int Pdelay; //the delay (ms) + int Pfb; //feedback + int Plrcross; //feedback + int Pflangemode; //how the LFO is scaled, to result chorus or flange + int Poutsub; //if I wish to substract the output instead of the adding it + + + //Control Parametrii + void setvolume (int Pvolume); + void setpanning (int Ppanning); + void setdepth (int Pdepth); + void setdelay (int Pdelay); + void setfb (int Pfb); + void setlrcross (int Plrcross); + + //Valorile interne + int maxdelay; + int dlk, drk, dlhi, dlhi2; + int awesome_mode; + + float depth, delay, fb, lrcross, panning, oldr, oldl; + float dl1, dl2, dr1, dr2, lfol, lfor; + float *delayl, *delayr; + float getdelay (float xlfo); + float dllo, mdel; + + class delayline *ldelay, *rdelay; + + float fSAMPLE_RATE; + +}; + +#endif diff --git a/src/effect_audio/rkrlv2/delayline.cpp b/src/effect_audio/rkrlv2/delayline.cpp new file mode 100644 index 0000000..3c7bd6d --- /dev/null +++ b/src/effect_audio/rkrlv2/delayline.cpp @@ -0,0 +1,428 @@ +/* + Author: Ryan BillingV + + This program is free software; you can redistribute it and/or modify + it under the terms of version 3 of the GNU General Public License + as published by the Free Software Foundation. + + 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 (version 2) for more details. + + You should have received a copy of the GNU General Public License (version 2) + along with this program; if not, write to the Free Software Foundation, + Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ +#include "delayline.h" +#include +#include +#include "f_sin.h" + +delayline::delayline(float maxdelay, int maxtaps_, double samplerate) +{ + fSAMPLE_RATE = samplerate; + maxtaps = maxtaps_; + maxtime = fSAMPLE_RATE * maxdelay; + maxdelaysmps = fSAMPLE_RATE * lrintf(ceilf(maxdelay)); + ringbuffer = (float *) malloc(sizeof(float) * maxdelaysmps); + avgtime = (float *) malloc(sizeof(float) * maxtaps); + time = (float *) malloc(sizeof(float) * maxtaps); + xfade = (float *) malloc(sizeof(float) * maxtaps); + cur_smps = (float *) malloc(sizeof(float) * maxtaps); + oldtime = (int *) malloc(sizeof(int) * maxtaps); + newtime = (int *) malloc(sizeof(int) * maxtaps); + crossfade = (int *) malloc(sizeof(int) * maxtaps); + + pstruct = (phasevars *) malloc(sizeof(struct phasevars) * maxtaps); + tapstruct = (tapvars *) malloc(sizeof(struct tapvars) * maxtaps); + + zero_index = 0; + tap = 0; + rvptr = 0; + distance = 0; + + mix = 0.5f; + imix = 0.5f; + + float dt = 1.0f / fSAMPLE_RATE; + alpha = dt / (0.15f + dt); + beta = 1.0f - alpha; //time change smoothing parameters + + cleanup(); +}; + +delayline::~delayline() +{ + free(ringbuffer); + free(avgtime); + free(time); + free(xfade); + free(cur_smps); + free(oldtime); + free(newtime); + free(crossfade); + free(pstruct); + free(tapstruct); +} + +void +delayline::cleanup() +{ + zero_index = 0; + int i, k; + for (i = 0; i < maxdelaysmps; i++) + ringbuffer[i] = 0.0; + for (i = 0; i < maxtaps; i++) { + avgtime[i] = 0.0; + time[i] = 0.0; + for (k = 0; k < 4; k++) { + pstruct[i].yn1[k] = 0.0f; + pstruct[i].xn1[k] = 0.0f; + pstruct[i].gain[k] = 0.0f; + tapstruct[i].lvars[k] = 0.0f; + tapstruct[i].ivars[k] = 0.0f; + tapstruct[i].fracts[k] = 0.0f; + + } + } + + for (i = 0; i < maxtaps; i++) { + avgtime[i] = 0.0f; + newtime[i] = 0; + oldtime[i] = 0; + xfade[i] = 0.0f; + crossfade[i] = 0; + cur_smps[i] = 0.0f; + + } + + set_averaging(0.25f); + +}; + + +float +delayline::delay_simple(float smps, float time_, int tap_, int touch, + int reverse) +{ + int dlytime = 0; + int bufptr = 0; + + if (tap_ >= maxtaps) + tap = 0; + else + tap = tap_; + + time[tap] = fSAMPLE_RATE * time_; //convert to something that can be used as a delay line index + +//Do some checks to keep things in bounds + if (time[tap] > maxtime) + time[tap] = maxtime; + dlytime = lrintf(time[tap]); + + if (crossfade[tap]) { + xfade[tap] += fadetime; + if (xfade[tap] >= 1.0f) { + xfade[tap] = 0.0f; + crossfade[tap] = 0; + oldtime[tap] = newtime[tap]; + newtime[tap] = dlytime; + } + } + + if (crossfade[tap] == 0) { + if (dlytime != oldtime[tap]) { + crossfade[tap] = 1; + xfade[tap] = 0.0f; + oldtime[tap] = newtime[tap]; + newtime[tap] = dlytime; + } + + } + + + + + + dlytime = newtime[tap]; + +//now put in the sample + if (touch) { //make touch zero if you only want to pull samples off the delay line + ringbuffer[zero_index] = smps; + if (--zero_index < 0) + zero_index = maxdelaysmps - 1; + } +//if we want reverse delay +//you need to call this every time to keep the buffers up to date, and it's on a different tap + if (reverse) { + + bufptr = (dlytime + zero_index); //this points to the sample we want to get + if (bufptr >= maxdelaysmps) + bufptr -= maxdelaysmps; + if (++rvptr > maxdelaysmps) + rvptr = 0; + + if (bufptr > zero_index) { + if (rvptr > bufptr) { + rvptr = zero_index; + distance = 0; + } else + distance = rvptr - zero_index; + } else if ((bufptr < zero_index) && (rvptr < zero_index)) { + if (rvptr > bufptr) { + rvptr = zero_index; + distance = 0; + } else + distance = + rvptr + maxdelaysmps - zero_index; + } else + distance = rvptr - zero_index; + + + bufptr = rvptr; //this points to the sample we want to get + + } else { + bufptr = (dlytime + zero_index); //this points to the sample we want to get + if (bufptr >= maxdelaysmps) + bufptr -= maxdelaysmps; + } + + int oldnewdiff = newtime[tap] - oldtime[tap]; + int tmpptr = 0; + if (crossfade[tap] != 0) { + tmpptr = bufptr + oldnewdiff; + if (tmpptr >= maxdelaysmps) + tmpptr -= maxdelaysmps; + else if (tmpptr <= 0) + tmpptr += maxdelaysmps; + return (xfade[tap] * ringbuffer[bufptr] + (1.0f - xfade[tap]) * ringbuffer[tmpptr]); //fade nicely to new tap + } else + return (ringbuffer[bufptr]); + +}; + +/* +* Interpolated delay line +*/ + +float +delayline::delay(float smps, float time_, int tap_, int touch, + int reverse) +{ + int dlytime = 0; + int bufptr = 0; + + tap = fabs(tap_); + if (tap >= maxtaps) + tap = 0; + + if (reverse) avgtime[tap] = alpha * 2.0*time_ + beta * avgtime[tap]; //smoothing the rate of time change + else avgtime[tap] = alpha * time_ + beta * avgtime[tap]; //smoothing the rate of time change + time[tap] = 1.0f + fSAMPLE_RATE * avgtime[tap]; //convert to something that can be used as a delay line index + +//Do some checks to keep things in bounds + if (time[tap] > maxtime) + time[tap] = maxtime; + if (time[tap] < 0.0f) + time[tap] = 0.0f; + + float fract = (time[tap] - floorf(time[tap])); //compute fractional delay + dlytime = lrintf(floorf(time[tap])); + +//now put in the sample + if (touch) { //make touch zero if you only want to pull samples off the delay line + cur_smps[tap] = ringbuffer[zero_index] = smps; + if (--zero_index < 0) + zero_index = maxdelaysmps - 1; + } +//if we want reverse delay +//you need to call this every time to keep the buffers up to date, and it's on a different tap + if (reverse) { + + bufptr = (dlytime + zero_index); //this points to the sample we want to get + if (bufptr >= maxdelaysmps) + bufptr -= maxdelaysmps; + if (++rvptr > maxdelaysmps) + rvptr = 0; + + if (bufptr > zero_index) { + if (rvptr > bufptr) { + rvptr = zero_index; + distance = 0; + } else + distance = rvptr - zero_index; + } else if ((bufptr < zero_index) && (rvptr < zero_index)) { + if (rvptr > bufptr) { + rvptr = zero_index; + distance = 0; + } else + distance = + rvptr + maxdelaysmps - zero_index; + } else + distance = rvptr - zero_index; + + bufptr = rvptr; //this points to the sample we want to get + + } else { + bufptr = (dlytime + zero_index); //this points to the sample we want to get + if (bufptr >= maxdelaysmps) + bufptr -= maxdelaysmps; + } + + tapstruct[tap].lvars[3] = tapstruct[tap].lvars[2]; + tapstruct[tap].lvars[2] = tapstruct[tap].lvars[1]; + tapstruct[tap].lvars[1] = tapstruct[tap].lvars[0]; + tapstruct[tap].lvars[0] = ringbuffer[bufptr]; + + tapstruct[tap].ivars[3] = tapstruct[tap].ivars[2]; + tapstruct[tap].ivars[2] = tapstruct[tap].ivars[1]; + tapstruct[tap].ivars[1] = tapstruct[tap].ivars[0]; + tapstruct[tap].ivars[0] = cur_smps[tap]; + + tapstruct[tap].fracts[3] = tapstruct[tap].fracts[2]; + tapstruct[tap].fracts[2] = tapstruct[tap].fracts[1]; + tapstruct[tap].fracts[1] = tapstruct[tap].fracts[0]; + tapstruct[tap].fracts[0] = fract; + + float tmpfrac = + 0.5f * (tapstruct[tap].fracts[1] + tapstruct[tap].fracts[2]); + //float itmpfrac = 1.0f - tmpfrac; + float itmpfrac = 0.5f; //it was the original approximation + + float output = + mix * lagrange(tapstruct[tap].ivars[0], + tapstruct[tap].ivars[1], + tapstruct[tap].ivars[2], + tapstruct[tap].ivars[3], + itmpfrac) + imix * lagrange(tapstruct[tap].lvars[0], + tapstruct[tap].lvars[1], + tapstruct[tap].lvars[2], + tapstruct[tap].lvars[3], + tmpfrac); + + return (output); + +}; + + +inline float +delayline::get_phaser(float smps, float lfo, int tap_, int stg) +{ + float delta = lfo; + if (delta > 1.0f) + delta = 1.0f; + if (delta < 0.0f) + delta = 0.0f; + tap = tap_; + + pstruct[tap].gain[0] = (1.0f - delta) / (1.0f + delta); + pstruct[tap].stages = stg; + + return (phaser(smps)); +}; + + +inline float +delayline::phaser(float fxn) //All-pass interpolation +{ + + float xn = fxn; + for (int st = 0; st < pstruct[tap].stages; st++) { + pstruct[tap].yn1[st] = + pstruct[tap].xn1[st] - pstruct[tap].gain[st] * (xn + + pstruct + [tap]. + yn1 + [st]); + pstruct[tap].xn1[st] = xn; + xn = pstruct[tap].yn1[st]; + } + + return xn; + +}; + +/* Unfactored WYSIWYG implementation of order=4 Lagrange interpolation polynomial +inline float +delayline::lagrange(float p0, float p1, float p2, float p3, float x_) +{ +float x = x_; + +float xm2xm1 = (x - 1.0f)*(x - 2.0f); +x = -p0*x*xm2xm1*0.16666666667f + p1*(x + 1.0f)*xm2xm1*0.5f - p2*x*(x + 1.0f)*(x - 2.0f)*0.5f + p3*x*(x + 1.0f)*(x - 1.0f)*0.16666666667f; + +return x; +}; +*/ + +inline float +delayline::lagrange(float p0, float p1, float p2, float p3, float x_) +{ +//factored version for less multiplies + float x = x_; + + const float c0p0 = -0.16666666667f * p0; + const float c1p1 = 0.5f * p1; + const float c2p2 = -0.5f * p2; + const float c3p3 = 0.16666666667f * p3; + + const float a = c3p3 + c2p2 + c1p1 + c0p0; + const float b = -3.0f * c0p0 - p1 - c2p2; + const float c = 2.0f * c0p0 - c1p1 + p2 - c3p3; + const float d = p1; + + x = ((a * x + b) * x + c) * x + d; + return x; +}; + +inline float +delayline::spline(float p0, float p1, float p2, float p3, float x_) +{ +//does not produce any better results than lagrange(), but has less multiplies +//seems to produce discontinuities on a low level (-48dB), so not a preferred algorithm + + float x = x_; + + float const c0 = p1; + float const c1 = 0.5f * (p2 - p0); + float const c2 = p0 - 2.5f * p1 + 2.0f * p2 - 0.5f * p3; + float const c3 = 0.5f * (p3 - p0) + 1.5f * (p1 - p2); + + return (((c3 * x + c2) * x + c1) * x + c0); +} + +void delayline::set_averaging(float tc_) +{ + float tc = tc_; + float dt = 1.0f / fSAMPLE_RATE; + fadetime = dt * tc; + alpha = dt / (tc + dt); + beta = 1.0f - alpha; //time change smoothing parameters +}; + +void delayline::set_mix(float mix_) //mix amount of dry w/ wet +{ + mix = fabs(mix_); + imix = 1.0f - mix; + if(mix_<0.0f) imix*=-1.0f; +} + +float delayline::envelope() +{ + float fdist = ((float) distance) / time[tap]; + if (fdist > 0.5f) { + if (fdist <= 1.0f) + fdist = 1.0f - fdist; + else + fdist = 0.0f; + } + + if (fdist <= 0.125f) { + fdist = + 1.0f - f_sin(PI * fdist * 4.0f + 1.5707963267949f); + } else + fdist = 1.0f; + return fdist; + +}; diff --git a/src/effect_audio/rkrlv2/delayline.h b/src/effect_audio/rkrlv2/delayline.h new file mode 100644 index 0000000..6c6be7e --- /dev/null +++ b/src/effect_audio/rkrlv2/delayline.h @@ -0,0 +1,99 @@ +/* + Rakarrack Guitar FX + + delayline.h - An interpolated delay line. Input new sample and desired delay time for output. + Copyright (C) 2010 Ryan Billing + Author: Ryan Billing + + This program is free software; you can redistribute it and/or modify + it under the terms of version 3 of the GNU General Public License + as published by the Free Software Foundation. + + 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 (version 2) for more details. + + You should have received a copy of the GNU General Public License (version 2) + along with this program; if not, write to the Free Software Foundation, + Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +*/ + +#ifndef DLINE_H +#define DLINE_H + +#include "global.h" + + +class delayline +{ +public: + delayline(float maxdelay, int maxtaps_, double samplerate); //construct the object with intended maximum delay time + ~delayline(); + void cleanup(); + void set_averaging(float tc_); //use this if you want the time change averaging longer or shorter + void set_mix(float mix_); + float envelope(); + + //Delay line simple use case is this: + // mydelayed_sample = mydelayline->delay(input, delay_time, 0, 1, 0) + float delay(float smps, float time, int tap_, int touch, int reverse); //interpolating delay + float delay_simple(float smps, float time, int tap_, int touch, int reverse); //simple ring buffer + //smps - The current input sample + //time - amount of delay you want + //mix - for chorus or flanger how much of original to mix + //tap_ - if multi-tap delay, this is the tap you want to access. Usually set touch=0 + //when accessing multiple taps after input. + //touch -set to zero if you want smps written to the delay line. Set nonzero if you only want to read out of delay line + //reverse -set to nonzero if you want to play the samples in the delay line backward. + //Typically you want to apply an envelope to eliminate the click at wraparound from old to recent. + //in this case, multiply the sample by the envelope: + // myreversedelayedsample = mydelayline->delay(input, delay_time, 0, 1, 1) * mydelayline->envelope; + + float get_phaser(float smps, float lfo, int tap_, int stg); //Allows you to use phaser directly without delay line + //smps - input sample + //lfo - ranges from 0 to 1 + //tap - allows multiple separate phasers with the same object + //stg - number of phase stages to process + +private: + int zero_index; + int tap, maxtaps; + float maxtime; + long maxdelaysmps; + int rvptr, distance; + + float *avgtime, *time; //keeping it from changing too quickly + float tconst, alpha, beta, mix, imix; //don't allow change in delay time exceed 1 sample at a time + + int *newtime; + int *oldtime; + int *crossfade; + float *xfade, fadetime; + float *cur_smps; + + struct phasevars { + float yn1[4]; + float xn1[4]; + float gain[4]; + int stages; + } *pstruct; + + float phaser(float fxn); + float lagrange(float p0, float p1, float p2, float p3, float x_); + float spline(float p0, float p1, float p2, float p3, float x_); + + struct tapvars { + float lvars[4]; + float ivars[4]; + float fracts[4]; + } *tapstruct; + + float *ringbuffer; + + float fSAMPLE_RATE; + +}; + +#endif diff --git a/src/effects.h b/src/effects.h index 56b5691..e26066f 100644 --- a/src/effects.h +++ b/src/effects.h @@ -18,7 +18,7 @@ #include "effect_audio/effect_3bandeq.h" #include "effect_audio/effect_phaser.h" #include "effect_audio/effect_aphaser.h" - +#include "effect_audio/effect_flanger.h" class AudioEffects { @@ -37,6 +37,7 @@ public: EQ3BAND = AudioEffect3BandEQ::ID, PHASER = AudioEffectPhaser::ID, APHASER = AudioEffectAPhaser::ID, + FLANGER = AudioEffectFlanger::ID, UNKNOWN }; }; @@ -67,6 +68,8 @@ inline AudioEffect* newAudioEffect(unsigned type, float32_t samplerate) return new AudioEffectPhaser(samplerate); case AudioEffects::Types::APHASER: return new AudioEffectAPhaser(samplerate); + case AudioEffects::Types::FLANGER: + return new AudioEffectFlanger(samplerate); case AudioEffects::Types::NONE: default: return new AudioEffect(samplerate); @@ -99,6 +102,8 @@ inline std::string ToFXType(int nValue) return AudioEffectPhaser::NAME; case AudioEffects::Types::APHASER: return AudioEffectAPhaser::NAME; + case AudioEffects::Types::FLANGER: + return AudioEffectFlanger::NAME; case AudioEffects::Types::NONE: default: return AudioEffect::NAME; diff --git a/src/uimenu.cpp b/src/uimenu.cpp index 8c3e92e..9a260cd 100644 --- a/src/uimenu.cpp +++ b/src/uimenu.cpp @@ -301,6 +301,25 @@ CUIMenu::TMenuItem CUIMenu::s_FXAPhaser[] = {0} }; +CUIMenu::TMenuItem CUIMenu::s_FXFlanger[] = +{ + {"Bypass", EditTGFXParameter, 0, AudioEffectFlanger::Param::BYPASS}, + {"Mix", EditTGFXParameter, 0, AudioEffectFlanger::Param::MIX}, + {"Pan", EditTGFXParameter, 0, AudioEffectFlanger::Param::PAN}, + {"Freq", EditTGFXParameter, 0, AudioEffectFlanger::Param::FL_FREQ}, + {"Random", EditTGFXParameter, 0, AudioEffectFlanger::Param::FL_RND}, + {"Type", EditTGFXParameter, 0, AudioEffectFlanger::Param::TYPE}, + {"Stereo", EditTGFXParameter, 0, AudioEffectFlanger::Param::STDL}, + {"Depth", EditTGFXParameter, 0, AudioEffectFlanger::Param::FL_DEPTH}, + {"Delay", EditTGFXParameter, 0, AudioEffectFlanger::Param::DELAY}, + {"Feedback", EditTGFXParameter, 0, AudioEffectFlanger::Param::FB}, + {"L/R Cross", EditTGFXParameter, 0, AudioEffectFlanger::Param::LRCR}, + {"Mode", EditTGFXParameter, 0, AudioEffectFlanger::Param::MODE}, + {"Sub", EditTGFXParameter, 0, AudioEffectFlanger::Param::SUB}, + {"Awesome", EditTGFXParameter, 0, AudioEffectFlanger::Param::AWESOME}, + {0} +}; + const CUIMenu::TMenuItem CUIMenu::s_MidiFX[] = { {"Type:", EditTGParameter2, 0, CMiniDexed::TGParameterMidiFXType}, @@ -594,6 +613,25 @@ const CUIMenu::TParameter CUIMenu::s_TGFXAPhaserParam[AudioEffectAPhaser::Param: {0, 1, 1, ToOnOff}, // HYPER }; +// must match AudioEffectFlanger::Param +const CUIMenu::TParameter CUIMenu::s_TGFXFlangerParam[AudioEffectFlanger::Param::UNKNOWN] = +{ + {0, 1, 1, ToOnOff}, // BYPASS + {0, 127, 1}, // MIX + {0, 127, 1}, // PAN + {1, 600, 1}, // FL_FREQ + {0, 127, 1}, // FL_RND + {0, 11, 1}, // TYPE + {0, 127, 1}, // STDL + {0, 127, 1}, // FL_DEPTH + {0, 127, 1}, // DELAY + {0, 127, 1}, // FB + {0, 127, 1}, // LRCR + {0, 1, 1, ToOnOff}, // MODE + {0, 1, 1, ToOnOff}, // SUB + {0, 1, 1, ToOnOff}, // AWESOME +}; + // must match MidiArp::Param const CUIMenu::TParameter CUIMenu::s_TGMidiFXArpParam[MidiArp::Param::UNKNOWN] = { @@ -2995,6 +3033,9 @@ CUIMenu::TMenuItem* CUIMenu::getFXMenuItem(unsigned type) case AudioEffects::Types::APHASER: menu = s_FXAPhaser; break; + case AudioEffects::Types::FLANGER: + menu = s_FXFlanger; + break; case AudioEffects::Types::NONE: default: menu = s_FXNone; @@ -3069,6 +3110,9 @@ CUIMenu::TParameter CUIMenu::getFXParameter(unsigned type, unsigned nParam) case AudioEffects::Types::APHASER: pParam = s_TGFXAPhaserParam[nParam]; break; + case AudioEffects::Types::FLANGER: + pParam = s_TGFXFlangerParam[nParam]; + break; default: break; } diff --git a/src/uimenu.h b/src/uimenu.h index ae86499..981f558 100644 --- a/src/uimenu.h +++ b/src/uimenu.h @@ -184,7 +184,8 @@ private: static TMenuItem s_FX3BandEQ[]; static TMenuItem s_FXPhaser[]; static TMenuItem s_FXAPhaser[]; - + static TMenuItem s_FXFlanger[]; + static TMenuItem s_MidiFXNone[]; static TMenuItem s_MidiFXArp[]; @@ -211,6 +212,7 @@ private: static const TParameter s_TGFX3BandEQParam[]; static const TParameter s_TGFXPhaserParam[]; static const TParameter s_TGFXAPhaserParam[]; + static const TParameter s_TGFXFlangerParam[]; static const TParameter s_TGMidiFXArpParam[]; static const TParameter s_VoiceParameter[]; static const TParameter s_OPParameter[];