Learn Arduino Guitar Effects with me. Project 2 - Distortion

I hope you read and learned something from the first project - Audio Pass Through. Let’s expand on that and make a simple distortion effect. I took the example provided by Daisy and broke it down a bit.

How does distortion work? We’re going to take a look at soft clipping and hard clipping. Here’s a quick breakdown from wikipedia Distortion (music) - Wikipedia

#include <DaisyDuino.h>

DaisyHardware hw;

float hardClip(float in) {
  in = in > 1.f ? 1.f : in;
  in = in < -1.f ? -1.f : in;
  return in;
}

static void AudioCallback(float *in, float *out, size_t size)
{

  float Gain = 100;
  
  for(size_t i = 0; i < size; i+= 2)
  {
    float wet = in[i];
    wet *= Gain;
    wet = hardClip(wet);
    out[i] = wet / 6.f;
  }
}


void setup() {

  hw = DAISY.init(DAISY_PETAL, AUDIO_SR_48K);

  DAISY.begin(AudioCallback);

}

void loop() {
  // put your main code here, to run repeatedly:

}

Our ‘boiler-plate’ stuff is the same so lets take a look at the new stuff starting in the AudioCallback function.

float Gain = 100;

All we’re doing here is defining a float variable called ‘Gain’ with a value of 100. It was an arbitrary number I picked that sounded good, you can change that number to see how it affects the signal.

float wet = in[i];

Now we’re defining a float variable ‘wet’ as what’s coming in on the input signal. We’ll talk about wet and dry signals in the next project. You may ask, why are we using all of these float variables instead of int? Int’s are only whole numbers, 1, 2, 3, 4, etc. Floats give us decimal places. The converted analog to digital input signal is a level between -1 and 1 so we need to use a float to be able to get decent audio.

wet *= Gain;

Here we’re just multiplying the signal by our Gain variable, which we set to 100. This would be the same statement as ‘wet = wet * Gain;’

wet = hardClip(wet);

Now we’re going to send the wet variable to the hardClip function to process. For this example, we could just do the distortion processing in the AudioCallback function, but we should get used to using and calling functions.

float hardClip(float in) {
  in = in > 1.f ? 1.f : in;
  in = in < -1.f ? -1.f : in;
  return in;
}

In this function, we’re going to take that ‘wet’ variable and now assign it to a float called ‘in’. Keep in mind that because this is a different function than AudioCallback, ‘in’ is an isolated variable and not the same as the ‘in’ in the AudioCallback function. Variables are only used in the function they’re defined in.
The next two lines are comparison operators. It’s basically saying “Is ‘in’ greater than 1? Yes, then in = 1. No then in = in.” It repeats it for the negative portion of the audio signal. This will ensure that the highest and lowest possible values are 1 and -1 (Seeds max and min values), effectively clipping off the signal that goes higher than that. The .f after 1 treats the statement as a float, not sure if it’s needed but it’s a good habit to get into.
Next, we’re going to return the value stored in ‘in’ back to the variable ‘wet’ when we called it from the AudioCallback function.

out[i] = wet / 6.f;

Our final piece of code outputs our ‘wet’ signal and divides it by 6. Why are we dividing it by 6? Well, the signal is now amplified above the point that the Seed can handle so it cuts it off at the max. In the last lesson I had mentioned that the guitar signal is much lower than what the Seed normally handles. So now our distorted guitar signal will be MUCH louder than the input. I divided it by 6 just to turn the volume down a bit. Feel free to play with that number to your liking.

So we talked about hard clipping, what about soft clipping?

Let’s replace our hardClip function with a softClip one (or just add a new function)

float softClip(float in) {
  if (in > 0)
    return 1 - expf(-in);
  return -1 + expf(in);
}

Now what’s going on here? This statement is saying “if the input signal is greater than 0, then send back ‘1 - expf(-in)’. Otherwise, send back ‘-1 + expf(in)’”. Let’s take it a step further and see exactly what this is doing and how to calculate it. Remember, the Daisy outputs a signal between -1 and 1, we can never have a higher number. Let’s say the input audio level at that sample happened to be a value of 0.1. Let’s plug that into this formula on a calculator: 1 - e^(-0.1) and we should get .095. Now plug in a 1 - e^(-.999) and we should get a .63, which is the highest value we can get. If you plug in and graph out a bunch of different points, it should look like a squished down hard clipped signal like the picture in the wikipedia article. It will effectively reduce the sharp edges of the distortion.

Make sure we change the function we call to

wet = softClip(wet);

Compile all that and let’s see what we get. There’s some sweet sounding soft distortion. If you reduce the Gain variable, you may be able to better hear the difference between the hard and the soft clipping.

Hope all of this made some sense. Next up, let’s make a useful effect by digging into the DaisyDuino modules and create some reverb.

4 Likes

Hey again! I uploaded this code and got the distortion sound, albeit I noticed that there is a very strong hiss present in the audio signal. I saw somewhere on the Daisy Slack forums that this is a known issue, and probably generated by the hardware, but I couldn’t elaborate at this moment on the details.

Here’s a plot I made from [Audacity → Analyze → Plot Spectrum] of the first few seconds of noise/hiss to try and understand what the frequency of this thing is. (I’m just throwing darts in the dark, I have no idea what I’m looking at.)

Here is an [audio file download link] for my test of the distortion code for anyone interested in hearing the issue I described.

By the way, I am kind of blown away that it is possible to load a distortion pedal onto a tiny micro-controller! (Thx, Daisy!)

Great post, etchx, thanks for the effort you put into explaining stuff for newbies, I really appreciate it!

My recording gear: Behringer U-PHORIA UMC202HD (rev 1.12), 24-bit, 48kHz sample rate (192k is max), 1 channel (cause mono guitar), no clipping.

  float Gain = 100;
...
    wet *= Gain;

This adds 40 db to noise floor. That’s not a sane way to do distortion, normally a waveshaping function like tanh is used without messing with signal gain. That’s just one part of the story as you’d still have to deal with signal aliasing due to its increased bandwidth.

For a distortion, raising the gain by several tens of dB is pretty common. Analogue distortion pedals with more than 60 dB gain are not unusual. The noise is amplified too of course, and when the distortion is turned on it can be heard distinctly if the input signal is not very clean. The Daisy Seed also adds some noise coming from the digital circuitry, as mentioned here.

My intention was to show newcomers how to make a simple distortion using the resources that I’m familiar with. The two distortions in my example were taken directly from the Electro-Smith examples. The Daisy can be intimidating to newcomers/beginners and I think it’s important to get peoples feet wet and interested in what it can produce.
I’m not familiar with the tanh function or how it ties into distortion. Perhaps you can make an example us newbies can go off of? It would be interesting to hear the differences.

o5akafeeva:
That’s an awesome plot! I may have to pump in to Audacity and get some plots, too! My guess for the source of noise/hiss is either your preamp or buffer, the daisy itself, or your buffer has a higher gain than mine and the gain value in the code is way too high. Could be other things like noise induced from your guitar cable or pickups, power supply for your Seed, or the guitar outputs a much higher signal than mine. antisin is also correct by saying that the noise floor is significantly raised for this effect so it will amplify problems. The gain applies not just the guitar signal, but the noise as well. In a perfect world, there would be no noise. There are certainly better ways to do distortion than a hard clip.

As I mentioned before, I’m using a pedalpcb Terrarium as a buffer and controls. I think it’s very important you have a buffer or else you get a ton of noise. If you aren’t really into DIY analog electronics, I believe tuner pedals almost always have buffers built in. My terrarium buffer op-amp is currently a LM741, but I may put in a TL072 soon. I get a little noise, which is common for any distortion effect, but not nearly as much as the sample you provided.

There are some nice-ish graphs of what’s going on with tanh type distortion here:

https://csoundjournal.com/ezine/winter1999/processing/index.html

I appreciate the challenge, but you could just comment out gain boosting in your patch and call tanhf instead of clipper function. But tanhf is not that special - just one of many functions that can be applied for distortion/saturation. If you actually want to learn more about this topic, here’s a nice article series - Complex Nonlinearities Episode 0: Why? | by Jatin Chowdhury | Medium

1 Like

I stole a very nice and very simple distortion algorithm from the Spin Semi FV-1 Knowledge Base:

"This technique essentially transforms the input X to the output Y (ignoring sign):
If X<1 then Y=X
*If X>1 then Y=2 - 1/X *
This allows the input to remain undistorted until it peaks at a value of 1.0; beyond this, the maximum amplitude that is possible at the output is +/-2.0, and would require an infinite signal amplitude to achieve this."

It codes just like it reads, dividing the result by 2 to keep it between -1 and 1:

float MySoftClip(float in)      // 1/X DISTORTION FROM FV-1
{
    float out = in;
    if ( in > 1.0)
    {
        out = 2.0 - 1.0 / in;
    }
    if ( in < -1.0)
    {
        out = -2.0 - 1.0 / in;
    }
    return out / 2.0;
}

The transfer function looks like this:

That would work for hot signal clipping, but do nothing for normalized signal that is in [-1;1] range. Well not quite nothing, it would just dropping its gain by 6dB.

Obviously it would have to boost signal internally to be used as a saturator effect. I’ve made a graph with adjustable gain here: Don's reciprocal distortion .

Since the piecewise function is using fairly trivial maths, it should be possible to write an antiderivative function to use for antialiasing as described in https://aaltodoc.aalto.fi/bitstream/handle/123456789/30723/article5.pdf?sequence=8

1 Like

The softClip() function presented in the tutorial is perfectly fine for soft clipping, and is actually softer than tanh(). The tutorial also specifies that the Gain variable is somewhat arbitrary and could be changed at taste. Maybe a lower value (10 for example) could be a better starting point, but this is a major parameter for a distortion effect, so wouldn’t comment it out at all.

As @Firesledge says, distortion effects include gain before the distorting element - some gain in the case of “overdrive” and lots of gain in the case of “fuzz”. So the increase in noise comes with the territory.

Most pedals address this by doing some “tone shaping” after the distorting element, which invariably includes some lowpass filtering. Some pedals actually increase the amount of filtering as the gain (drive) is increased.

The other way to address the increased noise is with a “noise gate”, which attenuates the output when the input signal level is low. “Gated fuzz” pedals actually exaggerate and exploit the gating threshold to generate a “sputtering”, “spitting” or “ripping” effect.

1 Like

That’s a fantastic paper!
Are there any code snippets exist?

Actually yes, there’s some public code from one of that paper’s authors. Fabian Esqueda made a VCVRack plugin with antialiased softclipper/hardclipper and a wavefolder that uses second order derivatives. I’m not sure if that softclipper worked correctly, but other code was OK IIRC.

I was experimenting with other functions recently and made a few similar waveshapers. The end goal is to add this stuff to OWL library once I’m sure that everything is working correctly.

I found an interesting guy with a friendly language, sharing code and video

1 Like

Great article, @Daniel_BASS. Very clear and well written! I knew about aliasing with nonlinear effects in digital systems, and about oversampling, but the antiderivative method was new to me.

Specifically in terms of guitar effects, I wonder how much of a concern aliasing is. The spectrum for guitar tops out at 5K or 6KHz due to the impedance of the pickup and cable. So a 44- or 48-KHz sample rate is about 4 times the Nyquist rate of even the higher harmonics. Maybe it is still a problem with hard clipping or very high gains?

Speaking of digital distortion and high gain, I remember someone saying that it helps to divide the gain between two or more distortions in series, rather than all the gain in front of one. Anyone know about this? Maybe a fun experiment to try one of these days.

Hard clipping will produce aliasing artifacts for any frequency because its first derivative contains discontinuities. Second issue with it is that it requires gain boosting for normal level signals, which adds noise.

Soft clipping doesn’t have those problems, also some boost may be required if distortion function is too mild. The advantage of using antiderivatives is that typically it’s computationally cheaper than relying on oversampling. The effect itself is similar to x2 oversampling, but can be hit and miss for some functions, considering that on embedded systems you often have to use approximated math that would result in noise from computing errors. At least it works great for hard clipping.

As for you guitar frequencies question, if your math is correct then it should be possible any polynomial based distortion with order <= 4 without aliasing. Above that you’ll need more than x4 bandwidth for representing distorted signal. It’s a fairly common knowledge that polynomial functions add harmonics up to order of their polynomial, there’s some proof in Wikipedia article on waveshaping for anyone curious.

Generally when you use a waveshaper (hard-clip or soft-clip) with some kind of gain, even moderate, you have to reduce the output volume, because the power of something tending to a full-range quasi-rectangle signal is generally much larger than the input power and requires compensation. This can also be viewed as reducing the scale of the shape when increasing the input gain, but having the 3 distinct operations (pre-gain, shape, post-gain) is usually easier to implement in the general case.

Polynomial waveshapers require hard-clipping beforehand too, unless you’re sure the input signal stays within a specific range so the output doesn’t fold back and grows at the power of N.

This family of polyshapers have their first derivatives reaching 0 at the end points, lowering the requirement to band-limit the hard-clipping part (thanks to Andrew Simper for the coefficients). They also have a unity gain at 0 and their min and max at ±1.

For anyone interested, the old discussion that @Firesledge referenced is archived here - family of soft clipping functions. (note - looks like site performs region based IP filtering, may require VPN for some)