Line data Source code
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 :
10 : AH_DIAGNOSTIC_WERROR()
11 :
12 : BEGIN_CS_NAMESPACE
13 :
14 : // :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: //
15 :
16 399 : void MIDI_Sink::connectSourcePipe(MIDI_Pipe *source) {
17 399 : if (this->sourcePipe == nullptr) {
18 304 : source->connectSink(this);
19 303 : this->sourcePipe = source;
20 : } else {
21 95 : this->sourcePipe->connectSourcePipe(source);
22 : }
23 398 : }
24 :
25 2277 : void MIDI_Sink::disconnectSourcePipes() {
26 2277 : if (sourcePipe != nullptr) {
27 67 : sourcePipe->disconnectSourcePipes();
28 67 : sourcePipe->disconnect();
29 67 : sourcePipe = nullptr;
30 : }
31 2277 : }
32 :
33 303 : void MIDI_Sink::disconnectSourcePipesShallow() {
34 303 : if (sourcePipe != nullptr) {
35 303 : sourcePipe->disconnectSink();
36 303 : sourcePipe = nullptr;
37 : }
38 303 : }
39 :
40 13 : bool MIDI_Sink::disconnect(TrueMIDI_Source &source) {
41 13 : if (!hasSourcePipe())
42 3 : return false;
43 10 : return sourcePipe->disconnect(source);
44 : }
45 :
46 2209 : MIDI_Sink::~MIDI_Sink() { disconnectSourcePipes(); }
47 :
48 5 : MIDI_Sink::MIDI_Sink(MIDI_Sink &&other)
49 5 : : sourcePipe(std::exchange(other.sourcePipe, nullptr)) {
50 5 : if (this->hasSourcePipe()) {
51 5 : this->sourcePipe->disconnectSink();
52 5 : this->sourcePipe->connectSink(this);
53 : }
54 5 : }
55 :
56 3 : void MIDI_Sink::swap(MIDI_Sink &a, MIDI_Sink &b) {
57 3 : std::swap(a.sourcePipe, b.sourcePipe);
58 3 : if (a.hasSourcePipe()) {
59 3 : a.sourcePipe->disconnectSink();
60 3 : a.sourcePipe->connectSink(&a);
61 : }
62 3 : if (b.hasSourcePipe()) {
63 1 : b.sourcePipe->disconnectSink();
64 1 : b.sourcePipe->connectSink(&b);
65 : }
66 3 : }
67 :
68 2 : MIDI_Sink &MIDI_Sink::operator=(MIDI_Sink &&other) {
69 2 : swap(*this, other);
70 2 : return *this;
71 : }
72 :
73 : // :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: //
74 :
75 362 : void MIDI_Source::connectSinkPipe(MIDI_Pipe *sink) {
76 362 : if (this->sinkPipe == nullptr) {
77 290 : sink->connectSource(this);
78 289 : this->sinkPipe = sink;
79 : } else {
80 72 : this->sinkPipe->connectSinkPipe(sink);
81 : }
82 361 : }
83 :
84 2391 : void MIDI_Source::disconnectSinkPipes() {
85 2391 : if (sinkPipe != nullptr) {
86 164 : sinkPipe->disconnectSinkPipes();
87 164 : sinkPipe->disconnect();
88 164 : sinkPipe = nullptr;
89 : }
90 2391 : }
91 :
92 289 : void MIDI_Source::disconnectSinkPipesShallow() {
93 289 : if (sinkPipe != nullptr) {
94 289 : sinkPipe->disconnectSource();
95 289 : sinkPipe = nullptr;
96 : }
97 289 : }
98 :
99 13 : bool MIDI_Source::disconnect(TrueMIDI_Sink &sink) {
100 13 : if (!hasSinkPipe())
101 3 : return false;
102 10 : return sinkPipe->disconnect(sink);
103 : }
104 :
105 5 : MIDI_Source::MIDI_Source(MIDI_Source &&other)
106 5 : : sinkPipe(std::exchange(other.sinkPipe, nullptr)) {
107 5 : if (this->hasSinkPipe()) {
108 5 : this->sinkPipe->disconnectSource();
109 5 : this->sinkPipe->connectSource(this);
110 : }
111 5 : }
112 :
113 3 : void MIDI_Source::swap(MIDI_Source &a, MIDI_Source &b) {
114 3 : std::swap(a.sinkPipe, b.sinkPipe);
115 3 : if (a.hasSinkPipe()) {
116 3 : a.sinkPipe->disconnectSource();
117 3 : a.sinkPipe->connectSource(&a);
118 : }
119 3 : if (b.hasSinkPipe()) {
120 1 : b.sinkPipe->disconnectSource();
121 1 : b.sinkPipe->connectSource(&b);
122 : }
123 3 : }
124 :
125 2 : MIDI_Source &MIDI_Source::operator=(MIDI_Source &&other) {
126 2 : swap(*this, other);
127 2 : return *this;
128 : }
129 :
130 2225 : MIDI_Source::~MIDI_Source() { disconnectSinkPipes(); }
131 :
132 149 : void MIDI_Source::sourceMIDItoPipe(ChannelMessage msg) {
133 149 : if (sinkPipe != nullptr) {
134 135 : handleStallers();
135 135 : sinkPipe->acceptMIDIfromSource(msg);
136 : }
137 149 : }
138 15 : void MIDI_Source::sourceMIDItoPipe(SysExMessage msg) {
139 15 : if (sinkPipe != nullptr) {
140 10 : handleStallers();
141 10 : sinkPipe->acceptMIDIfromSource(msg);
142 : }
143 15 : }
144 0 : void MIDI_Source::sourceMIDItoPipe(SysCommonMessage msg) {
145 0 : if (sinkPipe != nullptr) {
146 0 : handleStallers();
147 0 : sinkPipe->acceptMIDIfromSource(msg);
148 : }
149 0 : }
150 19 : void MIDI_Source::sourceMIDItoPipe(RealTimeMessage msg) {
151 19 : if (sinkPipe != nullptr) {
152 : // Always send write to pipe, don't check if it's stalled or not
153 15 : sinkPipe->acceptMIDIfromSource(msg);
154 : }
155 19 : }
156 :
157 36 : void MIDI_Source::stall(MIDIStaller *cause) {
158 36 : if (hasSinkPipe())
159 36 : sinkPipe->stallDownstream(cause, this);
160 35 : }
161 :
162 22 : void MIDI_Source::unstall(MIDIStaller *cause) {
163 22 : if (hasSinkPipe())
164 22 : sinkPipe->unstallDownstream(cause, this);
165 21 : }
166 :
167 40 : bool MIDI_Source::isStalled() const {
168 40 : if (hasSinkPipe())
169 36 : return sinkPipe->isStalled();
170 4 : return false;
171 : }
172 :
173 30 : MIDIStaller *MIDI_Source::getStaller() const {
174 30 : if (hasSinkPipe())
175 14 : return sinkPipe->getStaller();
176 16 : return nullptr;
177 : }
178 :
179 4 : const char *MIDI_Source::getStallerName() const {
180 4 : return MIDIStaller::getNameNull(getStaller());
181 : }
182 :
183 150 : void MIDI_Source::handleStallers() const {
184 150 : if (hasSinkPipe())
185 150 : sinkPipe->handleStallers();
186 149 : }
187 :
188 : // :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: //
189 :
190 313 : void MIDI_Pipe::connectSink(MIDI_Sink *sink) {
191 313 : if (this->sink != nullptr) {
192 1 : FATAL_ERROR(F("This pipe is already connected to a sink"), 0x9145);
193 : return; // LCOV_EXCL_LINE
194 : }
195 312 : this->sink = sink;
196 : }
197 :
198 312 : void MIDI_Pipe::disconnectSink() { this->sink = nullptr; }
199 :
200 299 : void MIDI_Pipe::connectSource(MIDI_Source *source) {
201 299 : if (this->source != nullptr) {
202 1 : FATAL_ERROR(F("This pipe is already connected to a source"), 0x9146);
203 : return; // LCOV_EXCL_LINE
204 : }
205 298 : this->source = source;
206 : }
207 :
208 298 : void MIDI_Pipe::disconnectSource() { this->source = nullptr; }
209 :
210 1629 : void MIDI_Pipe::disconnect() {
211 1629 : if (hasSink() && hasThroughIn()) {
212 26 : auto oldSink = sink;
213 26 : auto oldThroughIn = throughIn;
214 26 : sink->disconnectSourcePipesShallow();
215 26 : this->disconnectSourcePipesShallow(); // disconnect throughIn
216 26 : oldSink->connectSourcePipe(oldThroughIn);
217 : }
218 1629 : if (hasSource() && hasThroughOut()) {
219 14 : auto oldSource = source;
220 14 : auto oldThroughOut = throughOut;
221 14 : source->disconnectSinkPipesShallow();
222 14 : this->disconnectSinkPipesShallow(); // disconnect throughOut
223 14 : oldSource->connectSinkPipe(oldThroughOut);
224 : }
225 1629 : if (hasSink())
226 251 : sink->disconnectSourcePipesShallow();
227 :
228 1629 : if (hasSource())
229 261 : source->disconnectSinkPipesShallow();
230 :
231 1629 : if (hasThroughIn() || hasThroughOut())
232 : FATAL_ERROR(F("Invalid state"), 0x9147); // LCOV_EXCL_LINE
233 1629 : }
234 :
235 1380 : MIDI_Pipe::~MIDI_Pipe() { disconnect(); }
236 :
237 74 : void MIDI_Pipe::stallDownstream(MIDIStaller *cause, MIDI_Source *stallsrc) {
238 74 : if (!sinkIsUnstalledOrStalledBy(cause)) {
239 1 : FATAL_ERROR(F("Cannot stall pipe from ")
240 : << MIDIStaller::getNameNull(cause)
241 : << F(" because pipe is already stalled by ")
242 : << MIDIStaller::getNameNull(sink_staller),
243 : 0x6665);
244 : } // LCOV_EXCL_LINE
245 73 : sink_staller = cause;
246 73 : if (hasThroughOut() && stallsrc == source)
247 1 : throughOut->stallDownstream(cause, this);
248 73 : if (hasSink())
249 73 : sink->stallDownstream(cause, this);
250 73 : 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 37 : if (through_staller != nullptr)
255 3 : source->unstallUpstream(through_staller, this);
256 37 : source->stallUpstream(cause, this);
257 : }
258 73 : if (hasThroughIn() && throughIn != stallsrc)
259 8 : throughIn->stallUpstream(cause, this);
260 73 : }
261 :
262 35 : void MIDI_Pipe::stallUpstream(MIDIStaller *cause, MIDI_Sink *stallsrc) {
263 35 : if (stallsrc == sink) {
264 : // This cannot be a different cause, because then our sink would
265 : // already have caught it in stallDownstream().
266 9 : sink_staller = cause;
267 9 : if (hasSource())
268 9 : source->stallUpstream(cause, this);
269 9 : if (hasThroughIn())
270 1 : throughIn->stallUpstream(cause, this);
271 : } else {
272 26 : if (through_staller == nullptr) {
273 17 : through_staller = cause;
274 17 : if (hasSource())
275 17 : source->stallUpstream(cause, this);
276 : }
277 : }
278 35 : }
279 :
280 41 : void MIDI_Pipe::unstallDownstream(MIDIStaller *cause, MIDI_Source *stallsrc) {
281 41 : if (!sinkIsUnstalledOrStalledBy(cause)) {
282 1 : FATAL_ERROR(F("Cannot unstall pipe from ")
283 : << MIDIStaller::getNameNull(cause)
284 : << F(" because pipe is stalled by ")
285 : << MIDIStaller::getNameNull(sink_staller),
286 : 0x6666);
287 : } // LCOV_EXCL_LINE
288 40 : this->sink_staller = nullptr;
289 40 : if (hasThroughOut() && stallsrc == source)
290 1 : throughOut->unstallDownstream(cause, this);
291 40 : if (hasSink())
292 40 : sink->unstallDownstream(cause, this);
293 40 : if (hasSource() && source != stallsrc) {
294 18 : 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 18 : if (through_staller != nullptr)
298 4 : source->stallUpstream(through_staller, this);
299 : }
300 40 : if (hasThroughIn() && throughIn != stallsrc)
301 7 : throughIn->unstallUpstream(cause, this);
302 40 : }
303 :
304 25 : void MIDI_Pipe::unstallUpstream(MIDIStaller *cause, MIDI_Sink *stallsrc) {
305 25 : if (stallsrc == sink) {
306 : // This cannot be a different cause, because then our sink would
307 : // already have caught it in unstallDownstream().
308 8 : sink_staller = nullptr;
309 8 : if (hasSource())
310 8 : source->unstallUpstream(cause, this);
311 8 : if (hasThroughIn())
312 1 : throughIn->unstallUpstream(cause, this);
313 : } else {
314 17 : if (cause == through_staller) {
315 11 : through_staller = nullptr;
316 11 : if (hasSource())
317 11 : source->unstallUpstream(cause, this);
318 : }
319 : }
320 25 : }
321 :
322 4 : const char *MIDI_Pipe::getSinkStallerName() const {
323 4 : return MIDIStaller::getNameNull(sink_staller);
324 : }
325 :
326 2 : const char *MIDI_Pipe::getThroughStallerName() const {
327 2 : return MIDIStaller::getNameNull(through_staller);
328 : }
329 :
330 20 : MIDIStaller *MIDI_Pipe::getStaller() const {
331 20 : return sink_staller ? sink_staller : through_staller;
332 : }
333 :
334 3 : const char *MIDI_Pipe::getStallerName() const {
335 3 : return MIDIStaller::getNameNull(getStaller());
336 : }
337 :
338 150 : void MIDI_Pipe::handleStallers() const {
339 150 : if (!isStalled())
340 142 : return;
341 8 : if (sink_staller == eternal_stall || through_staller == eternal_stall)
342 1 : FATAL_ERROR(F("Unable to unstall pipe (eternal stall)"), 0x4827);
343 7 : uint8_t iterations = 10;
344 23 : while(isStalled() && iterations-- > 0) {
345 16 : if (sink_staller)
346 15 : sink_staller->handleStall();
347 16 : if (through_staller)
348 1 : through_staller->handleStall();
349 : }
350 : }
351 :
352 : // :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: //
353 :
354 17 : const char *MIDIStaller::getNameNull(MIDIStaller *s) {
355 17 : if (s == nullptr)
356 4 : return "(null)";
357 13 : if (s == eternal_stall)
358 1 : return "(eternal stall)";
359 12 : return s->getName();
360 : }
361 :
362 : END_CS_NAMESPACE
363 :
364 : AH_DIAGNOSTIC_POP()
|