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