Phase Distortion Synthesis
This tutorial focuses on the implementation of phase distortion synthesis in Reaktor. I’ll be using section 2 of this paper as a source. The paper identifies four simple equations (1-4) that make up the entirety of the phase distortion system.
We’ll go over them one by one, starting with the simplest, equation 4, and working backwards towards equation 1. I’ll do my best to link to articles explaining relevant math techniques but please don’t hesitate to ask questions pertaining to the math – it really is essential.
This tutorial expands upon topics first covered in a tutorial on creating custom oscillators in core, which can be found here.
An example download is given as well.
Eq. 4 is defined as the Modulo 1 function, which simply strips out the integer component of the input and outputs any decimal values that remain. To achieve this, need to use the floor function, probably more commonly known as rounding down. Rounding in Reaktor is actually a substantially more complicated subject than one might think, check out this forum thread for more.
Fortunately, in this case, there is a very simple structure that will suffice for a floor function:
Notice that at the output, the value is translated into an integer. For more on the integer data type, check this tutorial.
To finish up our Mod 1 function, we simply need to subtract the output of the floor macro from the input to the Mod 1 macro like so:
Eq. 3 is defined as
fo is a common DSP shorthand that stands for the frequency of an oscillator, while fs is defined as the sampling frequency. Finally, n represents the sample number. So, for each tick of the sample clock, we read the old value of phi (ϕ) and add fo/fs, apply, the Mod 1 function we made above, and store the value for the next sample tick.
This is easy to achieve in Reaktor:
In fact, this structure is nearly identical to the phase accumulator that we built in the custom core oscillators tutorial, and will act as a phase accumulator for our phase distortion oscillator. The only difference here is that we are creating a phase from 0-1, where in the previous tutorials I have generally used a phase running from -1 to 1 or from -π to π.
Eq. 2 is a piecewise function that looks like this:
Where x is the input and d is a variable with a value between 0 and 1 that determines the final shape of our waveform. When d = 0.5, we end up with a sine wave. The further d deviates away from this point, the more harmonic distortion is added to the signal, until finally at d = 0 or d = 1, the wave resembles a sawtooth.
When implementing piecewise functions in Reaktor it is important to take care that changes to variables (in this case, d) do not cause the wrong function to be triggered. To achieve this, make sure that any value dependent upon the value of d is stored in a latch and only triggered by an incoming x value.
We end up with this:
Some of the math has been slightly rearranged for the sake of efficiency. Notice that an incoming value at the d port causes no event to be sent to the output of this macro.
Finally, let’s implement eq. 1:
Notice that we require eqs. 2-4 in order to implement this properly. We calculate the phase using eq. 3, and use it to feed eq. 2, which in turn controls the phase of the cosine function in eq 1.
I’m not sure what the authors hope to gain by inverting the signal, which is simply an added math operation that does not seem to affect the sound at all. I decided to omit it from this macro:
Phase is the macro that implements eq. 3, while distort implements eq. 2.
Here’s a few different waveforms with different values of d:
Note that the further away from 0.5 d is, the more aliasing will be added to signal.
ANTI-ALIASING THROUGH OVERSAMPLING
In this section, I’ll be using some structures introduced recently in the Oversampling in Reaktor series.
By oversampling, we can substantially reduce the audible aliasing artifacts that are caused by extreme values of d. We can also use a different method for upsampling than used in previous tutorials.
In short, to oversample the phase accumulator is easy – we know where the phase starts at the beginning of the sample, and we know how much it increases by per sample tick (fo/fs). Therefore, it is simple to calculate the interim values, here is an example of oversampling 8x:
Now we have 8 outputs evenly spread from the old phase value to the new value. I chose to use this method instead of the 8x upsampling polyphase filter I designed previously because this method splits the values into 8 streams more efficiently (the polyphase filter required 8 adds and 8 multiplies per stream).
Now we can do our processing with each different phase value, then run each output into an input in a downsampling filter like this:
Here is the spectrum with a high value of d (0.999) at the normal sampling rate:
and oversampled 8x:
This does a pretty good job but will not completely eliminate audible aliasing at very high or low values of d.
A simple oscillator based on the work done today can be downloaded here.