Line data Source code
1 : #pragma once
2 :
3 : #include <MIDI_Parsers/MIDI_MessageTypes.hpp>
4 : #include <Settings/SettingsWrapper.hpp>
5 :
6 : AH_DIAGNOSTIC_WERROR()
7 :
8 : BEGIN_CS_NAMESPACE
9 :
10 : /**
11 : * @brief A class for sending MIDI USB messages. Includes support for chunked
12 : * MIDI USB SysEx messages.
13 : */
14 : class USBMIDI_Sender {
15 : public:
16 : /// Send a MIDI Channel message using the given sender.
17 : template <class Send>
18 : void sendChannelMessage(ChannelMessage, Send &&send);
19 :
20 : /// Send a MIDI System Common message using the given sender.
21 : template <class Send>
22 : void sendSysCommonMessage(SysCommonMessage, Send &&send);
23 :
24 : /// Send a MIDI Real-Time message using the given sender.
25 : template <class Send>
26 : void sendRealTimeMessage(RealTimeMessage, Send &&send);
27 :
28 : /// Send a MIDI System Exclusive message using the given sender.
29 : /// Message may be chunked, remaining bytes are stored until next chunk is
30 : /// sent.
31 : template <class Send>
32 : void sendSysEx(SysExMessage, Send &&send);
33 :
34 : /// Send a MIDI System Exclusive message using the given sender.
35 : /// Message should be complete, i.e. from SysEx Start to SysEx End.
36 : template <class Send>
37 : void sendFullSysEx(SysExMessage, Send &&send);
38 :
39 : #if !NO_SYSEX_OUTPUT
40 : private:
41 : /// Send a single SysEx starts or continues USB packet. Exactly 3 bytes are
42 : /// sent. The `data` pointer is not incremented.
43 : template <class Send>
44 : void sendSysExStartCont1(const uint8_t *data, Cable cable, Send &send);
45 : /// Send as many SysEx starts or continues USB packets, such that the
46 : /// remaining length is 3, 2 or 1 byte. The `data` pointer is incremented,
47 : /// and the `length` is decremented.
48 : /// The reason for leaving 3, 2 or 1 bytes remaining is so the message can
49 : /// be finished using a SysExEnd USB packet, which has to have 3, 2 or 1
50 : /// bytes.
51 : template <class Send>
52 : void sendSysExStartCont(const uint8_t *&data, uint16_t &length, Cable cable,
53 : Send &send);
54 : /// Send a SysExEnd USB packet. The `length` should be either 3, 2 or 1
55 : /// bytes, and the last byte of `data` should be a SysExEnd byte.
56 : template <class Send>
57 : void sendSysExEnd(const uint8_t *data, uint16_t length, Cable cable,
58 : Send &send);
59 :
60 : private:
61 : /// Stores remainder of outgoing SysEx chunks. Each USB packet (except the
62 : /// last one) should contain a multiple of 3 SysEx bytes. If the SysEx chunk
63 : /// size is not a multiple of 3, there will be remaining bytes that can't be
64 : /// sent yet, until the next chunk arrives. See the comments in the
65 : /// @ref USBMIDI_Sender::sendSysEx() implementation for more details.
66 : uint8_t storedSysExData[16][3];
67 : /// Number of remaining SysEx bytes stored.
68 : uint8_t storedSysExLength[16] = {};
69 : #endif
70 :
71 : private:
72 : using CIN = MIDICodeIndexNumber;
73 : };
74 :
75 : template <class Send>
76 3 : void USBMIDI_Sender::sendChannelMessage(ChannelMessage msg, Send &&send) {
77 3 : send(msg.cable, CIN(msg.header >> 4), // CN|CIN
78 3 : msg.header, // status
79 3 : msg.data1, // data 1
80 3 : msg.data2); // data 2
81 3 : }
82 :
83 : template <class Send>
84 0 : void USBMIDI_Sender::sendSysCommonMessage(SysCommonMessage msg, Send &&send) {
85 0 : auto cn = msg.cable;
86 0 : switch (msg.getNumberOfDataBytes()) {
87 0 : case 2: // 3B
88 0 : send(cn, CIN::SYSTEM_COMMON_3B, msg.header, msg.data1, msg.data2);
89 0 : break;
90 0 : case 1: // 2B
91 0 : send(cn, CIN::SYSTEM_COMMON_2B, msg.header, msg.data1, 0);
92 0 : break;
93 0 : case 0: // 1B
94 0 : send(cn, CIN::SYSTEM_COMMON_1B, msg.header, 0, 0);
95 0 : break;
96 0 : default: break;
97 : }
98 0 : }
99 :
100 : template <class Send>
101 1 : void USBMIDI_Sender::sendRealTimeMessage(RealTimeMessage msg, Send &&send) {
102 1 : send(msg.cable, CIN::SINGLE_BYTE, msg.message, 0, 0);
103 1 : }
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.
107 : template <class Send>
108 : void USBMIDI_Sender::sendFullSysEx(SysExMessage msg, Send &&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 :
127 : template <class Send>
128 15 : void USBMIDI_Sender::sendSysExStartCont1(const uint8_t *data, Cable cable,
129 : Send &send) {
130 15 : send(cable, CIN::SYSEX_START_CONT, data[0], data[1], data[2]);
131 15 : }
132 :
133 : template <class Send>
134 11 : void USBMIDI_Sender::sendSysExStartCont(const uint8_t *&data, uint16_t &length,
135 : Cable cable, Send &send) {
136 11 : while (length > 3) {
137 3 : sendSysExStartCont1(data, cable, send);
138 3 : data += 3;
139 3 : length -= 3;
140 : }
141 8 : }
142 :
143 : template <class Send>
144 9 : void USBMIDI_Sender::sendSysExEnd(const uint8_t *data, uint16_t length,
145 : Cable cn, Send &send) {
146 9 : switch (length) {
147 4 : case 3: send(cn, CIN::SYSEX_END_3B, data[0], data[1], data[2]); break;
148 3 : case 2: send(cn, CIN::SYSEX_END_2B, data[0], data[1], 0); break;
149 2 : case 1: send(cn, CIN::SYSEX_END_1B, data[0], 0, 0); break;
150 : default: break; // LCOV_EXCL_LINE
151 : }
152 9 : }
153 :
154 : template <class Send>
155 19 : void USBMIDI_Sender::sendSysEx(const SysExMessage msg, Send &&send) {
156 : // Don't bother trying to send empty messages
157 19 : if (msg.length == 0)
158 0 : return;
159 :
160 19 : Cable cable = msg.cable;
161 19 : uint8_t c = cable.getRaw();
162 19 : uint16_t length = msg.length;
163 19 : 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 19 : if (msg.isFirstChunk()) {
168 : // TODO: send a SysExEnd for previous chunk?
169 10 : storedSysExLength[c] = 0;
170 : }
171 :
172 : // Complete the previous unsent chunk to (at most) 3 bytes
173 60 : while (length > 0 && storedSysExLength[c] < 3) {
174 41 : storedSysExData[c][storedSysExLength[c]++] = *data++;
175 41 : --length;
176 : }
177 :
178 : // If all bytes of the new chunk were used, there are <= 3 stored bytes
179 19 : 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 11 : if (msg.isLastChunk()) {
183 3 : sendSysExEnd(storedSysExData[c], storedSysExLength[c], cable, send);
184 3 : 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 8 : else if (storedSysExLength[c] == 3) {
189 3 : sendSysExStartCont1(storedSysExData[c], cable, send);
190 3 : 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 8 : 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 8 : 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 8 : if (msg.isLastChunk()) {
206 6 : sendSysExEnd(data, length, cable, send);
207 6 : 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 2 : else if (length == 3) {
212 1 : sendSysExStartCont1(data, cable, send);
213 1 : 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 1 : storedSysExLength[c] = length;
219 1 : memcpy(storedSysExData[c], data, length);
220 : }
221 : }
222 : }
223 :
224 : #else
225 :
226 : template <class Send>
227 : void USBMIDI_Sender::sendSysEx(SysExMessage, Send &&) {}
228 :
229 : #endif
230 :
231 : END_CS_NAMESPACE
232 :
233 : AH_DIAGNOSTIC_POP()
|