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