Control Surface new-input
MIDI Control Surface library for Arduino
SerialMIDI_Parser.cpp
Go to the documentation of this file.
1 #include "SerialMIDI_Parser.hpp"
2 
4 
6  rtmsg.message = midiByte;
8 }
9 
10 /*
11  * Relevant sources about MIDI running status:
12  * - MIDI 1.0 Detailed Specification: A-2 (pdf p.65):
13  * “cleared when a System Exclusive or Common status message is received”
14  * - BLE-MIDI: p.4 (pdf p.7):
15  * “System Common and System Real-Time messages do not cancel Running Status”
16  */
17 
19  // If it's a Real-Time message
20  if (midiByte >= uint8_t(MIDIMessageType::TIMING_CLOCK)) {
21  return handleRealTime(midiByte);
22  }
23 
24  // Normal header (channel message, system exclusive, system common)
25  else {
26  // If a SysEx message was being received, and now we receive
27  // another status byte, remember to correctly terminate the SysEx
28  // message later
29  bool unterminatedSysEx =
31 
32  // Tune Request is a special System Common message of 1 byte
33  if (midiByte == uint8_t(MIDIMessageType::TUNE_REQUEST)) {
34  if (unterminatedSysEx) {
35  // If we were previously receiving a SysEx message, this Tune
36  // Request should terminate that SysEx message first.
37  // We cannot return both the SysEx message and the Tune Request
38  // at the same time, so store the Tune Request for later.
39  storeByte(midiByte);
40  } else {
41  midimsg.header = midiByte;
42  midimsg.data1 = 0;
43  midimsg.data2 = 0;
45  runningHeader = 0;
46  currentHeader = 0;
48  }
49  }
50 
51 #if !IGNORE_SYSEX
52  // If the new status byte terminates the SysEx message, try to finish
53  // the message (if buffer space allows it)
54  if (unterminatedSysEx) {
55  // Check if the SysEx buffer has enough space to store the end byte
56  if (!hasSysExSpace()) {
57  storeByte(midiByte);
59  }
60 
61  // Enough space available in buffer, store the end byte and
62  // terminate the message
64  endSysEx();
65 
66  // If the previous SysEx message was terminated by the SysExStart
67  // byte of next message, we have to return the previous message now
68  // and remember to start the next message in the next iteration
69  if (midiByte == uint8_t(MIDIMessageType::SYSEX_START)) {
70  storeByte(midiByte);
71  runningHeader = 0;
72  currentHeader = 0;
74  }
75  }
76 
77  // If we don't have to handle the unterminated SysEx message, and the
78  // new byte is a SysExStart, reset the SysEx buffer and store the start
79  // byte
80  else if (midiByte == uint8_t(MIDIMessageType::SYSEX_START)) {
81  startSysEx();
83  }
84 
85  // Save the newly received status byte
86  currentHeader = midiByte;
87  // A new message starts, so we haven't received the second byte yet
88  thirdByte = false;
89 
90  // If a SysEx message was terminated without starting a new message,
91  // the current header should be zero because no messages are active
92  if (midiByte == uint8_t(MIDIMessageType::SYSEX_END))
93  currentHeader = 0;
94 
95  return unterminatedSysEx ? MIDIReadEvent::SYSEX_MESSAGE
97 #else
98  (void)unterminatedSysEx;
99  // Save the newly received status byte
100  currentHeader = midiByte;
101  // A new message starts, so we haven't received the second byte yet
102  thirdByte = false;
104 #endif // IGNORE_SYSEX
105  }
106 }
107 
109  if (currentHeader == 0) {
110  // If we didn't receive a header, we can't do anything with this data
111  if (runningHeader == 0) {
112  DEBUGREF(F("Data byte ignored"));
114  }
115  // If we have an active running status, use that as the current header
117  }
118 
120 
121  // If this is the third byte of three (second data byte)
122  if (thirdByte) {
123  // If it's a channel message
125  midimsg.data2 = midiByte;
126  // Next byte is either a header or the first data byte of the next
127  // message, so clear the thirdByte flag
128  thirdByte = false;
130  currentHeader = 0;
132  }
133  // If it's a system common message
134  else if (midimsg.hasValidSystemCommonHeader()) {
135  midimsg.data2 = midiByte;
136  thirdByte = false;
138  runningHeader = 0;
139  currentHeader = 0;
141  }
142  }
143 
144  // If this is not the third byte of three, it's either the second byte
145  // (first data byte) of a channel or system common message,
146  // or a SysEx data byte
147 
148  // If it's a channel message
150  // If it's a channel message with two data bytes
151  if (ChannelMessage(midimsg).hasTwoDataBytes()) {
152  midimsg.data1 = midiByte;
153  // We've received the second byte, expect the third byte next
154  thirdByte = true;
156  }
157  // If it's a channel message with one data byte
158  else {
159  midimsg.data1 = midiByte;
160  midimsg.data2 = 0;
162  currentHeader = 0;
163  // The message is finished
165  }
166  }
167 
168  // If it's a system common message
169  else if (midimsg.hasValidSystemCommonHeader()) {
170  // If it's a system common message with two data bytes
171  if (SysCommonMessage(midimsg).getNumberOfDataBytes() == 2) {
172  midimsg.data1 = midiByte;
173  // We've received the second byte, expect the third byte next
174  thirdByte = true;
176  }
177  // If it's a system common message with one data byte
178  else if (SysCommonMessage(midimsg).getNumberOfDataBytes() == 1) {
179  midimsg.data1 = midiByte;
180  midimsg.data2 = 0;
182  runningHeader = 0;
183  currentHeader = 0;
184  // The message is finished
186  }
187  }
188 
189  // Otherwise, it's not a channel message
190 
191 #if !IGNORE_SYSEX
192  // If we're receiving a SysEx message, it's a SysEx data byte
193  else if (currentHeader == uint8_t(MIDIMessageType::SYSEX_START)) {
194  // Check if the SysEx buffer has enough space to store the data
195  if (!hasSysExSpace()) {
196  storeByte(midiByte); // Remember to add it next time
198  }
199 
200  addSysExByte(midiByte);
202  }
203 #endif // IGNORE_SYSEX
204 
205  ERROR(F("Data byte after invalid header"), 0x3434); // LCOV_EXCL_LINE
206  runningHeader = 0; // LCOV_EXCL_LINE
207  currentHeader = 0; // LCOV_EXCL_LINE
208  return MIDIReadEvent::NO_MESSAGE; // LCOV_EXCL_LINE
209 }
210 
212  // DEBUGREF(hex << NAMEDVALUE(midiByte) << dec);
213 
214  // If it's a status byte (first byte of a message)
215  if (isStatus(midiByte)) {
216  return handleStatus(midiByte);
217  }
218 
219  // If it's a data byte
220  else {
221  return handleData(midiByte);
222  }
223 }
224 
226  if (!hasStoredByte())
228 
229  uint8_t midiByte = popStoredByte();
230 
231 #if !IGNORE_SYSEX
232  // If a SysEx message was in progress
233  bool receivingSysEx =
235 
236  if (receivingSysEx) {
237  // Reset the buffer for the next chunk
238  startSysEx();
239  }
240 #endif
241 
242  return feed(midiByte);
243 }
244 
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.
#define END_CS_NAMESPACE
#define BEGIN_CS_NAMESPACE
static bool isStatus(uint8_t data)
Check if the given byte is a MIDI header/status byte.
Definition: MIDI_Parser.hpp:37
MIDIMessage midimsg
Definition: MIDI_Parser.hpp:32
RealTimeMessage rtmsg
Definition: MIDI_Parser.hpp:33
MIDIReadEvent resume()
Resume the parser with the previously stored and unhandled byte.
bool sysCommonCancelsRunningStatus
Accounts for running status differences between MIDI 1.0 and BLE-MIDI.
uint8_t currentHeader
Current header (not necessarily running), contains the header of the message that's currently being r...
MIDIReadEvent handleRealTime(uint8_t midiByte)
uint8_t runningHeader
Running status header.
bool hasStoredByte() const
Check whether there's a stored byte.
uint8_t popStoredByte()
Get the stored byte. Afterwards, hasStoredByte will return false.
MIDIReadEvent feed(uint8_t midibyte)
Feed a new byte to the parser.
void storeByte(uint8_t midiByte)
Store a byte to parse later.
MIDIReadEvent handleData(uint8_t midiByte)
bool thirdByte
Flag that remembers that the next data byte will be the third byte of a message.
bool hasSysExSpace() const
void addSysExByte(uint8_t data)
MIDIReadEvent handleStatus(uint8_t midiByte)
#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
#define ERROR(msg, errc)
Print the error message and error code, and stop the execution if FATAL_ERRORS are enabled.
Definition: Error.hpp:42
uint8_t data2
First MIDI data byte.
bool hasValidChannelMessageHeader() const
Check whether the header is a valid header for a channel message.
uint8_t header
MIDI status byte (message type and channel).
uint8_t data1
First MIDI data byte.
bool hasValidSystemCommonHeader() const
Check whether the header is a valid header for a System Common message.