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 135 : 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 15 : 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 132 : 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 : #if !DISABLE_PIPES 120 : /// Un-stall the given MIDI interface. Assumes the interface has been 121 : /// stalled because of a chunked SysEx messages. Waits until that message 122 : /// is finished. 123 : template <class MIDIInterface_t> 124 : static void handleStall(MIDIInterface_t *self); 125 : using MIDIStaller::handleStall; 126 : #endif 127 : 128 : private: 129 : MIDI_Callbacks *callbacks = nullptr; 130 : 131 : private: 132 : static MIDI_Interface *DefaultMIDI_Interface; 133 : }; 134 : 135 : template <class MIDIInterface_t> 136 23 : void MIDI_Interface::updateIncoming(MIDIInterface_t *self) { 137 : #if DISABLE_PIPES 138 : MIDIReadEvent event = self->read(); 139 : while (event != MIDIReadEvent::NO_MESSAGE) { 140 : dispatchIncoming(self, event); 141 : event = self->read(); 142 : } 143 : #else 144 23 : MIDIReadEvent event = self->read(); 145 23 : if (event == MIDIReadEvent::NO_MESSAGE) 146 2 : return; 147 21 : if (self->getStaller() == self) 148 0 : self->unstall(self); 149 21 : int16_t size_rem = 512 * 3 / 4; // Don't keep on reading for too long 150 21 : bool chunked = false; // Whether there's an unterminated SysEx chunk 151 59 : while (event != MIDIReadEvent::NO_MESSAGE) { 152 38 : dispatchIncoming(self, event); 153 38 : if (event == MIDIReadEvent::SYSEX_CHUNK) { 154 1 : size_rem -= self->getSysExMessage().length; 155 1 : chunked = true; 156 37 : } else if (event == MIDIReadEvent::SYSEX_MESSAGE) { 157 8 : size_rem -= self->getSysExMessage().length; 158 8 : chunked = false; 159 : } else { 160 29 : size_rem -= 3; 161 : } 162 38 : if (size_rem < 0) 163 0 : break; 164 38 : event = self->read(); 165 : } 166 21 : if (chunked) 167 1 : self->stall(self); 168 : #endif 169 : // TODO: add logic to detect MIDI messages such as (N)RPN that span over 170 : // multiple channel voice messages and that shouldn't be interrupted. 171 : // For short messages such as (N)RPN, I suggest waiting with a timeout. 172 : } 173 : 174 : template <class MIDIInterface_t> 175 40 : void MIDI_Interface::dispatchIncoming(MIDIInterface_t *self, 176 : MIDIReadEvent event) { 177 40 : switch (event) { 178 25 : case MIDIReadEvent::CHANNEL_MESSAGE: 179 25 : self->onChannelMessage(self->getChannelMessage()); 180 25 : break; 181 11 : case MIDIReadEvent::SYSEX_CHUNK: // fallthrough 182 : case MIDIReadEvent::SYSEX_MESSAGE: 183 11 : self->onSysExMessage(self->getSysExMessage()); 184 11 : break; 185 0 : case MIDIReadEvent::SYSCOMMON_MESSAGE: 186 0 : self->onSysCommonMessage(self->getSysCommonMessage()); 187 0 : break; 188 4 : case MIDIReadEvent::REALTIME_MESSAGE: 189 4 : self->onRealTimeMessage(self->getRealTimeMessage()); 190 4 : break; 191 : case MIDIReadEvent::NO_MESSAGE: break; // LCOV_EXCL_LINE 192 : default: break; // LCOV_EXCL_LINE 193 : } 194 40 : } 195 : 196 : #if !DISABLE_PIPES 197 : template <class MIDIInterface_t> 198 1 : void MIDI_Interface::handleStall(MIDIInterface_t *self) { 199 1 : const char *staller_name = self->getStallerName(); 200 : DEBUGFN(F("Handling stall. Cause: ") << staller_name); 201 1 : self->unstall(self); 202 : 203 1 : unsigned long startTime = millis(); 204 2 : while (millis() - startTime < SYSEX_CHUNK_TIMEOUT) { 205 2 : MIDIReadEvent event = self->read(); 206 2 : dispatchIncoming(self, event); 207 2 : if (event == MIDIReadEvent::SYSEX_CHUNK) 208 1 : startTime = millis(); // reset timeout 209 1 : else if (event == MIDIReadEvent::SYSEX_MESSAGE) 210 1 : return; 211 : } 212 : DEBUGFN(F("Warning: Unable to un-stall pipes. Cause: ") << staller_name); 213 : static_cast<void>(staller_name); 214 : } 215 : #endif 216 : 217 : END_CS_NAMESPACE