Control Surface new-input
MIDI Control Surface library for Arduino
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 
13 constexpr auto MIDI_BAUD = 31250;
14 
15 class 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;
75  virtual void sendRealTimeImpl(RealTimeMessage) = 0;
77  virtual void sendNowImpl() = 0;
78 
79  protected:
81  void sinkMIDIfromPipe(ChannelMessage msg) override { send(msg); }
83  void sinkMIDIfromPipe(SysExMessage msg) override { send(msg); }
85  void sinkMIDIfromPipe(SysCommonMessage msg) override { send(msg); }
87  void sinkMIDIfromPipe(RealTimeMessage msg) override { send(msg); }
88 
89  protected:
91  void onChannelMessage(ChannelMessage message);
93  void onSysExMessage(SysExMessage message);
99  void onRealTimeMessage(RealTimeMessage message);
100 
101  protected:
103  template <class MIDIInterface_t>
104  static void updateIncoming(MIDIInterface_t *iface);
106  template <class MIDIInterface_t>
107  static void dispatchIncoming(MIDIInterface_t *iface, MIDIReadEvent event);
111  template <class MIDIInterface_t>
112  static void handleStall(MIDIInterface_t *iface);
113 
114  private:
116 
117  private:
119 };
120 
121 template <class MIDIInterface_t>
122 void MIDI_Interface::updateIncoming(MIDIInterface_t *iface) {
123  if (iface->getStaller() == iface)
124  iface->unstall(iface);
125  bool chunked = false;
126  MIDIReadEvent event = iface->read();
127  while (event != MIDIReadEvent::NO_MESSAGE) {
128  dispatchIncoming(iface, event);
129  if (event == MIDIReadEvent::SYSEX_CHUNK)
130  chunked = true;
131  if (event == MIDIReadEvent::SYSEX_MESSAGE)
132  chunked = false;
133  event = iface->read();
134  }
135  if (chunked)
136  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 }
141 
142 template <class MIDIInterface_t>
143 void MIDI_Interface::dispatchIncoming(MIDIInterface_t *iface,
144  MIDIReadEvent event) {
145  switch (event) {
147  iface->onChannelMessage(iface->getChannelMessage());
148  break;
149  case MIDIReadEvent::SYSEX_CHUNK: // fallthrough
151  iface->onSysExMessage(iface->getSysExMessage());
152  break;
154  iface->onSysCommonMessage(iface->getSysCommonMessage());
155  break;
157  iface->onRealTimeMessage(iface->getRealTimeMessage());
158  break;
159  case MIDIReadEvent::NO_MESSAGE: break; // LCOV_EXCL_LINE
160  default: break; // LCOV_EXCL_LINE
161  }
162 }
163 
164 template <class MIDIInterface_t>
165 void MIDI_Interface::handleStall(MIDIInterface_t *iface) {
166  iface->unstall(iface);
167 
168  unsigned long startTime = millis();
169  while (millis() - startTime < SYSEX_CHUNK_TIMEOUT) {
170  MIDIReadEvent event = iface->read();
171  dispatchIncoming(iface, event);
172  if (event == MIDIReadEvent::SYSEX_CHUNK)
173  startTime = millis(); // reset timeout
174  else if (event == MIDIReadEvent::SYSEX_MESSAGE)
175  return;
176  }
177  DEBUGREF(F("Warning: Unable to un-stall pipes: ")
178  << iface->getStallerName());
179 }
180 
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.
Definition: Updatable.hpp:173
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.
Definition: MIDI_Sender.hpp:11
void send(ChannelMessage message)
Send a MIDI Channel Voice message.
#define DEBUGREF(x)
Print an expression and its location (file and line number) to the debug output if debugging is enabl...
Definition: Debug.hpp:108
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.
Definition: MIDI_Pipes.hpp:586