11namespace tracktion {
inline namespace engine
16 const auto loopStartBeats = clip.getLoopStartBeats();
17 const auto loopLengthBeats = clip.getLoopLengthBeats();
22 const auto& notes = sourceSequence.getNotes();
23 const auto& sysex = sourceSequence.getSysexEvents();
24 const auto& controllers = sourceSequence.getControllerEvents();
26 auto v = MidiList::createMidiList();
28 for (
int i = 0; i < loopTimes; ++i)
30 const auto loopPos = loopLengthBeats * i;
31 const auto nextLoopPos = loopLengthBeats * (i + 1);
34 for (
auto note : notes)
36 auto start = (note->getStartBeat() - loopStartBeats) + loopPos;
37 auto length = note->getLengthBeats();
41 length = length - (loopPos - start);
42 start = start + (loopPos - start);
45 if (start + length > nextLoopPos)
46 length = length - ((start + length) - nextLoopPos);
48 if (start >= loopPos && start < nextLoopPos && length > BeatDuration())
49 v.addChild (MidiNote::createNote (*note, toPosition (start), length), -1,
nullptr);
53 for (
auto oldEvent : sysex)
55 const auto start = (oldEvent->getBeatPosition() - loopStartBeats) + loopPos;
57 if (start >= loopPos && start < nextLoopPos)
58 v.addChild (MidiSysexEvent::createSysexEvent (*oldEvent, toPosition (start)), -1,
nullptr);
62 for (
auto oldEvent : controllers)
64 const auto start = (oldEvent->getBeatPosition() - loopStartBeats) + loopPos;
66 if (start >= loopPos && start < nextLoopPos)
67 v.addChild (MidiControllerEvent::createControllerEvent (*oldEvent, toPosition (start)), -1,
nullptr);
72 destSequence->setMidiChannel (sourceSequence.getMidiChannel());
77static std::unique_ptr<MidiList> createLoopRangeDefinesSubsequentRepetitionsSequence (MidiClip& clip, MidiList& sourceSequence)
80 return createLoopRangeDefinesAllRepetitionsSequence (clip, sourceSequence);
84MidiClip::MidiClip (
const juce::ValueTree& v, EditItemID
id, ClipOwner& targetParent)
85 : Clip (v, targetParent, id, Type::midi)
87 auto um = getUndoManager();
91 if (state.hasProperty (IDs::quantisation))
92 quantisation->setType (state.getProperty (IDs::quantisation));
94 level->dbGain.referTo (state, IDs::volDb, um, 0.0f);
95 level->mute.referTo (state, IDs::mute, um,
false);
96 sendPatch.referTo (state, IDs::sendProgramChange, um,
true);
97 sendBankChange.referTo (state, IDs::sendBankChange, um,
true);
98 mpeMode.referTo (state, IDs::mpeMode, um,
false);
99 grooveStrength.referTo (state, IDs::grooveStrength, um, 0.1f);
101 loopStartBeats.referTo (state, IDs::loopStartBeats, um, BeatPosition());
102 loopLengthBeats.referTo (state, IDs::loopLengthBeats, um, BeatDuration());
103 originalLength.referTo (state, IDs::originalLength, um, BeatDuration());
105 useClipLaunchQuantisation.referTo (state, IDs::useClipLaunchQuantisation, um);
107 proxyAllowed.referTo (state, IDs::proxyAllowed, um,
true);
108 currentTake.referTo (state, IDs::currentTake, um);
110 auto grooveTree = state.getOrCreateChildWithName (IDs::GROOVE, um);
111 grooveTemplate.referTo (grooveTree, IDs::current, um, {});
113 const int loopType = edit.engine.getEngineBehaviour().getDefaultLoopedSequenceType();
114 loopedSequenceType.referTo (state, IDs::loopedSequenceType, um,
115 static_cast<LoopedSequenceType
> (
juce::jlimit (0, 1, loopType)));
116 jassert (loopType ==
int (LoopedSequenceType::loopRangeDefinesAllRepetitions)
117 || loopType ==
int (LoopedSequenceType::loopRangeDefinesSubsequentRepetitions));
119 auto pgen = state.getChildWithName (IDs::PATTERNGENERATOR);
127 notifyListenersOfDeletion();
131void MidiClip::initialise()
135 auto um = getUndoManager();
136 auto takesTree = state.getChildWithName (IDs::TAKES);
138 if (takesTree.isValid() && takesTree.getNumChildren() > 0)
140 channelSequence.clearQuick (
true);
142 for (
int i = 0; i < takesTree.getNumChildren(); ++i)
144 auto sequence = takesTree.getChild (i);
149 channelSequence.add (
new MidiList (sequence, um));
152 if (state.getChildWithName (IDs::COMPS).
isValid())
153 callBlocking ([
this] { getCompManager(); });
157 auto list = state.getChildWithName (IDs::SEQUENCE);
160 channelSequence.add (
new MidiList (list, um));
162 state.addChild (MidiList::createMidiList(), -1, um);
167 if (grooveTemplate.get().isNotEmpty())
169 auto g = edit.engine.getGrooveTemplateManager().getTemplateByName (grooveTemplate);
171 auto grooveTree = state.getChildWithName (IDs::GROOVE);
173 if (g ==
nullptr && grooveTree.getNumChildren() > 0)
175 auto grooveXml = grooveTree.getChild (0).createXml();
180 edit.engine.getGrooveTemplateManager().updateTemplate (-1, gt);
189 getSequence().rescale (factor, getUndoManager());
190 setLoopRangeBeats ({ loopStartBeats * factor, (loopStartBeats + loopLengthBeats) * factor });
191 Clip::rescale (pivotTimeInEdit, factor);
194void MidiClip::cloneFrom (
Clip* c)
196 if (
auto other =
dynamic_cast<MidiClip*
> (c))
198 Clip::cloneFrom (other);
200 level->dbGain .setValue (other->level->dbGain,
nullptr);
201 level->mute .setValue (other->level->mute,
nullptr);
202 currentTake .setValue (other->currentTake,
nullptr);
203 mpeMode .setValue (other->mpeMode,
nullptr);
204 originalLength .setValue (other->originalLength,
nullptr);
205 sendPatch .setValue (other->sendPatch,
nullptr);
206 sendBankChange .setValue (other->sendBankChange,
nullptr);
207 grooveTemplate .setValue (other->grooveTemplate,
nullptr);
208 grooveStrength .setValue (other->grooveStrength,
nullptr);
210 quantisation->setType (other->quantisation->getType (
false));
211 quantisation->setProportion (other->quantisation->getProportion());
215 for (
int i = 0; i < other->channelSequence.size(); i++)
217 MidiList* cs = i < channelSequence.size() ? channelSequence[i]
218 : channelSequence.add (
new MidiList());
219 cs->
copyFrom (*other->channelSequence[i], um);
222 while (channelSequence.size() > other->channelSequence.size())
223 channelSequence.removeLast();
235 return juce::Colours::red.
withHue (3.0f / 9.0f);
238bool MidiClip::usesGrooveStrength()
const
240 auto gt = edit.engine.getGrooveTemplateManager().getTemplateByName (getGrooveTemplate());
242 if (gt !=
nullptr && gt->isEmpty())
246 return gt->isParameterized();
251MidiList& MidiClip::getSequence() const noexcept
253 if (! hasValidSequence())
260 jassert (channelSequence.size() > 0);
261 return *channelSequence.getUnchecked (currentTake);
264MidiList& MidiClip::getSequenceLooped()
267 TRACKTION_ASSERT_MESSAGE_THREAD
270 return getSequence();
272 if (cachedLoopedSequence ==
nullptr)
273 cachedLoopedSequence = createSequenceLooped (getSequence());
275 return *cachedLoopedSequence;
280 switch (loopedSequenceType)
282 case LoopedSequenceType::loopRangeDefinesSubsequentRepetitions:
283 return createLoopRangeDefinesSubsequentRepetitionsSequence (*
this, sourceSequence);
285 case LoopedSequenceType::loopRangeDefinesAllRepetitions:
287 return createLoopRangeDefinesAllRepetitionsSequence (*
this, sourceSequence);
291MidiClip::ScopedEventsList::ScopedEventsList (MidiClip& c, SelectedMidiEvents* e)
294 clip.setSelectedEvents (e);
297MidiClip::ScopedEventsList::~ScopedEventsList()
299 clip.setSelectedEvents (
nullptr);
305 auto offsetNeededInBeats =
edit.tempoSequence.toBeats (
getPosition().getStart())
306 -
edit.tempoSequence.toBeats (newStartTime);
308 setStart (newStartTime,
false,
false);
311 getSequence().moveAllBeatPositions (offsetNeededInBeats,
getUndoManager());
314void MidiClip::trimBeyondEnds (
bool beyondStart,
bool beyondEnd,
juce::UndoManager* um)
318 auto& sequence = getSequence();
328 getSequence().trimOutside ({}, endBeats, um);
336 auto noteStartBeat = note.getQuantisedStartBeat (*
this);
337 auto nextNoteStartBeat = noteStartBeat;
339 auto nextNote = notesToUse[notesToUse.
indexOf (¬e) + 1];
341 while (nextNote !=
nullptr)
343 nextNoteStartBeat = nextNote->getQuantisedStartBeat (*
this);
345 if (nextNoteStartBeat != noteStartBeat)
348 nextNote = notesToUse[notesToUse.
indexOf (nextNote) + 1];
351 auto& sequence = getSequence();
353 if (nextNote ==
nullptr)
355 if (
auto lastInSequence = sequence.getNotes().getLast())
357 if (*lastInSequence == note)
359 const auto separation = maxEndBeat - noteStartBeat;
360 note.setStartAndLength (note.getStartBeat(), separation, &um);
366 const auto diff = nextNoteStartBeat - noteStartBeat;
369 note.setStartAndLength (note.getStartBeat(), diff, &um);
375 for (
int i = channelSequence.size(); --i >= 0;)
376 if (
auto ml = channelSequence.getUnchecked (i))
384void MidiClip::setQuantisation (
const QuantisationType& newType)
386 if (newType.getType (
false) != quantisation->getType (
false))
387 *quantisation = newType;
401 if (! launchQuantisation)
404 return launchQuantisation.get();
412 return followActions.
get();
420 if (midiCompManager ==
nullptr)
422 auto ptr =
edit.engine.getCompFactory().getCompManager (*
this);
424 jassert (midiCompManager !=
nullptr);
425 midiCompManager->initialise();
428 return *midiCompManager;
431void MidiClip::scaleVerticallyToFit()
436 for (
auto n : getSequence().getNotes())
438 maxNote =
std::max (maxNote, n->getNoteNumber() + 3);
439 minNote =
std::min (minNote, n->getNoteNumber() - 3);
442 if (minNote < maxNote)
444 const double newVisProp = (maxNote - minNote) / 128.0;
447 getAudioTrack()->setMidiVerticalPos (newVisProp, 1.0 - (maxNote / 128.0));
457 if (! takesTree.isValid())
465 takesTree.addChild (seq, -1, um);
469 auto seq = MidiList::createMidiList();
470 takesTree.addChild (seq, -1, um);
472 if (
auto take = getMidiListForState (seq))
475 currentTake = channelSequence.size() - 1;
476 take->setMidiChannel (chan);
500 if (
auto current = channelSequence[currentTake])
503 auto take = current->state;
506 for (
int i = takes.getNumChildren(); --i >= 0;)
508 auto t = takes.getChild (i);
509 t.getParent().removeChild (t, um);
513 jassert (! exisiting.isValid());
515 if (exisiting.isValid())
520 if (
auto newList = channelSequence.getFirst())
522 jassert (newList->state == take);
523 newList->setCompList (
false);
524 channelSequence.minimiseStorageOverheads();
528 midiCompManager =
nullptr;
540 return channelSequence.size();
550 if (midiCompManager !=
nullptr)
552 for (
int i = 0; i < channelSequence.size(); ++i)
554 if (! midiCompManager->isTakeComp (i))
567 for (
int i = 0; i < channelSequence.size(); ++i)
577 currentTake = takeIndex;
588void MidiClip::deleteAllUnusedTakesConfirmingWithUser()
590 if (
edit.engine.getUIBehaviour()
591 .showOkCancelAlertBox (
TRANS(
"Delete Unused Takes"),
592 TRANS(
"This will permanently delete all unused MIDI takes in this clip.")
594 +
TRANS(
"Are you sure you want to do this?"),
608 if (shouldBeShowingTakes)
613 if (! clipNode.isValid())
616 clipNode.
removeChild (clipNode.getChildWithName (IDs::TAKES),
nullptr);
617 clipNode.removeChild (clipNode.getChildWithName (IDs::COMPS),
nullptr);
619 int trackIndex = t->getIndexInEditTrackList();
622 for (
int i = 0; i < channelSequence.size(); ++i)
624 auto srcList = channelSequence[i];
626 if (srcList ==
nullptr || getCompManager().isTakeComp (i))
631 if (toNewTracks || targetTrack ==
nullptr)
632 targetTrack = t->edit.insertNewAudioTrack (
TrackInsertPoint (t->getParentTrack(), t.get()),
nullptr);
634 if (targetTrack !=
nullptr)
636 if (
auto mc = targetTrack->insertMIDIClip (
TRANS(
"Take") +
" #" +
juce::String (i + 1),
639 mc->getSequence().copyFrom (*srcList,
nullptr);
640 newClips.add (mc.get());
651 if (shouldBeShowingTakes)
662 auto& take = getSequence();
666 auto chan = take.getMidiChannel();
698 auto& ts =
edit.tempoSequence;
700 auto newStartBeat = BeatPosition::fromBeats (pos.getOffset().inSeconds() * ts.getBeatsPerSecondAt (pos.getStart()));
703 auto endTime = ts.toTime (
getStartBeat() + originalLength.get() * num);
704 setLength (endTime - pos.getStart(),
true);
732 if (loopStartBeats != newStartBeat || loopLengthBeats != newLengthBeat)
739 loopStartBeats = newStartBeat;
740 loopLengthBeats = newLengthBeat;
748 auto& ts =
edit.tempoSequence;
750 auto newStartBeat = BeatPosition::fromBeats (newRange.getStart().inSeconds() * ts.getBeatsPerSecondAt (pos.getStart()));
751 auto newLengthBeats = ts.toBeats (pos.getStart() + newRange.getLength()) - ts.toBeats (pos.getStart());
758 return TimePosition::fromSeconds (loopStartBeats.get().inBeats() /
edit.tempoSequence.getBeatsPerSecondAt (
getPosition().getStart()));
763 return TimeDuration::fromSeconds (loopLengthBeats.get().inBeats() /
edit.tempoSequence.getBeatsPerSecondAt (
getPosition().getStart()));
767void MidiClip::setSendingBankChanges (
bool sendBank)
769 if (sendBank != sendBankChange)
770 sendBankChange = ! sendBankChange;
787 if (
auto p = track->getParentFolderTrack())
790 clearCachedLoopSequence();
792 else if (
id == IDs::volDb ||
id == IDs::mpeMode
793 ||
id == IDs::loopStartBeats ||
id == IDs::loopLengthBeats
794 ||
id == IDs::loopedSequenceType
795 ||
id == IDs::grooveStrength)
797 clearCachedLoopSequence();
799 else if (
id == IDs::currentTake)
803 for (SelectionManager::Iterator sm; sm.next();)
804 if (sm->isSelected (getSelectedEvents()))
807 clearCachedLoopSequence();
809 else if (
id == IDs::launchQuantisation ||
id == IDs::useClipLaunchQuantisation)
815 if (
id == IDs::length ||
id == IDs::offset)
816 clearCachedLoopSequence();
818 Clip::valueTreePropertyChanged (tree,
id);
821 else if (tree.
hasType (IDs::NOTE)
824 || tree.
hasType (IDs::QUANTISATION)
825 || (tree.
hasType (IDs::SEQUENCE) &&
id == IDs::channelNumber)
826 || (tree.
hasType (IDs::GROOVE) &&
id == IDs::current))
828 clearCachedLoopSequence();
832 Clip::valueTreePropertyChanged (tree,
id);
838 if (p.hasType (IDs::SEQUENCE))
839 clearCachedLoopSequence();
840 else if ((p ==
state || p.getParent() ==
state) && c.hasType (IDs::SEQUENCE))
843 if (c.hasType (IDs::PATTERNGENERATOR))
849 if (p.hasType (IDs::SEQUENCE))
851 clearCachedLoopSequence();
853 else if ((p ==
state || p.getParent() ==
state) && c.hasType (IDs::SEQUENCE))
855 for (
int i = channelSequence.size(); --i >= 0;)
856 if (
auto ml = channelSequence.getUnchecked (i))
858 channelSequence.remove (i);
860 else if (p ==
state && c.hasType (IDs::COMPS))
862 midiCompManager =
nullptr;
863 clearCachedLoopSequence();
865 else if (p ==
state && c.hasType (IDs::PATTERNGENERATOR))
867 patternGenerator =
nullptr;
871void MidiClip::clearCachedLoopSequence()
873 cachedLoopedSequence =
nullptr;
883 jassert (patternGenerator !=
nullptr);
884 return patternGenerator.get();
889 if (patternGenerator !=
nullptr)
890 patternGenerator->refreshPatternIfNeeded();
903 const auto start = TimePosition::fromSeconds (ms.
getStartTime());
904 const auto end = TimePosition::fromSeconds (ms.
getEndTime());
908 if (pos.getStart() > start)
911 if (pos.getEnd() < end)
912 mc.
setEnd (end + 0.1s,
true);
914 mc.mergeInMidiSequence (ms, automationType);
int indexOf(ParameterType elementToLookFor) const
void forceUpdateOfCachedValue()
Colour withHue(float newHue) const noexcept
bool isValid() const noexcept
void addTimeToMessages(double deltaTime) noexcept
double getStartTime() const noexcept
double getEndTime() const noexcept
MidiEventHolder * getEventPointer(int index) const noexcept
int getNumEvents() const noexcept
void add(String stringToAdd)
bool hasType(const Identifier &typeName) const noexcept
void removeChild(const ValueTree &child, UndoManager *undoManager)
bool isValid() const noexcept
void addChild(const ValueTree &child, int index, UndoManager *undoManager)
ValueTree getParent() const noexcept
ValueTree createCopy() const
ValueTree getChildWithName(const Identifier &type) const
ValueTree getOrCreateChildWithName(const Identifier &type, UndoManager *undoManager)
void sendPropertyChangeMessage(const Identifier &property)
double getMidiVisibleProportion() const
vertical scales for displaying the midi note editor
Base class for items that can contain clips.
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.
void setStart(TimePosition newStart, bool preserveSync, bool keepLength)
Sets the start time of the clip.
Track * getTrack() const override
Returns the parent Track this clip is on (if any).
BeatPosition getContentBeatAtTime(TimePosition) const
Returns the beat number (with offset) at the given time.
void setLength(TimeDuration newLength, bool preserveSync)
Sets the length of the clip.
virtual void setShowingTakes(bool shouldShow)
Sets whether the clip should be showing takes.
virtual void setSpeedRatio(double)
Sets a speed ratio i.e.
virtual bool isShowingTakes() const
Returns true if the clip is showing takes.
juce::UndoManager * getUndoManager() const
Returns the UndoManager.
ClipPosition getPosition() const override
Returns the ClipPosition on the parent Track.
void setEnd(TimePosition newEnd, bool preserveSync)
Sets the end of the clip.
void setPosition(ClipPosition newPosition)
Sets the position of the clip.
bool isCurrentTakeComp() const
Returns true if the current take is a comp.
int getNumTakes() const
Returns the number of takes that are not comps.
static constexpr double maximumLength
The maximum length an Edit can be.
Represents a launch quantisation.
void pitchTempoTrackChanged() override
Called when there are pitch or tempo changes made which might require clips to adjust timing informat...
TimeDuration getLoopLength() const override
Returns the length of loop in seconds.
PatternGenerator * getPatternGenerator() override
Returns the PatternGenerator for this clip if it has one.
TimePosition getLoopStart() const override
Returns the start time of the loop start point.
BeatDuration getLoopLengthBeats() const override
Returns the length of loop in beats.
LaunchQuantisation * getLaunchQuantisation() override
Some clip types can be launched, if that's possible, this returns a quantisation that can be used for...
void setLoopRange(TimeRange) override
Sets the loop range the clip should use in seconds.
int getNumTakes(bool includeComps) override
Returns the total number of takes.
void extendStart(TimePosition newStartTime)
This will extend the start time backwards, moving the notes along if this takes the offset below 0....
bool isLooping() const override
Returns true if this clip is currently looping.
void clearTakes() override
Clears any takes this clip has.
FollowActions * getFollowActions() override
Some clip types can be launched, if that's possible, this can be used to determine the action to perf...
void disableLooping() override
Disables all looping.
std::shared_ptr< LaunchHandle > getLaunchHandle() override
Some clip types can be launched, if that's possible, this returns a handle to trigger starting/stoppi...
bool canBeAddedTo(ClipOwner &) override
Tests whether this clip can go on the given parent.
Clip::Array unpackTakes(bool toNewTracks) override
Attempts to unpack the takes to new clips.
void setLoopRangeBeats(BeatRange) override
Sets the loop range the clip should use in beats.
void legatoNote(MidiNote ¬e, const juce::Array< MidiNote * > ¬esToUse, BeatPosition maxEndBeat, juce::UndoManager &)
Lengthens or shortens a note to match the start of the next note in the given array.
juce::StringArray getTakeDescriptions() const override
Returns the descriptions of any takes.
void setCurrentTake(int takeIndex) override
Sets a given take index to be the current take.
bool isCurrentTakeComp() override
Returns true if the current take is a comp.
void setNumberOfLoops(int) override
Sets the clip looping a number of times.
bool hasAnyTakes() const override
Returns true if this clip has any takes.
void copyFrom(const MidiList &, juce::UndoManager *)
Clears the current list and copies the others contents and properties.
MidiChannel getMidiChannel() const
Gets the list's midi channel number.
@ none
No automation, add the sequence as plain MIDI with the channel of the clip.
@ expression
Add the automation as EXP assuming the source sequence is MPE MIDI.
virtual void changed()
This should be called to send a change notification to any SelectableListeners that are registered wi...
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.
TimeRange getEditTimeRange() const
Returns the time range of this item.
#define TRANS(stringLiteral)
Type jlimit(Type lowerLimit, Type upperLimit, Type valueToConstrain) noexcept
bool isPositiveAndBelow(Type1 valueToTest, Type2 upperLimit) noexcept
int roundToInt(const FloatType value) noexcept
juce::String getName(LaunchQType t)
Retuns the name of a LaunchQType for display purposes.
bool canContainMIDI(const ClipOwner &co)
Returns true if this Track can contain MidiClip[s].
juce::Array< Track * > getAllTracks(const Edit &edit)
Returns all the tracks in an Edit.
Represents a duration in beats.
Represents a position in beats.
Represents a duration in real-life time.
Represents a position in real-life time.
TimePosition getStartOfSource() const
Returns what would be the the start of the source material in the timeline.
Provides a thread-safe way to share a clip's levels with an audio engine without worrying about the C...
Defines the place to insert Track[s].
#define CRASH_TRACER
This macro adds the current location to a stack which gets logged if a crash happens.