It’s been just over a month since I released my free ring modulator plug-in, Dyner. Since then I’ve gotten some very nice feedback on it, and thankfully no crash reports (well.. that I know of). Additionally, I’ve been tweaking a few things like the GUI controls and doing some minor optimizations, but more importantly I just finished adding smoothing to the depth parameter, so I figured it was time to upgrade to version 1.0.4 and put that out. Also, since parameter smoothing is a pretty important feature in DSP for achieving good quality sound, I thought I’d give a brief overview of what I did.
First, let’s hear the difference. Previously, artifacts were most easily heard using a low frequency setting with the sine wave while changing the depth somewhat rapidly. Other wave forms, due to their rich harmonic content, obscured most artifacts that resulted in automating the depth. So here then, are two audio samples “before” and “after”:
In writing the parameter smoother class, there were a few features I felt were important to include:
- Lightweight and easy to use,
- Flexible in terms of how the interpolated smoothing is calculated,
- Reusable for other plug-ins or DSP effects.
One thing I wanted to avoid was having to insert a whole bunch of new code into my plug-in to “check for this”, “update that”, “initialize”, etc. The more operations that are handled inside the parameter smoother class, the better. To accomplish this, I decided to include a reference to the parameter value that’s receiving the smoothing. Once the object is initialized with this reference, it is automatically notified when the user changes the value of the parameter, and I compare that to the previous value that is stored within the class to signal the change. In other words,
Since the first argument (initialValue) is passed by reference and stored internally as such, changing paramDepth outside the object automatically changes its reference inside as well since they point to the same address space. Once this connections is made, there is no need to call an external function to check whether the parameter value has changed, or to explicitly set the change. This minimizes function calls and makes it very easy to use and implement in any project; just instantiate the object as above, and then call one method within the audio’s process loop to fetch the parameter value.
Parameter smoothing itself is typically handled by interpolating from the current value to the target value that has just been set by the user, making the change more gradual to avoid artifacting that can result from the abrupt jump otherwise (other methods might include passing parameter values through a low-pass filter). Linear interpolation is often good enough for this task. It’s very light on computation and easy to implement. But I wanted to leave it open to implement different curve shapes that the interpolation can be mapped onto. Especially for cases where the smoothing takes place over a longer duration of samples whereby a specific curve would become more noticeable. This was easily handled through a callback function (also seen in the above image of the constructor), which allows me to define a custom function that the interpolation follows.
For example, to define an exponential curve, I could write the following function, and then pass it as an argument (which is basically a function pointer) to the constructor:
Here we can see the difference graphically:
Being able to specify the number of samples over which the parameter smoothing takes place is also a fairly obvious customization that I included. This way, I can adapt the smoothing to be equal to the block size that the host passes to my plug-in, for example. Another option I wanted to include was the ability to calculate new interpolated values at a different rate than the sampling rate. In other words, instead of calculating new values every sample in the processing loop, I can have it calculate every 2, 4, 12, 48, etc. samples by setting the stride variable that you might have noticed above. It’s rarely necessary to calculate a new value every sample, and in fact, the smoothing in Dyner happens every 4 samples. This could be increased as well if a custom callback is used that is of greater complexity than simple linear interpolation (e.g. mapping to an S-curve). The resulting interpolation looks like this with a stride value of 24:
Lastly, you might have noticed that the above graph (apart from the staircases) looks different from the preceding ones. This illustrates a critical feature that needed attention: allowing the parameter smoothing to change when a new value is set while smoothing is already taking place due to a previous change. In such a case, the current state nees to be saved and a new one begins towards the new target parameter value. I did this by defining four states that I keep track of using bitwise flags.
As can be seen above, comparison between the parameter reference value and its previous setting determines whether a new change has occurred. mPos is the position index along the interpolation line/curve, so when it is non-zero it means smoothing is currently processing due to a previous value change.
It’s also worth mentioning that the actual method that retrieves the smoothed parameter value (getParameterValue) needs to be called every frame of processing so it can properly update itself. It replaces any direct use of the original parameter for processing calculations.