theallelectricsmartgrid

Multi-Phasor Gate and trigger decision

The Multi-Phasor Gate (MultiPhasorGateInternal in private/src/MultiPhasorGate.hpp) decides, per voice, whether to emit a trigger – the gate that starts a note and drives the AHD envelope and the rest of the voice chain. The Nonagon feeds it from the Theory of Time, LameJuis, and index arp, then the Multi-Phasor Gate combines phasor-based gate timing with trigger logic so that only one condition (or the right combination) actually fires a note.


Overview


1. Inputs to the trigger logic

The Nonagon sets these before calling SetInput (in SetMultiPhasorGateInputs):


2. How m_trigs[i] and m_newTrigCanStart[i] are computed

NonagonTrigLogic::SetInput fills the Multi-Phasor Gate input for all 9 voices.

For each voice i:

Result: m_trigs[i] is true only when the chosen trigger source (pitch-changed or sub-trigger) fired, the note is not early-muted, and no lower-index voice “interrupts” it. m_newTrigCanStart[i] is true when running and not early-muted.


3. How the Multi-Phasor Gate turns that into m_trig and m_gate

MultiPhasorGateInternal::Process runs once per frame with that input.

Phasor inputs (set by the Nonagon after SetInput):

Per voice i:

  1. Emit triggerm_ahdControl[i].m_trig = input.m_trigs[i] && input.m_newTrigCanStart[i] && !input.m_mute[i]. So the trigger (what the rest of the synth sees) is true only when: the trigger logic requested a trig, a new trig is allowed, and the voice is not muted. If that is true, m_ahdControl[i].m_release = false.

  2. Start gate and bounds – If input.m_trigs[i] && input.m_newTrigCanStart[i]:
    • If not muted: m_gate[i] = true and m_ahdControl[i].m_samples = 0.
    • In all cases: m_preGate[i] = true, m_set[i] = true, and m_bounds[i].Set(input.m_phasor, input.m_phasorDenominator[i]). So we record the current phasor and the voice’s denominator for phase tracking.
  3. Envelope timingenvelopeTimeSamples = m_masterLoopSamples / input.m_phasorDenominator[i] (length of one voice “step” in samples). Stored in m_ahdControl[i].m_envelopeTimeSamples.

  4. Phase advance and gate off – If m_set[i]:
    • thisPhase = m_bounds[i].Process(input.m_phasor): distance along the circle from the start of this gate to the current phasor, scaled by the voice’s denominator (so 0 -> 1 over one voice period).
    • If thisPhase >= 0.5: gate is turned off (m_gate[i] = false, m_preGate[i] = false). If in addition !input.m_newTrigCanStart[i] || input.m_mute[i], then m_ahdControl[i].m_release = true and m_set[i] = false.
    • m_ahdControl[i].m_samples = thisPhase * envelopeTimeSamples so the AHD envelope sees the correct elapsed time.
  5. m_anyGate – If any voice has m_gate[i] true, m_anyGate is true (used e.g. to keep the Theory of Time “running” while a note is held).

So: m_ahdControl[i].m_trig is the single “emit a trigger” flag. The gate m_gate[i] is true from the frame the trigger is accepted until the phasor has advanced half a voice period (0.5 in normalized phase), giving a 50% duty cycle per step unless a new trigger restarts the bounds.

m_gate is not used for envelopes. Envelope attack/hold/decay are driven by m_ahdControl (m_trig, m_samples, m_envelopeTimeSamples, m_release); the DSP copies m_ahdControl into each voice’s AHD input. m_gate is used instead for: (1) note-off and output – when !m_multiPhasorGate.m_gate[i], the Nonagon sets m_output.m_gate[i] = false and records note end; (2) UISetGate(i, m_gate[i]) so the interface can show which voices have gate high. So m_gate tracks “note still held” for release timing and display; the envelope sees only AHDControl.


4. Summary