Making Audio Plugins Part 5: Digital Distortion

It’s time to write our first plugin, a nasty digital distortion. To be more precise, the plugin will apply clipping to the audio signal. Signal values above a certain threshold will be limited to it so that the threshold is never exceeded:

Threshold

Note that by “above” I mean “above a certain positive threshold or below a certain negative threshold“.

Using the duplicate script introduced earlier, we can clone any existing project to a new project with a new name. This means that we don’t have to make all the modifications we did again for every new project.
Open a Terminal, go to your IPlugExamples folder and run:

./duplicate.py MyFirstPlugin/ DigitalDistortion YourName

If you haven’t followed the last post, you can download the files here. Make sure you don’t have any other project open in Xcode. From the newly created DigitalDistortion folder, open DigitalDistortion.xcodeproj. Verify that it builds the APP target without errors. Edit the Schemes like I’ve shown before to make sure Reaper runs for the VST2 and AU targets. Don’t forget to change the Arguments Passed On Launch to point to the right .RPP file.

Now when you run Reaper, you’ll see that instead of loading MyFirstPlugin, it has magically loaded the DigitalDistortion plugin! This is because the Reaper project file is text-based and the duplicate script replaces all occurrences of “MyFirstPlugin” with “DigitalDistortion”.

Let’s start with turning our mGain parameter into a mThreshold parameter. Go into DigitalDistortion.h and rename the private member variable:

private:
  double mThreshold;

Now open DigitalDistortion.cpp and replace (Cmd+Alt+F) all occurrences of the word Gain with Threshold. You should be able to build without errors. Change the parameter initialization in the constructor to have 0.01 as the minimum and 100.0 as the default value:

GetParam(kThreshold)->InitDouble("Threshold", 100.0, 0.01, 100.0, 0.01, "%");

Let’s implement the DSP part:

void DigitalDistortion::ProcessDoubleReplacing(
    double** inputs,
    double** outputs,
    int nFrames)
{
  // Mutex is already locked for us.

  int const channelCount = 2;

  for (int i = 0; i < channelCount; i++) {
    double* input = inputs[i];
    double* output = outputs[i];

    for (int s = 0; s < nFrames; ++s, ++input, ++output) {
      if(*input >= 0) {
        // Make sure positive values can't go above the threshold:
        *output = fmin(*input, mThreshold);
      } else {
        // Make sure negative values can't go below the threshold:
        *output = fmax(*input, -mThreshold);
      }
    }
  }
}

If you get an error that fmax and fmin are not defined, try changing the fmin call to just min, and fmax to max. If that doesn’t work, add this line to the top of DigitalDistortion.cpp:

#include <math.h>

If that still doesn’t solve the problem, try adding this line instead:

#include <algorithm>

And change the fmin to std::min, and fmax to std::max.

Although the channelCount is still hardcoded, we have removed some duplication by using an outer for loop to iterate over all channels. This means that the plugin processes the samples for one audio channel at the time before processing the next channel.
The interesting part is the if statement: For positive values we take the input value or the threshold value, whichever is lower. For negative values we take *input or the negative threshold, whichever is closer to zero.
Run the plugin in Reaper and try it on the test tone. When the knob is all the way to the right, you’ll hear a clean tone. When you turn the knob counter-clockwise, it will start to sound more and more distorted.
You’ll also notice that the signal is getting quieter the more you distort it. That’s because the threshold goes towards zero and so we’re clipping the signal to very small values. To compensate for this, divide the signal value by the threshold value:

if(*input >= 0) {
  *output = fmin(*input, mThreshold);
} else {
  *output = fmax(*input, -mThreshold);
}
*output /= mThreshold;

Remember how we changed the parameter earlier to have a minimum value of 0.01? This ensures that we’re never dividing by zero, even if we turn the knob fully counter-clockwise.
If you run the plugin again, you’ll hear that the amplitude stays the same no matter how much you distort the signal. In fact, the signal appears to become louder! The clipping turns our sine wave input into something close to a square wave, which has a higher RMS than a sine wave.

For now, I’m intentionally keeping the DSP part as simple as I can. In my opinion, a good plugin isn’t just about processing a chunk of samples. It’s a combination of:

  • Solid host integration (Presets, Compatibility)
  • Good sound (this is pure DSP)
  • Clear user interface
  • Beautiful graphics

So before we dive into more complex DSP, we will add presets and a better GUI to our plugin.

If you found this useful, please feel free to
!
comments powered by | Disqus