/* ZynAddSubFX - a software synthesizer Echo.cpp - Echo effect Copyright (C) 2002-2005 Nasca Octavian Paul Copyright (C) 2009-2010 Mark McCurry Author: Nasca Octavian Paul Mark McCurry 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 2 of the License, or (at your option) any later version. */ #include #include #include #include "../Misc/Allocator.h" #include "Echo.h" #define MAX_DELAY 2 namespace zyncarla { #define rObject Echo #define rBegin [](const char *msg, rtosc::RtData &d) { #define rEnd } rtosc::Ports Echo::ports = { {"preset::i", rOptions(Echo 1, Echo 2, Echo 3, Simple Echo, Canyon, Panning Echo 1, Panning Echo 2, Panning Echo 3, Feedback Echo) rProp(parameter) rDoc("Instrument Presets"), 0, rBegin; rObject *o = (rObject*)d.obj; if(rtosc_narguments(msg)) o->setpreset(rtosc_argument(msg, 0).i); else d.reply(d.loc, "i", o->Ppreset); rEnd}, rEffParVol(rDefault(67), rPresetsAt(6, 81, 81, 62)), rEffParPan(rPresetsAt(2, 75, 60, 60, 64, 60, 60, 64)), rEffPar(Pdelay, 2, rShort("delay"), rPresets(35, 21, 60, 44, 102, 44, 46, 26, 28), "Length of Echo"), rEffPar(Plrdelay, 3, rShort("lr delay"), rPresetsAt(4, 50, 17, 118, 100, 64), rDefault(64), "Difference In Left/Right Delay"), rEffPar(Plrcross, 4, rShort("cross"), rPresetsAt(5, 0, 100, 127, 100), rDefault(30), "Left/Right Crossover"), rEffPar(Pfb, 5, rShort("feedback"), rPresets(59, 59, 59, 0, 82, 82, 68, 67, 90), "Echo Feedback"), rEffPar(Phidamp, 6, rShort("damp"), rPresets(0, 0, 10, 0, 48, 24, 18, 36, 55), "Dampen High Frequencies"), }; #undef rBegin #undef rEnd #undef rObject Echo::Echo(EffectParams pars) :Effect(pars), Pvolume(50), Pdelay(60), Plrdelay(100), Pfb(40), Phidamp(60), delayTime(1), lrdelay(0), avgDelay(0), delay(memory.valloc(MAX_DELAY * pars.srate), memory.valloc(MAX_DELAY * pars.srate)), old(0.0f), pos(0), delta(1), ndelta(1) { initdelays(); setpreset(Ppreset); } Echo::~Echo() { memory.devalloc(delay.l); memory.devalloc(delay.r); } //Cleanup the effect void Echo::cleanup(void) { memset(delay.l, 0, MAX_DELAY * samplerate * sizeof(float)); memset(delay.r, 0, MAX_DELAY * samplerate * sizeof(float)); old = Stereo(0.0f); } inline int max(int a, int b) { return a > b ? a : b; } //Initialize the delays void Echo::initdelays(void) { cleanup(); //number of seconds to delay left chan float dl = avgDelay - lrdelay; //number of seconds to delay right chan float dr = avgDelay + lrdelay; ndelta.l = max(1, (int) (dl * samplerate)); ndelta.r = max(1, (int) (dr * samplerate)); delta = ndelta; } //Effect output void Echo::out(const Stereo &input) { for(int i = 0; i < buffersize; ++i) { float ldl = delay.l[pos.l]; float rdl = delay.r[pos.r]; ldl = ldl * (1.0f - lrcross) + rdl * lrcross; rdl = rdl * (1.0f - lrcross) + ldl * lrcross; efxoutl[i] = ldl * 2.0f; efxoutr[i] = rdl * 2.0f; ldl = input.l[i] * pangainL - ldl * fb; rdl = input.r[i] * pangainR - rdl * fb; //LowPass Filter old.l = delay.l[(pos.l + delta.l) % (MAX_DELAY * samplerate)] = ldl * hidamp + old.l * (1.0f - hidamp); old.r = delay.r[(pos.r + delta.r) % (MAX_DELAY * samplerate)] = rdl * hidamp + old.r * (1.0f - hidamp); //increment ++pos.l; // += delta.l; ++pos.r; // += delta.r; //ensure that pos is still in bounds pos.l %= MAX_DELAY * samplerate; pos.r %= MAX_DELAY * samplerate; //adjust delay if needed delta.l = (15 * delta.l + ndelta.l) / 16; delta.r = (15 * delta.r + ndelta.r) / 16; } } //Parameter control void Echo::setvolume(unsigned char _Pvolume) { Pvolume = _Pvolume; if(insertion == 0) { if (Pvolume == 0) { outvolume = 0.0f; } else { outvolume = powf(0.01f, (1.0f - Pvolume / 127.0f)) * 4.0f; } volume = 1.0f; } else volume = outvolume = Pvolume / 127.0f; if(Pvolume == 0) cleanup(); } void Echo::setdelay(unsigned char _Pdelay) { Pdelay = _Pdelay; avgDelay = (Pdelay / 127.0f * 1.5f); //0 .. 1.5 sec initdelays(); } void Echo::setlrdelay(unsigned char _Plrdelay) { float tmp; Plrdelay = _Plrdelay; tmp = (powf(2.0f, fabsf(Plrdelay - 64.0f) / 64.0f * 9.0f) - 1.0f) / 1000.0f; if(Plrdelay < 64.0f) tmp = -tmp; lrdelay = tmp; initdelays(); } void Echo::setfb(unsigned char _Pfb) { Pfb = _Pfb; fb = Pfb / 128.0f; } void Echo::sethidamp(unsigned char _Phidamp) { Phidamp = _Phidamp; hidamp = 1.0f - Phidamp / 127.0f; } void Echo::setpreset(unsigned char npreset) { const int PRESET_SIZE = 7; const int NUM_PRESETS = 9; unsigned char presets[NUM_PRESETS][PRESET_SIZE] = { {67, 64, 35, 64, 30, 59, 0 }, //Echo 1 {67, 64, 21, 64, 30, 59, 0 }, //Echo 2 {67, 75, 60, 64, 30, 59, 10}, //Echo 3 {67, 60, 44, 64, 30, 0, 0 }, //Simple Echo {67, 60, 102, 50, 30, 82, 48}, //Canyon {67, 64, 44, 17, 0, 82, 24}, //Panning Echo 1 {81, 60, 46, 118, 100, 68, 18}, //Panning Echo 2 {81, 60, 26, 100, 127, 67, 36}, //Panning Echo 3 {62, 64, 28, 64, 100, 90, 55} //Feedback Echo }; if(npreset >= NUM_PRESETS) npreset = NUM_PRESETS - 1; for(int n = 0; n < PRESET_SIZE; ++n) changepar(n, presets[npreset][n]); if(insertion) setvolume(presets[npreset][0] / 2); //lower the volume if this is insertion effect Ppreset = npreset; } void Echo::changepar(int npar, unsigned char value) { switch(npar) { case 0: setvolume(value); break; case 1: setpanning(value); break; case 2: setdelay(value); break; case 3: setlrdelay(value); break; case 4: setlrcross(value); break; case 5: setfb(value); break; case 6: sethidamp(value); break; } } unsigned char Echo::getpar(int npar) const { switch(npar) { case 0: return Pvolume; case 1: return Ppanning; case 2: return Pdelay; case 3: return Plrdelay; case 4: return Plrcross; case 5: return Pfb; case 6: return Phidamp; default: return 0; // in case of bogus parameter number } } }