SmoothedValue: should you call getNextValue() for the first sample?

If you are using SmoothedValue to compute values across a buffer in ProcessBlock, should you call getNextValue() starting with the first sample, or use the start value for the first sample and then call getNextValue() for each subsequent sample? Not clear to me…

SmoothedValue is derived from SmoothedValueBase which has 2 functions to remember to use:

getCurrentValue() and setCurrentAndTargetValue()

getNextValue() increments and returns the next value:

    FloatType getNextValue() noexcept
    {
        if (! this->isSmoothing())
            return this->target;

        --(this->countdown);

        if (this->isSmoothing())
            setNextValue();
        else
            this->currentValue = this->target;

        return this->currentValue;
    }

setNextValue() does the increment:

    template <typename T = SmoothingType>
    LinearVoid<T> setNextValue() noexcept
    {
        this->currentValue += step;
    }

    template <typename T = SmoothingType>
    MultiplicativeVoid<T> setNextValue() noexcept
    {
        this->currentValue *= step;
    }

One option would be to set a member variable in your prepareToPlay as your initial value that you want your smoothing to start with:

void prepareToPlay(...)
{
    //after you've set up your smoother, init your member variable
    smootherStart = smoother.getCurrentValue(); //the initial value your smoother starts at
}

void processBlock(...)
{
    for( all your samples... )
    {
        sample *= smootherStart; //read from the member variable
        smootherStart = smoother.getNextValue(); //update it after reading
    }
}

This will ensure that the first time you run the plugin, your processing begins with the initial value your smoother is initialized with in prepareToPlay

If you were to call getNextValue() directly, the value your smoother is supposed to start at would never be hit. This can be a source of clicks and pops if you’re doing a volume ramp that should start at 0, but inadvertently starts at 0.001.

Hope that helps.

ehm… no

in audioprocessor::prepareToPlay()… and audioprocessor::reset()

call smoother.reset (sampleRate, rampLengthInSeconds)
call smoother.setCurrentAndTargetValue (parameterValue)

in processBlock

call setTargetValue (parameterValue) ONCE at the beginning of processBlock

and

just call smoother.getNextValue(); before each sample iteration to get the current value (but be aware if you have an outer loop for your channels)

1 Like

ah, right right. the first time the processBlock is ever called, the smoother’s target and current value will be the parameterValue set in prepareToPlay, so getNextValue() will return that param value. good call.

It would work well just to use the inverse value of the out fading value:

auto gain = smoother.getNextValue();
sample = gain * endingSound;
sample += (1.0 - gain) * startingSound;

just one of many solutions… another would be to have a SmoothedValue for each, in case it is not always the same length for fading out and fading in.

Ok, well that’s easy as well. In this case I wouldn’t use SmoothedValue:

buffer.copyFromWithRamp (channel, 0, fadeOutSound.getReadPointer (channel), 
                         buffer.getNumSamples(), 1.0f, 0.0f);
buffer.addFromWithRamp (channel, 0, fadeInSound.getReadPointer (channel), 
                        buffer.getNumSamples(), 0.0f, 1.0f);

However I would advise against using the block size as fade length, since that can vary and will change your sound at random.

A better apploach is to allow the crossfde to start at any position within a block and possibly span over multiple blocks.

1 Like

Back to the original question: :wink:

I’m not sure why anything needs to be done in prepareToPlay, at least in my case. I am using the SmoothedValue to smooth a “pan” setting that results in some different adding together of the samples in the block. In processBlock, each sample is processed. So I need to smooth the value over each block. PrepareToPlay is not called for every block…

There is a pan member variable, and a prevPan. So I want to smooth from the prevPan to the pan value. So I was doing something like this, in processBlock (temporary handling of only a stereo bus):

void PanProcessor::processBlock(AudioBuffer<float>& buffer, MidiBuffer& midiMessages)
{
	int numSamples = buffer.getNumSamples();
	int numChannels = buffer.getNumChannels();
	jassert(numChannels == 2);
         
	float* loutPtr = buffer.getWritePointer(0);
	float* routPtr = buffer.getWritePointer(1);
                
	SmoothedValue<float>panSmoother(prevPan);
	panSmoother.reset(numSamples);
	panSmoother.setTargetValue(pan);
                
	while (numSamples-- > 0)
	{
	    auto left = *loutPtr;
	    auto right = *routPtr;
	    float newLeft, newRight;

	    auto smoothedPan = panSmoother.getNextValue();
                    
            // do something here with the samples and smoothedPan value, i.e.
            // newLeft = blah-blah;
            // newRight = blah-blah;

            *loutPtr++ = newLeft;
	    *routPtr++ = newRight;
	}
	prevPan = pan;
}

Is that not correct, or could it be improved? I guess maybe it would be better to make the SmoothedValue a member variable so it isn’t constantly being created and destroyed?

And my original question was specifically: in the first call to smoothedPan = panSmoother.getNextValue(), does it return the current value first, or the first computed value? In thinking about it, I guess it really doesn’t matter, since I am already at the “pan” level from the previous block.

Because that’s the only function that is guaranteed to be called by the host so you can set up your DSP params with the host’s sample rate, which is kind of important lol

As I posted originally, you can look at the implementation directly to answer the question.

Thanks. I see, the answer is “yes it does return the incremented value the first time.”

My confusion in asking that comes from the fact, I believe, that I was using this class wrong. I was attempting to use it locally - in which case you would not do anything in prepareToPlay. But now I get it, that’s the wrong way.

If you have it instantiated as a member variable, and then initialize it in prepareToPlay, the current value will always be correct and you only need to continuously update the targetValue and steps in processBlock. Is that correct? i.e. like the following pseudo-code?

//member variables:

float param;                 
float prevParam;
SmoothedValue<float>paramSmoother;

void prepareToPlay (double sampleRate, int maximumExpectedSamplesPerBlock)
{
    prevParam = param;

    paramSmoother.reset(sampleRate, maximumExpectedSamplesPerBlock);
    paramSmoother.setCurrentAndTargetValue(prevParam);
};

void processBlock(AudioBuffer<float>& buffer, MidiBuffer& midiMessages)
{
	int numSamples = buffer.getNumSamples();
	int numChannels = buffer.getNumChannels();
	jassert(numChannels == 2);
         
	float* loutPtr = buffer.getWritePointer(0);
	float* routPtr = buffer.getWritePointer(1);
                
	paramSmoother.reset(numSamples);
	paramSmoother.setTargetValue(param);
                
	while (numSamples-- > 0)
	{
		auto left = *loutPtr;
		auto right = *routPtr;
		float newLeft, newRight;

		auto smoothedParam = paramSmoother.getNextValue();
                    
		// do something here with the samples and smoothedParam value

		*loutPtr++ = newLeft;
		*routPtr++ = newRight;
	}
	prevParam = param;
}

If you see anything wrong with this, please let me know. It seems to be working. I’m pretty new to audio buffer processing.

Hey I was curious about this idea of only calling setTargetValue once per processBlock - care to elaborate? Is that for performance reasons?

there are several mistakes in the code you posted (I know the post is old, but just wanted to mention it in case someone read it now and take it as an example).

. in prepareToPlay you’re doing :
paramSmoother.reset(sampleRate, maximumExpectedSamplesPerBlock);

this will set up a ramp time of “maximumExpectedSamplesPerBlock” seconds.
you probably want to do something like that instead: paramSmoother.reset (sampleRate, 0.01f);
to have a constant ramp time of 10ms or whatever.

. in processBlock you’re calling that :
paramSmoother.reset(numSamples);

this will set the ramp time to ‘numSamples’ samples. this is not a good idea, because the number of samples can dramatically change from one processBlock call to another, so there’s no way to know what will be your ramp time if you do that. It can even (and will) be 1 sample in some hosts.

But anyway, you should not call reset() in the process block, because when you do so the smoothedValue will jump to its target value. So the code you posted above will not correctly smooth the value.