Control Surface main
MIDI Control Surface library for Arduino
USBMIDI_Sender.hpp
Go to the documentation of this file.
1#pragma once
2
4#include <Settings/SettingsWrapper.hpp>
5
7
9
15 public:
17 template <class Send>
18 void sendChannelMessage(ChannelMessage, Send &&send);
19
21 template <class Send>
22 void sendSysCommonMessage(SysCommonMessage, Send &&send);
23
25 template <class Send>
26 void sendRealTimeMessage(RealTimeMessage, Send &&send);
27
31 template <class Send>
32 void sendSysEx(SysExMessage, Send &&send);
33
36 template <class Send>
37 void sendFullSysEx(SysExMessage, Send &&send);
38
39#if !NO_SYSEX_OUTPUT
40 private:
43 template <class Send>
44 void sendSysExStartCont1(const uint8_t *data, Cable cable, Send &send);
51 template <class Send>
52 void sendSysExStartCont(const uint8_t *&data, uint16_t &length, Cable cable,
53 Send &send);
56 template <class Send>
57 void sendSysExEnd(const uint8_t *data, uint16_t length, Cable cable,
58 Send &send);
59
60 private:
66 uint8_t storedSysExData[16][3];
68 uint8_t storedSysExLength[16] = {};
69#endif
70
71 private:
73};
74
75template <class Send>
77 send(msg.cable, CIN(msg.header >> 4), // CN|CIN
78 msg.header, // status
79 msg.data1, // data 1
80 msg.data2); // data 2
81}
82
83template <class Send>
85 auto cn = msg.cable;
86 switch (msg.getNumberOfDataBytes()) {
87 case 2: // 3B
88 send(cn, CIN::SYSTEM_COMMON_3B, msg.header, msg.data1, msg.data2);
89 break;
90 case 1: // 2B
91 send(cn, CIN::SYSTEM_COMMON_2B, msg.header, msg.data1, 0);
92 break;
93 case 0: // 1B
94 send(cn, CIN::SYSTEM_COMMON_1B, msg.header, 0, 0);
95 break;
96 default: break;
97 }
98}
99
100template <class Send>
102 send(msg.cable, CIN::SINGLE_BYTE, msg.message, 0, 0);
103}
104
105// This is the readable documentation version for sending full SysEx messages,
106// the chunked version below is more complicated, but the principle is similar.
107template <class Send>
109 size_t length = msg.length;
110 const uint8_t *data = msg.data;
111 Cable cn = msg.cable;
112 while (length > 3) {
113 send(cn, CIN::SYSEX_START_CONT, data[0], data[1], data[2]);
114 data += 3;
115 length -= 3;
116 }
117 switch (length) {
118 case 3: send(cn, CIN::SYSEX_END_3B, data[0], data[1], data[2]); break;
119 case 2: send(cn, CIN::SYSEX_END_2B, data[0], data[1], 0); break;
120 case 1: send(cn, CIN::SYSEX_END_1B, data[0], 0, 0); break;
121 default: break;
122 }
123}
124
125#if !NO_SYSEX_OUTPUT
126
127template <class Send>
128void USBMIDI_Sender::sendSysExStartCont1(const uint8_t *data, Cable cable,
129 Send &send) {
130 send(cable, CIN::SYSEX_START_CONT, data[0], data[1], data[2]);
131}
132
133template <class Send>
134void USBMIDI_Sender::sendSysExStartCont(const uint8_t *&data, uint16_t &length,
135 Cable cable, Send &send) {
136 while (length > 3) {
137 sendSysExStartCont1(data, cable, send);
138 data += 3;
139 length -= 3;
140 }
141}
142
143template <class Send>
144void USBMIDI_Sender::sendSysExEnd(const uint8_t *data, uint16_t length,
145 Cable cn, Send &send) {
146 switch (length) {
147 case 3: send(cn, CIN::SYSEX_END_3B, data[0], data[1], data[2]); break;
148 case 2: send(cn, CIN::SYSEX_END_2B, data[0], data[1], 0); break;
149 case 1: send(cn, CIN::SYSEX_END_1B, data[0], 0, 0); break;
150 default: break; // LCOV_EXCL_LINE
151 }
152}
153
154template <class Send>
155void USBMIDI_Sender::sendSysEx(const SysExMessage msg, Send &&send) {
156 // Don't bother trying to send empty messages
157 if (msg.length == 0)
158 return;
159
160 Cable cable = msg.cable;
161 uint8_t c = cable.getRaw();
162 uint16_t length = msg.length;
163 const uint8_t *data = msg.data;
164
165 // Even if the previous chunk wasn't terminated correctly, if this is a new
166 // SysEx message, always forget the previous unsent chunk.
167 if (msg.isFirstChunk()) {
168 // TODO: send a SysExEnd for previous chunk?
169 storedSysExLength[c] = 0;
170 }
171
172 // Complete the previous unsent chunk to (at most) 3 bytes
173 while (length > 0 && storedSysExLength[c] < 3) {
174 storedSysExData[c][storedSysExLength[c]++] = *data++;
175 --length;
176 }
177
178 // If all bytes of the new chunk were used, there are <= 3 stored bytes
179 if (length == 0) {
180 // If this chunk is the last one, terminate the SysEx (termination can
181 // have 1, 2 or 3 bytes, so no need to store anything)
182 if (msg.isLastChunk()) {
184 storedSysExLength[c] = 0;
185 }
186 // If it's the end of the chunk but not the end of the SysEx, and if we
187 // have exactly 3 bytes left, we can send them immediately
188 else if (storedSysExLength[c] == 3) {
189 sendSysExStartCont1(storedSysExData[c], cable, send);
190 storedSysExLength[c] = 0;
191 }
192 // If we have less than 3 bytes, we cannot send them now, we have to
193 // store them and wait for the next chunk
194 }
195 // If there are new bytes left in the chunk, there are exactly 3 stored
196 // bytes
197 else {
198 // First send the 3 stored bytes
199 sendSysExStartCont1(storedSysExData[c], cable, send);
200 // Then send whatever new data is left in the new chunk (but leave at
201 // least 3 bytes)
202 sendSysExStartCont(data, length, cable, send);
203 // If this chunk is the last one, terminate the SysEx (termination can
204 // have 1, 2 or 3 bytes, so no need to store anything)
205 if (msg.isLastChunk()) {
206 sendSysExEnd(data, length, cable, send);
207 storedSysExLength[c] = 0;
208 }
209 // If it's the end of the chunk but not the end of the SysEx, and if we
210 // have exactly 3 bytes left, we can send them immediately
211 else if (length == 3) {
212 sendSysExStartCont1(data, cable, send);
213 storedSysExLength[c] = 0;
214 }
215 // If we have 1 or 2 bytes left, we cannot send them now, so store them
216 // until we get enough data to fill a 3-byte packet
217 else {
218 storedSysExLength[c] = length;
219 memcpy(storedSysExData[c], data, length);
220 }
221 }
222}
223
224#else
225
226template <class Send>
228
229#endif
230
232
MIDICodeIndexNumber
MIDI USB Code Index Numbers.
#define END_CS_NAMESPACE
#define BEGIN_CS_NAMESPACE
#define AH_DIAGNOSTIC_POP()
Definition: Warnings.hpp:37
#define AH_DIAGNOSTIC_WERROR()
Definition: Warnings.hpp:36
A type-safe class for MIDI USB Cable numbers.
Definition: Cable.hpp:13
constexpr uint8_t getRaw() const
Get the cable as an integer.
Definition: Cable.hpp:29
A class for sending MIDI USB messages.
void sendChannelMessage(ChannelMessage, Send &&send)
Send a MIDI Channel message using the given sender.
void sendSysEx(SysExMessage, Send &&send)
Send a MIDI System Exclusive message using the given sender.
void sendSysExEnd(const uint8_t *data, uint16_t length, Cable cable, Send &send)
Send a SysExEnd USB packet.
void sendFullSysEx(SysExMessage, Send &&send)
Send a MIDI System Exclusive message using the given sender.
void sendSysExStartCont1(const uint8_t *data, Cable cable, Send &send)
Send a single SysEx starts or continues USB packet.
void sendSysCommonMessage(SysCommonMessage, Send &&send)
Send a MIDI System Common message using the given sender.
void sendSysExStartCont(const uint8_t *&data, uint16_t &length, Cable cable, Send &send)
Send as many SysEx starts or continues USB packets, such that the remaining length is 3,...
uint8_t storedSysExLength[16]
Number of remaining SysEx bytes stored.
void sendRealTimeMessage(RealTimeMessage, Send &&send)
Send a MIDI Real-Time message using the given sender.
uint8_t storedSysExData[16][3]
Stores remainder of outgoing SysEx chunks.
uint8_t data2
First MIDI data byte.
uint8_t header
MIDI status byte (message type and channel).
uint8_t data1
First MIDI data byte.
Cable cable
USB MIDI cable number;.
uint8_t getNumberOfDataBytes() const
Get the number of data bytes of this type of System Common message.
const uint8_t * data
bool isLastChunk() const
bool isFirstChunk() const