What's wrong
DelayLine.ReadInterpolated(float) (Containers/DelayLine.cs ~line 126–134 at 1d328b0) range-checks with delaySamples > Capacity, which promotes the int Capacity to float. Above 2^24, float can't represent every int, so the conversion rounds:
Capacity = 33554431 (2^25 − 1): (float)Capacity == 33554432f. A delay of 33554432f is one more than Capacity, yet it passes the check.
(int)delaySamples is then 33554432, the full buffer length. (writeIndex - 1 - 33554432) & mask wraps to writeIndex - 1, which is the sample at delay 0.
The natural call line.ReadInterpolated(line.Capacity) hits this, because the int argument is implicitly converted to float and rounds up the same way.
Reproduction (observed on net10.0)
DelayLine line = new((1 << 25) - 1);
line.Write(1f); // oldest sample, delay == Capacity
for (int i = 1; i < line.Capacity; i++) line.Write(0f);
line.Write(99f); // newest sample, delay 0
Console.WriteLine($"{line.Read(line.Capacity)} {line.ReadInterpolated(line.Capacity)}");
// actual: 1 99
// expected: 1 1
The integer Read(Capacity) returns the oldest sample correctly. The float overload returns the newest one, and no exception is thrown.
A milder variant: with Capacity = 2^24 + 1, (float)Capacity rounds down to 2^24. ReadInterpolated(Capacity) then silently reads the sample at delay 2^24 instead of the oldest one.
Why it matters
maxDelaySamples = 2^k − 1 is exactly the size that fills the power-of-two buffer with no waste, so callers are likely to pick it. 2^25 samples is about 11.6 minutes at 48 kHz, a realistic size for looper or long-echo buffers. The failure is silent: the caller gets the most recent audio fed straight back instead of the oldest, and the documented ArgumentOutOfRangeException for delays outside [0, Capacity] never fires.
This is separate from #89, which is about sizes above 2^30 overflowing at construction.
Suggested fix / acceptance criteria
- Do the range check in
double, where every int is exact: (double)delaySamples > Capacity.
- Clamp the integer part:
int delayInt = Math.Min((int)delaySamples, Capacity);, keeping the existing olderDelay clamp. This guarantees the masked index can never wrap onto newer samples.
- Add a regression test: for a
(1 << 25) - 1 line, ReadInterpolated(Capacity) must equal Read(Capacity).
What's wrong
DelayLine.ReadInterpolated(float)(Containers/DelayLine.cs~line 126–134 at 1d328b0) range-checks withdelaySamples > Capacity, which promotes the intCapacitytofloat. Above 2^24,floatcan't represent every int, so the conversion rounds:Capacity = 33554431(2^25 − 1):(float)Capacity == 33554432f. A delay of33554432fis one more thanCapacity, yet it passes the check.(int)delaySamplesis then 33554432, the full buffer length.(writeIndex - 1 - 33554432) & maskwraps towriteIndex - 1, which is the sample at delay 0.The natural call
line.ReadInterpolated(line.Capacity)hits this, because the int argument is implicitly converted tofloatand rounds up the same way.Reproduction (observed on net10.0)
The integer
Read(Capacity)returns the oldest sample correctly. The float overload returns the newest one, and no exception is thrown.A milder variant: with
Capacity = 2^24 + 1,(float)Capacityrounds down to 2^24.ReadInterpolated(Capacity)then silently reads the sample at delay 2^24 instead of the oldest one.Why it matters
maxDelaySamples = 2^k − 1is exactly the size that fills the power-of-two buffer with no waste, so callers are likely to pick it. 2^25 samples is about 11.6 minutes at 48 kHz, a realistic size for looper or long-echo buffers. The failure is silent: the caller gets the most recent audio fed straight back instead of the oldest, and the documentedArgumentOutOfRangeExceptionfor delays outside[0, Capacity]never fires.This is separate from #89, which is about sizes above 2^30 overflowing at construction.
Suggested fix / acceptance criteria
double, where every int is exact:(double)delaySamples > Capacity.int delayInt = Math.Min((int)delaySamples, Capacity);, keeping the existingolderDelayclamp. This guarantees the masked index can never wrap onto newer samples.(1 << 25) - 1line,ReadInterpolated(Capacity)must equalRead(Capacity).