LCOV - code coverage report
Current view: top level - src/MIDI_Interfaces - MIDI_Pipes.cpp (source / functions) Hit Total Coverage
Test: b8a30b4b7040ae1abf162fd0a258beaa2de43626 Lines: 225 230 97.8 %
Date: 2024-12-21 21:28:55 Functions: 41 45 91.1 %
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 1.15