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

« « « Anklang Documentation
Loading...
Searching...
No Matches
tracktion_StepClip.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
14struct StepClip::ChannelList : public ValueTreeObjectList<StepClip::Channel>
15{
17 : ValueTreeObjectList<Channel> (v), clip (c)
18 {
19 rebuildObjects();
20 }
21
22 ~ChannelList() override
23 {
24 freeObjects();
25 }
26
27 bool isSuitableType (const juce::ValueTree& v) const override
28 {
29 return v.hasType (IDs::CHANNEL);
30 }
31
32 Channel* createNewObject (const juce::ValueTree& v) override
33 {
34 return new Channel (clip, v);
35 }
36
37 void deleteObject (Channel* c) override
38 {
39 delete c;
40 }
41
42 void newObjectAdded (Channel*) override {}
43 void objectRemoved (Channel*) override {}
44 void objectOrderChanged() override {}
45
46 StepClip& clip;
47
49};
50
51//==============================================================================
52StepClip::StepClip (const juce::ValueTree& v, EditItemID id, ClipOwner& targetParent)
53 : Clip (v, targetParent, id, Type::step)
54{
55 auto um = getUndoManager();
56 channelList = std::make_unique<ChannelList> (*this, state.getOrCreateChildWithName (IDs::CHANNELS, um));
57 repeatSequence.referTo (state, IDs::repeatSequence, um);
58 level->dbGain.referTo (state, IDs::volDb, um, 0.0f);
59 level->mute.referTo (state, IDs::mute, um, false);
60
61 loopStartBeats.referTo (state, IDs::loopStartBeats, um, 0_bp);
62 loopLengthBeats.referTo (state, IDs::loopLengthBeats, um, 0_bd);
63 originalLength.referTo (state, IDs::originalLength, um, 0_bd);
64
65 useClipLaunchQuantisation.referTo (state, IDs::useClipLaunchQuantisation, um);
66
67 if (getChannels().isEmpty())
68 {
69 for (int i = defaultNumChannels; --i >= 0;)
70 insertNewChannel (-1);
71
72 auto getDefaultNoteNumber = [] (int chanNum)
73 {
74 switch (chanNum)
75 {
76 case 0: return 36;
77 case 1: return 38;
78 case 2: return 42;
79 case 3: return 46;
80 case 4: return 39;
81 case 5: return 45;
82 case 6: return 50;
83 case 7: return 51;
84 default: return 60 + chanNum - 4;
85 }
86 };
87
88 for (int i = 0; i < std::min (getChannels().size(), 8); ++i)
89 {
90 if (Channel* c = getChannels()[i])
91 {
92 c->noteNumber = getDefaultNoteNumber (i);
93 c->name = juce::MidiMessage::getRhythmInstrumentName (c->noteNumber);
94 }
95 }
96 }
97
98 createDefaultPatternIfEmpty();
99 updatePatternList();
100}
101
102StepClip::~StepClip()
103{
104 notifyListenersOfDeletion();
105 channelList = nullptr;
106}
107
109{
110 if (auto other = dynamic_cast<StepClip*> (c))
111 {
112 Clip::cloneFrom (other);
113
114 repeatSequence .setValue (other->repeatSequence, nullptr);
115 level->dbGain .setValue (other->level->dbGain, nullptr);
116 level->mute .setValue (other->level->mute, nullptr);
117
118 auto chans = state.getChildWithName (IDs::CHANNELS);
119 auto patterns = state.getChildWithName (IDs::PATTERNS);
120
121 copyValueTree (chans, other->state.getChildWithName (IDs::CHANNELS), nullptr);
122 copyValueTree (patterns, other->state.getChildWithName (IDs::PATTERNS), nullptr);
123
124 state.setProperty (IDs::sequence, other->state[IDs::sequence], nullptr);
125
127 }
128}
129
130const StepClip::PatternInstance::Ptr StepClip::getPatternInstance (int i, bool repeatSeq) const
131{
132 auto size = patternInstanceList.size();
133
134 return patternInstanceList[repeatSeq ? (i % size)
135 : std::min (size - 1, i)];
136}
137
138void StepClip::updatePatternList()
139{
140 auto newSequence = juce::StringArray::fromTokens (state[IDs::sequence].toString(), ",", {});
141
142 PatternArray newArray (patternInstanceList);
143 newArray.removeRange (newSequence.size(), newArray.size());
144
145 for (int i = 0; i < newSequence.size(); ++i)
146 if (newArray[i] == nullptr || newArray.getUnchecked (i)->patternIndex != newSequence[i].getIntValue())
147 newArray.set (i, new PatternInstance (*this, newSequence[i].getIntValue()));
148
149 patternInstanceList.swapWith (newArray);
150
151 // make sure that removed items get deselected
152 for (auto p : patternInstanceList)
153 newArray.removeObject (p);
154
155 for (auto p : newArray)
156 p->deselect();
157
159}
160
161void StepClip::valueTreePropertyChanged (juce::ValueTree& v, const juce::Identifier& i)
162{
163 Clip::valueTreePropertyChanged (v, i);
164
165 if (i == IDs::sequence || i == IDs::repeatSequence)
166 updatePatternList();
167
168 changed();
169
170 if (v.hasType (IDs::PATTERN))
171 {
172 for (auto patternInstance : patternInstanceList)
173 if (v == patternInstance->getPattern().state)
174 patternInstance->changed();
175 }
176 else if (v.hasType (IDs::CHANNEL))
177 {
178 for (auto channel : *channelList)
179 if (v == channel->state)
180 channel->changed();
181 }
182}
183
184void StepClip::valueTreeChildAdded (juce::ValueTree& p, juce::ValueTree& c)
185{
187
188 if (p.hasType (IDs::PATTERN))
189 changed();
190}
191
192void StepClip::valueTreeChildRemoved (juce::ValueTree& p, juce::ValueTree& c, int oldIndex)
193{
194 Clip::valueTreeChildRemoved (p, c, oldIndex);
195
196 if (p.hasType (IDs::PATTERN))
197 changed();
198}
199
200void StepClip::valueTreeChildOrderChanged (juce::ValueTree& p, int o, int n)
201{
203
204 changed();
205}
206
207//==============================================================================
209{
210 return canContainMIDI (co);
211}
212
214{
215 return TRANS("Step Clip") + " - \"" + getName() + "\"";
216}
217
218//==============================================================================
219juce::Array<BeatPosition> StepClip::getBeatTimesOfPatternStarts() const
220{
222
223 auto& tempoSequence = edit.tempoSequence;
224 auto pos = getPosition();
225
226 auto patternStartBeats = tempoSequence.toBeats (pos.getStartOfSource());
227 auto endBeat = tempoSequence.toBeats (pos.getEnd());
228 auto ratio = 1.0 / speedRatio;
229 bool repeatSeq = repeatSequence;
230
231 for (int i = 0; ; ++i)
232 {
233 starts.add (patternStartBeats);
234
235 auto numBeats = BeatDuration::fromBeats (4.0);
236
237 if (auto p = getPatternInstance (i, repeatSeq))
238 {
239 auto pattern = p->getPattern();
240 numBeats = pattern.getNoteLength() * pattern.getNumNotes() * ratio;
241 }
242
243 patternStartBeats = patternStartBeats + numBeats;
244
245 if (patternStartBeats >= endBeat)
246 break;
247 }
248
249 return starts;
250}
251
252BeatPosition StepClip::getStartBeatOf (PatternInstance* instance)
253{
254 if (instance == nullptr
255 || (instance != nullptr && ! patternInstanceList.contains (instance)))
256 {
258 return BeatPosition();
259 }
260
261 auto index = patternInstanceList.indexOf (instance);
262 return getBeatTimesOfPatternStarts()[index];
263}
264
265BeatPosition StepClip::getEndBeatOf (PatternInstance* instance)
266{
267 if (instance == nullptr
268 || (instance != nullptr && ! patternInstanceList.contains (instance)))
269 {
271 return BeatPosition();
272 }
273
274 auto index = patternInstanceList.indexOf (instance) + 1;
275 return getBeatTimesOfPatternStarts()[index];
276}
277
278void StepClip::resizeClipForPatternInstances()
279{
280 if (patternInstanceList.getLast().get() != nullptr)
281 {
282 auto end = -getOffsetInBeats();
283
284 auto ratio = 1.0 / speedRatio;
285
286 for (auto p : patternInstanceList)
287 {
288 auto pattern = p->getPattern();
289 end = end + pattern.getNoteLength() * pattern.getNumNotes() * ratio;
290 }
291
292 if (end > getLengthInBeats())
293 {
294 auto& ts = edit.tempoSequence;
295 auto pos = getPosition();
296 auto startBeat = ts.toBeats (pos.getStart());
297 auto endBeat = startBeat + end;
298
299 setEnd (ts.toTime (endBeat), false);
300 }
301 }
302}
303
304int StepClip::getBeatsPerBar()
305{
306 return edit.tempoSequence.getTimeSigAt (getPosition().getStart()).numerator;
307}
308
309void StepClip::generateMidiSequenceForChannels (juce::MidiMessageSequence& result,
310 bool convertToSeconds, const Pattern& pattern,
311 BeatPosition startBeat, BeatPosition clipStartBeat,
312 BeatPosition clipEndBeat, const TempoSequence& tempoSequence)
313{
315
316 auto pos = getPosition();
317 auto ratio = 1.0 / speedRatio;
318
319 auto& gtm = edit.engine.getGrooveTemplateManager();
320 auto numChannels = getChannels().size();
321 auto numNotes = pattern.getNumNotes();
322 auto noteLength = pattern.getNoteLength();
323
325 caches.ensureStorageAllocated (numChannels);
326
327 for (int f = 0; f < numChannels; ++f)
328 caches.add (new Pattern::CachedPattern (pattern, f));
329
330 for (int i = 0; i < numNotes; ++i)
331 {
332 auto currentBeatEnd = startBeat + (noteLength * ratio);
333
334 if (startBeat >= clipEndBeat)
335 break;
336
337 if (currentBeatEnd > clipStartBeat)
338 {
339 for (int f = 0; f < numChannels; ++f)
340 {
341 const auto* cache = caches.getUnchecked (f);
342
343 if (cache == nullptr)
344 {
346 continue;
347 }
348
349 if (cache->getNote (i))
350 {
351 auto prob = cache->getProbability (i);
352
354 continue;
355
356 auto gate = cache->getGate (i);
357 auto beatEnd = startBeat + (noteLength * gate * ratio);
358 jassert (gate > 0.0);
359
360 auto start = std::max (clipStartBeat, startBeat);
361 auto end = std::min (clipEndBeat - 0.0001_bd, beatEnd);
362
363 double eventStart = start.inBeats();
364 double eventEnd = end.inBeats();
365
366 if (end > (start + 0.0001_bd))
367 {
368 auto& c = *getChannels().getUnchecked (f);
369
370 if (convertToSeconds)
371 {
372 const auto startTime = tempoSequence.toTime (start) - toDuration (pos.getStart());
373 const auto endTime = tempoSequence.toTime (end) - toDuration (pos.getStart());
374
375 if (auto gt = gtm.getTemplateByName (c.grooveTemplate))
376 {
377 eventStart = gt->editTimeToGroovyTime (startTime, c.grooveStrength, edit).inSeconds();
378 eventEnd = gt->editTimeToGroovyTime (endTime, c.grooveStrength, edit).inSeconds();
379 }
380 else
381 {
382 eventStart = startTime.inSeconds();
383 eventEnd = endTime.inSeconds();
384 }
385 }
386 else
387 {
388 if (auto gt = gtm.getTemplateByName (c.grooveTemplate))
389 {
390 eventStart = gt->beatsTimeToGroovyTime (start, c.grooveStrength).inBeats();
391 eventEnd = gt->beatsTimeToGroovyTime (end, c.grooveStrength).inBeats();
392 }
393 }
394
395 const float channelVelScale = c.noteValue / 127.0f;
396 const float vel = cache->getVelocity (i) / 127.0f;
397 jassert (c.channel.get().isValid());
398 auto chan = c.channel.get().getChannelNumber();
399 result.addEvent (juce::MidiMessage::noteOn (chan, c.noteNumber, vel * channelVelScale), eventStart);
400 result.addEvent (juce::MidiMessage::noteOff (chan, c.noteNumber, (uint8_t) juce::jlimit (0, 127, c.noteValue.get())), eventEnd);
401 }
402 }
403 }
404 }
405
406 startBeat = currentBeatEnd;
407 }
408}
409
411 bool convertToSeconds,
412 PatternInstance* instance)
413{
414 if (instance != nullptr && ! patternInstanceList.contains (instance))
415 {
417 return;
418 }
419
420 auto& tempoSequence = edit.tempoSequence;
421 auto pos = getPosition();
422
423 auto clipStartBeat = tempoSequence.toBeats (pos.getStart());
424 auto clipEndBeat = tempoSequence.toBeats (pos.getEnd());
425 bool repeatSeq = repeatSequence;
426 auto starts = getBeatTimesOfPatternStarts();
427
428 for (int i = 0; i < starts.size(); ++i)
429 {
430 if (auto p = getPatternInstance (i, repeatSeq))
431 {
432 if (instance != nullptr && p.get() != instance)
433 continue;
434
435 auto startBeat = starts.getUnchecked (i);
436
437 generateMidiSequenceForChannels (result, convertToSeconds, p->getPattern(), startBeat,
438 clipStartBeat, clipEndBeat, tempoSequence);
439
440 if (instance != nullptr)
441 {
442 result.addTimeToMessages (-tempoSequence.toTime (clipStartBeat + toDuration (startBeat)).inSeconds());
443 break;
444 }
445 }
446 }
447
448 result.sort();
449 result.updateMatchedPairs();
450}
451
453{
454 if (instance != nullptr && ! patternInstanceList.contains (instance))
455 {
457 return {};
458 }
459
461 auto& tempoSequence = edit.tempoSequence;
462 const auto pos = getPosition();
463 const auto clipBeatRange = tempoSequence.toBeats (pos.time);
464
465 const bool repeatSeq = repeatSequence;
466 const auto starts = getBeatTimesOfPatternStarts();
467
468 for (int i = 0; i < starts.size(); ++i)
469 {
470 if (auto p = getPatternInstance (i, repeatSeq))
471 {
472 if (instance != nullptr && p.get() != instance)
473 continue;
474
475 auto startBeat = starts.getUnchecked (i);
476
477 generateMidiSequenceForChannels (result, timeBase == MidiList::TimeBase::seconds,
478 p->getPattern(), startBeat,
479 clipBeatRange.getStart(), clipBeatRange.getEnd(), tempoSequence);
480
481 if (instance != nullptr && timeBase != MidiList::TimeBase::beatsRaw)
482 {
483 result.addTimeToMessages (-tempoSequence.toTime (clipBeatRange.getStart() + toDuration (startBeat)).inSeconds());
484 break;
485 }
486 }
487 }
488
489 result.sort();
490 result.updateMatchedPairs();
491
492 return result;
493}
494
495//==============================================================================
497{
498 return juce::Colours::red.withHue (3.0f / 9.0f);
499}
500
501LiveClipLevel StepClip::getLiveClipLevel()
502{
503 return { level };
504}
505
506//==============================================================================
508{
509 SelectedChannelIndex (int i, bool selected) noexcept
510 : index (i), wasSelected (selected) {}
511
512 const int index;
513 const bool wasSelected;
514
515 SelectedChannelIndex() = delete;
517};
518
519const juce::Array<StepClip::Channel*>& StepClip::getChannels() const noexcept
520{
521 return channelList->objects;
522}
523
524bool StepClip::usesProbability()
525{
526 for (auto seq : getPatternSequence())
527 {
528 auto p = seq->getPattern();
529 for (int ch = 0; ch < getChannels().size(); ch++)
530 for (auto v : p.getProbabilities (ch))
531 if (v < 1.0f)
532 return true;
533 }
534 return false;
535}
536
537void StepClip::insertNewChannel (int index)
538{
539 if (getChannels().size() < maxNumChannels)
540 {
541 auto* um = getUndoManager();
542
543 auto v = createValueTree (IDs::CHANNEL,
544 IDs::channel, defaultMidiChannel,
545 IDs::note, defaultNoteNumber,
546 IDs::velocity, defaultNoteValue);
547
548 state.getOrCreateChildWithName (IDs::CHANNELS, um)
549 .addChild (v, index, um);
550
551 for (auto& p : getPatterns())
552 p.insertChannel (index);
553 }
554 else
555 {
556 edit.engine.getUIBehaviour().showWarningMessage (TRANS("This clip already contains the maximum number of channels!"));
557 }
558}
559
560void StepClip::removeChannel (int index)
561{
562 if (auto* c = getChannels()[index])
563 {
564 state.getChildWithName (IDs::CHANNELS)
565 .removeChild (c->state, getUndoManager());
566
567 for (auto& p : getPatterns())
568 p.removeChannel (index);
569 }
570}
571
572StepClip::PatternArray StepClip::getPatternSequence() const
573{
574 return patternInstanceList;
575}
576
577void StepClip::setPatternSequence (const StepClip::PatternArray& newSequence)
578{
580
581 for (auto* p : newSequence)
582 s.add (juce::String (p->patternIndex));
583
584 state.setProperty (IDs::sequence, s.joinIntoString (","), getUndoManager());
585}
586
587//==============================================================================
588void StepClip::insertVariation (int patternIndex, int insertIndex)
589{
590 PatternArray s (patternInstanceList);
591 s.insert (insertIndex, new PatternInstance (*this, juce::jlimit (0, getPatterns().size() - 1, patternIndex)));
592 setPatternSequence (s);
593}
594
595void StepClip::removeVariation (int variationIndex)
596{
597 jassert (juce::isPositiveAndBelow (variationIndex, patternInstanceList.size()));
598
599 PatternArray s (patternInstanceList);
600 s.remove (variationIndex);
601 setPatternSequence (s);
602}
603
604void StepClip::removeAllVariations()
605{
606 state.setProperty (IDs::sequence, juce::String(), getUndoManager());
607}
608
609void StepClip::createDefaultPatternIfEmpty()
610{
611 while (getChannels().size() < minNumChannels)
612 insertNewChannel (getChannels().size());
613
614 if (! state.getChildWithName (IDs::PATTERNS).isValid())
615 insertNewPattern (-1);
616
617 if (! state.hasProperty (IDs::sequence))
618 insertVariation (0, -1);
619}
620
621void StepClip::setPatternForVariation (int variationIndex, int newPatternIndex)
622{
623 jassert (juce::isPositiveAndBelow (variationIndex, patternInstanceList.size()));
624 jassert (juce::isPositiveAndBelow (newPatternIndex, getPatterns().size()));
625
626 PatternArray s (patternInstanceList);
627 s.set (variationIndex, new PatternInstance (*this, juce::jlimit (0, getPatterns().size() - 1, newPatternIndex)));
628 setPatternSequence (s);
629}
630
631//==============================================================================
632juce::Array<StepClip::Pattern> StepClip::getPatterns()
633{
635 auto patterns = state.getChildWithName (IDs::PATTERNS);
636
637 for (int i = 0; i < patterns.getNumChildren(); ++i)
638 {
639 auto v = patterns.getChild (i);
640
641 if (v.hasType (IDs::PATTERN))
642 p.add (Pattern (*this, v));
643 }
644
645 return p;
646}
647
648StepClip::Pattern StepClip::getPattern (int index)
649{
650 return Pattern (*this, state.getChildWithName (IDs::PATTERNS).getChild (index));
651}
652
653int StepClip::insertPattern (const Pattern& p, int index)
654{
656 .addChild (p.state.createCopy(), index, getUndoManager());
657
658 return index < 0 ? getPatterns().size() : index;
659}
660
661int StepClip::insertNewPattern (int index)
662{
663 auto v = createValueTree (IDs::PATTERN,
664 IDs::numNotes, getBeatsPerBar() * 4,
665 IDs::noteLength, 0.25);
666
668 .addChild (v, index, getUndoManager());
669
670 return index < 0 ? getPatterns().size() : index;
671}
672
673void StepClip::removeUnusedPatterns()
674{
675 auto patterns = state.getChildWithName (IDs::PATTERNS);
676 juce::Array<int> usedPatterns;
678
679 for (auto* p : patternInstanceList)
680 {
681 const int index = p->patternIndex;
682 usedPatterns.addIfNotAlreadyThere (index);
683 sequence.add (patterns.getChild (index));
684 }
685
686 auto um = getUndoManager();
687
688 for (int i = patterns.getNumChildren(); --i >= 0;)
689 if (! usedPatterns.contains (i))
690 patterns.removeChild (i, um);
691
692 juce::StringArray newSequence;
693
694 for (int i = 0; i < sequence.size(); ++i)
695 {
696 const int index = patterns.indexOf (sequence.getUnchecked (i));
697
698 if (index != -1)
699 newSequence.add (juce::String (index));
700 }
701
702 state.setProperty (IDs::sequence, newSequence.joinIntoString (","), um);
703}
704
705//==============================================================================
706bool StepClip::getCell (int patternIndex, int channelIndex, int noteIndex)
707{
708 return getPattern (patternIndex).getNote (channelIndex, noteIndex);
709}
710
711void StepClip::setCell (int patternIndex, int channelIndex,
712 int noteIndex, bool value)
713{
714 if (getCell (patternIndex, channelIndex, noteIndex) != value
715 && juce::isPositiveAndBelow (channelIndex, getChannels().size()))
716 {
717 Pattern p (getPattern (patternIndex));
718
719 if (juce::isPositiveAndBelow (noteIndex, p.getNumNotes()))
720 p.setNote (channelIndex, noteIndex, value);
721 }
722}
723
724//==============================================================================
726{
727 const auto s = getClipSlot() != nullptr ? 0_tp : clipStart.get();
728 return { { s, s + length.get() }, offset.get() };
729}
730
732{
733 return getClipSlot() != nullptr;
734}
735
737{
738 if (! canLoop())
739 num = 0;
740
741 if (! isLooping())
742 originalLength = getLengthInBeats();
743
744 auto& ts = edit.tempoSequence;
745 auto pos = getPosition();
746 auto newStartBeat = BeatPosition::fromBeats (pos.getOffset().inSeconds() * ts.getBeatsPerSecondAt (pos.getStart()));
747 setLoopRangeBeats ({ newStartBeat, newStartBeat + originalLength.get() });
748
749 auto endTime = ts.toTime (getStartBeat() + originalLength.get() * num);
750 setLength (endTime - pos.getStart(), true);
751 setOffset ({});
752}
753
755{
756 if (! isLooping())
757 return;
758
759 auto pos = getPosition();
760
761 auto offsetB = loopStartBeats.get() + getOffsetInBeats();
762 auto lengthB = getLoopLengthBeats();
763
764 pos.time = pos.time.withEnd (std::min (getTimeOfRelativeBeat (lengthB), pos.getEnd()));
765 pos.offset = getTimeOfRelativeBeat (toDuration (offsetB)) - pos.getStart(); // TODO: is this correct? Needs testing..
766
767 setLoopRange ({});
768 setPosition (pos);
769}
770
771void StepClip::setLoopRangeBeats (BeatRange newRangeBeats)
772{
773 if (! canLoop())
774 return;
775
776 jassert (newRangeBeats.getStart() >= BeatPosition());
777
778 auto newStartBeat = std::max (BeatPosition(), newRangeBeats.getStart());
779 auto newLengthBeat = std::max (BeatDuration(), newRangeBeats.getLength());
780
781 if (loopStartBeats != newStartBeat || loopLengthBeats != newLengthBeat)
782 {
784
785 if (! isLooping())
786 originalLength = getLengthInBeats();
787
788 loopStartBeats = newStartBeat;
789 loopLengthBeats = newLengthBeat;
790 }
791}
792
793void StepClip::setLoopRange (TimeRange newRange)
794{
795 if (! canLoop())
796 return;
797
798 jassert (newRange.getStart() >= TimePosition());
799
800 auto& ts = edit.tempoSequence;
801 auto pos = getPosition();
802 auto newStartBeat = BeatPosition::fromBeats (newRange.getStart().inSeconds() * ts.getBeatsPerSecondAt (pos.getStart()));
803 auto newLengthBeats = ts.toBeats (pos.getStart() + newRange.getLength()) - ts.toBeats (pos.getStart());
804
805 setLoopRangeBeats ({ newStartBeat, newStartBeat + newLengthBeats });
806}
807
809{
810 return TimePosition::fromSeconds (loopStartBeats.get().inBeats() / edit.tempoSequence.getBeatsPerSecondAt (getPosition().getStart()));
811}
812
814{
815 return TimeDuration::fromSeconds (loopLengthBeats.get().inBeats() / edit.tempoSequence.getBeatsPerSecondAt (getPosition().getStart()));
816}
817
819{
820 if (! launchHandle)
821 launchHandle = std::make_shared<LaunchHandle>();
822
823 return launchHandle;
824}
825
827{
828 if (! launchQuantisation)
829 launchQuantisation = std::make_unique<LaunchQuantisation> (state, edit);
830
831 return launchQuantisation.get();
832}
833
835{
836 if (! followActions)
838
839 return followActions.get();
840}
841
842}} // namespace tracktion { inline namespace engine
ElementType getUnchecked(int index) const
int size() const noexcept
void add(const ElementType &newElement)
bool contains(ParameterType elementToLookFor) const
bool addIfNotAlreadyThere(ParameterType newElement)
void setValue(const Type &newValue, UndoManager *undoManagerToUse)
Colour withHue(float newHue) const noexcept
bool isValid() const noexcept
MidiEventHolder * addEvent(const MidiMessage &newMessage, double timeAdjustment=0)
void updateMatchedPairs() noexcept
void addTimeToMessages(double deltaTime) noexcept
static MidiMessage noteOn(int channel, int noteNumber, float velocity) noexcept
static MidiMessage noteOff(int channel, int noteNumber, float velocity) noexcept
static const char * getRhythmInstrumentName(int midiNoteNumber)
ObjectClass * getUnchecked(int index) const noexcept
void ensureStorageAllocated(int minNumElements) noexcept
ObjectClass * add(ObjectClass *newObject)
float nextFloat() noexcept
static Random & getSystemRandom() noexcept
int indexOf(const ObjectClass *objectToLookFor) const noexcept
int size() const noexcept
bool contains(const ObjectClass *objectToLookFor) const noexcept
void swapWith(OtherArrayType &otherArray) noexcept
ObjectClassPtr getLast() const noexcept
String joinIntoString(StringRef separatorString, int startIndex=0, int numberOfElements=-1) const
static StringArray fromTokens(StringRef stringToTokenise, bool preserveQuotedStrings)
void insert(int index, String stringToAdd)
void add(String stringToAdd)
void set(int index, String newString)
void remove(int index)
virtual void valueTreeChildRemoved(ValueTree &parentTree, ValueTree &childWhichHasBeenRemoved, int indexFromWhichChildWasRemoved)
virtual void valueTreeChildOrderChanged(ValueTree &parentTreeWhoseChildrenHaveMoved, int oldIndex, int newIndex)
virtual void valueTreeChildAdded(ValueTree &parentTree, ValueTree &childWhichHasBeenAdded)
void removeChild(const ValueTree &child, UndoManager *undoManager)
ValueTree getChild(int index) const
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
ValueTree getOrCreateChildWithName(const Identifier &type, UndoManager *undoManager)
bool hasProperty(const Identifier &name) const noexcept
Base class for items that can contain clips.
A clip in an edit.
ClipSlot * getClipSlot() const
Returns the parent ClipSlot this clip is on (if any).
virtual juce::String getName() const override
Returns the name of the clip.
void changed() override
This should be called to send a change notification to any SelectableListeners that are registered wi...
void setOffset(TimeDuration newOffset)
Sets the offset of the clip, i.e.
juce::ValueTree state
The ValueTree of the Clip state.
virtual void cloneFrom(Clip *)
Clones the given clip to this clip.
void setLength(TimeDuration newLength, bool preserveSync)
Sets the length of the clip.
virtual void setSpeedRatio(double)
Sets a speed ratio i.e.
juce::UndoManager * getUndoManager() const
Returns the UndoManager.
void setEnd(TimePosition newEnd, bool preserveSync)
Sets the end of the clip.
void setPosition(ClipPosition newPosition)
Sets the position of the clip.
TimeBase
Determines MIDI event timing.
@ beatsRaw
Event times will be in beats relative to the Edit timeline.
virtual void changed()
This should be called to send a change notification to any SelectableListeners that are registered wi...
juce::String getSelectableDescription() override
Subclasses must return a description of what they are.
bool canBeAddedTo(ClipOwner &) override
Tests whether this clip can go on the given parent.
std::shared_ptr< LaunchHandle > getLaunchHandle() override
Some clip types can be launched, if that's possible, this returns a handle to trigger starting/stoppi...
void setLoopRange(TimeRange) override
Sets the loop range the clip should use in seconds.
void generateMidiSequence(juce::MidiMessageSequence &result, bool convertToSeconds=true, PatternInstance *instance=nullptr)
Generate a MidiMessageSequence from either the entire clip or one of its pattern instances.
LaunchQuantisation * getLaunchQuantisation() override
Some clip types can be launched, if that's possible, this returns a quantisation that can be used for...
BeatDuration getLoopLengthBeats() const override
Returns the length of loop in beats.
void cloneFrom(Clip *) override
Clones the given clip to this clip.
bool isLooping() const override
Returns true if this clip is currently looping.
void disableLooping() override
Disables all looping.
TimePosition getLoopStart() const override
Returns the start time of the loop start point.
void setNumberOfLoops(int) override
Sets the clip looping a number of times.
juce::Colour getDefaultColour() const override
Returns the default colour for this clip.
bool canLoop() const override
StepClips can only loop if they're being used as launcher clips.
void setLoopRangeBeats(BeatRange) override
Sets the loop range the clip should use in beats.
TimeDuration getLoopLength() const override
Returns the length of loop in seconds.
FollowActions * getFollowActions() override
Some clip types can be launched, if that's possible, this can be used to determine the action to perf...
ClipPosition getPosition() const override
Returns the ClipPosition on the parent Track.
Type
Defines the types of item that can live on Track[s].
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.
TimePosition getTimeOfRelativeBeat(BeatDuration) const
Returns an Edit time point for a given number of beats from the start of this item.
BeatDuration getOffsetInBeats() const
Returns an the offset of this item in beats.
T end(T... args)
T get(T... args)
T is_pointer_v
#define TRANS(stringLiteral)
#define jassert(expression)
#define JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(className)
#define jassertfalse
T max(T... args)
T min(T... args)
Type jlimit(Type lowerLimit, Type upperLimit, Type valueToConstrain) noexcept
bool isPositiveAndBelow(Type1 valueToTest, Type2 upperLimit) noexcept
bool canContainMIDI(const ClipOwner &co)
Returns true if this Track can contain MidiClip[s].
constexpr TimeDuration toDuration(TimePosition)
Converts a TimePosition to a TimeDuration.
T size(T... args)
Represents a duration in beats.
Represents a position in beats.
Represents a duration in real-life time.
Represents a position in real-life time.
Represents the position of a clip on a timeline.
ID for objects of type EditElement - e.g.
Provides a thread-safe way to share a clip's levels with an audio engine without worrying about the C...
#define CRASH_TRACER
This macro adds the current location to a stack which gets logged if a crash happens.