|
@@ -51,6 +51,19 @@ static const char f_midi_longname[] = "MIDI Gadget";
|
|
|
*/
|
|
|
#define MAX_PORTS 16
|
|
|
|
|
|
+/* MIDI message states */
|
|
|
+enum {
|
|
|
+ STATE_INITIAL = 0, /* pseudo state */
|
|
|
+ STATE_1PARAM,
|
|
|
+ STATE_2PARAM_1,
|
|
|
+ STATE_2PARAM_2,
|
|
|
+ STATE_SYSEX_0,
|
|
|
+ STATE_SYSEX_1,
|
|
|
+ STATE_SYSEX_2,
|
|
|
+ STATE_REAL_TIME,
|
|
|
+ STATE_FINISHED, /* pseudo state */
|
|
|
+};
|
|
|
+
|
|
|
/*
|
|
|
* This is a gadget, and the IN/OUT naming is from the host's perspective.
|
|
|
* USB -> OUT endpoint -> rawmidi
|
|
@@ -61,13 +74,6 @@ struct gmidi_in_port {
|
|
|
int active;
|
|
|
uint8_t cable;
|
|
|
uint8_t state;
|
|
|
-#define STATE_UNKNOWN 0
|
|
|
-#define STATE_1PARAM 1
|
|
|
-#define STATE_2PARAM_1 2
|
|
|
-#define STATE_2PARAM_2 3
|
|
|
-#define STATE_SYSEX_0 4
|
|
|
-#define STATE_SYSEX_1 5
|
|
|
-#define STATE_SYSEX_2 6
|
|
|
uint8_t data[2];
|
|
|
};
|
|
|
|
|
@@ -403,118 +409,166 @@ static int f_midi_snd_free(struct snd_device *device)
|
|
|
return 0;
|
|
|
}
|
|
|
|
|
|
-static void f_midi_transmit_packet(struct usb_request *req, uint8_t p0,
|
|
|
- uint8_t p1, uint8_t p2, uint8_t p3)
|
|
|
-{
|
|
|
- unsigned length = req->length;
|
|
|
- u8 *buf = (u8 *)req->buf + length;
|
|
|
-
|
|
|
- buf[0] = p0;
|
|
|
- buf[1] = p1;
|
|
|
- buf[2] = p2;
|
|
|
- buf[3] = p3;
|
|
|
- req->length = length + 4;
|
|
|
-}
|
|
|
-
|
|
|
/*
|
|
|
* Converts MIDI commands to USB MIDI packets.
|
|
|
*/
|
|
|
static void f_midi_transmit_byte(struct usb_request *req,
|
|
|
struct gmidi_in_port *port, uint8_t b)
|
|
|
{
|
|
|
- uint8_t p0 = port->cable << 4;
|
|
|
+ uint8_t p[4] = { port->cable << 4, 0, 0, 0 };
|
|
|
+ uint8_t next_state = STATE_INITIAL;
|
|
|
+
|
|
|
+ switch (b) {
|
|
|
+ case 0xf8 ... 0xff:
|
|
|
+ /* System Real-Time Messages */
|
|
|
+ p[0] |= 0x0f;
|
|
|
+ p[1] = b;
|
|
|
+ next_state = port->state;
|
|
|
+ port->state = STATE_REAL_TIME;
|
|
|
+ break;
|
|
|
+
|
|
|
+ case 0xf7:
|
|
|
+ /* End of SysEx */
|
|
|
+ switch (port->state) {
|
|
|
+ case STATE_SYSEX_0:
|
|
|
+ p[0] |= 0x05;
|
|
|
+ p[1] = 0xf7;
|
|
|
+ next_state = STATE_FINISHED;
|
|
|
+ break;
|
|
|
+ case STATE_SYSEX_1:
|
|
|
+ p[0] |= 0x06;
|
|
|
+ p[1] = port->data[0];
|
|
|
+ p[2] = 0xf7;
|
|
|
+ next_state = STATE_FINISHED;
|
|
|
+ break;
|
|
|
+ case STATE_SYSEX_2:
|
|
|
+ p[0] |= 0x07;
|
|
|
+ p[1] = port->data[0];
|
|
|
+ p[2] = port->data[1];
|
|
|
+ p[3] = 0xf7;
|
|
|
+ next_state = STATE_FINISHED;
|
|
|
+ break;
|
|
|
+ default:
|
|
|
+ /* Ignore byte */
|
|
|
+ next_state = port->state;
|
|
|
+ port->state = STATE_INITIAL;
|
|
|
+ }
|
|
|
+ break;
|
|
|
|
|
|
- if (b >= 0xf8) {
|
|
|
- f_midi_transmit_packet(req, p0 | 0x0f, b, 0, 0);
|
|
|
- } else if (b >= 0xf0) {
|
|
|
+ case 0xf0 ... 0xf6:
|
|
|
+ /* System Common Messages */
|
|
|
+ port->data[0] = port->data[1] = 0;
|
|
|
+ port->state = STATE_INITIAL;
|
|
|
switch (b) {
|
|
|
case 0xf0:
|
|
|
port->data[0] = b;
|
|
|
- port->state = STATE_SYSEX_1;
|
|
|
+ port->data[1] = 0;
|
|
|
+ next_state = STATE_SYSEX_1;
|
|
|
break;
|
|
|
case 0xf1:
|
|
|
case 0xf3:
|
|
|
port->data[0] = b;
|
|
|
- port->state = STATE_1PARAM;
|
|
|
+ next_state = STATE_1PARAM;
|
|
|
break;
|
|
|
case 0xf2:
|
|
|
port->data[0] = b;
|
|
|
- port->state = STATE_2PARAM_1;
|
|
|
+ next_state = STATE_2PARAM_1;
|
|
|
break;
|
|
|
case 0xf4:
|
|
|
case 0xf5:
|
|
|
- port->state = STATE_UNKNOWN;
|
|
|
+ next_state = STATE_INITIAL;
|
|
|
break;
|
|
|
case 0xf6:
|
|
|
- f_midi_transmit_packet(req, p0 | 0x05, 0xf6, 0, 0);
|
|
|
- port->state = STATE_UNKNOWN;
|
|
|
- break;
|
|
|
- case 0xf7:
|
|
|
- switch (port->state) {
|
|
|
- case STATE_SYSEX_0:
|
|
|
- f_midi_transmit_packet(req,
|
|
|
- p0 | 0x05, 0xf7, 0, 0);
|
|
|
- break;
|
|
|
- case STATE_SYSEX_1:
|
|
|
- f_midi_transmit_packet(req,
|
|
|
- p0 | 0x06, port->data[0], 0xf7, 0);
|
|
|
- break;
|
|
|
- case STATE_SYSEX_2:
|
|
|
- f_midi_transmit_packet(req,
|
|
|
- p0 | 0x07, port->data[0],
|
|
|
- port->data[1], 0xf7);
|
|
|
- break;
|
|
|
- }
|
|
|
- port->state = STATE_UNKNOWN;
|
|
|
+ p[0] |= 0x05;
|
|
|
+ p[1] = 0xf6;
|
|
|
+ next_state = STATE_FINISHED;
|
|
|
break;
|
|
|
}
|
|
|
- } else if (b >= 0x80) {
|
|
|
+ break;
|
|
|
+
|
|
|
+ case 0x80 ... 0xef:
|
|
|
+ /*
|
|
|
+ * Channel Voice Messages, Channel Mode Messages
|
|
|
+ * and Control Change Messages.
|
|
|
+ */
|
|
|
port->data[0] = b;
|
|
|
+ port->data[1] = 0;
|
|
|
+ port->state = STATE_INITIAL;
|
|
|
if (b >= 0xc0 && b <= 0xdf)
|
|
|
- port->state = STATE_1PARAM;
|
|
|
+ next_state = STATE_1PARAM;
|
|
|
else
|
|
|
- port->state = STATE_2PARAM_1;
|
|
|
- } else { /* b < 0x80 */
|
|
|
+ next_state = STATE_2PARAM_1;
|
|
|
+ break;
|
|
|
+
|
|
|
+ case 0x00 ... 0x7f:
|
|
|
+ /* Message parameters */
|
|
|
switch (port->state) {
|
|
|
case STATE_1PARAM:
|
|
|
- if (port->data[0] < 0xf0) {
|
|
|
- p0 |= port->data[0] >> 4;
|
|
|
- } else {
|
|
|
- p0 |= 0x02;
|
|
|
- port->state = STATE_UNKNOWN;
|
|
|
- }
|
|
|
- f_midi_transmit_packet(req, p0, port->data[0], b, 0);
|
|
|
+ if (port->data[0] < 0xf0)
|
|
|
+ p[0] |= port->data[0] >> 4;
|
|
|
+ else
|
|
|
+ p[0] |= 0x02;
|
|
|
+
|
|
|
+ p[1] = port->data[0];
|
|
|
+ p[2] = b;
|
|
|
+ /* This is to allow Running State Messages */
|
|
|
+ next_state = STATE_1PARAM;
|
|
|
break;
|
|
|
case STATE_2PARAM_1:
|
|
|
port->data[1] = b;
|
|
|
- port->state = STATE_2PARAM_2;
|
|
|
+ next_state = STATE_2PARAM_2;
|
|
|
break;
|
|
|
case STATE_2PARAM_2:
|
|
|
- if (port->data[0] < 0xf0) {
|
|
|
- p0 |= port->data[0] >> 4;
|
|
|
- port->state = STATE_2PARAM_1;
|
|
|
- } else {
|
|
|
- p0 |= 0x03;
|
|
|
- port->state = STATE_UNKNOWN;
|
|
|
- }
|
|
|
- f_midi_transmit_packet(req,
|
|
|
- p0, port->data[0], port->data[1], b);
|
|
|
+ if (port->data[0] < 0xf0)
|
|
|
+ p[0] |= port->data[0] >> 4;
|
|
|
+ else
|
|
|
+ p[0] |= 0x03;
|
|
|
+
|
|
|
+ p[1] = port->data[0];
|
|
|
+ p[2] = port->data[1];
|
|
|
+ p[3] = b;
|
|
|
+ /* This is to allow Running State Messages */
|
|
|
+ next_state = STATE_2PARAM_1;
|
|
|
break;
|
|
|
case STATE_SYSEX_0:
|
|
|
port->data[0] = b;
|
|
|
- port->state = STATE_SYSEX_1;
|
|
|
+ next_state = STATE_SYSEX_1;
|
|
|
break;
|
|
|
case STATE_SYSEX_1:
|
|
|
port->data[1] = b;
|
|
|
- port->state = STATE_SYSEX_2;
|
|
|
+ next_state = STATE_SYSEX_2;
|
|
|
break;
|
|
|
case STATE_SYSEX_2:
|
|
|
- f_midi_transmit_packet(req,
|
|
|
- p0 | 0x04, port->data[0], port->data[1], b);
|
|
|
- port->state = STATE_SYSEX_0;
|
|
|
+ p[0] |= 0x04;
|
|
|
+ p[1] = port->data[0];
|
|
|
+ p[2] = port->data[1];
|
|
|
+ p[3] = b;
|
|
|
+ next_state = STATE_SYSEX_0;
|
|
|
break;
|
|
|
}
|
|
|
+ break;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* States where we have to write into the USB request */
|
|
|
+ if (next_state == STATE_FINISHED ||
|
|
|
+ port->state == STATE_SYSEX_2 ||
|
|
|
+ port->state == STATE_1PARAM ||
|
|
|
+ port->state == STATE_2PARAM_2 ||
|
|
|
+ port->state == STATE_REAL_TIME) {
|
|
|
+
|
|
|
+ unsigned int length = req->length;
|
|
|
+ u8 *buf = (u8 *)req->buf + length;
|
|
|
+
|
|
|
+ memcpy(buf, p, sizeof(p));
|
|
|
+ req->length = length + sizeof(p);
|
|
|
+
|
|
|
+ if (next_state == STATE_FINISHED) {
|
|
|
+ next_state = STATE_INITIAL;
|
|
|
+ port->data[0] = port->data[1] = 0;
|
|
|
+ }
|
|
|
}
|
|
|
+
|
|
|
+ port->state = next_state;
|
|
|
}
|
|
|
|
|
|
static void f_midi_drop_out_substreams(struct f_midi *midi)
|
|
@@ -641,7 +695,7 @@ static int f_midi_in_open(struct snd_rawmidi_substream *substream)
|
|
|
VDBG(midi, "%s()\n", __func__);
|
|
|
port = midi->in_ports_array + substream->number;
|
|
|
port->substream = substream;
|
|
|
- port->state = STATE_UNKNOWN;
|
|
|
+ port->state = STATE_INITIAL;
|
|
|
return 0;
|
|
|
}
|
|
|
|