Commitf39cce1eRecorded18 Sep 2026Repositorysigil-dsp

chorus: keep the tap read position inside the delay line

Message

A read position a few float ulps below zero rounded to exactly bufsize after the wrap, indexing one past the buffer. Found by review; an exhaustive index probe hit it 2,425 times in 25M (position, delay) pairs and 0 after the fix. Also document that comp applies makeup at ratio 1 and treats a ratio below 1 as bypass.

Changed
 src/c/fx4.c | 3 +++
 src/c/fx4.h | 3 ++-
 2 files changed, 5 insertions(+), 1 deletion(-)
Diff
src/c/fx4.cmodified
@@ -44,6 +44,9 @@ static inline SPFLOAT chorus_tap(const sp_chorus *p, SPFLOAT delay)
44
if (delay > maxd) delay = maxd;
45
SPFLOAT rpos = (SPFLOAT)p->pos - delay;
46
while (rpos < 0.0f) rpos += (SPFLOAT)p->bufsize;
+47
/* A tiny negative rpos rounds to exactly bufsize after the add
+48
* (float ulp at ~1768), which would index one past the buffer. */
+49
if (rpos >= (SPFLOAT)p->bufsize) rpos -= (SPFLOAT)p->bufsize;
50
int i0 = (int)rpos;
51
SPFLOAT frac = rpos - (SPFLOAT)i0;
52
int i1 = i0 + 1;
src/c/fx4.hmodified
@@ -11,7 +11,8 @@
11
* rate reduction. compute(in, drive, bits, rate_div)
12
* sp_comp feed-forward peak compressor with a one-pole attack/release
13
* envelope. compute(in, threshold_db, ratio); attack, release
−14
* and makeup gain are init-time.
+14
* and makeup gain are init-time; makeup is applied even at
+15
* ratio 1, and a ratio below 1 is a bypass, not expansion.
16
*
17
* Deterministic: no randomness, no libm on the per-sample path except
18
* tanhf in the crush drive stage and the dB conversions in the compressor