COCKOS
CONFEDERATED FORUMS
Cockos : REAPER : NINJAM : Forums
Forum Home : Register : FAQ : Members List : Search :

Go Back   Cockos Incorporated Forums > Other Software Discussion > WDL users forum

Reply
 
Thread Tools Display Modes
Old 07-24-2015, 11:23 AM   #1
Placid Mouse
Human being with feelings
 
Placid Mouse's Avatar
 
Join Date: Jul 2015
Posts: 17
Default 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
Placid Mouse is offline   Reply With Quote
Old 07-24-2015, 11:56 AM   #2
Xenakios
Human being with feelings
 
Xenakios's Avatar
 
Join Date: Feb 2007
Location: Oulu, Finland
Posts: 7,673
Default

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.)
__________________
For info on SWS Reaper extension plugin (including Xenakios' previous extension/actions) :
http://www.sws-extension.org/
https://github.com/Jeff0S/sws
--
Xenakios blog (about HourGlass, Paul(X)Stretch and λ) :
http://xenakios.wordpress.com/
Xenakios is online now   Reply With Quote
Old 07-26-2015, 03:17 PM   #3
Placid Mouse
Human being with feelings
 
Placid Mouse's Avatar
 
Join Date: Jul 2015
Posts: 17
Default

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!
Placid Mouse is offline   Reply With Quote
Old 07-26-2015, 04:27 PM   #4
Xenakios
Human being with feelings
 
Xenakios's Avatar
 
Join Date: Feb 2007
Location: Oulu, Finland
Posts: 7,673
Default

Quote:
Originally Posted by Placid Mouse View Post
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.
__________________
For info on SWS Reaper extension plugin (including Xenakios' previous extension/actions) :
http://www.sws-extension.org/
https://github.com/Jeff0S/sws
--
Xenakios blog (about HourGlass, Paul(X)Stretch and λ) :
http://xenakios.wordpress.com/
Xenakios is online now   Reply With Quote
Old 08-11-2015, 03:12 PM   #5
Placid Mouse
Human being with feelings
 
Placid Mouse's Avatar
 
Join Date: Jul 2015
Posts: 17
Default

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

Code:
{mStereoWidth,}
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
Placid Mouse is offline   Reply With Quote
Old 08-11-2015, 11:09 PM   #6
Tale
Human being with feelings
 
Tale's Avatar
 
Join Date: Jul 2008
Location: The Netherlands
Posts: 2,932
Default

Quote:
Originally Posted by Placid Mouse View Post
add to EParams

Code:
{mStereoWidth,}
You can't use the symbol mStereoWidth here, because you have already used it for something else, so I would suggest:

Code:
{kStereoWidth,}
Quote:
Originally Posted by Placid Mouse View Post
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 View Post
Code:
{ case mStereoWidth:
      mStereoWidth.setWidth(GetParam(paramIdx)->Value());
      
      break;}
And one more:

Code:
case kStereoWidth:
Tale is offline   Reply With Quote
Old 08-12-2015, 08:10 PM   #7
Placid Mouse
Human being with feelings
 
Placid Mouse's Avatar
 
Join Date: Jul 2015
Posts: 17
Default

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 ; )
Placid Mouse is offline   Reply With Quote
Old 08-12-2015, 11:17 PM   #8
Tale
Human being with feelings
 
Tale's Avatar
 
Join Date: Jul 2008
Location: The Netherlands
Posts: 2,932
Default

Quote:
Originally Posted by Placid Mouse View Post
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]);
Tale is offline   Reply With Quote
Old 08-13-2015, 12:28 PM   #9
Placid Mouse
Human being with feelings
 
Placid Mouse's Avatar
 
Join Date: Jul 2015
Posts: 17
Default

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.
Placid Mouse is offline   Reply With Quote
Old 08-13-2015, 02:52 PM   #10
Tale
Human being with feelings
 
Tale's Avatar
 
Join Date: Jul 2008
Location: The Netherlands
Posts: 2,932
Default

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;
};
Tale is offline   Reply With Quote
Old 08-13-2015, 06:31 PM   #11
Placid Mouse
Human being with feelings
 
Placid Mouse's Avatar
 
Join Date: Jul 2015
Posts: 17
Default

Does this mean there is no use for the header file for this class?
Placid Mouse is offline   Reply With Quote
Old 08-13-2015, 11:06 PM   #12
Tale
Human being with feelings
 
Tale's Avatar
 
Join Date: Jul 2008
Location: The Netherlands
Posts: 2,932
Default

Yeah... Although I guess it might be better to split it up into header and actual source, even for such a small class.
Tale is offline   Reply With Quote
Old 08-14-2015, 06:06 PM   #13
Placid Mouse
Human being with feelings
 
Placid Mouse's Avatar
 
Join Date: Jul 2015
Posts: 17
Default

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.
Placid Mouse is offline   Reply With Quote
Old 08-15-2015, 02:01 AM   #14
Tale
Human being with feelings
 
Tale's Avatar
 
Join Date: Jul 2008
Location: The Netherlands
Posts: 2,932
Default

Quote:
Originally Posted by Placid Mouse View Post
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
Tale is offline   Reply With Quote
Old 08-15-2015, 08:12 PM   #15
Placid Mouse
Human being with feelings
 
Placid Mouse's Avatar
 
Join Date: Jul 2015
Posts: 17
Default

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?
Placid Mouse is offline   Reply With Quote
Old 08-16-2015, 02:25 AM   #16
Tale
Human being with feelings
 
Tale's Avatar
 
Join Date: Jul 2008
Location: The Netherlands
Posts: 2,932
Default

Quote:
Originally Posted by Placid Mouse View Post
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 View Post
Also should "mSampleRate" be declared along with the other doubles?
Oops, that should have been sampleRate (parameter), not mSampleRate.
Tale is offline   Reply With Quote
Old 08-16-2015, 07:54 PM   #17
Placid Mouse
Human being with feelings
 
Placid Mouse's Avatar
 
Join Date: Jul 2015
Posts: 17
Default

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 ; )
Placid Mouse is offline   Reply With Quote
Old 08-16-2015, 11:28 PM   #18
Tale
Human being with feelings
 
Tale's Avatar
 
Join Date: Jul 2008
Location: The Netherlands
Posts: 2,932
Default

Quote:
Originally Posted by Placid Mouse View Post
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 View Post
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 View Post
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;
  }
}
Tale is offline   Reply With Quote
Old 08-17-2015, 12:26 AM   #19
Placid Mouse
Human being with feelings
 
Placid Mouse's Avatar
 
Join Date: Jul 2015
Posts: 17
Default

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?
Placid Mouse is offline   Reply With Quote
Old 08-17-2015, 02:18 PM   #20
Tale
Human being with feelings
 
Tale's Avatar
 
Join Date: Jul 2008
Location: The Netherlands
Posts: 2,932
Default

Quote:
Originally Posted by Placid Mouse View Post
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 View Post
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 View Post
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.
Tale is offline   Reply With Quote
Old 08-18-2015, 02:16 PM   #21
Placid Mouse
Human being with feelings
 
Placid Mouse's Avatar
 
Join Date: Jul 2015
Posts: 17
Default

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!
Placid Mouse is offline   Reply With Quote
Old 08-23-2015, 06:26 PM   #22
Placid Mouse
Human being with feelings
 
Placid Mouse's Avatar
 
Join Date: Jul 2015
Posts: 17
Default

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 ; )
Placid Mouse is offline   Reply With Quote
Old 08-24-2015, 03:01 AM   #23
Tale
Human being with feelings
 
Tale's Avatar
 
Join Date: Jul 2008
Location: The Netherlands
Posts: 2,932
Default

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);
Tale is offline   Reply With Quote
Old 08-25-2015, 08:34 PM   #24
Placid Mouse
Human being with feelings
 
Placid Mouse's Avatar
 
Join Date: Jul 2015
Posts: 17
Default

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.
Placid Mouse is offline   Reply With Quote
Old 08-26-2015, 12:50 AM   #25
Tale
Human being with feelings
 
Tale's Avatar
 
Join Date: Jul 2008
Location: The Netherlands
Posts: 2,932
Default

Quote:
Originally Posted by Placid Mouse View Post
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 View Post
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 View Post
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 View Post
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 View Post
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 View Post
Major thanks mate.
You're welcome.
Tale is offline   Reply With Quote
Old 08-26-2015, 10:58 PM   #26
Placid Mouse
Human being with feelings
 
Placid Mouse's Avatar
 
Join Date: Jul 2015
Posts: 17
Default

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.

Placid Mouse is offline   Reply With Quote
Old 08-27-2015, 12:18 AM   #27
Tale
Human being with feelings
 
Tale's Avatar
 
Join Date: Jul 2008
Location: The Netherlands
Posts: 2,932
Default

Quote:
Originally Posted by Placid Mouse View Post
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.
Tale is offline   Reply With Quote
Old 08-27-2015, 07:17 PM   #28
Placid Mouse
Human being with feelings
 
Placid Mouse's Avatar
 
Join Date: Jul 2015
Posts: 17
Default

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
Placid Mouse is offline   Reply With Quote
Old 08-28-2015, 12:57 AM   #29
Tale
Human being with feelings
 
Tale's Avatar
 
Join Date: Jul 2008
Location: The Netherlands
Posts: 2,932
Default

Quote:
Originally Posted by Placid Mouse View Post
I GOT IT TO WORK!!!!
Cool!

Quote:
Originally Posted by Placid Mouse View Post
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 View Post
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 View Post
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.
Tale is offline   Reply With Quote
Reply

Thread Tools
Display Modes

Posting Rules
You may not post new threads
You may not post replies
You may not post attachments
You may not edit your posts

BB code is On
Smilies are On
[IMG] code is On
HTML code is Off

Forum Jump


All times are GMT -7. The time now is 04:42 AM.


Powered by vBulletin® Version 3.8.11
Copyright ©2000 - 2019, vBulletin Solutions Inc.