Line data Source code
1 : #pragma once 2 : 3 : #include "MIDI_Pipes.hpp" 4 : #include "MIDI_Sender.hpp" 5 : #include "MIDI_Staller.hpp" 6 : #include <AH/Containers/Updatable.hpp> 7 : #include <Def/Def.hpp> 8 : #include <Def/MIDIAddress.hpp> 9 : #include <MIDI_Parsers/MIDI_Parser.hpp> 10 : 11 : BEGIN_CS_NAMESPACE 12 : 13 : constexpr auto MIDI_BAUD = 31250; 14 : 15 : class MIDI_Callbacks; 16 : 17 : /** 18 : * @brief An abstract class for MIDI interfaces. 19 : */ 20 : class MIDI_Interface : public TrueMIDI_SinkSource, 21 : public MIDI_Sender<MIDI_Interface>, 22 : public AH::Updatable<MIDI_Interface>, 23 : protected MIDIStaller { 24 : protected: 25 133 : MIDI_Interface() = default; 26 : MIDI_Interface(MIDI_Interface &&) = default; 27 : 28 : public: 29 : /// Destructor. 30 : virtual ~MIDI_Interface(); 31 : 32 : /// Initialize the MIDI Interface. 33 3 : void begin() override {} 34 : /// Read the MIDI interface and call the callback if a message was received. 35 : void update() override = 0; 36 : 37 : /// @name Default MIDI Interfaces 38 : /// @{ 39 : 40 : /// Set this MIDI interface as the default interface. 41 : void setAsDefault(); 42 : /// Return the default MIDI interface. If the default MIDI interface was 43 : /// configured explicitly using @ref setAsDefault(), that interface is 44 : /// returned. If it wasn't set, or if that MIDI interface no longer exists, 45 : /// this function returns the newest MIDI interface, the one that was 46 : /// constructed most recently. If no MIDI interfaces exist, `nullptr` is 47 : /// returned. 48 : static MIDI_Interface *getDefault(); 49 : 50 : /// @} 51 : 52 : /// @name MIDI Input Callbacks 53 : /// @{ 54 : 55 : /// Set the callbacks that will be called when a MIDI message is received. 56 : /// @param cb 57 : /// A pointer to the callback object. 58 14 : void setCallbacks(MIDI_Callbacks *cb) { this->callbacks = cb; } 59 : /// Set the callbacks that will be called when a MIDI message is received. 60 : /// @param cb 61 : /// A reference to the callback object. 62 3 : void setCallbacks(MIDI_Callbacks &cb) { setCallbacks(&cb); } 63 : 64 : /// @} 65 : 66 : protected: 67 : friend class MIDI_Sender<MIDI_Interface>; 68 : /// Low-level function for sending a MIDI channel voice message. 69 : virtual void sendChannelMessageImpl(ChannelMessage) = 0; 70 : /// Low-level function for sending a MIDI system common message. 71 : virtual void sendSysCommonImpl(SysCommonMessage) = 0; 72 : /// Low-level function for sending a system exclusive MIDI message. 73 : virtual void sendSysExImpl(SysExMessage) = 0; 74 : /// Low-level function for sending a MIDI real-time message. 75 : virtual void sendRealTimeImpl(RealTimeMessage) = 0; 76 : /// Low-level function for sending any buffered outgoing MIDI messages. 77 : virtual void sendNowImpl() = 0; 78 : 79 : public: 80 : #if DISABLE_PIPES 81 : virtual MIDIReadEvent read() = 0; 82 : virtual ChannelMessage getChannelMessage() const = 0; 83 : virtual SysCommonMessage getSysCommonMessage() const = 0; 84 : virtual RealTimeMessage getRealTimeMessage() const = 0; 85 : virtual SysExMessage getSysExMessage() const = 0; 86 : #endif 87 : 88 : protected: 89 : #if !DISABLE_PIPES 90 : /// Accept an incoming MIDI Channel message from the source pipe. 91 131 : void sinkMIDIfromPipe(ChannelMessage msg) override { send(msg); } 92 : /// Accept an incoming MIDI System Exclusive message from the source pipe. 93 8 : void sinkMIDIfromPipe(SysExMessage msg) override { send(msg); } 94 : /// Accept an incoming MIDI System Common message from the source pipe. 95 0 : void sinkMIDIfromPipe(SysCommonMessage msg) override { send(msg); } 96 : /// Accept an incoming MIDI Real-Time message from the source pipe. 97 2 : void sinkMIDIfromPipe(RealTimeMessage msg) override { send(msg); } 98 : #endif 99 : 100 : protected: 101 : /// Call the channel message callback and send the message to the sink pipe. 102 : void onChannelMessage(ChannelMessage message); 103 : /// Call the SysEx message callback and send the message to the sink pipe. 104 : void onSysExMessage(SysExMessage message); 105 : /// Call the System Common message callback and send the message to the sink 106 : /// pipe. 107 : void onSysCommonMessage(SysCommonMessage message); 108 : /// Call the real-time message callback and send the message to the sink 109 : /// pipe. 110 : void onRealTimeMessage(RealTimeMessage message); 111 : 112 : public: 113 : /// Read, parse and dispatch incoming MIDI messages on the given interface. 114 : template <class MIDIInterface_t> 115 : static void updateIncoming(MIDIInterface_t *iface); 116 : /// Dispatch the given type of MIDI message from the given interface. 117 : template <class MIDIInterface_t> 118 : static void dispatchIncoming(MIDIInterface_t *iface, MIDIReadEvent event); 119 : /// Un-stall the given MIDI interface. Assumes the interface has been 120 : /// stalled because of a chunked SysEx messages. Waits untill that message 121 : /// is finished. 122 : template <class MIDIInterface_t> 123 : static void handleStall(MIDIInterface_t *iface); 124 : 125 : private: 126 : MIDI_Callbacks *callbacks = nullptr; 127 : 128 : private: 129 : static MIDI_Interface *DefaultMIDI_Interface; 130 : }; 131 : 132 : template <class MIDIInterface_t> 133 22 : void MIDI_Interface::updateIncoming(MIDIInterface_t *iface) { 134 : #if DISABLE_PIPES 135 : MIDIReadEvent event = iface->read(); 136 : while (event != MIDIReadEvent::NO_MESSAGE) { 137 : dispatchIncoming(iface, event); 138 : event = iface->read(); 139 : } 140 : #else 141 22 : if (iface->getStaller() == iface) 142 0 : iface->unstall(iface); 143 22 : bool chunked = false; 144 22 : MIDIReadEvent event = iface->read(); 145 52 : while (event != MIDIReadEvent::NO_MESSAGE) { 146 30 : dispatchIncoming(iface, event); 147 30 : if (event == MIDIReadEvent::SYSEX_CHUNK) 148 1 : chunked = true; 149 30 : if (event == MIDIReadEvent::SYSEX_MESSAGE) 150 8 : chunked = false; 151 30 : event = iface->read(); 152 : } 153 22 : if (chunked) 154 1 : iface->stall(iface); 155 : #endif 156 : // TODO: add logic to detect MIDI messages such as (N)RPN that span over 157 : // multiple channel voice messages and that shouldn't be interrupted. 158 : // For short messages such as (N)RPN, I suggest waiting with a timeout. 159 22 : } 160 : 161 : template <class MIDIInterface_t> 162 32 : void MIDI_Interface::dispatchIncoming(MIDIInterface_t *iface, 163 : MIDIReadEvent event) { 164 32 : switch (event) { 165 17 : case MIDIReadEvent::CHANNEL_MESSAGE: 166 17 : iface->onChannelMessage(iface->getChannelMessage()); 167 17 : break; 168 11 : case MIDIReadEvent::SYSEX_CHUNK: // fallthrough 169 : case MIDIReadEvent::SYSEX_MESSAGE: 170 11 : iface->onSysExMessage(iface->getSysExMessage()); 171 11 : break; 172 0 : case MIDIReadEvent::SYSCOMMON_MESSAGE: 173 0 : iface->onSysCommonMessage(iface->getSysCommonMessage()); 174 0 : break; 175 4 : case MIDIReadEvent::REALTIME_MESSAGE: 176 4 : iface->onRealTimeMessage(iface->getRealTimeMessage()); 177 4 : break; 178 : case MIDIReadEvent::NO_MESSAGE: break; // LCOV_EXCL_LINE 179 : default: break; // LCOV_EXCL_LINE 180 : } 181 32 : } 182 : 183 : #if !DISABLE_PIPES 184 : template <class MIDIInterface_t> 185 1 : void MIDI_Interface::handleStall(MIDIInterface_t *iface) { 186 1 : iface->unstall(iface); 187 : 188 1 : unsigned long startTime = millis(); 189 2 : while (millis() - startTime < SYSEX_CHUNK_TIMEOUT) { 190 2 : MIDIReadEvent event = iface->read(); 191 2 : dispatchIncoming(iface, event); 192 2 : if (event == MIDIReadEvent::SYSEX_CHUNK) 193 1 : startTime = millis(); // reset timeout 194 1 : else if (event == MIDIReadEvent::SYSEX_MESSAGE) 195 1 : return; 196 : } 197 : DEBUGREF(F("Warning: Unable to un-stall pipes: ") 198 : << iface->getStallerName()); 199 : } 200 : #endif 201 : 202 : END_CS_NAMESPACE