How change volume of a sampler sound without audio glitches?

I’m working with the built in sampler (te::SamplerPlugin) of the tracktion engine. I get audio glitches and very high cpu usage while changing the sound (setSoundGains()) with a slider.

volumeSlider.setSliderStyle (Slider::RotaryHorizontalVerticalDrag);
volumeSlider.setTextBoxStyle (Slider::NoTextBox, 0, 0, false);
volumeSlider.setRange (-48.0f, 0.0f, 0.10f);
volumeSlider.setValue(sampler->getSoundGainDb(soundIndex), dontSendNotification);
volumeSlider.setDoubleClickReturnValue (true, te::StepClip::defaultNoteValue);
volumeSlider.addListener(this);
void sliderValueChanged (Slider *slider) override
{
    sampler->setSoundGains(soundIndex, float(slider->getValue()), 0.0);
}

II guess that I generate unnecessary traffic on the message thread.

by using a gain ramp. DSP Gain as an example

without it

with ramp

the reason for clicks and such is, going from -INF to -1 in 1 sample theres no audio then suddenly its full volume. you need to process per sample and apply ramp value to smooth the differnce from -INF to x

In your sample->setSoundGains() method, you want to gradually increment the gain over a short period of time, to smoothen the transition, so it’s not a sudden change (which causes a click sound).

JUCE has a class called SmoothedValue which does this, like this:

// Create gain like this:
SmoothedValue<FloatType> gain(startingGain);

You’re going to want to set the time it takes to ramp before you use it, with:

gain.reset(sampleRate, numOfSecondsToTransition);

Then, to use it, first set the new target gain:

gain.setTargetValue(newGain);

It automatically transitions, you just have to get the current value:

float newGain = gain.getNextValue();

Then, you use newGain to set your gain on the next sampler update. Note that the gain is updated every time you call getNextValue(), so be careful not to call it twice (once for each channel) in stereo projects.


For a possibly simpler solution, JUCE’s Gain class uses this method, and is another way to smoothly adjust the gain. In this case, the sampler always plays the same gain, and you apply Gain<FloatType>::processSample(sampleToAdjustGain) to the output of the sampler to adjust the sample’s gain.

That works like this:

First you have to have the gain variable created as a property of the class:

Gain<FloatType> gain;

Then, after the sampler generates a sample, you edit the sample:

float newSample = sampler->generateNextSample();
gain.setGainLinear(newGain);
newSample = gain.processSample(newSample);

The issue with the second method is similar to how the first one generates a new gain value each time you call, getNextValue(). Gain does the same thing, so if you have a stereo configuration and need to adjust two different samples that occur at the same time, you’ll need two Gain instances.

You can also write your own. You really just have to transition instead of suddenly jumping to a new gain and your click should go away!

As far as high CPU usage, I don’t know. I think it depends on what setSoundGains is doing. Ideally, it just sets a few values, doing minimal. Slider also has a property called onDragEnd which is a lambda that is called only when the dragging has just ended. If you use that as the slider’s callback (instead of sliderValueChanged), it would only send a message when the slider is finished being dragged. The disadvantage of this is that you won’t be able to hear real-time updates while dragging the slider, but it would potentially reduce CPU usage (although the slider itself updating shouldn’t use that much CPU; it’s about the same as saying the mouse moved – these are high-volume events).

(Note: I haven’t tested the aforementioned code.)

Have you checked what the Tracktion Engine code actually ends up doing when you call the setSoundGains method? There are sometimes a bit “surprising” things that happen in the Tracktion Engine.

Yes, unfortunately te::SamplerPlugin is really a very minimal sampler implementation and setting sound gains isn’t supposed to be dynamic. Doing so will actually rebuild all the sounds asynchronously so will always cause a dropout.

If you want to do something more than just play some predetermined samples I suggest taking a copy of the class and heavily adapting it to suit your needs. Or you could write your own te::Plugin subclass.

Thanks @SourGummi @xenakios @psyzza @dave96 for your comments!
I already suspected that. I think I’ll check te::SamplerPlugin and copy them.