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

« « « Anklang Documentation
Loading...
Searching...
No Matches
tracktion_GrooveTemplate.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
14const char* GrooveTemplate::grooveXmlTag = "GROOVETEMPLATE";
15
16GrooveTemplate::GrooveTemplate()
17 : numNotes (16),
18 notesPerBeat (2),
19 name (TRANS("Unnamed")),
20 parameterized (false)
21{
22}
23
24GrooveTemplate::GrooveTemplate (const juce::XmlElement* node)
25 : GrooveTemplate()
26{
27 if (node != nullptr && node->hasTagName (grooveXmlTag))
28 {
29 numNotes = node->getIntAttribute ("numberOfNotes", 16);
30 notesPerBeat = node->getIntAttribute ("notesPerBeat", 2);
31 name = node->getStringAttribute ("name", TRANS("Unnamed"));
32 parameterized = node->getBoolAttribute ("parameterized", false);
33
34 for (auto n : node->getChildWithTagNameIterator ("SHIFT"))
35 latenesses.add ((float) n->getDoubleAttribute ("delta", 0.0));
36 }
37}
38
39GrooveTemplate::GrooveTemplate (const GrooveTemplate& other)
40{
41 latenesses = other.latenesses;
42 numNotes = other.numNotes;
43 notesPerBeat = other.notesPerBeat;
44 name = other.name;
45 parameterized = other.parameterized;
46}
47
48GrooveTemplate::~GrooveTemplate()
49{
50}
51
52bool GrooveTemplate::operator== (const GrooveTemplate& o) const
53{
54 return latenesses == o.latenesses
55 && numNotes == o.numNotes
56 && notesPerBeat == o.notesPerBeat
57 && parameterized == o.parameterized;
58}
59
60bool GrooveTemplate::isParameterized() const
61{
62 return parameterized;
63}
64
65const GrooveTemplate& GrooveTemplate::operator= (const GrooveTemplate& other)
66{
67 latenesses = other.latenesses;
68 numNotes = other.numNotes;
69 notesPerBeat = other.notesPerBeat;
70 name = other.name;
71 parameterized = other.parameterized;
72
73 return *this;
74}
75
76void GrooveTemplate::setName (const juce::String& n)
77{
78 name = n;
79}
80
81juce::XmlElement* GrooveTemplate::createXml() const
82{
83 auto node = new juce::XmlElement (grooveXmlTag);
84 node->setAttribute ("name", name);
85 node->setAttribute ("numberOfNotes", numNotes);
86 node->setAttribute ("notesPerBeat", notesPerBeat);
87 node->setAttribute ("parameterized", parameterized);
88
89 int lastNonZeroNote = numNotes;
90 while (--lastNonZeroNote >= 0)
91 if (latenesses[lastNonZeroNote] != 0.0f)
92 break;
93
94 for (int i = 0; i <= lastNonZeroNote; ++i)
95 {
96 auto n = new juce::XmlElement ("SHIFT");
97 n->setAttribute ("delta", 0.001 * juce::roundToInt (1000.0 * latenesses[i]));
98 node->addChildElement (n);
99 }
100
101 return node;
102}
103
104void GrooveTemplate::setNumberOfNotes (int notes)
105{
106 numNotes = juce::jlimit (2, 1024, notes);
107}
108
109void GrooveTemplate::setNotesPerBeat (int notes)
110{
111 notesPerBeat = juce::jlimit (1, 8, notes);
112}
113
114float GrooveTemplate::getLatenessProportion (int noteNumber, float strength) const
115{
116 if (parameterized)
117 return latenesses[noteNumber] * strength;
118
119 return latenesses[noteNumber];
120}
121
122void GrooveTemplate::setLatenessProportion (int noteNumber, float p, float strength)
123{
124 while (latenesses.size() < noteNumber)
125 latenesses.add (0.0f);
126
127 if (parameterized)
128 latenesses.set (noteNumber, juce::jlimit (-1.0f, 1.0f, p / strength));
129 else
130 latenesses.set (noteNumber, juce::jlimit (-1.0f, 1.0f, p));
131}
132
133void GrooveTemplate::clearLatenesses()
134{
135 latenesses.clearQuick();
136}
137
138BeatPosition GrooveTemplate::beatsTimeToGroovyTime (BeatPosition beatsTime, float strength) const
139{
140 auto activeStrength = parameterized ? strength : 1.0f;
141
142 const double beatNum = std::floor (beatsTime.inBeats() * notesPerBeat);
143 const double offset = notesPerBeat * (beatsTime.inBeats() - (beatNum / notesPerBeat));
144 const int latenessIndex = juce::roundToInt (beatNum) % numNotes;
145
146 const double lateness = latenesses[latenessIndex] * activeStrength;
147 const double t1 = (beatNum + 0.5f * lateness);
148 const double t2minust1 = 1.0 + 0.5f * ((latenesses[(latenessIndex + 1) % numNotes] * activeStrength) - lateness);
149
150 return BeatPosition::fromBeats ((t1 + offset * t2minust1) / notesPerBeat);
151}
152
153TimePosition GrooveTemplate::editTimeToGroovyTime (TimePosition editTime, float strength, Edit& edit) const
154{
155 auto beats = edit.tempoSequence.toBeats (editTime);
156 beats = beatsTimeToGroovyTime (beats, strength);
157 return edit.tempoSequence.toTime (beats);
158}
159
160bool GrooveTemplate::isEmpty() const
161{
162 for (int i = latenesses.size(); --i >= 0;)
163 if (latenesses.getUnchecked (i) != 0.0f)
164 return false;
165
166 return true;
167}
168
169//==============================================================================
170const char* basic8SwingXML = "<GROOVETEMPLATE name=\"Basic 8th Swing\" numberOfNotes=\"2\" notesPerBeat=\"2\" parameterized=\"1\"><SHIFT delta=\"0.0\"/><SHIFT delta=\"0.66\"/></GROOVETEMPLATE>";
171const char* basic16SwingXML = "<GROOVETEMPLATE name=\"Basic 16th Swing\" numberOfNotes=\"2\" notesPerBeat=\"4\" parameterized=\"1\"><SHIFT delta=\"0.0\"/><SHIFT delta=\"0.66\"/></GROOVETEMPLATE>";
172
173//==============================================================================
174GrooveTemplateManager::GrooveTemplateManager (Engine& e)
175 : engine (e)
176{
177 reload();
178
179 if (knownGrooves.isEmpty())
180 {
181 // load the defaults..
182 if (auto defSettings = juce::parseXML (TracktionBinaryData::groove_templates_xml))
183 if (defSettings->getTagName() == "GROOVETEMPLATES")
184 reload (defSettings.get());
185 }
186
187 bool anyParameterized = false;
188 for (auto g : knownGrooves)
189 if (g->isParameterized())
190 anyParameterized = true;
191
192 // load the new defaults..
193 if (! anyParameterized)
194 {
195 if (auto defSettings = juce::parseXML (TracktionBinaryData::groove_templates_2_xml))
196 if (defSettings->hasTagName ("GROOVETEMPLATES"))
197 for (auto n : defSettings->getChildWithTagNameIterator (GrooveTemplate::grooveXmlTag))
198 knownGrooves.add (new GrooveTemplate (n));
199 }
200
201 // Load even newer presets
202 auto hasGroove = [this] (const GrooveTemplate& gt)
203 {
204 for (auto g : knownGrooves)
205 if (g->getName() == gt.getName())
206 return true;
207
208 return false;
209 };
210
211 auto addGrooveIfNotExists = [this, &hasGroove] (juce::String xmlString)
212 {
213 auto xml = juce::parseXML (xmlString);
214 auto gt = std::make_unique<GrooveTemplate> (xml.get());
215
216 if (! hasGroove (*gt))
217 knownGrooves.add (gt.release());
218 };
219
220 addGrooveIfNotExists (basic8SwingXML);
221 addGrooveIfNotExists (basic16SwingXML);
222}
223
224//==============================================================================
225void GrooveTemplateManager::useParameterizedGrooves (bool use)
226{
227 activeGrooves.clear();
228
229 useParameterized = use;
230 if (useParameterized)
231 {
232 for (auto g : knownGrooves)
233 activeGrooves.add (g);
234 }
235 else
236 {
237 for (auto g : knownGrooves)
238 if (! g->isParameterized())
239 activeGrooves.add (g);
240 }
241}
242
244{
245 if (auto xml = engine.getPropertyStorage().getXmlProperty (SettingID::grooveTemplates))
246 reload (xml.get());
247}
248
250{
251 knownGrooves.clearQuick (true);
252
253 if (grooves != nullptr && grooves->hasTagName ("GROOVETEMPLATES"))
254 for (auto n : grooves->getChildWithTagNameIterator (GrooveTemplate::grooveXmlTag))
255 knownGrooves.add (new GrooveTemplate (n));
256
257 useParameterizedGrooves (useParameterized);
258}
259
260void GrooveTemplateManager::save()
261{
262 juce::XmlElement n ("GROOVETEMPLATES");
263
264 for (auto gt : knownGrooves)
265 if (gt != nullptr)
266 n.addChildElement (gt->createXml());
267
268 engine.getPropertyStorage().setXmlProperty (SettingID::grooveTemplates, n);
269}
270
271//==============================================================================
272int GrooveTemplateManager::getNumTemplates() const
273{
274 return activeGrooves.size();
275}
276
277juce::String GrooveTemplateManager::getTemplateName (int index) const
278{
279 if (auto gt = activeGrooves[index])
280 return gt->getName();
281
282 return {};
283}
284
285const GrooveTemplate* GrooveTemplateManager::getTemplate (int index)
286{
287 return activeGrooves[index];
288}
289
290const GrooveTemplate* GrooveTemplateManager::getTemplateByName (const juce::String& name)
291{
292 for (auto gt : activeGrooves)
293 if (gt->getName() == name)
294 return gt;
295
296 return {};
297}
298
299juce::StringArray GrooveTemplateManager::getTemplateNames() const
300{
301 juce::StringArray names;
302
303 for (auto gt : activeGrooves)
304 names.add (gt->getName());
305
306 return names;
307}
308
309void GrooveTemplateManager::updateTemplate (int index, const GrooveTemplate& gt)
310{
311 index = knownGrooves.indexOf (activeGrooves[index]);
312
313 // check the name doesn't clash..
314 auto n = gt.getName().trim();
315
316 if (n.isEmpty())
317 n = TRANS("Unnamed");
318 else
319 n = n.substring (0, 32);
320
321 auto originalName = n;
322
323 if (originalName.trim().endsWithChar (')'))
324 {
325 const int openBracks = originalName.lastIndexOfChar ('(');
326 const int closeBracks = originalName.lastIndexOfChar (')');
327
328 if (openBracks > 0 && closeBracks > openBracks
329 && originalName.substring (openBracks + 1, closeBracks).containsOnly ("0123456789"))
330 {
331 originalName = originalName.substring (0, openBracks).trim();
332 }
333 }
334
335 if (knownGrooves[index] != nullptr)
336 knownGrooves.remove (index);
337
338 useParameterizedGrooves (useParameterized);
339
340 int suff = 2;
341 while (getTemplateByName (n) != nullptr)
342 n = originalName + " (" + juce::String (suff++) + ")";
343
344 auto newGroove = knownGrooves.insert (index, new GrooveTemplate (gt));
345 newGroove->setName (n);
346 newGroove->setParameterized (useParameterized);
347
348 save();
349 useParameterizedGrooves (useParameterized);
351}
352
353void GrooveTemplateManager::deleteTemplate (int index)
354{
355 knownGrooves.removeObject (activeGrooves[index]);
356
357 useParameterizedGrooves (useParameterized);
358
359 save();
361}
362
363}} // namespace tracktion { inline namespace engine
void addChildElement(XmlElement *newChildElement) noexcept
bool getBoolAttribute(StringRef attributeName, bool defaultReturnValue=false) const
bool hasTagName(StringRef possibleTagName) const noexcept
int getIntAttribute(StringRef attributeName, int defaultReturnValue=0) const
const String & getStringAttribute(StringRef attributeName) const noexcept
void setAttribute(const Identifier &attributeName, const String &newValue)
The Tracktion Edit class!
TempoSequence tempoSequence
The global TempoSequence of this Edit.
The Engine is the central class for all tracktion sessions.
PropertyStorage & getPropertyStorage() const
Returns the PropertyStorage user settings customisable XML file.
void reload()
called when usersettings change, because that's where the grooves are kept.
TimePosition toTime(BeatPosition) const
Converts a number of beats a time.
BeatPosition toBeats(TimePosition) const
Converts a time to a number of beats.
static std::vector< std::unique_ptr< ScopedContextAllocator > > restartAllTransports(Engine &, bool clearDevices)
Restarts all TransportControl[s] in the Edit.
T floor(T... args)
T is_pointer_v
#define TRANS(stringLiteral)
typedef float
Type jlimit(Type lowerLimit, Type upperLimit, Type valueToConstrain) noexcept
std::unique_ptr< XmlElement > parseXML(const String &textToParse)
int roundToInt(const FloatType value) noexcept
juce::String getName(LaunchQType t)
Retuns the name of a LaunchQType for display purposes.
Represents a position in beats.
constexpr double inBeats() const
Returns the position as a number of beats.
Represents a position in real-life time.