Consider PCG in place of the voice LCG #6

Closed
opened 2026-08-18 01:34:38 +00:00 by bcox · 0 comments
Owner

The per-voice random generator is an LCG stepping
x = 1664525x + 1013904223, in dsp/layer.h. It is well suited to the target:
one multiply, one add, no state beyond a word, and the draw takes the high bits
((int32)x >> 1), which sidesteps the classic low-bit weakness.

Its real flaw showed up when voices were given distinct seeds: the LCG is
affine, so structure in the seed survives into the output.
Seeding voice i
with i + 1 spaced four voices 0.0008 apart — one value to any ear. Even the
golden-ratio constant produced an arithmetic progression (0.522, 0.572, 0.622,
0.672): a ramp across the chord, not independent draws.

Fixed in 6776866 by running the index through a bit-mixing finalizer before
it becomes a seed, so the affinity is broken before the generator sees it.

The proposal

PCG is an LCG plus an output permutation — the same mixing, applied to every
draw instead of once to the seed. That would make seeding robust by
construction
: even index + 1 would then give decorrelated draws, and nobody
has to remember why SeedFor looks the way it does.

Cost is a shift/xor/multiply per draw, still one word of state, still
synthesisable.

Why it is not urgent

The seed fix addresses the observed problem, and the test in
dsp/voice_allocator_test.cc asserts the shape — that the draws are not an
arithmetic progression — so the affine failure mode cannot come back
unnoticed. Worth revisiting if per-voice or per-note correlation appears
somewhere the seed fix does not reach.

The per-voice random generator is an LCG stepping `x = 1664525x + 1013904223`, in `dsp/layer.h`. It is well suited to the target: one multiply, one add, no state beyond a word, and the draw takes the high bits (`(int32)x >> 1`), which sidesteps the classic low-bit weakness. Its real flaw showed up when voices were given distinct seeds: **the LCG is affine, so structure in the seed survives into the output.** Seeding voice `i` with `i + 1` spaced four voices 0.0008 apart — one value to any ear. Even the golden-ratio constant produced an arithmetic progression (0.522, 0.572, 0.622, 0.672): a ramp across the chord, not independent draws. Fixed in `6776866` by running the index through a bit-mixing finalizer before it becomes a seed, so the affinity is broken before the generator sees it. ## The proposal PCG is an LCG plus an output permutation — the same mixing, applied to every draw instead of once to the seed. That would make seeding robust *by construction*: even `index + 1` would then give decorrelated draws, and nobody has to remember why `SeedFor` looks the way it does. Cost is a shift/xor/multiply per draw, still one word of state, still synthesisable. ## Why it is not urgent The seed fix addresses the observed problem, and the test in `dsp/voice_allocator_test.cc` asserts the *shape* — that the draws are not an arithmetic progression — so the affine failure mode cannot come back unnoticed. Worth revisiting if per-voice or per-note correlation appears somewhere the seed fix does not reach.
bcox closed this issue 2026-08-28 18:48:14 +00:00
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
bcox/tymbal#6
No description provided.