tracktion-engine 3.0-10-g034fdde4aa5
Tracktion Engine — High level data model for audio applications

« « « Anklang Documentation
Loading...
Searching...
No Matches
tracktion_LFOModifier.cpp
Go to the documentation of this file.
1 /*
2 ,--. ,--. ,--. ,--.
3 ,-' '-.,--.--.,--,--.,---.| |,-.,-' '-.`--' ,---. ,--,--, Copyright 2024
4 '-. .-'| .--' ,-. | .--'| /'-. .-',--.| .-. || \ Tracktion Software
5 | | | | \ '-' \ `--.| \ \ | | | |' '-' '| || | Corporation
6 `---' `--' `--`--'`---'`--'`--' `---' `--' `---' `--''--' www.tracktion.com
7
8 Tracktion Engine uses a GPL/commercial licence - see LICENCE.md for details.
9*/
10
11namespace tracktion { inline namespace engine
12{
13
15{
17 : modifier (lfo)
18 {
19 }
20
21 void updateStreamTime (TimePosition editTime, int numSamples) override
22 {
23 const double blockLength = numSamples / modifier.getSampleRate();
24 modifier.setEditTime (editTime);
25 modifier.updateParameterStreams (editTime);
26
27 const auto syncTypeThisBlock = juce::roundToInt (modifier.syncTypeParam->getCurrentValue());
28 const auto rateTypeThisBlock = getTypedParamValue<ModifierCommon::RateType> (*modifier.rateTypeParam);
29
30 const float rateThisBlock = modifier.rateParam->getCurrentValue();
31
32 if (rateTypeThisBlock == ModifierCommon::hertz)
33 {
34 const float durationPerPattern = 1.0f / rateThisBlock;
35 ramp.setDuration (durationPerPattern);
36
37 if (syncTypeThisBlock == ModifierCommon::transport)
38 ramp.setPosition (std::fmod ((float) editTime.inSeconds(), durationPerPattern));
39
40 setPhase (ramp.getProportion());
41
42 // Move the ramp on for the next block
43 ramp.process ((float) blockLength);
44 }
45 else
46 {
47 tempoSequence.set (editTime);
48 const auto currentTempo = tempoSequence.getTempo();
49 const auto currentTimeSig = tempoSequence.getTimeSignature();
50 const auto proportionOfBar = ModifierCommon::getBarFraction (rateTypeThisBlock);
51
52 if (syncTypeThisBlock == ModifierCommon::transport)
53 {
54 const auto editTimeInBeats = tempoSequence.getBeats().inBeats();
55 const auto bars = (editTimeInBeats / currentTimeSig.numerator) * rateThisBlock;
56
57 if (rateTypeThisBlock >= ModifierCommon::fourBars && rateTypeThisBlock <= ModifierCommon::sixtyFourthD)
58 {
59 const double virtualBars = bars / proportionOfBar;
60 setPhase ((float) std::fmod (virtualBars, 1.0f));
61 }
62 }
63 else
64 {
65 const double bpm = (currentTempo * rateThisBlock) / proportionOfBar;
66 const double secondsPerBeat = 60.0 / bpm;
67 const float secondsPerStep = static_cast<float> (secondsPerBeat * currentTimeSig.numerator);
68 const float secondsPerPattern = secondsPerStep;
69 ramp.setDuration (secondsPerPattern);
70
71 setPhase (ramp.getProportion());
72
73 // Move the ramp on for the next block
74 ramp.process ((float) blockLength);
75 }
76 }
77 }
78
79 void setPhase (float newPhase)
80 {
81 newPhase += modifier.phaseParam->getCurrentValue();
82
83 while (newPhase >= 1.0f) newPhase -= 1.0f;
84 while (newPhase < 0.0f) newPhase += 1.0f;
85
86 if (newPhase < modifier.getCurrentPhase())
87 {
88 previousRandom = currentRandom;
89 currentRandom = rand.nextFloat();
90 randomDifference = currentRandom - previousRandom;
91 }
92
93 jassert (juce::isPositiveAndBelow (newPhase, 1.0f));
94 modifier.currentPhase.store (newPhase, std::memory_order_release);
95
96 auto getValue = [this, newPhase]
97 {
98 using namespace PredefinedWavetable;
99 switch (getTypedParamValue<LFOModifier::Wave> (*modifier.waveParam))
100 {
101 case waveSine: return getSinSample (newPhase);
102 case waveTriangle: return getTriangleSample (newPhase);
103 case waveSawUp: return getSawUpSample (newPhase);
104 case waveSawDown: return getSawDownSample (newPhase);
105 case waveSquare: return getSquareSample (newPhase);
106 case fourStepsUp: return getStepsUpSample (newPhase, 4);
107 case fourStepsDown: return getStepsDownSample (newPhase, 4);
108 case eightStepsUp: return getStepsUpSample (newPhase, 8);
109 case eightStepsDown: return getStepsDownSample (newPhase, 8);
110 case random: return currentRandom;
111 case noise: return (randomDifference * newPhase) + previousRandom;
112 }
113
114 return 0.0f;
115 };
116
117 float newValue = getValue();
118 newValue *= modifier.depthParam->getCurrentValue();
119 newValue += modifier.offsetParam->getCurrentValue();
120
121 if (getBoolParamValue (*modifier.bipolarParam))
122 newValue = (newValue * 2.0f) - 1.0f;
123
124 modifier.currentValue.store (newValue, std::memory_order_release);
125 }
126
127 void resync (double duration)
128 {
129 const auto type = juce::roundToInt (modifier.syncTypeParam->getCurrentValue());
130
131 if (type == ModifierCommon::note)
132 {
133 ramp.setPosition (0.0f);
134 setPhase (0.0f);
135
136 // Move the ramp on for the next block
137 ramp.process ((float) duration);
138 }
139 }
140
141 LFOModifier& modifier;
142 Ramp ramp;
143 tempo::Sequence::Position tempoSequence { createPosition (modifier.edit.tempoSequence) };
144
145 juce::Random rand;
146 float previousRandom = 0.0f, currentRandom = 0.0f, randomDifference = 0.0f;
147
149};
150
151//==============================================================================
152LFOModifier::LFOModifier (Edit& e, const juce::ValueTree& v)
153 : Modifier (e, v)
154{
155 auto um = &edit.getUndoManager();
156
157 wave.referTo (state, IDs::wave, um, waveSine);
158 syncType.referTo (state, IDs::syncType, um, float (ModifierCommon::free));
159 rate.referTo (state, IDs::rate, um, 1.0f);
160 rateType.referTo (state, IDs::rateType, um, float (ModifierCommon::bar));
161 depth.referTo (state, IDs::depth, um, 1.0f);
162 bipolar.referTo (state, IDs::bipolar, um);
163 phase.referTo (state, IDs::phase, um);
164 offset.referTo (state, IDs::offset, um);
165
166 auto addDiscreteParam = [this] (const juce::String& paramID, const juce::String& name,
168 const juce::StringArray& labels) -> AutomatableParameter*
169 {
170 auto* p = new DiscreteLabelledParameter (paramID, name, *this, valueRange, labels.size(), labels);
171 addAutomatableParameter (p);
172 p->attachToCurrentValue (val);
173
174 return p;
175 };
176
177 auto addParam = [this] (const juce::String& paramID,
178 const juce::String& name,
180 float centreVal, juce::CachedValue<float>& val,
181 const juce::String& suffix) -> AutomatableParameter*
182 {
183 valueRange.setSkewForCentre (centreVal);
184 auto* p = new SuffixedParameter (paramID, name, *this, valueRange, suffix);
185 addAutomatableParameter (p);
186 p->attachToCurrentValue (val);
187
188 return p;
189 };
190
191 using namespace ModifierCommon;
192 waveParam = addDiscreteParam ("wave", TRANS("Wave"), { 0.0f, (float) getWaveNames().size() - 1 }, wave, getWaveNames());
193 syncTypeParam = addDiscreteParam ("syncType", TRANS("Sync type"), { 0.0f, (float) getSyncTypeChoices().size() - 1 }, syncType, getSyncTypeChoices());
194 rateParam = addParam ("rate", TRANS("Rate"), { 0.01f, 50.0f }, 1.0f, rate, {});
195 rateTypeParam = addDiscreteParam ("rateType", TRANS("Rate Type"), { 0.0f, (float) getRateTypeChoices().size() - 1 }, rateType, getRateTypeChoices());
196 depthParam = addParam ("depth", TRANS("Depth"), { 0.0f, 1.0f }, 0.5f, depth, {});
197 bipolarParam = addDiscreteParam ("biopolar", TRANS("Bipoloar"), { 0.0f, 1.0f }, bipolar, { NEEDS_TRANS("Uni-polar"), NEEDS_TRANS("Bi-polar") });
198 phaseParam = addParam ("phase", TRANS("Phase"), { 0.0f, 1.0f }, 0.5f, phase, {});
199 offsetParam = addParam ("offset", TRANS("Offset"), { 0.0f, 1.0f }, 0.5f, offset, {});
200
201 changedTimer.setCallback ([this]
202 {
203 changedTimer.stopTimer();
204 changed();
205 });
206
207 state.addListener (this);
208}
209
210LFOModifier::~LFOModifier()
211{
212 state.removeListener (this);
213 notifyListenersOfDeletion();
214
215 edit.removeModifierTimer (*modifierTimer);
216
217 for (auto p : getAutomatableParameters())
218 p->detachFromCurrentValue();
219
220 deleteAutomatableParameters();
221}
222
224{
225 // Do this here in case the audio code starts using the parameters before the constructor has finished
226 modifierTimer = std::make_unique<LFOModifierTimer> (*this);
227 edit.addModifierTimer (*modifierTimer);
228
230}
231
232//==============================================================================
234float LFOModifier::getCurrentPhase() const { return currentPhase.load (std::memory_order_acquire); }
235
240
242{
243 if (prc.bufferForMidiMessages == nullptr)
244 return;
245
246 for (auto& m : *prc.bufferForMidiMessages)
247 if (m.isNoteOn())
248 modifierTimer->resync (prc.bufferNumSamples / getSampleRate());
249}
250
251//==============================================================================
252LFOModifier::Assignment::Assignment (const juce::ValueTree& v, const LFOModifier& lfo)
253 : AutomatableParameter::ModifierAssignment (lfo.edit, v),
254 lfoModifierID (lfo.itemID)
255{
256}
257
258bool LFOModifier::Assignment::isForModifierSource (const ModifierSource& source) const
259{
260 if (auto* mod = dynamic_cast<const LFOModifier*> (&source))
261 return mod->itemID == lfoModifierID;
262
263 return false;
264}
265
266LFOModifier::Ptr LFOModifier::Assignment::getLFOModifier() const
267{
268 if (auto mod = findModifierTypeForID<LFOModifier> (edit, lfoModifierID))
269 return mod;
270
271 return {};
272}
273
274//==============================================================================
275juce::StringArray LFOModifier::getWaveNames()
276{
277 return
278 {
279 NEEDS_TRANS("Sine"),
280 NEEDS_TRANS("Triangle"),
281 NEEDS_TRANS("Saw Up"),
282 NEEDS_TRANS("Saw Down"),
283 NEEDS_TRANS("Square"),
284 NEEDS_TRANS("4 Steps Up"),
285 NEEDS_TRANS("4 Steps Down"),
286 NEEDS_TRANS("8 Steps Up"),
287 NEEDS_TRANS("8 Steps Down"),
288 NEEDS_TRANS("Random"),
289 NEEDS_TRANS("Noise")
290 };
291}
292
293//==============================================================================
294void LFOModifier::valueTreeChanged()
295{
296 if (! changedTimer.isTimerRunning())
297 changedTimer.startTimerHz (30);
298}
299
300}} // namespace tracktion { inline namespace engine
float nextFloat() noexcept
int size() const noexcept
void startTimerHz(int timerFrequencyHz) noexcept
bool isTimerRunning() const noexcept
void addListener(Listener *listener)
void removeListener(Listener *listener)
void restoreChangedParametersFromState()
Restores the value of any explicitly set parameters.
void updateParameterStreams(TimePosition)
Updates all the parameter streams to their positions at this time.
const EditItemID itemID
Every EditItem has an ID which is unique within the edit.
The Tracktion Edit class!
void removeModifierTimer(ModifierTimer &)
Removes a ModifierTimer previously added.
TempoSequence tempoSequence
The global TempoSequence of this Edit.
void addModifierTimer(ModifierTimer &)
Adds a ModifierTimer to be updated each block.
float getCurrentValue() override
Returns the current value of the LFO.
void applyToBuffer(const PluginRenderContext &) override
Sub classes should implement this to process the Modifier.
float getCurrentPhase() const
Returns the current phase between 0 & 1.
void initialise() override
Call this once after construction to connect it to the audio graph.
AutomatableParameter::ModifierAssignment * createAssignment(const juce::ValueTree &) override
Must return a new ModifierAssignment for a given state.
virtual void changed()
This should be called to send a change notification to any SelectableListeners that are registered wi...
T fmod(T... args)
random
T is_pointer_v
#define TRANS(stringLiteral)
#define NEEDS_TRANS(stringLiteral)
#define jassert(expression)
#define JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(className)
T load(T... args)
typedef float
bool isPositiveAndBelow(Type1 valueToTest, Type2 upperLimit) noexcept
int roundToInt(const FloatType value) noexcept
tempo::Sequence::Position createPosition(const TempoSequence &s)
Creates a Position to iterate over the given TempoSequence.
bool getBoolParamValue(const AutomatableParameter &ap)
Returns a bool version of an AutomatableParameter.
T store(T... args)
constexpr double inBeats() const
Returns the position as a number of beats.
Represents a position in real-life time.
constexpr double inSeconds() const
Returns the TimePosition as a number of seconds.
A Sequence::Position is an iterator through a Sequence.
TimeSignature getTimeSignature() const
Returns the current TimeSignature of the Position.
BeatPosition getBeats() const
Returns the current beats of the Position.
void set(TimePosition)
Sets the Position to a new time.
double getTempo() const
Returns the current tempo of the Position.
Connects a modifier source to an AutomatableParameter.
Base class for objects which need to know about the global Edit time every block.
Bass class for parameter Modifiers.
juce::ValueTree state
Modifier internal state.
void setEditTime(TimePosition newEditTime)
Subclasses can call this to update the edit time of the current value.
double getSampleRate() const
Returns the sample rate the Modifier has been initialised with.
The context passed to plugin render methods to provide it with buffers to fill.
int bufferNumSamples
The number of samples to write into the audio buffer.
MidiMessageArray * bufferForMidiMessages
A buffer of MIDI events to process.
A ramp which goes between 0 and 1 over a set duration.