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

« « « Anklang Documentation
Loading...
Searching...
No Matches
tracktion_StepModifier.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 (sm)
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 numStepsThisBlock = modifier.numStepsParam->getCurrentValue();
31 const float rateThisBlock = modifier.rateParam->getCurrentValue();
32
33 if (rateTypeThisBlock == ModifierCommon::hertz)
34 {
35 const float durationPerPattern = numStepsThisBlock * (1.0f / rateThisBlock);
36 ramp.setDuration (durationPerPattern);
37
38 if (syncTypeThisBlock == ModifierCommon::transport)
39 ramp.setPosition (std::fmod ((float) editTime.inSeconds(), durationPerPattern));
40
41 const int step = static_cast<int> (std::floor (numStepsThisBlock * ramp.getProportion()));
42 modifier.currentStep.store (step, std::memory_order_release);
43
44 // Move the ramp on for the next block
45 ramp.process ((float) blockLength);
46 }
47 else
48 {
49 tempoSequence.set (editTime);
50 const auto currentTempo = tempoSequence.getTempo();
51 const auto currentTimeSig = tempoSequence.getTimeSignature();
52 const auto proportionOfBar = ModifierCommon::getBarFraction (rateTypeThisBlock);
53
54 if (syncTypeThisBlock == ModifierCommon::transport)
55 {
56 const auto editTimeInBeats = tempoSequence.getBeats().inBeats();
57 const auto bars = (editTimeInBeats / currentTimeSig.numerator) * rateThisBlock;
58
59 if (rateTypeThisBlock >= ModifierCommon::fourBars && rateTypeThisBlock <= ModifierCommon::sixtyFourthD)
60 {
61 const double virtualBars = bars / proportionOfBar;
62 const int step = static_cast<int> (std::fmod (virtualBars, numStepsThisBlock));
63 modifier.currentStep.store (step, std::memory_order_release);
64 }
65 }
66 else
67 {
68 const double bpm = (currentTempo * rateThisBlock) / proportionOfBar;
69 const double secondsPerBeat = 60.0 / bpm;
70 const float secondsPerStep = static_cast<float> (secondsPerBeat * currentTimeSig.numerator);
71 const float secondsPerPattern = (numStepsThisBlock * secondsPerStep);
72 ramp.setDuration (secondsPerPattern);
73
74 const int step = static_cast<int> (std::floor (numStepsThisBlock * ramp.getProportion()));
75 modifier.currentStep.store (step, std::memory_order_release);
76
77 // Move the ramp on for the next block
78 ramp.process ((float) blockLength);
79 }
80 }
81 }
82
83 void resync (double duration)
84 {
85 const auto type = juce::roundToInt (modifier.syncTypeParam->getCurrentValue());
86
87 if (type == ModifierCommon::note)
88 {
89 ramp.setPosition (0.0f);
90 modifier.currentStep.store (0, std::memory_order_release);
91
92 // Move the ramp on for the next block
93 ramp.process ((float) duration);
94 }
95 }
96
97 StepModifier& modifier;
98 Ramp ramp;
99 tempo::Sequence::Position tempoSequence { createPosition (modifier.edit.tempoSequence) };
100};
101
102//==============================================================================
103StepModifier::StepModifier (Edit& e, const juce::ValueTree& v)
104 : Modifier (e, v)
105{
106 auto um = &edit.getUndoManager();
107
108 restoreStepsFromProperty();
109
110 syncType.referTo (state, IDs::syncType, um, float (ModifierCommon::free));
111 numSteps.referTo (state, IDs::numSteps, um, 16.0f);
112 rate.referTo (state, IDs::rate, um, 1.0f);
113 rateType.referTo (state, IDs::rateType, um, float (ModifierCommon::bar));
114 depth.referTo (state, IDs::depth, um, 1.0f);
115
116 auto addDiscreteParam = [this] (const juce::String& paramID, const juce::String& name,
118 const juce::StringArray& labels) -> AutomatableParameter*
119 {
120 auto* p = new DiscreteLabelledParameter (paramID, name, *this, valueRange, labels.size(), labels);
121 addAutomatableParameter (p);
122 p->attachToCurrentValue (val);
123
124 return p;
125 };
126
127 auto addParam = [this] (const juce::String& paramID, const juce::String& name,
128 juce::NormalisableRange<float> valueRange, float centreVal,
130 {
131 valueRange.setSkewForCentre (centreVal);
132 auto* p = new SuffixedParameter (paramID, name, *this, valueRange, suffix);
133 addAutomatableParameter (p);
134 p->attachToCurrentValue (val);
135
136 return p;
137 };
138
139 using namespace ModifierCommon;
140 syncTypeParam = addDiscreteParam ("syncType", TRANS("Sync type"), { 0.0f, (float) note }, syncType, getSyncTypeChoices());
141 numStepsParam = addParam ("numSteps", TRANS("Steps"), { 2.0f, 64.0f, 1.0f }, 31.0f, numSteps, {});
142 rateParam = addParam ("rate", TRANS("Rate"), { 0.01f, 50.0f }, 1.0f, rate, {});
143 rateTypeParam = addDiscreteParam ("rateType", TRANS("Rate Type"), { 0.0f, (float) sixtyFourthD }, rateType, getRateTypeChoices());
144 depthParam = addParam ("depth", TRANS("Depth"), { -1.0f, 1.0f }, 0.0f, depth, {});
145
146 changedTimer.setCallback ([this]
147 {
148 changedTimer.stopTimer();
149 changed();
150 });
151
152 state.addListener (this);
153}
154
155StepModifier::~StepModifier()
156{
157 state.removeListener (this);
158 notifyListenersOfDeletion();
159
160 edit.removeModifierTimer (*stepModifierTimer);
161
162 for (auto p : getAutomatableParameters())
163 p->detachFromCurrentValue();
164
165 deleteAutomatableParameters();
166}
167
169{
170 // Do this here in case the audio code starts using the parameters before the constructor has finished
171 stepModifierTimer = std::make_unique<StepModifierTimer> (*this);
172 edit.addModifierTimer (*stepModifierTimer);
173
175}
176
178{
179 return getStep (getCurrentStep()) * depthParam->getCurrentValue();
180}
181
182//==============================================================================
183float StepModifier::getStep (int step) const
184{
185 jassert (juce::isPositiveAndBelow (step, maxNumSteps));
186 return juce::jlimit (-1.0f, 1.0f, steps[step]);
187}
188
189void StepModifier::setStep (int step, float value)
190{
191 if (! juce::isPositiveAndBelow (step, maxNumSteps) || steps[step] == value)
192 return;
193
194 steps[step] = juce::jlimit (-1.0f, 1.0f, value);
195 flushStepsToProperty();
196}
197
198int StepModifier::getCurrentStep() const noexcept
199{
200 return currentStep.load (std::memory_order_acquire);
201}
202
207
209{
210 if (prc.bufferForMidiMessages == nullptr)
211 return;
212
213 for (auto& m : *prc.bufferForMidiMessages)
214 if (m.isNoteOn())
215 stepModifierTimer->resync (prc.bufferNumSamples / getSampleRate());
216}
217
218//==============================================================================
219StepModifier::Assignment::Assignment (const juce::ValueTree& v, const StepModifier& sm)
220 : AutomatableParameter::ModifierAssignment (sm.edit, v),
221 stepModifierID (sm.itemID)
222{
223}
224
225bool StepModifier::Assignment::isForModifierSource (const ModifierSource& source) const
226{
227 if (auto* mod = dynamic_cast<const StepModifier*> (&source))
228 return mod->itemID == stepModifierID;
229
230 return false;
231}
232
233StepModifier::Ptr StepModifier::Assignment::getStepModifier() const
234{
235 if (auto mod = findModifierTypeForID<StepModifier> (edit, stepModifierID))
236 return mod;
237
238 return {};
239}
240
241//==============================================================================
243{
244 return TRANS("Step Modifier");
245}
246
247//==============================================================================
248void StepModifier::flushStepsToProperty()
249{
251
252 for (int i = 0; i < maxNumSteps; ++i)
253 stream.writeFloat (steps[i]);
254
255 stream.flush();
256 state.setProperty (IDs::stepData, stream.getMemoryBlock(), &edit.getUndoManager());
257}
258
259void StepModifier::restoreStepsFromProperty()
260{
261 std::fill_n (steps, juce::numElementsInArray (steps), 0.0f);
262
263 if (auto mb = state[IDs::stepData].getBinaryData())
264 {
265 juce::MemoryInputStream stream (*mb, false);
266 int index = 0;
267
268 while (! stream.isExhausted())
269 steps[index++] = stream.readFloat();
270 }
271}
272
273//==============================================================================
274void StepModifier::valueTreeChanged()
275{
276 if (! changedTimer.isTimerRunning())
277 changedTimer.startTimerHz (30);
278}
279
280void StepModifier::valueTreePropertyChanged (juce::ValueTree& v, const juce::Identifier& i)
281{
282 if (v == state && i == IDs::stepData)
283 restoreStepsFromProperty();
284
285 ValueTreeAllEventListener::valueTreePropertyChanged (v, i);
286}
287
288}} // namespace tracktion { inline namespace engine
void flush() override
MemoryBlock getMemoryBlock() const
virtual bool writeFloat(float value)
void startTimerHz(int timerFrequencyHz) noexcept
bool isTimerRunning() const noexcept
ValueTree & setProperty(const Identifier &name, const var &newValue, UndoManager *undoManager)
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.
juce::UndoManager & getUndoManager() noexcept
Returns the juce::UndoManager used for 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...
void initialise() override
Call this once after construction to connect it to the audio graph.
void applyToBuffer(const PluginRenderContext &) override
Sub classes should implement this to process the Modifier.
float getCurrentValue() override
Must return the current value of the modifier.
AutomatableParameter::ModifierAssignment * createAssignment(const juce::ValueTree &) override
Must return a new ModifierAssignment for a given state.
juce::String getSelectableDescription() override
Subclasses must return a description of what they are.
T fill_n(T... args)
T floor(T... args)
T fmod(T... args)
T is_pointer_v
#define TRANS(stringLiteral)
#define jassert(expression)
T load(T... args)
typedef float
Type jlimit(Type lowerLimit, Type upperLimit, Type valueToConstrain) noexcept
bool isPositiveAndBelow(Type1 valueToTest, Type2 upperLimit) noexcept
int roundToInt(const FloatType value) noexcept
constexpr int numElementsInArray(Type(&)[N]) noexcept
tempo::Sequence::Position createPosition(const TempoSequence &s)
Creates a Position to iterate over the given TempoSequence.
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.