14namespace tracktion {
inline namespace engine
18using EditBeatPosition =
double;
19using ClipBeatPosition =
double;
20using SequenceBeatPosition =
double;
21using BlockBeatPosition =
double;
23using EditBeatDuration =
double;
24using ClipBeatDuration =
double;
25using BlockBeatDuration =
double;
32namespace chocMidiHelpers
57 [&, index = 0] (
const auto& v)
mutable
66 void update (
int controller,
uint8_t value)
68 values[controller] = value;
82 if (bankLSB && bankMSB)
96 case 0x00: bankMSB = v;
return true;
97 case 0x20: bankLSB = v;
return true;
120 const auto newestMsb = newestKind == Kind::rpn ? newestRpnMsb : newestNrpnMsb;
121 const auto newestLsb = newestKind == Kind::rpn ? newestRpnLsb : newestNrpnLsb;
123 auto lastSent =
std::tie (lastSentKind, lastSentMsb, lastSentLsb);
124 const auto newest =
std::tie (newestKind, newestMsb, newestLsb);
126 if (lastSent == newest || ! newestMsb || ! newestLsb)
139 case 0x65: newestRpnMsb = value; newestKind = Kind::rpn;
return true;
140 case 0x64: newestRpnLsb = value; newestKind = Kind::rpn;
return true;
141 case 0x63: newestNrpnMsb = value; newestKind = Kind::nrpn;
return true;
142 case 0x62: newestNrpnLsb = value; newestKind = Kind::nrpn;
return true;
149 enum class Kind { rpn, nrpn };
153 Kind lastSentKind = Kind::rpn, newestKind = Kind::rpn;
156 inline void createControllerUpdatesForTime (
const choc::midi::Sequence& sequence,
157 uint8_t channel,
double time,
165 for (
const auto& event : sequence)
167 if (! event.message.isShortMessage())
170 const auto& mm =
event.message;
172 if (! (mm.getChannel1to16() == channel && event.timeStamp <= time))
175 if (mm.isController())
177 const auto num = mm.getControllerNumber();
179 if (parameterNumberState.trySetProgramNumber (num, mm.getControllerValue()))
182 if (programChange.trySetBank (num, mm.getControllerValue()))
185 constexpr int passthroughs[] { 0x06, 0x26, 0x60, 0x61 };
189 parameterNumberState.sendIfNecessary (channel, event.timeStamp, dest);
190 dest.
add (toMidiMessage (event));
194 controllerValues.update (num, mm.getControllerValue());
197 else if (mm.isProgramChange())
199 programChange.setProgram (mm.getProgramChangeNumber());
201 else if (mm.isPitchWheel())
203 pitchWheel.update (mm.getPitchWheelValue());
207 pitchWheel.addToBuffer (channel, dest);
209 controllerValues.addToBuffer (channel, dest);
212 programChange.addToBuffer (channel, time, dest);
215 parameterNumberState.sendIfNecessary (channel, time, dest);
236 if (meh->noteOffObject !=
nullptr
237 && meh->message.isNoteOn())
240 meh->noteOffObject->message.getTimeStamp());
243 if (noteRange.getEnd() < loopRange.
getStart())
247 if (noteRange.getStart() >= loopRange.
getEnd())
251 if (noteRange.getStart() < loopRange.
getStart())
252 noteRange = noteRange.withStart (loopRange.
getStart());
255 if (noteRange.getEnd() > loopRange.
getEnd())
256 noteRange = noteRange.withEnd (loopRange.
getEnd());
260 if (noteRange.isEmpty())
263 res.
addEvent ({ meh->message, noteRange.getStart() });
264 res.
addEvent ({ meh->noteOffObject->message, noteRange.getEnd() });
266 else if (! meh->message.isNoteOff())
268 if (meh->message.getTimeStamp() >= loopRange.
getEnd())
271 if (meh->message.getTimeStamp() < loopRange.
getStart())
292 for (
auto& seq : sourceSequences)
293 res.push_back (createLoopSection (seq, loopRange));
298 inline void applyQuantisationToSequence (
const QuantisationType& q,
juce::MidiMessageSequence& ms,
bool canQuantiseNoteOffs)
303 const bool quantiseNoteOffs = canQuantiseNoteOffs && q.isQuantisingNoteOffs();
308 auto& m = e->message;
312 const auto noteOnTime = (q.roundBeatToNearest (BeatPosition::fromBeats (m.getTimeStamp()))).inBeats();
314 if (
auto noteOff = e->noteOffObject)
316 auto& mOff = noteOff->message;
318 if (quantiseNoteOffs)
320 auto noteOffTime = (q.roundBeatUp (BeatPosition::fromBeats (mOff.getTimeStamp()))).inBeats();
322 static constexpr double beatsToBumpUpBy = 1.0 / 512.0;
324 if (noteOffTime <= noteOnTime)
325 noteOffTime = q.roundBeatUp (BeatPosition::fromBeats (noteOnTime + beatsToBumpUpBy)).inBeats();
327 mOff.setTimeStamp (noteOffTime);
332 mOff.setTimeStamp (noteOnTime + (mOff.getTimeStamp() - m.getTimeStamp()) - 0.00001);
336 m.setTimeStamp (noteOnTime);
338 else if (m.isNoteOff() && quantiseNoteOffs)
340 m.setTimeStamp ((q.roundBeatUp (BeatPosition::fromBeats (m.getTimeStamp()))).inBeats());
349 auto& m = mh->message;
351 if (m.isNoteOn() || m.isNoteOff())
352 m.setTimeStamp (groove.beatsTimeToGroovyTime (BeatPosition::fromBeats (m.getTimeStamp()), grooveStrength).inBeats());
356 inline choc::midi::Sequence& addSequence (choc::midi::Sequence& dest,
const juce::MidiMessageSequence& src,
double timeStampOffset)
360 dest.events.push_back ({ meh->message.getTimeStamp() + timeStampOffset,
361 { meh->message.getRawData(), (
size_t) meh->message.getRawDataSize() } });
368 const choc::midi::Sequence& seq)
371 const auto seqLen = seq.events.size();
373 for (
size_t i = 0; i < seqLen; ++i)
375 const auto& m = seq.events[i].message;
377 if (! m.isShortMessage())
382 const auto note = m.getNoteNumber();
383 const auto chan = m.getChannel0to15();
385 for (
size_t j = i + 1; j < seqLen; ++j)
387 const auto& m2 = seq.events[j].message;
389 if (! m2.isShortMessage())
392 if (m2.getNoteNumber() == note
393 && m2.getChannel0to15() == chan
404 inline choc::midi::Sequence::Event* getNoteOff (
size_t noteOnIndex,
405 choc::midi::Sequence& ms,
408 auto found =
std::find_if (noteOffMap.begin(), noteOffMap.end(),
409 [noteOnIndex] (
const auto& m) { return m.first == noteOnIndex; });
411 if (found != noteOffMap.end())
412 return &ms.events[found->second];
417 inline const choc::midi::Sequence::Event* getNoteOff (
size_t noteOnIndex,
418 const choc::midi::Sequence& ms,
421 auto found =
std::find_if (noteOffMap.begin(), noteOffMap.end(),
422 [noteOnIndex] (
const auto& m) { return m.first == noteOnIndex; });
424 if (found != noteOffMap.end())
425 return &ms.events[found->second];
433 auto found =
std::find_if (noteOffMap.begin(), noteOffMap.end(),
434 [noteOnIndex] (
const auto& m) { return m.first == noteOnIndex; });
436 if (found != noteOffMap.end())
437 return found->second;
443 inline void applyQuantisationToSequence (
const QuantisationType& q,
bool canQuantiseNoteOffs,
449 const bool quantiseNoteOffs = canQuantiseNoteOffs && q.isQuantisingNoteOffs();
451 size_t index = ms.events.size();
457 if (! e.message.isShortMessage())
460 const auto& m = e.message;
464 const auto noteOnTime = q.roundBeatToNearest (BeatPosition::fromBeats (e.timeStamp)).inBeats();
466 if (auto noteOff = getNoteOff (index, ms, noteOffMap))
468 if (quantiseNoteOffs)
470 auto noteOffTime = (q.roundBeatUp (BeatPosition::fromBeats (noteOff->timeStamp))).inBeats();
472 static constexpr double beatsToBumpUpBy = 1.0 / 512.0;
474 if (noteOffTime <= noteOnTime)
475 noteOffTime = q.roundBeatUp (BeatPosition::fromBeats (noteOnTime + beatsToBumpUpBy)).inBeats();
477 noteOff->timeStamp = noteOffTime;
482 noteOff->timeStamp = (noteOnTime + (noteOff->timeStamp - e.timeStamp) - 0.00001);
486 e.timeStamp = noteOnTime;
488 else if (m.isNoteOff() && quantiseNoteOffs)
490 e.timeStamp = q.roundBeatUp (BeatPosition::fromBeats (e.timeStamp)).inBeats();
495 inline void applyGrooveToSequence (
const GrooveTemplate& groove,
float grooveStrength, choc::midi::Sequence& ms)
498 if (e.message.isNoteOn() || e.message.isNoteOff())
499 e.timeStamp = groove.beatsTimeToGroovyTime (BeatPosition::fromBeats (e.timeStamp), grooveStrength).inBeats();
502 inline void createMessagesForTime (MidiMessageArray& destBuffer,
503 const choc::midi::Sequence& sourceSequence,
507 LiveClipLevel& clipLevel,
508 bool useMPEChannelMode, MidiMessageArray::MPESourceID midiSourceID,
511 if (useMPEChannelMode)
513 const auto indexOfTime = [&]() ->
size_t
515 const auto numEvents = sourceSequence.events.size();
517 for (
size_t i = 0; i < numEvents; ++i)
518 if (sourceSequence.events[i].timeStamp >= time)
526 for (
int i = channelNumbers.
getStart(); i < channelNumbers.
getEnd(); ++i)
529 for (
auto& m : controllerMessagesScratchBuffer)
530 destBuffer.addMidiMessage (m, 0.0001, midiSourceID);
537 for (
int i = channelNumbers.
getStart(); i < channelNumbers.
getEnd(); ++i)
538 chocMidiHelpers::createControllerUpdatesForTime (sourceSequence, (uint8_t) i,
time, controllerMessagesScratchBuffer);
540 for (
auto& m : controllerMessagesScratchBuffer)
541 destBuffer.addMidiMessage (m, midiSourceID);
544 if (! clipLevel.isMute())
546 auto volScale = clipLevel.getGain();
548 for (
size_t i = 0; i < sourceSequence.events.size(); ++i)
550 auto e = sourceSequence.events[i];
551 const auto& m = e.message;
556 if (
auto noteOffEvent = getNoteOff (i, sourceSequence, noteOffMap))
558 if (e.timeStamp >= time)
562 if (noteOffEvent->timeStamp > time + 0.0001)
564 auto data = m.data();
566 m2.multiplyVelocity (volScale);
569 destBuffer.addMidiMessage (m2, 0.0001, midiSourceID);
577 inline ActiveNoteList getNotesOnAtTime (
const choc::midi::Sequence& sourceSequence,
582 ActiveNoteList noteList;
584 if (clipLevel.isMute())
587 for (
size_t i = 0; i < sourceSequence.events.size(); ++i)
589 const auto& e = sourceSequence.events[i];
590 const auto& m = e.message;
595 if (! channelNumbers.
contains ((
int) m.getChannel1to16()))
598 if (
auto noteOffEvent = getNoteOff (i, sourceSequence, noteOffMap))
600 if (e.timeStamp >= time)
604 if (noteOffEvent->timeStamp > time + 0.0001)
605 noteList.startNote ((
int) m.getChannel1to16(), (
int) m.getNoteNumber());
612 inline void clipSequenceToRange (choc::midi::Sequence& sequence,
const juce::Range<double> clipRange,
623 for (
auto& m : sequence)
624 if (m.message.isShortMessage())
625 if (auto& sm = m.message; sm.isNoteOn() || sm.isNoteOff())
626 m.timeStamp = clipRange.
clipValue (m.timeStamp);
629 for (
int i = (
int) sequence.events.size(); --i >= 0;)
631 auto index =
static_cast<size_t> (i);
632 const auto& e = sequence.events[index];
633 const auto& m = e.message;
638 if (
auto noteOffIndex = getNoteOffIndex (index, noteOffMap))
640 if (*noteOffIndex < index)
643 const auto noteLength = sequence.events[*noteOffIndex].timeStamp - e.timeStamp;
645 if (noteLength > 0.0)
648 sequence.events[*noteOffIndex].timeStamp = timeStampToRemoveFlag;
649 sequence.events[index].timeStamp = timeStampToRemoveFlag;
654 sequence.events.erase (
std::remove_if (sequence.events.begin(), sequence.events.end(),
656 sequence.events.end());
674 bool useMPEChannelMode, MidiMessageArray::MPESourceID,
677 juce::ignoreUnused (destBuffer,
time, channelNumbers, useMPEChannelMode, controllerMessagesScratchBuffer);
689 virtual void setTime (
double) = 0;
690 virtual bool advance() = 0;
692 virtual bool exhausted() = 0;
702 : sequence (seq), noteOffMap (noteOffs)
707 SequenceBeatPosition
time,
711 bool useMPEChannelMode, MidiMessageArray::MPESourceID midiSourceID,
715 scratchBuffer.clear();
716 cleanedBufferToMerge.clear();
718 MidiHelpers::createMessagesForTime (scratchBuffer,
719 sequence, noteOffMap,
723 useMPEChannelMode, midiSourceID,
724 controllerMessagesScratchBuffer);
727 for (
const auto& e : scratchBuffer)
731 if (! activeNoteList.isNoteActive (e.getChannel(), e.getNoteNumber()))
733 cleanedBufferToMerge.add (e);
734 activeNoteList.startNote (e.getChannel(), e.getNoteNumber());
737 else if (e.isNoteOff())
739 if (activeNoteList.isNoteActive (e.getChannel(), e.getNoteNumber()))
741 activeNoteList.clearNote (e.getChannel(), e.getNoteNumber());
742 cleanedBufferToMerge.add (e);
747 cleanedBufferToMerge.add (e);
751 destBuffer.mergeFrom (cleanedBufferToMerge);
756 return MidiHelpers::getNotesOnAtTime (sequence, noteOffMap,
762 void setTime (SequenceBeatPosition pos)
override
764 auto numEvents = sequence.events.size();
771 if (sequence.events[currentIndex].timeStamp >= pos)
773 while (currentIndex > 0 && sequence.events[currentIndex - 1].timeStamp >= pos)
778 while (currentIndex < numEvents && sequence.events[currentIndex].timeStamp < pos)
786 [[ maybe_unused ]]
auto numEvents = sequence.events.size();
787 jassert (currentIndex < numEvents);
792 bool advance()
override
795 return ! exhausted();
798 bool exhausted()
override
800 return currentIndex >= sequence.events.size();
803 const choc::midi::Sequence& sequence;
805 size_t currentIndex = 0;
817 : sequences (std::move (seq)),
818 quantisation (std::move (qt)),
819 groove (grooveTemplate),
820 grooveStrength (grooveStrength_)
823 cacheSequence (0.0, {});
826 size_t maxNumEvents = 0, maxNumNoteOns = 0;
828 for (
auto& sequence : sequences)
830 size_t squenceNumEvents = 0, squenceNumNoteOns = 0;
832 for (
auto meh : sequence)
836 if (meh->message.isNoteOn())
840 maxNumEvents =
std::max (squenceNumEvents, maxNumEvents);
841 maxNumNoteOns =
std::max (squenceNumNoteOns, maxNumNoteOns);
844 noteOffMap.
reserve (maxNumNoteOns);
845 currentSequence.events.reserve (maxNumEvents);
849 EditBeatPosition editBeatPosition,
853 bool useMPEChannelMode, MidiMessageArray::MPESourceID midiSourceID,
856 generator.createMessagesForTime (destBuffer,
861 useMPEChannelMode, midiSourceID,
862 controllerMessagesScratchBuffer);
867 return MidiHelpers::getNotesOnAtTime (currentSequence, noteOffMap,
873 void setTime (EditBeatPosition editBeatPosition)
override
875 generator.setTime (editBeatPosition);
889 if (sequences.
size() > 0)
890 if (++currentSequenceIndex >= sequences.
size())
891 currentSequenceIndex = 0;
894 currentSequence.events.clear();
896 if (currentSequenceIndex < sequences.
size())
897 MidiHelpers::addSequence (currentSequence, sequences[currentSequenceIndex], offsetBeats);
900 MidiHelpers::createNoteOffMap (noteOffMap, currentSequence);
901 MidiHelpers::applyQuantisationToSequence (quantisation,
false, currentSequence, noteOffMap);
903 if (! groove.isEmpty())
904 MidiHelpers::applyGrooveToSequence (groove, grooveStrength, currentSequence);
906 currentSequence.sortEvents();
910 MidiHelpers::createNoteOffMap (noteOffMap, currentSequence);
911 MidiHelpers::clipSequenceToRange (currentSequence, *clipRange, noteOffMap);
914 MidiHelpers::createNoteOffMap (noteOffMap, currentSequence);
916 cachedSequenceOffset = offsetBeats;
921 auto e = generator.getEvent();
926 bool advance()
override
928 return generator.advance();
931 bool exhausted()
override
933 return generator.exhausted();
939 choc::midi::Sequence currentSequence;
945 float grooveStrength = 0.0;
947 size_t currentSequenceIndex = 0;
948 double cachedSequenceOffset = 0.0;
960 : generator (std::move (gen)),
961 activeNoteList (std::move (anl)),
962 clipRange (clipRangeToUse),
963 loopTimes (loopTimesToUse)
969 EditBeatPosition editBeatPosition,
973 bool useMPEChannelMode, MidiMessageArray::MPESourceID midiSourceID,
977 setTime (editBeatPosition);
979 generator->createMessagesForTime (destBuffer,
980 editBeatPositionToSequenceBeatPosition (editBeatPosition),
984 useMPEChannelMode, midiSourceID,
985 controllerMessagesScratchBuffer);
990 return generator->getNotesOnAtTime (editBeatPositionToSequenceBeatPosition (editBeatPosition),
995 void setTime (EditBeatPosition editBeatPosition)
override
997 const ClipBeatPosition clipPos = editBeatPosition - clipRange.
getStart();
1001 generator->setTime (clipPos);
1007 setLoopIndex (
static_cast<int> (clipPos / loopTimes.
getLength()));
1008 generator->setTime (sequencePos);
1016 auto e = generator->getEvent();
1017 e.addToTimeStamp (offsetBeats);
1021 bool advance()
override
1023 generator->advance();
1025 if (exhausted() && ! loopTimes.
isEmpty())
1027 setLoopIndex (loopIndex + 1);
1028 generator->setTime (0.0);
1034 bool exhausted()
override
1036 return generator->exhausted();
1047 SequenceBeatPosition editBeatPositionToSequenceBeatPosition (EditBeatPosition editBeatPosition)
const
1049 const ClipBeatPosition clipPos = editBeatPosition - clipRange.
getStart();
1059 void setLoopIndex (
int newLoopIndex)
1061 if (newLoopIndex == loopIndex)
1064 loopIndex = newLoopIndex;
1065 const auto sequenceOffset = clipRange.
getStart() + (loopIndex * loopTimes.
getLength());
1066 generator->cacheSequence (sequenceOffset, loopTimes + sequenceOffset);
1076 ClipBeatDuration offsetToUse,
1078 : generator (std::move (gen)),
1079 clipOffset (offsetToUse),
1080 dynamicOffset (std::move (dynamicOffsetToUse))
1085 EditBeatPosition editBeatPosition,
1089 bool useMPEChannelMode, MidiMessageArray::MPESourceID midiSourceID,
1092 generator->createMessagesForTime (destBuffer,
1093 editBeatPosition + getOffset(),
1097 useMPEChannelMode, midiSourceID,
1098 controllerMessagesScratchBuffer);
1103 return generator->getNotesOnAtTime (editBeatPosition + getOffset(),
1108 void setTime (EditBeatPosition editBeatPosition)
override
1110 generator->setTime (editBeatPosition + getOffset());
1115 auto e = generator->getEvent();
1116 e.addToTimeStamp (-getOffset());
1120 bool advance()
override
1122 return generator->advance();
1125 bool exhausted()
override
1127 return generator->exhausted();
1133 const ClipBeatDuration clipOffset;
1136 ClipBeatDuration getOffset()
const
1138 return clipOffset - dynamicOffset->inBeats();
1148 BeatRange editRangeToUse,
1149 BeatRange loopRangeToUse,
1153 float grooveStrength_)
1154 : sequences (std::move (sequencesToUse)),
1155 editRange (editRangeToUse),
1156 loopRange (loopRangeToUse),
1157 offset (offsetToUse),
1158 quantisation (quantisation_),
1160 grooveStrength (grooveStrength_)
1166 bool clipPropertiesHaveChanged,
size_t lastSequencesHash,
1169 if (isInitialised())
1173 dynamicOffsetBeats = std::move (dynamicOffsetBeatsToUse);
1174 shouldCreateMessagesForTime = clipPropertiesHaveChanged || noteListToUse ==
nullptr;
1175 activeNoteList = noteListToUse ? std::move (noteListToUse)
1178 const EditBeatRange clipRangeRaw { editRange.getStart().inBeats(), editRange.getEnd().inBeats() };
1181 if (! loopRangeRaw.isEmpty())
1182 sequences = MidiHelpers::createLoopSection (std::move (sequences), loopRangeRaw);
1186 if (sequencesHash != lastSequencesHash || clipPropertiesHaveChanged)
1187 shouldSendNoteOffsForNotesNoLongerPlaying =
true;
1190 std::move (quantisation), std::move (groove), grooveStrength);
1192 activeNoteList, clipRangeRaw, loopRangeRaw);
1194 offset.
inBeats(), dynamicOffsetBeats);
1202 return activeNoteList;
1205 void processSection (
MidiMessageArray& destBuffer, choc::buffer::FrameCount numSamples,
1206 BeatRange sectionEditBeatRange,
1207 TimeRange sectionEditTimeRange,
1210 bool useMPEChannelMode,
1211 MidiMessageArray::MPESourceID midiSourceID,
1213 bool isContiguousWithPreviousBlock,
1214 bool lastBlockOfLoop)
1216 const auto secondsPerBeat = sectionEditTimeRange.getLength() / sectionEditBeatRange.getLength().inBeats();
1217 const auto blockStartBeatRelativeToClip = sectionEditBeatRange.getStart() - (editRange.getStart() + *dynamicOffsetBeats);
1219 const auto volScale = clipLevel.
getGain();
1220 const auto isLastBlockOfClip = sectionEditBeatRange.containsInclusive ((editRange.getEnd() + *dynamicOffsetBeats));
1221 const double beatDurationOfOneSample = sectionEditBeatRange.getLength().inBeats() / numSamples;
1222 const auto timePositionOfLastSample = sectionEditTimeRange.getLength() > 0_td
1223 ? (sectionEditTimeRange.getLength() - sectionEditTimeRange.getLength() / numSamples).inSeconds()
1226 const auto clipIntersection = sectionEditBeatRange.getIntersectionWith (editRange + *dynamicOffsetBeats);
1228 if (clipIntersection.isEmpty())
1230 if (activeNoteList->areAnyNotesActive())
1231 MidiNodeHelpers::createNoteOffs (*activeNoteList,
1234 timePositionOfLastSample,
1242 if (shouldSendNoteOffsForNotesNoLongerPlaying)
1247 const auto currentlyPlayingNoteList = generator->getNotesOnAtTime (clipIntersection.getStart().inBeats(), channelNumbers, clipLevel);
1249 if (activeNoteList->areAnyNotesActive())
1250 activeNoteList->iterate ([&] (
auto chan,
auto note)
1252 if (currentlyPlayingNoteList.isNoteActive (chan, note))
1256 activeNoteList->clearNote (chan, note);
1259 shouldSendNoteOffsForNotesNoLongerPlaying =
false;
1262 if (! isContiguousWithPreviousBlock
1263 || blockStartBeatRelativeToClip <= 0.00001_bd)
1265 MidiNodeHelpers::createNoteOffs (*activeNoteList,
1270 shouldCreateMessagesForTime =
true;
1273 if (shouldCreateMessagesForTime)
1275 generator->createMessagesForTime (destBuffer, clipIntersection.getStart().inBeats(),
1277 channelNumbers, clipLevel, useMPEChannelMode, midiSourceID,
1278 controllerMessagesScratchBuffer);
1279 shouldCreateMessagesForTime =
false;
1282 generator->setTime (clipIntersection.getStart().inBeats());
1289 if (generator->exhausted())
1292 auto e = generator->getEvent();
1293 const EditBeatPosition editBeatPosition = e.getTimeStamp();
1296 if (editBeatPosition >= clipIntersection.getEnd().inBeats())
1299 BlockBeatPosition blockBeatPosition = editBeatPosition - sectionEditBeatRange.getStart().inBeats();
1302 if (blockBeatPosition < -0.000001)
1304 generator->advance();
1308 blockBeatPosition =
std::max (blockBeatPosition, 0.0);
1311 if (e.isNoteOff() &&
juce::isWithin (editBeatPosition, clipIntersection.getEnd().inBeats(), beatDurationOfOneSample))
1312 blockBeatPosition = blockBeatPosition - beatDurationOfOneSample;
1314 e.multiplyVelocity (volScale);
1315 const auto eventTimeSeconds = blockBeatPosition * secondsPerBeat.inSeconds();
1316 destBuffer.addMidiMessage (e, eventTimeSeconds, midiSourceID);
1320 activeNoteList->startNote (e.getChannel(), e.getNoteNumber());
1321 else if (e.isNoteOff())
1322 activeNoteList->clearNote (e.getChannel(), e.getNoteNumber());
1324 generator->advance();
1328 if (lastBlockOfLoop)
1329 MidiNodeHelpers::createNoteOffs (*activeNoteList,
1332 timePositionOfLastSample,
1335 if (isLastBlockOfClip)
1337 const auto endOfClipBeats = (editRange.getEnd() + *dynamicOffsetBeats) - sectionEditBeatRange.getStart();
1340 auto eventTimeSeconds = (endOfClipBeats.inBeats() - beatDurationOfOneSample) * secondsPerBeat.inSeconds();
1342 MidiNodeHelpers::createNoteOffs (*activeNoteList,
1352 return editRange.getStart() == o.editRange.getStart()
1353 && loopRange == o.loopRange
1354 && offset == o.offset
1355 && quantisation == o.quantisation
1356 && groove == o.groove
1357 && grooveStrength == o.grooveStrength;
1360 size_t getSequencesHash()
const
1362 return sequencesHash;
1365 bool isInitialised()
const
1376 size_t sequencesHash = 0;
1377 const BeatRange editRange, loopRange;
1381 float grooveStrength = 0.0f;
1382 bool initialised =
false;
1384 bool shouldCreateMessagesForTime =
false, shouldSendNoteOffsForNotesNoLongerPlaying =
false;
1394 BeatRange editTimeRange,
1395 BeatRange sequenceLoopRange,
1402 float grooveStrength,
1405 channelNumbers (midiChannelNumbers),
1406 useMPEChannelMode (useMPE),
1407 editRange (editTimeRange),
1408 clipLevel (liveClipLevel),
1409 editItemID (editItemIDToUse),
1410 shouldBeMutedDelegate (
std::move (shouldBeMuted)),
1411 wasMute (liveClipLevel.isMute())
1430 return generatorAndNoteList->getActiveNoteList();
1439 (*dynamicOffsetBeats) = newOffset;
1444 MidiNodeHelpers::createNoteOffs (*generatorAndNoteList->getActiveNoteList(),
1447 timestampForNoteOffs,
1455 props.hasMidi =
true;
1456 props.nodeID = (
size_t) editItemID.getRawID();
1462 if (generatorAndNoteList->isInitialised())
1465 jassert (info.nodeGraphToReplace ==
nullptr);
1470 bool clipPropertiesHaveChanged =
false;
1471 size_t lastSequencesHash = 0;
1473 if (
auto oldNode = findNodeWithIDIfNonZero<LoopingMidiNode> (info.nodeGraphToReplace,
getNodeProperties().nodeID))
1475 midiSourceID = oldNode->midiSourceID;
1476 activeNoteList = oldNode->generatorAndNoteList->getActiveNoteList();
1477 clipPropertiesHaveChanged = ! generatorAndNoteList->hasSameContentAs (*oldNode->generatorAndNoteList);
1478 lastSequencesHash = oldNode->generatorAndNoteList->getSequencesHash();
1479 dynamicOffsetBeats = oldNode->dynamicOffsetBeats;
1482 generatorAndNoteList->initialise (activeNoteList, clipPropertiesHaveChanged, lastSequencesHash, dynamicOffsetBeats);
1492 SCOPED_REALTIME_CHECK
1493 if (shouldBeMutedDelegate && shouldBeMutedDelegate())
1498 if (
const bool mute = clipLevel.isMute(); mute)
1500 if (mute != wasMute)
1503 MidiNodeHelpers::createNoteOffs (*generatorAndNoteList->getActiveNoteList(),
1513 generatorAndNoteList->processSection (pc.buffers.midi, pc.numSamples,
void ensureStorageAllocated(int minNumElements)
void add(const ElementType &newElement)
MidiEventHolder * addEvent(const MidiMessage &newMessage, double timeAdjustment=0)
void updateMatchedPairs() noexcept
MidiEventHolder * getEventPointer(int index) const noexcept
int getNumEvents() const noexcept
static MidiMessage pitchWheel(int channel, int position) noexcept
static MidiMessage controllerEvent(int channel, int controllerType, int value) noexcept
static MidiMessage noteOff(int channel, int noteNumber, float velocity) noexcept
void addToTimeStamp(double delta) noexcept
static MidiMessage programChange(int channel, int programNumber) noexcept
constexpr ValueType getStart() const noexcept
constexpr bool isEmpty() const noexcept
constexpr ValueType getEnd() const noexcept
ValueType clipValue(const ValueType value) const noexcept
constexpr ValueType getLength() const noexcept
constexpr bool contains(const ValueType position) const noexcept
bool isReadyToProcess() override
Should return true when this node is ready to be processed.
tracktion::graph::NodeProperties getNodeProperties() override
Should return the properties of the node.
const std::shared_ptr< ActiveNoteList > & getActiveNoteList() const
Returns the ActiveNoteList in use for this Node.
void prepareToPlay(const tracktion::graph::PlaybackInitialisationInfo &) override
Called once before playback begins for each node.
void process(ProcessContext &) override
Called when the node is to be processed.
void killActiveNotes(MidiMessageArray &, double timestampForNoteOffs)
Iterates the ActiveNoteList adding note-off events for the active notes and then resets them.
void setDynamicOffsetBeats(BeatDuration) override
Sets an offset to be applied to all times in this node, effectively shifting it forwards or backwards...
Base class for Nodes that provides information about the current process call.
TimeRange getEditTimeRange() const
Returns the edit time range of the current process block.
BeatRange getEditBeatRange() const
Returns the edit beat range of the current process block.
tracktion::graph::PlayHeadState & getPlayHeadState()
Returns the PlayHeadState in use.
tracktion::graph::PlayHead & getPlayHead()
Returns the PlayHead in use.
Struct to describe a single iteration of a process call.
bool isPlaying() const noexcept
Returns true is the play head is currently playing.
bool isWithin(Type a, Type b, Type tolerance) noexcept
constexpr bool approximatelyEqual(Type a, Type b, Tolerance< Type > tolerance=Tolerance< Type >{} .withAbsolute(std::numeric_limits< Type >::min()) .withRelative(std::numeric_limits< Type >::epsilon()))
void ignoreUnused(Types &&...) noexcept
juce::MidiMessage toMidiMessage(const choc::midi::Sequence::Event &e)
Converts a choc::midi event to a juce::MidiMessage.
Represents a duration in beats.
constexpr double inBeats() const
Returns the position as a number of beats.
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...
float getGain() const noexcept
Returns the clip's absolute gain.
static void reconstructExpression(juce::Array< juce::MidiMessage > &mpeMessagesToAddAtStart, const juce::MidiMessageSequence &data, int trimIndex, int channel)
Reconstruct note expression for a particular channel.
Holds the state of a process call.
Holds some really basic properties of a node.
Passed into Nodes when they are being initialised, to give them useful contextual information that th...