Easiest way to implement a Z-1 unit delay function? (resolved)


#1

I am working on rebuilding some distortion units from Reaktor based on Infinite Linear Oversampling which is a technique to reduce aliasing. It involves utilizing the integrals of the distortion equations with unit delays. Here’s an example of a schematic:
https://www.native-instruments.com/forum/attachments/ilo-tanh-png.54931/

I’m wondering what the simplest way to write a function that delays output by one unit.

I read Jordan Harris’s posts (who designed the State Variable Filter I’m currently enjoying very much incidentally) here, but I’m not sure I follow his technique.

Here’s what I’m wondering if might be the same idea:

double output = nullptr;

inline double getUnitDelay(float& input) {
return output;
input = output;
}

So in principle, it takes an input, but it doesn’t return that input. It copies it to another variable called output, which I think needs to be initialized as nullptr so that there is something in it (even nullptr) for the first sample request. Not sure if this can be folded into the function.

Since C++ is order sensitive (I think), this function returns the output from the prior sample each time it is run.

Then for example, it can be used in equations like this:

integral - getUnitDelay(integral) ... ;

Would that work? Is there a better way to do it?

Thanks as always


#2

if this is the exact code notice there is huge control flow issue.
notice return output; would basically mean your code flow ends there. input would never be updated by output.

if you’re coming from modular/dsp environments you should remember:

  • serial/parallel logic should be explicit. sometimes requiring “boiler-plate” coding…
  • do not expect things to be like circuits… a process callback will run circular for you but you’ll need to make sure all your variables and states are progressing and saved correctly.
  • multi-input state requires multiple variables. for example z-1 state MUST have a variable for each input. (so if you have stereo input… that means 2 variables each one for each input).
  • understand the concept of reference / pointer better (from your code it seems you’re still figuring it out).

If I get your intentions, it should look like this:

again, reminding that your output variable / this code would only be good for single channel.
if you get multiple inputs… you’d need to have multiple variables (or in modernized coding maybe a class with instance per input).

double output = 0.0; // notice this IS NOT a pointer! it cannot reference nullptr

inline double getUnitDelay(float& input) {
    const double prevOut = output;
    input = output;
    return prevOut;
}

#3

With this implementation, the value of output never changes and always stays at 0.0. The function always returns the same value that it assigns to input. I don’t think that’s what mikem intends to do.

I suggest not taking the input by reference, but rather just by value:

double output = 0.0;

inline double getUnitDelay(float input) {
    const double prevOut = output;
    output = input;
    return prevOut;
}

To execute something more complex after a return, ScopeGuard is a good pattern. Similar to JUCE’s ScopedValueSetter. But in this simple case, the local prevOut is better IMO.


#4

since we’re with snippets of code I also don’t know how other parts interact with this code. so I’ve tried to do minimal changes but I guess your suggestion might be more of what @mikem had in-mind.


#7

I was told on KVR it is pointless to try to do this as a function, and I should just hardcode what I want to do into the class using variables as storage. So for example, here’s what I’ve come up with to demonstrate the idea:

private:
double integralZMemory = 0;
double inputZMemory = 0;
double integralZDelayed;
double inputZDelayed;
double Overdrive::getOutput(double &input, "other parameters") {

double integral = ...;
integralZDelayed = integralZMemory;
integralZMemory = integral;

inputZDelayed = inputZMemory;
inputZMemory = input;

double output = integralZDelayed + inputZDelayed;
return output;

If I did that correctly, it would continuously output the integral delayed by 1 sample + the input delayed by 1 sample. I would never want that. But just for illustration purposes. Makes sense?


#8

Or even simpler …

private:
double integral_1 = 0;
double input_1 = 0;
double Overdrive::getOutput(double &input, "other parameters") {

double integral = ...;

double output = integral_1 + input_1;

integral_1 = integral;
input_1 = input;

return output;

I think that’s it. Problem solved. Now I just have to write the actual code. :slight_smile: