This repository has been archived by the owner on Oct 26, 2023. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 3
/
MS3.h
361 lines (304 loc) · 10.8 KB
/
MS3.h
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
/**
* This is a simple library to control the Boss MS-3.
*
* Check README.md or visit https://github.com/MrHaroldA/MS3 for more information.
*
* Debug information:
* - Define MS3_DEBUG_MODE in your sketch before including this library.
*
* Dependencies
* - An Arduino with a USB Host Shield
* - https://github.com/felis/USB_Host_Shield_2.0
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef MS3_h
#define MS3_h
#include "Arduino.h"
/**
* Overridable config.
*
* MS3_WRITE_INTERVAL_MSEC: The delay before a new message is sent after a write action.
* MS3_READ_INTERVAL_MSEC: The delay before a new message is sent after a read action.
* MS3_RECEIVE_INTERVAL_MSEC: The delay after receiving data from the MS-3.
* MS3_HEADER: The manufacturer and device id header. Define MS3_CUSTOM_HEADER to override this.
* MS3_QUEUE_SIZE: The maximum number of items in the send queue.
*/
#ifndef MS3_WRITE_INTERVAL_MSEC
#define MS3_WRITE_INTERVAL_MSEC 4
#endif
#ifndef MS3_READ_INTERVAL_MSEC
#define MS3_READ_INTERVAL_MSEC 25
#endif
#ifndef MS3_RECEIVE_INTERVAL_MSEC
#define MS3_RECEIVE_INTERVAL_MSEC 4
#endif
#ifndef MS3_CUSTOM_HEADER
const byte MS3_HEADER[6] = {0x41, 0x00, 0x00, 0x00, 0x00, 0x3B};
#endif
#ifndef MS3_CUSTOM_HANDSHAKE
const byte HANDSHAKE[15] = {0xF0, 0x7E, 0x00, 0x06, 0x02, 0x41, 0x3B, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF7};
#endif
#ifndef MS3_QUEUE_SIZE
#define MS3_QUEUE_SIZE 20
#endif
/**
* The configuration options below are internal and should not be changed.
*/
#ifdef MS3_DEBUG_MODE
#define MS3_DEBUG(x) Serial.print(x)
#define MS3_DEBUGLN(x) Serial.println(x)
#define MS3_DEBUG_AS(x, y) Serial.print(x, y)
#else
#define MS3_DEBUG(x) (void)(x)
#define MS3_DEBUGLN(x) (void)(x)
#define MS3_DEBUG_AS(x, y) (void)(x)
#endif
#include "usbh_midi.h"
#include "Queue.h"
// General configuration.
const unsigned int INIT_DELAY_MSEC = 100;
const byte MS3_WRITE = 0x12;
const byte MS3_READ = 0x11;
// Return values.
const int8_t MS3_NOT_READY = 0;
const int8_t MS3_READY = 1;
const int8_t MS3_DATA_SENT = 2;
const int8_t MS3_DATA_RECEIVED = 3;
const int8_t MS3_NOTHING_HAPPENED = 4;
const int8_t MS3_ALMOST_IDLE = 5;
const int8_t MS3_IDLE = 6;
// Fixed data.
const byte SYSEX_START = 0xF0;
const byte SYSEX_END = 0xF7;
const unsigned long P_EDIT = 0x7F000001;
// Load the Queue class.
Queue Queue;
class MS3 : public USBH_MIDI {
private:
USB Usb;
byte lastState = 0;
bool ready = false;
unsigned long nextMessage = 0;
/**
* The last bit of the data sent to the MS-3 contains a checksum of the parameter and data.
*/
byte checksum(byte const *data, byte dataLength) {
byte sum = 0, i;
for (i = 8; i < 12 + dataLength; i++) {
sum = (sum + data[i]) & (byte) 0x7F;
}
return ((byte) 128 - sum) & (byte) 0x7F;
}
/**
* Construct and send a full SysEx message.
*/
void send(const unsigned long address, byte *data, byte dataLength, byte action) {
byte sysex[14 + dataLength] = {0};
memcpy(sysex + 1, MS3_HEADER, 7);
sysex[8] = (byte) (address >> 24);
sysex[9] = (byte) (address >> 16);
sysex[10] = (byte) (address >> 8);
sysex[11] = (byte) (address);
memcpy(sysex + 12, data, dataLength);
sysex[0] = SYSEX_START;
sysex[7] = action;
sysex[12 + dataLength] = checksum(sysex, dataLength);
sysex[13 + dataLength] = SYSEX_END;
MS3::send(sysex);
}
/**
* Send the data to the MS-3.
*/
void send(byte *data) {
byte result, dataLength = (byte) MS3::countSysExDataSize(data);
MS3_DEBUG(F("TX:"));
MS3::printSysEx(data, dataLength);
if ((result = MS3::SendSysEx(data, dataLength)) != 0) {
MS3_DEBUG(F(" *** Transfer error: "));
MS3_DEBUG(result);
}
MS3_DEBUGLN(F("."));
}
/**
* MS3_DEBUG printer for the received SysEx data.
*/
void printSysEx(byte *data, byte dataLength) {
char buf[10];
for (byte i = 0; i < dataLength; i++) {
sprintf(buf, " %02X", data[i]);
MS3_DEBUG(buf);
}
MS3_DEBUG(F(" ("));
MS3_DEBUG(dataLength);
MS3_DEBUG(F(")"));
}
/**
* Check if we've received any data.
*/
bool receive(unsigned long ¶meter, int &dataOut) {
byte
incoming[MIDI_EVENT_PACKET_SIZE] = {0},
data[MIDI_EVENT_PACKET_SIZE] = {0},
dataLength = 0,
i;
uint16_t rcvd;
if (MS3::RecvData(&rcvd, incoming) == 0) {
if (rcvd == 0) {
return false;
}
byte *p = incoming;
for (i = 0; i < MIDI_EVENT_PACKET_SIZE; i += 4) {
if (*p == 0 && *(p + 1) == 0) {
break;
}
byte chunk[3] = {0};
if (MS3::extractSysExData(p, chunk) != 0) {
for (byte part : chunk) {
data[dataLength] = part;
dataLength++;
if (part == SYSEX_END) {
break;
}
}
p += 4;
}
}
MS3_DEBUG(F("RX:"));
MS3::printSysEx(data, dataLength);
MS3_DEBUGLN(F("."));
// Return values.
parameter = 0;
for (i = 0; i < 4; i++) {
parameter += (unsigned long) data[8 + i] << (3 - i) * 8;
}
dataOut = (byte) data[dataLength - 3];
// If the data is one byte longer, add x times 128 to the return value for a full integer range.
if (dataLength == 16) {
dataOut += data[dataLength - 4] * 128;
}
return true;
}
return false;
}
/**
* Check if the USB layer is ready.
*/
bool isReady() {
Usb.Task();
if (Usb.getUsbTaskState() == USB_STATE_RUNNING) {
return true;
} else if (MS3::lastState != Usb.getUsbTaskState()) {
MS3_DEBUG(F("*** USB task state: "));
MS3_DEBUG_AS(MS3::lastState = Usb.getUsbTaskState(), HEX);
MS3_DEBUGLN(F("."));
MS3::ready = false;
}
return false;
}
public:
/**
* Constructor.
*/
MS3() : USBH_MIDI(&Usb) {}
/**
* Set up the USB layer.
*/
bool begin() {
if (Usb.Init() == -1) {
MS3_DEBUG(F("*** USB init error! ***"));
return false;
}
return true;
}
/**
* Init the editor mode.
*/
void setEditorMode(bool active = true) {
MS3::send((byte *) HANDSHAKE);
delay(MS3_WRITE_INTERVAL_MSEC);
MS3::send((byte *) HANDSHAKE);
delay(MS3_WRITE_INTERVAL_MSEC);
byte data[1] = {(active) ? (byte) 0x01 : (byte) 0x00};
MS3::send(P_EDIT, data, 1, MS3_WRITE);
delay(INIT_DELAY_MSEC);
MS3_DEBUGLN(F("*** Up and ready!"));
}
/**
* This is the main function for both receiving and sending data when
* there's nothing to receive.
*/
byte update(unsigned long ¶meter, int &data) {
// Are we ready?
if (MS3::isReady()) {
if (!MS3::ready) {
MS3::ready = true;
return MS3_READY;
}
} else {
return MS3_NOT_READY;
}
// Is there data waiting to be picked up?
if (MS3::receive(parameter, data)) {
MS3::nextMessage = millis() + MS3_RECEIVE_INTERVAL_MSEC;
return MS3_DATA_RECEIVED;
}
// Check if we need to send out a queued item.
queueItem item = {};
if (MS3::nextMessage <= millis() && Queue.read(item)) {
// Construct the data to send to the MS-3.
byte input[item.dataLength] = {0};
input[item.dataLength - 1] = item.data % (byte) 128;
if (item.dataLength >= 2) {
input[item.dataLength - 2] = (item.data >= 128) ? (byte) 1 : (byte) 0;
}
// Send the queue item to the MS-3.
MS3::send(
item.address,
input,
item.dataLength,
item.operation
);
// Store when the next message may be sent.
MS3::nextMessage = millis() + (item.operation == MS3_READ ? MS3_READ_INTERVAL_MSEC : MS3_WRITE_INTERVAL_MSEC);
return MS3_DATA_SENT;
}
// Are we done? Did we wait for the last message to finish?
if (Queue.isEmpty()) {
return (MS3::nextMessage > millis()) ? MS3_ALMOST_IDLE : MS3_IDLE;
}
// Nothing interesting happened.
return MS3_NOTHING_HAPPENED;
}
/**
* Set this single byte parameter on the MS-3. Optionally pad it with leading zero-bytes with a dataLength >= 1.
*/
void write(const unsigned long address, byte data, byte dataLength = 1) {
Queue.write(address, data, dataLength, MS3_WRITE);
}
/**
* Tell the MS-3 to send us the value of this parameter.
*/
void read(const unsigned long address, byte data) {
Queue.write(address, data, 4, MS3_READ);
}
/**
* Flush the queue if it's not empty.
*/
void flushQueue() {
if (!Queue.isEmpty()) {
Queue.flush();
}
}
};
#endif