Control Surface main
MIDI Control Surface library for Arduino
Loading...
Searching...
No Matches
MIDI_Pipes.cpp
Go to the documentation of this file.
1#include <Settings/SettingsWrapper.hpp>
2#if !DISABLE_PIPES
3
4#include "MIDI_Pipes.hpp"
5#include "MIDI_Staller.hpp"
6#include <AH/Error/Error.hpp>
7#include <AH/STL/utility>
8
9#if defined(ESP32) || !defined(ARDUINO)
10#include <mutex>
11#endif
12
14
15// :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: //
16
18 if (this->sourcePipe == nullptr) {
19 source->connectSink(this);
20 this->sourcePipe = source;
21 } else {
22 this->sourcePipe->connectSourcePipe(source);
23 }
24}
25
27 if (sourcePipe != nullptr) {
30 sourcePipe = nullptr;
31 }
32}
33
35 if (sourcePipe != nullptr) {
37 sourcePipe = nullptr;
38 }
39}
40
42 if (!hasSourcePipe())
43 return false;
44 return sourcePipe->disconnect(source);
45}
46
48
50 : sourcePipe(std::exchange(other.sourcePipe, nullptr)) {
51 if (this->hasSourcePipe()) {
53 this->sourcePipe->connectSink(this);
54 }
55}
56
58 std::swap(a.sourcePipe, b.sourcePipe);
59 if (a.hasSourcePipe()) {
62 }
63 if (b.hasSourcePipe()) {
66 }
67}
68
70 swap(*this, other);
71 return *this;
72}
73
74// :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: //
75
77 if (this->sinkPipe == nullptr) {
78 sink->connectSource(this);
79 this->sinkPipe = sink;
80 } else {
81 this->sinkPipe->connectSinkPipe(sink);
82 }
83}
84
86 if (sinkPipe != nullptr) {
89 sinkPipe = nullptr;
90 }
91}
92
94 if (sinkPipe != nullptr) {
96 sinkPipe = nullptr;
97 }
98}
99
101 if (!hasSinkPipe())
102 return false;
103 return sinkPipe->disconnect(sink);
104}
105
107 : sinkPipe(std::exchange(other.sinkPipe, nullptr)) {
108 if (this->hasSinkPipe()) {
109 this->sinkPipe->disconnectSource();
110 this->sinkPipe->connectSource(this);
111 }
112}
113
115 std::swap(a.sinkPipe, b.sinkPipe);
116 if (a.hasSinkPipe()) {
118 a.sinkPipe->connectSource(&a);
119 }
120 if (b.hasSinkPipe()) {
122 b.sinkPipe->connectSource(&b);
123 }
124}
125
127 swap(*this, other);
128 return *this;
129}
130
132
134 if (sinkPipe != nullptr) {
137 }
138}
140 if (sinkPipe != nullptr) {
143 }
144}
146 if (sinkPipe != nullptr) {
149 }
150}
152 if (sinkPipe != nullptr) {
153 // Always send write to pipe, don't check if it's stalled or not
155 }
156}
157
159 if (hasSinkPipe())
160 sinkPipe->stallDownstream(cause, this);
161 DEBUGFN(F("Stalled MIDI source. Cause: ") << getStallerName());
162}
163
165 DEBUGFN(F("Un-stalling MIDI source. Cause: ") << getStallerName());
166 if (hasSinkPipe())
167 sinkPipe->unstallDownstream(cause, this);
168}
169
171 if (hasSinkPipe())
172 return sinkPipe->isStalled();
173 return false;
174}
175
177 if (hasSinkPipe())
178 return sinkPipe->getStaller();
179 return nullptr;
180}
181
182const char *MIDI_Source::getStallerName() const {
184}
185
187 if (hasSinkPipe())
189}
190
191// :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: //
192
194 if (this->sink != nullptr) {
195 FATAL_ERROR(F("This pipe is already connected to a sink"), 0x9145);
196 return; // LCOV_EXCL_LINE
197 }
198 this->sink = sink;
199}
200
201void MIDI_Pipe::disconnectSink() { this->sink = nullptr; }
202
204 if (this->source != nullptr) {
205 FATAL_ERROR(F("This pipe is already connected to a source"), 0x9146);
206 return; // LCOV_EXCL_LINE
207 }
208 this->source = source;
209}
210
211void MIDI_Pipe::disconnectSource() { this->source = nullptr; }
212
214 if (hasSink() && hasThroughIn()) {
215 auto oldSink = sink;
216 auto oldThroughIn = getThroughIn();
218 this->disconnectSourcePipesShallow(); // disconnect throughIn
219 oldSink->connectSourcePipe(oldThroughIn);
220 }
221 if (hasSource() && hasThroughOut()) {
222 auto oldSource = source;
223 auto oldThroughOut = getThroughOut();
225 this->disconnectSinkPipesShallow(); // disconnect throughOut
226 oldSource->connectSinkPipe(oldThroughOut);
227 }
228 if (hasSink())
230
231 if (hasSource())
233
234 if (hasThroughIn() || hasThroughOut())
235 FATAL_ERROR(F("Invalid state"), 0x9147); // LCOV_EXCL_LINE
236}
237
239
241 if (!sinkIsUnstalledOrStalledBy(cause)) {
242 FATAL_ERROR(F("Cannot stall pipe from ")
244 << F(" because pipe is already stalled by ")
246 0x6665);
247 } // LCOV_EXCL_LINE
248 sink_staller = cause;
249 if (hasThroughOut() && stallsrc == source)
250 getThroughOut()->stallDownstream(cause, this);
251 if (hasSink())
252 sink->stallDownstream(cause, this);
253 if (hasSource() && source != stallsrc) {
254 // If our through output is stalled, that means our upstream is stalled
255 // as well by this staller. Unstall it first, and replace it by the new
256 // staller
257 if (through_staller != nullptr)
259 source->stallUpstream(cause, this);
260 }
261 if (hasThroughIn() && getThroughIn() != stallsrc)
262 getThroughIn()->stallUpstream(cause, this);
263}
264
266 if (stallsrc == sink) {
267 // This cannot be a different cause, because then our sink would
268 // already have caught it in stallDownstream().
269 sink_staller = cause;
270 if (hasSource())
271 source->stallUpstream(cause, this);
272 if (hasThroughIn())
273 getThroughIn()->stallUpstream(cause, this);
274 } else {
275 if (through_staller == nullptr) {
276 through_staller = cause;
277 if (hasSource())
278 source->stallUpstream(cause, this);
279 }
280 }
281}
282
284 if (!sinkIsUnstalledOrStalledBy(cause)) {
285 FATAL_ERROR(F("Cannot unstall pipe from ")
287 << F(" because pipe is stalled by ")
289 0x6666);
290 } // LCOV_EXCL_LINE
291 this->sink_staller = nullptr;
292 if (hasThroughOut() && stallsrc == source)
293 getThroughOut()->unstallDownstream(cause, this);
294 if (hasSink())
295 sink->unstallDownstream(cause, this);
296 if (hasSource() && source != stallsrc) {
297 source->unstallUpstream(cause, this);
298 // If the through output of this pipe is stalled, we cannot just unstall
299 // our upstream, we have to update it our through output staller
300 if (through_staller != nullptr)
302 }
303 if (hasThroughIn() && getThroughIn() != stallsrc)
304 getThroughIn()->unstallUpstream(cause, this);
305}
306
308 if (stallsrc == sink) {
309 // This cannot be a different cause, because then our sink would
310 // already have caught it in unstallDownstream().
311 sink_staller = nullptr;
312 if (hasSource())
313 source->unstallUpstream(cause, this);
314 if (hasThroughIn())
315 getThroughIn()->unstallUpstream(cause, this);
316 } else {
317 if (cause == through_staller) {
318 through_staller = nullptr;
319 if (hasSource())
320 source->unstallUpstream(cause, this);
321 }
322 }
323}
324
328
332
336
337const char *MIDI_Pipe::getStallerName() const {
339}
340
342 if (!isStalled())
343 return;
345 FATAL_ERROR(F("Unable to unstall pipe (eternal stall)"), 0x4827);
346 uint8_t iterations = 10;
347 while (isStalled() && iterations-- > 0) {
348 if (sink_staller)
350 if (through_staller)
352 }
353}
354
355// :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: //
356
358 if (s == nullptr)
359 return "(null)";
360 if (s == eternal_stall)
361 return "(eternal stall)";
362 return s->getName();
363}
364
366
367#endif
MIDIStaller *const eternal_stall
#define END_CS_NAMESPACE
#define BEGIN_CS_NAMESPACE
Class that routes MIDI messages from a MIDI_Source to a MIDI_Sink.
void acceptMIDIfromSource(Message msg)
Accept a MIDI message from the source, forward it to the “through” output if necessary,...
bool hasSink() const
Check if this pipe is connected to a sink.
const char * getStallerName() const
Get the name of any staller.
MIDIStaller * getStaller() const
Get any staller: returns getSinkStaller() if it's not null, getThroughStaller() otherwise.
MIDI_Source * source
void connectSource(MIDI_Source *source)
Set the source pointer to point to the given source.
MIDI_Sink * sink
void unstallDownstream(MIDIStaller *cause, MIDI_Source *stallsrc) override
Undoes the stalling by stallDownstream.
bool hasSource() const
Check if this pipe is connected to a source.
void stallDownstream(MIDIStaller *cause, MIDI_Source *stallsrc) override
Stall this pipe and all other pipes further downstream (following the path of the sink and the “throu...
bool hasThroughOut() const
Check if this pipe has a “through” output that sends all incoming messages from the input (source) to...
MIDIStaller * through_staller
void connectSink(MIDI_Sink *sink)
Set the sink pointer to point to the given sink.
const char * getSinkStallerName() const
Get the name of the staller (cause of the stall) that causes the sink of this pipe to be stalled.
MIDI_Pipe * getThroughOut() const
Get the pipe connected to the “through” output of this pipe.
bool isStalled() const
Check if this pipe is stalled.
MIDIStaller * sink_staller
virtual ~MIDI_Pipe()
Destructor.
const char * getThroughStallerName() const
Get the name of the staller (cause of the stall) that causes the “through” output of this pipe to be ...
void unstallUpstream(MIDIStaller *cause, MIDI_Sink *stallsrc) override
Undoes the stalling by stallUpstream.
void disconnect()
Disconnect this pipe from all other pipes, sources and sinks.
void handleStallers() const
Give the code that is stalling the MIDI pipe the opportunity to do its job and unstall the pipe.
void disconnectSink()
Set the sink pointer to null.
bool hasThroughIn() const
Check if this pipe has a “through” input that merges all messages from another pipe into the output (...
void disconnectSource()
Set the source pointer to null.
bool sinkIsUnstalledOrStalledBy(MIDIStaller *cause)
Returns true if this pipe is either not stalled at all, or if the pipe is stalled by the given stalle...
void stallUpstream(MIDIStaller *cause, MIDI_Sink *stallsrc) override
Stall this pipe and all other pipes further upstream (following the path of the "trough" input).
MIDI_Pipe * getThroughIn() const
Get the pipe connected to the “through” input of this pipe.
Receives MIDI messages from a MIDI pipe.
void connectSourcePipe(MIDI_Pipe *source)
Fully connect a source pipe to this sink.
virtual void stallDownstream(MIDIStaller *, MIDI_Source *)
Base case for recursive stall function.
virtual ~MIDI_Sink()
Destructor.
friend void swap(MIDI_Sink &a, MIDI_Sink &b)
virtual void unstallDownstream(MIDIStaller *, MIDI_Source *)
Base case for recursive un-stall function.
MIDI_Sink()=default
Default constructor.
void disconnectSourcePipes()
Disconnect all source pipes that sink to this sink (recursively).
void disconnectSourcePipesShallow()
Disconnect only the first pipe connected to this sink.
MIDI_Pipe * sourcePipe
bool disconnect(TrueMIDI_Source &source)
Disconnect the given source from this sink.
bool hasSourcePipe() const
Check if this sink is connected to a source pipe.
MIDI_Sink & operator=(const MIDI_Sink &)=delete
Copy assignment (copying not allowed).
Class that can send MIDI messages to a MIDI pipe.
const char * getStallerName() const
Get the name of whatever is causing this MIDI source to be stalled.
void connectSinkPipe(MIDI_Pipe *sink)
Fully connect a sink pipe to this source.
virtual ~MIDI_Source()
Destructor.
MIDIStaller * getStaller() const
Get a pointer to whatever is causing this MIDI source to be stalled.
MIDI_Source()=default
Default constructor.
void sourceMIDItoPipe(ChannelMessage)
Send a MIDI Channel Message down the pipe.
MIDI_Pipe * sinkPipe
void stall(MIDIStaller *cause=eternal_stall)
Stall this MIDI source.
virtual void stallUpstream(MIDIStaller *, MIDI_Sink *)
Base case for recursive stall function.
bool isStalled() const
Check if this source can write to the sinks it connects to.
friend void swap(MIDI_Source &a, MIDI_Source &b)
void disconnectSinkPipesShallow()
Disconnect only the first pipe connected to this source.
bool disconnect(TrueMIDI_Sink &sink)
Disconnect the given sink from this source.
void disconnectSinkPipes()
Disconnect all sink pipes that this source sinks to (recursively).
MIDI_Source & operator=(const MIDI_Source &)=delete
Copy assignment (copying not allowed).
void handleStallers() const
Give the code that is stalling the MIDI sink pipes the opportunity to do its job and un-stall the pip...
virtual void unstallUpstream(MIDIStaller *, MIDI_Sink *)
Base case for recursive un-stall function.
bool hasSinkPipe() const
Check if this source is connected to a sink pipe.
void unstall(MIDIStaller *cause=eternal_stall)
Un-stall the pipes connected to this source, so other sources are allowed to send again.
#define DEBUGFN(x)
Print an expression and its function (function name and line number) to the debug output if debugging...
Definition Debug.hpp:115
#define FATAL_ERROR(msg, errc)
Print the error message and error code, and stop the execution.
Definition Error.hpp:57
Struct that can cause a MIDI_Pipe to be stalled.
virtual const char * getName() const
Get the staller's name for debugging purposes.
virtual void handleStall()=0
Call back that should finish any MIDI messages that are in progress, and un-stall the pipe or MIDI so...
static const char * getNameNull(MIDIStaller *s)
Get the staller's name for debugging purposes.