cs-midi/MIDI_Interfaces/MIDI_Pipes.cpp

368 lines
11 KiB
C++

#include <Settings/SettingsWrapper.hpp>
#if !DISABLE_PIPES
#include "MIDI_Pipes.hpp"
#include "MIDI_Staller.hpp"
#include <AH/Error/Error.hpp>
#include <AH/STL/utility>
#if defined(ESP32) || !defined(ARDUINO)
#include <mutex>
#endif
BEGIN_CS_NAMESPACE
// :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: //
void MIDI_Sink::connectSourcePipe(MIDI_Pipe *source) {
if (this->sourcePipe == nullptr) {
source->connectSink(this);
this->sourcePipe = source;
} else {
this->sourcePipe->connectSourcePipe(source);
}
}
void MIDI_Sink::disconnectSourcePipes() {
if (sourcePipe != nullptr) {
sourcePipe->disconnectSourcePipes();
sourcePipe->disconnect();
sourcePipe = nullptr;
}
}
void MIDI_Sink::disconnectSourcePipesShallow() {
if (sourcePipe != nullptr) {
sourcePipe->disconnectSink();
sourcePipe = nullptr;
}
}
bool MIDI_Sink::disconnect(TrueMIDI_Source &source) {
if (!hasSourcePipe())
return false;
return sourcePipe->disconnect(source);
}
MIDI_Sink::~MIDI_Sink() { disconnectSourcePipes(); }
MIDI_Sink::MIDI_Sink(MIDI_Sink &&other)
: sourcePipe(std::exchange(other.sourcePipe, nullptr)) {
if (this->hasSourcePipe()) {
this->sourcePipe->disconnectSink();
this->sourcePipe->connectSink(this);
}
}
void MIDI_Sink::swap(MIDI_Sink &a, MIDI_Sink &b) {
std::swap(a.sourcePipe, b.sourcePipe);
if (a.hasSourcePipe()) {
a.sourcePipe->disconnectSink();
a.sourcePipe->connectSink(&a);
}
if (b.hasSourcePipe()) {
b.sourcePipe->disconnectSink();
b.sourcePipe->connectSink(&b);
}
}
MIDI_Sink &MIDI_Sink::operator=(MIDI_Sink &&other) {
swap(*this, other);
return *this;
}
// :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: //
void MIDI_Source::connectSinkPipe(MIDI_Pipe *sink) {
if (this->sinkPipe == nullptr) {
sink->connectSource(this);
this->sinkPipe = sink;
} else {
this->sinkPipe->connectSinkPipe(sink);
}
}
void MIDI_Source::disconnectSinkPipes() {
if (sinkPipe != nullptr) {
sinkPipe->disconnectSinkPipes();
sinkPipe->disconnect();
sinkPipe = nullptr;
}
}
void MIDI_Source::disconnectSinkPipesShallow() {
if (sinkPipe != nullptr) {
sinkPipe->disconnectSource();
sinkPipe = nullptr;
}
}
bool MIDI_Source::disconnect(TrueMIDI_Sink &sink) {
if (!hasSinkPipe())
return false;
return sinkPipe->disconnect(sink);
}
MIDI_Source::MIDI_Source(MIDI_Source &&other)
: sinkPipe(std::exchange(other.sinkPipe, nullptr)) {
if (this->hasSinkPipe()) {
this->sinkPipe->disconnectSource();
this->sinkPipe->connectSource(this);
}
}
void MIDI_Source::swap(MIDI_Source &a, MIDI_Source &b) {
std::swap(a.sinkPipe, b.sinkPipe);
if (a.hasSinkPipe()) {
a.sinkPipe->disconnectSource();
a.sinkPipe->connectSource(&a);
}
if (b.hasSinkPipe()) {
b.sinkPipe->disconnectSource();
b.sinkPipe->connectSource(&b);
}
}
MIDI_Source &MIDI_Source::operator=(MIDI_Source &&other) {
swap(*this, other);
return *this;
}
MIDI_Source::~MIDI_Source() { disconnectSinkPipes(); }
void MIDI_Source::sourceMIDItoPipe(ChannelMessage msg) {
if (sinkPipe != nullptr) {
handleStallers();
sinkPipe->acceptMIDIfromSource(msg);
}
}
void MIDI_Source::sourceMIDItoPipe(SysExMessage msg) {
if (sinkPipe != nullptr) {
handleStallers();
sinkPipe->acceptMIDIfromSource(msg);
}
}
void MIDI_Source::sourceMIDItoPipe(SysCommonMessage msg) {
if (sinkPipe != nullptr) {
handleStallers();
sinkPipe->acceptMIDIfromSource(msg);
}
}
void MIDI_Source::sourceMIDItoPipe(RealTimeMessage msg) {
if (sinkPipe != nullptr) {
// Always send write to pipe, don't check if it's stalled or not
sinkPipe->acceptMIDIfromSource(msg);
}
}
void MIDI_Source::stall(MIDIStaller *cause) {
if (hasSinkPipe())
sinkPipe->stallDownstream(cause, this);
DEBUGFN(F("Stalled MIDI source. Cause: ") << getStallerName());
}
void MIDI_Source::unstall(MIDIStaller *cause) {
DEBUGFN(F("Un-stalling MIDI source. Cause: ") << getStallerName());
if (hasSinkPipe())
sinkPipe->unstallDownstream(cause, this);
}
bool MIDI_Source::isStalled() const {
if (hasSinkPipe())
return sinkPipe->isStalled();
return false;
}
MIDIStaller *MIDI_Source::getStaller() const {
if (hasSinkPipe())
return sinkPipe->getStaller();
return nullptr;
}
const char *MIDI_Source::getStallerName() const {
return MIDIStaller::getNameNull(getStaller());
}
void MIDI_Source::handleStallers() const {
if (hasSinkPipe())
sinkPipe->handleStallers();
}
// :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: //
void MIDI_Pipe::connectSink(MIDI_Sink *sink) {
if (this->sink != nullptr) {
FATAL_ERROR(F("This pipe is already connected to a sink"), 0x9145);
return; // LCOV_EXCL_LINE
}
this->sink = sink;
}
void MIDI_Pipe::disconnectSink() { this->sink = nullptr; }
void MIDI_Pipe::connectSource(MIDI_Source *source) {
if (this->source != nullptr) {
FATAL_ERROR(F("This pipe is already connected to a source"), 0x9146);
return; // LCOV_EXCL_LINE
}
this->source = source;
}
void MIDI_Pipe::disconnectSource() { this->source = nullptr; }
void MIDI_Pipe::disconnect() {
if (hasSink() && hasThroughIn()) {
auto oldSink = sink;
auto oldThroughIn = getThroughIn();
sink->disconnectSourcePipesShallow();
this->disconnectSourcePipesShallow(); // disconnect throughIn
oldSink->connectSourcePipe(oldThroughIn);
}
if (hasSource() && hasThroughOut()) {
auto oldSource = source;
auto oldThroughOut = getThroughOut();
source->disconnectSinkPipesShallow();
this->disconnectSinkPipesShallow(); // disconnect throughOut
oldSource->connectSinkPipe(oldThroughOut);
}
if (hasSink())
sink->disconnectSourcePipesShallow();
if (hasSource())
source->disconnectSinkPipesShallow();
if (hasThroughIn() || hasThroughOut())
FATAL_ERROR(F("Invalid state"), 0x9147); // LCOV_EXCL_LINE
}
MIDI_Pipe::~MIDI_Pipe() { disconnect(); }
void MIDI_Pipe::stallDownstream(MIDIStaller *cause, MIDI_Source *stallsrc) {
if (!sinkIsUnstalledOrStalledBy(cause)) {
FATAL_ERROR(F("Cannot stall pipe from ")
<< MIDIStaller::getNameNull(cause)
<< F(" because pipe is already stalled by ")
<< MIDIStaller::getNameNull(sink_staller),
0x6665);
} // LCOV_EXCL_LINE
sink_staller = cause;
if (hasThroughOut() && stallsrc == source)
getThroughOut()->stallDownstream(cause, this);
if (hasSink())
sink->stallDownstream(cause, this);
if (hasSource() && source != stallsrc) {
// If our through output is stalled, that means our upstream is stalled
// as well by this staller. Unstall it first, and replace it by the new
// staller
if (through_staller != nullptr)
source->unstallUpstream(through_staller, this);
source->stallUpstream(cause, this);
}
if (hasThroughIn() && getThroughIn() != stallsrc)
getThroughIn()->stallUpstream(cause, this);
}
void MIDI_Pipe::stallUpstream(MIDIStaller *cause, MIDI_Sink *stallsrc) {
if (stallsrc == sink) {
// This cannot be a different cause, because then our sink would
// already have caught it in stallDownstream().
sink_staller = cause;
if (hasSource())
source->stallUpstream(cause, this);
if (hasThroughIn())
getThroughIn()->stallUpstream(cause, this);
} else {
if (through_staller == nullptr) {
through_staller = cause;
if (hasSource())
source->stallUpstream(cause, this);
}
}
}
void MIDI_Pipe::unstallDownstream(MIDIStaller *cause, MIDI_Source *stallsrc) {
if (!sinkIsUnstalledOrStalledBy(cause)) {
FATAL_ERROR(F("Cannot unstall pipe from ")
<< MIDIStaller::getNameNull(cause)
<< F(" because pipe is stalled by ")
<< MIDIStaller::getNameNull(sink_staller),
0x6666);
} // LCOV_EXCL_LINE
this->sink_staller = nullptr;
if (hasThroughOut() && stallsrc == source)
getThroughOut()->unstallDownstream(cause, this);
if (hasSink())
sink->unstallDownstream(cause, this);
if (hasSource() && source != stallsrc) {
source->unstallUpstream(cause, this);
// If the through output of this pipe is stalled, we cannot just unstall
// our upstream, we have to update it our through output staller
if (through_staller != nullptr)
source->stallUpstream(through_staller, this);
}
if (hasThroughIn() && getThroughIn() != stallsrc)
getThroughIn()->unstallUpstream(cause, this);
}
void MIDI_Pipe::unstallUpstream(MIDIStaller *cause, MIDI_Sink *stallsrc) {
if (stallsrc == sink) {
// This cannot be a different cause, because then our sink would
// already have caught it in unstallDownstream().
sink_staller = nullptr;
if (hasSource())
source->unstallUpstream(cause, this);
if (hasThroughIn())
getThroughIn()->unstallUpstream(cause, this);
} else {
if (cause == through_staller) {
through_staller = nullptr;
if (hasSource())
source->unstallUpstream(cause, this);
}
}
}
const char *MIDI_Pipe::getSinkStallerName() const {
return MIDIStaller::getNameNull(sink_staller);
}
const char *MIDI_Pipe::getThroughStallerName() const {
return MIDIStaller::getNameNull(through_staller);
}
MIDIStaller *MIDI_Pipe::getStaller() const {
return sink_staller ? sink_staller : through_staller;
}
const char *MIDI_Pipe::getStallerName() const {
return MIDIStaller::getNameNull(getStaller());
}
void MIDI_Pipe::handleStallers() const {
if (!isStalled())
return;
if (sink_staller == eternal_stall || through_staller == eternal_stall)
FATAL_ERROR(F("Unable to unstall pipe (eternal stall)"), 0x4827);
uint8_t iterations = 10;
while (isStalled() && iterations-- > 0) {
if (sink_staller)
sink_staller->handleStall();
if (through_staller)
through_staller->handleStall();
}
}
// :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: //
const char *MIDIStaller::getNameNull(MIDIStaller *s) {
if (s == nullptr)
return "(null)";
if (s == eternal_stall)
return "(eternal stall)";
return s->getName();
}
END_CS_NAMESPACE
#endif