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.
m_gate[i]) and an AHD control (m_ahdControl[i]). The downstream synth uses m_ahdControl[i].m_trig as “this voice should start a note this frame.”NonagonTrigLogic) – Builds a boolean m_trigs[i] and m_newTrigCanStart[i] per voice from LameJuis triggers, index-arp “triggered,” mutes, and interrupt rules.Process – Combines those with phasor-based timing and mute to set m_ahdControl[i].m_trig and m_gate[i], and to turn the gate off after half a voice-period.The Nonagon sets these before calling SetInput (in SetMultiPhasorGateInputs):
input.m_running (transport running). Must be true for any new trigger to be allowed.m_lameJuis.m_lanes[trio].m_trigger[voiceInTrio]).NonagonTrigLogic::SetInput fills the Multi-Phasor Gate input for all 9 voices.
For each voice i:
trioId = i / 3. The “logical” voice used for trigger sources is ixToCheck = (m_unisonMaster[trioId] == -1) ? i : m_unisonMaster[trioId] (unison master or self).input.m_trigs[i] = (m_trigOnSubTrigger[trioId] && m_subTrigger[ixToCheck]) || (m_trigOnPitchChanged[trioId] && m_pitchChanged[ixToCheck])input.m_trigs[i] &= !m_earlyMuted[ixToCheck]. Reserved for future mute logic; currently always false. When true, blocks triggers.input.m_newTrigCanStart[i] = m_running && !m_earlyMuted[ixToCheck]. A new note is allowed only when the transport is running and the voice is not early-muted.input.m_mute[i] = m_mute[i] (per-voice mute).m_interrupt[trioId][jTrioId] is true and voice j has a trigger this frame and is not muted (input.m_trigs[j] && !input.m_mute[j]), then set input.m_trigs[i] = false. So a lower-index voice with a trigger can cancel the trigger for voice i when interrupt is enabled between their trios.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.
MultiPhasorGateInternal::Process runs once per frame with that input.
Phasor inputs (set by the Nonagon after SetInput):
1 / m_phasorDenominator[i] (derived from the voice’s clock and lens so that the gate length matches the voice’s logical step).Per voice i:
Emit trigger –
m_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.
input.m_trigs[i] && input.m_newTrigCanStart[i]:
m_gate[i] = true and m_ahdControl[i].m_samples = 0.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.Envelope timing – envelopeTimeSamples = m_masterLoopSamples / input.m_phasorDenominator[i] (length of one voice “step” in samples). Stored in m_ahdControl[i].m_envelopeTimeSamples.
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).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.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) UI – SetGate(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.
m_newTrigCanStart (running, not early-muted) and voice not muted.m_trig (start note), m_samples, m_envelopeTimeSamples, and m_release from the same structure, so envelope and voice are driven by this decision.