|
07-24-2015, 11:23 AM
|
#1
|
Human being with feelings
Join Date: Jul 2015
Posts: 17
|
Hello all, WDL-OL Help needed creating a multi fx plug-in
Hello everyone I hope you're all well, this is my first post on here and I am new to plug-in development so please be gentle!
I am using the WDL-OL framework to create a multi-fx plug-in entitled 'Repeater Pan' It will combine Gain, Filtering, Resonant Delay and tremolo in a simple GUI with only a few parameters.
I have been following Martin Finkes tutorials which are really helpful ( http://www.martin-finke.de/blog/)
I have got a gain plug-in working and would like to combine the filter example he used but as an effect plug-in as oppose to the synthesiser.
I have created the Filter class, assigned the correct knobs/ switch in the constructor and in the onparamchange function. My problem is how to correctly implement/call it in the ProcessDoubleReplacing function.
Here is my code for ProcessDoubleReplacing
void RepeaterPan::ProcessDoubleReplacing(double** inputs, double** outputs, int nFrames)
{
double* in1 = inputs[0];
double* in2 = inputs[1];
double* out1 = outputs[0];
double* out2 = outputs[1];
//Gain function
for (int s = 0; s < nFrames; ++s, ++in1, ++in2, ++out1, ++out2)
{
*out1 = *in1 * mGain;
*out2 = *in2 * mGain;
}
// Filter call
for (int i = 0; i < nFrames; ++i, ++in1, ++in2, ++out1, ++out2)
{
out1[i] = mFilter.process(mGain);
out2[i] = mFilter.process(mGain);
}
Understanding how to successfully implement multiple effects in series is a critical part of the project (and my development as a plug-in creator) and I will be able to progress exponentially when I do.
Thank you so much for your patience and time.
-Luke
|
|
|
07-24-2015, 11:56 AM
|
#2
|
Human being with feelings
Join Date: Feb 2007
Location: Oulu, Finland
Posts: 8,062
|
Try something like :
Code:
void RepeaterPan::ProcessDoubleReplacing(double** inputs, double** outputs, int nFrames)
{
double* in1 = inputs[0];
double* in2 = inputs[1];
double* out1 = outputs[0];
double* out2 = outputs[1];
for (int i = 0; i < nFrames; ++i)
{
out1[i] = in1[i] * mGain;
out2[i] = in2[i] * mGain;
out1[i] = mFilter1.process(out1[i]);
out2[i] = mFilter2.process(out2[i]);
}
Some things to note there :
1) Don't use the extremely yucky pointer arithmetics stuff in the for loop needlessly to access the buffer. There's absolutely no need to do it. Just using the [i] notation to access the elements is as fast if not faster than the ugly pointer arithmetics. (Unless you are using some stupidly old and/or bad compiler...)
2) You will need 2 instances of your filter object to process stereo audio. So it's not enough to have that one mFilter object in your class. Change the class to have mFilter1 and mFilter2 or something along those lines. (I'd myself probably have a dynamically resizable array of the filter objects, to accommodate any number of channels needed for the processing.)
__________________
I am no longer part of the REAPER community. Please don't contact me with any REAPER-related issues.
|
|
|
07-26-2015, 03:17 PM
|
#3
|
Human being with feelings
Join Date: Jul 2015
Posts: 17
|
Thank you Xenakios you are a true gent! that worked a charm.
The dynamic array thing is a little bit beyond me at this point, I completely understand the concept but actually coding it is another matter! I only intended for this plug-in to be a stereo one anyway so a duplicated instance is a ok ; )
I'll post up screenshots/ the finished thing when I'm done with it incase anyone is interested in seeing a noobs work!
|
|
|
07-26-2015, 04:27 PM
|
#4
|
Human being with feelings
Join Date: Feb 2007
Location: Oulu, Finland
Posts: 8,062
|
Quote:
Originally Posted by Placid Mouse
I only intended for this plug-in to be a stereo one anyway
|
That's probably fine, just having stereo will work most of the time. (Making multichannel/surround capable plugins might be a profitable niche market to tap into, though...)
I've only recently started about worrying things higher in channel count than stereo. My HourGlass app that I started developing in 2009 had accumulated tons of stuff that assumed things are either mono or stereo...Been having some "fun" lately turning all that to work with more channels... I swore I will never again do that same mistake.
__________________
I am no longer part of the REAPER community. Please don't contact me with any REAPER-related issues.
|
|
|
08-11-2015, 03:12 PM
|
#5
|
Human being with feelings
Join Date: Jul 2015
Posts: 17
|
Hello folks, a little help if you would be so good ; )
I am trying to implement a parameter for control over the stereo width of the signal. The idea is that all the other effects in the program will have the ability to be assigned to either the extremities of the stereo field or anywhere in between.
With this in mind I have got the following code from musicdsp.org
Code :
Code:
{
// calculate scale coefficient
coef_S = width*0.5;
// then do this per sample
m = (in_left + in_right)*0.5;
s = (in_right - in_left )*coef_S;
out_left = m - s;
out_right = m + s;
}
Ok so bear with me because my thought processes can be troubling at the best of times! but this is my thought process of how to implement this correctly
1. create a 'StereoWidth' .cpp class...
Code:
Code:
{
#include "StereoWidth.h"
double StereoWidth::process(double inputValue)
{
// calculate scale coefficient
coef_S = width * 0.5;
// then do this per sample
mono = (in_left + in_right) * 0.5;
stereo = (in_right - in_left ) * coef_S;
out_left = mono - stereo;
out_right = mono + stereo;
return 0.0;
}
}
2. Create a header file to go with this lovely new class
Code:
{
#ifndef __RepeaterPan__StereoWidth__
#define __RepeaterPan__StereoWidth__
#include <stdio.h>
class StereoWidth {
public:
double process(double inputValue);
inline void setWidth (double newWidth) { width = newWidth;}
private:
double in_left;
double in_right;
double width;
double coef_S;
double mono;
double stereo;
double out_left;
double out_right;
};
#endif /* defined(__RepeaterPan__StereoWidth__) */}
3. edit my main class.
add to header file private section.
Code:
{StereoWidth mStereoWidth;}
add to EParams
Now this is where it goes a bit pear shaped, I need to create a control for the parameter so naturally I added this to the constructor...
Code:
{// Stereowidth knob:
GetParam(mStereoWidth)->InitDouble("StereoWidth", 0.01, 0.01, 0.99, 0.001);
pGraphics->AttachControl(new IKnobMultiControl(this, 500, 300, mStereoWidth, &knob));}
however it doesn't seem to like it, I'm probably being dumb but I don't see why it should be any different to the other knob attachments.
I have also edited the processDoubleReplacing function
Code:
{
void RepeaterPan::ProcessDoubleReplacing(double** inputs, double** outputs, int nFrames)
{
double* in1 = inputs[0];
double* in2 = inputs[1];
double* out1 = outputs[0];
double* out2 = outputs[1];
for (int i = 0; i < nFrames; ++i)
{
out1[i] = in1[i] * mGain;
out2[i] = in2[i] * mGain;
out1[i] = mFilterL.process(out1[i]);
out2[i] = mFilterR.process(out2[i]);
out1[i] = mStereoWidth.process(out1[i]);
out2[i] = mStereoWidth.process(out2[i]);
}
}
}
and attempted implementing the onParam change function.
Code:
{ case mStereoWidth:
mStereoWidth.setWidth(GetParam(paramIdx)->Value());
break;}
Again sorry for such a long post, I really appreciate any help, I'll buy a unicorn for the first person to reply to this.
-Luke
|
|
|
08-11-2015, 11:09 PM
|
#6
|
Human being with feelings
Join Date: Jul 2008
Location: The Netherlands
Posts: 3,645
|
Quote:
Originally Posted by Placid Mouse
|
You can't use the symbol mStereoWidth here, because you have already used it for something else, so I would suggest:
Quote:
Originally Posted by Placid Mouse
Code:
{// Stereowidth knob:
GetParam(mStereoWidth)->InitDouble("StereoWidth", 0.01, 0.01, 0.99, 0.001);
pGraphics->AttachControl(new IKnobMultiControl(this, 500, 300, mStereoWidth, &knob));}
|
Same here, mStereoWidth is an object of yor StereoWidth class, kStereoWidth is an enumerator value (i.e. an integer number), so:
Code:
GetParam(kStereoWidth)->InitDouble("StereoWidth", 0.01, 0.01, 0.99, 0.001);
pGraphics->AttachControl(new IKnobMultiControl(this, 500, 300, mStereoWidth, &knob));
Quote:
Originally Posted by Placid Mouse
Code:
{ case mStereoWidth:
mStereoWidth.setWidth(GetParam(paramIdx)->Value());
break;}
|
And one more:
|
|
|
08-12-2015, 08:10 PM
|
#7
|
Human being with feelings
Join Date: Jul 2015
Posts: 17
|
Thanks Tale, I realised the extent of my stupidity as soon as you made the first point about the eparams.
It builds without errors now but again I'm having trouble calling it in the processDoubleReplacing function again.
I tried
code:
Code:
{
{void RepeaterPan::ProcessDoubleReplacing(double** inputs, double** outputs, int nFrames)
{
double* in1 = inputs[0];
double* in2 = inputs[1];
double* out1 = outputs[0];
double* out2 = outputs[1];
for (int i = 0; i < nFrames; ++i)
{
out1[i] = in1[i] * mGain;
out2[i] = in2[i] * mGain;
out1[i] = mFilterL.process(mStereoWidth.process(out1[i]));
out2[i] = mFilterR.process(mStereoWidth.process(out2[i]));
}
}
}
and the one posted earlier, all to no avail.
Tale I've checked with UPS and your unicorn is on it's way ; )
|
|
|
08-12-2015, 11:17 PM
|
#8
|
Human being with feelings
Join Date: Jul 2008
Location: The Netherlands
Posts: 3,645
|
Quote:
Originally Posted by Placid Mouse
It builds without errors now but again I'm having trouble calling it in the processDoubleReplacing function again.
|
I think you need to change StereoWidth:: process(), so it processes two samples (left and right) rather than just one, i.e.:
Code:
void StereoWidth::process(double in_left, double in_right, double* p_out_left, double* p_out_right)
{
coef_S = width * 0.5;
mono = (in_left + in_right) * 0.5;
stereo = (in_right - in_left ) * coef_S;
*out_left = mono - stereo;
*out_right = mono + stereo;
}
Then in the ProcessDoubleReplacing() loop you can do:
Code:
mStereoWidth.process(in1[i], in2[i], &out1[i], &out2[i]);
|
|
|
08-13-2015, 12:28 PM
|
#9
|
Human being with feelings
Join Date: Jul 2015
Posts: 17
|
Thanks Tale, this is very helpful. It's quite hard to tell if it's worked at the moment, I may need to play with the values in the constructor. The idea is that, say I was to apply a high pass filter, the stereo width knob would only apply the high pass to the extremities of the stereo field and move in toward the centre depending on the position of the knob.
|
|
|
08-13-2015, 02:52 PM
|
#10
|
Human being with feelings
Join Date: Jul 2008
Location: The Netherlands
Posts: 3,645
|
Well, if you want to test your mid/side processing just temporarily set mono = 0.0, and your width to 1.0. This will leave only the sides, so if you feed it a mono signal you should get silence, but if you feed it a stereo signal you should get some output.
BTW, you don't need to declare all variables in your class, variables that are only used by a single method are probably better declared locally to that method. I would also suggest to prefix member variables with m (or m_), so it is easier to distinguish between local and member variables. E.g.:
Code:
class StereoWidth
{
public:
// Init coef to sensible default, in case we forget to call setWidth().
StereoWidth(): mCoef_S(0) {}
void process(double in_left, double in_right, double* out_left, double* out_right)
{
double mono = (in_left + in_right) * 0.5;
double stereo = (in_right - in_left ) * mCoef_S;
*out_left = mono - stereo;
*out_right = mono + stereo;
}
inline void setWidth(double newWidth)
{
mCoef_S = newWidth * 0.5;
}
private:
double mCoef_S;
};
|
|
|
08-13-2015, 06:31 PM
|
#11
|
Human being with feelings
Join Date: Jul 2015
Posts: 17
|
Does this mean there is no use for the header file for this class?
|
|
|
08-13-2015, 11:06 PM
|
#12
|
Human being with feelings
Join Date: Jul 2008
Location: The Netherlands
Posts: 3,645
|
Yeah... Although I guess it might be better to split it up into header and actual source, even for such a small class.
|
|
|
08-14-2015, 06:06 PM
|
#13
|
Human being with feelings
Join Date: Jul 2015
Posts: 17
|
Ok thanks dude, I stuck with the header file but got rid of all the unnecessary variable declarations.
It's not quite working how i want to at the moment, I'l come back to it while I finish working on some other features of the plug-in. I want to implement a delay, a tremolo, and some kind of limiter to stop the outputs getting too hot.
How easy would it be to implement an assignable LFO? Ie one that I can change the waveform of and assign to control the other parameters of the plug-in? Like you see in soft synths like massive, razor etc.
I have an oscillator class courtesy of the Finke tutorials, he explains how to assign one to the filter cutoff, I wonder how easy it would be to give the user control over the ability to assign it to any given parameter.
|
|
|
08-15-2015, 02:01 AM
|
#14
|
Human being with feelings
Join Date: Jul 2008
Location: The Netherlands
Posts: 3,645
|
Quote:
Originally Posted by Placid Mouse
How easy would it be to implement an assignable LFO? Ie one that I can change the waveform of and assign to control the other parameters of the plug-in? Like you see in soft synths like massive, razor etc.
I have an oscillator class courtesy of the Finke tutorials, he explains how to assign one to the filter cutoff, I wonder how easy it would be to give the user control over the ability to assign it to any given parameter.
|
Pretty easy, IMHO, YMMV... I don't know what the oscillator class of Finke looks like, but something like this could work:
Code:
class Oscillator
{
public:
Oscillator(): mPhase(0), mPeriod(0) {}
void SetFreq(double freq, double sampleRate)
{
mPeriod = freq / sampleRate;
}
inline double GetSin() const
{
return sin(2*M_PI * mPhase);
}
inline double GetSquare() const
{
return mPhase < 0.5 ? 1 : -1;
}
inline void IncPhase()
{
mPhase += mPeriod;
mPhase -= (int)mPhase;
}
private:
double mPhase, mPeriod;
};
// Usage:
Oscillator MyOsc;
MyOsc.SetFreq(4);
double output = 0;
switch (waveform)
{
case 0: output = MyOsc.GetSin(); break;
case 1: output = MyOsc.GetSquare(); break;
}
MyOsc.IncPhase();
You could also integrate the waveform selection inside the class of course...
To apply an LFO to a parameter you simply use either the parameter value or the LFO output, e.g.:
Code:
for (int i = 0; i < nFrames; ++i)
{
double lfo = mMyLFO.GetSin();
mMyLFO.IncPhase();
double value = wantLFO ? : lfo : GetParam(kMyParam)->Value();
// Now use value as gain, or pass it to your filter class or whatever.
}
Note that you might want to smooth e.q. a square wave a bit if you plan to use it as gain (tremolo), because else you will get pops/clicks.
Last edited by Tale; 08-16-2015 at 02:26 AM.
Reason: SetFreq(): sampleRate, not mSampleRate
|
|
|
08-15-2015, 08:12 PM
|
#15
|
Human being with feelings
Join Date: Jul 2015
Posts: 17
|
Woah man thanks, I've created a branch to give this oscillator a try.
Ok so I should create a header file with...
Code:
{
#ifndef __LfoExperiment__Oscillator__
#define __LfoExperiment__Oscillator__
#include <math.h>
class Oscillator
{
public:
Oscillator(): mPhase(0), mPeriod(0) {}
void SetFreq(double freq, double sampleRate)
{
mPeriod = freq / mSampleRate;
}
inline double GetSin() const
{
return sin(2*M_PI * mPhase);
}
inline double GetSquare() const
{
return mPhase < 0.5 ? 1 : -1;
}
inline void IncPhase()
{
mPhase += mPeriod;
mPhase -= (int)mPhase;
}
private:
double mPhase, mPeriod;
};
#endif /* defined(__LfoExperiment__Oscillator__) */
}
and then put this in the cpp file?
Code:
{
// Usage:
Oscillator MyOsc;
MyOsc.SetFreq(4);
double output = 0;
switch (waveform)
{
case 0: output = MyOsc.GetSin(); break;
case 1: output = MyOsc.GetSquare(); break;
}
MyOsc.IncPhase();
}
Or should i have it all in one cpp file? Sorry for asking such a noob question! I kind of get what you're doing it's just that I'm a little confused as to how I should implement it.
Also should "mSampleRate" be declared along with the other doubles?
|
|
|
08-16-2015, 02:25 AM
|
#16
|
Human being with feelings
Join Date: Jul 2008
Location: The Netherlands
Posts: 3,645
|
Quote:
Originally Posted by Placid Mouse
and then put this in the cpp file?
|
No, you only need the header file. Again it might be better to split up declaration and implementation, but for such a small class I am usually lazy and just put it all in a header file (can always split up later on anyway).
The "usage" bits of code were just to show you how to call this from within your plug-in.
Quote:
Originally Posted by Placid Mouse
Also should "mSampleRate" be declared along with the other doubles?
|
Oops, that should have been sampleRate (parameter), not mSampleRate.
|
|
|
08-16-2015, 07:54 PM
|
#17
|
Human being with feelings
Join Date: Jul 2015
Posts: 17
|
Thanks mate
So I should call the code:
Code:
{ MyOsc.SetFreq(4,4);
double output = 0;
switch (waveform)
{
case 0: output = MyOsc.GetSin(); break;
case 1: output = MyOsc.GetSquare(); break;
}
MyOsc.IncPhase();}
inside processDoubleReplacing?
I've changed the My.osc.SetFreq to (4,4) because it required 2 parameters I don't know if this is right or not.
Where should I declare the 'Waveform' variable?
With regards to the lfo mode selection.
I have done this slightly differently to how you suggested in order to make it more like the way I have implemented the filter switch, just so I can get my head around it and apply the graphics easily.
Ok so now I have...
Oscillator Class:
Code:
{class Oscillator
{
public:
enum lfoMode
{
LFO_MODE_SIN = 0,
LFO_MODE_SQUARE,
kNumlfoModes
};
Oscillator():
mlfoMode (LFO_MODE_SIN),
mPhase(0),
mPeriod(0)
{
};
inline void setlfoMode(lfoMode newMlfoMode) { mlfoMode = newMlfoMode; }
void SetFreq(double freq, double sampleRate)
{
mPeriod = freq / sampleRate;
}
inline double GetSin() const
{
return sin(2*M_PI * mPhase);
}
inline double GetSquare() const
{
return mPhase < 0.5 ? 1 : -1;
}
inline void IncPhase()
{
mPhase += mPeriod;
mPhase -= (int)mPhase;
}
private:
double mPhase, mPeriod;
lfoMode mlfoMode;
};
}
Graphics Attachment:
Code:
{//lfo waveform switch
GetParam(kLFOWaveform)->InitEnum("Lfo Waveform", Oscillator::LFO_MODE_SIN, Oscillator::kNumlfoModes);
IBitmap lfomodeBitmap = pGraphics->LoadIBitmap(LFOMODE_ID, LFOMODE_FN, 2);
pGraphics->AttachControl(new ISwitchControl(this, 650, 300, klfoMode, &lfomodeBitmap));
}
Parameter change function:
Code:
{case klfoMode:
MyOsc.setlfoMode(static_cast<Oscillator::lfoMode>(GetParam(paramIdx)->Int()));
break;}
I am having trouble how to exactly call it in the procesDoubleReplacing function. This hasn't effected the other fx. I need to implement some kind of way of assigning it to the other parameter, preferably an 'lfo amount' knob next to each parameter to provide control over how much of the parameter is being modulated. Control over lfo frequency would be needed also. After that I'm done. No time for global beat syncing unless it's really easy to do.
Code:
{void LfoExperiment::ProcessDoubleReplacing(double** inputs, double** outputs, int nFrames)
{
double* in1 = inputs[0];
double* in2 = inputs[1];
double* out1 = outputs[0];
double* out2 = outputs[1];
for (int i = 0; i < nFrames; ++i)
{
out1[i] = in1[i] * mGain;
out2[i] = in2[i] * mGain;
out1[i] = mFilterL.process(out1[i]);
out2[i] = mFilterR.process(out2[i]);
MyOsc.SetFreq(4,4);
double output = 0;
// I presume this is what should go here
switch (klfoMode)
{
case 0: output = MyOsc.GetSin(); break;
case 1: output = MyOsc.GetSquare(); break;
}
MyOsc.IncPhase();
}
}}
Here's a picture of the GUI so far,as you can see the lfo waveform selector is already in place.
free picture upload
Again thanks so much dude, I really appreciate the help ; )
|
|
|
08-16-2015, 11:28 PM
|
#18
|
Human being with feelings
Join Date: Jul 2008
Location: The Netherlands
Posts: 3,645
|
Quote:
Originally Posted by Placid Mouse
So I should call the code:
Code:
MyOsc.SetFreq(4,4);
|
Almost, but the 2nd parameter should be the sample rate:
Code:
MyOsc.SetFreq(4,GetSampleRate());
You will probably want to call this from OnParamChange(), and set the frequency to the LFO frequency parameter.
Quote:
Originally Posted by Placid Mouse
Where should I declare the 'Waveform' variable?
|
Now that you have added setlfoMode() you probably want to add this to your oscillator class:
Code:
double GetOutput() const
{
double output = 0;
switch (mlfoMode)
{
case LFO_MODE_SIN: output = GetSin(); break;
case LFO_MODE_SQUARE: output = GetSquare(); break;
}
return output;
}
Quote:
Originally Posted by Placid Mouse
I am having trouble how to exactly call it in the procesDoubleReplacing function.
|
Well, this is how you e.g. would use the LFO output as a tremolo:
Code:
void SimpleTremolo::ProcessDoubleReplacing(double** inputs, double** outputs, int nFrames)
{
double* in1 = inputs[0];
double* in2 = inputs[1];
double* out1 = outputs[0];
double* out2 = outputs[1];
for (int i = 0; i < nFrames; ++i)
{
double lfo = mMyOsc.GetOutput();
mMyOsc.IncPhase();
// LFO returns [-1, +1], so convert to [0, 1].
double tremolo = (lfo + 1) * 0.5;
out1[i] = in1[i] * tremolo;
out2[i] = in2[i] * tremolo;
}
}
|
|
|
08-17-2015, 12:26 AM
|
#19
|
Human being with feelings
Join Date: Jul 2015
Posts: 17
|
Thanks man this works as a tremolo great ; )
Do you know how I could apply it to the other effects? Or even for the moment have it turn on or off, with the filter/ gain process remaining in tact?
|
|
|
08-17-2015, 02:18 PM
|
#20
|
Human being with feelings
Join Date: Jul 2008
Location: The Netherlands
Posts: 3,645
|
Quote:
Originally Posted by Placid Mouse
Thanks man this works as a tremolo great ; )
|
Well, if you set it to square, then you probably will want to smooth it, so it's not exactly great (IMHO). But I guess it's a good start.
Quote:
Originally Posted by Placid Mouse
Do you know how I could apply it to the other effects?
|
I will leave that for you to figure out...
Quote:
Originally Posted by Placid Mouse
Or even for the moment have it turn on or off, with the filter/ gain process remaining in tact?
|
Alrighty then, I will give you that one:
Code:
void SimpleTremolo::ProcessDoubleReplacing(double** inputs, double** outputs, int nFrames)
{
double* in1 = inputs[0];
double* in2 = inputs[1];
double* out1 = outputs[0];
double* out2 = outputs[1];
for (int i = 0; i < nFrames; ++i)
{
// Read input signal.
double left = in1[i];
double right = in2[i];
// Apply gain.
left = left * mGain;
right = right * mGain;
// Apply filter.
left = mFilterL.process(left);
right = mFilterR.process(right);
// Apply tremolo.
double lfo = mMyOsc.GetOutput();
mMyOsc.IncPhase();
double tremolo = (lfo + 1) * 0.5;
left = left * tremolo;
right = right * tremolo;
// Write output signal.
out1[i] = left;
out2[i] = right;
}
}
Notice how you now already have a miniture signal chain inside your process loop. You could even change the order (although in this case it wouldn't really matter). Also notice the similarity between applying the gain and applying the tremolo (both do left = left * something).
Anyway, I hope this helps.
|
|
|
08-18-2015, 02:16 PM
|
#21
|
Human being with feelings
Join Date: Jul 2015
Posts: 17
|
Oh it helps a lot! Thankyou
Quote:
I will leave that for you to figure out...
|
Yeah I should probably stop being lazy and do a bit more experimenting myself, it's just that you're spoiling me with your knowledge!
|
|
|
08-23-2015, 06:26 PM
|
#22
|
Human being with feelings
Join Date: Jul 2015
Posts: 17
|
Hey dude.
Since I last posted I managed to do two of the three things I wanted to do with this lfo!
1. implement an on/ off switch which I did with enumerators and an if else statement...
Code:
{ //lfo state switch function
double GetLfoState(double lfo)
{
if (mlfoState == LFO_STATE_ON)
{
return (lfo + 1) * 0.5;
}
else
{
return 1;
}
works like a charm.
2. added a frequency change parameter.
Code:
{ case klfoFrquency:
MyOsc.SetFreq((GetParam(paramIdx)->Int()),GetSampleRate());
break;}
works fine.
3. Route the lfo to a list of given parameters...here's my thoughts
create a list of enumerators
Code:
{enum lfoDestination
{
LFO_DESTINATION_GAIN,
LFO_DESTINATION_CUTOFF,
LFO_DESTINATION_RESONANCE,
LFO_DESTINATION_WIDTH,
kNumLfoDestination
};}
set destination
Code:
{/set lfo destination.
inline void setLfoDestination (lfoDestination newMlfoDestination) {mlfoDestination = newMlfoDestination;}}
Cycle through the enums with a if else function
Code:
{ //lfo destination switch
double GetlfoDestination(double lfo)
{
if(mlfoDestination == LFO_DESTINATION_GAIN)
{
return
}
else if (mlfoDestination == LFO_DESTINATION_CUTOFF)
{
return
else if (mlfoDestination == LFO_DESTINATION_RESONANCE)
{
return
}
else if (mlfoDestination == LFO_DESTINATION_WIDTH)
{
return
}
}}
as you can see there isn't anything in the returns, this is because I don't quite understand how the lfo is applied to the gain in the main function...
Code:
{ // Apply Lfo.
double lfo = MyOsc.GetOutput();
MyOsc.IncPhase();
double tremolo = MyOsc.GetLfoState(lfo);
left = left * tremolo;
right = right * tremolo;}
I understand everything in that function, apart from the way it knows how to modulate the gain specifically... some light please?
Thanks ; )
|
|
|
08-24-2015, 03:01 AM
|
#23
|
Human being with feelings
Join Date: Jul 2008
Location: The Netherlands
Posts: 3,645
|
I guess I would do something like this:
Code:
double tremolo = 1;
// Apply Lfo.
double lfo = MyOsc.GetOutput();
MyOsc.IncPhase();
if (mlfoDestination == LFO_DESTINATION_GAIN)
{
tremolo = MyOsc.GetLfoState(lfo);
}
else if (mlfoDestination == LFO_DESTINATION_CUTOFF)
{
double cutoff = 20 + 19980 * MyOsc.GetLfoState(lfo);
MyFilterL.SetCutoff(cutoff);
}
// etc.
left = left * tremolo;
right = right * tremolo;
left = MyFilterL.Process(left);
right = MyFilterL.Process(right);
|
|
|
08-25-2015, 08:34 PM
|
#24
|
Human being with feelings
Join Date: Jul 2015
Posts: 17
|
Thanks Tale
I understand your logic here but I can't quite translate it.
The GetlfoDestination function is in the Oscillator class.
As expected this works fine.
Code:
{//lfo destination switch
double GetlfoDestination(double lfo)
{
if(mlfoDestination == LFO_DESTINATION_GAIN)
{
return tremolo = GetLfoState(lfo);
}}
The problem comes when applying the modulation to the filter cutoff, resonance and so on.
How does tremolo know to modulate mGain when no reference to mGain is made?
Code:
{void LfoExperiment::ProcessDoubleReplacing(double** inputs, double** outputs, int nFrames)
{
double* in1 = inputs[0];
double* in2 = inputs[1];
double* out1 = outputs[0];
double* out2 = outputs[1];
for (int i = 0; i < nFrames; ++i)
{
// Read input signal.
double left = in1[i];
double right = in2[i];
// Apply gain.
left = left * mGain;
right = right * mGain;
// Apply filter.
left = mFilterL.process(left);
right = mFilterR.process(right);
// Apply Lfo.
double lfo = MyOsc.GetOutput();
MyOsc.IncPhase();
double tremolo = MyOsc.GetlfoDestination(lfo);
left = left * tremolo;
right = right * tremolo;
// Write output signal.
out1[i] = left;
out2[i] = right;
}
}
}
I have a set cutoff function in my filter class, I've included the filter class .h below.
Code:
{class Filter {
public:
enum FilterMode
{
FILTER_MODE_LOWPASS = 0,
FILTER_MODE_HIGHPASS,
FILTER_MODE_BANDPASS,
kNumFilterModes
};
Filter() :
cutoff(0.99),
resonance(0.0),
cutoffMod(0.0),
mode(FILTER_MODE_LOWPASS),
buf0(0.0),
buf1(0.0),
buf2(0.0),
buf3(0.0)
{
calculateFeedbackAmount();
};
double process(double inputValue);
inline void setCutoff(double newCutoff) { cutoff = newCutoff; calculateFeedbackAmount(); };
inline void setResonance(double newResonance) { resonance = newResonance; calculateFeedbackAmount(); };
inline void setFilterMode(FilterMode newMode) { mode = newMode; }
inline void setCutoffMod(double newCutoffMod)
{
cutoffMod = newCutoffMod;
calculateFeedbackAmount();
}
private:
double cutoff;
double resonance;
FilterMode mode;
double feedbackAmount;
inline void calculateFeedbackAmount() {
feedbackAmount = resonance + resonance/(1.0 - getCalculatedCutoff());
}
double buf0;
double buf1;
double buf2;
double buf3;
double cutoffMod;
inline double getCalculatedCutoff() const {
return fmax(fmin(cutoff + cutoffMod, 0.99), 0.01);
};
};}
Could you explain the numbers in the cutoff modulation statement you provided? Are they supposed to represent the minimum and maximum hz?
I don't understand how the below method returns a double, surely in order to modulate the parameter it needs to return a value.
Code:
{else if (mlfoDestination == LFO_DESTINATION_CUTOFF)
{
double cutoff = 20 + 19980 * MyOsc.GetLfoState(lfo);
MyFilterL.SetCutoff(cutoff);
}}
Sorry to keep firing questions at you, I'm close to getting this done but I want to understand everything that is implemented.
Major thanks mate.
|
|
|
08-26-2015, 12:50 AM
|
#25
|
Human being with feelings
Join Date: Jul 2008
Location: The Netherlands
Posts: 3,645
|
Quote:
Originally Posted by Placid Mouse
As expected this works fine.
Code:
{//lfo destination switch
double GetlfoDestination(double lfo)
{
if(mlfoDestination == LFO_DESTINATION_GAIN)
{
return tremolo = GetLfoState(lfo);
}}
|
Yeah, but the problem is that you need to call some of the filter code when the destination is the filter cutoff. But when the destination is something else, you will have to call that code. However, the LFO class doesn't have access to the filter and other objects. That is why I would suggest you let change GetlfoDestination() to:
Code:
lfoDestination GetlfoDestination() { return mlfoDestination; }
And then move the if(mlfoDestination == ...) to ProcessDoubleReplacing(), which does have access to all objects.
Quote:
Originally Posted by Placid Mouse
How does tremolo know to modulate mGain when no reference to mGain is made?
|
It does not. In fact, if you remove mGain altogether, then the tremolo will still work.
Let me try to explain: If you apply the filter, then you replace the input with the filter's output, right? I.e.:
Code:
left = mFilterL.process(left);
When you apply the tremolo you do the same, except that here the process function for the tremolo is simply a single multiplication:
Code:
left = left * tremolo;
// You could write a tremolo class and do:
// left = mTremoloL.process(left);
Quote:
Originally Posted by Placid Mouse
Could you explain the numbers in the cutoff modulation statement you provided? Are they supposed to represent the minimum and maximum hz?
|
Yeah. I was assuming that GetLfoState() returns a value in the [0.0, 1.0], so that would place the cutoff in the [20, 20000] range. Note that you might want to change the curve (currently it is linear), but you can do that later.
Quote:
Originally Posted by Placid Mouse
I don't understand how the below method returns a double, surely in order to modulate the parameter it needs to return a value.
|
As I explained above, it shouldn't return anything, just insert it inside ProcessDoubleReplacing().
Quote:
Originally Posted by Placid Mouse
Sorry to keep firing questions at you, I'm close to getting this done but I want to understand everything that is implemented.
|
No problem.
Quote:
Originally Posted by Placid Mouse
Major thanks mate.
|
You're welcome.
|
|
|
08-26-2015, 10:58 PM
|
#26
|
Human being with feelings
Join Date: Jul 2015
Posts: 17
|
Thanks for explaining this man, I get it now!!! I'm still having trouble with this if else selector though.
At the moment I can only hear the gain being modulated no matter what the lfodestination parameter is set to.
The enumerators, initialiser list and set lfo destination function are all in Oscillator.h. This is exactly the same way I implemented the 'lfostate' switch and that works fine.
here is my code for processDoubleReplacing.
Code:
{
double* in1 = inputs[0];
double* in2 = inputs[1];
double* out1 = outputs[0];
double* out2 = outputs[1];
for (int i = 0; i < nFrames; ++i)
{
// Read input signal.
double left = in1[i];
double right = in2[i];
// Apply gain.
left = left * mGain;
right = right * mGain;
// Apply filter.
left = mFilterL.process(left);
right = mFilterR.process(right);
// Apply LFO.
double lfo = mOscillator.GetOutput();
mOscillator.IncPhase();
double tremolo = 1;
if (mlfoDestination == Oscillator::LFO_DESTINATION_GAIN)
{
tremolo = mOscillator.GetLfoState(lfo);
}
else if (mlfoDestination == Oscillator::LFO_DESTINATION_CUTOFF)
{
double cutoff = 20 + 19980 * mOscillator.GetLfoState(lfo);
mFilterL.setCutoffMod(cutoff);
}
left = left * tremolo;
right = right * tremolo;
// Write output signal.
out1[i] = left;
out2[i] = right;
}
}
When you posted the code earlier you didn't have the Oscillator::LFO_DESTINATION_GAIN, just
:LFO_DESTINATION_GAIN. My enumerators are in the Oscillator class.
The mlfoDestination in that function is a member object defined in the main.h
I don't understand why it will still output a gain modulation even though the destination switch is set to LFO_DESTINATION_CUTOFF.
Here's how I am implementing the graphics.
On param change:
Code:
case klfoDestination:
mOscillator.setLfoDestination(static_cast<Oscillator::lfoDestination>(GetParam(paramIdx)->Int()));
break;
Main Class constructor:
Code:
//Lfo Destination switch
GetParam(klfoDestination)->InitEnum("Lfo Destination", Oscillator::LFO_DESTINATION_GAIN, Oscillator::kNumLfoDestination);
IBitmap lfoDestinationBitmap = pGraphics->LoadIBitmap(LFODESTINATION_ID, LFODESTINATION_FN, 4);
pGraphics->AttachControl(new ISwitchControl(this, 600, 300, klfoDestination, &lfoDestinationBitmap));
Set lfo destination function inside Oscillator.h
Code:
//set lfo destination.
inline void setLfoDestination (lfoDestination newMlfoDestination) {mlfoDestination = newMlfoDestination;}
I even tried this to see if the signal would cut out.
Code:
else if (mlfoDestination == Oscillator::LFO_DESTINATION_CUTOFF)
{
tremolo = 0;
}
Oh and I presumed that I should create member functions in my main header file.
Code:
Oscillator mOscillator;
Oscillator::lfoDestination mlfoDestination;
Man I really want this to work. I only have until the weekend until I need to show the final version of the plug in to my peers.
Just this and the stereo width thing to sort.
By the way is smoothing the square wave an easy thing to do?
Thankyou Tale, you are my hero.
|
|
|
08-27-2015, 12:18 AM
|
#27
|
Human being with feelings
Join Date: Jul 2008
Location: The Netherlands
Posts: 3,645
|
Quote:
Originally Posted by Placid Mouse
Code:
if (mlfoDestination == Oscillator::LFO_DESTINATION_GAIN)
{
tremolo = mOscillator.GetLfoState(lfo);
}
else if (mlfoDestination == Oscillator::LFO_DESTINATION_CUTOFF)
{
double cutoff = 20 + 19980 * mOscillator.GetLfoState(lfo);
mFilterL.setCutoffMod(cutoff);
}
|
I think the problem is that you set Oscillator::mlfoDestination, but you check the mlfoDestination from your main header file. So I would remove
mlfoDestination from your main header file, and change the code like this:
Code:
Oscillator::lfoDestination lfoDest = mOscillator.GetlfoDestination();
if (lfoDest == Oscillator::LFO_DESTINATION_GAIN)
{
tremolo = mOscillator.GetLfoState(lfo);
}
else if (lfoDest == Oscillator::LFO_DESTINATION_CUTOFF)
{
double cutoff = 20 + 19980 * mOscillator.GetLfoState(lfo);
mFilterL.setCutoffMod(cutoff);
}
BTW, I would probably move the filter processing, so it comes after the LFO (i.e. first set cutoff, then process). And don't forget to also update the cutoff of the right channel filter.
|
|
|
08-27-2015, 07:17 PM
|
#28
|
Human being with feelings
Join Date: Jul 2015
Posts: 17
|
I GOT IT TO WORK!!!!
AMAZING THANKS MAN!!!!
I made a slight variation to your code. Seeing as I have already declared the value of the cutoff, I set that to 1 instead. It now works on the resonance also : )
Again you're my hero. Here's my updated code.
Code:
// Apply LFO.
double lfo = mOscillator.GetOutput();
mOscillator.IncPhase();
Oscillator::lfoDestination lfoDest = mOscillator.GetlfoDestination();
double tremolo = 1;
double cutoff = 1;
double resonance = 1;
double width = 1;
if (lfoDest == Oscillator::LFO_DESTINATION_GAIN)
{
tremolo = mOscillator.GetLfoState(lfo);
}
else if (lfoDest == Oscillator::LFO_DESTINATION_CUTOFF)
{
cutoff = mOscillator.GetLfoState(lfo);
mFilterL.setCutoff(cutoff);
mFilterR.setCutoff(cutoff);
}
else if (lfoDest == Oscillator::LFO_DESTINATION_RESONANCE)
{
resonance = mOscillator.GetLfoState(lfo);
mFilterL.setResonance(resonance);
mFilterR.setResonance(resonance);
}
else if (lfoDest == Oscillator::LFO_DESTINATION_WIDTH)
{
width = mOscillator.GetLfoState(lfo);
mStereoWidth.setWidth(width);
}
left = left * tremolo;
right = right * tremolo;
Ok so only a few more things to do, for this weekend. I will endeavour to make it a lot better in the future, that list is infinite though!!!
1. Smooth the square, any ideas/ are you able to point me toward some resources on how to do this?
2. Sort the stereo width out, I'll have a play with this.
3. Render it as a vst that others can use. I don't know how to do this, anyone know of any documentation/ tutorials on this. I am currently testing it as a vst and au in debug mode inside of reaper.
Also there's a ton of bugs, like output cut outs when combining certain parameters.
With regards to this do plug-ins generally have built in limiters? I was thinking of quickly putting one in.
Take Care
|
|
|
08-28-2015, 12:57 AM
|
#29
|
Human being with feelings
Join Date: Jul 2008
Location: The Netherlands
Posts: 3,645
|
Quote:
Originally Posted by Placid Mouse
I GOT IT TO WORK!!!!
|
Cool!
Quote:
Originally Posted by Placid Mouse
I made a slight variation to your code. Seeing as I have already declared the value of the cutoff, I set that to 1 instead. It now works on the resonance also : )
|
Actually you don't need to init the cutoff/resonance/width variables, because they are only used as intermediate variables. Note that this is not the case for tremolo, you do need to init that one.
Quote:
Originally Posted by Placid Mouse
1. Smooth the square, any ideas/ are you able to point me toward some resources on how to do this?
|
I would use a 1st order (single pole) low-pass filter for this. This will smooth out any sudden changes.
Quote:
Originally Posted by Placid Mouse
3. Render it as a vst that others can use. I don't know how to do this, anyone know of any documentation/ tutorials on this. I am currently testing it as a vst and au in debug mode inside of reaper.
|
Well, if you set the build config to release, then basically you should be good to go.
|
|
|
Thread Tools |
|
Display Modes |
Linear Mode
|
Posting Rules
|
You may not post new threads
You may not post replies
You may not post attachments
You may not edit your posts
HTML code is Off
|
|
|
All times are GMT -7. The time now is 06:05 PM.
|