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
|