read MIDI IN messages in new thread

This commit is contained in:
Tomasz Moń
2009-04-30 10:32:24 +02:00
parent f1359d18e4
commit 6512696240
3 changed files with 139 additions and 44 deletions

View File

@@ -1,7 +1,7 @@
CC = gcc CC = gcc
CFLAGS = `pkg-config --cflags glib-2.0 gio-2.0 gtk+-2.0` -Wall -g -ansi -std=c99 CFLAGS = `pkg-config --cflags glib-2.0 gio-2.0 gtk+-2.0` -Wall -g -ansi -std=c99
OFLAG = -o OFLAG = -o
LIBS = `pkg-config --libs glib-2.0 gio-2.0 gtk+-2.0 alsa` -lexpat LIBS = `pkg-config --libs glib-2.0 gio-2.0 gtk+-2.0 gthread-2.0 alsa` -lexpat
.SUFFIXES : .o .c .SUFFIXES : .o .c
.c.o : .c.o :

3
TODO
View File

@@ -3,5 +3,6 @@
-handling presets (saving, exporting to xml patches) -handling presets (saving, exporting to xml patches)
-buildsystem (install knob.png to share dir, don't use inline knob pixbuf) -buildsystem (install knob.png to share dir, don't use inline knob pixbuf)
-add expression pedal settings to gui -add expression pedal settings to gui
-read asynchronously from MIDI IN -various fixes to MIDI IN messages handling
-guess device port when user doesn't explicitly provide it (don't use hardcoded "hw:1,0,0") -guess device port when user doesn't explicitly provide it (don't use hardcoded "hw:1,0,0")
-optimizations

178
gdigi.c
View File

@@ -30,6 +30,9 @@ static snd_rawmidi_t *output = NULL;
static snd_rawmidi_t *input = NULL; static snd_rawmidi_t *input = NULL;
static char *device = "hw:1,0,0"; static char *device = "hw:1,0,0";
static GQueue *message_queue = NULL;
static GMutex *message_queue_mutex = NULL;
/** /**
* \param array data to calculate checksum * \param array data to calculate checksum
* \param length data length * \param length data length
@@ -169,17 +172,59 @@ static void unpack_message(GString *msg)
} }
/** /**
* Reads data from MIDI IN. This function uses global input variable. * \param msg SysEx message
* *
* \return GString containing data, or NULL when no data was read. * Checks message ID.
*
* \return MessageID, or -1 on error.
**/ **/
GString* read_data() static MessageID get_message_id(GString *msg)
{
/** \todo check if msg is valid SysEx message */
g_return_val_if_fail(msg != NULL, -1);
g_return_val_if_fail(msg->str != NULL, -1);
if (msg->len > 7) {
return (unsigned char)msg->str[7];
}
return -1;
}
void push_message(GString *msg)
{
if (((unsigned char)msg->str[0] == 0xF0) && ((unsigned char)msg->str[msg->len-1] == 0xF7))
g_message("Pushing correct message!");
else
g_warning("Pushing incorrect message!");
int x;
for (x = 0; x<msg->len; x++)
printf("%02x ", (unsigned char)msg->str[x]);
printf("\n");
if (get_message_id(msg) == ACK) {
g_message("Received ACK");
g_string_free(msg, TRUE);
return;
}
if (get_message_id(msg) == NACK) {
g_message("Received NACK");
g_string_free(msg, TRUE);
return;
}
g_mutex_lock(message_queue_mutex);
g_queue_push_tail(message_queue, msg);
g_mutex_unlock(message_queue_mutex);
}
gpointer read_data_thread(gboolean *stop)
{ {
/* This is mostly taken straight from alsa-utils-1.0.19 amidi/amidi.c /* This is mostly taken straight from alsa-utils-1.0.19 amidi/amidi.c
by Clemens Ladisch <clemens@ladisch.de> */ by Clemens Ladisch <clemens@ladisch.de> */
int err; int err;
int npfds; int npfds;
gboolean stop = FALSE;
struct pollfd *pfds; struct pollfd *pfds;
GString *string = NULL; GString *string = NULL;
@@ -188,10 +233,14 @@ GString* read_data()
snd_rawmidi_poll_descriptors(input, pfds, npfds); snd_rawmidi_poll_descriptors(input, pfds, npfds);
do { do {
char buf[20]; unsigned char buf[256];
int i, length; int i, length;
unsigned short revents; unsigned short revents;
/* SysEx messages can't contain bytes with 8th bit set.
memset our buffer to 0xFF, so if for some reason we'll get out of reply bounds, we'll catch it */
memset(buf, sizeof(buf), 0xFF);
err = poll(pfds, npfds, 200); err = poll(pfds, npfds, 200);
if (err < 0 && errno == EINTR) if (err < 0 && errno == EINTR)
break; break;
@@ -221,19 +270,39 @@ GString* read_data()
if (buf[i] != 0xFE) /* ignore active sensing */ if (buf[i] != 0xFE) /* ignore active sensing */
buf[length++] = buf[i]; buf[length++] = buf[i];
if ((unsigned char)buf[length-1] == 0xF7) i = 0;
stop = TRUE;
if (length != 0) { while (i < length) {
if (string == NULL) { int pos;
string = g_string_new_len(buf, length); int bytes;
} else {
string = g_string_append_len(string, buf, length); pos = i;
for (bytes = 0; (bytes<length-i) && (buf[i+bytes] != 0xF7); bytes++);
if (buf[i+bytes] == 0xF7) bytes++;
i += bytes;
if (string == NULL)
string = g_string_new_len((gchar*)&buf[pos], bytes);
else
g_string_append_len(string, (gchar*)&buf[pos], bytes);
if ((unsigned char)string->str[string->len-1] == 0xF7) {
/* push message on stack */
push_message(string);
string = NULL;
} }
} }
} while ((err != 0) && (stop == FALSE)); } while (*stop == FALSE);
return string; if (string) {
g_string_free(string, TRUE);
string = NULL;
}
return NULL;
} }
/** /**
@@ -268,40 +337,33 @@ void send_message(gint procedure, gchar *data, gint len)
g_string_free(msg, TRUE); g_string_free(msg, TRUE);
} }
/**
* \param msg SysEx message
*
* Checks message ID.
*
* \return MessageID, or -1 on error.
**/
static MessageID get_message_id(GString *msg)
{
/** \todo check if msg is valid SysEx message */
g_return_val_if_fail(msg != NULL, -1);
if (msg->len > 7) {
return (unsigned char)msg->str[7];
}
return -1;
}
/** /**
* \param id MessageID of requested message * \param id MessageID of requested message
* *
* Reads data from MIDI IN until message with matching id is found. * Reads data from message queue until message with matching id is found.
* *
* \return GString containing unpacked message. * \return GString containing unpacked message.
**/ **/
GString *get_message_by_id(MessageID id) GString *get_message_by_id(MessageID id)
{ {
GString *data = NULL; GString *data = NULL;
guint x, len;
gboolean found = FALSE;
do { do {
if (data) g_mutex_lock(message_queue_mutex);
g_string_free(data, TRUE);
data = read_data(); len = g_queue_get_length(message_queue);
} while (get_message_id(data) != id); for (x = 0; x<len; x++) {
data = g_queue_peek_nth(message_queue, x);
if (get_message_id(data) == id) {
found = TRUE;
g_queue_pop_nth(message_queue, x);
break;
}
}
g_mutex_unlock(message_queue_mutex);
} while (found == FALSE);
unpack_message(data); unpack_message(data);
@@ -484,12 +546,12 @@ static gboolean request_who_am_i(unsigned char *device_id, unsigned char *family
{ {
send_message(REQUEST_WHO_AM_I, NULL, 0); send_message(REQUEST_WHO_AM_I, NULL, 0);
GString *data = read_data(); GString *data = get_message_by_id(RECEIVE_WHO_AM_I);
if (data != NULL) { if (data != NULL) {
if ((data->len == 15) && (data->str[7] == RECEIVE_WHO_AM_I)) { if (data->len == 14) {
*device_id = data->str[9]; *device_id = data->str[8];
*family_id = data->str[10]; *family_id = data->str[9];
*product_id = data->str[11]; *product_id = data->str[10];
g_string_free(data, TRUE); g_string_free(data, TRUE);
return TRUE; return TRUE;
} }
@@ -544,9 +606,19 @@ static GOptionEntry options[] = {
#endif /* DOXYGEN_SHOULD_SKIP_THIS */ #endif /* DOXYGEN_SHOULD_SKIP_THIS */
static void message_queue_free_func(GString *msg, gpointer user_data)
{
g_string_free(msg, TRUE);
}
int main(int argc, char *argv[]) { int main(int argc, char *argv[]) {
GError *error = NULL; GError *error = NULL;
GOptionContext *context; GOptionContext *context;
static gboolean stop_read_thread = FALSE;
GThread *read_thread = NULL;
g_thread_init(NULL);
context = g_option_context_new(NULL); context = g_option_context_new(NULL);
g_option_context_add_main_entries(context, options, NULL); g_option_context_add_main_entries(context, options, NULL);
g_option_context_add_group(context, gtk_get_option_group(TRUE)); g_option_context_add_group(context, gtk_get_option_group(TRUE));
@@ -563,6 +635,12 @@ int main(int argc, char *argv[]) {
if (open_device() == TRUE) { if (open_device() == TRUE) {
show_error_message(NULL, "Failed to open MIDI device"); show_error_message(NULL, "Failed to open MIDI device");
} else { } else {
message_queue = g_queue_new();
message_queue_mutex = g_mutex_new();
read_thread = g_thread_create((GThreadFunc)read_data_thread,
&stop_read_thread,
TRUE, NULL);
if (request_who_am_i(&device_id, &family_id, &product_id) == FALSE) { if (request_who_am_i(&device_id, &family_id, &product_id) == FALSE) {
show_error_message(NULL, "No suitable reply from device"); show_error_message(NULL, "No suitable reply from device");
} else { } else {
@@ -583,6 +661,22 @@ int main(int argc, char *argv[]) {
} }
} }
if (read_thread != NULL) {
stop_read_thread = TRUE;
g_thread_join(read_thread);
}
if (message_queue_mutex != NULL) {
g_mutex_free(message_queue_mutex);
}
if (message_queue != NULL) {
g_message("%d unread messages in queue",
g_queue_get_length(message_queue));
g_queue_foreach(message_queue, (GFunc) message_queue_free_func, NULL);
g_queue_free(message_queue);
}
if (output != NULL) { if (output != NULL) {
snd_rawmidi_drain(output); snd_rawmidi_drain(output);
snd_rawmidi_close(output); snd_rawmidi_close(output);