Added Flanger

pull/764/head
Javier Nonis 6 months ago
parent 7f21a03dac
commit 20eeb54ba2
  1. 2
      src/Makefile
  2. 185
      src/effect_audio/effect_flanger.h
  3. 402
      src/effect_audio/rkrlv2/Chorus.cpp
  4. 89
      src/effect_audio/rkrlv2/Chorus.h
  5. 428
      src/effect_audio/rkrlv2/delayline.cpp
  6. 99
      src/effect_audio/rkrlv2/delayline.h
  7. 7
      src/effects.h
  8. 44
      src/uimenu.cpp
  9. 4
      src/uimenu.h

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

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

@ -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 <math.h>
#include "Chorus.h"
#include <stdio.h>
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);
};
};

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

@ -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 <arm_math.h>
#include <stdlib.h>
#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;
};

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

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

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

@ -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[];

Loading…
Cancel
Save