LCOV - code coverage report
Current view: top level - src/MIDI_Interfaces - MIDI_Pipes.cpp (source / functions) Coverage Total Hit
Test: 73449d9b107c772cf65493691543348214e5d5eb Lines: 97.8 % 230 225
Test Date: 2026-06-06 17:44:35 Functions: 97.6 % 42 41
Legend: Lines:     hit not hit

            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
        

Generated by: LCOV version 2.4-beta