11namespace tracktion {
inline namespace engine
29 return v.hasType (IDs::CHANNEL);
37 void deleteObject (
Channel* c)
override
42 void newObjectAdded (
Channel*)
override {}
43 void objectRemoved (
Channel*)
override {}
44 void objectOrderChanged()
override {}
53 :
Clip (v, targetParent, id,
Type::step)
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);
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);
65 useClipLaunchQuantisation.referTo (
state, IDs::useClipLaunchQuantisation, um);
67 if (getChannels().isEmpty())
69 for (
int i = defaultNumChannels; --i >= 0;)
70 insertNewChannel (-1);
72 auto getDefaultNoteNumber = [] (
int chanNum)
84 default:
return 60 + chanNum - 4;
88 for (
int i = 0; i <
std::min (getChannels().size(), 8); ++i)
90 if (Channel* c = getChannels()[i])
92 c->noteNumber = getDefaultNoteNumber (i);
98 createDefaultPatternIfEmpty();
104 notifyListenersOfDeletion();
105 channelList =
nullptr;
110 if (
auto other =
dynamic_cast<StepClip*
> (c))
114 repeatSequence .
setValue (other->repeatSequence,
nullptr);
115 level->dbGain .setValue (other->level->dbGain,
nullptr);
116 level->mute .setValue (other->level->mute,
nullptr);
121 copyValueTree (chans, other->state.getChildWithName (IDs::CHANNELS),
nullptr);
122 copyValueTree (patterns, other->state.getChildWithName (IDs::PATTERNS),
nullptr);
132 auto size = patternInstanceList.
size();
134 return patternInstanceList[repeatSeq ? (i % size)
135 :
std::min (size - 1, i)];
138void StepClip::updatePatternList()
142 PatternArray newArray (patternInstanceList);
143 newArray.removeRange (newSequence.size(), newArray.size());
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()));
149 patternInstanceList.
swapWith (newArray);
152 for (
auto p : patternInstanceList)
153 newArray.removeObject (p);
155 for (
auto p : newArray)
163 Clip::valueTreePropertyChanged (v, i);
165 if (i == IDs::sequence || i == IDs::repeatSequence)
170 if (v.hasType (IDs::PATTERN))
172 for (
auto patternInstance : patternInstanceList)
173 if (v == patternInstance->getPattern().
state)
176 else if (v.hasType (IDs::CHANNEL))
178 for (
auto channel : *channelList)
179 if (v == channel->
state)
188 if (p.hasType (IDs::PATTERN))
196 if (p.hasType (IDs::PATTERN))
200void StepClip::valueTreeChildOrderChanged (
juce::ValueTree& p,
int o,
int n)
223 auto& tempoSequence =
edit.tempoSequence;
226 auto patternStartBeats = tempoSequence.toBeats (pos.getStartOfSource());
227 auto endBeat = tempoSequence.toBeats (pos.getEnd());
228 auto ratio = 1.0 / speedRatio;
229 bool repeatSeq = repeatSequence;
231 for (
int i = 0; ; ++i)
233 starts.
add (patternStartBeats);
235 auto numBeats = BeatDuration::fromBeats (4.0);
237 if (
auto p = getPatternInstance (i, repeatSeq))
239 auto pattern = p->getPattern();
240 numBeats = pattern.getNoteLength() * pattern.getNumNotes() * ratio;
243 patternStartBeats = patternStartBeats + numBeats;
245 if (patternStartBeats >= endBeat)
252BeatPosition StepClip::getStartBeatOf (PatternInstance* instance)
254 if (instance ==
nullptr
255 || (instance !=
nullptr && ! patternInstanceList.
contains (instance)))
258 return BeatPosition();
261 auto index = patternInstanceList.
indexOf (instance);
262 return getBeatTimesOfPatternStarts()[index];
265BeatPosition StepClip::getEndBeatOf (PatternInstance* instance)
267 if (instance ==
nullptr
268 || (instance !=
nullptr && ! patternInstanceList.
contains (instance)))
271 return BeatPosition();
274 auto index = patternInstanceList.
indexOf (instance) + 1;
275 return getBeatTimesOfPatternStarts()[index];
278void StepClip::resizeClipForPatternInstances()
280 if (patternInstanceList.
getLast().get() !=
nullptr)
284 auto ratio = 1.0 / speedRatio;
286 for (
auto p : patternInstanceList)
288 auto pattern = p->getPattern();
289 end =
end + pattern.getNoteLength() * pattern.getNumNotes() * ratio;
294 auto& ts =
edit.tempoSequence;
296 auto startBeat = ts.toBeats (pos.getStart());
297 auto endBeat = startBeat +
end;
299 setEnd (ts.toTime (endBeat),
false);
304int StepClip::getBeatsPerBar()
306 return edit.tempoSequence.getTimeSigAt (
getPosition().getStart()).numerator;
310 bool convertToSeconds,
const Pattern& pattern,
311 BeatPosition startBeat, BeatPosition clipStartBeat,
312 BeatPosition clipEndBeat,
const TempoSequence& tempoSequence)
317 auto ratio = 1.0 / speedRatio;
319 auto& gtm =
edit.engine.getGrooveTemplateManager();
320 auto numChannels = getChannels().size();
321 auto numNotes = pattern.getNumNotes();
322 auto noteLength = pattern.getNoteLength();
327 for (
int f = 0; f < numChannels; ++f)
328 caches.
add (
new Pattern::CachedPattern (pattern, f));
330 for (
int i = 0; i < numNotes; ++i)
332 auto currentBeatEnd = startBeat + (noteLength * ratio);
334 if (startBeat >= clipEndBeat)
337 if (currentBeatEnd > clipStartBeat)
339 for (
int f = 0; f < numChannels; ++f)
343 if (cache ==
nullptr)
349 if (cache->getNote (i))
351 auto prob = cache->getProbability (i);
356 auto gate = cache->getGate (i);
357 auto beatEnd = startBeat + (noteLength * gate * ratio);
360 auto start =
std::max (clipStartBeat, startBeat);
361 auto end =
std::min (clipEndBeat - 0.0001_bd, beatEnd);
363 double eventStart = start.inBeats();
364 double eventEnd =
end.inBeats();
366 if (end > (start + 0.0001_bd))
368 auto& c = *getChannels().getUnchecked (f);
370 if (convertToSeconds)
372 const auto startTime = tempoSequence.toTime (start) -
toDuration (pos.getStart());
373 const auto endTime = tempoSequence.toTime (end) -
toDuration (pos.getStart());
375 if (
auto gt = gtm.getTemplateByName (c.grooveTemplate))
377 eventStart = gt->editTimeToGroovyTime (startTime, c.grooveStrength,
edit).inSeconds();
378 eventEnd = gt->editTimeToGroovyTime (endTime, c.grooveStrength,
edit).inSeconds();
382 eventStart = startTime.inSeconds();
383 eventEnd = endTime.inSeconds();
388 if (
auto gt = gtm.getTemplateByName (c.grooveTemplate))
390 eventStart = gt->beatsTimeToGroovyTime (start, c.grooveStrength).inBeats();
391 eventEnd = gt->beatsTimeToGroovyTime (end, c.grooveStrength).inBeats();
395 const float channelVelScale = c.noteValue / 127.0f;
396 const float vel = cache->getVelocity (i) / 127.0f;
398 auto chan = c.channel.get().getChannelNumber();
406 startBeat = currentBeatEnd;
411 bool convertToSeconds,
414 if (instance !=
nullptr && ! patternInstanceList.
contains (instance))
420 auto& tempoSequence =
edit.tempoSequence;
423 auto clipStartBeat = tempoSequence.toBeats (pos.getStart());
424 auto clipEndBeat = tempoSequence.toBeats (pos.getEnd());
425 bool repeatSeq = repeatSequence;
426 auto starts = getBeatTimesOfPatternStarts();
428 for (
int i = 0; i < starts.
size(); ++i)
430 if (
auto p = getPatternInstance (i, repeatSeq))
432 if (instance !=
nullptr && p.get() != instance)
437 generateMidiSequenceForChannels (result, convertToSeconds, p->getPattern(), startBeat,
438 clipStartBeat, clipEndBeat, tempoSequence);
440 if (instance !=
nullptr)
442 result.
addTimeToMessages (-tempoSequence.toTime (clipStartBeat + toDuration (startBeat)).inSeconds());
454 if (instance !=
nullptr && ! patternInstanceList.
contains (instance))
461 auto& tempoSequence =
edit.tempoSequence;
463 const auto clipBeatRange = tempoSequence.toBeats (pos.time);
465 const bool repeatSeq = repeatSequence;
466 const auto starts = getBeatTimesOfPatternStarts();
468 for (
int i = 0; i < starts.
size(); ++i)
470 if (
auto p = getPatternInstance (i, repeatSeq))
472 if (instance !=
nullptr && p.get() != instance)
477 generateMidiSequenceForChannels (result, timeBase == MidiList::TimeBase::seconds,
478 p->getPattern(), startBeat,
479 clipBeatRange.getStart(), clipBeatRange.getEnd(), tempoSequence);
483 result.
addTimeToMessages (-tempoSequence.toTime (clipBeatRange.getStart() + toDuration (startBeat)).inSeconds());
498 return juce::Colours::red.
withHue (3.0f / 9.0f);
510 : index (i), wasSelected (selected) {}
513 const bool wasSelected;
521 return channelList->objects;
524bool StepClip::usesProbability()
526 for (
auto seq : getPatternSequence())
528 auto p = seq->getPattern();
529 for (
int ch = 0; ch < getChannels().size(); ch++)
530 for (
auto v : p.getProbabilities (ch))
537void StepClip::insertNewChannel (
int index)
539 if (getChannels().
size() < maxNumChannels)
543 auto v = createValueTree (IDs::CHANNEL,
544 IDs::channel, defaultMidiChannel,
545 IDs::note, defaultNoteNumber,
546 IDs::velocity, defaultNoteValue);
551 for (
auto& p : getPatterns())
552 p.insertChannel (index);
556 edit.engine.getUIBehaviour().showWarningMessage (
TRANS(
"This clip already contains the maximum number of channels!"));
560void StepClip::removeChannel (
int index)
562 if (
auto* c = getChannels()[index])
567 for (
auto& p : getPatterns())
568 p.removeChannel (index);
572StepClip::PatternArray StepClip::getPatternSequence()
const
574 return patternInstanceList;
577void StepClip::setPatternSequence (
const StepClip::PatternArray& newSequence)
581 for (
auto* p : newSequence)
582 s.add (
juce::String (p->patternIndex));
588void StepClip::insertVariation (
int patternIndex,
int insertIndex)
590 PatternArray s (patternInstanceList);
591 s.
insert (insertIndex,
new PatternInstance (*
this,
juce::jlimit (0, getPatterns().
size() - 1, patternIndex)));
592 setPatternSequence (s);
595void StepClip::removeVariation (
int variationIndex)
599 PatternArray s (patternInstanceList);
600 s.
remove (variationIndex);
601 setPatternSequence (s);
604void StepClip::removeAllVariations()
609void StepClip::createDefaultPatternIfEmpty()
611 while (getChannels().
size() < minNumChannels)
612 insertNewChannel (getChannels().
size());
615 insertNewPattern (-1);
618 insertVariation (0, -1);
621void StepClip::setPatternForVariation (
int variationIndex,
int newPatternIndex)
626 PatternArray s (patternInstanceList);
627 s.
set (variationIndex,
new PatternInstance (*
this,
juce::jlimit (0, getPatterns().
size() - 1, newPatternIndex)));
628 setPatternSequence (s);
637 for (
int i = 0; i < patterns.getNumChildren(); ++i)
639 auto v = patterns.getChild (i);
641 if (v.hasType (IDs::PATTERN))
642 p.add (Pattern (*
this, v));
648StepClip::Pattern StepClip::getPattern (
int index)
653int StepClip::insertPattern (
const Pattern& p,
int index)
658 return index < 0 ? getPatterns().size() : index;
661int StepClip::insertNewPattern (
int index)
663 auto v = createValueTree (IDs::PATTERN,
664 IDs::numNotes, getBeatsPerBar() * 4,
665 IDs::noteLength, 0.25);
670 return index < 0 ? getPatterns().size() : index;
673void StepClip::removeUnusedPatterns()
679 for (
auto* p : patternInstanceList)
681 const int index = p->patternIndex;
683 sequence.add (patterns.getChild (index));
688 for (
int i = patterns.getNumChildren(); --i >= 0;)
690 patterns.removeChild (i, um);
694 for (
int i = 0; i < sequence.size(); ++i)
696 const int index = patterns.indexOf (sequence.getUnchecked (i));
706bool StepClip::getCell (
int patternIndex,
int channelIndex,
int noteIndex)
708 return getPattern (patternIndex).getNote (channelIndex, noteIndex);
711void StepClip::setCell (
int patternIndex,
int channelIndex,
712 int noteIndex,
bool value)
714 if (getCell (patternIndex, channelIndex, noteIndex) != value
717 Pattern p (getPattern (patternIndex));
720 p.setNote (channelIndex, noteIndex, value);
727 const auto s =
getClipSlot() !=
nullptr ? 0_tp : clipStart.get();
728 return { { s, s + length.get() }, offset.get() };
744 auto& ts =
edit.tempoSequence;
746 auto newStartBeat = BeatPosition::fromBeats (pos.getOffset().inSeconds() * ts.getBeatsPerSecondAt (pos.getStart()));
749 auto endTime = ts.toTime (
getStartBeat() + originalLength.get() * num);
750 setLength (endTime - pos.getStart(),
true);
781 if (loopStartBeats != newStartBeat || loopLengthBeats != newLengthBeat)
788 loopStartBeats = newStartBeat;
789 loopLengthBeats = newLengthBeat;
800 auto& ts =
edit.tempoSequence;
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());
810 return TimePosition::fromSeconds (loopStartBeats.get().inBeats() /
edit.tempoSequence.getBeatsPerSecondAt (
getPosition().getStart()));
815 return TimeDuration::fromSeconds (loopLengthBeats.get().inBeats() /
edit.tempoSequence.getBeatsPerSecondAt (
getPosition().getStart()));
828 if (! launchQuantisation)
831 return launchQuantisation.get();
839 return followActions.
get();
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)
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.
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.
Represents a launch quantisation.
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.
#define TRANS(stringLiteral)
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.
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.