Skip to content

Commit 27b7c99

Browse files
committed
LFOModifier: gate output by MIDI activity in note-trigger mode
Adds setGateOnTriggerSource(): when set and syncType == note, applyToBuffer drives the existing gated_ flag from the MIDI stream itself — NoteOn ungates and increments a held-note count, NoteOff (vel-0-aware) decrements it, AllNotesOff/AllSoundOff hard-resets. Gate re-asserts once the count returns to zero. This lets MIDI-trigger LFOs behave like envelopes — output sits at 0 between notes — without changing the free-run sync mode for callers that don't opt in.
1 parent ad97a0b commit 27b7c99

2 files changed

Lines changed: 44 additions & 2 deletions

File tree

modules/tracktion_engine/model/automation/modifiers/tracktion_LFOModifier.cpp

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -256,12 +256,38 @@ void LFOModifier::applyToBuffer (const PluginRenderContext& prc)
256256
if (prc.bufferForMidiMessages == nullptr)
257257
return;
258258

259-
if (skipNativeResync_.load (std::memory_order_acquire))
259+
const bool skipResync = skipNativeResync_.load (std::memory_order_acquire);
260+
const bool gateByMidi = gateOnTriggerSource_.load (std::memory_order_acquire)
261+
&& juce::roundToInt (syncTypeParam->getCurrentValue()) == ModifierCommon::note;
262+
263+
if (skipResync && ! gateByMidi)
260264
return;
261265

262266
for (auto& m : *prc.bufferForMidiMessages)
267+
{
263268
if (m.isNoteOn())
264-
modifierTimer->resync (prc.bufferNumSamples / getSampleRate());
269+
{
270+
if (! skipResync)
271+
modifierTimer->resync (prc.bufferNumSamples / getSampleRate());
272+
273+
if (gateByMidi)
274+
{
275+
++heldNotes_;
276+
gated_.store (false, std::memory_order_release);
277+
}
278+
}
279+
else if (gateByMidi && m.isNoteOff (true))
280+
{
281+
heldNotes_ = juce::jmax (0, heldNotes_ - 1);
282+
if (heldNotes_ == 0)
283+
gated_.store (true, std::memory_order_release);
284+
}
285+
else if (gateByMidi && (m.isAllNotesOff() || m.isAllSoundOff()))
286+
{
287+
heldNotes_ = 0;
288+
gated_.store (true, std::memory_order_release);
289+
}
290+
}
265291
}
266292

267293
void LFOModifier::triggerNoteOn (bool forceZeroValue)

modules/tracktion_engine/model/automation/modifiers/tracktion_LFOModifier.h

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,20 @@ class LFOModifier : public Modifier,
105105
void setSkipNativeResync (bool skip) { skipNativeResync_.store (skip, std::memory_order_release); }
106106
bool getSkipNativeResync() const { return skipNativeResync_.load (std::memory_order_acquire); }
107107

108+
/** When true and syncType == note, applyToBuffer() drives the gated_ flag
109+
from the MIDI stream: NoteOn ungates and increments a held-note count,
110+
NoteOff (or vel-0 NoteOn) decrements it; gate is re-asserted once the
111+
count returns to zero. AllNotesOff / AllSoundOff force the count to
112+
zero. Lets MIDI-trigger LFOs behave like envelopes — output sits at 0
113+
between notes — without changing the free-run sync mode. */
114+
void setGateOnTriggerSource (bool g)
115+
{
116+
gateOnTriggerSource_.store (g, std::memory_order_release);
117+
if (! g)
118+
gated_.store (false, std::memory_order_release);
119+
}
120+
bool getGateOnTriggerSource() const { return gateOnTriggerSource_.load (std::memory_order_acquire); }
121+
108122
private:
109123
struct LFOModifierTimer;
110124
std::unique_ptr<LFOModifierTimer> modifierTimer;
@@ -113,6 +127,8 @@ class LFOModifier : public Modifier,
113127
std::atomic<float> currentPhase { 0.0f }, currentValue { 0.0f };
114128
std::atomic<bool> gated_ { false };
115129
std::atomic<bool> skipNativeResync_ { false };
130+
std::atomic<bool> gateOnTriggerSource_ { false };
131+
int heldNotes_ = 0; // audio-thread only; touched from applyToBuffer
116132

117133
void valueTreeChanged() override;
118134
};

0 commit comments

Comments
 (0)