zynaddsubfx

ZynAddSubFX open source synthesizer
Log | Files | Refs | Submodules | LICENSE

commit a0c59218af9957ea4af0288882ed115a9453da65
parent 76f68a08f01d3a3cd4fd4bc0b75650ef4a9d4f99
Author: friedolino78 <34608315+friedolino78@users.noreply.github.com>
Date:   Wed, 23 Jun 2021 21:14:50 +0200

Add LFO fade in feature (#114)


Diffstat:
Msrc/Params/LFOParams.cpp | 39+++++++++++++++++++++++++++++++++------
Msrc/Params/LFOParams.h | 6++++++
Msrc/Synth/ADnote.cpp | 3+++
Msrc/Synth/LFO.cpp | 99++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-----------
Msrc/Synth/LFO.h | 25+++++++++++++++++++++++++
Msrc/Synth/PADnote.cpp | 3+++
Msrc/Tests/AdNoteTest.cpp | 2+-
Msrc/Tests/WatchTest.cpp | 2+-
Msrc/Tests/guitar-adnote.xmz | 22++++++++++++++++++++++
9 files changed, 180 insertions(+), 21 deletions(-)

diff --git a/src/Params/LFOParams.cpp b/src/Params/LFOParams.cpp @@ -80,6 +80,14 @@ static const rtosc::Ports _ports = { rLinear(0.0, 4.0), rDefaultDepends(loc), rDefault(0), rPreset(ad_voice_amp, 0.94), "Delay before LFO start\n0..4 second delay"), + rParamF(fadein, rShort("fadein"), rSpecial(disable), rUnit(S), + rLinear(0.0, 10.0f), rDefault(0.0f), + "Time to ramp up LFO amplitude\n \ + 0..10 seconds"), + rParamF(fadeout, rShort("fadeout"), rSpecial(disable), rUnit(S), + rLinear(0.001, 10.0f), rDefault(10.0f), + "Time to ramp down LFO amplitude on key release\n \ + 0..10 seconds, 10=off (default)"), {"Pdelay::i", rShort("delay") rLinear(0,127) rDoc("Delay before LFO start\n0..4 second delay"), NULL, [](const char *msg, RtData &d) @@ -91,6 +99,7 @@ static const rtosc::Ports _ports = { obj->delay = 4.0f * rtosc_argument(msg, 0).i / 127.0f; } }}, + rToggle(Pcontinous, rShort("c"), rDefault(false), "Enable for global operation"), rParamZyn(Pstretch, rShort("str"), rCentered, rDefault(64), @@ -145,7 +154,7 @@ void LFOParams::setup() // TODO: reuse LFOParams::LFOParams(const AbsTime *time_) : - LFOParams(2.65, 0, 0, 127, 0, 0, 0, 0, loc_unspecified, time_) + LFOParams(2.65, 0, 0, 127, 0, 0, 0.0, 0.0, 10.0, 0, loc_unspecified, time_) { } @@ -155,7 +164,9 @@ LFOParams::LFOParams(float freq_, char Pcutoff_, char PLFOtype_, char Prandomness_, - float Pdelay_, + float delay_, + float fadein_, + float fadeout_, char Pcontinous_, consumer_location_t loc, const AbsTime *time_) : loc(loc), @@ -167,7 +178,9 @@ LFOParams::LFOParams(float freq_, Dcutoff = Pcutoff_; DLFOtype = PLFOtype_; Drandomness = Prandomness_; - Ddelay = Pdelay_; + Ddelay = delay_; + Dfadein = fadein_; + Dfadeout = fadeout_; Dcontinous = Pcontinous_; setup(); @@ -189,6 +202,8 @@ LFOParams::LFOParams(consumer_location_t loc, DLFOtype = PLFOtype_; Drandomness = Prandomness_; Ddelay = delay_; + Dfadein = 0.0f; + Dfadeout = 10.0f; Dcontinous = Pcontinous_; }; @@ -198,8 +213,8 @@ LFOParams::LFOParams(consumer_location_t loc, case ad_global_freq: init(3.71, 0, 64, 127, 0, 0, 0, 0); break; case ad_global_filter: init(6.49, 0, 64, 127, 0, 0, 0, 0); break; case ad_voice_amp: init(11.25, 32, 64, 127, 0, 0, 0.94, 0); break; - case ad_voice_freq: init(1.19, 40, 0, 127, 0, 0, 0, 0); break; - case ad_voice_filter: init(1.19, 20, 64, 127, 0, 0, 0, 0); break; + case ad_voice_freq: init(1.19, 40, 0, 127, 0, 0, 0, 0); break; + case ad_voice_filter: init(1.19, 20, 64, 127, 0, 0, 0, 0); break; default: throw std::logic_error("Invalid LFO consumer location"); } @@ -218,6 +233,8 @@ void LFOParams::defaults() PLFOtype = DLFOtype; Prandomness = Drandomness; delay = Ddelay; + fadein = Dfadein; + fadeout = Dfadeout; Pcontinous = Dcontinous; Pfreqrand = 0; Pstretch = 64; @@ -234,6 +251,8 @@ void LFOParams::add2XML(XMLwrapper& xml) xml.addpar("randomness_amplitude", Prandomness); xml.addpar("randomness_frequency", Pfreqrand); xml.addparreal("delay", delay); + xml.addparreal("fadein", fadein); + xml.addparreal("fadeout", fadeout); xml.addpar("stretch", Pstretch); xml.addparbool("continous", Pcontinous); } @@ -247,7 +266,7 @@ void LFOParams::getfromXML(XMLwrapper& xml) } Pintensity = xml.getpar127("intensity", Pintensity); Pstartphase = xml.getpar127("start_phase", Pstartphase); - Pcutoff = xml.getpar127("cutoff", Pcutoff); + Pcutoff = xml.getpar127("cutoff", Pcutoff); PLFOtype = xml.getpar127("lfo_type", PLFOtype); Prandomness = xml.getpar127("randomness_amplitude", Prandomness); Pfreqrand = xml.getpar127("randomness_frequency", Pfreqrand); @@ -257,6 +276,12 @@ void LFOParams::getfromXML(XMLwrapper& xml) delay = 4.0f * xml.getpar127("delay", (int)delay *127.0f/4.0f) / 127.0f; } + if (xml.hasparreal("fadein")) { + fadein = xml.getparreal("fadein", fadein); + } + if (xml.hasparreal("fadeout")) { + fadeout = xml.getparreal("fadeout", fadeout); + } Pstretch = xml.getpar127("stretch", Pstretch); Pcontinous = xml.getparbool("continous", Pcontinous); } @@ -272,6 +297,8 @@ void LFOParams::paste(LFOParams &x) COPY(Prandomness); COPY(Pfreqrand); COPY(delay); + COPY(fadein); + COPY(fadeout); COPY(Pcontinous); COPY(Pstretch); diff --git a/src/Params/LFOParams.h b/src/Params/LFOParams.h @@ -44,6 +44,8 @@ class LFOParams:public Presets char PLFOtype_, char Prandomness_, float delay_, + float fadein_, + float fadeout_, char Pcontinous, consumer_location_t loc, const AbsTime* time_ = nullptr); @@ -64,6 +66,8 @@ class LFOParams:public Presets unsigned char Prandomness; /**<randomness (0=off)*/ unsigned char Pfreqrand; /**<frequency randomness (0=off)*/ float delay; /**<delay (0=off)*/ + float fadein; /**<fadein, relative to delay*/ + float fadeout; /**<fadeout on key release (10.0=off)*/ unsigned char Pcontinous; /**<1 if LFO is continous*/ unsigned char Pstretch; /**<how the LFO is "stretched" according the note frequency (64=no stretch)*/ @@ -87,6 +91,8 @@ class LFOParams:public Presets unsigned char DLFOtype; unsigned char Drandomness; float Ddelay; + float Dfadein; + float Dfadeout; unsigned char Dcontinous; }; diff --git a/src/Synth/ADnote.cpp b/src/Synth/ADnote.cpp @@ -1936,6 +1936,9 @@ void ADnote::releasekey() NoteGlobalPar.FreqEnvelope->releasekey(); NoteGlobalPar.FilterEnvelope->releasekey(); NoteGlobalPar.AmpEnvelope->releasekey(); + NoteGlobalPar.FreqLfo->releasekey(); + NoteGlobalPar.FilterLfo->releasekey(); + NoteGlobalPar.AmpLfo->releasekey(); } /* diff --git a/src/Synth/LFO.cpp b/src/Synth/LFO.cpp @@ -28,7 +28,8 @@ LFO::LFO(const LFOParams &lfopars, float basefreq, const AbsTime &t, WatchManage waveShape(lfopars.PLFOtype), deterministic(!lfopars.Pfreqrand), dt_(t.dt()), - lfopars_(lfopars), basefreq_(basefreq), + lfopars_(lfopars), + basefreq_(basefreq), watchOut(m, watch_prefix, "out") { int stretch = lfopars.Pstretch; @@ -74,6 +75,11 @@ LFO::LFO(const LFOParams &lfopars, float basefreq, const AbsTime &t, WatchManage break; } + lfo_state=lfo_state_type::delaying; + + rampUp = 0.0f; + rampDown = 1.0f; + amp1 = (1 - lfornd) + lfornd * RND; amp2 = (1 - lfornd) + lfornd * RND; incrnd = nextincrnd = 1.0f; @@ -154,46 +160,113 @@ float LFO::biquad(float input) return (cutoff==127) ? input : output; // at cutoff 127 bypass filter } +void LFO::releasekey() +{ + if (lfopars_.fadeout==10.0f) { // deactivated + fadeOutDuration = 0.0f; + return; + } + // store current ramp value in case of release while fading in + rampOnRelease = rampUp; + // burn in the current amount of outStartValue + // therefor multiply its current reverse ramping factor + // and divide by current ramp factor. It will be multiplied during fading out. + outStartValue *= (1.0f - rampOnRelease); + // store current time + releaseTimestamp = lfopars_.time->time(); + // calculate fade out duration in frames + fadeOutDuration = lfopars_.fadeout * lfopars_.time->framesPerSec(); + // set fadeout state + lfo_state = lfo_state_type::fadingOut; +} float LFO::lfoout() { - //update internals XXX TODO cleanup + //update internals if ( ! lfopars_.time || lfopars_.last_update_timestamp == lfopars_.time->time()) { waveShape = lfopars_.PLFOtype; int stretch = lfopars_.Pstretch; if(stretch == 0) stretch = 1; + const float lfostretch = powf(basefreq_ / 440.0f, (stretch - 64.0f) / 63.0f); float lfofreq = lfopars_.freq * lfostretch; - phaseInc = fabsf(lfofreq) * dt_; switch(lfopars_.fel) { case consumer_location_type_t::amp: - lfointensity = lfopars_.Pintensity / 127.0f; + lfointensity = lfopars_.Pintensity / 127.0f; // [0...1] break; case consumer_location_type_t::filter: - lfointensity = lfopars_.Pintensity / 127.0f * 4.0f; - break; //in octave + lfointensity = lfopars_.Pintensity / 127.0f * 4.0f; // [0...4] octaves + break; case consumer_location_type_t::freq: case consumer_location_type_t::unspecified: - lfointensity = powf(2, lfopars_.Pintensity / 127.0f * 11.0f) - 1.0f; //in centi - //x -= 0.25f; //chance the starting phase + lfointensity = powf(2, lfopars_.Pintensity / 127.0f * 11.0f) - 1.0f; // [0...2047] cent break; } } - + float out = baseOut(waveShape, phase); - + // Apply intensity if(waveShape == LFO_SINE || waveShape == LFO_TRIANGLE) out *= lfointensity * (amp1 + phase * (amp2 - amp1)); else out *= lfointensity * amp2; + + + // handle lfo state (delay, fade in, fade out) + switch(lfo_state) { + case delaying: + + outStartValue = out; // keep start value to prevent jump + if (delayTime.inFuture()) { + return out; + }else{ + fadeInTimestamp = lfopars_.time->time(); + fadeInDuration = lfopars_.fadein * lfopars_.time->framesPerSec(); + lfo_state = lfo_state_type::fadingIn; + } + + break; - if(delayTime.inFuture()) - return out; + case fadingIn: + + if (fadeInDuration && rampUp < 1.0) { + rampUp = ((float)(lfopars_.time->time() - fadeInTimestamp) / (float)fadeInDuration); + rampUp *= rampUp; // square for soft start + + } + else { + rampUp = 1.0f; + lfo_state = lfo_state_type::running; + } + + out *= rampUp; + out += outStartValue * (1.0f-rampUp); + + break; + + case fadingOut: + if(fadeOutDuration && rampDown) {// no division by zero, please + rampDown = 1.0f - ( (float)(lfopars_.time->time() - releaseTimestamp) / (float)fadeOutDuration ); + rampDown *= rampDown; // square for soft end + } + else // no ramp down + rampDown = 0.0f; + + + out *= rampOnRelease * rampDown; + out += outStartValue*rampDown; + + break; + + case running: + default: + break; + } //Start oscillating if(deterministic) @@ -209,7 +282,7 @@ float LFO::lfoout() computeNextFreqRnd(); } - + float watch_data[2] = {phase, out}; watchOut(watch_data, 2); diff --git a/src/Synth/LFO.h b/src/Synth/LFO.h @@ -18,6 +18,8 @@ #include "../Misc/Time.h" #include "WatchPoint.h" + + namespace zyn { /**Class for creating Low Frequency Oscillators*/ @@ -35,9 +37,20 @@ class LFO float lfoout(); float amplfoout(); + void releasekey(); private: + typedef enum lfo_state_type{ + delaying, + fadingIn, + running, + fadingOut + } lfo_state_type; + float baseOut(const char waveShape, const float phase); float biquad(float input); + lfo_state_type lfo_state; + + //~ float out; //Phase of Oscillator float phase; //Phase Increment Per Frame @@ -60,6 +73,18 @@ class LFO //Delay before starting RelTime delayTime; + int64_t fadeInDuration; + //Timestamp of begin fadein + int64_t fadeInTimestamp; + //Timestamp of noteoff + int64_t releaseTimestamp; + //Time to ramp out + + int64_t fadeOutDuration; + float rampUp, rampDown, rampOnRelease; + // store the constant out value before oscillating starts + float outStartValue = 0.0; + char waveShape; //If After initialization there are no calls to random number gen. diff --git a/src/Synth/PADnote.cpp b/src/Synth/PADnote.cpp @@ -461,6 +461,9 @@ void PADnote::releasekey() NoteGlobalPar.FreqEnvelope->releasekey(); NoteGlobalPar.FilterEnvelope->releasekey(); NoteGlobalPar.AmpEnvelope->releasekey(); + NoteGlobalPar.FreqLfo->releasekey(); + NoteGlobalPar.FilterLfo->releasekey(); + NoteGlobalPar.AmpLfo->releasekey(); } } diff --git a/src/Tests/AdNoteTest.cpp b/src/Tests/AdNoteTest.cpp @@ -219,7 +219,7 @@ class AdNoteTest TS_ASSERT_EQUAL_INT(sampleCount, 30208); - lfop = new LFOParams(); + lfop = new LFOParams(time); lfop->fel = zyn::consumer_location_type_t::amp; lfop->freq = 2.0f; lfop->delay = 0.0f; diff --git a/src/Tests/WatchTest.cpp b/src/Tests/WatchTest.cpp @@ -51,7 +51,7 @@ class WatchTest s = new SYNTH_T; at = new AbsTime(*s); w = new WatchManager(tr); - par = new LFOParams; + par = new LFOParams(at); l = new LFO(*par, 440.0, *at, w); } diff --git a/src/Tests/guitar-adnote.xmz b/src/Tests/guitar-adnote.xmz @@ -100,6 +100,8 @@ version-revision="6" ZynAddSubFX-author="Nasca Octavian Paul"> <par name="randomness_amplitude" value="0" /> <par name="randomness_frequency" value="0" /> <par_real name="delay" value="0" exact_value="0x00000000" /> +<par_real name="fadein" value="0" exact_value="0x00000000" /> +<par_real name="fadeout" value="10" exact_value="0x41200000" /> <par name="stretch" value="64" /> <par_bool name="continous" value="no" /> </AMPLITUDE_LFO> @@ -134,6 +136,8 @@ version-revision="6" ZynAddSubFX-author="Nasca Octavian Paul"> <par name="randomness_amplitude" value="0" /> <par name="randomness_frequency" value="0" /> <par_real name="delay" value="0.598425" exact_value="0x3F193265" /> +<par_real name="fadein" value="0" exact_value="0x00000000" /> +<par_real name="fadeout" value="10" exact_value="0x41200000" /> <par name="stretch" value="64" /> <par_bool name="continous" value="no" /> </FREQUENCY_LFO> @@ -175,6 +179,8 @@ version-revision="6" ZynAddSubFX-author="Nasca Octavian Paul"> <par name="randomness_amplitude" value="0" /> <par name="randomness_frequency" value="0" /> <par_real name="delay" value="0" exact_value="0x00000000" /> +<par_real name="fadein" value="0" exact_value="0x00000000" /> +<par_real name="fadeout" value="10" exact_value="0x41200000" /> <par name="stretch" value="64" /> <par_bool name="continous" value="no" /> </FILTER_LFO> @@ -273,6 +279,8 @@ version-revision="6" ZynAddSubFX-author="Nasca Octavian Paul"> <par name="randomness_amplitude" value="0" /> <par name="randomness_frequency" value="0" /> <par_real name="delay" value="0.944882" exact_value="0x3F71E3C8" /> +<par_real name="fadein" value="0" exact_value="0x00000000" /> +<par_real name="fadeout" value="10" exact_value="0x41200000" /> <par name="stretch" value="64" /> <par_bool name="continous" value="no" /> </AMPLITUDE_LFO> @@ -312,6 +320,8 @@ version-revision="6" ZynAddSubFX-author="Nasca Octavian Paul"> <par name="randomness_amplitude" value="0" /> <par name="randomness_frequency" value="0" /> <par_real name="delay" value="0" exact_value="0x00000000" /> +<par_real name="fadein" value="0" exact_value="0x00000000" /> +<par_real name="fadeout" value="10" exact_value="0x41200000" /> <par name="stretch" value="64" /> <par_bool name="continous" value="yes" /> </FREQUENCY_LFO> @@ -355,6 +365,8 @@ version-revision="6" ZynAddSubFX-author="Nasca Octavian Paul"> <par name="randomness_amplitude" value="0" /> <par name="randomness_frequency" value="0" /> <par_real name="delay" value="0" exact_value="0x00000000" /> +<par_real name="fadein" value="0" exact_value="0x00000000" /> +<par_real name="fadeout" value="10" exact_value="0x41200000" /> <par name="stretch" value="64" /> <par_bool name="continous" value="no" /> </FILTER_LFO> @@ -524,6 +536,8 @@ version-revision="6" ZynAddSubFX-author="Nasca Octavian Paul"> <par name="randomness_amplitude" value="0" /> <par name="randomness_frequency" value="0" /> <par_real name="delay" value="0" exact_value="0x00000000" /> +<par_real name="fadein" value="0" exact_value="0x00000000" /> +<par_real name="fadeout" value="10" exact_value="0x41200000" /> <par name="stretch" value="64" /> <par_bool name="continous" value="yes" /> </FREQUENCY_LFO> @@ -611,6 +625,8 @@ version-revision="6" ZynAddSubFX-author="Nasca Octavian Paul"> <par name="randomness_amplitude" value="0" /> <par name="randomness_frequency" value="0" /> <par_real name="delay" value="0" exact_value="0x00000000" /> +<par_real name="fadein" value="0" exact_value="0x00000000" /> +<par_real name="fadeout" value="10" exact_value="0x41200000" /> <par name="stretch" value="64" /> <par_bool name="continous" value="yes" /> </FREQUENCY_LFO> @@ -3707,6 +3723,8 @@ version-revision="6" ZynAddSubFX-author="Nasca Octavian Paul"> <par name="randomness_amplitude" value="0" /> <par name="randomness_frequency" value="0" /> <par_real name="delay" value="0" exact_value="0x00000000" /> +<par_real name="fadein" value="0" exact_value="0x00000000" /> +<par_real name="fadeout" value="10" exact_value="0x41200000" /> <par name="stretch" value="64" /> <par_bool name="continous" value="no" /> </AMPLITUDE_LFO> @@ -3744,6 +3762,8 @@ version-revision="6" ZynAddSubFX-author="Nasca Octavian Paul"> <par name="randomness_amplitude" value="0" /> <par name="randomness_frequency" value="0" /> <par_real name="delay" value="0" exact_value="0x00000000" /> +<par_real name="fadein" value="0" exact_value="0x00000000" /> +<par_real name="fadeout" value="10" exact_value="0x41200000" /> <par name="stretch" value="64" /> <par_bool name="continous" value="no" /> </FREQUENCY_LFO> @@ -3785,6 +3805,8 @@ version-revision="6" ZynAddSubFX-author="Nasca Octavian Paul"> <par name="randomness_amplitude" value="0" /> <par name="randomness_frequency" value="0" /> <par_real name="delay" value="0" exact_value="0x00000000" /> +<par_real name="fadein" value="0" exact_value="0x00000000" /> +<par_real name="fadeout" value="10" exact_value="0x41200000" /> <par name="stretch" value="64" /> <par_bool name="continous" value="no" /> </FILTER_LFO>