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

« « « Anklang Documentation
Loading...
Searching...
No Matches
tracktion_BreakpointOscillatorModifier.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
14namespace BezierHelpers
15{
16 template<typename Type>
17 static juce::Point<Type> getQuadraticControlPoint (juce::Point<Type> start,
19 Type curve)
20 {
21 static_assert (std::is_floating_point<Type>::value, "");
22 jassert (curve >= -0.5 && curve <= 0.5);
23
24 const Type x1 = start.x;
25 const Type y1 = start.y;
26 const Type x2 = end.x;
27 const Type y2 = end.y;
28 const Type c = juce::jlimit (-1.0f, 1.0f, curve * 2.0f);
29
30 if (y2 > y1)
31 {
32 const Type run = x2 - x1;
33 const Type rise = y2 - y1;
34
35 const Type xc = x1 + run / 2;
36 const Type yc = y1 + rise / 2;
37
38 const Type x = xc - run / 2 * -c;
39 const Type y = yc + rise / 2 * -c;
40
41 return { x, y };
42 }
43
44 const Type run = x2 - x1;
45 const Type rise = y1 - y2;
46
47 const Type xc = x1 + run / 2;
48 const Type yc = y2 + rise / 2;
49
50 const Type x = xc - run / 2 * -c;
51 const Type y = yc - rise / 2 * -c;
52
53 return { x, y };
54 }
55
56 template<typename Type>
57 static Type getQuadraticYFromX (Type x, Type x1, Type y1, Type xb, Type yb, Type x2, Type y2)
58 {
59 // test for straight lines and bail out
60 if (x1 == x2 || y1 == y2)
61 return y1;
62
63 // ok, we have a bezier curve with one control point,
64 // we know x, we need to find y
65
66 // flip the bezier equation around so its an quadratic equation
67 const Type a = x1 - 2 * xb + x2;
68 const Type b = -2 * x1 + 2 * xb;
69 const Type c = x1 - x;
70
71 // solve for t, [0..1]
72 Type t = 0;
73
74 if (a == 0.0f)
75 {
76 t = -c / b;
77 }
78 else
79 {
80 t = (-b + std::sqrt (b * b - 4 * a * c)) / (2 * a);
81
82 if (t < 0.0f || t > 1.0f)
83 t = (-b - std::sqrt (b * b - 4 * a * c)) / (2 * a);
84 }
85
86 // find y using the t we just found
87 const Type y = (std::pow (1 - t, 2.0f) * y1) + 2 * t * (1 - t) * yb + std::pow (t, 2.0f) * y2;
88 return (Type) y;
89 }
90
91 template<typename Type>
92 static Type getQuadraticYFromX (Type x,
93 juce::Point<Type> startPoint,
94 juce::Point<Type> controlPoint,
95 juce::Point<Type> endPoint)
96 {
97 return getQuadraticYFromX (x, startPoint.x, startPoint.y, controlPoint.x, controlPoint.y, endPoint.x, endPoint.y);
98 }
99}
100
101//==============================================================================
103{
105 : modifier (bom)
106 {
107 }
108
109 void updateStreamTime (TimePosition editTime, int numSamples) override
110 {
111 using namespace ModifierCommon;
112 const double blockLength = numSamples / modifier.getSampleRate();
113 modifier.setEditTime (editTime);
114 modifier.updateParameterStreams (editTime);
115
116 const auto syncTypeThisBlock = getTypedParamValue<SyncType> (*modifier.syncTypeParam);
117 const auto rateTypeThisBlock = getTypedParamValue<RateType> (*modifier.rateTypeParam);
118 const auto rateThisBlock = modifier.rateParam->getCurrentValue();
119
120 if (rateTypeThisBlock == hertz)
121 {
122 auto durationPerPattern = 1.0f / rateThisBlock;
123 ramp.setDuration (durationPerPattern);
124
125 if (syncTypeThisBlock == transport)
126 ramp.setPosition (std::fmod ((float) editTime.inSeconds(), durationPerPattern));
127
128 modifier.setPhase (ramp.getProportion());
129
130 // Move the ramp on for the next block
131 ramp.process ((float) blockLength);
132 }
133 else
134 {
135 tempoSequence.set (editTime);
136 const auto currentTempo = tempoSequence.getTempo();
137 const auto currentTimeSig = tempoSequence.getTimeSignature();
138 const auto proportionOfBar = ModifierCommon::getBarFraction (rateTypeThisBlock);
139
140 if (syncTypeThisBlock == transport)
141 {
142 const auto editTimeInBeats = tempoSequence.getBeats().inBeats();
143 const auto bars = (editTimeInBeats / currentTimeSig.numerator) * rateThisBlock;
144
145 if (rateTypeThisBlock >= fourBars && rateTypeThisBlock <= sixtyFourthD)
146 {
147 auto virtualBars = std::max (0.0, bars / proportionOfBar);
148 modifier.setPhase ((float) std::fmod (virtualBars, 1.0f));
149 }
150 }
151 else
152 {
153 auto bpm = (currentTempo * rateThisBlock) / proportionOfBar;
154 auto secondsPerBeat = 60.0 / bpm;
155 auto secondsPerStep = static_cast<float> (secondsPerBeat * currentTimeSig.numerator);
156 auto secondsPerPattern = secondsPerStep;
157 ramp.setDuration (secondsPerPattern);
158
159 modifier.setPhase (ramp.getProportion());
160
161 // Move the ramp on for the next block
162 ramp.process ((float) blockLength);
163 }
164 }
165 }
166
167 void resync (double duration)
168 {
169 if (juce::roundToInt (modifier.syncTypeParam->getCurrentValue()) == ModifierCommon::note)
170 {
171 ramp.setPosition (0.0f);
172 modifier.setPhase (0.0f);
173
174 // Move the ramp on for the next block
175 ramp.process ((float) duration);
176 }
177 }
178
180 Ramp ramp;
181 tempo::Sequence::Position tempoSequence { createPosition (modifier.edit.tempoSequence) };
182
184};
185
186
187//==============================================================================
188BreakpointOscillatorModifier::BreakpointOscillatorModifier (Edit& e, const juce::ValueTree& v)
189 : Modifier (e, v)
190{
191 auto um = &edit.getUndoManager();
192
193 numActivePoints.referTo (state, IDs::numActivePoints, um, 4);
194 syncType.referTo (state, IDs::syncType, um, float (ModifierCommon::free));
195 rate.referTo (state, IDs::rate, um, 1.0f);
196 rateType.referTo (state, IDs::rateType, um, float (ModifierCommon::bar));
197 depth.referTo (state, IDs::depth, um, 1.0f);
198 bipolar.referTo (state, IDs::bipolar, um);
199 stageZeroValue.referTo (state, IDs::stageZeroValue, um, 0.0f);
200 stageOneValue.referTo (state, IDs::stageOneValue, um, 1.0f);
201 stageOneTime.referTo (state, IDs::stageOneTime, um, 0.25f);
202 stageOneCurve.referTo (state, IDs::stageOneCurve, um, 0.0f);
203 stageTwoValue.referTo (state, IDs::stageTwoValue, um, 0.5f);
204 stageTwoTime.referTo (state, IDs::stageTwoTime, um, 0.5f);
205 stageTwoCurve.referTo (state, IDs::stageTwoCurve, um, 0.0f);
206 stageThreeValue.referTo (state, IDs::stageThreeValue, um, 0.5f);
207 stageThreeTime.referTo (state, IDs::stageThreeTime, um, 0.75f);
208 stageThreeCurve.referTo (state, IDs::stageThreeCurve, um, 0.0f);
209 stageFourValue.referTo (state, IDs::stageFourValue, um, 0.0f);
210 stageFourTime.referTo (state, IDs::stageFourTime, um, 1.0f);
211 stageFourCurve.referTo (state, IDs::stageFourCurve, um, 0.0f);
212
213 auto addDiscreteParam = [this] (const juce::String& paramID, const juce::String& name,
215 const juce::StringArray& labels) -> AutomatableParameter*
216 {
217 auto* p = new DiscreteLabelledParameter (paramID, name, *this, valueRange, labels.size(), labels);
218 addAutomatableParameter (p);
219 p->attachToCurrentValue (val);
220
221 return p;
222 };
223
224 auto addParam = [this] (const juce::String& paramID, const juce::String& name,
225 juce::NormalisableRange<float> valueRange, float centreVal,
227 {
228 valueRange.setSkewForCentre (centreVal);
229 auto* p = new SuffixedParameter (paramID, name, *this, valueRange, suffix);
230 addAutomatableParameter (p);
231 p->attachToCurrentValue (val);
232
233 return p;
234 };
235
236 using namespace ModifierCommon;
237 numActivePointsParam = addParam ("numActivePoints", TRANS("Num points"),{ 1.0f, 4.0f, 1.0f }, 2.5f, numActivePoints, {});
238 syncTypeParam = addDiscreteParam ("syncType", TRANS("Sync type"), { 0.0f, (float) getSyncTypeChoices().size() - 1 }, syncType, getSyncTypeChoices());
239 rateParam = addParam ("rate", TRANS("Rate"), { 0.01f, 50.0f }, 1.0f, rate, {});
240 rateTypeParam = addDiscreteParam ("rateType", TRANS("Rate Type"), { 0.0f, (float) getRateTypeChoices().size() - 1 }, rateType, getRateTypeChoices());
241 depthParam = addParam ("depth", TRANS("Depth"), { 0.0f, 1.0f }, 0.5f, depth, {});
242 bipolarParam = addDiscreteParam ("biopolar", TRANS("Bipoloar"), { 0.0f, 1.0f }, bipolar, { NEEDS_TRANS("Uni-polar"), NEEDS_TRANS("Bi-polar") });
243
244 stageZeroValueParam = addParam ("stageZeroValue", TRANS("Stage zero value"), { 0.0f, 1.0f }, 0.5f, stageZeroValue, {});
245 stageOneValueParam = addParam ("stageOneValue", TRANS("Stage one value"), { 0.0f, 1.0f }, 0.5f, stageOneValue, {});
246 stageOneTimeParam = addParam ("stageOneTime", TRANS("Stage one time"), { 0.0f, 1.0f }, 0.5f, stageOneTime, {});
247 stageOneCurveParam = addParam ("stageOneCurve", TRANS("Stage one curve"), { -0.5f, 0.5f }, 0.0f, stageOneCurve, {});
248 stageTwoValueParam = addParam ("stageTwoValue", TRANS("Stage two value"), { 0.0f, 1.0f }, 0.5f, stageTwoValue, {});
249 stageTwoTimeParam = addParam ("stageTwoTime", TRANS("Stage two time"), { 0.0f, 1.0f }, 0.5f, stageTwoTime, {});
250 stageTwoCurveParam = addParam ("stageTwoCurve", TRANS("Stage two curve"), { -0.5f, 0.5f }, 0.0f, stageTwoCurve, {});
251 stageThreeValueParam = addParam ("stageThreeValue", TRANS("Stage three value"), { 0.0f, 1.0f }, 0.5f, stageThreeValue, {});
252 stageThreeTimeParam = addParam ("stageThreeTime", TRANS("Stage three time"), { 0.0f, 1.0f }, 0.5f, stageThreeTime, {});
253 stageThreeCurveParam = addParam ("stageThreeCurve", TRANS("Stage three curve"), { -0.5f, 0.5f }, 0.0f, stageThreeCurve, {});
254 stageFourValueParam = addParam ("stageFourValue", TRANS("Stage four value"), { 0.0f, 1.0f }, 0.5f, stageFourValue, {});
255 stageFourTimeParam = addParam ("stageFourTime", TRANS("Stage four time"), { 0.0f, 1.0f }, 0.5f, stageFourTime, {});
256 stageFourCurveParam = addParam ("stageFourCurve", TRANS("Stage four curve"), { -0.5f, 0.5f }, 0.0f, stageFourCurve, {});
257
258 changedTimer.setCallback ([this]
259 {
260 changedTimer.stopTimer();
261 changed();
262 });
263
264 state.addListener (this);
265}
266
267BreakpointOscillatorModifier::~BreakpointOscillatorModifier()
268{
269 state.removeListener (this);
270 notifyListenersOfDeletion();
271
272 edit.removeModifierTimer (*modifierTimer);
273
274 for (auto p : getAutomatableParameters())
275 p->detachFromCurrentValue();
276
277 deleteAutomatableParameters();
278}
279
281{
282 // Do this here in case the audio code starts using the parameters before the constructor has finished
284 edit.addModifierTimer (*modifierTimer);
285
287}
288
289//==============================================================================
294
296{
297 return currentEnvelopeValue.load (std::memory_order_acquire);
298}
299
301{
302 return currentPhase.load (std::memory_order_acquire);
303}
304
306{
307 return 1.0f;
308}
309
314
316{
317 if (prc.bufferForMidiMessages == nullptr)
318 return;
319
320 for (auto& m : *prc.bufferForMidiMessages)
321 if (m.isNoteOn())
322 modifierTimer->resync (prc.bufferNumSamples / getSampleRate());
323}
324
325//==============================================================================
326BreakpointOscillatorModifier::Assignment::Assignment (const juce::ValueTree& v, const BreakpointOscillatorModifier& bom)
327 : AutomatableParameter::ModifierAssignment (bom.edit, v),
328 breakpointOscillatorModifierID (bom.itemID)
329{
330}
331
332bool BreakpointOscillatorModifier::Assignment::isForModifierSource (const ModifierSource& source) const
333{
334 if (auto* mod = dynamic_cast<const BreakpointOscillatorModifier*> (&source))
335 return mod->itemID == breakpointOscillatorModifierID;
336
337 return false;
338}
339
340BreakpointOscillatorModifier::Ptr BreakpointOscillatorModifier::Assignment::getModifier() const
341{
342 if (auto mod = findModifierTypeForID<BreakpointOscillatorModifier> (edit, breakpointOscillatorModifierID))
343 return mod;
344
345 return {};
346}
347
348//==============================================================================
349BreakpointOscillatorModifier::Stage BreakpointOscillatorModifier::getStage (int index) const
350{
352 switch (index)
353 {
354 case 0: return { stageZeroValueParam.get() };
355 case 1: return { stageOneValueParam.get(), stageOneTimeParam.get(), stageOneCurveParam.get() };
356 case 2: return { stageTwoValueParam.get(), stageTwoTimeParam.get(), stageTwoCurveParam.get() };
357 case 3: return { stageThreeValueParam.get(), stageThreeTimeParam.get(), stageThreeCurveParam.get() };
358 case 4: return { stageFourValueParam.get(), stageFourTimeParam.get(), stageFourCurveParam.get() };
359 default: jassertfalse; return {};
360 };
361}
362
363std::array<BreakpointOscillatorModifier::Section, 5> BreakpointOscillatorModifier::getAllSections() const
364{
365 auto getVal = [] (auto& p) -> float { return p->getCurrentValue(); };
366
367 return {{
368 { getVal (stageZeroValueParam), 0.0f, 0.0f },
369 { getVal (stageOneValueParam), getVal (stageOneTimeParam), getVal (stageOneCurveParam) },
370 { getVal (stageTwoValueParam), getVal (stageTwoTimeParam), getVal (stageTwoCurveParam) },
371 { getVal (stageThreeValueParam), getVal (stageThreeTimeParam), getVal (stageThreeCurveParam) },
372 { getVal (stageFourValueParam), getVal (stageFourTimeParam), getVal (stageFourCurveParam) }
373 }};
374}
375
376std::pair<BreakpointOscillatorModifier::Section, BreakpointOscillatorModifier::Section> BreakpointOscillatorModifier::getSectionsSurrounding (float time) const
377{
378 const auto sections = getAllSections();
379 const size_t numPoints = (size_t) getIntParamValue (*numActivePointsParam);
380 jassert (numPoints < sections.size());
381
382 for (size_t i = 1; i <= numPoints; ++i)
383 if (sections[i].time > time)
384 return std::make_pair (sections[i - 1], sections[i]);
385
386 auto dummyEndSection = sections[numPoints];
387 dummyEndSection.time = 1.0f;
388
389 return std::make_pair (sections[numPoints], dummyEndSection);
390}
391
392namespace BreakpointInterpolation
393{
394 constexpr float linearInterpolate (float x1, float y1, float x2, float y2, float x)
395 {
396 return (y1 * (x2 - x) + y2 * (x - x1)) / (x2 - x1);
397 }
398
399 template<typename Type>
400 static Type quadraticInterpolate (Type x,
401 juce::Point<Type> startPoint,
402 juce::Point<Type> endPoint,
403 Type curve)
404 {
405 auto cp = BezierHelpers::getQuadraticControlPoint (startPoint, endPoint, curve);
406 return BezierHelpers::getQuadraticYFromX (x, startPoint, cp, endPoint);
407 }
408}
409
410void BreakpointOscillatorModifier::setPhase (float newPhase)
411{
412 // Calculate the total time
413 // Find the time for that phase
414 // Find the sections before and after that time
415 // Skew the time based on the curve
416 // Interpolate the value between those two
417 // Multiply by depth
418 // If bipolar, scale and shift
419
420 using namespace BreakpointInterpolation;
421 jassert (juce::isPositiveAndBelow (newPhase, 1.0f));
422 currentPhase.store (newPhase, std::memory_order_release);
423
424 const float totalTime = getTotalTime();
425 float timeForPhase = totalTime * newPhase;
426
427 auto surroundingSections = getSectionsSurrounding (timeForPhase);
428 auto& s1 = surroundingSections.first;
429 auto& s2 = surroundingSections.second;
430 const float curve = s2.curve;
431
432 float newValue = 0.0f;
433
434 if (curve == 0.0f)
435 newValue = linearInterpolate (s1.time, s1.value, s2.time, s2.value, timeForPhase);
436 else
437 newValue = quadraticInterpolate (timeForPhase, { s1.time, s1.value }, { s2.time, s2.value }, curve);
438
439 currentEnvelopeValue.store (newValue, std::memory_order_release);
440
441 newValue = newValue * depthParam->getCurrentValue();
442
443 if (getBoolParamValue (*bipolarParam))
444 newValue = (newValue * 2.0f) - 1.0f;
445
446 jassert (! std::isnan (newValue));
447 currentValue.store (newValue, std::memory_order_release);
448}
449
450void BreakpointOscillatorModifier::valueTreeChanged()
451{
452 if (! changedTimer.isTimerRunning())
453 changedTimer.startTimerHz (30);
454}
455
456}} // namespace tracktion { inline namespace engine
ValueType y
ValueType x
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.
AutomatableParameter::ModifierAssignment * createAssignment(const juce::ValueTree &) override
Must return a new ModifierAssignment for a given state.
void applyToBuffer(const PluginRenderContext &) override
Sub classes should implement this to process the Modifier.
float getTotalTime() const
Returns the total time for this envelope.
float getCurrentValue() override
Returns the current value of the modifier.
float getCurrentPhase() const noexcept
Returns the current phase between 0 & 1.
float getCurrentEnvelopeValue() const noexcept
Returns the envelope value before the bipolar and depth have been applied.
void initialise() override
Call this once after construction to connect it to the audio graph.
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.
virtual void changed()
This should be called to send a change notification to any SelectableListeners that are registered wi...
T end(T... args)
T fmod(T... args)
T is_pointer_v
T isnan(T... args)
#define TRANS(stringLiteral)
#define NEEDS_TRANS(stringLiteral)
#define jassert(expression)
#define JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(className)
#define jassertfalse
T load(T... args)
T make_pair(T... args)
typedef float
T max(T... args)
Type jlimit(Type lowerLimit, Type upperLimit, Type valueToConstrain) noexcept
bool isPositiveAndNotGreaterThan(Type1 valueToTest, Type2 upperLimit) noexcept
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.
int getIntParamValue(const AutomatableParameter &ap)
Returns an int version of an AutomatableParameter.
T pow(T... args)
T sqrt(T... args)
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.
typedef size_t
y1