11namespace tracktion {
inline namespace engine
14MidiInputDeviceNode::MidiInputDeviceNode (InputDeviceInstance& idi, MidiInputDevice& owner, MidiMessageArray::MPESourceID msi,
17 midiInputDevice (owner),
24MidiInputDeviceNode::~MidiInputDeviceNode()
26 instance.removeConsumer (
this);
33 props.nodeID = nodeID;
39 sampleRate = info.sampleRate;
40 maxExpectedMsPerBuffer = (
unsigned int) (((info.blockSize * 1000) / info.sampleRate) * 2 + 100);
41 assert (getNodeProperties().nodeID == nodeID);
43 if (
auto oldNode = findNodeWithIDIfNonZero<MidiInputDeviceNode> (info.nodeGraphToReplace, nodeID))
44 state = oldNode->state;
50 auto channelToUse = midiInputDevice.getChannelToUse();
51 auto programToUse = midiInputDevice.getProgramToUse();
53 if (channelToUse.isValid() && programToUse > 0)
59 state->liveRecordedMessages.clear();
60 state->numLiveMessagesToPlay = 0;
63 instance.addConsumer (
this);
64 loopOverdubsChecker.startTimerHz (1);
67bool MidiInputDeviceNode::isReadyToProcess()
77 jassert (! splitTimelineRange.isSplit);
79 processSection (pc, splitTimelineRange.timelineRange1);
87 auto channelToUse = midiInputDevice.getChannelToUse().getChannelNumber();
92 if (state->numIncomingMessages < state->incomingMessages.size())
94 auto& m = *state->incomingMessages.getUnchecked (state->numIncomingMessages);
98 m.setChannel (channelToUse);
100 ++(state->numIncomingMessages);
104 auto& playHead = playHeadState.playHead;
106 if (playHead.isPlaying() && isLivePlayOverActive())
108 const bool isLooping = state->canLoop && playHead.isLooping();
109 const auto loopTimes = timeRangeFromSamples (playHead.getLoopRange(), sampleRate);
110 const auto messageReferenceSamplePosition = tracktion::graph::timeToSample (message.
getTimeStamp(), sampleRate);
111 const auto timelinePosition = playHead.referenceSamplePositionToTimelinePosition (messageReferenceSamplePosition);
112 auto sourceTime = TimePosition::fromSamples (timelinePosition, sampleRate);
115 sourceTime = midiInputDevice.quantisation.roundUp (sourceTime, instance.edit);
117 sourceTime = midiInputDevice.quantisation.roundToNearest (sourceTime, instance.edit);
120 if (sourceTime >= loopTimes.getEnd())
121 sourceTime = loopTimes.getStart();
125 if (channelToUse > 0)
129 state->liveRecordedMessages.addMidiMessage (newMess, sourceTime.inSeconds(), midiSourceID);
137 const auto editTime = tracktion::graph::sampleToTime (timelineRange, sampleRate);
139 auto& destMidi = pc.buffers.midi;
141 if (! playHeadState.isContiguousWithPreviousBlock())
142 createProgramChanges (destMidi);
149 if (timeNow > state->lastReadTime + maxExpectedMsPerBuffer)
152 state->numIncomingMessages = 0;
155 state->lastReadTime = timeNow;
157 jassert (state->numIncomingMessages <= state->incomingMessages.size());
159 if (
int num =
juce::jmin (state->numIncomingMessages, state->incomingMessages.size()))
162 const auto timeAdjust = state->incomingMessages.getUnchecked (0)->getTimeStamp();
164 for (
int i = 0; i < num; ++i)
166 auto m = state->incomingMessages.getUnchecked (i);
167 destMidi.addMidiMessage (*m,
173 state->numIncomingMessages = 0;
178 if (state->lastPlayheadTime > editTime.getStart())
180 state->numLiveMessagesToPlay = state->liveRecordedMessages.size();
182 state->lastPlayheadTime = editTime.getStart();
184 auto& mi = midiInputDevice;
185 const bool createTakes = mi.recordingEnabled && ! (mi.mergeRecordings || mi.replaceExistingClips);
187 if ((! createTakes && ! mi.replaceExistingClips)
188 && state->numLiveMessagesToPlay > 0
189 && playHeadState.playHead.isPlaying())
191 for (
int i = 0; i < state->numLiveMessagesToPlay; ++i)
193 auto& m = state->liveRecordedMessages[i];
194 auto t = m.getTimeStamp();
196 if (editTime.contains (t))
197 destMidi.add (m, t - editTime.getStart());
202void MidiInputDeviceNode::createProgramChanges (MidiMessageArray& bufferForMidiMessages)
204 auto channelToUse = midiInputDevice.getChannelToUse();
205 auto programToUse = midiInputDevice.getProgramToUse();
207 if (programToUse > 0 && channelToUse.isValid())
212bool MidiInputDeviceNode::isLivePlayOverActive()
214 return instance.isRecording()
216 && instance.context.transport.looping;
219void MidiInputDeviceNode::updateLoopOverdubs()
221 bool canLoopFlag =
false;
224 for (
auto tID : instance.getTargets())
225 if (instance.
isRecording (tID) && instance.edit.trackCache.findItem (tID) != nullptr)
228 state->canLoop = canLoopFlag;
231void MidiInputDeviceNode::discardRecordings (
EditItemID targetIDToDiscard)
233 if (targetIDToDiscard != targetID)
237 state->liveRecordedMessages.clear();
bool isNoteOn(bool returnTrueForVelocity0=false) const noexcept
bool isNoteOff(bool returnTrueForNoteOnVelocity0=true) const noexcept
double getTimeStamp() const noexcept
static MidiMessage programChange(int channel, int programNumber) noexcept
void setChannel(int newChannelNumber) noexcept
static uint32 getApproximateMillisecondCounter() noexcept
Struct to describe a single iteration of a process call.
Determines how this block releates to other previous render blocks and if the play head has jumped in...
constexpr Type jmin(Type a, Type b)
constexpr Type jmax(Type a, Type b)
Type jlimit(Type lowerLimit, Type upperLimit, Type valueToConstrain) noexcept
bool isRecording(EditPlaybackContext &epc)
Returns true if any inputs are currently recording.
SplitTimelineRange referenceSampleRangeToSplitTimelineRange(const PlayHead &playHead, juce::Range< int64_t > referenceSampleRange)
Converts a reference sample range to a TimelinePositionWindow which could have two time ranges if the...
ID for objects of type EditElement - e.g.
Holds some really basic properties of a node.
Passed into Nodes when they are being initialised, to give them useful contextual information that th...