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