I cannot figure out why this simple multiplication makes my output go NaN..
setup:
in process, a doubleRingBuffer(some parts changed from racks internal but relevant bits are identical) that is 512 samples long, pulling chunks that size from the input buffer whenever it is empty. in the relevant snippets it then shifts out every sample. the function TriWindow creates a simple 0.f -1.f-0.f triangle from a given 0.f-1.f phase. delayloop is just a previously existing integer for persistence
everything is fine. but thats not what I want, and sounds fluttery. i’d like the triangle window to last for the same time as the delay itself. however, if i do this:
//totaldelay is a size_t number of samples to delay by, so making it a phase like this is just a direct relation to time
float delayWet = 0.f;
float delooptime = 0.f;
if (!delayBuffer.empty()) {
delayWet = delayBuffer.shift();
delayloop++;
if (delayloop >= (float)totaldelay) delayloop = 0;
}
delooptime = delayloop / (float)(totaldelay);
delayWet *= TriWindow(delooptime);
my output (delayWet) becomes NaN. The result of TriWindow, and the variable delooptime, function exactly how I expect them to, and at no point is delayWet a pointer that could be invalidated, nor am i reaching for an index in the delaybuffer that does not exist. so how does this break it? what gives?
Unrelatedly, I think you may be interested in this trick: If your delay buffer is a power of two size, instead of if(++position >= size) position = 0 you can do the following: position = ++position & (size - 1);.
So in your case, delayloop = ++delayloop & 511; or for the second one, delayloop = ++delayloop & (totalDelay - 1); (though again, totalDelay must be a power of two). Saves you a branch!
quick differences that may help, though the culprit is still almost definitely ‘totaldelay’: the delaybuffer size is 512, but that isnt what the 512 in the snippet or the totaldelay that replace it mean. those are just determining the time the window should take to complete . in my tests the lowest number (float)totaldelay reached was 2871 at the fastest delay time, and I know for a fact it wont ever be 0 because i clamped that out. coding in hard numbers anywhere from 40 to 44500 worked just fine, but i want the window to change with the delay time. Which again it does just fine alone, even outputs usable voltages by itself, but turns my output to nan when i multiply it in.
Hmmm…. Spontaneously I don’t see how a total delay time in samples greater than your total buffer size can be useful? Looking at it again, I’m also not sure why you need the (float)casts. Especially in the comparison since those are both ints, surely?
But yeah this will be hard to help with without knowing more context.
In the comparison ur correct it doesn’t need to be a float, size_t and int compare just fine. The input buffer is several seconds long, that’s where the delay time really matters, as the delay buffer is pulling from that buffer. The vcv fundamental delay only has a 16 sample long output buffer(analogous to my delayBuffer), so that’s why my delaybuffer is shorter than the total delay samples. I’m windowing the delay as if it’s a grain in granular synthesis to give a sort of flutter/wave feel which I like when it’s working, but that window is detached from the size of the outputbuffer. Like, I essentially pull a ‘grain’ the size of my delay time from the inputbuffer , then read that out in 512 chunks(delaybuffer), with an overarching window.
I took the exact same calculations for totaldelay, put them in a variable that is a float the whole time instead of cast from a size_t, and suddenly it works just fine. Both variables output the same value, except of course the size_t conversion is rounded to whole numbers, which still should cause absolutely no issue. So. Reason never found, but solution found. Great.
Very good advice. In addition, if you have significantly more reads from the buffer than writes to it, as in an FIR filter or a windowing function, there is another optimization: make the buffer size twice the nearest larger power of two (let’s say 480 samples required, buffersize is 2 * 512 = 1024) and write values at two positions (with a buffer declared for example as size = 512; delayBuffer[2 * size];):
Then you don’t need to worry about buffer wrap around when reading in a loop as long as the loop counter is less than or equal to size:
read_position = ++read_position & (size - 1);
float* read_ptr = delayBuffer + read_position;
for (int i = 0; i < window_length; i++) {
float value = *read_ptr;
// do something with value
++read_ptr;
}
```
I believe it comes down to assigning to the same variable twice in a single statement in C. Having the same lvalue. Recent C++ introduces sequence points which is why it is defined there. Google/stack overflow is a better reference than myself on the topic.
Here’s a warning my version of gcc / g++ spits out:
2 | a = ++a;
| ~~^~~~~
foo.c:2:5: warning: operation on ‘a’ may be undefined [-Wsequence-point]
Thank you, I was not aware of this. I did some googling and I can see where there can be issues. I am not sure if and how that applies to prefix ++ as in ++position, but there is no harm in doing it in two lines. As Paul said, the compiled code won’t be any different.