Line data Source code
1 : #pragma once
2 :
3 : #include <MIDI_Parsers/MIDI_MessageTypes.hpp>
4 :
5 : AH_DIAGNOSTIC_WERROR()
6 :
7 : BEGIN_CS_NAMESPACE
8 :
9 : /**
10 : * @brief A class for sending MIDI USB messages. Includes support for chunked
11 : * MIDI USB SysEx messages.
12 : */
13 : class USBMIDI_Sender {
14 : public:
15 : /// Send a MIDI Channel message using the given sender.
16 : template <class Send>
17 : void sendChannelMessage(ChannelMessage, Send &&send);
18 :
19 : /// Send a MIDI System Common message using the given sender.
20 : template <class Send>
21 : void sendSysCommonMessage(SysCommonMessage, Send &&send);
22 :
23 : /// Send a MIDI Real-Time message using the given sender.
24 : template <class Send>
25 : void sendRealTimeMessage(RealTimeMessage, Send &&send);
26 :
27 : /// Send a MIDI System Exclusive message using the given sender.
28 : /// Message may be chunked, remaining bytes are stored until next chunk is
29 : /// sent.
30 : template <class Send>
31 : void sendSysEx(SysExMessage, Send &&send);
32 :
33 : private:
34 : /// Send a single SysEx starts or continues USB packet. Exactly 3 bytes are
35 : /// sent. The `data` pointer is not incremented.
36 : template <class Send>
37 : void sendSysExStartCont1(const uint8_t *data, Cable cable, Send &send);
38 : /// Send as many SysEx starts or continues USB packets, such that the
39 : /// remaining length is 3, 2 or 1 byte. The `data` pointer is incremented,
40 : /// and the `length` is decremented.
41 : /// The reason for leaving 3, 2 or 1 bytes remaining is so the message can
42 : /// be finished using a SysExEnd USB packet, which has to have 3, 2 or 1
43 : /// bytes.
44 : template <class Send>
45 : void sendSysExStartCont(const uint8_t *&data, uint16_t &length, Cable cable,
46 : Send &send);
47 : /// Send a SysExEnd USB packet. The `length` should be either 3, 2 or 1
48 : /// bytes, and the last byte of `data` should be a SysExEnd byte.
49 : template <class Send>
50 : void sendSysExEnd(const uint8_t *data, uint16_t length, Cable cable,
51 : Send &send);
52 :
53 : private:
54 : /// Stores remainder of outgoing SysEx chunks. Each USB packet (except the
55 : /// last one) should contain a multiple of 3 SysEx bytes. If the SysEx chunk
56 : /// size is not a multiple of 3, there will be remaining bytes that can't be
57 : /// sent yet, until the next chunk arrives. See the comments in the
58 : /// @ref USBMIDI_Sender::sendSysEx() implementation for more details.
59 : uint8_t storedSysExData[16][3];
60 : /// Number of remaining SysEx bytes stored.
61 : uint8_t storedSysExLength[16] = {};
62 :
63 : using CIN = MIDICodeIndexNumber;
64 : };
65 :
66 : template <class Send>
67 3 : void USBMIDI_Sender::sendChannelMessage(ChannelMessage msg, Send &&send) {
68 3 : send(msg.cable, CIN(msg.header >> 4), // CN|CIN
69 3 : msg.header, // status
70 3 : msg.data1, // data 1
71 3 : msg.data2); // data 2
72 3 : }
73 :
74 : template <class Send>
75 0 : void USBMIDI_Sender::sendSysCommonMessage(SysCommonMessage msg, Send &&send) {
76 0 : auto cn = msg.cable;
77 0 : switch (msg.getNumberOfDataBytes()) {
78 0 : case 2: // 3B
79 0 : send(cn, CIN::SYSTEM_COMMON_3B, msg.header, msg.data1, msg.data2);
80 0 : break;
81 0 : case 1: // 2B
82 0 : send(cn, CIN::SYSTEM_COMMON_2B, msg.header, msg.data1, 0);
83 0 : break;
84 0 : case 0: // 1B
85 0 : send(cn, CIN::SYSTEM_COMMON_1B, msg.header, 0, 0);
86 0 : break;
87 0 : default: break;
88 : }
89 0 : }
90 :
91 : template <class Send>
92 1 : void USBMIDI_Sender::sendRealTimeMessage(RealTimeMessage msg, Send &&send) {
93 1 : send(msg.cable, CIN::SINGLE_BYTE, msg.message, 0, 0);
94 1 : }
95 :
96 : // This is the readable documentation version for sending full SysEx messages
97 : #if 0
98 : template <class Send>
99 : void USBMIDI_Sender::sendFullSysEx(SysExMessage msg, Send &send) {
100 : size_t length = msg.length;
101 : const uint8_t *data = msg.data;
102 : Cable cn = msg.cable;
103 : while (length > 3) {
104 : send(cn, CIN::SYSEX_START_CONT, data[0], data[1], data[2]);
105 : data += 3;
106 : length -= 3;
107 : }
108 : switch (length) {
109 : case 3: send(cn, CIN::SYSEX_END_3B, data[0], data[1], data[2]); break;
110 : case 2: send(cn, CIN::SYSEX_END_2B, data[0], data[1], 0); break;
111 : case 1: send(cn, CIN::SYSEX_END_1B, data[0], 0, 0); break;
112 : default: break;
113 : }
114 : }
115 : #endif // 0
116 :
117 : template <class Send>
118 15 : void USBMIDI_Sender::sendSysExStartCont1(const uint8_t *data, Cable cable,
119 : Send &send) {
120 15 : send(cable, CIN::SYSEX_START_CONT, data[0], data[1], data[2]);
121 15 : }
122 :
123 : template <class Send>
124 11 : void USBMIDI_Sender::sendSysExStartCont(const uint8_t *&data, uint16_t &length,
125 : Cable cable, Send &send) {
126 11 : while (length > 3) {
127 3 : sendSysExStartCont1(data, cable, send);
128 3 : data += 3;
129 3 : length -= 3;
130 : }
131 8 : }
132 :
133 : template <class Send>
134 9 : void USBMIDI_Sender::sendSysExEnd(const uint8_t *data, uint16_t length,
135 : Cable cn, Send &send) {
136 9 : switch (length) {
137 4 : case 3: send(cn, CIN::SYSEX_END_3B, data[0], data[1], data[2]); break;
138 3 : case 2: send(cn, CIN::SYSEX_END_2B, data[0], data[1], 0); break;
139 2 : case 1: send(cn, CIN::SYSEX_END_1B, data[0], 0, 0); break;
140 : default: break; // LCOV_EXCL_LINE
141 : }
142 9 : }
143 :
144 : template <class Send>
145 19 : void USBMIDI_Sender::sendSysEx(const SysExMessage msg, Send &&send) {
146 : // Don't bother trying to send empty messages
147 19 : if (msg.length == 0)
148 0 : return;
149 :
150 19 : Cable cable = msg.cable;
151 19 : uint8_t c = cable.getRaw();
152 19 : uint16_t length = msg.length;
153 19 : const uint8_t *data = msg.data;
154 :
155 : // Even if the previous chunk wasn't terminated correctly, if this is a new
156 : // SysEx message, always forget the previous unsent chunk.
157 19 : if (msg.isFirstChunk()) {
158 : // TODO: send a SysExEnd for previous chunk?
159 10 : storedSysExLength[c] = 0;
160 : }
161 :
162 : // Complete the previous unsent chunk to (at most) 3 bytes
163 60 : while (length > 0 && storedSysExLength[c] < 3) {
164 41 : storedSysExData[c][storedSysExLength[c]++] = *data++;
165 41 : --length;
166 : }
167 :
168 : // If all bytes of the new chunk were used, there are <= 3 stored bytes
169 19 : if (length == 0) {
170 : // If this chunk is the last one, terminate the SysEx (termination can
171 : // have 1, 2 or 3 bytes, so no need to store anything)
172 11 : if (msg.isLastChunk()) {
173 3 : sendSysExEnd(storedSysExData[c], storedSysExLength[c], cable, send);
174 3 : storedSysExLength[c] = 0;
175 : }
176 : // If it's the end of the chunk but not the end of the SysEx, and if we
177 : // have exactly 3 bytes left, we can send them immediately
178 8 : else if (storedSysExLength[c] == 3) {
179 3 : sendSysExStartCont1(storedSysExData[c], cable, send);
180 3 : storedSysExLength[c] = 0;
181 : }
182 : // If we have less than 3 bytes, we cannot send them now, we have to
183 : // store them and wait for the next chunk
184 : }
185 : // If there are new bytes left in the chunk, there are exactly 3 stored
186 : // bytes
187 : else {
188 : // First send the 3 stored bytes
189 8 : sendSysExStartCont1(storedSysExData[c], cable, send);
190 : // Then send whatever new data is left in the new chunk (but leave at
191 : // least 3 bytes)
192 8 : sendSysExStartCont(data, length, cable, send);
193 : // If this chunk is the last one, terminate the SysEx (termination can
194 : // have 1, 2 or 3 bytes, so no need to store anything)
195 8 : if (msg.isLastChunk()) {
196 6 : sendSysExEnd(data, length, cable, send);
197 6 : storedSysExLength[c] = 0;
198 : }
199 : // If it's the end of the chunk but not the end of the SysEx, and if we
200 : // have exactly 3 bytes left, we can send them immediately
201 2 : else if (length == 3) {
202 1 : sendSysExStartCont1(data, cable, send);
203 1 : storedSysExLength[c] = 0;
204 : }
205 : // If we have 1 or 2 bytes left, we cannot send them now, so store them
206 : // until we get enough data to fill a 3-byte packet
207 : else {
208 1 : storedSysExLength[c] = length;
209 1 : memcpy(storedSysExData[c], data, length);
210 : }
211 : }
212 : }
213 :
214 : END_CS_NAMESPACE
215 :
216 : AH_DIAGNOSTIC_POP()
|