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

« « « Anklang Documentation
Loading...
Searching...
No Matches
tracktion_MIDITrackerModifier.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
14//==============================================================================
15MIDITrackerModifier::MIDITrackerModifier (Edit& e, const juce::ValueTree& v)
16 : Modifier (e, v)
17{
18 auto um = &edit.getUndoManager();
19
20 nodeState = state.getOrCreateChildWithName (IDs::NODES, um);
21
22 type.referTo (state, IDs::wave, um, pitch);
23 mode.referTo (state, IDs::mode, um, absolute);
24 relativeRoot.referTo (state, IDs::root, um, 60);
25 relativeSpread.referTo (state, IDs::spread, um, 12);
26
27 auto addDiscreteParam = [this] (const juce::String& paramID, const juce::String& name,
29 const juce::StringArray& labels) -> AutomatableParameter*
30 {
31 auto* p = new DiscreteLabelledParameter (paramID, name, *this, valueRange, labels.size(), labels);
32 addAutomatableParameter (p);
33 p->attachToCurrentValue (val);
34
35 return p;
36 };
37
38 auto addParam = [this] (const juce::String& paramID, const juce::String& name,
39 juce::NormalisableRange<float> valueRange, float centreVal,
40 juce::CachedValue<float>& val, const juce::String& suffix) -> AutomatableParameter*
41 {
42 valueRange.setSkewForCentre (centreVal);
43 auto* p = new SuffixedParameter (paramID, name, *this, valueRange, suffix);
44 addAutomatableParameter (p);
45 p->attachToCurrentValue (val);
46
47 return p;
48 };
49
50 typeParam = addDiscreteParam ("type", TRANS("MIDI Type"), { 0.0f, (float) getTypeNames().size() - 1 }, type, getTypeNames());
51 modeParam = addDiscreteParam ("mode", TRANS("Mode"), { 0.0f, (float) getModeNames().size() - 1 }, mode, getModeNames());
52 relativeRootParam = addParam ("relativeRoot", TRANS("Root"), { 1.0f, 127.0f, 1.0f }, 63.5f, relativeRoot, {});
53 relativeSpreadParam = addParam ("relativeSpread", TRANS("Spread"), { 1.0f, 64.0f, 1.0f }, 32.0f, relativeSpread, {});
54
55 updateMapFromTree();
56
57 changedTimer.setCallback ([this]
58 {
59 changedTimer.stopTimer();
60 changed();
61 });
62
63 state.addListener (this);
64}
65
66MIDITrackerModifier::~MIDITrackerModifier()
67{
68 state.removeListener (this);
69 notifyListenersOfDeletion();
70
71 for (auto p : getAutomatableParameters())
72 p->detachFromCurrentValue();
73
74 deleteAutomatableParameters();
75}
76
77void MIDITrackerModifier::initialise()
78{
79 restoreChangedParametersFromState();
80}
81
82//==============================================================================
83float MIDITrackerModifier::getCurrentValue()
84{
85 return currentModifierValue.load (std::memory_order_acquire);
86}
87
88int MIDITrackerModifier::getCurrentMIDIValue() const noexcept
89{
90 return currentMIDIValue.load (std::memory_order_acquire);
91}
92
93AutomatableParameter::ModifierAssignment* MIDITrackerModifier::createAssignment (const juce::ValueTree& v)
94{
95 return new Assignment (v, *this);
96}
97
98juce::StringArray MIDITrackerModifier::getMidiInputNames()
99{
100 return { TRANS("MIDI input") };
101}
102
103void MIDITrackerModifier::applyToBuffer (const PluginRenderContext& pc)
104{
105 setEditTime (pc.editTime.getStart());
106
107 if (pc.bufferForMidiMessages != nullptr)
108 for (auto& m : *pc.bufferForMidiMessages)
109 if (m.isNoteOn())
110 midiEvent (m);
111}
112
113//==============================================================================
114MIDITrackerModifier::Assignment::Assignment (const juce::ValueTree& v, const MIDITrackerModifier& mtm)
115 : AutomatableParameter::ModifierAssignment (mtm.edit, v),
116 midiTrackerModifierID (mtm.itemID)
117{
118}
119
120bool MIDITrackerModifier::Assignment::isForModifierSource (const ModifierSource& source) const
121{
122 if (auto* mod = dynamic_cast<const MIDITrackerModifier*> (&source))
123 return mod->itemID == midiTrackerModifierID;
124
125 return false;
126}
127
128MIDITrackerModifier::Ptr MIDITrackerModifier::Assignment::getMIDITrackerModifier() const
129{
130 if (auto mod = findModifierTypeForID<MIDITrackerModifier> (edit, midiTrackerModifierID))
131 return mod;
132
133 return {};
134}
135
136//==============================================================================
137juce::StringArray MIDITrackerModifier::getTypeNames()
138{
139 return { NEEDS_TRANS("Pitch"),
140 NEEDS_TRANS("Velocity") };
141}
142
143juce::StringArray MIDITrackerModifier::getModeNames()
144{
145 return { NEEDS_TRANS("Absolute"),
146 NEEDS_TRANS("Relative") };
147}
148
149//==============================================================================
150void MIDITrackerModifier::refreshCurrentValue()
151{
152 // CAS to ensure the modifier value is up to date with the new map and current note
153 for (;;)
154 {
155 auto midiVal = getCurrentMIDIValue();
156 updateValueFromMap (midiVal);
157
158 if (midiVal == getCurrentMIDIValue())
159 break;
160 }
161}
162
163void MIDITrackerModifier::updateMapFromTree()
164{
165 if (! edit.isLoading())
166 TRACKTION_ASSERT_MESSAGE_THREAD
167
168 Map newMap;
169
170 for (int i = 0; i < 5; ++i)
171 {
172 auto v = nodeState.getChild (i);
173
174 if (! v.isValid())
175 {
176 v = juce::ValueTree (IDs::NODE);
177 v.setProperty (IDs::midi, juce::jmap (i, 0, 4, 0, 127), nullptr);
178 v.setProperty (IDs::value, juce::jmap ((float) i, 0.0f, 4.0f, -1.0f, 1.0f), nullptr);
179 nodeState.addChild (v, -1, nullptr);
180 }
181
182 newMap.node[i] = std::pair<int, float> ( v[IDs::midi], v[IDs::value]);
183 }
184
185 {
186 const juce::SpinLock::ScopedLockType sl (mapLock);
187 currentMap = newMap;
188 }
189
190 refreshCurrentValue();
191}
192
193void MIDITrackerModifier::updateValueFromMap (int midiVal)
194{
195 jassert (juce::isPositiveAndBelow (midiVal, 128));
196 auto linearInterpolate = [] (float x1, float y1, float x2, float y2, float x)
197 {
198 return (y1 * (x2 - x) + y2 * (x - x1)) / (x2 - x1);
199 };
200
201 if (getTypedParamValue<Mode> (*modeParam) == relative)
202 {
203 const float root = relativeRootParam->getCurrentValue();
204 const float spread = relativeSpreadParam->getCurrentValue();
205
206 currentMIDIValue.store (midiVal, std::memory_order_release);
207 auto val = linearInterpolate (root, 0.0f, root + spread, 1.0f, (float) midiVal);
208 currentModifierValue.store (val, std::memory_order_release);
209 return;
210 }
211 else
212 {
213 Map map;
214
215 {
216 const juce::SpinLock::ScopedLockType sl (mapLock);
217 map = currentMap;
218 }
219
220 auto lastNode = map.node[4];
221
222 for (int i = 4; --i >= 0;)
223 {
224 const auto node = map.node[i];
225 const int nodeMidiVal = std::get<int> (node);
226
227 if (nodeMidiVal <= midiVal)
228 {
229 currentMIDIValue.store (midiVal, std::memory_order_release);
230
231 auto val = linearInterpolate ((float) nodeMidiVal, std::get<float> (node),
232 (float) std::get<int> (lastNode), std::get<float> (lastNode),
233 (float) midiVal);
234 currentModifierValue.store (val, std::memory_order_release);
235 return;
236 }
237
238 lastNode = node;
239 }
240 }
241
243}
244
245void MIDITrackerModifier::midiEvent (const juce::MidiMessage& m)
246{
247 if (! m.isNoteOn())
248 return;
249
250 if (getTypedParamValue<Type> (*typeParam) == pitch)
251 updateValueFromMap (m.getNoteNumber());
252 else
253 updateValueFromMap (m.getVelocity());
254}
255
256void MIDITrackerModifier::valueTreeChanged()
257{
258 if (! changedTimer.isTimerRunning())
259 changedTimer.startTimerHz (30);
260}
261
262void MIDITrackerModifier::valueTreePropertyChanged (juce::ValueTree& v, const juce::Identifier& i)
263{
264 if (v == state)
265 {
266 if (i == IDs::mode)
267 {
268 updateMapFromTree();
269 propertiesChanged();
270 }
271 else if (i == IDs::root || i == IDs::spread)
272 {
273 refreshCurrentValue();
274 }
275 }
276 else if (v.hasType (IDs::NODE))
277 {
278 if (i == IDs::midi || i == IDs::value)
279 updateMapFromTree();
280 }
281
282 ValueTreeAllEventListener::valueTreePropertyChanged (v, i);
283}
284
285}} // namespace tracktion { inline namespace engine
bool isValid() const noexcept
void startTimerHz(int timerFrequencyHz) noexcept
bool isTimerRunning() const noexcept
ValueTree getChild(int index) const
void addChild(const ValueTree &child, int index, UndoManager *undoManager)
bool isLoading() const
Returns true if the Edit's not yet fully loaded.
int getCurrentMIDIValue() const noexcept
Returns the MIDI value, either a pitch or velocity.
T is_pointer_v
#define TRANS(stringLiteral)
#define NEEDS_TRANS(stringLiteral)
#define jassert(expression)
#define jassertfalse
typedef float
constexpr Type jmap(Type value0To1, Type targetRangeMin, Type targetRangeMax)
bool isPositiveAndBelow(Type1 valueToTest, Type2 upperLimit) noexcept
T store(T... args)
Connects a modifier source to an AutomatableParameter.
juce::ValueTree state
Modifier internal state.
The context passed to plugin render methods to provide it with buffers to fill.
MidiMessageArray * bufferForMidiMessages
A buffer of MIDI events to process.
TimeRange editTime
The edit time range this context represents.
y1