11namespace tracktion {
inline namespace engine
34 void hiResTimerCallback()
override
38 if (items.
size() == 0)
44 for (
size_t i = 0; i < items.
size(); i++)
46 if (items[i].when > now)
52 owner.handleIncomingMidiMessage (m);
68 void clear (
int channel,
int note)
73 [&](
const Item& i) ->
bool { return i.m.getChannel() == channel && i.m.getNoteNumber() == note; }),
98 enum class HasFinished { no, yes };
101 : callback (std::move (checkTargetFinishedCallback))
108 if (contains_v (targetIDs, targetID))
111 targetIDs.push_back (targetID);
117 return contains_v (targetIDs, targetID);
121 LambdaTimer timer { [
this] { checkForTargetsThatHaveFinished(); } };
125 void checkForTargetsThatHaveFinished()
129 for (
auto targetID : targetIDs)
130 if (callback (targetID) == HasFinished::yes)
134 [&] (
auto tID) { return contains_v (targetsToErase, tID); }),
137 if (targetIDs.
empty())
152 const int channel = m.getChannel();
154 if (m.isController())
156 const int number = m.getControllerNumber();
157 const int value = m.getControllerValue();
161 lastNRPNFine = value;
164 else if (number == 99)
166 lastNRPNCoarse = value;
169 else if (number == 100)
174 else if (number == 101)
176 lastRPNCoarse = value;
179 else if (number == 6)
183 lastParamNumber = 0x20000 + (lastNRPNCoarse << 7) + lastNRPNFine;
185 lastParamNumber = 0x30000 + (lastRPNCoarse << 7) + lastRPNFine;
187 if (lastParamNumber != 0)
189 controllerMoved (lastParamNumber, value, channel);
193 else if (number == 0x26)
201 lastParamNumber = 0x10000 + number;
202 controllerMoved (lastParamNumber, value, channel);
205 else if (m.isChannelPressure())
207 lastParamNumber = 0x40000;
208 controllerMoved (lastParamNumber, m.getChannelPressureValue(), channel);
213 void controllerMoved (
int number,
int value,
int channel)
217 pendingMessages.
add ({ number, channel, value / 127.0f });
223 void handleAsyncUpdate()
override
229 pendingMessages.
swapWith (messages);
232 if (
auto pcm = ParameterControlMappings::getCurrentlyFocusedMappings (engine))
233 for (
const auto& m : messages)
234 pcm->sendChange (m.controllerID, m.newValue, m.channel);
237 int lastParamNumber = 0;
238 int lastRPNCoarse = 0, lastRPNFine = 0, lastNRPNCoarse = 0, lastNRPNFine = 0;
239 bool wasNRPN =
false;
258 lengthInSeconds = e.
getPropertyStorage().getProperty (SettingID::retrospectiveRecord, 30);
266 + adjust - lengthInSeconds;
268 if (m.getTimeStamp() > cutoffTime)
273 for (
int i = 0; i < sequence.
size() && sequence[i].getTimeStamp() < cutoffTime; i++)
290 for (
auto m : sequence)
307 if (evt->message.isNoteOn() && evt->noteOffObject !=
nullptr)
318 if (evt->message.isNoteOnOrOff() && ! usedIndexes.
contains (i))
328 double lengthInSeconds = 0;
337 levelMeasurer.setShowMidi (
true);
341 std::memset (keyDownVelocities, 0,
sizeof (keyDownVelocities));
343 keyboardState.addListener (
this);
346MidiInputDevice::~MidiInputDevice()
349 notifyListenersOfDeletion();
352void MidiInputDevice::setEnabled (
bool b)
364 monitorMode = defaultMonitorMode;
365 recordingEnabled =
true;
366 mergeRecordings =
true;
367 replaceExistingClips =
false;
375 overrideNoteVels =
false;
376 disallowedChannels.
clear();
380 if (! isTrackDevice())
383 monitorMode = magic_enum::enum_cast<MonitorMode> (n->
getStringAttribute (
"monitorMode").toStdString()).value_or (monitorMode);
386 replaceExistingClips = n->
getBoolAttribute (
"replaceExisting", replaceExistingClips);
389 channelToUse = MidiChannel::fromChannelOrZero (n->
getIntAttribute (
"channel", channelToUse.getChannelNumber()));
392 overrideNoteVels = n->
getBoolAttribute (
"useFullVelocity", overrideNoteVels);
399 connectionStateChanged();
402 if (minimumLengthMs > 0 && noteDispatcher ==
nullptr)
404 else if (minimumLengthMs <= 0)
405 noteDispatcher =
nullptr;
407 lastNoteOns.
resize (minimumLengthMs > 0 ? 128 * 16 : 0);
416 n.
setAttribute (
"replaceExisting", replaceExistingClips);
417 n.
setAttribute (
"recordToNoteAutomation", recordToNoteAutomation);
418 n.
setAttribute (
"quantisation", quantisation.getType (
false));
419 n.
setAttribute (
"channel", channelToUse.getChannelNumber());
426 if (! disallowedChannels.
isZero())
429 if (! noteFilterRange.isAllNotes())
431 n.
setAttribute (
"noteStart", noteFilterRange.startNote);
439 if (
auto in = edit->getCurrentInstanceForInputDevice (
this))
445void MidiInputDevice::setChannelAllowed (
int midiChannel,
bool allowed)
447 if (allowed != isChannelAllowed (midiChannel))
449 disallowedChannels.
setBit (midiChannel - 1, ! allowed);
455void MidiInputDevice::setNoteFilterRange (NoteFilterRange newRange)
457 if (noteFilterRange.startNote != newRange.startNote
458 || noteFilterRange.endNote != newRange.endNote)
460 noteFilterRange = newRange;
466void MidiInputDevice::setChannelToUse (
int newChan)
468 auto chan = MidiChannel::fromChannelOrZero (newChan);
470 if (channelToUse != chan)
478void MidiInputDevice::setProgramToUse (
int prog)
484void MidiInputDevice::setBankToUse (
int bank)
489void MidiInputDevice::setOverridingNoteVelocities (
bool b)
491 if (overrideNoteVels != b)
493 overrideNoteVels = b;
499void MidiInputDevice::setManualAdjustmentMs (
double ms)
501 if (manualAdjustMs != ms)
509void MidiInputDevice::setMinimumLengthMs (
double ms)
511 if (minimumLengthMs != ms)
513 minimumLengthMs = ms;
517 if (minimumLengthMs > 0 && noteDispatcher ==
nullptr)
519 else if (minimumLengthMs <= 0)
520 noteDispatcher =
nullptr;
522 lastNoteOns.
resize (minimumLengthMs > 0 ? 128 * 16 : 0);
528 return getName().
contains (
"Seaboard");
534 if (eventReceivedFromDevice)
542 if (eventReceivedFromDevice)
551 handleIncomingMidiMessage (m);
555void MidiInputDevice::updateRetrospectiveBufferLength (
double length)
557 if (retrospectiveBuffer !=
nullptr)
558 retrospectiveBuffer->lengthInSeconds = length;
562void MidiInputDevice::addInstance (MidiInputDeviceInstanceBase* i) {
const juce::ScopedLock sl (instanceLock); instances.addIfNotAlreadyThere (i); }
563void MidiInputDevice::removeInstance (MidiInputDeviceInstanceBase* i) {
const juce::ScopedLock sl (instanceLock); instances.removeAllInstancesOf (i); }
565void MidiInputDevice::connectionStateChanged()
567 if (isTrackDevice() && (! enabled))
570 if (programToUse > 0 && channelToUse.isValid())
574 if (
auto destTrack = getDestinationTracks().getFirst())
575 bankID = destTrack->getIdForBank (bankToUse);
579 auto chan = channelToUse.getChannelNumber();
591 if (overrideNoteVels)
600 keysDown[noteNum] =
true;
601 keyDownVelocities[noteNum] = message.
getVelocity();
612 keysUp[noteNum] =
true;
619void MidiInputDevice::timerCallback()
625 bool keysDownCopy[128], keysUpCopy[128];
626 uint8_t keyDownVelocitiesCopy[128];
631 memcpy (keysUpCopy, keysUp,
sizeof (keysUp));
632 memcpy (keysDownCopy, keysDown,
sizeof (keysDown));
633 memcpy (keyDownVelocitiesCopy, keyDownVelocities,
sizeof (keyDownVelocities));
637 std::memset (keyDownVelocities, 0,
sizeof (keyDownVelocities));
640 for (
int i = 0; i < 128; ++i)
645 vels.
add (keyDownVelocitiesCopy[i]);
653 if (down.
size() > 0 || up.
size() > 0)
654 for (
auto t : getDestinationTracks())
655 midiKeyChangeDispatcher->listeners.call (&MidiKeyChangeDispatcher::Listener::midiKeyStateChanged, t, down, vels, up);
659static bool trackContainsClipWithName (
const AudioTrack& track,
const juce::String& name)
661 for (
auto& c : track.getClips())
662 if (c->
getName().equalsIgnoreCase (name))
668static juce::String getNameForNewClip (AudioTrack& track)
670 for (
int index = 1; ; ++index)
672 auto clipName = track.getName() +
" " +
TRANS(
"Recording") +
" " +
juce::String (index);
674 if (! trackContainsClipWithName (track, clipName))
681 if (
auto slot =
dynamic_cast<ClipSlot*
> (&owner))
682 if (
auto at =
dynamic_cast<AudioTrack*
> (&slot->track))
683 return getNameForNewClip (*at);
685 if (
auto at =
dynamic_cast<AudioTrack*
> (&owner))
686 return getNameForNewClip (*at);
692Clip* MidiInputDevice::addMidiAsTransaction (Edit& ed, EditItemID targetID,
694 TimeRange position, MergeMode merge, MidiChannel midiChannel)
697 Clip* createdClip =
nullptr;
700 auto clipOwner = track !=
nullptr ?
static_cast<ClipOwner*
> (track)
701 : static_cast<ClipOwner*> (clipSlot);
709 quantisation.applyQuantisationToSequence (ms, ed, position.getStart());
711 bool needToAddClip =
true;
716 if ((merge == MergeMode::optional && mergeRecordings) || merge == MergeMode::always)
717 needToAddClip = ! track->mergeInMidiSequence (ms, position.getStart(),
nullptr, automationType);
719 if (takeClip !=
nullptr)
721 if (
auto midiClip =
dynamic_cast<MidiClip*
> (takeClip))
725 midiClip->addTake (ms, automationType);
727 midiClip->setMidiChannel (midiChannel);
729 if (programToUse > 0)
730 midiClip->getSequence().addControllerEvent ({}, MidiControllerEvent::programChangeType,
731 (programToUse - 1) << 7, &ed.getUndoManager());
734 else if (needToAddClip)
738 if ((replaceExistingClips && merge == MergeMode::optional)
742 track->deleteRegion (position,
nullptr);
744 clipSlot->setClip (
nullptr);
747 if (
auto mc =
insertMIDIClip (*clipOwner, getNameForNewClip (*clipOwner), position))
751 track->mergeInMidiSequence (std::move (ms), mc->getPosition().getStart(), mc.get(), automationType);
752 mc->setLength (position.getLength(),
true);
756 mergeInMidiSequence (*mc, std::move (ms), toDuration (mc->getPosition().getStart()), automationType);
759 if (recordToNoteAutomation)
760 mc->setMPEMode (
true);
762 mc->setMidiChannel (midiChannel);
764 if (programToUse > 0)
765 mc->getSequence().addControllerEvent ({}, MidiControllerEvent::programChangeType,
766 (programToUse - 1) << 7, &ed.getUndoManager());
768 createdClip = mc.get();
786 getMidiInput().addInstance (
this);
791 getMidiInput().removeInstance (
this);
806 return getRecordStopper().isQueued (targetID);
811 return getContextForID (t.itemID) !=
nullptr
812 && ! getMidiInput().mergeRecordings;
822 if (
auto rc = getContextForID (targetID))
823 return rc->liveNotes;
833 scopedActiveRecordingDevice (epc),
834 punchRange (punchRange_)
837 liveNotes->
reset (100);
841 const TimeRange punchRange;
847 std::function<void (tl::expected<Clip::Array, juce::String>)> stopCallback;
853 TRACKTION_ASSERT_MESSAGE_THREAD
858 if (dest->recordEnabled)
859 params.
targets.push_back (dest->targetID);
861 for (
auto targetID : params.
targets)
866 results.emplace_back (std::move (recContext));
874 TRACKTION_ASSERT_MESSAGE_THREAD
875 bool hasAddedContexts =
false;
877 for (
auto& recContext : newContexts)
881 const auto targetID = midiContext->targetID;
888 hasAddedContexts =
true;
889 recContext.release();
890 context.transport.callRecordingAboutToStartListeners (*
this, targetID);
898 return std::move (erase_if_null (newContexts));
903 TRACKTION_ASSERT_MESSAGE_THREAD
905 const auto numContextsRecording = [
this]
908 return recordingContexts.size();
912 contextsToStop.
reserve (numContextsRecording);
918 for (
auto& recContext : recordingContexts)
921 if (! contains_v (params.
targetsToStop, recContext->targetID))
925 contextsToStop.
push_back (std::move (recContext));
928 erase_if_null (recordingContexts);
929 assert ((recordingContexts.size() + contextsToStop.
size()) == numContextsRecording);
935 for (
auto& recContext : contextsToStop)
937 const auto targetID = recContext->targetID;
938 auto stopCallback = std::move (recContext->stopCallback);
939 context.transport.callRecordingAboutToStopListeners (*
this, targetID);
940 auto contextClips = applyRecording (std::move (recContext),
944 context.transport.callRecordingFinishedListeners (*
this, targetID, contextClips);
947 stopCallback (contextClips);
949 clips.
addArray (std::move (contextClips));
956 std::function<
void (tl::expected<Clip::Array, juce::String>)> callback)
override
958 TRACKTION_ASSERT_MESSAGE_THREAD
960 const auto getNumContextsRecording = [
this]
963 return recordingContexts.size();
972 for (
auto& recContext : recordingContexts)
980 for (
auto& recContext : recordingContexts)
982 if (! contains_v (params.
targetsToStop, recContext->targetID))
990 recContext->stopCallback = callback;
991 recContext->stopParams = params;
992 recContext->stopParams.
targetsToStop = { recContext->targetID };
1000 getRecordStopper().addTargetToStop (targetID);
1007 for (
auto& recContext : recordingContexts)
1008 if (recContext->targetID == targetID)
1009 return recContext->punchRange.getStart();
1016 return getContextForID (targetID) !=
nullptr;
1022 return ! recordingContexts.empty();
1043 for (
auto& recContext : recordingContexts)
1044 recContext->liveNotes->push (m1);
1048 for (
auto& recContext : recordingContexts)
1049 recContext->recorded.addEvent (m2);
1054 for (
auto c : consumers)
1055 c->handleIncomingMidiMessage (message);
1057 return recording || consumers.
size() > 0;
1062 if (! channelToApply.isValid())
1064 for (
int i = 0; i < sequence.getNumEvents(); ++i)
1066 auto chan = MidiChannel::fromChannelOrZero (sequence.getEventPointer (i)->message.getChannel());
1070 channelToApply = chan;
1076 if (channelToApply.isValid())
1077 for (
int i = sequence.getNumEvents(); --i >= 0;)
1078 sequence.getEventPointer(i)->message.setChannel (channelToApply.getChannelNumber());
1080 return channelToApply;
1085 if (adjustmentMs != 0)
1086 sequence.addTimeToMessages (adjustmentMs * 0.001);
1090 TimePosition unloopedEndTime,
1091 bool isLooping, TimeRange loopRange,
1092 bool discardRecordings)
1095 if (! recContext || discardRecordings)
1098 c->discardRecordings (recContext ? recContext->targetID : EditItemID());
1103 if (recContext->recorded.getNumEvents() == 0)
1109 if (! track && ! clipSlot)
1116 auto clipOwner = track !=
nullptr ?
static_cast<ClipOwner*
> (track)
1117 : static_cast<ClipOwner*> (clipSlot);
1118 Clip::Array createdClips;
1119 auto& mi = getMidiInput();
1120 auto& recorded = recContext->recorded;
1123 recorded.updateMatchedPairs();
1124 auto channelToApply = mi.recordToNoteAutomation ? mi.getChannelToUse()
1125 : applyChannel (recorded, mi.getChannelToUse());
1126 auto timeAdjustMs = mi.getManualAdjustmentMs();
1128 if (
context.getNodePlayHead() !=
nullptr)
1131 applyTimeAdjustment (recorded, timeAdjustMs);
1133 TimeRange recordedRange (recContext->punchRange.getStart(), unloopedEndTime);
1134 auto recordingStart = recordedRange.getStart();
1135 auto recordingEnd = recordedRange.getEnd();
1137 const bool createTakes = mi.recordingEnabled && ! (mi.mergeRecordings || mi.replaceExistingClips);
1139 if (isLooping && recordingEnd > loopRange.getEnd())
1142 const auto loopLen = loopRange.getLength();
1143 const auto maxNumLoops = 2 + (
int) ((recordingEnd - recordingStart) / loopLen);
1145 Clip* takeClip =
nullptr;
1147 for (
int loopNum = 0; loopNum < maxNumLoops; ++loopNum)
1150 const auto thisLoopStart = loopRange.getStart() + loopLen * loopNum;
1151 const auto thisLoopEnd = (thisLoopStart + loopLen).inSeconds();
1153 for (
int i = 0; i < recorded.getNumEvents(); ++i)
1155 auto& m = recorded.getEventPointer (i)->message;
1159 double s = m.getTimeStamp();
1162 if (
auto noteOff = recorded.getEventPointer (i)->noteOffObject)
1163 e = noteOff->message.getTimeStamp();
1165 if (e > thisLoopStart.inSeconds() && s < thisLoopEnd)
1167 if (s < thisLoopStart.inSeconds())
1170 s =
std::fmod (s - loopRange.getStart().inSeconds(), loopLen.inSeconds());
1172 if (e > thisLoopEnd)
1173 e = loopLen.inSeconds();
1175 e =
std::fmod (e - loopRange.getStart().inSeconds(), loopLen.inSeconds());
1179 m.getNoteNumber()), e));
1182 else if (! m.isNoteOff())
1184 const double t = m.getTimeStamp();
1186 if (t >= thisLoopStart.inSeconds() && t < thisLoopEnd)
1197 if (
auto clip = mi.addMidiAsTransaction (
edit, clipOwner->getClipOwnerID(), takeClip,
1198 std::move (loopSequence), loopRange,
1199 MidiInputDevice::MergeMode::optional,
1203 createdClips.add (clip);
1206 else if (mi.replaceExistingClips)
1208 replaceSequence = std::move (loopSequence);
1212 if (
auto clip = mi.addMidiAsTransaction (
edit, clipOwner->getClipOwnerID(),
nullptr,
1213 std::move (loopSequence), loopRange,
1214 MidiInputDevice::MergeMode::always,
1217 createdClips.add (clip);
1223 if (mi.replaceExistingClips && replaceSequence.
getNumEvents() > 0)
1225 if (
auto clip = mi.addMidiAsTransaction (
edit, clipOwner->getClipOwnerID(),
nullptr,
1226 std::move (replaceSequence), loopRange,
1227 MidiInputDevice::MergeMode::optional,
1230 createdClips.add (clip);
1236 auto startPos = recordingStart;
1237 auto endPos = recordingEnd;
1238 auto maxEndPos = endPos + 0.5s;
1240 if (wasPunchRecording)
1242 if (startPos < loopRange.getEnd())
1245 if (endPos <= loopRange.getStart())
1246 return createdClips;
1248 startPos =
std::max (startPos, loopRange.getStart());
1249 endPos =
juce::jlimit (startPos + 0.1s, loopRange.getEnd(), endPos);
1254 startPos =
std::max (startPos, loopRange.getStart());
1263 jassert (m.message.isNoteOn());
1265 if (m.noteOffObject ==
nullptr)
1267 m.message.getNoteNumber()), endPos.inSeconds()));
1269 else if (m.noteOffObject->message.getTimeStamp() > endPos.inSeconds())
1270 m.noteOffObject->message.setTimeStamp (endPos.inSeconds());
1275 return m.message.isNoteOn() && (m.noteOffObject ==
nullptr || m.noteOffObject->message.getTimeStamp() > startPos.inSeconds());
1278 const auto isOutsideClipAndNotNoteOff = [startPos, maxEndPos] (
const juce::MidiMessage& m)
1280 return (m.getTimeStamp() < startPos.inSeconds() || m.getTimeStamp() > maxEndPos.inSeconds()) && ! m.isNoteOff();
1283 if (mi.recordToNoteAutomation)
1285 auto clipStartIndex = recorded.getNextIndexAtTime (startPos.inSeconds());
1287 for (
int i = recorded.getNumEvents(); --i >= 0;)
1289 auto& m = *recorded.getEventPointer (i);
1291 if (m.message.getTimeStamp() < startPos.inSeconds() && isNoteOnThatEndsAfterClipStart (m))
1295 ensureNoteOffIsInsideClip (m);
1296 eventsToDelete.
add (i);
1297 m.noteOffObject =
nullptr;
1299 else if (isOutsideClipAndNotNoteOff (m.message))
1301 eventsToDelete.
add (i);
1303 else if (m.message.getTimeStamp() < endPos.inSeconds() && m.message.isNoteOn())
1305 ensureNoteOffIsInsideClip (m);
1311 for (
int i = recorded.getNumEvents(); --i >= 0;)
1313 auto& m = *recorded.getEventPointer (i);
1315 if (m.message.getTimeStamp() < startPos.inSeconds() && isNoteOnThatEndsAfterClipStart (m))
1316 m.message.setTimeStamp (startPos.inSeconds());
1318 if (isOutsideClipAndNotNoteOff (m.message))
1319 eventsToDelete.
add (i);
1320 else if (m.message.getTimeStamp() < endPos.inSeconds() && m.message.isNoteOn())
1321 ensureNoteOffIsInsideClip (m);
1325 if (! eventsToDelete.
isEmpty())
1328 for (
int index : eventsToDelete)
1329 recorded.deleteEvent (index, true);
1332 if (! noteOffMessagesToAdd.
isEmpty())
1334 for (
const auto& m : noteOffMessagesToAdd)
1335 recorded.addEvent (m);
1338 if (! mpeMessagesToAddAtStartPos.
isEmpty())
1340 for (
const auto& m : mpeMessagesToAddAtStartPos)
1341 recorded.addEvent (m, startPos.inSeconds());
1345 recorded.updateMatchedPairs();
1346 recorded.addTimeToMessages (-startPos.inSeconds());
1348 if (recorded.getNumEvents() > 0)
1350 if (
auto clip = mi.addMidiAsTransaction (
edit, clipOwner->getClipOwnerID(),
nullptr,
1351 std::move (recorded), { startPos, endPos },
1352 MidiInputDevice::MergeMode::optional,
1355 createdClips.add (clip);
1361 c->discardRecordings (recContext->targetID);
1363 return createdClips;
1372 auto& mi = getMidiInput();
1373 auto retrospective = mi.getRetrospectiveMidiBuffer();
1375 if (retrospective ==
nullptr)
1383 auto sequence = retrospective->takeMidiMessages();
1385 if (sequence.getNumEvents() == 0)
1388 sequence.updateMatchedPairs();
1389 auto channelToApply = mi.recordToNoteAutomation ? mi.getChannelToUse()
1390 : applyChannel (sequence, mi.getChannelToUse());
1391 auto timeAdjustMs = mi.getManualAdjustmentMs();
1393 if (
context.getNodePlayHead() !=
nullptr)
1396 applyTimeAdjustment (sequence, timeAdjustMs);
1399 - retrospective->lengthInSeconds + mi.getAdjustSecs();
1400 sequence.addTimeToMessages (-clipStart);
1403 double length = retrospective->lengthInSeconds;
1410 else if (lastEditTime >= 0 && pausedTime < 20)
1412 start = lastEditTime + pausedTime - length;
1417 auto position =
context.getPosition();
1420 start = position.inSeconds() - length;
1425 if (sequence.getNumEvents() > 0)
1427 auto firstEventTime =
std::floor (sequence.getStartTime());
1428 sequence.addTimeToMessages (-firstEventTime);
1429 start += firstEventTime;
1430 length -= firstEventTime;
1440 if (sequence.getNumEvents() > 0)
1442 auto clip = mi.addMidiAsTransaction (
edit, track->getClipOwnerID(),
nullptr, std::move (sequence),
1443 { TimePosition::fromSeconds (start), TimePosition::fromSeconds (start + length) },
1444 MidiInputDevice::MergeMode::never,
1446 clip->setOffset (TimeDuration::fromSeconds (offset));
1449 if (
auto mc =
dynamic_cast<MidiClip*
> (clip))
1451 if (track->playSlotClips.get())
1453 if (
auto slot = getFreeSlot (*track))
1455 mc->setUsesProxy (
false);
1456 mc->setStart (0_tp,
false,
true);
1458 if (! mc->isLooping ())
1459 mc->setLoopRangeBeats (mc->getEditBeatRange());
1461 mc->removeFromParent();
1472 void masterTimeUpdate (
double time)
1485 MidiInputDevice& getMidiInput()
const {
return static_cast<MidiInputDevice&
> (
owner); }
1493 double lastEditTime = -1.0;
1494 double pausedTime = 0;
1495 MidiMessageArray::MPESourceID midiSourceID = MidiMessageArray::createUniqueMPESourceID();
1496 ActiveNoteList activeNotes;
1504 if (p == state && c.hasType (IDs::INPUTDEVICEDESTINATION))
1505 injectNoteOffsToTrack();
1507 InputDeviceInstance::valueTreeChildRemoved (p, c, index);
1510 void injectNoteOffsToTrack()
1512 ActiveNoteList notes;
1518 notes = activeNotes;
1523 notes.iterate ([&] (
auto channel,
auto noteNumber)
1530 MidiRecordingContext* getContextForID (EditItemID targetID)
const
1534 for (
auto& recContext : recordingContexts)
1535 if (recContext->targetID == targetID)
1536 return recContext.get();
1541 RecordStopper& getRecordStopper()
1543 TRACKTION_ASSERT_MESSAGE_THREAD
1545 if (! recordStopper)
1549 const auto unloopedTimeNow =
context.getUnloopedPosition();
1552 if (
auto recContext = getContextForID (targetID))
1554 if (unloopedTimeNow >= recContext->unloopedStopTime)
1556 auto stopParams = recContext->stopParams;
1563 return RecordStopper::HasFinished::yes;
1566 return RecordStopper::HasFinished::no;
1569 return RecordStopper::HasFinished::yes;
1573 return *recordStopper;
1583 if (m.isActiveSense())
1586 if (disallowedChannels[m.getChannel() - 1])
1589 if (m.isNoteOnOrOff())
1591 auto note = m.getNoteNumber();
1593 if (note < noteFilterRange.startNote || note >= noteFilterRange.endNote)
1600 m.addToTimeStamp (adjustSecs);
1602 if (! retrospectiveRecordLock && retrospectiveBuffer !=
nullptr)
1603 retrospectiveBuffer->addMessage (m, adjustSecs);
1605 sendNoteOnToMidiKeyListeners (m);
1615 for (
auto instance : instances)
1616 instance->masterTimeUpdate (
time);
1621 bool messageUnused =
true;
1626 for (
auto i : instances)
1627 if (i->handleIncomingMidiMessage (message))
1628 messageUnused = false;
1631 if (messageUnused && message.
isNoteOn())
1633 warnOfWasted (
this);
void swapWith(OtherArrayType &otherArray) noexcept
bool isEmpty() const noexcept
int removeAllInstancesOf(ParameterType valueToRemove)
int size() const noexcept
void removeRange(int startIndex, int numberToRemove)
void add(const ElementType &newElement)
bool addIfNotAlreadyThere(ParameterType newElement)
void triggerAsyncUpdate()
BigInteger & clear() noexcept
void parseString(StringRef text, int base)
String toString(int base, int minimumNumCharacters=1) const
bool isZero() const noexcept
BigInteger & setBit(int bitNumber)
void startTimer(int intervalInMilliseconds)
void processNextMidiEvent(const MidiMessage &message)
void removeListener(Listener *listener)
MidiEventHolder * addEvent(const MidiMessage &newMessage, double timeAdjustment=0)
void updateMatchedPairs() noexcept
int getIndexOfMatchingKeyUp(int index) const noexcept
void addTimeToMessages(double deltaTime) noexcept
void deleteEvent(int index, bool deleteMatchingNoteUp)
MidiEventHolder * getEventPointer(int index) const noexcept
int getNumEvents() const noexcept
bool isNoteOn(bool returnTrueForVelocity0=false) const noexcept
float getFloatVelocity() const noexcept
int getChannel() const noexcept
void setVelocity(float newVelocity) noexcept
bool isNoteOff(bool returnTrueForNoteOnVelocity0=true) const noexcept
static MidiMessage noteOn(int channel, int noteNumber, float velocity) noexcept
int getNoteNumber() const noexcept
double getTimeStamp() const noexcept
static MidiMessage controllerEvent(int channel, int controllerType, int value) noexcept
static MidiMessage noteOff(int channel, int noteNumber, float velocity) noexcept
uint8 getVelocity() const noexcept
static MidiMessage programChange(int channel, int programNumber) noexcept
void addArray(const ReferenceCountedArray &arrayToAddFrom, int startIndex=0, int numElementsToAdd=-1) noexcept
bool add(const ElementType &newElement) noexcept
bool contains(const ElementType &elementToLookFor) const noexcept
bool contains(StringRef text) const noexcept
static double getMillisecondCounterHiRes() noexcept
void stopTimer() noexcept
void startTimerHz(int timerFrequencyHz) noexcept
void startTimer(int intervalInMilliseconds) noexcept
double getDoubleAttribute(StringRef attributeName, double defaultReturnValue=0.0) const
bool getBoolAttribute(StringRef attributeName, bool defaultReturnValue=false) const
int getIntAttribute(StringRef attributeName, int defaultReturnValue=0) const
const String & getStringAttribute(StringRef attributeName) const noexcept
void setAttribute(const Identifier &attributeName, const String &newValue)
std::function< void(InputDevice *)> warnOfWastedMidiMessagesFunction
If this is set, it will get called (possibly on the midi thread) when incoming messages seem to be un...
int getLatencySamples() const
Returns the overall latency of the currently prepared graph.
TransportControl & getTransport() const noexcept
Returns the TransportControl which is used to stop/stop/position playback and recording.
int getNumCountInBeats() const
Returns the number of beats of the count in.
juce::CachedValue< bool > recordingPunchInOut
Whether recoridng only happens within the in/out markers.
Engine & engine
A reference to the Engine.
virtual bool isMidiDriverUsedForIncommingMessageTiming()
Should return true if the incoming timestamp for MIDI messages should be used.
The Engine is the central class for all tracktion sessions.
MidiProgramManager & getMidiProgramManager() const
Returns the MidiProgramManager instance that handles MIDI banks, programs, sets or presets.
PropertyStorage & getPropertyStorage() const
Returns the PropertyStorage user settings customisable XML file.
UIBehaviour & getUIBehaviour() const
Returns the UIBehaviour class.
DeviceManager & getDeviceManager() const
Returns the DeviceManager instance for handling audio / MIDI devices.
EngineBehaviour & getEngineBehaviour() const
Returns the EngineBehaviour instance.
Base class for RecordingContexts.
@ 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.
Polls a set of targets to see if they should be stopped.
virtual void changed()
This should be called to send a change notification to any SelectableListeners that are registered wi...
Base class for tracks which contain clips and plugins and can be added to Edit[s].
void play(bool justSendMMCIfEnabled)
Starts playback of an Edit.
TimePosition getTimeWhenStarted() const
Returns the time when the transport was started.
bool isPlaying() const
Returns true if the transport is playing.
A basic spin lock that uses an atomic_flag to store the locked state so should never result in a syst...
void unlock() noexcept
Releases the lock, this should only be called after a successful call to try_lock or lock.
bool try_lock() noexcept
Attempts to take the lock once, returning true if successful.
void lock() noexcept
Takes the lock, blocking if necessary.
#define TRANS(stringLiteral)
Type jlimit(Type lowerLimit, Type upperLimit, Type valueToConstrain) noexcept
juce::String getName(LaunchQType t)
Retuns the name of a LaunchQType for display purposes.
ClipSlot * findClipSlotForID(const Edit &edit, EditItemID id)
Returns the ClipSlot for the given ID.
MidiClip::Ptr insertMIDIClip(ClipOwner &parent, const juce::String &name, TimeRange position)
Inserts a new MidiClip into the ClipOwner's clip list.
void mergeInMidiSequence(MidiClip &mc, juce::MidiMessageSequence ms, TimeDuration startTime, MidiList::NoteAutomationType automationType)
Copies a zero-time origin based MIDI sequence in to a MidiClip.
AudioTrack * findAudioTrackForID(const Edit &edit, EditItemID id)
Returns the AudioTrack with a given ID if contained in the Edit.
juce::Array< AudioTrack * > getTargetTracks(InputDeviceInstance &instance)
Returns the AudioTracks this instance is assigned to.
RangeType< TimePosition > TimeRange
A RangeType based on real time (i.e.
Represents a position in real-life time.
constexpr double inSeconds() const
Returns the TimePosition as a number of seconds.
ID for objects of type EditElement - e.g.
static void reconstructExpression(juce::Array< juce::MidiMessage > &mpeMessagesToAddAtStart, const juce::MidiMessageSequence &data, int trimIndex, int channel)
Reconstruct note expression for a particular channel.
#define CRASH_TRACER
This macro adds the current location to a stack which gets logged if a crash happens.
T unlock_shared(T... args)