Control Surface main
MIDI Control Surface library for Arduino
Loading...
Searching...
No Matches
MIDI_Interface.hpp
Go to the documentation of this file.
1#pragma once
2
3#include "MIDI_Pipes.hpp"
4#include "MIDI_Sender.hpp"
5#include "MIDI_Staller.hpp"
7#include <Def/Def.hpp>
8#include <Def/MIDIAddress.hpp>
10
12
13constexpr auto MIDI_BAUD = 31250;
14
15class MIDI_Callbacks;
16
21 public MIDI_Sender<MIDI_Interface>,
22 public AH::Updatable<MIDI_Interface>,
23 protected MIDIStaller {
24 protected:
25 MIDI_Interface() = default;
27
28 public:
30 virtual ~MIDI_Interface();
31
33 void begin() override {}
35 void update() override = 0;
36
39
41 void setAsDefault();
48 static MIDI_Interface *getDefault();
49
51
54
58 void setCallbacks(MIDI_Callbacks *cb) { this->callbacks = cb; }
63
65
66 protected:
67 friend class MIDI_Sender<MIDI_Interface>;
73 virtual void sendSysExImpl(SysExMessage) = 0;
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
91 void sinkMIDIfromPipe(ChannelMessage msg) override { send(msg); }
93 void sinkMIDIfromPipe(SysExMessage msg) override { send(msg); }
95 void sinkMIDIfromPipe(SysCommonMessage msg) override { send(msg); }
97 void sinkMIDIfromPipe(RealTimeMessage msg) override { send(msg); }
98#endif
99
100 protected:
102 void onChannelMessage(ChannelMessage message);
104 void onSysExMessage(SysExMessage message);
111
112 public:
114 template <class MIDIInterface_t>
115 static void updateIncoming(MIDIInterface_t *iface);
117 template <class MIDIInterface_t>
118 static void dispatchIncoming(MIDIInterface_t *iface, MIDIReadEvent event);
122 template <class MIDIInterface_t>
123 static void handleStall(MIDIInterface_t *self);
124
125 private:
127
128 private:
130};
131
132template <class MIDIInterface_t>
133void 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 MIDIReadEvent event = self->read();
142 if (event == MIDIReadEvent::NO_MESSAGE)
143 return;
144 if (self->getStaller() == self)
145 self->unstall(self);
146 int16_t size_rem = 512 * 3 / 4; // Don't keep on reading for too long
147 bool chunked = false; // Whether there's an unterminated SysEx chunk
148 while (event != MIDIReadEvent::NO_MESSAGE) {
149 dispatchIncoming(self, event);
150 if (event == MIDIReadEvent::SYSEX_CHUNK) {
151 size_rem -= self->getSysExMessage().length;
152 chunked = true;
153 } else if (event == MIDIReadEvent::SYSEX_MESSAGE) {
154 size_rem -= self->getSysExMessage().length;
155 chunked = false;
156 } else {
157 size_rem -= 3;
158 }
159 if (size_rem < 0)
160 break;
161 event = self->read();
162 }
163 if (chunked)
164 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
171template <class MIDIInterface_t>
172void MIDI_Interface::dispatchIncoming(MIDIInterface_t *self,
173 MIDIReadEvent event) {
174 switch (event) {
176 self->onChannelMessage(self->getChannelMessage());
177 break;
178 case MIDIReadEvent::SYSEX_CHUNK: // fallthrough
180 self->onSysExMessage(self->getSysExMessage());
181 break;
183 self->onSysCommonMessage(self->getSysCommonMessage());
184 break;
186 self->onRealTimeMessage(self->getRealTimeMessage());
187 break;
188 case MIDIReadEvent::NO_MESSAGE: break; // LCOV_EXCL_LINE
189 default: break; // LCOV_EXCL_LINE
190 }
191}
192
193#if !DISABLE_PIPES
194template <class MIDIInterface_t>
195void MIDI_Interface::handleStall(MIDIInterface_t *self) {
196 const char *staller_name = self->getStallerName();
197 DEBUGFN(F("Handling stall. Cause: ") << staller_name);
198 self->unstall(self);
199
200 unsigned long startTime = millis();
201 while (millis() - startTime < SYSEX_CHUNK_TIMEOUT) {
202 MIDIReadEvent event = self->read();
203 dispatchIncoming(self, event);
204 if (event == MIDIReadEvent::SYSEX_CHUNK)
205 startTime = millis(); // reset timeout
206 else if (event == MIDIReadEvent::SYSEX_MESSAGE)
207 return;
208 }
209 DEBUGFN(F("Warning: Unable to un-stall pipes. Cause: ") << staller_name);
210 static_cast<void>(staller_name);
211}
212#endif
213
MIDIReadEvent
Values returned by the MIDI reading functions.
@ CHANNEL_MESSAGE
A MIDI Channel message was received.
@ SYSEX_CHUNK
An incomplete System Exclusive message.
@ SYSCOMMON_MESSAGE
A MIDI System Common message was received.
@ NO_MESSAGE
No new messages were received.
@ SYSEX_MESSAGE
A MIDI System Exclusive message was received.
@ REALTIME_MESSAGE
A MIDI Real-Time message was received.
constexpr auto MIDI_BAUD
#define END_CS_NAMESPACE
#define BEGIN_CS_NAMESPACE
constexpr unsigned long SYSEX_CHUNK_TIMEOUT
Timeout in milliseconds to wait for a SysEx chunk to complete.
A super class for object that have to be updated regularly.
A class for callbacks from MIDI input.
An abstract class for MIDI interfaces.
void onSysCommonMessage(SysCommonMessage message)
Call the System Common message callback and send the message to the sink pipe.
virtual void sendSysCommonImpl(SysCommonMessage)=0
Low-level function for sending a MIDI system common message.
MIDI_Interface()=default
virtual void sendRealTimeImpl(RealTimeMessage)=0
Low-level function for sending a MIDI real-time message.
virtual void sendNowImpl()=0
Low-level function for sending any buffered outgoing MIDI messages.
void sinkMIDIfromPipe(SysCommonMessage msg) override
Accept an incoming MIDI System Common message from the source pipe.
virtual ~MIDI_Interface()
Destructor.
virtual void sendChannelMessageImpl(ChannelMessage)=0
Low-level function for sending a MIDI channel voice message.
virtual void sendSysExImpl(SysExMessage)=0
Low-level function for sending a system exclusive MIDI message.
void sinkMIDIfromPipe(RealTimeMessage msg) override
Accept an incoming MIDI Real-Time message from the source pipe.
void begin() override
Initialize the MIDI Interface.
static void dispatchIncoming(MIDIInterface_t *iface, MIDIReadEvent event)
Dispatch the given type of MIDI message from the given interface.
void setAsDefault()
Set this MIDI interface as the default interface.
void sinkMIDIfromPipe(ChannelMessage msg) override
Accept an incoming MIDI Channel message from the source pipe.
MIDI_Callbacks * callbacks
void onSysExMessage(SysExMessage message)
Call the SysEx message callback and send the message to the sink pipe.
void onRealTimeMessage(RealTimeMessage message)
Call the real-time message callback and send the message to the sink pipe.
static MIDI_Interface * DefaultMIDI_Interface
static void updateIncoming(MIDIInterface_t *iface)
Read, parse and dispatch incoming MIDI messages on the given interface.
void setCallbacks(MIDI_Callbacks *cb)
Set the callbacks that will be called when a MIDI message is received.
void onChannelMessage(ChannelMessage message)
Call the channel message callback and send the message to the sink pipe.
static MIDI_Interface * getDefault()
Return the default MIDI interface.
void sinkMIDIfromPipe(SysExMessage msg) override
Accept an incoming MIDI System Exclusive message from the source pipe.
void update() override=0
Read the MIDI interface and call the callback if a message was received.
MIDI_Interface(MIDI_Interface &&)=default
Statically polymorphic template for classes that send MIDI messages.
void send(ChannelMessage message)
Send a MIDI Channel Voice message.
#define DEBUGFN(x)
Print an expression and its function (function name and line number) to the debug output if debugging...
Definition Debug.hpp:115
Struct that can cause a MIDI_Pipe to be stalled.
virtual void handleStall()=0
Call back that should finish any MIDI messages that are in progress, and un-stall the pipe or MIDI so...
A struct that is both a TrueMIDI_Sink and a TrueMIDI_Source.