Paraphony: one filter row per channel, and the gate a shared envelope needs #108
Labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
bcox/tymbal#108
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
bcox asked on 2026-10-04 how a Poly-800's shared filter envelope would be done in Tymbal, and then how far a "paraphonic" switch could carry it. This ticket records the answer so a spec can start from it. Nothing here is built, measured or listened to. It is an assessment from reading the tree at
a9725d90onmain. The hardware claims are from the sources at the end, read in that session; the DSP claims are from the tree's own comments and tests.Summary
A shared filter is already expressible as two slots. What is missing is the gate, and the thing worth building is the switch that hides the wiring.
VaShape::kInputreading the first slot's tap. That gives one filter row for the whole chord, which is the half that cannot be faked.dsp/voice_allocator.h:917) -- and is never released. Its envelopes reach sustain at program load and stay there. Nothing retriggers them, and a paraphonic machine's whole character is that each key press hits the one filter EG.What the hardware does
Two consequences that shape the design:
What is already here
dsp::LayerScope, the held voice indsp/voice_allocator.hpatches/sympathetic_strings.handpatches/soundboard.hprove both directionsVaShape::kInput(dsp/va_oscillator.h:1383)kSharedChannelLoad(dsp/mod_source.h:88), followed bydsp/va_sag.hkSharedChannelLfo1/2dsp/va_layer.h:52), pinned byVaLayer.TheEnvelopeSilencesARingingFilterVoiceMode::retrigger_envelope, and the legatoReferenceNote(true, …)path atdsp/voice_allocator.h:372VoiceMode::polyphony(dsp/layer.h:267)patches/poly800_strings.his not a counterexample: it is one voice-scope VA layer with a 2-pole SVF at a fixed cutoff, no filter envelope at all, andsag.channel_loadrouted to amplitude only. It models the shared supply, which its header says plainly, and not the shared filter. It is the obvious first caller here.The work
1. A channel gate for the held voice
kSharedChannelLoad. That source deliberately counts released-but-ringing voices, which is right for sag and wrong for a trigger: a two-second release would swallow the next phrase's attack.slot.releasedis already tracked.2. The trigger mode as the assigner's sample-and-hold
The shared half cannot read per-voice sources -- whose velocity, whose key? The hardware's answer is the S&H on the keyboard assigner, and the trigger mode already names it: under all the shared half latches the note that last triggered it, under single the first note of the phrase. One field defines the gate and the latched CVs together, and VCF keytrack, velocity -> cutoff and note -> cutoff all become well-defined at channel scope. Destinations past the cut then accept channel sources plus the latched set; the editor can grey out the rest.
3. The split itself
sources -> cross-mod -> level -> ring -> noise -> wide sum -> requantize -> [shaper, pre-row] -> filter 1 -> [shaper, inter-filter] -> filter 2 -> VCA -> (+ post-filter bus x amplitude) -> [shaper, post-row] -> decimate. The cut is afterrequantizeand takes the whole shaper/filter/VCA block with it -- defined as the block, not as a stage, because the shaper has three insert points and one of them is pre-row.dsp/va_layer.h:52does not apply. The second argument in that comment -- a resonant filter cannot be closed by an upstream VCA -- does apply, and is exactly the Poly-800's behavior.kInputalready does, so the two-slot idiom has already proved that path.4. Invariants that become half-rules
Scope is one enum per slot today, which is what keeps
HeldSelects/Soundssimple and gives "a note voice never starts a channel-scope slot" its teeth. A paraphonic slot is rendered by both roles with different halves live, so those predicates have to learn about halves. The tap row needs no new storage: a paraphonic slot produces on the note side and consumes on the held side at the same slot index, which is one tap per slot per channel either way, and the graph stays acyclic because the front half never reads the back.5. Format
Additive: a per-layer flag, a trigger mode, a sum gain law. Minor bump, absent fields read as voice scope and unity.
The two machines, as patches
A full Omni is then two or three paraphonic layers in one patch -- strings, synth, and the bass section as a mono layer with its own 2-pole filter -- which is how the instrument is laid out. Korg Mono/Poly and the Solina-family string machines fall in the same envelope.
Open questions
scope[]'s granularity and lets one patch hold a paraphonic Poly-800 part beside an ordinary poly pad; two paraphonic layers read the same note-on events with their own trigger modes.Sources
Addendum, 2026-10-05: three cases from the third set of instruments, and a correction
From the research recorded in bcox's
moregaps.mdthat day: the Korg Trident, Delta, Polysix and Mono/Poly, the Elka Synthex, and the ARP Omni-2's service manual, all read as archive.org OCR by research agents and not re-checked here. Engine references are tof6aa8eba. Nothing below is built.Three cases the trigger vocabulary above does not cover
A trigger that waits for a count of keys. The Trident's brass section has a "silence note" count: no brass sounds until that many keys are down. Its trigger select is first-note-only, multiple, that threshold, or an external trigger. So single wants a parameter, the held-key count the gate fires at, with one as today's meaning.
The shared filter tracking the highest key. The Delta's keyboard follower moves cutoff "in proportion to the pitch of the highest key played", and the Poly-800's tracking follows the highest note too. §2's latch rule -- all latches the last note, single the first -- gives neither. A third rule: the highest held key (and the lowest, for symmetry), re-evaluated on every note-on and note-off rather than latched. This is also the source #114 wants for its "other key".
A modulator delay restarted only by the first key after all are up. The Polysix's MG delay is "initiated whenever all keys are released and new key(s) are then depressed"; the Synthex's LFO 1 delay runs from the first note, and above delay 1 the first note also restarts the waveform. These are consumers of §1's single gate for something that is not an envelope: a channel LFO's restart and its delay. The gate should therefore be a source any channel-scope thing can read as an edge, not a call wired to the shared half's envelopes alone. #116 proposes that edge as a general trigger kind.
Correction to "What the hardware does" and "The two machines, as patches"
The Omni strings row says attack and release per key, shared VCA open, no shared envelope. The Omni-2 service manual, as read, says otherwise:
So the strings need the channel gate too: single, with an attack-only shared envelope, and the per-key release in the front half. The manual confirms the rest of the Omni column: one 4075 VCF at 24 dB per octave, one ADSR for filter and VCA in the synth section, the single-trigger switch, and a two-pole bass filter.
More machines that land here
Mono/Poly (poly and unison/share modes: four keys through one filter and one EG pair), the Trident's brass, both Delta sections (synth and strings, each with its own single/multiple trigger), the Polymoog's one VCF (each key also has an amplifier and a filter of its own), and the Pro 2's and Pro 3's paraphonic modes, where each oscillator has its own amplitude envelope into the shared filter -- §3's front-half VCA per oscillator rather than per voice. Duophony (Odyssey, CAT, Quadra's lead, Teisco S-110F) is not this ticket: it keeps both oscillators in one voice on two keys, and is #114.