From the original SynthMaker forum (2004–2014), with its members’ names and dates.
Leslie147 ·
I think we understand that the subtraction of 0.499999etc. in interpolated delays is there to overcome a difficulty associated with the use of 'banker's rounding' in SM which rounds exactly half-way values (i.e. x.500000) to the nearest even integer. However, I, and possibly others, had been harbouring the belief that the subtraction merely pushes the issue away to near-integer values but I have realised that this is not so. It helped me to write out a sequence of values in a table, and I hope this comes out as intended - I have needed to use lots of . . . separators because strings of spaces seem to get swallowed up.
Using the 'banker's rounding' alone to produce integer + fraction = delay:
… so fraction is kept positive for a greater portion of the cycle whilst integer + fraction = delay with no loss of accuracy. Indeed, by subtracting the number that is less than 0.5 by the limit of the resolution of the number system (e.g. 0.49999999999) the incidence of negative values of fraction is all but eliminated. But why bother?
In interpolated delays, with either linear or all-pass interpolation, fraction is used as a multiplier to adjust the cross-fade between the integer-delayed sample and its neighbour.
out = temp1 + fraction*(temp2–temp1) ... in linear interpolation, and:
out = temp2 + fraction*(temp1–previousout) ... in all-pass interpolation.
At the moment I can't see a mechanism by which allowing fraction to go negative would cause anything to go wrong; glitches or anything like that. So could this all be something to do with the notion that subtraction is to be avoided because it takes more CPU cycles, which appears to be discredited, at least for PCs using Intel processors. In any case, the implied subtraction would be implemented as addition of a negative number.
In practice, I have tried taking out the subtraction from interpolated delays and they seem to perform just the same - without glitches. The same principle was applied in the original code for stream exponentiation, where the exponent is broken up into an integer and a fraction, and that appears to work fine without the subtraction.
Can anyone else throw any light on this subject?
Chris
cyto ·
I just read over your post briefly (but will come back to digest later) so this may be a bit OT. I just wanted to quickly point out to anyone that's interested that the practice of subtracting absurdly precise values like .499999999999999999999999999999999 is counter-productive. I would assert that the "optimal" value is 0.49999997 (exactly). That is the largest number that the float-32 representation can accommodate before rounding up to .5
-cyto
cyto ·
Okay, I thought about this a little (though I suspect not as much as you 😉 ). You are right that the negative fractions shouldn't be a problem in the interpolation math and if you are not modulating the delay, there should be no difference. I suspect the issue here has more to do with the actual indices of the memory buffer that are being accessed. You need a consistent method of finding those "temp 1" and "temp 2" variables. Without the rounding down, I believe that when you modulate the delay value, there will be intermittent "jumps" in the reading of the buffer (since in the int delay module, temp2 is always = (temp1-1)). I admit I haven't done any tests on this theory, but it makes sense to me that the most precise interpolation math won't do any good if one of the values is incorrect. Out of curiosity, when you did your tests, did you just use static delay values or did you modulate them as well?
-cyto
(just throwing out ideas)
Leslie147 ·
I've found that 0.4999995 is at the limit before rounding to 0.5. Easy enough to verify by adding 0.00000001 and seeing what happens.
Actually, the all-pass interpolated delay does behave differently with the subtraction disabled, as the delay is varied, looking at the impulse response - in that 'inscrutable' way that all-pass fractional-sample delay behaves with its damped-oscillatory impulse response. Too hard for me to explain at the moment, but I'll look further into it. However, as I wrote before:
Can anyone else throw any light on this subject? ... i.e. an answer, rather than further questions?
Chris
cyto ·
Leslie147 wrote: Can anyone else throw any light on this subject? ... i.e. an answer, rather than further questions?
What a snotty thing to say! This is a "forum" afterall and people should be able to comment as they deem appropriate. BTW, I wasn't adding more questions, I was pointing out a potential flaw in your experiments. But since you seem to be too self-absorbed to actually digest what I said, let me hold your hand through an example using concrete data...
Say we're implementing a classic linearly-interpolated, circular-buffer delay. Stored in that buffer are the following values:
Now say our pointer is currently at 40 and we want a delay of 12.4 samples. So we want a value that will theoretically be at index[27.6]. Since we don't have that, we find the value 60% between index[27] and index[28], which should leave us with a value of 32. Right?
Okay, so let's do the math that would be done in the delay code, but without subtraction.
intDelay = 12 (cvtps2dq rounding of 12.4)
frac = 12.4-12 = .4 (delay-intDelay)
temp1(index) = 40-12 = 28
temp2(index) = 28-1 = 27
temp1(value) = 20 (from above table)
temp2(value) = 50 (from above table)
interpolation = 20+.4(50-20) = 20+12 = 32
So it works out correctly!
But now suppose we want a delay of 12.6 (keeping all other assumptions the same). We would want a value that would theoretically be at index[27.4]. Which should be 38. Right?
Let's redo the math...
intDelay = 13 (cvtps2dq rounding of 12.6 - rounded up this time because it's over .5)
frac = 12.6-13 = -.4 (delay-intDelay)
temp1(index) = 40-13 = 27
temp2(index) = 27-1 = 26
temp1(value) = 50 (from above table)
temp2(value) = 10 (from above table)
interpolation = 50-.4(10-50) = 50+16 = 66
NOT RIGHT!!!!
You'll should notice that if we had performed the appropriate corrective subtraction to either of the two examples it would have rounded to the correct integers in both cases and the result would have been correct (or do you need me to walk you through that, too?). That's what I'm talking about. It has nothing to do with "banker's rounding". It has to do with correct buffer indices.
Leslie147 wrote: I've found that 0.4999995 is at the limit before rounding to 0.5. Easy enough to verify by adding 0.00000001 and seeing what happens.
That value leaves bit 4 unset. There's still a little room to go...
-cyto
Leslie147 ·
Cyto, thank you for that explanation - now I understand what you were saying in your previous posting and clearly you have it right. Thank you for going to the trouble of explaining. I apologise for writing in what appeared to be a brusque manner.
My error concerning 0.499999 comes from the fact that when I type 0.499999 into a green 'Float ', its value stays as typed; if I type anything from 0.4999991 to 0.4999995 the value appears to be rounded down to 0.499999; but if I type anything between 0.4999996 and 0.4999999 the value is rounded up to 0.5.
In assembly-language, if I put:
streamout value;
float value=0.4999995;
... the output value is as typed, but if I increase 'value' to 0.4999996 the output value is 0.5.
There's still something I'm missing in the example you give: adding 0.4999995 to 0.00000001, giving a result of 0.49999949, seems odd to me because that would be the result of subtraction. Also, I don't seem able to get a Mono readout to display so many digits. I'm beginning to wonder if the difference could be something to do with me still using Version 1.1.7 of SM.
Chris
cyto ·
Hey Chris.
I should apologize. I was very cranky when I wrote that. I'm in the process of trying to get some crazy envelope splines working, knee-deep in Bernstein polynomials and getting really frustrated. I took my frustrations out here 😊
Regarding the 0.4999 numbers: I don't know if your version has this option, but there is a setting in the options menu to "display float32 representations" (or something like that). That will show the numbers as they are actually represented (though I have found that the representations are not always right). I have a IEEE 754 Converter tool that I built in SM that I use to see how the numbers are represented. I'm putting together a little bundle of tools that will include it that I hope to be posting here soon. In the mean time, there are plenty of online converters/calculators.
The issue with the 0.4999995 number is that there is no direct binary representation of that exact number. The closest is 0.49999949. I think what's happening in the addition is that the .00000001 number is just getting "eaten up" by the other one because of its relative insignificance (exponent of 2^-27 vs 2^-2).
Again, sorry for the earlier outburst.
-cyto
stw ·
@cyto: cranky or not... a very illustrative explanation. Should go into the wiki! Thanks for that!
Wrote something here? To have a post removed or your name hidden, write to [email protected].