tracktion-engine 3.0-10-g034fdde4aa5
Tracktion Engine — High level data model for audio applications

« « « Anklang Documentation
Loading...
Searching...
No Matches
tracktion_MidiInputDeviceNode.cpp
Go to the documentation of this file.
1 /*
2 ,--. ,--. ,--. ,--.
3 ,-' '-.,--.--.,--,--.,---.| |,-.,-' '-.`--' ,---. ,--,--, Copyright 2024
4 '-. .-'| .--' ,-. | .--'| /'-. .-',--.| .-. || \ Tracktion Software
5 | | | | \ '-' \ `--.| \ \ | | | |' '-' '| || | Corporation
6 `---' `--' `--`--'`---'`--'`--' `---' `--' `---' `--''--' www.tracktion.com
7
8 Tracktion Engine uses a GPL/commercial licence - see LICENCE.md for details.
9*/
10
11namespace tracktion { inline namespace engine
12{
13
14MidiInputDeviceNode::MidiInputDeviceNode (InputDeviceInstance& idi, MidiInputDevice& owner, MidiMessageArray::MPESourceID msi,
15 tracktion::graph::PlayHeadState& phs, EditItemID targetID_)
16 : instance (idi),
17 midiInputDevice (owner),
18 midiSourceID (msi),
19 playHeadState (phs),
20 targetID (targetID_)
21{
22}
23
24MidiInputDeviceNode::~MidiInputDeviceNode()
25{
26 instance.removeConsumer (this);
27}
28
29tracktion::graph::NodeProperties MidiInputDeviceNode::getNodeProperties()
30{
32 props.hasMidi = true;
33 props.nodeID = nodeID;
34 return props;
35}
36
37void MidiInputDeviceNode::prepareToPlay (const tracktion::graph::PlaybackInitialisationInfo& info)
38{
39 sampleRate = info.sampleRate;
40 maxExpectedMsPerBuffer = (unsigned int) (((info.blockSize * 1000) / info.sampleRate) * 2 + 100);
41 assert (getNodeProperties().nodeID == nodeID);
42
43 if (auto oldNode = findNodeWithIDIfNonZero<MidiInputDeviceNode> (info.nodeGraphToReplace, nodeID))
44 state = oldNode->state;
45
46 if (! state)
48
49 {
50 auto channelToUse = midiInputDevice.getChannelToUse();
51 auto programToUse = midiInputDevice.getProgramToUse();
52
53 if (channelToUse.isValid() && programToUse > 0)
54 handleIncomingMidiMessage (juce::MidiMessage::programChange (channelToUse.getChannelNumber(), programToUse - 1));
55 }
56
57 {
58 const std::lock_guard sl (state->liveMessagesMutex);
59 state->liveRecordedMessages.clear();
60 state->numLiveMessagesToPlay = 0;
61 }
62
63 instance.addConsumer (this);
64 loopOverdubsChecker.startTimerHz (1);
65}
66
67bool MidiInputDeviceNode::isReadyToProcess()
68{
69 return true;
70}
71
72void MidiInputDeviceNode::process (ProcessContext& pc)
73{
74 SCOPED_REALTIME_CHECK
75
76 const auto splitTimelineRange = referenceSampleRangeToSplitTimelineRange (playHeadState.playHead, pc.referenceSampleRange);
77 jassert (! splitTimelineRange.isSplit); // This should be handled by the NodePlayer
78
79 processSection (pc, splitTimelineRange.timelineRange1);
80}
81
82void MidiInputDeviceNode::handleIncomingMidiMessage (const juce::MidiMessage& message)
83{
84 if (state->activeNode.load (std::memory_order_acquire) != this)
85 return;
86
87 auto channelToUse = midiInputDevice.getChannelToUse().getChannelNumber();
88
89 {
90 const std::lock_guard sl (state->incomingMessagesMutex);
91
92 if (state->numIncomingMessages < state->incomingMessages.size())
93 {
94 auto& m = *state->incomingMessages.getUnchecked (state->numIncomingMessages);
95 m = message;
96
97 if (channelToUse > 0)
98 m.setChannel (channelToUse);
99
100 ++(state->numIncomingMessages);
101 }
102 }
103
104 auto& playHead = playHeadState.playHead;
105
106 if (playHead.isPlaying() && isLivePlayOverActive())
107 {
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);
113
114 if (message.isNoteOff())
115 sourceTime = midiInputDevice.quantisation.roundUp (sourceTime, instance.edit);
116 else if (message.isNoteOn())
117 sourceTime = midiInputDevice.quantisation.roundToNearest (sourceTime, instance.edit);
118
119 if (isLooping)
120 if (sourceTime >= loopTimes.getEnd())
121 sourceTime = loopTimes.getStart();
122
123 juce::MidiMessage newMess (message, sourceTime.inSeconds());
124
125 if (channelToUse > 0)
126 newMess.setChannel (channelToUse);
127
128 const std::lock_guard sl (state->liveMessagesMutex);
129 state->liveRecordedMessages.addMidiMessage (newMess, sourceTime.inSeconds(), midiSourceID);
130 }
131}
132
133void MidiInputDeviceNode::processSection (ProcessContext& pc, juce::Range<int64_t> timelineRange)
134{
135 state->activeNode.store (this, std::memory_order_release);
136
137 const auto editTime = tracktion::graph::sampleToTime (timelineRange, sampleRate);
139 auto& destMidi = pc.buffers.midi;
140
141 if (! playHeadState.isContiguousWithPreviousBlock())
142 createProgramChanges (destMidi);
143
144 {
145 const std::lock_guard sl (state->incomingMessagesMutex);
146
147 // if it's been a long time since the last block, clear the buffer because
148 // it means we were muted or glitching
149 if (timeNow > state->lastReadTime + maxExpectedMsPerBuffer)
150 {
151 //jassertfalse;
152 state->numIncomingMessages = 0;
153 }
154
155 state->lastReadTime = timeNow;
156
157 jassert (state->numIncomingMessages <= state->incomingMessages.size());
158
159 if (int num = juce::jmin (state->numIncomingMessages, state->incomingMessages.size()))
160 {
161 // not quite right as the first event won't be at the start of the buffer, but near enough for live stuff
162 const auto timeAdjust = state->incomingMessages.getUnchecked (0)->getTimeStamp();
163
164 for (int i = 0; i < num; ++i)
165 {
166 auto m = state->incomingMessages.getUnchecked (i);
167 destMidi.addMidiMessage (*m,
168 juce::jlimit (0.0, juce::jmax (0.0, editTime.getLength()), m->getTimeStamp() - timeAdjust),
169 midiSourceID);
170 }
171 }
172
173 state->numIncomingMessages = 0;
174 }
175
176 const std::lock_guard sl (state->liveMessagesMutex);
177
178 if (state->lastPlayheadTime > editTime.getStart())
179 // when we loop, we can assume all the messages in here are now from the previous time round, so are playable
180 state->numLiveMessagesToPlay = state->liveRecordedMessages.size();
181
182 state->lastPlayheadTime = editTime.getStart();
183
184 auto& mi = midiInputDevice;
185 const bool createTakes = mi.recordingEnabled && ! (mi.mergeRecordings || mi.replaceExistingClips);
186
187 if ((! createTakes && ! mi.replaceExistingClips)
188 && state->numLiveMessagesToPlay > 0
189 && playHeadState.playHead.isPlaying())
190 {
191 for (int i = 0; i < state->numLiveMessagesToPlay; ++i)
192 {
193 auto& m = state->liveRecordedMessages[i];
194 auto t = m.getTimeStamp();
195
196 if (editTime.contains (t))
197 destMidi.add (m, t - editTime.getStart());
198 }
199 }
200}
201
202void MidiInputDeviceNode::createProgramChanges (MidiMessageArray& bufferForMidiMessages)
203{
204 auto channelToUse = midiInputDevice.getChannelToUse();
205 auto programToUse = midiInputDevice.getProgramToUse();
206
207 if (programToUse > 0 && channelToUse.isValid())
208 bufferForMidiMessages.addMidiMessage (juce::MidiMessage::programChange (channelToUse.getChannelNumber(), programToUse - 1),
209 0, midiSourceID);
210}
211
212bool MidiInputDeviceNode::isLivePlayOverActive()
213{
214 return instance.isRecording()
215 && state->canLoop
216 && instance.context.transport.looping;
217}
218
219void MidiInputDeviceNode::updateLoopOverdubs()
220{
221 bool canLoopFlag = false;
222
223 // Only enable overdubs if a track is being recorded
224 for (auto tID : instance.getTargets())
225 if (instance.isRecording (tID) && instance.edit.trackCache.findItem (tID) != nullptr)
226 canLoopFlag = true;
227
228 state->canLoop = canLoopFlag;
229}
230
231void MidiInputDeviceNode::discardRecordings (EditItemID targetIDToDiscard)
232{
233 if (targetIDToDiscard != targetID)
234 return;
235
236 const std::lock_guard sl (state->liveMessagesMutex);
237 state->liveRecordedMessages.clear();
238}
239
240}} // namespace tracktion { inline namespace engine
assert
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...
T is_pointer_v
#define jassert(expression)
typedef int
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...