#pragma once #include #if !DISABLE_PIPES #include #include #include #include #include #include BEGIN_CS_NAMESPACE struct MIDIStaller; MIDIStaller *const eternal_stall = reinterpret_cast(std::numeric_limits::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 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 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; /// 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 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 using BidirectionalMIDI_PipeFactory = MIDI_PipeFactory; template inline MIDI_Pipe &operator>>(TrueMIDI_Source &source, MIDI_PipeFactory &pipe_fact) { return source >> pipe_fact.getNext(); } template MIDI_Pipe &operator>>(MIDI_Pipe &, MIDI_PipeFactory &) = delete; template inline TrueMIDI_Sink &operator>>(MIDI_PipeFactory &pipe_fact, TrueMIDI_Sink &sink) { return pipe_fact.getNext() >> sink; } template MIDI_Pipe &operator>>(MIDI_PipeFactory &, MIDI_Pipe &) = delete; template inline MIDI_Pipe &operator<<(TrueMIDI_Sink &sink, MIDI_PipeFactory &pipe_fact) { return sink << pipe_fact.getNext(); } template inline TrueMIDI_Source &operator<<(MIDI_PipeFactory &pipe_fact, TrueMIDI_Source &source) { return pipe_fact.getNext() << source; } template inline TrueMIDI_SinkSource & operator|(BidirectionalMIDI_PipeFactory &pipe_fact, TrueMIDI_SinkSource &sinksource) { return pipe_fact.getNext() | sinksource; } template inline BidirectionalMIDI_Pipe & operator|(TrueMIDI_SinkSource &sinksource, BidirectionalMIDI_PipeFactory &pipe_fact) { return sinksource | pipe_fact.getNext(); } /// @} END_CS_NAMESPACE #else BEGIN_CS_NAMESPACE struct TrueMIDI_Source { template void sourceMIDItoPipe(Args &&...) {} }; struct TrueMIDI_Sink {}; struct TrueMIDI_SinkSource : TrueMIDI_Source, TrueMIDI_Sink {}; END_CS_NAMESPACE #endif