-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathtracktion_LFOModifier.cpp
More file actions
361 lines (299 loc) · 13.7 KB
/
Copy pathtracktion_LFOModifier.cpp
File metadata and controls
361 lines (299 loc) · 13.7 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
/*
,--. ,--. ,--. ,--.
,-' '-.,--.--.,--,--.,---.| |,-.,-' '-.`--' ,---. ,--,--, Copyright 2024
'-. .-'| .--' ,-. | .--'| /'-. .-',--.| .-. || \ Tracktion Software
| | | | \ '-' \ `--.| \ \ | | | |' '-' '| || | Corporation
`---' `--' `--`--'`---'`--'`--' `---' `--' `---' `--''--' www.tracktion.com
Tracktion Engine uses a GPL/commercial licence - see LICENCE.md for details.
*/
namespace tracktion { inline namespace engine
{
struct LFOModifier::LFOModifierTimer : public ModifierTimer
{
LFOModifierTimer (LFOModifier& lfo)
: modifier (lfo)
{
}
void updateStreamTime (TimePosition editTime, int numSamples) override
{
const double blockLength = numSamples / modifier.getSampleRate();
modifier.setEditTime (editTime);
modifier.updateParameterStreams (editTime);
const auto syncTypeThisBlock = juce::roundToInt (modifier.syncTypeParam->getCurrentValue());
const auto rateTypeThisBlock = getTypedParamValue<ModifierCommon::RateType> (*modifier.rateTypeParam);
const float rateThisBlock = modifier.rateParam->getCurrentValue();
if (rateTypeThisBlock == ModifierCommon::hertz)
{
const float durationPerPattern = 1.0f / rateThisBlock;
ramp.setDuration (durationPerPattern);
if (syncTypeThisBlock == ModifierCommon::transport)
ramp.setPosition (std::fmod ((float) editTime.inSeconds(), durationPerPattern));
setPhase (ramp.getProportion());
// Move the ramp on for the next block
ramp.process ((float) blockLength);
}
else
{
tempoSequence.set (editTime);
const auto currentTempo = tempoSequence.getTempo();
const auto currentTimeSig = tempoSequence.getTimeSignature();
const auto proportionOfBar = ModifierCommon::getBarFraction (rateTypeThisBlock);
if (syncTypeThisBlock == ModifierCommon::transport)
{
const auto editTimeInBeats = tempoSequence.getBeats().inBeats();
const auto bars = (editTimeInBeats / currentTimeSig.numerator) * rateThisBlock;
if (rateTypeThisBlock >= ModifierCommon::sixteenBars && rateTypeThisBlock <= ModifierCommon::sixtyFourthT)
{
const double virtualBars = bars / proportionOfBar;
setPhase ((float) std::fmod (virtualBars, 1.0f));
}
}
else
{
const double bpm = (currentTempo * rateThisBlock) / proportionOfBar;
const double secondsPerBeat = 60.0 / bpm;
const float secondsPerStep = static_cast<float> (secondsPerBeat * currentTimeSig.numerator);
const float secondsPerPattern = secondsPerStep;
ramp.setDuration (secondsPerPattern);
setPhase (ramp.getProportion());
// Move the ramp on for the next block
ramp.process ((float) blockLength);
}
}
}
void setPhase (float newPhase)
{
newPhase += modifier.phaseParam->getCurrentValue();
while (newPhase >= 1.0f) newPhase -= 1.0f;
while (newPhase < 0.0f) newPhase += 1.0f;
if (newPhase < modifier.getCurrentPhase())
{
previousRandom = currentRandom;
currentRandom = rand.nextFloat();
randomDifference = currentRandom - previousRandom;
}
jassert (juce::isPositiveAndBelow (newPhase, 1.0f));
modifier.currentPhase.store (newPhase, std::memory_order_release);
auto getValue = [this, newPhase]
{
using namespace PredefinedWavetable;
switch (getTypedParamValue<LFOModifier::Wave> (*modifier.waveParam))
{
case waveSine: return getSinSample (newPhase);
case waveTriangle: return getTriangleSample (newPhase);
case waveSawUp: return getSawUpSample (newPhase);
case waveSawDown: return getSawDownSample (newPhase);
case waveSquare: return getSquareSample (newPhase);
case fourStepsUp: return getStepsUpSample (newPhase, 4);
case fourStepsDown: return getStepsDownSample (newPhase, 4);
case eightStepsUp: return getStepsUpSample (newPhase, 8);
case eightStepsDown: return getStepsDownSample (newPhase, 8);
case random: return currentRandom;
case noise: return (randomDifference * newPhase) + previousRandom;
case waveCustomCallback:
{
auto fn = modifier.customWaveFunction.load (std::memory_order_acquire);
auto ud = modifier.customWaveUserData.load (std::memory_order_acquire);
if (fn) return fn (newPhase, ud);
return getSinSample (newPhase);
}
}
return 0.0f;
};
float newValue = getValue();
newValue *= modifier.depthParam->getCurrentValue();
newValue += modifier.offsetParam->getCurrentValue();
if (getBoolParamValue (*modifier.bipolarParam))
newValue = (newValue * 2.0f) - 1.0f;
modifier.currentValue.store (newValue, std::memory_order_release);
}
void resync (double duration)
{
const auto type = juce::roundToInt (modifier.syncTypeParam->getCurrentValue());
if (type == ModifierCommon::note)
{
ramp.setPosition (0.0f);
setPhase (0.0f);
// Move the ramp on for the next block
ramp.process ((float) duration);
}
}
LFOModifier& modifier;
Ramp ramp;
tempo::Sequence::Position tempoSequence { createPosition (modifier.edit.tempoSequence) };
juce::Random rand;
float previousRandom = 0.0f, currentRandom = 0.0f, randomDifference = 0.0f;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (LFOModifierTimer)
};
//==============================================================================
LFOModifier::LFOModifier (Edit& e, const juce::ValueTree& v)
: Modifier (e, v)
{
auto um = &edit.getUndoManager();
wave.referTo (state, IDs::wave, um, waveSine);
syncType.referTo (state, IDs::syncType, um, float (ModifierCommon::free));
rate.referTo (state, IDs::rate, um, 1.0f);
rateType.referTo (state, IDs::rateType, um, float (ModifierCommon::bar));
depth.referTo (state, IDs::depth, um, 1.0f);
bipolar.referTo (state, IDs::bipolar, um);
phase.referTo (state, IDs::phase, um);
offset.referTo (state, IDs::offset, um);
auto addDiscreteParam = [this] (const juce::String& paramID, const juce::String& name,
juce::Range<float> valueRange, juce::CachedValue<float>& val,
const juce::StringArray& labels) -> AutomatableParameter*
{
auto* p = new DiscreteLabelledParameter (paramID, name, *this, valueRange, labels.size(), labels);
addAutomatableParameter (p);
p->attachToCurrentValue (val);
return p;
};
auto addParam = [this] (const juce::String& paramID,
const juce::String& name,
juce::NormalisableRange<float> valueRange,
float centreVal, juce::CachedValue<float>& val,
const juce::String& suffix) -> AutomatableParameter*
{
valueRange.setSkewForCentre (centreVal);
auto* p = new SuffixedParameter (paramID, name, *this, valueRange, suffix);
addAutomatableParameter (p);
p->attachToCurrentValue (val);
return p;
};
using namespace ModifierCommon;
waveParam = addDiscreteParam ("wave", TRANS("Wave"), { 0.0f, float (waveCustomCallback) }, wave, getWaveNames());
syncTypeParam = addDiscreteParam ("syncType", TRANS("Sync type"), { 0.0f, (float) getSyncTypeChoices().size() - 1 }, syncType, getSyncTypeChoices());
rateParam = addParam ("rate", TRANS("Rate"), { 0.01f, 50.0f }, 1.0f, rate, {});
rateTypeParam = addDiscreteParam ("rateType", TRANS("Rate Type"), { 0.0f, (float) getRateTypeChoices().size() - 1 }, rateType, getRateTypeChoices());
depthParam = addParam ("depth", TRANS("Depth"), { 0.0f, 1.0f }, 0.5f, depth, {});
bipolarParam = addDiscreteParam ("biopolar", TRANS("Bipoloar"), { 0.0f, 1.0f }, bipolar, { NEEDS_TRANS("Uni-polar"), NEEDS_TRANS("Bi-polar") });
phaseParam = addParam ("phase", TRANS("Phase"), { 0.0f, 1.0f }, 0.5f, phase, {});
offsetParam = addParam ("offset", TRANS("Offset"), { 0.0f, 1.0f }, 0.5f, offset, {});
changedTimer.setCallback ([this]
{
changedTimer.stopTimer();
changed();
});
state.addListener (this);
}
LFOModifier::~LFOModifier()
{
state.removeListener (this);
notifyListenersOfDeletion();
edit.removeModifierTimer (*modifierTimer);
for (auto p : getAutomatableParameters())
p->detachFromCurrentValue();
deleteAutomatableParameters();
}
void LFOModifier::initialise()
{
// Do this here in case the audio code starts using the parameters before the constructor has finished
modifierTimer = std::make_unique<LFOModifierTimer> (*this);
edit.addModifierTimer (*modifierTimer);
restoreChangedParametersFromState();
}
//==============================================================================
float LFOModifier::getCurrentValue()
{
if (gated_.load (std::memory_order_acquire))
return 0.0f;
return currentValue.load (std::memory_order_acquire);
}
float LFOModifier::getCurrentPhase() const { return currentPhase.load (std::memory_order_acquire); }
AutomatableParameter::ModifierAssignment* LFOModifier::createAssignment (const juce::ValueTree& v)
{
return new Assignment (v, *this);
}
void LFOModifier::applyToBuffer (const PluginRenderContext& prc)
{
if (prc.bufferForMidiMessages == nullptr)
return;
const bool skipResync = skipNativeResync_.load (std::memory_order_acquire);
const bool gateByMidi = gateOnTriggerSource_.load (std::memory_order_acquire)
&& juce::roundToInt (syncTypeParam->getCurrentValue()) == ModifierCommon::note;
if (skipResync && ! gateByMidi)
return;
for (auto& m : *prc.bufferForMidiMessages)
{
if (m.isNoteOn())
{
if (! skipResync)
modifierTimer->resync (prc.bufferNumSamples / getSampleRate());
if (gateByMidi)
{
++heldNotes_;
gated_.store (false, std::memory_order_release);
}
}
else if (gateByMidi && m.isNoteOff (true))
{
heldNotes_ = juce::jmax (0, heldNotes_ - 1);
if (heldNotes_ == 0)
gated_.store (true, std::memory_order_release);
}
else if (gateByMidi && (m.isAllNotesOff() || m.isAllSoundOff()))
{
heldNotes_ = 0;
gated_.store (true, std::memory_order_release);
}
}
}
void LFOModifier::triggerNoteOn (bool forceZeroValue)
{
// Zero-delay: reset phase and recompute value immediately
// instead of deferring to the next updateStreamTime() block.
// Safe because callers (SidechainMonitorPlugin) run on the audio
// thread before the instrument plugin reads the assignment value.
gated_.store (false, std::memory_order_release);
modifierTimer->resync (0.0);
if (forceZeroValue)
{
// Force value=0 on the noteOn block so the instrument sees a 0→value
// transient on the next block (matching playback where gating creates
// a zero gap before each noteOn). Only used for cross-track sidechain.
currentValue.store (0.0f, std::memory_order_release);
}
}
//==============================================================================
LFOModifier::Assignment::Assignment (const juce::ValueTree& v, const LFOModifier& lfo)
: AutomatableParameter::ModifierAssignment (lfo.edit, v),
lfoModifierID (lfo.itemID)
{
}
bool LFOModifier::Assignment::isForModifierSource (const ModifierSource& source) const
{
if (auto* mod = dynamic_cast<const LFOModifier*> (&source))
return mod->itemID == lfoModifierID;
return false;
}
LFOModifier::Ptr LFOModifier::Assignment::getLFOModifier() const
{
if (auto mod = findModifierTypeForID<LFOModifier> (edit, lfoModifierID))
return mod;
return {};
}
//==============================================================================
juce::StringArray LFOModifier::getWaveNames()
{
return
{
NEEDS_TRANS("Sine"),
NEEDS_TRANS("Triangle"),
NEEDS_TRANS("Saw Up"),
NEEDS_TRANS("Saw Down"),
NEEDS_TRANS("Square"),
NEEDS_TRANS("4 Steps Up"),
NEEDS_TRANS("4 Steps Down"),
NEEDS_TRANS("8 Steps Up"),
NEEDS_TRANS("8 Steps Down"),
NEEDS_TRANS("Random"),
NEEDS_TRANS("Noise"),
NEEDS_TRANS("Custom Callback")
};
}
//==============================================================================
void LFOModifier::valueTreeChanged()
{
if (! changedTimer.isTimerRunning())
changedTimer.startTimerHz (30);
}
}} // namespace tracktion { inline namespace engine