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

« « « Anklang Documentation
Loading...
Searching...
No Matches
tracktion_StepClipPattern.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
14StepClip::Pattern::Pattern (StepClip& c, const juce::ValueTree& v) noexcept
15 : clip (c), state (v)
16{
17}
18
19StepClip::Pattern::Pattern (const Pattern& other) noexcept
20 : clip (other.clip), state (other.state)
21{
22}
23
24juce::String StepClip::Pattern::getName() const { return state[IDs::name]; }
25void StepClip::Pattern::setName (const juce::String& name) { state.setProperty (IDs::name, name, clip.getUndoManager()); }
26
27int StepClip::Pattern::getNumNotes() const { return state[IDs::numNotes]; }
28void StepClip::Pattern::setNumNotes (int n) { state.setProperty (IDs::numNotes, n, clip.getUndoManager()); }
29
30BeatDuration StepClip::Pattern::getNoteLength() const { return BeatDuration::fromBeats (static_cast<double> (state[IDs::noteLength])); }
31void StepClip::Pattern::setNoteLength (BeatDuration n) { state.setProperty (IDs::noteLength, n.inBeats(), clip.getUndoManager()); }
32
33juce::BigInteger StepClip::Pattern::getChannel (int channel) const
34{
36 b.parseString (state.getChild (channel)[IDs::pattern].toString(), 2);
37 return b;
38}
39
40void StepClip::Pattern::setChannel (int channel, const juce::BigInteger& c)
41{
42 while (channel >= state.getNumChildren())
43 {
44 if (c.isZero())
45 return;
46
47 insertChannel (-1);
48 }
49
50 state.getChild (channel).setProperty (IDs::pattern, c.toString (2), clip.getUndoManager());
51}
52
53juce::Array<int> StepClip::Pattern::getVelocities (int channel) const
54{
56 auto sa = juce::StringArray::fromTokens (state.getChild (channel)[IDs::velocities].toString(), false);
57 v.ensureStorageAllocated (sa.size());
58
59 for (auto& s : sa)
60 v.add (s.getIntValue());
61
62 return v;
63}
64
65void StepClip::Pattern::setVelocities (int channel, const juce::Array<int>& va)
66{
67 if (channel >= state.getNumChildren())
68 {
70 return;
71 }
72
75
76 for (int v : va)
77 sa.add (juce::String (v));
78
79 state.getChild (channel).setProperty (IDs::velocities, sa.joinIntoString (" "), clip.getUndoManager());
80}
81
82static double parseFraction (const juce::String& s)
83{
84 const int splitIndex = s.indexOfChar ('/');
85 const double num = s.substring (0, splitIndex).getDoubleValue();
86 const double denom = s.substring (splitIndex + 1, s.length()).getDoubleValue();
87
88 if (denom == 0.0)
89 return 0.0;
90
91 return num / denom;
92}
93
94juce::Array<double> StepClip::Pattern::getGates (int channel) const
95{
97 auto sa = juce::StringArray::fromTokens (state.getChild (channel)[IDs::gates].toString(), false);
98 v.ensureStorageAllocated (sa.size());
99
100 for (auto& s : sa)
101 {
102 if (s.containsChar ('/'))
103 v.add (parseFraction (s));
104 else
105 v.add (s.getDoubleValue());
106 }
107
108 return v;
109}
110
111void StepClip::Pattern::setGates (int channel, const juce::Array<double>& ga)
112{
113 if (channel >= state.getNumChildren())
114 {
116 return;
117 }
118
121
122 for (double g : ga)
123 sa.add (juce::String (g));
124
125 state.getChild (channel).setProperty (IDs::gates, sa.joinIntoString (" "), clip.getUndoManager());
126}
127
128juce::Array<float> StepClip::Pattern::getProbabilities (int channel) const
129{
131 auto pa = juce::StringArray::fromTokens (state.getChild (channel)[IDs::probabilities].toString(), false);
132 v.ensureStorageAllocated (pa.size());
133
134 for (auto& p : pa)
135 v.add (p.getFloatValue());
136
137 return v;
138}
139
140void StepClip::Pattern::setProbabilities (int channel, const juce::Array<float>& pa)
141{
142 if (channel >= state.getNumChildren())
143 {
145 return;
146 }
147
150
151 for (double p : pa)
152 sa.add (juce::String (p));
153
154 state.getChild (channel).setProperty (IDs::probabilities, sa.joinIntoString (" "), clip.getUndoManager());
155}
156
157bool StepClip::Pattern::getNote (int channel, int index) const noexcept
158{
159 return getChannel (channel) [index];
160}
161
162void StepClip::Pattern::setNote (int channel, int index, bool value)
163{
164 if (getNote (channel, index) != value
165 && juce::isPositiveAndBelow (index, getNumNotes())
166 && juce::isPositiveAndBelow (channel, (int) maxNumChannels))
167 {
168 juce::BigInteger b (getChannel (channel));
169 b.setBit (index, value);
170 setChannel (channel, b);
171
172 if (value)
173 {
174 if (getVelocity (channel, index) == 0)
175 setVelocity (channel, index, defaultNoteValue);
176
177 if (getGate (channel, index) == 0.0)
178 setGate (channel, index, 1.0);
179 }
180 }
181}
182
183int StepClip::Pattern::getVelocity (int channel, int index) const
184{
185 if (! getNote (channel, index))
186 return -1;
187
188 auto velocities = getVelocities (channel);
189
190 if (juce::isPositiveAndBelow (index, velocities.size()))
191 return velocities[index];
192
193 if (clip.getChannels()[channel] != nullptr)
194 return 127;
195
196 return -1;
197}
198
199void StepClip::Pattern::setVelocity (int channel, int index, int value)
200{
201 if (! juce::isPositiveAndBelow (channel, (int) maxNumChannels))
202 return;
203
204 setNote (channel, index, true);
205
206 auto velocities = getVelocities (channel);
207 const int size = velocities.size();
208
209 if (! juce::isPositiveAndNotGreaterThan (index, size))
210 velocities.insertMultiple (size, 127, index - size);
211
212 velocities.set (index, value);
213 setVelocities (channel, velocities);
214}
215
216double StepClip::Pattern::getGate (int channel, int index) const
217{
218 if (! getNote (channel, index))
219 return 0.0;
220
221 auto gates = getGates (channel);
222
223 if (juce::isPositiveAndBelow (index, gates.size()))
224 return gates[index];
225
226 return 1.0;
227}
228
229float StepClip::Pattern::getProbability (int channel, int index) const
230{
231 if (! getNote (channel, index))
232 return 1.0;
233
234 auto p = getProbabilities (channel);
235
236 if (juce::isPositiveAndBelow (index, p.size()))
237 return p[index];
238
239 return 1.0;
240}
241
242void StepClip::Pattern::setGate (int channel, int index, double value)
243{
244 if (! juce::isPositiveAndBelow (channel, (int) maxNumChannels))
245 return;
246
247 setNote (channel, index, value != 0.0);
248
249 auto gates = getGates (channel);
250 const int size = gates.size();
251
252 if (! juce::isPositiveAndNotGreaterThan (index, size))
253 gates.insertMultiple (size, 1.0, index - size);
254
255 gates.set (index, value);
256 setGates (channel, gates);
257}
258
259void StepClip::Pattern::setProbability (int channel, int index, float value)
260{
261 if (! juce::isPositiveAndBelow (channel, (int) maxNumChannels))
262 return;
263
264 setNote (channel, index, value != 0.0);
265
266 auto p = getProbabilities (channel);
267 const int size = p.size();
268
269 if (! juce::isPositiveAndNotGreaterThan (index, size))
270 p.insertMultiple (size, 1.0f, index - size);
271
272 p.set (index, value);
273 setProbabilities (channel, p);
274}
275
276void StepClip::Pattern::clear()
277{
278 state.removeAllChildren (clip.getUndoManager());
279}
280
281void StepClip::Pattern::clearChannel (int channel)
282{
283 setChannel (channel, juce::BigInteger());
284}
285
286void StepClip::Pattern::insertChannel (int channel)
287{
288 state.addChild (juce::ValueTree (IDs::CHANNEL), channel, clip.getUndoManager());
289}
290
291void StepClip::Pattern::removeChannel (int channel)
292{
293 state.removeChild (channel, clip.getUndoManager());
294}
295
296void StepClip::Pattern::randomiseChannel (int channel)
297{
298 clearChannel (channel);
299
300 juce::Random r;
301 for (int i = 0; i < getNumNotes(); ++i)
302 setNote (channel, i, r.nextBool());
303}
304
305void StepClip::Pattern::randomiseSteps()
306{
307 juce::Random r;
308 const int numChannels = clip.getChannels().size();
309 const int numSteps = getNumNotes();
310
312 chans.insertMultiple (0, juce::BigInteger(), numChannels);
313
314 for (int i = 0; i < numSteps; ++i)
315 chans.getReference (r.nextInt (numChannels)).setBit (i);
316
317 for (int i = 0; i < numChannels; ++i)
318 setChannel (i, chans.getReference (i));
319}
320
321void StepClip::Pattern::shiftChannel (int channel, bool toTheRight)
322{
323 juce::BigInteger c (getChannel (channel));
324
325 // NB: Notes are added in reverse order
326 if (toTheRight)
327 c <<= 1;
328 else
329 c >>= 1;
330
331 setChannel (channel, c);
332}
333
334void StepClip::Pattern::toggleAtInterval (int channel, int interval)
335{
336 juce::BigInteger c = getChannel (channel);
337
338 for (int i = 0; i < getNumNotes(); ++i)
339 c.setBit (i, (i % interval) == 0);
340
341 setChannel (channel, c);
342}
343
344StepClip::Pattern::CachedPattern::CachedPattern (const Pattern& p, int c)
345 : notes (p.getChannel (c)),
346 velocities (p.getVelocities (c)),
347 gates (p.getGates (c)),
348 probabilities (p.getProbabilities (c))
349{
350}
351
352bool StepClip::Pattern::CachedPattern::getNote (int index) const noexcept
353{
354 return notes[index];
355}
356
357int StepClip::Pattern::CachedPattern::getVelocity (int index) const noexcept
358{
359 if (! getNote (index))
360 return -1;
361
362 if (juce::isPositiveAndBelow (index, velocities.size()))
363 return velocities[index];
364
365 return 127;
366}
367
368double StepClip::Pattern::CachedPattern::getGate (int index) const noexcept
369{
370 if (! getNote (index))
371 return 0.0;
372
373 if (juce::isPositiveAndBelow (index, gates.size()))
374 return gates[index];
375
376 return 1.0;
377}
378
379float StepClip::Pattern::CachedPattern::getProbability (int index) const noexcept
380{
381 if (! getNote (index))
382 return 0.0;
383
384 if (juce::isPositiveAndBelow (index, probabilities.size()))
385 return probabilities[index];
386
387 return 1.0;
388}
389
390//==============================================================================
391StepClip::Pattern StepClip::PatternInstance::getPattern() const
392{
393 return clip.getPattern (patternIndex);
394}
395
396int StepClip::PatternInstance::getSequenceIndex() const
397{
398 return clip.getPatternSequence().indexOf (this);
399}
400
402{
403 return clip.getName() + " - "
404 + TRANS("Section 123").replace ("123", juce::String (getSequenceIndex() + 1)) + " ("
405 + TRANS("Variation 123").replace ("123", juce::String (patternIndex + 1)) + ")";
406}
407
408}} // namespace tracktion { inline namespace engine
int size() const noexcept
void insertMultiple(int indexToInsertAt, ParameterType newElement, int numberOfTimesToInsertIt)
ElementType & getReference(int index) noexcept
const String & toString() const noexcept
bool nextBool() noexcept
int nextInt() noexcept
String joinIntoString(StringRef separatorString, int startIndex=0, int numberOfElements=-1) const
static StringArray fromTokens(StringRef stringToTokenise, bool preserveQuotedStrings)
int size() const noexcept
void ensureStorageAllocated(int minNumElements)
int indexOfChar(juce_wchar characterToLookFor) const noexcept
int length() const noexcept
bool containsChar(juce_wchar character) const noexcept
double getDoubleValue() const noexcept
String substring(int startIndex, int endIndex) const
void removeChild(const ValueTree &child, UndoManager *undoManager)
ValueTree getChild(int index) const
int getNumChildren() const noexcept
ValueTree & setProperty(const Identifier &name, const var &newValue, UndoManager *undoManager)
void removeAllChildren(UndoManager *undoManager)
void addChild(const ValueTree &child, int index, UndoManager *undoManager)
juce::ValueTree state
The ValueTree of the Clip state.
#define TRANS(stringLiteral)
#define jassertfalse
bool isPositiveAndNotGreaterThan(Type1 valueToTest, Type2 upperLimit) noexcept
bool isPositiveAndBelow(Type1 valueToTest, Type2 upperLimit) noexcept
T size(T... args)
Represents a duration in beats.
constexpr double inBeats() const
Returns the position as a number of beats.
juce::String getSelectableDescription() override
Subclasses must return a description of what they are.
BeatDuration getNoteLength() const
Returns the length of one step as a fraction of a beat.
void setNoteLength(BeatDuration)
Sets the length of one step as a fraction of a beat.