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 : BEGIN_CS_NAMESPACE
14 :
15 : // :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: //
16 :
17 401 : void MIDI_Sink::connectSourcePipe(MIDI_Pipe *source) {
18 401 : if (this->sourcePipe == nullptr) {
19 306 : source->connectSink(this);
20 305 : this->sourcePipe = source;
21 : } else {
22 95 : this->sourcePipe->connectSourcePipe(source);
23 : }
24 400 : }
25 :
26 2322 : void MIDI_Sink::disconnectSourcePipes() {
27 2322 : if (sourcePipe != nullptr) {
28 68 : sourcePipe->disconnectSourcePipes();
29 68 : sourcePipe->disconnect();
30 68 : sourcePipe = nullptr;
31 : }
32 2322 : }
33 :
34 305 : void MIDI_Sink::disconnectSourcePipesShallow() {
35 305 : if (sourcePipe != nullptr) {
36 305 : sourcePipe->disconnectSink();
37 305 : sourcePipe = nullptr;
38 : }
39 305 : }
40 :
41 13 : bool MIDI_Sink::disconnect(TrueMIDI_Source &source) {
42 13 : if (!hasSourcePipe())
43 3 : return false;
44 10 : return sourcePipe->disconnect(source);
45 : }
46 :
47 2253 : MIDI_Sink::~MIDI_Sink() { disconnectSourcePipes(); }
48 :
49 5 : MIDI_Sink::MIDI_Sink(MIDI_Sink &&other)
50 5 : : sourcePipe(std::exchange(other.sourcePipe, nullptr)) {
51 5 : if (this->hasSourcePipe()) {
52 5 : this->sourcePipe->disconnectSink();
53 5 : this->sourcePipe->connectSink(this);
54 : }
55 5 : }
56 :
57 3 : void MIDI_Sink::swap(MIDI_Sink &a, MIDI_Sink &b) {
58 3 : std::swap(a.sourcePipe, b.sourcePipe);
59 3 : if (a.hasSourcePipe()) {
60 3 : a.sourcePipe->disconnectSink();
61 3 : a.sourcePipe->connectSink(&a);
62 : }
63 3 : if (b.hasSourcePipe()) {
64 1 : b.sourcePipe->disconnectSink();
65 1 : b.sourcePipe->connectSink(&b);
66 : }
67 3 : }
68 :
69 2 : MIDI_Sink &MIDI_Sink::operator=(MIDI_Sink &&other) {
70 2 : swap(*this, other);
71 2 : return *this;
72 : }
73 :
74 : // :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: //
75 :
76 364 : void MIDI_Source::connectSinkPipe(MIDI_Pipe *sink) {
77 364 : if (this->sinkPipe == nullptr) {
78 292 : sink->connectSource(this);
79 291 : this->sinkPipe = sink;
80 : } else {
81 72 : this->sinkPipe->connectSinkPipe(sink);
82 : }
83 363 : }
84 :
85 2436 : void MIDI_Source::disconnectSinkPipes() {
86 2436 : if (sinkPipe != nullptr) {
87 165 : sinkPipe->disconnectSinkPipes();
88 165 : sinkPipe->disconnect();
89 165 : sinkPipe = nullptr;
90 : }
91 2436 : }
92 :
93 291 : void MIDI_Source::disconnectSinkPipesShallow() {
94 291 : if (sinkPipe != nullptr) {
95 291 : sinkPipe->disconnectSource();
96 291 : sinkPipe = nullptr;
97 : }
98 291 : }
99 :
100 13 : bool MIDI_Source::disconnect(TrueMIDI_Sink &sink) {
101 13 : if (!hasSinkPipe())
102 3 : return false;
103 10 : return sinkPipe->disconnect(sink);
104 : }
105 :
106 5 : MIDI_Source::MIDI_Source(MIDI_Source &&other)
107 5 : : sinkPipe(std::exchange(other.sinkPipe, nullptr)) {
108 5 : if (this->hasSinkPipe()) {
109 5 : this->sinkPipe->disconnectSource();
110 5 : this->sinkPipe->connectSource(this);
111 : }
112 5 : }
113 :
114 3 : void MIDI_Source::swap(MIDI_Source &a, MIDI_Source &b) {
115 3 : std::swap(a.sinkPipe, b.sinkPipe);
116 3 : if (a.hasSinkPipe()) {
117 3 : a.sinkPipe->disconnectSource();
118 3 : a.sinkPipe->connectSource(&a);
119 : }
120 3 : if (b.hasSinkPipe()) {
121 1 : b.sinkPipe->disconnectSource();
122 1 : b.sinkPipe->connectSource(&b);
123 : }
124 3 : }
125 :
126 2 : MIDI_Source &MIDI_Source::operator=(MIDI_Source &&other) {
127 2 : swap(*this, other);
128 2 : return *this;
129 : }
130 :
131 2269 : MIDI_Source::~MIDI_Source() { disconnectSinkPipes(); }
132 :
133 158 : void MIDI_Source::sourceMIDItoPipe(ChannelMessage msg) {
134 158 : if (sinkPipe != nullptr) {
135 136 : handleStallers();
136 136 : sinkPipe->acceptMIDIfromSource(msg);
137 : }
138 158 : }
139 15 : void MIDI_Source::sourceMIDItoPipe(SysExMessage msg) {
140 15 : if (sinkPipe != nullptr) {
141 10 : handleStallers();
142 10 : sinkPipe->acceptMIDIfromSource(msg);
143 : }
144 15 : }
145 0 : void MIDI_Source::sourceMIDItoPipe(SysCommonMessage msg) {
146 0 : if (sinkPipe != nullptr) {
147 0 : handleStallers();
148 0 : sinkPipe->acceptMIDIfromSource(msg);
149 : }
150 0 : }
151 19 : void MIDI_Source::sourceMIDItoPipe(RealTimeMessage msg) {
152 19 : if (sinkPipe != nullptr) {
153 : // Always send write to pipe, don't check if it's stalled or not
154 15 : sinkPipe->acceptMIDIfromSource(msg);
155 : }
156 19 : }
157 :
158 36 : void MIDI_Source::stall(MIDIStaller *cause) {
159 36 : if (hasSinkPipe())
160 36 : sinkPipe->stallDownstream(cause, this);
161 : DEBUGFN(F("Stalled MIDI source. Cause: ") << getStallerName());
162 35 : }
163 :
164 22 : void MIDI_Source::unstall(MIDIStaller *cause) {
165 : DEBUGFN(F("Un-stalling MIDI source. Cause: ") << getStallerName());
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 15 : return sinkPipe->getStaller();
179 15 : return nullptr;
180 : }
181 :
182 5 : const char *MIDI_Source::getStallerName() const {
183 5 : return MIDIStaller::getNameNull(getStaller());
184 : }
185 :
186 151 : void MIDI_Source::handleStallers() const {
187 151 : if (hasSinkPipe())
188 151 : sinkPipe->handleStallers();
189 150 : }
190 :
191 : // :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: //
192 :
193 315 : void MIDI_Pipe::connectSink(MIDI_Sink *sink) {
194 315 : 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 314 : this->sink = sink;
199 : }
200 :
201 314 : void MIDI_Pipe::disconnectSink() { this->sink = nullptr; }
202 :
203 301 : void MIDI_Pipe::connectSource(MIDI_Source *source) {
204 301 : 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 300 : this->source = source;
209 : }
210 :
211 300 : void MIDI_Pipe::disconnectSource() { this->source = nullptr; }
212 :
213 1659 : void MIDI_Pipe::disconnect() {
214 1659 : if (hasSink() && hasThroughIn()) {
215 26 : auto oldSink = sink;
216 26 : auto oldThroughIn = getThroughIn();
217 26 : sink->disconnectSourcePipesShallow();
218 26 : this->disconnectSourcePipesShallow(); // disconnect throughIn
219 26 : oldSink->connectSourcePipe(oldThroughIn);
220 : }
221 1659 : if (hasSource() && hasThroughOut()) {
222 14 : auto oldSource = source;
223 14 : auto oldThroughOut = getThroughOut();
224 14 : source->disconnectSinkPipesShallow();
225 14 : this->disconnectSinkPipesShallow(); // disconnect throughOut
226 14 : oldSource->connectSinkPipe(oldThroughOut);
227 : }
228 1659 : if (hasSink())
229 253 : sink->disconnectSourcePipesShallow();
230 :
231 1659 : if (hasSource())
232 263 : source->disconnectSinkPipesShallow();
233 :
234 1659 : if (hasThroughIn() || hasThroughOut())
235 : FATAL_ERROR(F("Invalid state"), 0x9147); // LCOV_EXCL_LINE
236 1659 : }
237 :
238 1408 : 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 : getThroughOut()->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() && getThroughIn() != stallsrc)
262 8 : getThroughIn()->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 : getThroughIn()->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 : getThroughOut()->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() && getThroughIn() != stallsrc)
304 7 : getThroughIn()->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 : getThroughIn()->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 21 : MIDIStaller *MIDI_Pipe::getStaller() const {
334 21 : 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 151 : void MIDI_Pipe::handleStallers() const {
342 151 : if (!isStalled())
343 143 : 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 18 : const char *MIDIStaller::getNameNull(MIDIStaller *s) {
358 18 : if (s == nullptr)
359 4 : return "(null)";
360 14 : if (s == eternal_stall)
361 1 : return "(eternal stall)";
362 13 : return s->getName();
363 : }
364 :
365 : END_CS_NAMESPACE
366 :
367 : #endif
|