How to write a simple Z-1 unit delay function in C++?

DSP, Plugin and Host development discussion.
Post Reply New Topic
RELATED
PRODUCTS

Post

I have a distortion I am trying to build from a Reaktor strategy called Infinite Linear Oversampling that reduces aliasing. This is what a schematic looks like for example of one such project:

https://www.native-instruments.com/foru ... png.54931/

I want to build a simple inline z-1 function that returns the output of the prior sample. I just want to do this as a simple function.

I presume this can be done simply by storing the value of the current sample in a variable, then retrieving that variable's output on the next sample in a continuous loop.

ie. In the Class Overdrive:

Code: Select all

Private:
double unitMemory = 0;

inline double getUnitDelay(&input) {
double output = unitMemory;
unitMemory = input;
return output;
}
Then when I want to utilize the function I can do it like this:

Code: Select all

double Overdrive::getOutput(double &input, "other parameters") {
	    double inputUnitDelayed = getUnitDelay(input);
	    double integral = ....;
	    double integralUnitDelayed = getUnitDelay(integral);
	    ....
	    double output = ...;
	    return output;
Does that work?

If I use this multiple times in the same class like this (ie. getUnitDelay(input), then getUnitDelay(integral)) will it still work? Ie. will they get the unitMemory = 0 on initialization, but then assign their own values within each function independently?

Post

Nope. You are assigning a new value to a common class member variable so the new assignment will override the previous assignment. If you really want to abstract the unit delay then use a unit delay class, optionally initialize it in the constructor and create a new unit delay object for each variable you want to delay.

If I were you I would just create one class member variable for each variable that I wanted to delay, e.g., lastInput, lastIntegral, etc. and forgo the function entirely. There's a temptation when converting Reaktor code to match macro abstractions with functions, that's going to lead to unnecessary complication and overhead.

Post

ghettosynth wrote: Mon Nov 05, 2018 3:11 am Nope. You are assigning a new value to a common class member variable so the new assignment will override the previous assignment. If you really want to abstract the unit delay then use a unit delay class, optionally initialize it in the constructor and create a new unit delay object for each variable you want to delay.

If I were you I would just create one class member variable for each variable that I wanted to delay, e.g., lastInput, lastIntegral, etc. and forgo the function entirely. There's a temptation when converting Reaktor code to match macro abstractions with functions, that's going to lead to unnecessary complication and overhead.
Okay ghettosynth. I'm trying to wrap my head around this.

So you are saying don't bother with trying to create a function, but rather write it line by line.

So for example:

Code: Select all

private:
double integralZMemory = 0;
double inputZMemory = 0;
double integralZDelayed;
double inputZDelayed;

Code: Select all

double Overdrive::getOutput(double &input, "other parameters") {

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

inputZDelayed = inputZMemory;
inputZMemory = input;

double output = integralZDelayed + inputZDelayed;
return output;
This would give me a unit delayed integral added to a unit delayed input?

That's obviously not what I need in my real code, but just for demo purposes. Works?

Post

Other than you're creating more variables than necessary, yes. You only need two storage locations for, e.g, input, the current input, and the unit delayed input. The current input is held by the parameter "input", and the unit delayed input is your class member variable. It sometimes helps to name them with numeric suffixes, e.g., input_1 to indicate that it's the unit delay of input. Now your computation will just involve some combination of input and input_1 and it's readily apparent by the variable names which one is unit delayed. To make sure that things are assigned in the correct order, just put the re-assignment of input_1 at the end of your loop, that is, when all your calculations with input and input_1 are finished, make input_1 equal to input, rinse wash and repeat. Note, the assignment can go between your computation of output and the return of output, clear?

Do the same for integral except instead of being an input parameter it's defined in your function as you show.

Post

ghettosynth wrote: Mon Nov 05, 2018 4:31 am Other than you're creating more variables than necessary, yes. You only need two storage locations for, e.g, input, the current input, and the unit delayed input. The current input is held by the parameter "input", and the unit delayed input is your class member variable. It sometimes helps to name them with numeric suffixes, e.g., input_1 to indicate that it's the unit delay of input. Now your computation will just involve some combination of input and input_1 and it's readily apparent by the variable names which one is unit delayed. To make sure that things are assigned in the correct order, just put the re-assignment of input_1 at the end of your loop, that is, when all your calculations with input and input_1 are finished, make input_1 equal to input, rinse wash and repeat. Note, the assignment can go between your computation of output and the return of output, clear?

Do the same for integral except instead of being an input parameter it's defined in your function as you show.
I see what you're saying... So for example that could be rewritten as:

Code: Select all

private:
double integral_1 = 0;
double input_1 = 0;
double output = 0;

Code: Select all

double Overdrive::getOutput(double &input, "other parameters") {

integral = ...;

output = integral_1 + input_1;

integral_1 = integral;
input_1 = input;

return output;
I think that should still work and it is what you're suggesting. Super easy if so! The point being as long as I define my "output" before I reset what integral_1 and input_1 equal, then they will always lag behind one sample.

Please let me know if I got it now! And thanks for your time. This has been very very helpful.
Last edited by mikejm on Sun Dec 02, 2018 4:07 am, edited 1 time in total.

Post

Exactly correct!

Post

I would suggest to ALWAYS have meaningful named variables, to avoid huge headaches in the future. something like "lastIntegral" and "lastInput" (or prevIntegral, prevInput), will make your code cleaner and easier to understand if you'll expand it.

my 2 cents ;)
Luca

Post Reply

Return to “DSP and Plugin Development”