Partial bank has no layer-local note-on randoms, but the README says it has two #55

Open
opened 2026-08-31 00:17:29 +00:00 by bcox · 0 comments
Owner

The partial bank's engine source vocabulary is its two LFOs and nothing else:

// dsp/partial_layer.h:358
// No randoms. Per-note variation on this engine is per *partial* -- a start
// phase drawn per note is what separates a strike from a chime -- and that is
// a patch mode rather than a matrix source, because one shared draw routed to
// thirty-two frequency lanes moves them all by the same amount, which is a
// detune and not a scatter. Appending them later is an append.
enum Source : int {
  kSourceLfo1 = 0,
  kSourceLfo2,
  kSourceCount,
};

The FM pair has them -- kSourceRandomA/kSourceRandomB, drawn from the engine's own LCG in NoteOn (dsp/fm_pair_layer.h:469, :2136), labelled "Random A"/"Random B" in editor/source_labels.h:47. The VA has none either, and doesn't claim any.

The README's Partial Bank section says otherwise:

There's a mod matrix with 64 entries; all continuous parameters as dests and as 1-sample-delayed sources. Two modulatable layer LFOs, two note-on randoms, plus the usual voice note-on randoms and the voice and global LFOs.

(Found while fact-checking the README. The entry count in that same sentence is wrong too -- kPartialModEntries is 32, not 64 -- and that half is a plain doc fix.)

Which way to resolve

The code's stated reason is narrow and correct as far as it goes: one draw routed across the 32 frequency lanes is a detune, not a scatter, and the scatter you actually want at note-on is already PhaseMode::kRandom over start_phase. But that argues only against randoms as a frequency source. A single per-note draw is still the ordinary way to reach:

  • amplitude and pan, for note-to-note variation in a struck/plucked bank
  • timeline speed (all four clocks are destinations), so a strike's decay rate varies per note
  • LFO rate/width/slew, so two notes' motion decorrelates -- the same complaint as #33, one level down
  • an amount via, to make any other routing note-dependent

The two voice-level randoms (kSharedRandomA/B) are reachable from this engine and cover some of that today, but they are shared across every layer in the patch: two partial-bank layers stacked in one voice get the same pair, so they cannot be decorrelated from each other.

So the options are:

  1. Append the two sources, mirroring the FM pair verbatim -- two Sample members, two draws off an engine-local LCG in NoteOn, two arms in SourceValue, two rows in editor/source_labels.h. The comment above already calls this an append, and it is: a source is a U16 on disk (plugin/patch_format.h:1169) and the new ids land past kSourceLfo2, so every existing patch keeps meaning what it meant. Worth noting the one-way part -- a patch using the new sources, read by a build predating them, falls through SourceValue's switch to zero rather than being refused.
  2. Fix the README instead, and extend the // No randoms. comment to say the decision covers the whole source vocabulary rather than just the frequency lanes.

I lean toward (1): the README is the design intent, the cost is a handful of members, and the decorrelation argument reaches beyond the frequency lanes the existing comment answers. But it is a design call, not a bug.

Related: #33 (shared S&H seed across LFOs), #36 (general-purpose lanes as sources, which would subsume some of this).

The partial bank's engine source vocabulary is its two LFOs and nothing else: ``` // dsp/partial_layer.h:358 // No randoms. Per-note variation on this engine is per *partial* -- a start // phase drawn per note is what separates a strike from a chime -- and that is // a patch mode rather than a matrix source, because one shared draw routed to // thirty-two frequency lanes moves them all by the same amount, which is a // detune and not a scatter. Appending them later is an append. enum Source : int { kSourceLfo1 = 0, kSourceLfo2, kSourceCount, }; ``` The FM pair has them -- `kSourceRandomA`/`kSourceRandomB`, drawn from the engine's own LCG in `NoteOn` (`dsp/fm_pair_layer.h:469`, `:2136`), labelled "Random A"/"Random B" in `editor/source_labels.h:47`. The VA has none either, and doesn't claim any. The README's Partial Bank section says otherwise: > There's a mod matrix with 64 entries; all continuous parameters as dests and as 1-sample-delayed sources. Two modulatable layer LFOs, **two note-on randoms**, plus the usual voice note-on randoms and the voice and global LFOs. (Found while fact-checking the README. The entry count in that same sentence is wrong too -- `kPartialModEntries` is 32, not 64 -- and that half is a plain doc fix.) ## Which way to resolve The code's stated reason is narrow and correct as far as it goes: one draw routed across the 32 frequency lanes is a detune, not a scatter, and the scatter you actually want at note-on is already `PhaseMode::kRandom` over `start_phase`. But that argues only against randoms as a *frequency* source. A single per-note draw is still the ordinary way to reach: - amplitude and pan, for note-to-note variation in a struck/plucked bank - timeline speed (all four clocks are destinations), so a strike's decay rate varies per note - LFO rate/width/slew, so two notes' motion decorrelates -- the same complaint as #33, one level down - an amount `via`, to make any other routing note-dependent The two voice-level randoms (`kSharedRandomA`/`B`) are reachable from this engine and cover some of that today, but they are shared across every layer in the patch: two partial-bank layers stacked in one voice get the same pair, so they cannot be decorrelated from each other. So the options are: 1. **Append the two sources**, mirroring the FM pair verbatim -- two `Sample` members, two draws off an engine-local LCG in `NoteOn`, two arms in `SourceValue`, two rows in `editor/source_labels.h`. The comment above already calls this an append, and it is: a source is a U16 on disk (`plugin/patch_format.h:1169`) and the new ids land past `kSourceLfo2`, so every existing patch keeps meaning what it meant. Worth noting the one-way part -- a patch *using* the new sources, read by a build predating them, falls through `SourceValue`'s switch to zero rather than being refused. 2. **Fix the README instead**, and extend the `// No randoms.` comment to say the decision covers the whole source vocabulary rather than just the frequency lanes. I lean toward (1): the README is the design intent, the cost is a handful of members, and the decorrelation argument reaches beyond the frequency lanes the existing comment answers. But it is a design call, not a bug. Related: #33 (shared S&H seed across LFOs), #36 (general-purpose lanes as sources, which would subsume some of this).
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#55
No description provided.