Control Surface new-input
MIDI Control Surface library for Arduino
MIDI_Pipes.cpp
Go to the documentation of this file.
1 #include "MIDI_Pipes.hpp"
2 #include "MIDI_Staller.hpp"
3 #include <AH/Error/Error.hpp>
4 #include <AH/STL/utility>
5 
6 #if defined(ESP32) || !defined(ARDUINO)
7 #include <mutex>
8 #endif
9 
11 
13 
14 // :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: //
15 
16 void MIDI_Sink::connectSourcePipe(MIDI_Pipe *source) {
17  if (this->sourcePipe == nullptr) {
18  source->connectSink(this);
19  this->sourcePipe = source;
20  } else {
21  this->sourcePipe->connectSourcePipe(source);
22  }
23 }
24 
26  if (sourcePipe != nullptr) {
29  sourcePipe = nullptr;
30  }
31 }
32 
34  if (sourcePipe != nullptr) {
36  sourcePipe = nullptr;
37  }
38 }
39 
41  if (!hasSourcePipe())
42  return false;
43  return sourcePipe->disconnect(source);
44 }
45 
47 
49  : sourcePipe(std::exchange(other.sourcePipe, nullptr)) {
50  if (this->hasSourcePipe()) {
51  this->sourcePipe->disconnectSink();
52  this->sourcePipe->connectSink(this);
53  }
54 }
55 
58  if (a.hasSourcePipe()) {
60  a.sourcePipe->connectSink(&a);
61  }
62  if (b.hasSourcePipe()) {
64  b.sourcePipe->connectSink(&b);
65  }
66 }
67 
69  swap(*this, other);
70  return *this;
71 }
72 
73 // :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: //
74 
76  if (this->sinkPipe == nullptr) {
77  sink->connectSource(this);
78  this->sinkPipe = sink;
79  } else {
80  this->sinkPipe->connectSinkPipe(sink);
81  }
82 }
83 
85  if (sinkPipe != nullptr) {
88  sinkPipe = nullptr;
89  }
90 }
91 
93  if (sinkPipe != nullptr) {
95  sinkPipe = nullptr;
96  }
97 }
98 
100  if (!hasSinkPipe())
101  return false;
102  return sinkPipe->disconnect(sink);
103 }
104 
106  : sinkPipe(std::exchange(other.sinkPipe, nullptr)) {
107  if (this->hasSinkPipe()) {
108  this->sinkPipe->disconnectSource();
109  this->sinkPipe->connectSource(this);
110  }
111 }
112 
115  if (a.hasSinkPipe()) {
117  a.sinkPipe->connectSource(&a);
118  }
119  if (b.hasSinkPipe()) {
121  b.sinkPipe->connectSource(&b);
122  }
123 }
124 
126  swap(*this, other);
127  return *this;
128 }
129 
131 
133  if (sinkPipe != nullptr) {
134  handleStallers();
136  }
137 }
139  if (sinkPipe != nullptr) {
140  handleStallers();
142  }
143 }
145  if (sinkPipe != nullptr) {
146  handleStallers();
148  }
149 }
151  if (sinkPipe != nullptr) {
152  // Always send write to pipe, don't check if it's stalled or not
154  }
155 }
156 
158  if (hasSinkPipe())
159  sinkPipe->stallDownstream(cause, this);
160 }
161 
163  if (hasSinkPipe())
164  sinkPipe->unstallDownstream(cause, this);
165 }
166 
168  if (hasSinkPipe())
169  return sinkPipe->isStalled();
170  return false;
171 }
172 
174  if (hasSinkPipe())
175  return sinkPipe->getStaller();
176  return nullptr;
177 }
178 
179 const char *MIDI_Source::getStallerName() const {
181 }
182 
184  if (hasSinkPipe())
186 }
187 
188 // :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: //
189 
191  if (this->sink != nullptr) {
192  FATAL_ERROR(F("This pipe is already connected to a sink"), 0x9145);
193  return; // LCOV_EXCL_LINE
194  }
195  this->sink = sink;
196 }
197 
198 void MIDI_Pipe::disconnectSink() { this->sink = nullptr; }
199 
201  if (this->source != nullptr) {
202  FATAL_ERROR(F("This pipe is already connected to a source"), 0x9146);
203  return; // LCOV_EXCL_LINE
204  }
205  this->source = source;
206 }
207 
208 void MIDI_Pipe::disconnectSource() { this->source = nullptr; }
209 
211  if (hasSink() && hasThroughIn()) {
212  auto oldSink = sink;
213  auto oldThroughIn = throughIn;
215  this->disconnectSourcePipesShallow(); // disconnect throughIn
216  oldSink->connectSourcePipe(oldThroughIn);
217  }
218  if (hasSource() && hasThroughOut()) {
219  auto oldSource = source;
220  auto oldThroughOut = throughOut;
222  this->disconnectSinkPipesShallow(); // disconnect throughOut
223  oldSource->connectSinkPipe(oldThroughOut);
224  }
225  if (hasSink())
227 
228  if (hasSource())
230 
231  if (hasThroughIn() || hasThroughOut())
232  FATAL_ERROR(F("Invalid state"), 0x9147); // LCOV_EXCL_LINE
233 }
234 
236 
238  if (!sinkIsUnstalledOrStalledBy(cause)) {
239  FATAL_ERROR(F("Cannot stall pipe from ")
240  << MIDIStaller::getNameNull(cause)
241  << F(" because pipe is already stalled by ")
243  0x6665);
244  } // LCOV_EXCL_LINE
245  sink_staller = cause;
246  if (hasThroughOut() && stallsrc == source)
247  throughOut->stallDownstream(cause, this);
248  if (hasSink())
249  sink->stallDownstream(cause, this);
250  if (hasSource() && source != stallsrc) {
251  // If our through output is stalled, that means our upstream is stalled
252  // as well by this staller. Unstall it first, and replace it by the new
253  // staller
254  if (through_staller != nullptr)
256  source->stallUpstream(cause, this);
257  }
258  if (hasThroughIn() && throughIn != stallsrc)
259  throughIn->stallUpstream(cause, this);
260 }
261 
263  if (stallsrc == sink) {
264  // This cannot be a different cause, because then our sink would
265  // already have caught it in stallDownstream().
266  sink_staller = cause;
267  if (hasSource())
268  source->stallUpstream(cause, this);
269  if (hasThroughIn())
270  throughIn->stallUpstream(cause, this);
271  } else {
272  if (through_staller == nullptr) {
273  through_staller = cause;
274  if (hasSource())
275  source->stallUpstream(cause, this);
276  }
277  }
278 }
279 
281  if (!sinkIsUnstalledOrStalledBy(cause)) {
282  FATAL_ERROR(F("Cannot unstall pipe from ")
283  << MIDIStaller::getNameNull(cause)
284  << F(" because pipe is stalled by ")
286  0x6666);
287  } // LCOV_EXCL_LINE
288  this->sink_staller = nullptr;
289  if (hasThroughOut() && stallsrc == source)
290  throughOut->unstallDownstream(cause, this);
291  if (hasSink())
292  sink->unstallDownstream(cause, this);
293  if (hasSource() && source != stallsrc) {
294  source->unstallUpstream(cause, this);
295  // If the through output of this pipe is stalled, we cannot just unstall
296  // our upstream, we have to update it our through output staller
297  if (through_staller != nullptr)
299  }
300  if (hasThroughIn() && throughIn != stallsrc)
301  throughIn->unstallUpstream(cause, this);
302 }
303 
305  if (stallsrc == sink) {
306  // This cannot be a different cause, because then our sink would
307  // already have caught it in unstallDownstream().
308  sink_staller = nullptr;
309  if (hasSource())
310  source->unstallUpstream(cause, this);
311  if (hasThroughIn())
312  throughIn->unstallUpstream(cause, this);
313  } else {
314  if (cause == through_staller) {
315  through_staller = nullptr;
316  if (hasSource())
317  source->unstallUpstream(cause, this);
318  }
319  }
320 }
321 
322 const char *MIDI_Pipe::getSinkStallerName() const {
324 }
325 
326 const char *MIDI_Pipe::getThroughStallerName() const {
328 }
329 
332 }
333 
334 const char *MIDI_Pipe::getStallerName() const {
336 }
337 
339  if (!isStalled())
340  return;
342  FATAL_ERROR(F("Unable to unstall pipe (eternal stall)"), 0x4827);
343  uint8_t iterations = 10;
344  while(isStalled() && iterations-- > 0) {
345  if (sink_staller)
347  if (through_staller)
349  }
350 }
351 
352 // :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: //
353 
355  if (s == nullptr)
356  return "(null)";
357  if (s == eternal_stall)
358  return "(eternal stall)";
359  return s->getName();
360 }
361 
363 
MIDIStaller *const eternal_stall
Definition: MIDI_Pipes.hpp:16
#define END_CS_NAMESPACE
#define BEGIN_CS_NAMESPACE
#define AH_DIAGNOSTIC_POP()
Definition: Warnings.hpp:36
#define AH_DIAGNOSTIC_WERROR()
Definition: Warnings.hpp:35
Class that routes MIDI messages from a MIDI_Source to a MIDI_Sink.
Definition: MIDI_Pipes.hpp:326
void acceptMIDIfromSource(Message msg)
Accept a MIDI message from the source, forward it to the “through” output if necessary,...
Definition: MIDI_Pipes.hpp:523
bool hasSink() const
Check if this pipe is connected to a sink.
Definition: MIDI_Pipes.hpp:417
const char * getStallerName() const
Get the name of any staller.
Definition: MIDI_Pipes.cpp:334
MIDIStaller * getStaller() const
Get any staller: returns getSinkStaller() if it's not null, getThroughStaller() otherwise.
Definition: MIDI_Pipes.cpp:330
MIDI_Source * source
Definition: MIDI_Pipes.hpp:573
void connectSource(MIDI_Source *source)
Set the source pointer to point to the given source.
Definition: MIDI_Pipes.cpp:200
MIDI_Sink * sink
Definition: MIDI_Pipes.hpp:572
void unstallDownstream(MIDIStaller *cause, MIDI_Source *stallsrc) override
Undoes the stalling by stallDownstream.
Definition: MIDI_Pipes.cpp:280
bool hasSource() const
Check if this pipe is connected to a source.
Definition: MIDI_Pipes.hpp:419
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...
Definition: MIDI_Pipes.cpp:237
bool hasThroughOut() const
Check if this pipe has a “through” output that sends all incoming messages from the input (source) to...
Definition: MIDI_Pipes.hpp:422
MIDIStaller * through_staller
Definition: MIDI_Pipes.hpp:577
void connectSink(MIDI_Sink *sink)
Set the sink pointer to point to the given sink.
Definition: MIDI_Pipes.cpp:190
const char * getSinkStallerName() const
Get the name of the staller (cause of the stall) that causes the sink of this pipe to be stalled.
Definition: MIDI_Pipes.cpp:322
MIDI_Pipe *& throughOut
Definition: MIDI_Pipes.hpp:574
bool isStalled() const
Check if this pipe is stalled.
Definition: MIDI_Pipes.hpp:370
MIDIStaller * sink_staller
Definition: MIDI_Pipes.hpp:576
virtual ~MIDI_Pipe()
Destructor.
Definition: MIDI_Pipes.cpp:235
const char * getThroughStallerName() const
Get the name of the staller (cause of the stall) that causes the “through” output of this pipe to be ...
Definition: MIDI_Pipes.cpp:326
void unstallUpstream(MIDIStaller *cause, MIDI_Sink *stallsrc) override
Undoes the stalling by stallUpstream.
Definition: MIDI_Pipes.cpp:304
void disconnect()
Disconnect this pipe from all other pipes, sources and sinks.
Definition: MIDI_Pipes.cpp:210
void handleStallers() const
Give the code that is stalling the MIDI pipe the opportunity to do its job and unstall the pipe.
Definition: MIDI_Pipes.cpp:338
MIDI_Pipe *& throughIn
Definition: MIDI_Pipes.hpp:575
void disconnectSink()
Set the sink pointer to null.
Definition: MIDI_Pipes.cpp:198
bool hasThroughIn() const
Check if this pipe has a “through” input that merges all messages from another pipe into the output (...
Definition: MIDI_Pipes.hpp:425
void disconnectSource()
Set the source pointer to null.
Definition: MIDI_Pipes.cpp:208
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...
Definition: MIDI_Pipes.hpp:396
void stallUpstream(MIDIStaller *cause, MIDI_Sink *stallsrc) override
Stall this pipe and all other pipes further upstream (following the path of the "trough" input).
Definition: MIDI_Pipes.cpp:262
Receives MIDI messages from a MIDI pipe.
Definition: MIDI_Pipes.hpp:71
void connectSourcePipe(MIDI_Pipe *source)
Fully connect a source pipe to this sink.
Definition: MIDI_Pipes.cpp:16
virtual void stallDownstream(MIDIStaller *, MIDI_Source *)
Base case for recursive stall function.
Definition: MIDI_Pipes.hpp:127
MIDI_Sink & operator=(const MIDI_Sink &)=delete
Copy assignment (copying not allowed).
virtual ~MIDI_Sink()
Destructor.
Definition: MIDI_Pipes.cpp:46
static void swap(MIDI_Sink &a, MIDI_Sink &b)
Definition: MIDI_Pipes.cpp:56
virtual void unstallDownstream(MIDIStaller *, MIDI_Source *)
Base case for recursive un-stall function.
Definition: MIDI_Pipes.hpp:130
MIDI_Sink()=default
Default constructor.
void disconnectSourcePipes()
Disconnect all source pipes that sink to this sink (recursively).
Definition: MIDI_Pipes.cpp:25
void disconnectSourcePipesShallow()
Disconnect only the first pipe connected to this sink.
Definition: MIDI_Pipes.cpp:33
MIDI_Pipe * sourcePipe
Definition: MIDI_Pipes.hpp:142
bool disconnect(TrueMIDI_Source &source)
Disconnect the given source from this sink.
Definition: MIDI_Pipes.cpp:40
bool hasSourcePipe() const
Check if this sink is connected to a source pipe.
Definition: MIDI_Pipes.hpp:117
Class that can send MIDI messages to a MIDI pipe.
Definition: MIDI_Pipes.hpp:154
MIDI_Source & operator=(const MIDI_Source &)=delete
Copy assignment (copying not allowed).
const char * getStallerName() const
Get the name of whatever is causing this MIDI source to be stalled.
Definition: MIDI_Pipes.cpp:179
void connectSinkPipe(MIDI_Pipe *sink)
Fully connect a sink pipe to this source.
Definition: MIDI_Pipes.cpp:75
virtual ~MIDI_Source()
Destructor.
Definition: MIDI_Pipes.cpp:130
MIDIStaller * getStaller() const
Get a pointer to whatever is causing this MIDI source to be stalled.
Definition: MIDI_Pipes.cpp:173
MIDI_Source()=default
Default constructor.
void sourceMIDItoPipe(ChannelMessage)
Send a MIDI Channel Message down the pipe.
Definition: MIDI_Pipes.cpp:132
MIDI_Pipe * sinkPipe
Definition: MIDI_Pipes.hpp:256
void stall(MIDIStaller *cause=eternal_stall)
Stall this MIDI source.
Definition: MIDI_Pipes.cpp:157
virtual void stallUpstream(MIDIStaller *, MIDI_Sink *)
Base case for recursive stall function.
Definition: MIDI_Pipes.hpp:241
bool isStalled() const
Check if this source can write to the sinks it connects to.
Definition: MIDI_Pipes.cpp:167
static void swap(MIDI_Source &a, MIDI_Source &b)
Definition: MIDI_Pipes.cpp:113
void disconnectSinkPipesShallow()
Disconnect only the first pipe connected to this source.
Definition: MIDI_Pipes.cpp:92
bool disconnect(TrueMIDI_Sink &sink)
Disconnect the given sink from this source.
Definition: MIDI_Pipes.cpp:99
void disconnectSinkPipes()
Disconnect all sink pipes that this source sinks to (recursively).
Definition: MIDI_Pipes.cpp:84
void handleStallers() const
Give the code that is stalling the MIDI sink pipes the opportunity to do its job and un-stall the pip...
Definition: MIDI_Pipes.cpp:183
virtual void unstallUpstream(MIDIStaller *, MIDI_Sink *)
Base case for recursive un-stall function.
Definition: MIDI_Pipes.hpp:244
bool hasSinkPipe() const
Check if this source is connected to a sink pipe.
Definition: MIDI_Pipes.hpp:231
void unstall(MIDIStaller *cause=eternal_stall)
Un-stall the pipes connected to this source, so other sources are allowed to send again.
Definition: MIDI_Pipes.cpp:162
#define FATAL_ERROR(msg, errc)
Print the error message and error code, and stop the execution.
Definition: Error.hpp:60
void swap(T &t1, T &t2)
Definition: move.h:21
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.
Definition: MIDI_Pipes.cpp:354