Search code examples
c++audioreal-timesignal-processingjuce

Changing gain of audio signal while it's playing causes artifacts


I am playing back audio files in a program, and in the audio rendering callbacks, I apply a gain multiplier to the input signal and add it to the output buffer. Here's some pseudo code to illustrate my actions:

void audioCallback(AudioOutputBuffer* ao, AudioInput* ai, int startSample, int numSamples){
    for (int i=startSample; i<numSamples+startSample; i++){
        ao[i] = ai[i]*gain;
    }
}

Basically I just multiply the data by some multiplier. In this case, gain is a float member that is being adjusted via a GUI callback. If I adjust this value while the audio is still playing, I can hear that the audio is getting softer or louder when I move the slider, but I hear lots of little pops and clicks.

Not really sure what the deal is. I know about interpolation, and I do that if the audio is pitch shifted, but I'm not sure if I need to do any extra interpolation or something if the gain is being adjusted in real time before the audio file is finished playing.

If I adjust the slider before the audio start playing, the gain is set properly and I get no clicks.

Am I missing something here? How else is gain implemented but a multiplier on the input signal?


Solution

  • Question: how does the multiplication operator know which operand is the audio signal and which one is the gain? Answer: it doesn't. They're both audio signals, and anything audible in either one will be audible in the output.

    A flat, unchanging signal doesn't produce any audible sounds. As long as the gain remains constant, it won't introduce any sound of its own.

    A signal that changes abruptly will be very audible, it sounds like a click, containing lots of high frequencies.

    As you've determined on your own, one way to reduce the high frequency content and thus the audibility is to stretch out the change over a number of samples, using a constant slope. This would certainly suffice in an application where you have lots of time to make the gain change.

    Another way would be to run a low-pass filter on the gain signal and use that as the input to the multiplication.