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 : protected: 80 : /// Accept an incoming MIDI Channel message from the source pipe. 81 131 : void sinkMIDIfromPipe(ChannelMessage msg) override { send(msg); } 82 : /// Accept an incoming MIDI System Exclusive message from the source pipe. 83 8 : void sinkMIDIfromPipe(SysExMessage msg) override { send(msg); } 84 : /// Accept an incoming MIDI System Common message from the source pipe. 85 0 : void sinkMIDIfromPipe(SysCommonMessage msg) override { send(msg); } 86 : /// Accept an incoming MIDI Real-Time message from the source pipe. 87 2 : void sinkMIDIfromPipe(RealTimeMessage msg) override { send(msg); } 88 : 89 : protected: 90 : /// Call the channel message callback and send the message to the sink pipe. 91 : void onChannelMessage(ChannelMessage message); 92 : /// Call the SysEx message callback and send the message to the sink pipe. 93 : void onSysExMessage(SysExMessage message); 94 : /// Call the System Common message callback and send the message to the sink 95 : /// pipe. 96 : void onSysCommonMessage(SysCommonMessage message); 97 : /// Call the real-time message callback and send the message to the sink 98 : /// pipe. 99 : void onRealTimeMessage(RealTimeMessage message); 100 : 101 : public: 102 : /// Read, parse and dispatch incoming MIDI messages on the given interface. 103 : template <class MIDIInterface_t> 104 : static void updateIncoming(MIDIInterface_t *iface); 105 : /// Dispatch the given type of MIDI message from the given interface. 106 : template <class MIDIInterface_t> 107 : static void dispatchIncoming(MIDIInterface_t *iface, MIDIReadEvent event); 108 : /// Un-stall the given MIDI interface. Assumes the interface has been 109 : /// stalled because of a chunked SysEx messages. Waits untill that message 110 : /// is finished. 111 : template <class MIDIInterface_t> 112 : static void handleStall(MIDIInterface_t *iface); 113 : 114 : private: 115 : MIDI_Callbacks *callbacks = nullptr; 116 : 117 : private: 118 : static MIDI_Interface *DefaultMIDI_Interface; 119 : }; 120 : 121 : template <class MIDIInterface_t> 122 22 : void MIDI_Interface::updateIncoming(MIDIInterface_t *iface) { 123 22 : if (iface->getStaller() == iface) 124 0 : iface->unstall(iface); 125 22 : bool chunked = false; 126 22 : MIDIReadEvent event = iface->read(); 127 52 : while (event != MIDIReadEvent::NO_MESSAGE) { 128 30 : dispatchIncoming(iface, event); 129 30 : if (event == MIDIReadEvent::SYSEX_CHUNK) 130 1 : chunked = true; 131 30 : if (event == MIDIReadEvent::SYSEX_MESSAGE) 132 8 : chunked = false; 133 30 : event = iface->read(); 134 : } 135 22 : if (chunked) 136 1 : iface->stall(iface); 137 : // TODO: add logic to detect MIDI messages such as (N)RPN that span over 138 : // multiple channel voice messages and that shouldn't be interrupted. 139 : // For short messages such as (N)RPN, I suggest waiting with a timeout. 140 22 : } 141 : 142 : template <class MIDIInterface_t> 143 32 : void MIDI_Interface::dispatchIncoming(MIDIInterface_t *iface, 144 : MIDIReadEvent event) { 145 32 : switch (event) { 146 17 : case MIDIReadEvent::CHANNEL_MESSAGE: 147 17 : iface->onChannelMessage(iface->getChannelMessage()); 148 17 : break; 149 11 : case MIDIReadEvent::SYSEX_CHUNK: // fallthrough 150 : case MIDIReadEvent::SYSEX_MESSAGE: 151 11 : iface->onSysExMessage(iface->getSysExMessage()); 152 11 : break; 153 0 : case MIDIReadEvent::SYSCOMMON_MESSAGE: 154 0 : iface->onSysCommonMessage(iface->getSysCommonMessage()); 155 0 : break; 156 4 : case MIDIReadEvent::REALTIME_MESSAGE: 157 4 : iface->onRealTimeMessage(iface->getRealTimeMessage()); 158 4 : break; 159 : case MIDIReadEvent::NO_MESSAGE: break; // LCOV_EXCL_LINE 160 : default: break; // LCOV_EXCL_LINE 161 : } 162 32 : } 163 : 164 : template <class MIDIInterface_t> 165 1 : void MIDI_Interface::handleStall(MIDIInterface_t *iface) { 166 1 : iface->unstall(iface); 167 : 168 1 : unsigned long startTime = millis(); 169 2 : while (millis() - startTime < SYSEX_CHUNK_TIMEOUT) { 170 2 : MIDIReadEvent event = iface->read(); 171 2 : dispatchIncoming(iface, event); 172 2 : if (event == MIDIReadEvent::SYSEX_CHUNK) 173 1 : startTime = millis(); // reset timeout 174 1 : else if (event == MIDIReadEvent::SYSEX_MESSAGE) 175 1 : return; 176 : } 177 : DEBUGREF(F("Warning: Unable to un-stall pipes: ") 178 : << iface->getStallerName()); 179 : } 180 : 181 : END_CS_NAMESPACE