Specify Waveshaper Function

With which type is your waveshaper instance templated? As you need to specify the FloatType you want to use in your wave shaper instance like

Waveshaper<float> myFloatWaveshaper;
Waveshaper<double> myDoubleWaveshaper;

You need to use the same type in your functionToUse lambda signature, like e.g.

myFloatWaveshaper.functionToUse = [] (float x)
{
     return std::tanh (x);
};

myDoubleWaveshaper.functionToUse = [] (double x)
{
     return std::tanh (x);
};

Does this answer your question?

Edit: To keep it even more generic, auto should work as type too

whateverWaveshaper.functionToUse = [] (auto x) { return std::tanh (x); };

My waveshaper instance is declared in a ProcessorChain like this:
juce::dsp::ProcessorChain<juce::dsp::Gain<Type>, juce::dsp::WaveShaper<Type>, juce::dsp::Gain<Type>> processorChain;

What I want to do is multiply the x in the function with a parameter:

waveshaper.functionToUse = [] (Type x)
        {
            return std::tanh (myParam * x);
        };

I get an error if I do it like above. Does this make sense?

First thing: There are so many kinds of errors, if you would post the error message helping you would be easier and giving you hints how to learn to read and interpret error messages yourself (and I guess that is what you want to be able to in future :wink: ) would be possible too.

Second thing: When reading your code the first question that comes to my mind is: Were is Type declared and what do you assign to it? Is it a template of the outer class, is there some using or typedef directive in the surrounding code? Some bit more of surrounding information helps as well to help you.

Third thing: What is myParam? Where is it declared? In case it’s not a static variable (and I don’t guess it is) you need to capture it in one of the ways possible (by copy, by reference or you can capture your outer class ‘this’ pointer) – which type of capture you chose is dependent on what you want to do. Otherwise it is not visible to your compiler inside the lambda – is this maybe the error you get?

I tried to keep it short; sorry for omitting details. You are right.

Type is defined as a template as such:
template <typename Type>

myParam was used as an example of a parameter, it is a simple float declared in the private section of the same class. It is not static.

So, simply multiplying x by this variable like this…:
return std::tanh (myParam * x);

results in this error at the same line:
‘this’ cannot be implicitly captured in this context

How am I able to capture this parameter?

Thank you very much for your help!

you can use either
[this]
[=]
[myParam]

https://docs.microsoft.com/en-us/cpp/cpp/lambda-expressions-in-cpp?view=vs-2019

Thank you Olivier! Unfortunately, all of these result in the same error:

Assigning to ‘float (*)(float)’ from incompatible type

Is this maybe a problem with the Type template?

No, that shouldn’t be a problem. Since you don’t specify the return type, it might be possible that the deducted return type of that function is something different than a float? This could happen if e.g. myParam would be a double and Type a float (or the other way round). In this case your compiler will convert them multiplication into something like float * double = double and then chose the double overload of std::tanh which will lead to a double return type. You could try if explicit casting works, like e.g.

waveshaper.functionToUse = [this] (Type x)
{
    auto paramCasted = static_cast<Type> (myParam);
    return std::tanh (paramCasted * x);
};

or

waveshaper.functionToUse = [this] (Type x)
{
    auto value = std::tanh (myParam * x);
    return static_cast<Type> (value);
};

However, as with the right kind of capture type, also the right choice for a solution for type casting thing (if this is really the problem here) depends on the use case, e.g. the desired precision you need.

I see what you are saying. The error I mentioned above (Assigning to ‘float (*)(float)’ from incompatible type) already happens at the line where this is supposed to be captured. So the code you provided wont even run unfortunately.

Mhm, I think you are mixing up run-time interpreted and compiled language concepts here. The error you are seeing is appearing at compile time, e.g. even before the code is run. It is displayed exactly at the line where something doesn’t match. And indeed, this is the case at the = sign in the line waveshaper.functionToUse = [this] (Type x). On the left side a function pointer to a function that take as float and return a float is expected. On the right side you specify a lambda that takes a float (aka Type in this case) and returns probably something else. The compiler deducts what “something else” is from the lambda body below, so even if the lambda body is a correct piece of C++ itself, the assignment expression above doesn’t match, therefore this is the line of code where the error is displayed. If you change the lambda body, the compiler will deduct a different function signature from the code it reads and the assignment above will become valid again.

And as long as there is any compiler error, no part of the code will ever be executed as the resulting executable won’t be generated at all

Thanks for the explanation. I was able to follow. I did try to plug in your code from above, but the same error persists. Do you maybe have another idea?

The best you can do is post your whole code or a reduced core part of it that is sufficient to reproduce the error :slight_smile:

Ok here we go:
This is the distortion class:

#pragma once
#include <JuceHeader.h>

template <typename Type>
class Distortion
{
public:
    Distortion()
    {
        auto& preGain = processorChain.template get<preGainIndex>();
        preGain.setGainDecibels (30.0f);

        auto& postGain = processorChain.template get<postGainIndex>();
        postGain.setGainDecibels (0.0f);
    }

    ~Distortion()
    {
    }
    
    //==============================================================================
    void prepare (const juce::dsp::ProcessSpec& spec)
    {
        auto& waveshaper = processorChain.template get<waveshaperIndex>();
        waveshaper.functionToUse = [this] (Type x)
        {
            //return std::tanh(x);
            auto paramCasted = static_cast<Type> (myParam);
            return std::tanh (paramCasted * x);
        };
        
        auto& filter = processorChain.template get<filterIndex>();
        filter.state = FilterCoefs::makeFirstOrderHighPass (spec.sampleRate, 1000.0f);

        processorChain.prepare (spec);
    }
    
    //==============================================================================
    template <typename ProcessContext>
    void process (const ProcessContext& context) noexcept
    {
        processorChain.process (context);
    }
    
    //==============================================================================
    void reset() noexcept
    {
        processorChain.reset();
    }
    
private:
    
    enum
    {
        filterIndex,
        preGainIndex,
        waveshaperIndex,
        postGainIndex
    };

    using Filter = juce::dsp::IIR::Filter<Type>;
    using FilterCoefs = juce::dsp::IIR::Coefficients<Type>;

    juce::dsp::ProcessorChain<juce::dsp::ProcessorDuplicator<Filter, FilterCoefs>, juce::dsp::Gain<Type>, juce::dsp::WaveShaper<float>, juce::dsp::Gain<Type>> processorChain;
    
    float myParam = 1.0f;
    
    JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Distortion)
};

Which is being instantiated in the private section of my PluginProcessor like so:
Distortion<float> distortion;

Prepared in the AudioProcessor::prepareToPlay like this:
distortion.prepare({ sampleRate, (uint32) samplesPerBlock, 2 });

And finally being processed in the AudioProcessor::processBlock like so:

auto block = juce::dsp::AudioBlock<float>(buffer);
auto context = juce::dsp::ProcessContextReplacing<float> (block);
distortion.process(context);

I hope this is clear enough? Thank You!

The waveshaper can’t use a lambda with captures (like [this]) as the waveshaping function.

your functionToUse should be of type std::function<float(float)> and it should work fine

Thanks Olivier, would you mind elaborating how that might be done? Thank you

don t use dsp::WaveShaper and just have a single std::function to apply your waveshaper on a buffer.
Please note it’s probably not the optimal way to do it and you should probably use something like described there

1 Like

In your class you have

in the ProcessorChain, should that be juce::dsp::WaveShaper<Type> instead?

oh yes, good catch. Fortunately it works either way.

Ok, with some sleep, re-reading the whole thing, the problem becomes obvious:

The waveshaper class expects a c-style function pointer as functionToUse – this is defined by the default parameter assigned to the second template argument. I had a std::function object in mind when writing the above. While you can assign all kinds of things that have a call operator () to a std::function, including function pointers and lambdas, you cannot assign all kinds of callable objects to a raw function pointer. As one special exception, the compiler will convert lambdas without any capture parameter into some in-place declared function, so in this special case, lambdas are supported as functionToUse.

As I was working with the waveshaper in combination with capture-less lambdas recently I didn’t have this clearly in mind. However, you are free to change the second template arg to what suits your case. So if you specify your waveshaper like this:

juce::dsp::WaveShaper<Type, std::function<Type(Type)>>

Your lambda with captures above should become assignable to the waveshaper function!

That being said, you can of course directly implement what the waveshaper does internally in your own member function, it’s not that complex. Or, as all you do with your parameter is adding up some gain before processing the tanh function, you could also consider to just move this gain stage into the preGain element right before the waveshaper in your processor chain

1 Like

Ah yes! This is it. Thank you Mr Penguin :smile:
This will do for now, but as you said it would make sense to implement my own waveshaping down the line.
Thank you for your help on this, I really appreciate it. This helps me loads on my project.