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

« « « Anklang Documentation
Loading...
Searching...
No Matches
tracktion_Musicality.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
14static const juce::String flat = "b";
15static auto oslash = juce::String (juce::CharPointer_UTF8 ("\xc3\xb8"));
16
17static inline bool isRoman (const juce::String& str) noexcept
18{
19 auto firstChar = str[0];
20 return firstChar == 'i' || firstChar == 'I' || firstChar == 'v' || firstChar == 'V';
21}
22
23static bool chordNameHasSymbol (const juce::String& name, const juce::String& symbol)
24{
25 if (isRoman (name))
26 return false;
27
28 auto str = name;
29
30 if (str.substring (0, 1).containsAnyOf ("ABCDEFG"))
31 str = str.substring (1);
32
33 if (str.substring (0, 1) == "b" || str.substring (0, 1) == "#")
34 str = str.substring (1);
35
36 return str == symbol;
37}
38
39static juce::String chordNote (const juce::String& name)
40{
41 if (isRoman (name))
42 return name;
43
44 auto note = name.substring (0, 1);
45 auto sharpFlat = name.substring (1, 2);
46
47 if (sharpFlat == "b" || sharpFlat == "#")
48 return note + sharpFlat;
49 return note;
50}
51
52static juce::String chordSymbol (const juce::String& name)
53{
54 if (isRoman (name))
55 return name;
56
57 auto note = name.substring (0, 1);
58 auto sharpFlat = name.substring (1, 2);
59
60 if (sharpFlat == "b" || sharpFlat == "#")
61 return name.substring (2);
62
63 return name.substring (1);
64}
65
66inline juce::String fixLegacyChordNames (const juce::String& oldName)
67{
68 if (chordNameHasSymbol (oldName, "maj")) return chordNote (oldName) + Chord (Chord::majorTriad).getSymbol();
69 if (chordNameHasSymbol (oldName, "min")) return chordNote (oldName) + Chord (Chord::minorTriad).getSymbol();
70 if (chordNameHasSymbol (oldName, "dim")) return chordNote (oldName) + Chord (Chord::diminishedTriad).getSymbol();
71 if (chordNameHasSymbol (oldName, "aug")) return chordNote (oldName) + Chord (Chord::augmentedTriad).getSymbol();
72 if (chordNameHasSymbol (oldName, "maj6")) return chordNote (oldName) + Chord (Chord::majorSixthChord).getSymbol();
73 if (chordNameHasSymbol (oldName, "min6")) return chordNote (oldName) + Chord (Chord::minorSixthChord).getSymbol();
74 if (chordNameHasSymbol (oldName, "dom7")) return chordNote (oldName) + Chord (Chord::dominatSeventhChord).getSymbol();
75 if (chordNameHasSymbol (oldName, "maj7")) return chordNote (oldName) + Chord (Chord::majorSeventhChord).getSymbol();
76 if (chordNameHasSymbol (oldName, "min7")) return chordNote (oldName) + Chord (Chord::minorSeventhChord).getSymbol();
77 if (chordNameHasSymbol (oldName, "aug7")) return chordNote (oldName) + Chord (Chord::augmentedSeventhChord).getSymbol();
78 if (chordNameHasSymbol (oldName, "dim7")) return chordNote (oldName) + Chord (Chord::diminishedSeventhChord).getSymbol();
79 if (chordNameHasSymbol (oldName, oslash)) return chordNote (oldName) + Chord (Chord::halfDiminishedSeventhChord).getSymbol();
80 if (chordNameHasSymbol (oldName, oslash + "7")) return chordNote (oldName) + Chord (Chord::minorMajorSeventhChord).getSymbol();
81 return oldName;
82}
83
84//==============================================================================
85Chord::Chord (ChordType c) : type (c)
86{
87}
88
89Chord::Chord (juce::Array<int> steps_, juce::String symbol_)
90 : type (Chord::customChord), steps (steps_), symbol (symbol_)
91{
92}
93
94juce::String Chord::toString()
95{
96 juce::String res;
97 res += juce::String ((int)type) + "|";
98 res += symbol + "|";
99
100 for (auto s : steps)
101 res += juce::String (s);
102
103 return res;
104}
105
106Chord Chord::fromString (const juce::String& s)
107{
108 Chord chord;
109
110 auto tokens = juce::StringArray::fromTokens (s, "|", "");
111 if (tokens.size() >= 2)
112 {
113 chord.type = (ChordType) tokens[0].getIntValue();
114 chord.symbol = tokens[1];
115
116 for (int i = 2; i < tokens.size(); i++)
117 if (tokens[i].isNotEmpty())
118 chord.steps.add (tokens[i].getIntValue());
119
120 return chord;
121 }
122 return { Chord::invalidChord };
123}
124
125juce::Array<Chord::ChordType> Chord::getAllChordType()
126{
128
129 for (int i = majorTriad; i <= diminishedMinorNinthChord; i++)
130 res.add ((ChordType) i);
131
132 return res;
133}
134
135juce::String Chord::getName() const
136{
137 switch (type)
138 {
139 case majorTriad: return TRANS("Major Triad");
140 case minorTriad: return TRANS("Minor Triad");
141 case diminishedTriad: return TRANS("Diminished Triad");
142 case augmentedTriad: return TRANS("Augmented Triad");
143 case majorSixthChord: return TRANS("Major Sixth");
144 case minorSixthChord: return TRANS("Minor Sixth");
145 case dominatSeventhChord: return TRANS("Dominant Seventh");
146 case majorSeventhChord: return TRANS("Major Seventh");
147 case minorSeventhChord: return TRANS("Minor Seventh");
148 case augmentedSeventhChord: return TRANS("Augmented Seventh");
149 case diminishedSeventhChord: return TRANS("Diminished Seventh");
150 case halfDiminishedSeventhChord: return TRANS("Half Diminished Seventh");
151 case minorMajorSeventhChord: return TRANS("Minor Major Seventh");
152 case powerChord: return TRANS("Power");
153 case suspendedSecond: return TRANS("Suspended Second");
154 case suspendedFourth: return TRANS("Suspended Fourth");
155 case majorNinthChord: return TRANS("Major Ninth");
156 case dominantNinthChord: return TRANS("Dominant Ninth");
157 case minorMajorNinthChord: return TRANS("Minor Major Ninth");
158 case minorDominantNinthChord: return TRANS("Minor Dominant Ninth");
159 case augmentedMajorNinthChord: return TRANS("Augmented Major Ninth");
160 case augmentedDominantNinthChord: return TRANS("Augmented Dominant Ninth");
161 case halfDiminishedNinthChord: return TRANS("Half Diminished Ninth");
162 case halfDiminishedMinorNinthChord: return TRANS("Half Diminished Minor Ninth");
163 case diminishedNinthChord: return TRANS("Diminished Ninth");
164 case diminishedMinorNinthChord: return TRANS("Diminished Minor Ninth");
165 case customChord: jassert (symbol.isNotEmpty()); return symbol;
166 case invalidChord:
167 default: jassertfalse; return {};
168 }
169}
170
171juce::String Chord::getShortName() const
172{
173 switch (type)
174 {
175 case majorTriad: return TRANS("major");
176 case minorTriad: return TRANS("minor");
177 case diminishedTriad: return TRANS("dim");
178 case augmentedTriad: return TRANS("aug");
179 case majorSixthChord: return TRANS("major 6");
180 case minorSixthChord: return TRANS("minor 6");
181 case dominatSeventhChord: return TRANS("dom 7");
182 case majorSeventhChord: return TRANS("major 7");
183 case minorSeventhChord: return TRANS("minor 7");
184 case augmentedSeventhChord: return TRANS("aug 7");
185 case diminishedSeventhChord: return TRANS("dim 7");
186 case halfDiminishedSeventhChord: return TRANS("half dim 7");
187 case minorMajorSeventhChord: return TRANS("min maj 7");
188 case powerChord: return TRANS("power");
189 case suspendedSecond: return TRANS("sus 2");
190 case suspendedFourth: return TRANS("sus 4");
191 case majorNinthChord: return TRANS("major 9");
192 case dominantNinthChord: return TRANS("dom 9");
193 case minorMajorNinthChord: return TRANS("min maj 9");
194 case minorDominantNinthChord: return TRANS("min dom 9");
195 case augmentedMajorNinthChord: return TRANS("aug maj 9");
196 case augmentedDominantNinthChord: return TRANS("aug dom 9");
197 case halfDiminishedNinthChord: return TRANS("half dim 9");
198 case halfDiminishedMinorNinthChord: return TRANS("half dim min 9");
199 case diminishedNinthChord: return TRANS("dim 9");
200 case diminishedMinorNinthChord: return TRANS("dim min 9");
201 case customChord: jassert (symbol.isNotEmpty()); return symbol;
202 case invalidChord:
203 default: jassertfalse; return {};
204 }
205}
206
207juce::String Chord::getSymbol() const
208{
209 switch (type)
210 {
211 case majorTriad: return "M";
212 case minorTriad: return "m";
213 case diminishedTriad: return "o";
214 case augmentedTriad: return "+";
215 case majorSixthChord: return "6";
216 case minorSixthChord: return "m6";
217 case dominatSeventhChord: return "7";
218 case majorSeventhChord: return "M7";
219 case minorSeventhChord: return "m7";
220 case augmentedSeventhChord: return "+7";
221 case diminishedSeventhChord: return "o7";
222 case halfDiminishedSeventhChord: return oslash + "7";
223 case minorMajorSeventhChord: return "mM7";
224 case suspendedSecond: return "sus2";
225 case suspendedFourth: return "sus4";
226 case powerChord: return "5";
227 case majorNinthChord: return "M9";
228 case dominantNinthChord: return "9";
229 case minorMajorNinthChord: return "mM9";
230 case minorDominantNinthChord: return "m9";
231 case augmentedMajorNinthChord: return "+M9";
232 case augmentedDominantNinthChord: return "+9";
233 case halfDiminishedNinthChord: return oslash + "9";
234 case halfDiminishedMinorNinthChord: return oslash + flat + "9";
235 case diminishedNinthChord: return "o9";
236 case diminishedMinorNinthChord: return "o" + flat + "9";
237 case customChord: jassert (symbol.isNotEmpty()); return symbol;
238 case invalidChord:
239 default: jassertfalse; return {};
240 }
241}
242
243juce::Array<int> Chord::getSteps() const
244{
245 switch (type)
246 {
247 case majorTriad: return { 0, 4, 7 };
248 case minorTriad: return { 0, 3, 7 };
249 case diminishedTriad: return { 0, 3, 6 };
250 case augmentedTriad: return { 0, 4, 8 };
251 case majorSixthChord: return { 0, 4, 7, 9 };
252 case minorSixthChord: return { 0, 3, 7, 9 };
253 case dominatSeventhChord: return { 0, 4, 7, 10 };
254 case majorSeventhChord: return { 0, 4, 7, 11 };
255 case minorSeventhChord: return { 0, 3, 7, 10 };
256 case augmentedSeventhChord: return { 0, 4, 8, 10 };
257 case diminishedSeventhChord: return { 0, 3, 6, 9 };
258 case halfDiminishedSeventhChord: return { 0, 3, 6, 10 };
259 case minorMajorSeventhChord: return { 0, 3, 7, 11 };
260 case suspendedSecond: return { 0, 2, 7 };
261 case suspendedFourth: return { 0, 5, 7 };
262 case powerChord: return { 0, 7 };
263 case majorNinthChord: return { 0, 4, 7, 11, 14 };
264 case dominantNinthChord: return { 0, 4, 7, 10, 14 };
265 case minorMajorNinthChord: return { 0, 3, 7, 11, 14 };
266 case minorDominantNinthChord: return { 0, 3, 7, 10, 14 };
267 case augmentedMajorNinthChord: return { 0, 4, 8, 11, 14 };
268 case augmentedDominantNinthChord: return { 0, 4, 8, 10, 14 };
269 case halfDiminishedNinthChord: return { 0, 3, 6, 10, 14 };
270 case halfDiminishedMinorNinthChord: return { 0, 3, 6, 10, 13 };
271 case diminishedNinthChord: return { 0, 3, 6, 9, 14 };
272 case diminishedMinorNinthChord: return { 0, 3, 6, 9, 13 };
273 case customChord: return steps;
274 case invalidChord:
275 default: return {};
276 }
277}
278
279juce::Array<int> Chord::getSteps (int inversion) const
280{
281 auto res = getSteps();
282
283 if (inversion > 0)
284 {
285 for (int i = 0; i < inversion; i++)
286 {
287 int step = res.removeAndReturn (0) + 12;
288 res.add (step);
289 }
290 }
291 else if (inversion < 0)
292 {
293 for (int i = 0; i < -inversion; i++)
294 {
295 int step = res.removeAndReturn (res.size() - 1) - 12;
296 res.insert (0, step);
297 }
298 }
299 return res;
300}
301
302//==============================================================================
303
304Scale::Scale (ScaleType type_) : type (type_)
305{
306 switch (type)
307 {
308 case major:
309 case ionian:
310 steps = { Whole, Whole, Half, Whole, Whole, Whole, Half };
311 triads = generateTriads (0);
312 sixths = generateSixths (0);
313 sevenths = generateSevenths (0);
314 break;
315 case dorian:
316 steps = { Whole, Half, Whole, Whole, Whole, Half, Whole };
317 triads = generateTriads (1);
318 sixths = generateSixths (1);
319 sevenths = generateSevenths (1);
320 break;
321 case phrygian:
322 steps = { Half, Whole, Whole, Whole, Half, Whole, Whole };
323 triads = generateTriads (2);
324 sixths = generateSixths (2);
325 sevenths = generateSevenths (2);
326 break;
327 case lydian:
328 steps = { Whole, Whole, Whole, Half, Whole, Whole, Half };
329 triads = generateTriads (3);
330 sixths = generateSixths (3);
331 sevenths = generateSevenths (3);
332 break;
333 case mixolydian:
334 steps = { Whole, Whole, Half, Whole, Whole, Half, Whole };
335 triads = generateTriads (4);
336 sixths = generateSixths (4);
337 sevenths = generateSevenths (4);
338 break;
339 case minor:
340 case aeolian:
341 steps = { Whole, Half, Whole, Whole, Half, Whole, Whole };
342 triads = generateTriads (5);
343 sixths = generateSixths (5);
344 sevenths = generateSevenths (5);
345 break;
346 case locrian:
347 steps = { Half, Whole, Whole, Half, Whole, Whole, Whole };
348 triads = generateTriads (6);
349 sixths = generateSixths (6);
350 sevenths = generateSevenths (6);
351 break;
352 case melodicMinor:
353 steps = { Whole, Half, Whole, Whole, Whole, Whole, Half };
354 triads = { Chord::minorTriad, Chord::minorTriad, Chord::augmentedTriad, Chord::majorTriad, Chord::majorTriad, Chord::diminishedTriad, Chord::diminishedTriad };
355 sixths = { Chord::invalidChord, Chord::invalidChord, Chord::invalidChord, Chord::invalidChord, Chord::invalidChord, Chord::invalidChord, Chord::invalidChord };
356 sevenths = { Chord::minorMajorSeventhChord, Chord::minorSeventhChord, Chord::augmentedSeventhChord, Chord::dominatSeventhChord, Chord::dominatSeventhChord, Chord::halfDiminishedSeventhChord, Chord::halfDiminishedSeventhChord };
357 break;
358 case harmonicMinor:
359 steps = { Whole, Half, Whole, Whole, Half, WholeHalf, Half };
360 triads = { Chord::minorTriad, Chord::diminishedTriad, Chord::augmentedTriad, Chord::minorTriad, Chord::majorTriad, Chord::majorTriad, Chord::diminishedTriad };
361 sixths = { Chord::invalidChord, Chord::invalidChord, Chord::invalidChord, Chord::invalidChord, Chord::invalidChord, Chord::invalidChord, Chord::invalidChord };
362 sevenths = { Chord::minorMajorSeventhChord, Chord::halfDiminishedSeventhChord, Chord::augmentedSeventhChord, Chord::minorSeventhChord, Chord::dominatSeventhChord, Chord::majorSeventhChord, Chord::diminishedSeventhChord };
363 break;
364 }
365}
366
367juce::Array<Scale::ScaleType> Scale::getAllScaleTypes()
368{
370
371 for (int i = major; i <= harmonicMinor; i++)
372 res.add ((ScaleType)i);
373
374 return res;
375}
376
377juce::StringArray Scale::getScaleStrings()
378{
380
381 for (int i = major; i <= harmonicMinor; i++)
382 res.add (getNameForType ((ScaleType) i));
383
384 return res;
385}
386
387Scale::ScaleType Scale::getTypeFromName (juce::String name)
388{
389 for (int i = major; i <= harmonicMinor; i++)
390 if (name == getNameForType ((ScaleType) i))
391 return (ScaleType) i;
392
394 return major;
395}
396
397juce::String Scale::getName() const
398{
399 return getNameForType (type);
400}
401
402juce::String Scale::getShortName() const
403{
404 return getShortNameForType (type);
405}
406
407juce::String Scale::getNameForType (ScaleType type)
408{
409 switch (type)
410 {
411 case ionian: return TRANS("Ionian");
412 case dorian: return TRANS("Dorian");
413 case phrygian: return TRANS("Phrygian");
414 case lydian: return TRANS("Lydian");
415 case mixolydian: return TRANS("Mixolydian");
416 case aeolian: return TRANS("Aeolian");
417 case locrian: return TRANS("Locrian");
418 case major: return TRANS("Major");
419 case minor: return TRANS("Minor");
420 case melodicMinor: return TRANS("Melodic Minor");
421 case harmonicMinor: return TRANS("Harmonic Minor");
422 default: jassertfalse; return {};
423 }
424}
425
426juce::String Scale::getShortNameForType (ScaleType type)
427{
428 switch (type)
429 {
430 case ionian: return TRANS("Ion");
431 case dorian: return TRANS("Dor");
432 case phrygian: return TRANS("Phr");
433 case lydian: return TRANS("Lyd");
434 case mixolydian: return TRANS("Mix");
435 case aeolian: return TRANS("Aeo");
436 case locrian: return TRANS("Loc");
437 case major: return TRANS("Maj");
438 case minor: return TRANS("Min");
439 case melodicMinor: return TRANS("Mel");
440 case harmonicMinor: return TRANS("Har");
441 default: jassertfalse; return {};
442 }
443}
444
445juce::Array<Chord> Scale::generateTriads (int offset) const
446{
448 juce::Array<Chord> base { Chord::majorTriad, Chord::minorTriad, Chord::minorTriad, Chord::majorTriad, Chord::majorTriad, Chord::minorTriad, Chord::diminishedTriad };
449
450 for (int i = 0; i < base.size(); i++)
451 res.add (base[(i + offset) % base.size()]);
452
453 return res;
454}
455
456juce::Array<Chord> Scale::generateSixths (int offset) const
457{
459 juce::Array<Chord> base { Chord::majorSixthChord, Chord::minorSixthChord, Chord::invalidChord, Chord::majorSixthChord, Chord::majorSixthChord, Chord::invalidChord, Chord::invalidChord };
460
461 for (int i = 0; i < base.size(); i++)
462 res.add (base[(i + offset) % base.size()]);
463
464 return res;
465}
466
467juce::Array<Chord> Scale::generateSevenths (int offset) const
468{
470 juce::Array<Chord> base { Chord::majorSeventhChord, Chord::minorSeventhChord, Chord::minorSeventhChord, Chord::majorSeventhChord, Chord::dominatSeventhChord, Chord::minorSeventhChord, Chord::halfDiminishedSeventhChord };
471
472 for (int i = 0; i < base.size(); i++)
473 res.add (base[(i + offset) % base.size()]);
474
475 return res;
476}
477
478juce::Array<int> Scale::getSteps (int octaves) const
479{
480 juce::Array<int> res { 0 };
481 int step = 0;
482
483 for (int i = 0; i < steps.size() - 1; i++)
484 {
485 int inc = 0;
486 switch (steps[i])
487 {
488 case Whole: inc = 2; break;
489 case Half: inc = 1; break;
490 case WholeHalf: inc = 3; break;
491 default: jassertfalse; break;
492 }
493 step += inc;
494 res.add (step);
495 }
496
497 if (octaves > 1)
498 {
499 const int itemsInScale = res.size();
500 for (int o = 1; o < octaves; o++)
501 for (int i = 0; i < itemsInScale; i++)
502 res.add (res[i] + o * 12);
503 }
504
505 return res;
506}
507
508juce::StringArray Scale::getIntervalNames()
509{
510 return {"i", "ii", "iii", "iv", "v", "vi", "vii"};
511}
512
513juce::String Scale::getIntervalName (Intervals interval) const
514{
515 auto name = getIntervalNames()[(int)interval];
516
517 switch (triads[(int)interval].getType())
518 {
519 case Chord::majorTriad: name = name.toUpperCase();
520 break;
521 case Chord::minorTriad:
522 break;
523 case Chord::augmentedTriad: name = name.toUpperCase() + "+";
524 break;
525 case Chord::diminishedTriad: name = name + juce::String::charToString (176);
526 break;
527 case Chord::customChord:
528 case Chord::invalidChord:
529 case Chord::majorSixthChord:
530 case Chord::minorSixthChord:
531 case Chord::dominatSeventhChord:
532 case Chord::majorSeventhChord:
533 case Chord::minorSeventhChord:
534 case Chord::augmentedSeventhChord:
535 case Chord::diminishedSeventhChord:
536 case Chord::halfDiminishedSeventhChord:
537 case Chord::minorMajorSeventhChord:
538 case Chord::suspendedSecond:
539 case Chord::suspendedFourth:
540 case Chord::powerChord:
541 case Chord::majorNinthChord:
542 case Chord::dominantNinthChord:
543 case Chord::minorMajorNinthChord:
544 case Chord::minorDominantNinthChord:
545 case Chord::augmentedMajorNinthChord:
546 case Chord::augmentedDominantNinthChord:
547 case Chord::halfDiminishedNinthChord:
548 case Chord::halfDiminishedMinorNinthChord:
549 case Chord::diminishedNinthChord:
550 case Chord::diminishedMinorNinthChord:
551 default: jassertfalse;
552 break;
553 }
554
555 return name;
556}
557
558//==============================================================================
559struct PatternGenerator::ProgressionList : public ValueTreeObjectList<PatternGenerator::ProgressionItem>
560{
562 : ValueTreeObjectList<ProgressionItem> (v), generator (g)
563 {
564 rebuildObjects();
565 }
566
567 ~ProgressionList() override
568 {
569 freeObjects();
570 }
571
572 bool isSuitableType (const juce::ValueTree& v) const override
573 {
574 return v.hasType (IDs::PROGRESSIONITEM);
575 }
576
577 ProgressionItem* createNewObject (const juce::ValueTree& v) override
578 {
579 return new ProgressionItem (generator, v);
580 }
581
582 void deleteObject (ProgressionItem* c) override
583 {
584 delete c;
585 }
586
587 void newObjectAdded (ProgressionItem*) override {}
588 void objectRemoved (ProgressionItem*) override {}
589 void objectOrderChanged() override {}
590
591 PatternGenerator& generator;
592
594};
595
596//==============================================================================
597PatternGenerator::ProgressionItem::ProgressionItem (PatternGenerator& g, const juce::ValueTree& s, bool temporary)
598 : generator (g), state (s)
599{
600 auto um = temporary ? nullptr : g.clip.getUndoManager();
601
602 chordName.referTo (state, IDs::chordName, um);
603 pitches.referTo (state, IDs::pitches, um);
604 lengthInBeats.referTo (state, IDs::length, um, BeatDuration::fromBeats (4));
605 octave.referTo (state, IDs::octave, um);
606 inversion.referTo (state, IDs::inversion, um);
607
608 // Chord name format changed between W8 and W9 - update to new version
609 juce::String oldName = state[IDs::name];
610 if (oldName.isNotEmpty() && chordName.get().isEmpty())
611 {
612 chordName = fixLegacyChordNames (oldName);
613 state.removeProperty (IDs::name, nullptr);
614 }
615}
616
617PatternGenerator::ProgressionItem::~ProgressionItem() noexcept
618{
619}
620
621bool PatternGenerator::ProgressionItem::operator== (const ProgressionItem& other) const noexcept
622{
623 return chordName == other.chordName
624 && pitches == other.pitches
625 && lengthInBeats == other.lengthInBeats;
626}
627
628void PatternGenerator::ProgressionItem::setChordName (juce::String name)
629{
630 if (isRoman (name))
631 chordName = name.toLowerCase().retainCharacters ("iv7");
632 else
633 chordName = name;
634
635 pitches = juce::String();
636}
637
638void PatternGenerator::ProgressionItem::setChordName (juce::String name, juce::String pitchesIn)
639{
640 chordName = name;
641 pitches = pitchesIn;
642}
643
644juce::String PatternGenerator::ProgressionItem::getChordName() const
645{
646 return generator.formatChordName (chordName);
647}
648
649void PatternGenerator::ProgressionItem::setRoot (int root)
650{
651 chordName = juce::MidiMessage::getMidiNoteName (root, true, false, 0) + chordSymbol (chordName);
652}
653
654void PatternGenerator::ProgressionItem::setChord (int root, Chord::ChordType type)
655{
656 chordName = juce::MidiMessage::getMidiNoteName (root, true, false, 0) + Chord (type).getSymbol();
657 pitches = juce::String();
658}
659
660Chord PatternGenerator::ProgressionItem::getChord (const Scale& scale) const
661{
662 if (isRoman (chordName))
663 {
664 auto progressionsOpts = Scale::getIntervalNames();
665 auto interval = progressionsOpts.indexOf (chordName.get().retainCharacters ("iv")); // find where progression we are
666
667 return chordName.get().contains ("7") ? scale.getSevenths()[interval] : scale.getTriads()[interval];
668 }
669
670 if (pitches.get().isNotEmpty())
671 {
672 juce::Array<int> steps;
673
674 for (auto p : juce::StringArray::fromTokens (pitches.get(), ", ", {}))
675 if (p.isNotEmpty())
676 steps.add (p.getIntValue());
677
678 return Chord (steps, chordSymbol (chordName));
679 }
680
681 for (Chord::ChordType type : Chord::getAllChordType())
682 {
683 Chord chord (type);
684
685 if (chordNameHasSymbol (chordName.get(), chord.getSymbol()))
686 return chord;
687 }
688
689 return Chord (Chord::invalidChord);
690}
691
692bool PatternGenerator::ProgressionItem::isRomanNumeral() const
693{
694 return isRoman (chordName);
695}
696
697juce::String PatternGenerator::ProgressionItem::getChordSymbol()
698{
699 BeatPosition beat;
700
701 for (auto itm : generator.getChordProgression())
702 {
703 if (itm == this)
704 break;
705
706 beat = beat + itm->lengthInBeats;
707 }
708
709 Scale scale = generator.getScaleAtBeat (beat);
710 const int root = getRootNote (generator.getNoteAtBeat (beat), scale);
711 Chord chord = getChord (scale);
712 bool sharp = generator.clip.edit.pitchSequence.getPitchAtBeat (generator.clip.getStartBeat() + toDuration (beat)).accidentalsSharp;
713
714 return juce::MidiMessage::getMidiNoteName (root, sharp, false, 0) + chord.getSymbol();
715}
716
717int PatternGenerator::ProgressionItem::getRootNote (int key, const Scale& scale)
718{
719 jassert (chordName.get().isNotEmpty());
720
721 if (isRoman (chordName))
722 {
723 auto progressionsOpts = Scale::getIntervalNames();
724 auto progressionSteps = scale.getSteps (3);
725
726 const int interval = progressionsOpts.indexOf (chordName.get().retainCharacters ("iv")); // find where progression we are
727 const int chordRoot = key + progressionSteps[interval]; // find base note of the current chord
728
729 return chordRoot;
730 }
731
732 auto name = chordName.get();
733 auto noteName = name.substring (1, 2) == "#" ? name.substring (0, 2) : name.substring (0, 1);
734
735 for (int note = 0; note < 12; note++)
736 if (juce::MidiMessage::getMidiNoteName (note, true, false, 0) == noteName)
737 return note;
738
740 return 0;
741}
742
743
744//==============================================================================
745//==============================================================================
747 public juce::AsyncUpdater
748{
750 : owner (owner_)
751 {
752 owner.clip.state.addListener (this);
753 }
754
755 ~AutoUpdateManager() override
756 {
757 owner.clip.state.removeListener (this);
758 }
759
760 void valueTreeChanged() override
761 {}
762
763 void valueTreePropertyChanged (juce::ValueTree& p, const juce::Identifier& c) override
764 {
765 if (Clip::isClipState (p))
766 if (c == IDs::start || c == IDs::length || c == IDs::offset)
767 if (! owner.clip.edit.getUndoManager().isPerformingUndoRedo())
768 triggerAsyncUpdate();
769 }
770
771 void handleAsyncUpdate() override
772 {
773 if (owner.getAutoUpdate())
774 owner.generatePattern();
775 }
776
777private:
778 PatternGenerator& owner;
779};
780
781
782//==============================================================================
783//==============================================================================
784const int PatternGenerator::scaleRootGlobalTrack = -1;
785const int PatternGenerator::scaleRootChordTrack = -2;
786
787PatternGenerator::PatternGenerator (Clip& c, juce::ValueTree v) : clip (c), state (v)
788{
789 auto um = c.getUndoManager();
790
791 progressionList = std::make_unique<ProgressionList> (*this, state.getOrCreateChildWithName (IDs::PROGRESSION, um));
792
793 mode.referTo (state, IDs::mode, um, Mode::off);
794 allNotes.referTo (state, IDs::allNotes, um);
795 octaveUp.referTo (state, IDs::octaveUp, um);
796 octaveDown.referTo (state, IDs::octaveDown, um);
797 spread.referTo (state, IDs::spread, um);
798 autoUpdate.referTo (state, IDs::autoUpdate, um, true);
799 patternHash.referTo (state, IDs::hash, um);
800 octave.referTo (state, IDs::octave, um, 4);
801 velocity.referTo (state, IDs::velocity, um, 100.0f);
802 arpUpDown.referTo (state, IDs::arpUpDown, um, false);
803 arpPlayRoot.referTo (state, IDs::arpPlayroot, um, true);
804 arpPatternLength.referTo (state, IDs::arpPatternLength, um, 4.0f);
805 arpStyle.referTo (state, IDs::arpStle, um, "0123");
806 arpSteps.referTo (state, IDs::arpSteps, um, 4);
807 melodyNoteLength.referTo (state, IDs::melodyNoteLength, um, BeatDuration::fromBeats (1.0 / 4.0f));
808 gate.referTo (state, IDs::gate, um, 100.0f);
809 scaleType.referTo (state, IDs::scaleType, um, Scale::major);
810 scaleRoot.referTo (state, IDs::scaleRoot, um, -1);
811
812 state.addListener (this);
813
814 editLoadedCallback = std::make_unique<Edit::LoadFinishedCallback<PatternGenerator>> (*this, clip.edit);
815}
816
817PatternGenerator::~PatternGenerator()
818{
819 state.removeListener (this);
820}
821
822MidiClip* PatternGenerator::getMidiClip() const
823{
824 return dynamic_cast<MidiClip*> (&clip);
825}
826
827void PatternGenerator::editFinishedLoading()
828{
829 // Update the note hash from v1 to v2 if required
830 if (auto mc = getMidiClip())
831 if (patternHash == hashNotes (mc->getSequence(), 1))
832 patternHash = hashNotes (mc->getSequence(), 2);
833
834 if (mode != Mode::off)
835 autoUpdateManager = std::make_unique<AutoUpdateManager> (*this);
836}
837
838BeatDuration PatternGenerator::getMinimumChordLength() const
839{
840 return BeatDuration::fromBeats (1.0);
841}
842
843BeatDuration PatternGenerator::getMaximumChordLength() const
844{
845 switch (mode)
846 {
847 case Mode::arpeggio:
848 case Mode::chords:
849 case Mode::bass:
850 case Mode::melody:
851 case Mode::off:
852 default:
853 return BeatDuration::fromBeats (1024.0);
854 }
855}
856
857void PatternGenerator::validateChordLengths()
858{
859 switch (mode)
860 {
861 case Mode::arpeggio:
862 case Mode::off:
863 case Mode::chords:
864 case Mode::bass:
865 case Mode::melody:
866 {
867 for (auto itm : getChordProgression())
868 if (itm->lengthInBeats.get() > getMaximumChordLength())
869 itm->lengthInBeats = getMaximumChordLength();
870
871 break;
872 }
873 }
874}
875
876int PatternGenerator::getChordProgressionLength() const
877{
878 return progressionList->size();
879}
880
881const juce::Array<PatternGenerator::ProgressionItem*>& PatternGenerator::getChordProgression() const noexcept
882{
883 return progressionList->objects;
884}
885
886void PatternGenerator::setChordProgression (juce::ValueTree src)
887{
888 jassert (src.isValid() && src.hasType (IDs::PROGRESSION));
889
890 auto um = clip.getUndoManager();
891
892 auto dst = state.getChildWithName (IDs::PROGRESSION);
893
894 dst.removeAllChildren (um);
895
896 for (int i = 0; i < src.getNumChildren(); i++)
897 dst.addChild (src.getChild (i).createCopy(), i, um);
898}
899
900juce::ValueTree PatternGenerator::getChordPattern()
901{
902 return state.getChildWithName (IDs::CHORDPATTERN);
903}
904
905juce::ValueTree PatternGenerator::getBassPattern()
906{
907 return state.getChildWithName (IDs::BASSPATTERN);
908}
909
910void PatternGenerator::setChordPattern (juce::ValueTree pattern)
911{
912 auto um = clip.getUndoManager();
913
914 state.removeChild (state.getChildWithName (IDs::CHORDPATTERN), um);
915
916 state.addChild (pattern, -1, um);
917}
918
919void PatternGenerator::setBassPattern (juce::ValueTree pattern)
920{
921 auto um = clip.getUndoManager();
922
923 state.removeChild (state.getChildWithName (IDs::BASSPATTERN), um);
924
925 state.addChild (pattern, -1, um);
926}
927
928void PatternGenerator::valueTreePropertyChanged (juce::ValueTree&, const juce::Identifier& c)
929{
930 if (c == IDs::arpSteps || c == IDs::arpUpDown)
931 {
932 // update current style to be of correct length
933 int length = arpUpDown ? (arpSteps * 2 - 2) : arpSteps;
934
935 if (arpStyle.get().length() != length)
936 arpStyle = getArpStyles()[0];
937 }
938 else if (c == IDs::mode)
939 {
940 if (mode == Mode::off)
941 {
942 autoUpdateManager = nullptr;
943 }
944 else
945 {
946 if (autoUpdateManager == nullptr)
947 autoUpdateManager = std::make_unique<AutoUpdateManager> (*this);
948 }
949 }
950}
951
952ChordClip* PatternGenerator::getChordClipAt (TimePosition t) const
953{
954 for (auto c : clip.edit.getChordTrack()->getClips())
955 if (c->getPosition().time.contains (t))
956 if (auto cc = dynamic_cast<ChordClip*> (c))
957 return cc;
958
959 return {};
960}
961
962Scale PatternGenerator::getScaleAtBeat (BeatPosition beat) const
963{
964 Scale scale;
965
966 if (dynamic_cast<ChordClip*> (&clip) != nullptr)
967 {
968 if (scaleRoot == scaleRootGlobalTrack)
969 {
970 auto t = clip.getTimeOfContentBeat (beat) + TimeDuration::fromSeconds (0.0001);
971 scale = Scale (clip.edit.pitchSequence.getPitchAt (t).getScale());
972 }
973 else
974 {
975 scale = Scale (scaleType);
976 }
977 }
978 else if (mode == Mode::off || scaleRoot == scaleRootChordTrack)
979 {
980 auto t = clip.getTimeOfContentBeat (beat) + TimeDuration::fromSeconds (0.0001);
981
982 if (auto cc = getChordClipAt (t))
983 {
984 const auto b = cc->getContentBeatAtTime (t);
985 return cc->getPatternGenerator()->getScaleAtBeat (b);
986 }
987
988 scale = Scale (clip.edit.pitchSequence.getPitchAt (t).getScale());
989 }
990 else if (scaleRoot == scaleRootGlobalTrack)
991 {
992 const auto t = clip.getTimeOfContentBeat (beat) + TimeDuration::fromSeconds (0.0001);
993 scale = Scale (clip.edit.pitchSequence.getPitchAt (t).getScale());
994 }
995 else
996 {
997 scale = Scale (scaleType);
998 }
999
1000 return scale;
1001}
1002
1003int PatternGenerator::getNoteAtBeat (BeatPosition beat) const
1004{
1005 if (dynamic_cast<ChordClip*>(&clip) != nullptr)
1006 {
1007 if (scaleRoot == scaleRootGlobalTrack)
1008 return clip.edit.pitchSequence.getPitchAtBeat (clip.getStartBeat() + toDuration (beat)).getPitch() % 12;
1009
1010 return scaleRoot;
1011 }
1012
1013 if (mode == Mode::off || scaleRoot == scaleRootChordTrack)
1014 {
1015 auto t = clip.getTimeOfContentBeat (beat) + TimeDuration::fromSeconds (0.0001);
1016
1017 if (auto cc = getChordClipAt (t))
1018 {
1019 auto b = cc->getContentBeatAtTime (t);
1020 return cc->getPatternGenerator()->getNoteAtBeat (b);
1021 }
1022
1023 return clip.edit.pitchSequence.getPitchAtBeat (clip.getStartBeat() + toDuration (beat)).getPitch() % 12;
1024 }
1025
1026 if (scaleRoot == scaleRootGlobalTrack)
1027 return clip.edit.pitchSequence.getPitchAtBeat (clip.getStartBeat() + toDuration (beat)).getPitch() % 12;
1028
1029 return scaleRoot;
1030}
1031
1032juce::StringArray PatternGenerator::getPossibleTriadNames() const
1033{
1035
1036 auto scale = getScaleAtBeat (BeatPosition());
1037
1038 for (int interval = int (Scale::Intervals::i); interval <= int (Scale::Intervals::vii); interval++)
1039 res.add (scale.getIntervalName ((Scale::Intervals) interval));
1040
1041 return res;
1042}
1043
1044juce::StringArray PatternGenerator::getPossibleSeventhNames() const
1045{
1047 auto scale = getScaleAtBeat (BeatPosition());
1048
1049 for (int interval = int (Scale::Intervals::i); interval <= int (Scale::Intervals::vii); interval++)
1050 res.add (scale.getIntervalName ((Scale::Intervals) interval) + "7");
1051
1052 return res;
1053}
1054
1055juce::String PatternGenerator::formatChordName (juce::String simplifiedChordName) const
1056{
1057 if (isRoman (simplifiedChordName))
1058 {
1059 auto interval = (Scale::Intervals) Scale::getIntervalNames().indexOf (simplifiedChordName.retainCharacters ("iv"));
1060 auto scale = getScaleAtBeat (BeatPosition());
1061 auto intervalStr = scale.getIntervalName (interval);
1062
1063 if (simplifiedChordName.contains ("7"))
1064 return intervalStr + "7";
1065
1066 return intervalStr;
1067 }
1068
1069 return simplifiedChordName;
1070}
1071
1072juce::StringArray PatternGenerator::getChordProgressionChordNames (bool simplified) const
1073{
1075
1076 for (auto item : *progressionList)
1077 {
1078 if (simplified)
1079 res.add (item->chordName);
1080 else if (item->isRomanNumeral())
1081 res.add (item->getChordName());
1082 else
1083 res.add (item->getChordSymbol());
1084 }
1085
1086 return res;
1087}
1088
1090{
1091 if (progressionItems.strings.size() > maxChords)
1092 progressionItems.strings.resize (maxChords);
1093
1094 auto& um = clip.edit.getUndoManager();
1095
1096 if (! state.getChildWithName (IDs::PROGRESSION).isValid())
1097 state.addChild (juce::ValueTree (IDs::PROGRESSION), -1, &um);
1098
1099 auto progression = state.getChildWithName (IDs::PROGRESSION);
1100
1101 progression.removeAllChildren (&um);
1102
1103 for (auto itmName : progressionItems)
1104 {
1105 // store in simplest form
1106 if (isRoman (itmName))
1107 itmName = itmName.toLowerCase().retainCharacters ("iv7");
1108
1109 juce::ValueTree item (IDs::PROGRESSIONITEM);
1110 item.setProperty (IDs::name, itmName, &um);
1111
1112 progression.addChild (item, -1, &um);
1113 }
1114}
1115
1116void PatternGenerator::duplicateChordInProgression (int idx)
1117{
1118 auto progression = state.getChildWithName (IDs::PROGRESSION);
1119 auto& um = clip.edit.getUndoManager();
1120
1121 auto vt = progression.getChild (idx).createCopy();
1122 progression.addChild (vt, idx + 1, &um);
1123}
1124
1125void PatternGenerator::removeIndexFromProgression (int idx)
1126{
1127 auto progression = state.getChildWithName (IDs::PROGRESSION);
1128 auto& um = clip.edit.getUndoManager();
1129
1130 progression.removeChild (idx, &um);
1131}
1132
1133void PatternGenerator::removeRangeFromProgression (int startIndex, int numberToRemove)
1134{
1135 auto progression = state.getChildWithName (IDs::PROGRESSION);
1136 auto& um = clip.edit.getUndoManager();
1137
1138 auto endIndex = juce::jlimit (0, progression.getNumChildren(), startIndex + numberToRemove);
1139 startIndex = juce::jlimit (0, progression.getNumChildren(), startIndex);
1140
1141 if (endIndex > startIndex)
1142 {
1143 numberToRemove = endIndex - startIndex;
1144
1145 for (int i = 0; i < numberToRemove; ++i)
1146 progression.removeChild (endIndex - i - 1, &um);
1147 }
1148}
1149
1150MidiNote* PatternGenerator::addNote (MidiList& sequence, int pitch, BeatPosition startBeat, BeatDuration lengthInBeats,
1151 int vel, int colourIndex, juce::UndoManager* um)
1152{
1153 if (pitch < 0 || pitch >= 128)
1154 return {};
1155
1156 if (startBeat + lengthInBeats <= BeatPosition())
1157 return {};
1158
1159 if (startBeat >= toPosition (clip.getLengthInBeats()))
1160 return {};
1161
1162 if (lengthInBeats < BeatDuration::fromBeats (0.00001))
1163 return {};
1164
1165 if (startBeat < BeatPosition())
1166 {
1167 lengthInBeats = lengthInBeats + toDuration (startBeat);
1168 startBeat = BeatPosition();
1169 }
1170
1171 if (startBeat + lengthInBeats > toPosition (clip.getLengthInBeats()))
1172 lengthInBeats = lengthInBeats - (toDuration (startBeat) + lengthInBeats - clip.getLengthInBeats());
1173
1174 return sequence.addNote (pitch, startBeat, lengthInBeats, vel, colourIndex, um);
1175}
1176
1177PatternGenerator::NoteType PatternGenerator::getTypeForNote (const MidiClip& mc, const MidiNote& note)
1178{
1179 bool inKey = false, inChord = false;
1180
1181 Scale s = getScaleAtBeat (note.getStartBeat());
1182 int root = getNoteAtBeat (note.getStartBeat());
1183
1184 inKey = s.getSteps().contains ((note.getNoteNumber() - root) % 12);
1185
1186 juce::OwnedArray<ProgressionItem> progressionItems;
1187 auto progressionOffset = getFlattenedChordProgression (progressionItems);
1188
1189 auto curBeat = BeatPosition::fromBeats (-progressionOffset.inBeats());
1190 int panic = 1000;
1191 int progressionCur = 0;
1192 const auto clipLength = toPosition (mc.isLooping() ? mc.getLoopLengthBeats() : mc.getLengthInBeats() + mc.getOffsetInBeats());
1193
1194 while (curBeat < clipLength)
1195 {
1196 if (panic-- == 0)
1197 break;
1198
1199 if (auto item = progressionItems[progressionCur])
1200 {
1201 const auto len = item->lengthInBeats.get();
1202
1203 if (note.getBeatPosition() + BeatDuration::fromBeats (0.0001) >= curBeat
1204 && note.getBeatPosition() + BeatDuration::fromBeats (0.0001) < curBeat + len)
1205 {
1206 if (item->getChord (s).isValid())
1207 {
1208 int chordRoot = item->getRootNote (root, s) % 12;
1209
1210 auto chordSteps = item->getChord (s).getSteps();
1211
1212 for (int i = 0; i < chordSteps.size(); i++)
1213 chordSteps.set (i, chordSteps[i] % 12);
1214
1215 inChord = chordSteps.contains ((note.getNoteNumber() - chordRoot) % 12);
1216 }
1217 break;
1218 }
1219
1220 curBeat = curBeat + len;
1221 }
1222
1223 progressionCur++;
1224
1225 if (progressionCur >= progressionItems.size())
1226 progressionCur = 0;
1227 }
1228
1229 if (inChord && inKey)
1230 return ChordInKeyNote;
1231
1232 if (inChord && ! inKey)
1233 return ChordNotInKeyNote;
1234
1235 if (inKey)
1236 return InKeyNote;
1237
1238 return NotInKeyNote;
1239}
1240
1241BeatDuration PatternGenerator::getFlattenedChordProgression (juce::OwnedArray<ProgressionItem>& progression, bool globalTime)
1242{
1243 if (mode == Mode::off || scaleRoot == scaleRootChordTrack)
1244 {
1245 BeatPosition pos;
1246
1247 // Get all the chord clips
1248 juce::Array<ChordClip*> chordClips;
1249
1250 for (auto c : clip.edit.getChordTrack()->getClips())
1251 if (auto cc = dynamic_cast<ChordClip*> (c))
1252 chordClips.add (cc);
1253
1254 // Drop any chord clips that are completely overlapped
1255 for (int i = chordClips.size(); --i >= 0;)
1256 {
1257 for (int j = chordClips.size(); --j >= 0;)
1258 {
1259 if (i != j)
1260 {
1261 auto c1 = chordClips[i];
1262 auto c2 = chordClips[j];
1263
1264 if (c1->getStartBeat() >= c2->getStartBeat() &&
1265 c1->getEndBeat() <= c2->getEndBeat())
1266 {
1267 chordClips.remove (i);
1268 break;
1269 }
1270 }
1271 }
1272 }
1273
1274 // Find the starts of chord clips
1275 juce::Array<BeatPosition> clipStarts;
1276
1277 for (auto cc : chordClips)
1278 clipStarts.add (cc->getStartBeat());
1279
1280 clipStarts.sort();
1281
1282 for (auto cc : chordClips)
1283 {
1284 // If there is a gap before this chord, insert empty space
1285 if (cc->getStartBeat() > pos)
1286 {
1287 auto length = cc->getStartBeat() - pos;
1288 juce::ValueTree s (IDs::PROGRESSIONITEM);
1289 auto newItem = new ProgressionItem (*this, s, true);
1290 newItem->lengthInBeats = length;
1291 progression.add (newItem);
1292 pos = cc->getStartBeat();
1293 }
1294
1295 BeatDuration amountToDrop;
1296
1297 if (cc->getStartBeat() < pos)
1298 amountToDrop = pos - cc->getStartBeat();
1299
1300 auto& src = cc->getPatternGenerator()->getChordProgression();
1301 if (src.size() > 0)
1302 {
1303 auto avaliableLength = cc->getLengthInBeats();
1304
1305 // check if following clip is overlapping, and truncate this clip
1306 for (auto start : clipStarts)
1307 {
1308 auto len = start - cc->getStartBeat();
1309
1310 if (len > BeatDuration() && len < avaliableLength)
1311 avaliableLength = len;
1312 }
1313
1314 BeatDuration progressionLength;
1315
1316 while (progressionLength < avaliableLength)
1317 {
1318 for (auto itm : src)
1319 {
1320 auto s = itm->state.createCopy();
1321 std::unique_ptr<ProgressionItem> newItm (new ProgressionItem (*this, s, true));
1322
1323 auto len = std::min (newItm->lengthInBeats.get(), avaliableLength - progressionLength);
1324 progressionLength = progressionLength + len;
1325
1326 // this item is covered by another chord clip,
1327 // either drop it or shorten it
1328 if (len < amountToDrop)
1329 {
1330 if (progressionLength >= avaliableLength)
1331 break;
1332
1333 amountToDrop = amountToDrop - len;
1334 continue;
1335 }
1336 if (len > amountToDrop && amountToDrop > BeatDuration())
1337 {
1338 len = len- amountToDrop;
1339 amountToDrop = BeatDuration();
1340 }
1341
1342 newItm->lengthInBeats = len;
1343
1344 progression.add (newItm.release());
1345
1346 if (progressionLength >= avaliableLength)
1347 break;
1348 }
1349 }
1350 pos = pos + progressionLength;
1351 }
1352 }
1353
1354 juce::ValueTree s (IDs::PROGRESSIONITEM);
1355 auto newItem = new ProgressionItem (*this, s, true);
1356 newItem->lengthInBeats = BeatDuration::fromBeats (100000);
1357 progression.add (newItem);
1358
1359 if (! globalTime)
1360 {
1361 BeatDuration toChop = toDuration (clip.getStartBeat());
1362
1363 while (toChop > BeatDuration())
1364 {
1365 auto itm = progression[0];
1366
1367 if (itm->lengthInBeats <= toChop)
1368 {
1369 toChop = toChop - itm->lengthInBeats;
1370 progression.remove (0);
1371 }
1372 else
1373 {
1374 return toChop;
1375 }
1376 }
1377 }
1378
1379 return {};
1380 }
1381 else
1382 {
1383 for (auto itm : getChordProgression())
1384 progression.add (new ProgressionItem (*this, itm->state, true));
1385
1386 return {};
1387 }
1388}
1389
1390void PatternGenerator::clearProgression()
1391{
1392 auto progression = state.getChildWithName (IDs::PROGRESSION);
1393
1394 auto& um = clip.edit.getUndoManager();
1395
1396 progression.removeAllChildren (&um);
1397}
1398
1399void PatternGenerator::insertChordIntoProgression (int idx, juce::String chordName)
1400{
1401 auto progression = state.getChildWithName (IDs::PROGRESSION);
1402
1403 if (progression.getNumChildren() < maxChords)
1404 {
1405 auto& um = clip.edit.getUndoManager();
1406
1407 if (isRoman (chordName))
1408 chordName = chordName.toLowerCase().retainCharacters ("iv7");
1409
1410 juce::ValueTree item (IDs::PROGRESSIONITEM);
1411 item.setProperty (IDs::name, chordName, &um);
1412
1413 progression.addChild (item, idx, &um);
1414 }
1415}
1416
1417void PatternGenerator::insertChordIntoProgression (int idx, juce::String chordName, juce::String pitches)
1418{
1419 auto progression = state.getChildWithName (IDs::PROGRESSION);
1420
1421 if (progression.getNumChildren() < maxChords)
1422 {
1423 auto& um = clip.edit.getUndoManager();
1424
1425 if (isRoman (chordName))
1426 chordName = chordName.toLowerCase().retainCharacters ("iv7");
1427
1428 auto item = createValueTree (IDs::PROGRESSIONITEM,
1429 IDs::name, chordName,
1430 IDs::pitches, pitches);
1431
1432 progression.addChild (item, idx, &um);
1433 }
1434}
1435
1436void PatternGenerator::moveChordInProgression(int srcIdx, int dstIdx)
1437{
1438 auto progression = state.getChildWithName (IDs::PROGRESSION);
1439 auto& um = clip.edit.getUndoManager();
1440 auto itm = progression.getChild (srcIdx);
1441 progression.removeChild (srcIdx, &um);
1442
1443 if (dstIdx > srcIdx)
1444 progression.addChild (itm, dstIdx - 1, &um);
1445 else
1446 progression.addChild (itm, dstIdx, &um);
1447}
1448
1449juce::StringArray PatternGenerator::getArpStyles()
1450{
1452 std::string base;
1453
1454 for (int i = 0; i < arpSteps; i++)
1455 base += std::to_string (i);
1456
1457 do
1458 {
1459 res.add (base);
1460 } while (std::next_permutation (base.begin(), base.end()));
1461
1462 if (arpUpDown)
1463 {
1464 for (int i = 0; i < res.size(); i++)
1465 {
1466 auto s = res[i];
1467 auto suffix = s.substring (1, s.length() - 1);
1468
1469 for (int j = suffix.length() - 1; j >= 0; j--)
1470 s += suffix[j];
1471
1472 res.set (i, s);
1473 }
1474 }
1475
1476 return res;
1477}
1478
1479void PatternGenerator::clearPattern()
1480{
1482
1483 auto um = clip.getUndoManager();
1484 if (auto mc = getMidiClip())
1485 mc->getSequence().clear (um);
1486}
1487
1488void PatternGenerator::generatePattern()
1489{
1490 if (auto mc = getMidiClip())
1491 {
1492 if (auto at = mc->getAudioTrack())
1493 at->injectLiveMidiMessage (juce::MidiMessage::allNotesOff (mc->getMidiChannel().getChannelNumber()),
1494 MidiMessageArray::notMPE);
1495
1496 switch (mode)
1497 {
1498 case Mode::off: clearPattern(); clearHash(); break;
1499 case Mode::arpeggio: generateArpPattern(); updateHash(); break;
1500 case Mode::chords: generateChordPattern(); updateHash(); break;
1501 case Mode::bass: generateBassPattern(); updateHash(); break;
1502 case Mode::melody: generateMelodyPattern(); updateHash(); break;
1503 }
1504 }
1505}
1506
1507void PatternGenerator::generateArpPattern()
1508{
1509 auto mc = getMidiClip();
1510
1511 if (mc == nullptr)
1512 return;
1513
1514 clearPattern();
1515
1517
1518 juce::OwnedArray<ProgressionItem> progressionItems;
1519 auto progressionOffset = getFlattenedChordProgression (progressionItems);
1520
1521 auto progressionLength = progressionItems.size();
1522
1523 if (progressionLength == 0)
1524 return;
1525
1526 auto um = mc->getUndoManager();
1527 auto& sequence = mc->getSequence();
1528
1529 int progressionCur = 0;
1530 int stepCur = 0;
1531
1532 // Convert the style string into array of ints
1533 juce::Array<int> styleValues;
1534
1535 for (int i = 0; i < arpStyle.get().length(); i++)
1536 styleValues.add (arpStyle.get().substring (i, i + 1).getIntValue());
1537
1538 const float lengthFactor = gate / 100.0f;
1539 const auto noteLengthBeat = BeatDuration::fromBeats (arpPatternLength / styleValues.size());
1540
1541 // clear offset before generating clip
1542 mc->setOffset ({});
1543
1544 // Loop over the length of the clip in increments of note length
1545 auto stepLengthLeft = progressionItems.getFirst()->lengthInBeats.get();
1546 const auto clipLength = toPosition (mc->isLooping() ? mc->getLoopLengthBeats() : mc->getLengthInBeats() + mc->getOffsetInBeats());
1547 auto curBeat = BeatPosition::fromBeats (-progressionOffset.inBeats());
1548 bool stepStart = true;
1549 int panic = 1000;
1550
1551 while (curBeat < clipLength)
1552 {
1553 if (panic-- == 0)
1554 break;
1555
1556 auto stepLength = std::min (stepLengthLeft, noteLengthBeat);
1557
1558 auto scale = getScaleAtBeat (curBeat);
1559 auto steps = scale.getSteps (3);
1560
1561 if (auto progressionItem = progressionItems[progressionCur])
1562 {
1563 if (progressionItem->isValid())
1564 {
1565 const int chordRoot = progressionItem->getRootNote (getNoteAtBeat (curBeat), scale);
1566 const int octaveOffset = (octave + progressionItem->octave - 1) * 12;
1567
1568 auto chord = progressionItem->getChord (scale);
1569 auto intervals = chord.getSteps (progressionItem->inversion);
1570
1571 for (auto v : chord.getSteps (progressionItem->inversion)) intervals.add (v + 12);
1572 for (auto v : chord.getSteps (progressionItem->inversion)) intervals.add (v + 24);
1573
1574 // find which note in the chord we are currently using and increment by that amount
1575 const int stepIndex = styleValues[stepCur];
1576 const int note = chordRoot + intervals[stepIndex] + octaveOffset;
1577
1578 addNote (sequence, note, curBeat, stepLength * lengthFactor, int (velocity / 100.0f * 127),
1579 mc->edit.engine.getEngineBehaviour().getDefaultNoteColour(), um);
1580
1581 // if we are at first beat of a new stage in the progression, play the root note if wanted
1582 if (stepStart && arpPlayRoot)
1583 {
1584 stepStart = false;
1585
1586 const int rootNote = chordRoot + octaveOffset - 12;
1587 addNote (sequence, rootNote, curBeat, stepLengthLeft, int (velocity / 100.0f * 127),
1588 mc->edit.engine.getEngineBehaviour().getDefaultNoteColour(), um);
1589 }
1590 }
1591
1592 // increment current note in chord, then incremeent position in progression
1593 stepLengthLeft = stepLengthLeft - stepLength;
1594 stepCur++;
1595
1596 if (stepCur >= styleValues.size())
1597 stepCur = 0;
1598
1599 if (stepLengthLeft < 0.0001_bd)
1600 {
1601 stepCur = 0;
1602 stepStart = true;
1603 progressionCur++;
1604
1605 if (progressionCur >= progressionLength)
1606 progressionCur = 0;
1607
1608 stepLengthLeft = progressionItems[progressionCur]->lengthInBeats;
1609 }
1610
1611 curBeat = curBeat + stepLength;
1612 }
1613 else
1614 {
1616 return;
1617 }
1618 }
1619}
1620
1622{
1623 ChordNote (BeatPosition start_, BeatDuration length_, float velocity_)
1624 : start (start_), length (length_), velocity (velocity_) {}
1625
1626 BeatPosition start;
1627 BeatDuration length;
1628 float velocity = 0;
1629};
1630
1631void PatternGenerator::generateChordPattern()
1632{
1633 auto mc = getMidiClip();
1634 if (mc == nullptr)
1635 return;
1636
1637 clearPattern();
1638
1640
1641 juce::OwnedArray<ProgressionItem> progressionItems;
1642 auto progressionOffset = getFlattenedChordProgression (progressionItems);
1643
1644 const int progressionLength = progressionItems.size();
1645 if (progressionLength == 0)
1646 return;
1647
1649
1650 // unpack the chord pattern
1651 auto pattern = getChordPattern();
1652
1653 if (pattern.isValid())
1654 {
1655 for (int i = 0; i < pattern.getNumChildren(); i++)
1656 {
1657 notes.add ({});
1658
1659 auto bar = pattern.getChild (i);
1660 const int barLen = std::max (1, int (bar.getProperty (IDs::length, 4)));
1661 const int end = static_cast<int> (getMaximumChordLength().inBeats() / barLen);
1662
1663 for (int j = 0; j < bar.getNumChildren(); j++)
1664 {
1665 auto c = bar.getChild (j);
1666
1667 for (int k = 0; k < end; k++)
1668 notes.getReference (i).add (ChordNote (BeatPosition::fromBeats (static_cast<double> (c.getProperty (IDs::start)) + barLen * k),
1669 BeatDuration::fromBeats (static_cast<double> (c.getProperty (IDs::length))),
1670 c.getProperty (IDs::velocity)));
1671 }
1672 }
1673 }
1674 else
1675 {
1676 notes.add ({});
1677 notes.getReference (0).add (ChordNote ({}, getMaximumChordLength(), 127.0f));
1678 }
1679
1680 auto um = mc->getUndoManager();
1681 MidiList& sequence = mc->getSequence();
1682
1683 int progressionCur = 0; // Current step in the chord progression
1684 int patternCur = 0; // Current step in the rhythm pattern
1685
1686 const float lengthFactor = gate / 100.0f;
1687
1688 // clear offset before generating clip
1689 mc->setOffset ({});
1690
1691 // Loop over the length of the clip in increments of pattern length
1692 auto curBeat = BeatPosition::fromBeats (-progressionOffset.inBeats());
1693 const auto clipLength = toPosition (mc->isLooping() ? mc->getLoopLengthBeats()
1694 : mc->getLengthInBeats() + mc->getOffsetInBeats());
1695 int panic = 1000;
1696
1697 while (curBeat < clipLength)
1698 {
1699 if (panic-- == 0)
1700 break;
1701
1702 auto scale = getScaleAtBeat (curBeat);
1703
1704 if (auto progressionItem = progressionItems[progressionCur])
1705 {
1706 const auto patternLength = progressionItem->lengthInBeats.get();
1707
1708 if (progressionItem->isValid())
1709 {
1710 const int chordRoot = progressionItem->getRootNote (getNoteAtBeat (curBeat), scale);
1711 const int octaveOffset = (octave + progressionItem->octave - 1) * 12;
1712 const int rootNote = chordRoot + octaveOffset;
1713
1714 Chord chord = progressionItem->getChord (scale);
1715
1716 auto steps = chord.getSteps (progressionItem->inversion);
1717
1718 if (octaveDown)
1719 steps.add (steps.getFirst() - 12);
1720
1721 if (octaveUp)
1722 steps.add (steps.getFirst() + 12);
1723
1724 if (spread)
1725 {
1726 steps.add (steps[1] + 12);
1727 steps.remove (1);
1728 }
1729
1730 for (int step : steps)
1731 {
1732 for (const ChordNote& chordNote : notes[patternCur])
1733 {
1734 const int note = rootNote + step;
1735
1736 if (chordNote.start < toPosition (patternLength))
1737 {
1738 addNote (sequence, note, curBeat + toDuration (chordNote.start),
1739 std::min (chordNote.length * lengthFactor, patternLength - toDuration (chordNote.start)),
1740 int (velocity / 100.0f * chordNote.velocity),
1741 mc->edit.engine.getEngineBehaviour().getDefaultNoteColour(), um);
1742 }
1743 }
1744 }
1745 }
1746
1747 curBeat = curBeat + patternLength;
1748
1749 progressionCur++;
1750 if (progressionCur >= progressionLength)
1751 progressionCur = 0;
1752
1753 patternCur++;
1754 if (patternCur >= notes.size())
1755 patternCur = 0;
1756 }
1757 else
1758 {
1760 return;
1761 }
1762 }
1763}
1764
1765void PatternGenerator::generateMelodyPattern()
1766{
1767 auto mc = getMidiClip();
1768
1769 if (mc == nullptr)
1770 return;
1771
1773 clearPattern();
1774
1775 juce::OwnedArray<ProgressionItem> progressionItems;
1776 auto progressionOffset = getFlattenedChordProgression (progressionItems);
1777 auto progressionLength = progressionItems.size();
1778
1779 if (progressionLength == 0)
1780 return;
1781
1782 auto um = mc->getUndoManager();
1783 auto& sequence = mc->getSequence();
1784 auto& edit = mc->edit;
1785 auto& tempoSequence = edit.tempoSequence;
1786
1787 auto beatsPerWhole = tempoSequence.getTimeSigAt (mc->getPosition().getStart()).denominator.get();
1788 auto noteLengthBeat = melodyNoteLength.get() * beatsPerWhole;
1789
1791
1792 for (int i = 0; i < std::ceil (getMaximumChordLength() / noteLengthBeat) * 4; i++)
1793 notes.add (ChordNote (toPosition (noteLengthBeat * i),
1794 std::min (noteLengthBeat, getMaximumChordLength() - (noteLengthBeat * i)), 127.0f));
1795
1796 int progressionCur = 0; // Current step in the chord progression
1797
1798 auto lengthFactor = gate / 100.0f;
1799
1800 // clear offset before generating clip
1801 mc->setOffset ({});
1802
1803 // Loop over the length of the clip in increments of bar length
1804 auto curBeat = BeatPosition::fromBeats (-progressionOffset.inBeats());
1805 const auto clipLength = toPosition (mc->isLooping() ? mc->getLoopLengthBeats()
1806 : mc->getLengthInBeats() + mc->getOffsetInBeats());
1807 int panic = 1000;
1808
1809 while (curBeat < clipLength)
1810 {
1811 if (panic-- == 0)
1812 break;
1813
1814 auto scale = getScaleAtBeat (curBeat);
1815 auto steps = scale.getSteps (3);
1816
1817 if (auto progressionItem = progressionItems[progressionCur])
1818 {
1819 const auto patternLength = progressionItem->lengthInBeats.get();
1820
1821 if (progressionItem->isValid())
1822 {
1823 auto chordRoot = progressionItem->getRootNote (getNoteAtBeat (curBeat), scale);
1824 auto octaveOffset = (octave + progressionItem->octave - 1) * 12;
1825 auto rootNote = chordRoot + octaveOffset;
1826 auto chord = progressionItem->getChord (scale);
1827
1828 if (allNotes)
1829 {
1830 int notesAdded = 0;
1831 juce::Array<int> chordSteps;
1832
1833 for (auto s : chord.getSteps (0))
1834 {
1835 chordSteps.add (rootNote + s);
1836 chordSteps.add (rootNote + s + 12);
1837 }
1838
1839 chordSteps.sort();
1840
1841 for (int o = 0; o < 3; o++)
1842 {
1843 for (int step : scale.getSteps())
1844 {
1845 auto note1 = getNoteAtBeat (curBeat) + octaveOffset + step + o * 12;
1846
1847 if (notesAdded < 14 && note1 >= chordSteps.getFirst())
1848 {
1849 for (auto& chordNote : notes)
1850 {
1851 if (chordNote.start < toPosition (patternLength))
1852 {
1853 if (auto newNote = addNote (sequence, note1, curBeat + toDuration (chordNote.start),
1854 std::min (chordNote.length * lengthFactor,
1855 patternLength - toDuration (chordNote.start)),
1856 (int) (velocity / 100.0f * chordNote.velocity),
1857 mc->edit.engine.getEngineBehaviour().getDefaultNoteColour(), um))
1858 {
1859 newNote->setMute (true, um);
1860 newNote->setColour (chordSteps.contains (note1) ? 0 : 2, um);
1861 }
1862 }
1863 }
1864
1865 ++notesAdded;
1866 }
1867 }
1868 }
1869 }
1870 else
1871 {
1872 for (int step : chord.getSteps (progressionItem->inversion))
1873 {
1874 for (auto& chordNote : notes)
1875 {
1876 auto note1 = rootNote + step;
1877
1878 if (chordNote.start < toPosition (patternLength))
1879 if (auto newNote = addNote (sequence, note1, curBeat + toDuration (chordNote.start),
1880 std::min (chordNote.length * lengthFactor,
1881 patternLength - toDuration (chordNote.start)),
1882 (int) (velocity / 100.0f * chordNote.velocity),
1883 mc->edit.engine.getEngineBehaviour().getDefaultNoteColour(), um))
1884 newNote->setMute (true, um);
1885
1886
1887 auto note2 = rootNote + step + 12;
1888
1889 if (chordNote.start < toPosition (patternLength))
1890 if (auto newNote = addNote (sequence, note2, curBeat + toDuration (chordNote.start),
1891 std::min (chordNote.length * lengthFactor,
1892 patternLength - toDuration (chordNote.start)),
1893 (int) (velocity / 100.0f * chordNote.velocity),
1894 mc->edit.engine.getEngineBehaviour().getDefaultNoteColour(), um))
1895 newNote->setMute (true, um);
1896
1897 }
1898 }
1899 }
1900 }
1901
1902 curBeat = curBeat + patternLength;
1903
1904 if (++progressionCur >= progressionLength)
1905 progressionCur = 0;
1906 }
1907 else
1908 {
1910 return;
1911 }
1912 }
1913}
1914
1916{
1917 BeatPosition start;
1918 BeatDuration length;
1919 float velocity = 0;
1920 int pitch = 0;
1921 int octave = 0;
1922};
1923
1924void PatternGenerator::generateBassPattern()
1925{
1926 auto mc = getMidiClip();
1927
1928 if (mc == nullptr)
1929 return;
1930
1932 clearPattern();
1933
1934 juce::OwnedArray<ProgressionItem> progressionItems;
1935 auto progressionOffset = getFlattenedChordProgression (progressionItems);
1936 auto progressionLength = progressionItems.size();
1937
1938 if (progressionLength == 0)
1939 return;
1940
1942
1943 // Unpack the bass pattern
1944 auto pattern = getBassPattern();
1945
1946 if (pattern.isValid())
1947 {
1948 for (int i = 0; i < pattern.getNumChildren(); i++)
1949 {
1950 notes.add ({});
1951
1952 auto bar = pattern.getChild (i);
1953 auto barLen = std::max (1, int (bar.getProperty (IDs::length, 4)));
1954 const int end = static_cast<int> (getMaximumChordLength().inBeats() / barLen);
1955
1956 for (int j = 0; j < bar.getNumChildren(); j++)
1957 {
1958 auto c = bar.getChild (j);
1959
1960 for (int k = 0; k < end; k++)
1961 {
1962 notes.getReference (i).add ({ BeatPosition::fromBeats (static_cast<double> (c.getProperty (IDs::start)) + barLen * k),
1963 BeatDuration::fromBeats (static_cast<double> (c.getProperty (IDs::length))),
1964 c.getProperty (IDs::velocity),
1965 c.getProperty (IDs::pitch),
1966 c.getProperty (IDs::octave) });
1967 }
1968 }
1969 }
1970 }
1971 else
1972 {
1973 notes.add ({});
1974 notes.getReference (0).add ({ {}, getMaximumChordLength(), 127.0f, 0, 0 });
1975 }
1976
1977 auto um = mc->getUndoManager();
1978 auto& sequence = mc->getSequence();
1979
1980 int progressionCur = 0; // Current step in the chord progression
1981 int patternCur = 0; // Current step in the rhythm pattern
1982
1983 const float lengthFactor = gate / 100.0f;
1984
1985 // clear offset before generating clip
1986 mc->setOffset ({});
1987
1988 // Loop over the length of the clip in increments of pattern length
1989 auto curBeat = BeatPosition::fromBeats (-progressionOffset.inBeats());
1990 const auto clipLength = toPosition (mc->isLooping() ? mc->getLoopLengthBeats()
1991 : mc->getLengthInBeats() + mc->getOffsetInBeats());
1992 int panic = 1000;
1993
1994 while (curBeat < clipLength)
1995 {
1996 if (panic-- == 0)
1997 break;
1998
1999 auto scale = getScaleAtBeat (curBeat);
2000 juce::Array<int> steps = scale.getSteps (3);
2001
2002 if (auto progressionItem = progressionItems[progressionCur])
2003 {
2004 const auto patternLength = progressionItem->lengthInBeats.get();
2005
2006 if (progressionItem->isValid())
2007 {
2008 const int chordRoot = progressionItem->getRootNote (getNoteAtBeat (curBeat), scale);
2009 const int octaveOffset = (octave + progressionItem->octave - 1) * 12;
2010 const int rootNote = chordRoot + octaveOffset;
2011
2012 for (const BassNote& bassNote : notes[patternCur])
2013 {
2014 int pos = bassNote.pitch;
2015 int off = bassNote.octave * 12;
2016
2017 if (pos < scale.getSteps().size())
2018 {
2019 const int rootNoteIndex = steps.indexOf (chordRoot);
2020 const int note = rootNote + steps[rootNoteIndex + pos] - steps[rootNoteIndex] + off;
2021
2022 if (bassNote.start < toPosition (patternLength))
2023 {
2024 addNote (sequence, note, curBeat + toDuration (bassNote.start),
2025 std::min (bassNote.length * lengthFactor,
2026 patternLength - toDuration (bassNote.start)),
2027 (int) (velocity / 100.0f * bassNote.velocity),
2028 mc->edit.engine.getEngineBehaviour().getDefaultNoteColour(), um);
2029 }
2030 }
2031 }
2032 }
2033
2034 curBeat = curBeat + patternLength;
2035
2036 progressionCur++;
2037 if (progressionCur >= progressionItems.size())
2038 progressionCur = 0;
2039
2040 patternCur++;
2041 if (patternCur >= notes.size())
2042 patternCur = 0;
2043 }
2044 else
2045 {
2047 return;
2048 }
2049 }
2050}
2051
2052void PatternGenerator::playGuideChord (int idx) const
2053{
2054 if (auto mc = getMidiClip())
2055 {
2056 auto progressionItems = getChordProgression();
2057
2058 if (idx < 0 || idx >= progressionItems.size())
2059 {
2061 return;
2062 }
2063
2064 BeatPosition curBeat;
2065
2066 for (int i = 0; i < idx; i++)
2067 curBeat = curBeat + progressionItems[i]->lengthInBeats;
2068
2069 auto scale = getScaleAtBeat (curBeat);
2070 auto steps = scale.getSteps (3);
2071
2072 if (auto progressionItem = progressionItems[idx])
2073 {
2074 const int chordRoot = progressionItem->getRootNote (getNoteAtBeat (curBeat), scale);
2075 const int octaveOffset = (octave + progressionItem->octave - 1) * 12;
2076 const int rootNote = chordRoot + octaveOffset;
2077
2078 Chord chord = progressionItem->getChord (scale);
2079
2080 if (auto at = mc->getAudioTrack())
2081 {
2082 bool stop = true;
2083
2084 for (int step : chord.getSteps (progressionItem->inversion))
2085 {
2086 auto note = rootNote + step;
2087
2088 if (note >= 0 && note < 128)
2089 {
2090 at->playGuideNote (note, mc->getMidiChannel(),
2091 (int) (velocity / 100 * 127), stop);
2092 stop = false;
2093 }
2094 }
2095 }
2096 }
2097 }
2098}
2099
2100void PatternGenerator::updateHash()
2101{
2102 if (auto mc = getMidiClip())
2103 patternHash = hashNotes (mc->getSequence(), 2);
2104 else
2105 patternHash = 0;
2106}
2107
2108void PatternGenerator::clearHash()
2109{
2110 patternHash = 0;
2111}
2112
2113HashCode PatternGenerator::hashNotes (MidiList& sequence, int version)
2114{
2115 // Version 1 of this hash had a bug where just changing mute would
2116 // generate hash collisions
2117 HashCode hash = sequence.getNumNotes() + 1;
2118
2119 for (auto note : sequence.getNotes())
2120 {
2121 hash ^= static_cast<HashCode> (note->getNoteNumber() * 31)
2122 ^ static_cast<HashCode> (note->getStartBeat().inBeats() * 73)
2123 ^ static_cast<HashCode> (note->getLengthBeats().inBeats() * 233)
2124 ^ static_cast<HashCode> (note->getColour() * 467)
2125 ^ static_cast<HashCode> (note->isMute() ? 877 : 947)
2126 ^ static_cast<HashCode> (note->getVelocity() * 3083);
2127
2128 if (version > 1)
2129 hash = static_cast<HashCode> (core::hash (static_cast<size_t> (hash), 7));
2130 }
2131
2132 return hash;
2133}
2134
2135void PatternGenerator::setAutoUpdate (bool on)
2136{
2137 if (on)
2138 {
2139 autoUpdate = true;
2140 generatePattern();
2141 }
2142 else
2143 {
2144 autoUpdate = false;
2145 }
2146}
2147
2148bool PatternGenerator::getAutoUpdate()
2149{
2150 if (auto mc = getMidiClip())
2151 return autoUpdate && patternHash == hashNotes (mc->getSequence(), 2);
2152
2153 return false;
2154}
2155
2156void PatternGenerator::refreshPatternIfNeeded()
2157{
2158 if (autoUpdateManager != nullptr)
2159 autoUpdateManager->triggerAsyncUpdate();
2160}
2161
2162
2163//==============================================================================
2164// Krumhansl-Schmuckler Key-Finding Algorithm.
2165juce::Array<KeyResult> determineKeyOfNotes (const juce::Array<MidiNote*>& notes)
2166{
2167 juce::Array<KeyResult> results;
2168
2169 double durations[12] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
2170
2171 for (auto n : notes)
2172 durations[n->getNoteNumber() % 12] += n->getLengthBeats().inBeats();
2173
2174 double major[12] = { 6.35, 2.23, 3.48, 2.33, 4.38, 4.09, 2.52, 5.19, 2.39, 3.66, 2.29, 2.88 };
2175 double minor[12] = { 6.33, 2.68, 3.52, 5.38, 2.60, 3.53, 2.54, 4.75, 3.98, 2.69, 3.34, 3.17 };
2176
2177 double xAveMajor = 0, xAveMinor = 0, yAve = 0;
2178
2179 for (int i = 0; i < 12; i++)
2180 {
2181 xAveMajor += major[i];
2182 xAveMinor += minor[i];
2183 yAve += durations[i];
2184 }
2185
2186 xAveMajor /= 12;
2187 xAveMinor /= 12;
2188 yAve /= 12;
2189
2190 for (int key = 0; key < 12; key++)
2191 {
2192 double s1Major = 0, s2Major = 0, s3Major = 0;
2193 double s1Minor = 0, s2Minor = 0, s3Minor = 0;
2194
2195 for (int i = 0; i < 12; i++)
2196 {
2197 double xiMajor = major[(i - key + 12) % 12];
2198 double xiMinor = minor[(i - key + 12) % 12];
2199
2200 s1Major += (xiMajor - xAveMajor) * (durations[i] - yAve);
2201 s2Major += (xiMajor - xAveMajor) * (xiMajor - xAveMajor);
2202 s3Major += (durations[i] - yAve) * (durations[i] - yAve);
2203
2204 s1Minor += (xiMinor - xAveMinor) * (durations[i] - yAve);
2205 s2Minor += (xiMinor - xAveMinor) * (xiMinor - xAveMinor);
2206 s3Minor += (durations[i] - yAve) * (durations[i] - yAve);
2207 }
2208
2209 if (s2Major * s3Major > 0)
2210 {
2211 double rMajor = s1Major / std::sqrt (s2Major * s3Major);
2212 double rMinor = s1Minor / std::sqrt (s2Minor * s3Minor);
2213
2214 results.add ({ rMajor, key, Scale::major });
2215 results.add ({ rMinor, key, Scale::minor });
2216 }
2217 }
2218
2219 results.sort();
2220
2221 return results;
2222}
2223
2224}} // namespace tracktion { inline namespace engine
T begin(T... args)
T ceil(T... args)
int size() const noexcept
void remove(int indexToRemove)
void insert(int indexToInsertAt, ParameterType newElement)
ElementType getFirst() const noexcept
int indexOf(ParameterType elementToLookFor) const
void add(const ElementType &newElement)
ElementType removeAndReturn(int indexToRemove)
bool contains(ParameterType elementToLookFor) const
ElementType & getReference(int index) noexcept
Type get() const noexcept
bool isValid() const noexcept
static MidiMessage allNotesOff(int channel) noexcept
static String getMidiNoteName(int noteNumber, bool useSharps, bool includeOctaveNumber, int octaveNumForMiddleC)
int size() const noexcept
static StringArray fromTokens(StringRef stringToTokenise, bool preserveQuotedStrings)
Array< String > strings
int size() const noexcept
void add(String stringToAdd)
void set(int index, String newString)
int length() const noexcept
int indexOf(StringRef textToLookFor) const noexcept
String retainCharacters(StringRef charactersToRetain) const
bool contains(StringRef text) const noexcept
static String charToString(juce_wchar character)
bool isNotEmpty() const noexcept
void removeChild(const ValueTree &child, UndoManager *undoManager)
bool isValid() const noexcept
ValueTree & setProperty(const Identifier &name, const var &newValue, UndoManager *undoManager)
void addChild(const ValueTree &child, int index, UndoManager *undoManager)
ValueTree getChildWithName(const Identifier &type) const
void removeListener(Listener *listener)
A clip in an edit.
TimePosition getTimeOfContentBeat(BeatPosition) const
Returns time of a beat number.
juce::UndoManager * getUndoManager() const
Returns the UndoManager.
PitchSequence pitchSequence
The global PitchSequence of this Edit.
juce::UndoManager & getUndoManager() noexcept
Returns the juce::UndoManager used for this Edit.
void setChordProgressionFromChordNames(juce::StringArray progression)
Sets a chord progression using chord roman numerals.
BeatPosition getStartBeat() const
Returns the start beat in the Edit of this item.
BeatDuration getLengthInBeats() const
Returns the duration in beats the of this item.
T end(T... args)
T is_pointer_v
#define TRANS(stringLiteral)
#define jassert(expression)
#define JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(className)
#define jassertfalse
typedef int
T max(T... args)
T min(T... args)
Type jlimit(Type lowerLimit, Type upperLimit, Type valueToConstrain) noexcept
constexpr TimePosition toPosition(TimeDuration)
Converts a TimeDuration to a TimePosition.
constexpr TimeDuration toDuration(TimePosition)
Converts a TimePosition to a TimeDuration.
size_t hash(size_t seed, const T &v)
Hashes a type with a given seed and returns the new hash value.
T next_permutation(T... args)
T sqrt(T... args)
Represents a duration in beats.
constexpr double inBeats() const
Returns the position as a number of beats.
Represents a position in beats.
time
T to_string(T... args)
#define CRASH_TRACER
This macro adds the current location to a stack which gets logged if a crash happens.