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