737 lines
27 KiB
C++
737 lines
27 KiB
C++
#pragma once
|
||
|
||
#include <Settings/SettingsWrapper.hpp>
|
||
#if !DISABLE_PIPES
|
||
|
||
#include <AH/Containers/BitArray.hpp>
|
||
#include <AH/STL/cstdint>
|
||
#include <AH/STL/limits>
|
||
#include <AH/STL/utility>
|
||
#include <MIDI_Parsers/MIDI_MessageTypes.hpp>
|
||
#include <Settings/NamespaceSettings.hpp>
|
||
|
||
BEGIN_CS_NAMESPACE
|
||
|
||
struct MIDIStaller;
|
||
MIDIStaller *const eternal_stall =
|
||
reinterpret_cast<MIDIStaller *>(std::numeric_limits<std::uintptr_t>::max());
|
||
|
||
/**
|
||
* @addtogroup MIDI_Routing MIDI Routing
|
||
* @brief Operators and utilities for MIDI routing.
|
||
*
|
||
* Two or more MIDI interfaces can be connected
|
||
* using @ref MIDI_Pipe "MIDI Pipes". The simplest pipe just carries messages
|
||
* from the input interface to the output interface, but you can write rules for
|
||
* filtering out certain messages, changing the channel of some messages, etc.
|
||
*
|
||
* Allows you to use syntax like:
|
||
*
|
||
* ~~~cpp
|
||
* HardwareSerialMIDI_Interface
|
||
* midiA = Serial1, midiB = Serial2, midiC = Serial3;
|
||
* MIDI_PipeFactory<3> pipes; // Factory that can produce 3 pipes
|
||
*
|
||
* midiA >> pipes >> midiB;
|
||
* midiC >> pipes >> midiB;
|
||
* midiC << pipes << midiB;
|
||
* ~~~
|
||
*
|
||
* Or for bidirectional connections:
|
||
*
|
||
* ~~~cpp
|
||
* HardwareSerialMIDI_Interface
|
||
* midiA = Serial1, midiB = Serial2, midiC = Serial3;
|
||
* BidirectionalMIDI_PipeFactory<2> pipes; // Factory that can produce 2 pipes
|
||
*
|
||
* midiA | pipes | midiB;
|
||
* midiA | pipes | midiC;
|
||
* ~~~
|
||
*
|
||
* Have a look at the following examples on MIDI routing:
|
||
*
|
||
* - @ref MIDI_Pipes-Routing.ino
|
||
* - @ref Dual-MIDI-Interface.ino
|
||
*
|
||
* If you're interested how the pipes work, see the documentation for
|
||
* @ref MIDI_Pipe.
|
||
*
|
||
* @see @ref midi_md-routing (MIDI tutorial)
|
||
*
|
||
* @{
|
||
*/
|
||
|
||
class MIDI_Pipe;
|
||
class MIDI_Source;
|
||
class MIDI_Sink;
|
||
/// A MIDI_Sink that is not a MIDI_Pipe.
|
||
using TrueMIDI_Sink = MIDI_Sink;
|
||
/// A MIDI_Source that is not a MIDI_Pipe.
|
||
using TrueMIDI_Source = MIDI_Source;
|
||
|
||
/// Receives MIDI messages from a MIDI pipe.
|
||
/// @see @ref MIDI_Routing
|
||
/// @see @ref midi_md-routing (MIDI tutorial)
|
||
class MIDI_Sink {
|
||
public:
|
||
/// Default constructor.
|
||
MIDI_Sink() = default;
|
||
|
||
/// Copy constructor (copying not allowed).
|
||
MIDI_Sink(const MIDI_Sink &) = delete;
|
||
/// Copy assignment (copying not allowed).
|
||
MIDI_Sink &operator=(const MIDI_Sink &) = delete;
|
||
|
||
/// Move constructor.
|
||
MIDI_Sink(MIDI_Sink &&other);
|
||
/// Move assignment.
|
||
MIDI_Sink &operator=(MIDI_Sink &&other);
|
||
|
||
/// Destructor.
|
||
virtual ~MIDI_Sink();
|
||
|
||
/// @name Sending data over a MIDI Pipe
|
||
/// @{
|
||
|
||
/// Accept an incoming MIDI Channel message.
|
||
virtual void sinkMIDIfromPipe(ChannelMessage) = 0;
|
||
/// Accept an incoming MIDI System Exclusive message.
|
||
virtual void sinkMIDIfromPipe(SysExMessage) = 0;
|
||
/// Accept an incoming MIDI System Common message.
|
||
virtual void sinkMIDIfromPipe(SysCommonMessage) = 0;
|
||
/// Accept an incoming MIDI Real-Time message.
|
||
virtual void sinkMIDIfromPipe(RealTimeMessage) = 0;
|
||
|
||
/// @}
|
||
|
||
/// @name Connecting and disconnecting MIDI Pipes
|
||
/// @{
|
||
|
||
/// Fully connect a source pipe to this sink.
|
||
void connectSourcePipe(MIDI_Pipe *source);
|
||
/// Disconnect all source pipes that sink to this sink (recursively).
|
||
void disconnectSourcePipes();
|
||
/// Disconnect the given source from this sink. Leaves other sources
|
||
/// connected.
|
||
/// Returns true if the source was found and disconnected, false if the
|
||
/// given source was not a direct or indirect source to this sink.
|
||
bool disconnect(TrueMIDI_Source &source);
|
||
bool disconnect(MIDI_Pipe &) = delete;
|
||
/// Check if this sink is connected to a source pipe.
|
||
bool hasSourcePipe() const { return sourcePipe != nullptr; }
|
||
/// Get a pointer to the pipe this sink is connected to, or `nullptr` if
|
||
/// not connected.
|
||
MIDI_Pipe *getSourcePipe() const { return sourcePipe; }
|
||
|
||
/// @}
|
||
|
||
private:
|
||
/// Base case for recursive stall function.
|
||
/// @see MIDI_Pipe::stallDownstream
|
||
virtual void stallDownstream(MIDIStaller *, MIDI_Source *) {}
|
||
/// Base case for recursive un-stall function.
|
||
/// @see MIDI_Pipe::unstallDownstream
|
||
virtual void unstallDownstream(MIDIStaller *, MIDI_Source *) {}
|
||
/// Base case for recursive function.
|
||
/// @see MIDI_Pipe::getFinalSink
|
||
virtual MIDI_Sink *getFinalSink() { return this; }
|
||
/// Disconnect only the first pipe connected to this sink. Leaves the
|
||
/// other pipes connected to the original pipe, which doesn't have a sink
|
||
/// anymore when this function finishes.
|
||
/// Used to disconnect a MIDI_Pipe while preserving the connections of its
|
||
/// “through” inputs.
|
||
void disconnectSourcePipesShallow();
|
||
|
||
protected:
|
||
MIDI_Pipe *sourcePipe = nullptr;
|
||
|
||
friend class MIDI_Pipe;
|
||
|
||
public:
|
||
static void swap(MIDI_Sink &a, MIDI_Sink &b);
|
||
friend void swap(MIDI_Sink &a, MIDI_Sink &b) { swap(a, b); }
|
||
};
|
||
|
||
// :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: //
|
||
|
||
/// Class that can send MIDI messages to a MIDI pipe.
|
||
/// @see @ref MIDI_Routing
|
||
/// @see @ref midi_md-routing (MIDI tutorial)
|
||
class MIDI_Source {
|
||
public:
|
||
/// Default constructor.
|
||
MIDI_Source() = default;
|
||
|
||
/// Copy constructor (copying not allowed).
|
||
MIDI_Source(const MIDI_Source &) = delete;
|
||
/// Copy assignment (copying not allowed).
|
||
MIDI_Source &operator=(const MIDI_Source &) = delete;
|
||
|
||
/// Move constructor.
|
||
MIDI_Source(MIDI_Source &&other);
|
||
/// Move assignment.
|
||
MIDI_Source &operator=(MIDI_Source &&other);
|
||
|
||
/// Destructor.
|
||
virtual ~MIDI_Source();
|
||
|
||
/// @name Sending data over a MIDI Pipe
|
||
/// @{
|
||
|
||
/// Send a MIDI Channel Message down the pipe.
|
||
void sourceMIDItoPipe(ChannelMessage);
|
||
/// Send a MIDI System Exclusive message down the pipe.
|
||
void sourceMIDItoPipe(SysExMessage);
|
||
/// Send a MIDI System Common message down the pipe.
|
||
void sourceMIDItoPipe(SysCommonMessage);
|
||
/// Send a MIDI Real-Time message down the pipe.
|
||
void sourceMIDItoPipe(RealTimeMessage);
|
||
|
||
/// @}
|
||
|
||
/// @name Stalling the sink pipes and exclusive access
|
||
/// @{
|
||
|
||
/// Stall this MIDI source.
|
||
/// This means that this becomes the only source that can sink to the sinks
|
||
/// connected to this source. Other sources have to wait until this source
|
||
/// un-stalls the pipe before they can send again.
|
||
/// @param cause
|
||
/// Pointer to the reason for this stall, can be called back to
|
||
/// un-stall the pipes.
|
||
void stall(MIDIStaller *cause = eternal_stall);
|
||
/// Un-stall the pipes connected to this source, so other sources
|
||
/// are allowed to send again.
|
||
/// @param cause
|
||
/// Pointer to the reason for the stall (this has to be the same one
|
||
/// that was used to stall).
|
||
void unstall(MIDIStaller *cause = eternal_stall);
|
||
/// Check if this source can write to the sinks it connects to.
|
||
bool isStalled() const;
|
||
/// Get a pointer to whatever is causing this MIDI source to be stalled.
|
||
/// There could be multiple stallers, this function just returns one.
|
||
MIDIStaller *getStaller() const;
|
||
/// Get the name of whatever is causing this MIDI source to be stalled.
|
||
/// There could be multiple stallers, this function just returns one.
|
||
const char *getStallerName() const;
|
||
/// Give the code that is stalling the MIDI sink pipes the opportunity to do
|
||
/// its job and un-stall the pipes.
|
||
void handleStallers() const;
|
||
|
||
/// @}
|
||
|
||
/// @name Connecting and disconnecting MIDI Pipes
|
||
/// @{
|
||
|
||
/// Fully connect a sink pipe to this source.
|
||
void connectSinkPipe(MIDI_Pipe *sink);
|
||
/// Disconnect all sink pipes that this source sinks to (recursively).
|
||
void disconnectSinkPipes();
|
||
/// Disconnect the given sink from this source. Leaves other sinks
|
||
/// connected.
|
||
/// Returns true if the sink was found and disconnected, false if the
|
||
/// given sink was not a direct or indirect sink of this source.
|
||
bool disconnect(TrueMIDI_Sink &sink);
|
||
bool disconnect(MIDI_Pipe &) = delete;
|
||
/// Check if this source is connected to a sink pipe.
|
||
bool hasSinkPipe() const { return sinkPipe != nullptr; }
|
||
/// Get a pointer to the pipe this source is connected to, or `nullptr` if
|
||
/// not connected.
|
||
MIDI_Pipe *getSinkPipe() { return sinkPipe; }
|
||
|
||
/// @}
|
||
|
||
private:
|
||
/// Base case for recursive stall function.
|
||
/// @see MIDI_Pipe::stallUpstream
|
||
virtual void stallUpstream(MIDIStaller *, MIDI_Sink *) {}
|
||
/// Base case for recursive un-stall function.
|
||
/// @see MIDI_Pipe::unstallUpstream
|
||
virtual void unstallUpstream(MIDIStaller *, MIDI_Sink *) {}
|
||
/// Base case for recursive function.
|
||
/// @see MIDI_Pipe::getInitialSource
|
||
virtual MIDI_Source *getInitialSource() { return this; }
|
||
/// Disconnect only the first pipe connected to this source. Leaves the
|
||
/// other pipes connected to the original pipe, which doesn't have a source
|
||
/// anymore when this function finishes.
|
||
/// Used to disconnect a @ref MIDI_Pipe while preserving the connections of
|
||
/// its “through” outputs.
|
||
void disconnectSinkPipesShallow();
|
||
|
||
protected:
|
||
MIDI_Pipe *sinkPipe = nullptr;
|
||
|
||
friend class MIDI_Pipe;
|
||
|
||
public:
|
||
static void swap(MIDI_Source &a, MIDI_Source &b);
|
||
friend void swap(MIDI_Source &a, MIDI_Source &b) { swap(a, b); }
|
||
};
|
||
|
||
// :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: //
|
||
|
||
/**
|
||
* @brief Class that routes MIDI messages from a MIDI_Source to a MIDI_Sink.
|
||
*
|
||
* A pipe has at least two, at most four connections. In the simplest case, a
|
||
* pipe just has a source and a sink. The source sends MIDI messages down the
|
||
* pipe, and the pipe sends them to the sink. A mapping or filter can be applied
|
||
* to the messages traveling down the pipe.
|
||
*
|
||
* To be able to connect multiple pipes to a single sink or source, a pipe also
|
||
* has a “through” output and a “through” input. Both are pipes, not sinks or
|
||
* sources. All data that comes from the source is sent to the “through” output
|
||
* as well, and all input that comes in from the “through” input is sent to the
|
||
* sink as well. The mapping or filter is not applied to the data going from/to
|
||
* the “through” connections.
|
||
*
|
||
* Merging data from multiple sources into a single sink can cause problems
|
||
* because messages can be interleaved (e.g. RPN/NRPN or chunked system
|
||
* exclusive messages). To circumvent this issue, a source can request exclusive
|
||
* access to the pipe it's connected to. This stalls all other pipes that sink
|
||
* into the same sinks as the exclusive source.
|
||
* When other sources try to send to a stalled pipe, this will automatically
|
||
* call back the source that originally stalled the pipes, so it can finish its
|
||
* message, and then un-stall the pipes so the other pipe can send its data.
|
||
*
|
||
* **Pipe model**
|
||
*
|
||
* ~~~
|
||
* ╭────────────────> through out
|
||
* │ ┌────────┐
|
||
* source >━━━━┷━┥ filter ┝━┯━━━> sink
|
||
* └────────┘ │
|
||
* through in >─────────────────╯
|
||
* ~~~
|
||
*
|
||
* For example, if you have one source that should connect to two sinks, the
|
||
* configuration is as follows:
|
||
*
|
||
* ~~~
|
||
* through out × ┌────────┐
|
||
* ╭────────────────> >━━━━┷━┥ pipe 2 ┝━┯━━━> sink 2
|
||
* │ ┌────────┐ └────────┘ ×
|
||
* source >━━━━┷━┥ pipe 1 ┝━┯━━━━━━━━━━━━━━━━━━━━━━━━━━━> sink 1
|
||
* └────────┘ ×
|
||
* ~~~
|
||
*
|
||
* If you have two sources that should connect to the same sink, a possible
|
||
* configuration is:
|
||
*
|
||
* ~~~
|
||
* × ┌────────┐
|
||
* source 1 >━━━━━━━━━━━━━━━━━━━━━━━━━━━━┷━┥ pipe 1 ┝━┯━━━> sink
|
||
* × ┌────────┐ └────────┘ │
|
||
* source 2 >━━━━┷━┥ pipe 2 ┝━┯━━━> >─────────────────╯
|
||
* └────────┘ ×
|
||
* ~~~
|
||
*
|
||
* Each connection between a source and a sink has its own pipe, and no two
|
||
* pipes are connected in series (only through the “through“ inputs/outputs).
|
||
*
|
||
* @see @ref MIDI_Routing
|
||
* @see @ref midi_md-routing (MIDI tutorial)
|
||
*/
|
||
class MIDI_Pipe : private MIDI_Sink, private MIDI_Source {
|
||
public:
|
||
/// Default constructor
|
||
MIDI_Pipe() = default;
|
||
|
||
/// Copy constructor (copying not allowed).
|
||
MIDI_Pipe(const MIDI_Pipe &) = delete;
|
||
/// Copy assignment (copying not allowed).
|
||
MIDI_Pipe &operator=(const MIDI_Pipe &) = delete;
|
||
|
||
/// Move constructor.
|
||
/// @todo Add move constructor.
|
||
MIDI_Pipe(MIDI_Pipe &&) = delete;
|
||
/// Move assignment.
|
||
/// @todo Add move assignment.
|
||
MIDI_Pipe &operator=(MIDI_Pipe &&) = delete;
|
||
|
||
/// Destructor.
|
||
virtual ~MIDI_Pipe();
|
||
|
||
private:
|
||
/// @name Mapping and filtering
|
||
/// @{
|
||
|
||
/// Function that maps, edits or filters MIDI messages, and then forwards
|
||
/// them to the sink of the pipe.
|
||
/// The MIDI_Pipe base class just forwards the messages to the sink, but
|
||
/// you can override this method to create pipes that filter out some
|
||
/// messages, transposes notes, changes the channel, etc.
|
||
virtual void mapForwardMIDI(ChannelMessage msg) { sourceMIDItoSink(msg); }
|
||
/// @copydoc mapForwardMIDI
|
||
virtual void mapForwardMIDI(SysExMessage msg) { sourceMIDItoSink(msg); }
|
||
/// @copydoc mapForwardMIDI
|
||
virtual void mapForwardMIDI(SysCommonMessage msg) { sourceMIDItoSink(msg); }
|
||
/// @copydoc mapForwardMIDI
|
||
virtual void mapForwardMIDI(RealTimeMessage msg) { sourceMIDItoSink(msg); }
|
||
|
||
/// @}
|
||
|
||
public:
|
||
/// @name Dealing with stalled pipes
|
||
/// @{
|
||
|
||
/// Check if this pipe is stalled.
|
||
bool isStalled() const { return sink_staller || through_staller; }
|
||
/// Get the staller (cause of the stall) that causes the sink of this pipe
|
||
/// to be stalled.
|
||
/// This pipe could sink to more than one stalled sink, this function just
|
||
/// returns one of the causes.
|
||
MIDIStaller *getSinkStaller() const { return sink_staller; }
|
||
/// Get the name of the staller (cause of the stall) that causes the sink
|
||
/// of this pipe to be stalled.
|
||
const char *getSinkStallerName() const;
|
||
/// Get the staller (cause of the stall) that causes the “through” output
|
||
/// of this pipe to be stalled.
|
||
/// The “through” output of this pipe could sink to more than one stalled
|
||
/// sink, this function just returns one of the causes.
|
||
MIDIStaller *getThroughStaller() const { return through_staller; }
|
||
/// Get the name of the staller (cause of the stall) that causes the
|
||
/// “through” output of this pipe to be stalled.
|
||
const char *getThroughStallerName() const;
|
||
/// Get any staller: returns @ref getSinkStaller() if it's not null,
|
||
/// @ref getThroughStaller() otherwise.
|
||
MIDIStaller *getStaller() const;
|
||
/// Get the name of any staller.
|
||
/// @see getStaller
|
||
const char *getStallerName() const;
|
||
/// Returns true if this pipe is either not stalled at all, or if the pipe
|
||
/// is stalled by the given staller (cause).
|
||
/// @see getSinkStaller
|
||
bool sinkIsUnstalledOrStalledBy(MIDIStaller *cause) {
|
||
return sink_staller == nullptr || sink_staller == cause;
|
||
}
|
||
/// Returns true if this pipe is either not stalled at all, or if the pipe
|
||
/// is stalled by the given staller (cause).
|
||
/// @see getThroughStaller
|
||
bool throughIsUnstalledOrStalledBy(MIDIStaller *cause) {
|
||
return through_staller == nullptr || through_staller == cause;
|
||
}
|
||
|
||
/// Give the code that is stalling the MIDI pipe the opportunity to do
|
||
/// its job and unstall the pipe.
|
||
void handleStallers() const;
|
||
|
||
/// @}
|
||
|
||
public:
|
||
/// @name Check connections
|
||
/// @{
|
||
|
||
/// Check if this pipe is connected to a sink.
|
||
bool hasSink() const { return sink != nullptr; }
|
||
/// Check if this pipe is connected to a source.
|
||
bool hasSource() const { return source != nullptr; }
|
||
/// Check if this pipe has a “through” output that sends all incoming
|
||
/// messages from the input (source) to another pipe.
|
||
bool hasThroughOut() const { return getThroughOut() != nullptr; }
|
||
/// Check if this pipe has a “through” input that merges all messages from
|
||
/// another pipe into the output (sink).
|
||
bool hasThroughIn() const { return getThroughIn() != nullptr; }
|
||
|
||
/// @}
|
||
|
||
public:
|
||
/// @name MIDI Pipe connection management and inspection
|
||
/// @{
|
||
|
||
/// Disconnect this pipe from all other pipes, sources and sinks. If the
|
||
/// “through” input and/or output were in use, they are reconnected to their
|
||
/// original sink and/or source respectively, their behavior doesn't change.
|
||
void disconnect();
|
||
/// Disconnect the given sink from this pipe. The sink can be connected
|
||
/// directly, or via the “through” output.
|
||
/// Returns true if the sink was found and disconnected, false if the given
|
||
/// sink was not a direct or indirect sink of this pipe.
|
||
bool disconnect(TrueMIDI_Sink &sink) {
|
||
if (getFinalSink() == &sink) {
|
||
disconnect();
|
||
return true;
|
||
}
|
||
if (hasThroughOut()) {
|
||
return getThroughOut()->disconnect(sink);
|
||
}
|
||
return false;
|
||
}
|
||
/// Disconnect the given source from this pipe. The source can be connected
|
||
/// directly, or via the “through” input.
|
||
/// Returns true if the source was found and disconnected, false if the
|
||
/// given source was not a direct or indirect source to this pipe.
|
||
bool disconnect(TrueMIDI_Source &source) {
|
||
if (getInitialSource() == &source) {
|
||
disconnect();
|
||
return true;
|
||
}
|
||
if (hasThroughIn()) {
|
||
return getThroughIn()->disconnect(source);
|
||
}
|
||
return false;
|
||
}
|
||
bool disconnect(MIDI_Pipe &) = delete;
|
||
|
||
/// Get the immediate source of this pipe.
|
||
MIDI_Source *getSource() const { return source; }
|
||
/// Get the immediate sink of this pipe.
|
||
MIDI_Sink *getSink() const { return sink; }
|
||
/// Get the pipe connected to the “through” output of this pipe.
|
||
MIDI_Pipe *getThroughOut() const { return MIDI_Source::sinkPipe; }
|
||
/// Get the pipe connected to the “through” input of this pipe.
|
||
MIDI_Pipe *getThroughIn() const { return MIDI_Sink::sourcePipe; }
|
||
|
||
/// Get the sink this pipe eventually sinks to, following the chain
|
||
/// recursively.
|
||
MIDI_Sink *getFinalSink() override {
|
||
return hasSink() ? sink->getFinalSink() : nullptr;
|
||
}
|
||
/// Get the original source that sources to this pipe, following the chain
|
||
/// recursively.
|
||
MIDI_Source *getInitialSource() override {
|
||
return hasSource() ? source->getInitialSource() : nullptr;
|
||
}
|
||
|
||
/// @}
|
||
|
||
private:
|
||
/// @name Private functions to connect and disconnect sinks and sources
|
||
/// @{
|
||
|
||
/// Set the sink pointer to point to the given sink. Does not connect this
|
||
/// pipe to the sink. Initiate the connection from the sink.
|
||
void connectSink(MIDI_Sink *sink);
|
||
/// Set the sink pointer to null. Does not disconnect this pipe from the
|
||
/// sink. Initiate the disconnection from the sink.
|
||
void disconnectSink();
|
||
/// Set the source pointer to point to the given source. Does not connect
|
||
/// this pipe to the source. Initiate the connection from the source.
|
||
void connectSource(MIDI_Source *source);
|
||
/// Set the source pointer to null. Does not disconnect this pipe from the
|
||
/// source. Initiate the disconnection from the source.
|
||
void disconnectSource();
|
||
|
||
/// @}
|
||
|
||
protected:
|
||
/// Send the given MIDI message to the sink of this pipe.
|
||
/// Useful when overriding @ref mapForwardMIDI.
|
||
template <class Message>
|
||
void sourceMIDItoSink(Message msg) {
|
||
if (hasSink())
|
||
sink->sinkMIDIfromPipe(msg);
|
||
}
|
||
|
||
protected:
|
||
/// Accept a MIDI message from the source, forward it to the “through”
|
||
/// output if necessary, map or filter the MIDI message if necessary,
|
||
/// and send it to the sink. This function transfers messages from a
|
||
/// @ref MIDI_Source to its @ref MIDI_Pipe.
|
||
template <class Message>
|
||
void acceptMIDIfromSource(Message msg) {
|
||
if (hasThroughOut())
|
||
getThroughOut()->acceptMIDIfromSource(msg);
|
||
mapForwardMIDI(msg);
|
||
}
|
||
|
||
private:
|
||
/// Called when data arrives from an upstream pipe connected to our
|
||
/// “through” input, this function forwards it to the sink.
|
||
void sinkMIDIfromPipe(ChannelMessage msg) override {
|
||
sourceMIDItoSink(msg);
|
||
}
|
||
/// @copydoc sinkMIDIfromPipe
|
||
void sinkMIDIfromPipe(SysExMessage msg) override { sourceMIDItoSink(msg); }
|
||
/// @copydoc sinkMIDIfromPipe
|
||
void sinkMIDIfromPipe(SysCommonMessage msg) override {
|
||
sourceMIDItoSink(msg);
|
||
}
|
||
/// @copydoc sinkMIDIfromPipe
|
||
void sinkMIDIfromPipe(RealTimeMessage msg) override {
|
||
sourceMIDItoSink(msg);
|
||
}
|
||
|
||
private:
|
||
/// @name Private functions to stall and un-stall pipes
|
||
/// @{
|
||
|
||
/// Stall this pipe and all other pipes further downstream (following the
|
||
/// path of the sink and the “through” output). Operates recursively until
|
||
/// the end of the chain is reached, and then continues upstream (using
|
||
/// @ref stallUpstream) to stall all pipes that connect to sources that
|
||
/// sink to the same sink as this pipe and its “through” output.
|
||
/// In short: stall all pipes that sink to the same sink as this pipe, and
|
||
/// then stall all pipes that source to this first set of pipes.
|
||
void stallDownstream(MIDIStaller *cause, MIDI_Source *stallsrc) override;
|
||
/// Undoes the stalling by @ref stallDownstream.
|
||
void unstallDownstream(MIDIStaller *cause, MIDI_Source *stallsrc) override;
|
||
|
||
/// Stall this pipe and all other pipes further upstream (following the
|
||
/// path of the "trough" input). Operates recursively until the end of the
|
||
/// chain is reached. This function is called in the second stage of
|
||
/// @ref stallDownstream.
|
||
void stallUpstream(MIDIStaller *cause, MIDI_Sink *stallsrc) override;
|
||
/// Undoes the stalling by @ref stallUpstream.
|
||
void unstallUpstream(MIDIStaller *cause, MIDI_Sink *stallsrc) override;
|
||
|
||
/// @}
|
||
|
||
private:
|
||
MIDI_Sink *sink = nullptr;
|
||
MIDI_Source *source = nullptr;
|
||
MIDIStaller *sink_staller = nullptr;
|
||
MIDIStaller *through_staller = nullptr;
|
||
|
||
friend class MIDI_Sink;
|
||
friend class MIDI_Source;
|
||
};
|
||
|
||
// :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: //
|
||
|
||
/// A struct that is both a TrueMIDI_Sink and a TrueMIDI_Source.
|
||
/// @see @ref MIDI_Routing
|
||
/// @see @ref midi_md-routing (MIDI tutorial)
|
||
struct TrueMIDI_SinkSource : TrueMIDI_Sink, TrueMIDI_Source {};
|
||
|
||
/// A bidirectional pipe consists of two unidirectional pipes.
|
||
/// @see @ref MIDI_Routing
|
||
/// @see @ref midi_md-routing (MIDI tutorial)
|
||
using BidirectionalMIDI_Pipe = std::pair<MIDI_Pipe, MIDI_Pipe>;
|
||
|
||
/// Connect a source to a pipe (`source >> pipe`).
|
||
inline MIDI_Pipe &operator>>(TrueMIDI_Source &source, MIDI_Pipe &pipe) {
|
||
source.connectSinkPipe(&pipe);
|
||
return pipe;
|
||
}
|
||
|
||
/// Connect a pipe to a sink (`pipe >> sink`).
|
||
inline TrueMIDI_Sink &operator>>(MIDI_Pipe &pipe, TrueMIDI_Sink &sink) {
|
||
sink.connectSourcePipe(&pipe);
|
||
return sink;
|
||
}
|
||
|
||
/// Connect a sink to a pipe (`sink << pipe`).
|
||
inline MIDI_Pipe &operator<<(TrueMIDI_Sink &sink, MIDI_Pipe &pipe) {
|
||
sink.connectSourcePipe(&pipe);
|
||
return pipe;
|
||
}
|
||
|
||
/// Connect a pipe to a source (`pipe << source`).
|
||
inline TrueMIDI_Source &operator<<(MIDI_Pipe &pipe, TrueMIDI_Source &source) {
|
||
source.connectSinkPipe(&pipe);
|
||
return source;
|
||
}
|
||
|
||
/// Don't connect two pipes to eachother.
|
||
MIDI_Pipe &operator<<(MIDI_Pipe &, MIDI_Pipe &) = delete;
|
||
|
||
/// Connect a pipe to a sink+source (`pipe | source+sink`).
|
||
inline TrueMIDI_SinkSource &operator|(BidirectionalMIDI_Pipe &pipe,
|
||
TrueMIDI_SinkSource &sinksource) {
|
||
sinksource.connectSinkPipe(&pipe.first);
|
||
sinksource.connectSourcePipe(&pipe.second);
|
||
return sinksource;
|
||
}
|
||
|
||
/// Connect a sink+source to a pipe (`source+sink | pipe`).
|
||
inline BidirectionalMIDI_Pipe &operator|(TrueMIDI_SinkSource &sinksource,
|
||
BidirectionalMIDI_Pipe &pipe) {
|
||
sinksource.connectSinkPipe(&pipe.second);
|
||
sinksource.connectSourcePipe(&pipe.first);
|
||
return pipe;
|
||
}
|
||
|
||
// :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: //
|
||
|
||
/**
|
||
* @brief Class that produces multiple MIDI_Pipe%s.
|
||
*
|
||
* @tparam N
|
||
* The maximum number of pipes it can produce.
|
||
* @tparam Pipe
|
||
* The type of pipes to produce.
|
||
*
|
||
* @see @ref MIDI_Routing
|
||
* @see @ref midi_md-routing (MIDI tutorial)
|
||
*/
|
||
template <size_t N, class Pipe = MIDI_Pipe>
|
||
struct MIDI_PipeFactory {
|
||
Pipe pipes[N];
|
||
size_t index = 0;
|
||
|
||
Pipe &getNext() {
|
||
if (index >= N)
|
||
FATAL_ERROR(F("Not enough pipes available"), 0x2459);
|
||
return pipes[index++];
|
||
}
|
||
Pipe &operator[](size_t i) { return pipes[i]; }
|
||
const Pipe &operator[](size_t i) const { return pipes[i]; }
|
||
};
|
||
|
||
template <size_t N>
|
||
using BidirectionalMIDI_PipeFactory =
|
||
MIDI_PipeFactory<N, BidirectionalMIDI_Pipe>;
|
||
|
||
template <size_t N, class Pipe>
|
||
inline MIDI_Pipe &operator>>(TrueMIDI_Source &source,
|
||
MIDI_PipeFactory<N, Pipe> &pipe_fact) {
|
||
return source >> pipe_fact.getNext();
|
||
}
|
||
|
||
template <size_t N, class Pipe>
|
||
MIDI_Pipe &operator>>(MIDI_Pipe &, MIDI_PipeFactory<N, Pipe> &) = delete;
|
||
|
||
template <size_t N, class Pipe>
|
||
inline TrueMIDI_Sink &operator>>(MIDI_PipeFactory<N, Pipe> &pipe_fact,
|
||
TrueMIDI_Sink &sink) {
|
||
return pipe_fact.getNext() >> sink;
|
||
}
|
||
|
||
template <size_t N, class Pipe>
|
||
MIDI_Pipe &operator>>(MIDI_PipeFactory<N, Pipe> &, MIDI_Pipe &) = delete;
|
||
|
||
template <size_t N, class Pipe>
|
||
inline MIDI_Pipe &operator<<(TrueMIDI_Sink &sink,
|
||
MIDI_PipeFactory<N, Pipe> &pipe_fact) {
|
||
return sink << pipe_fact.getNext();
|
||
}
|
||
|
||
template <size_t N, class Pipe>
|
||
inline TrueMIDI_Source &operator<<(MIDI_PipeFactory<N, Pipe> &pipe_fact,
|
||
TrueMIDI_Source &source) {
|
||
return pipe_fact.getNext() << source;
|
||
}
|
||
|
||
template <size_t N>
|
||
inline TrueMIDI_SinkSource &
|
||
operator|(BidirectionalMIDI_PipeFactory<N> &pipe_fact,
|
||
TrueMIDI_SinkSource &sinksource) {
|
||
return pipe_fact.getNext() | sinksource;
|
||
}
|
||
|
||
template <size_t N>
|
||
inline BidirectionalMIDI_Pipe &
|
||
operator|(TrueMIDI_SinkSource &sinksource,
|
||
BidirectionalMIDI_PipeFactory<N> &pipe_fact) {
|
||
return sinksource | pipe_fact.getNext();
|
||
}
|
||
|
||
/// @}
|
||
|
||
END_CS_NAMESPACE
|
||
|
||
#else
|
||
|
||
BEGIN_CS_NAMESPACE
|
||
|
||
struct TrueMIDI_Source {
|
||
template <class... Args>
|
||
void sourceMIDItoPipe(Args &&...) {}
|
||
};
|
||
struct TrueMIDI_Sink {};
|
||
struct TrueMIDI_SinkSource : TrueMIDI_Source, TrueMIDI_Sink {};
|
||
|
||
END_CS_NAMESPACE
|
||
|
||
#endif |