diff --git a/TODO b/TODO index 7c0a0b3..e3d9e64 100644 --- a/TODO +++ b/TODO @@ -6,21 +6,4 @@ -effects level -handling presets (loading, saving, exporting to xml patches) -buildsystem --start gui with proper values - To do so we need to figure out reply formatting of command querying preset. - amidi --port=hw:1,0,0 --send-hex F0 00 00 10 00 5E 02 2a 00 04 00 62 F7 --receive=preset - will create file named preset (give a while for it, and then hit ctrl+c) - this file should have around 440 bytes (depends on actual preset) - 0x21 byte holds amount of options - from 0x22 byte starts effects configuration which is: - -2 bytes for ID - -1 byte for position - -1 to 3 bytes for value - Each 8th byte (beginning from 0x27) seems to be status byte which describes - whether or not we shall add 0x80 to ID or value and whether or not value - will be multibyte. So far I couldn't figure the exact meaning of those bytes. - To check you can download some patch from DigiTech Sound Community, apply - it to your device, and then do this amidi command. - Open resulting file in hex editor, and open patch file in text editor. - Every ID, position and value found in patch will appear in the binary file. -fix expression pedal settings (possible types depend on active effects) diff --git a/gdigi.c b/gdigi.c index 6a802db..08ae825 100644 --- a/gdigi.c +++ b/gdigi.c @@ -22,6 +22,10 @@ #include "gdigi.h" #include "gui.h" +static u_char device_id = 0x7F; +static u_char family_id = 0x7F; +static u_char product_id = 0x7F; + static snd_rawmidi_t *output = NULL; static snd_rawmidi_t *input = NULL; static char *device = "hw:1,0,0"; @@ -47,6 +51,18 @@ char calculate_checksum(gchar *array, int length, int check) return checksum; } +static char calc_checksum(gchar *array, gint length) +{ + int x; + int checksum = 0; + + for (x = 0; x */ int err; int npfds; + gboolean stop = FALSE; struct pollfd *pfds; GString *string = NULL; @@ -118,6 +135,7 @@ GString* read_data() break; if (!(revents & POLLIN)) continue; + err = snd_rawmidi_read(input, buf, sizeof(buf)); if (err == -EAGAIN) continue; @@ -125,11 +143,15 @@ GString* read_data() g_error("cannot read: %s", snd_strerror(err)); break; } + length = 0; for (i = 0; i < err; ++i) if (buf[i] != 0xFE) // ignore active sensing buf[length++] = buf[i]; + if ((u_char)buf[length-1] == 0xF7) + stop = TRUE; + if (length != 0) { if (string == NULL) { string = g_string_new_len(buf, length); @@ -137,9 +159,40 @@ GString* read_data() string = g_string_append_len(string, buf, length); } } - } while (err != 0); + } while ((err != 0) && (stop == FALSE)); - return string; + return string; +} + +static void clear_midi_in_buffer() +{ + GString *str; + + do { + str = read_data(); + } while (str != NULL); +} + +static void send_message(gint procedure, gchar *data, gint len) +{ + GString *msg = g_string_new_len("\xF0" /* SysEx status byte */ + "\x00\x00\x10", /* Manufacturer ID */ + 4); + g_string_append_printf(msg, + "%c%c%c" /* device, family, product ID */ + "%c", /* procedure */ + device_id, family_id, product_id, + procedure); + + if (len > 0) + g_string_append_len(msg, data, len); + + g_string_append_printf(msg, "%c\xF7", + calc_checksum(&msg->str[1], msg->len - 1)); + + send_data(msg->str, msg->len); + + g_string_free(msg, TRUE); } /* @@ -310,9 +363,7 @@ GStrv query_preset_names(guint bank) gchar **str_array = NULL; /* clear MIDI IN buffer */ - data = read_data(); - if (data != NULL) - g_string_free(data, TRUE); + clear_midi_in_buffer(); /* query user preset names */ char command[] = {0xF0, 0x00, 0x00, 0x10, 0x00, 0x5E, 0x02, 0x21, 0x00, 0x00 /* bank */, 0x00 /* checksum */, 0xF7}; @@ -368,6 +419,75 @@ GStrv query_preset_names(guint bank) return str_array; } +static void unpack_message(GString *msg) +{ + int offset; + int x; + int i; + u_char status; + gboolean finish = FALSE; + + offset = 9; + x = 0; + i = 8; + + do { + printf("status byte [%d] 0x%02x\n", offset-1, msg->str[offset-1]); + status = (u_char)msg->str[offset-1]; + for (x=0; x<7; x++) { + if ((u_char)msg->str[offset+x] == 0xF7) { + msg->str[i] = 0xF7; + finish = TRUE; + } + + msg->str[i] = (((status << (x+1)) & 0x80) | (u_char)msg->str[x+offset]); + printf("merging byte [%d] with byte [%d] MSB is %d\n", + i, x+offset, (((status << (x+1)) & 0x80) >> 7)); + i++; + } + offset += 8; + } while (finish == FALSE && offset < msg->len); +} + +GString *get_current_preset() +{ + GString *data = NULL; + + /* clear MIDI IN buffer */ + clear_midi_in_buffer(); + + send_message(REQUEST_PRESET, "\x00\x04\x00", 3); + + /* read reply */ + data = read_data(); + g_string_free(data, TRUE); + + data = read_data(); + + unpack_message(data); + + return data; +} + +static gboolean request_who_am_i(u_char *device_id, u_char *family_id, + u_char *product_id) +{ + send_message(REQUEST_WHO_AM_I, NULL, 0); + + GString *data = read_data(); + if (data != NULL) { + if ((data->len == 15) && (data->str[7] == RECEIVE_WHO_AM_I)) { + *device_id = data->str[9]; + *family_id = data->str[10]; + *product_id = data->str[11]; + g_string_free(data, TRUE); + return TRUE; + } + g_string_free(data, TRUE); + } + return FALSE; +} + static GOptionEntry options[] = { {"device", 'd', G_OPTION_FLAG_IN_MAIN, G_OPTION_ARG_STRING, &device, "MIDI device port to use", NULL}, {NULL} @@ -390,16 +510,14 @@ int main(int argc, char *argv[]) { g_option_context_free(context); if (open_device() == TRUE) { - GtkWidget *msg = gtk_message_dialog_new(NULL, GTK_DIALOG_MODAL, - GTK_MESSAGE_ERROR, - GTK_BUTTONS_OK, - "Failed to open MIDI device"); - - gtk_dialog_run(GTK_DIALOG(msg)); - gtk_widget_destroy(msg); + show_error_message(NULL, "Failed to open MIDI device"); } else { - create_window(); - gtk_main(); + if (request_who_am_i(&device_id, &family_id, &product_id) == FALSE) { + show_error_message(NULL, "No suitable reply from device - is it connected?"); + } else { + create_window(); + gtk_main(); + } } if (output != NULL) diff --git a/gdigi.h b/gdigi.h index d860747..42dd81b 100644 --- a/gdigi.h +++ b/gdigi.h @@ -489,15 +489,51 @@ enum { #define USB_AUDIO_PLAYBACK_MIX 12297 #define USB_AUDIO_LEVEL 12307 -typedef enum { - PRESETS_SYSTEM = 0, - PRESETS_USER = 1 -} PresetBank; +enum { + PRESETS_SYSTEM = 0, + PRESETS_USER = 1, + PRESETS_ARTIST = 2, + + /* Version 1 and later */ + PRESETS_MEDIA_CARD = 3, + PRESETS_EDIT_BUFFER = 4, /* Current preset edit buffer, index must be 0 */ + PRESETS_FACTORY2 = 5, + + /* Version 2 and later */ + PRESETS_EXTERNAL = 6, +}; + +enum { + REQUEST_WHO_AM_I = 0x01, + RECEIVE_WHO_AM_I = 0x02, + + REQUEST_DEVICE_CONFIGURATION = 0x08, + RECEIVE_DEVICE_CONFIGURATION = 0x09, + + REQUEST_GLOBAL_PARAMETERS = 0x10, + RECEIVE_GLOBAL_PARAMETERS = 0x11, + + REQUEST_BULK_DUMP = 0x18, + RECEIVE_BULK_DUMP_START = 0x19, + RECEIVE_BULK_DUMP_END = 0x1B, + + REQUEST_PRESET_NAMES = 0x21, + RECEIVE_PRESET_NAMES = 0x22, + + REQUEST_PRESET_NAME = 0x28, + RECEIVE_PRESET_NAME = 0x29, + + REQUEST_PRESET = 0x2A, + RECEIVE_PRESET_START = 0x2B, + RECEIVE_PRESET_END = 0x2C, + RECEIVE_PRESET_PARAMETERS = 0x2D, +}; void set_option(guint id, guint position, guint value); void switch_preset(guint bank, guint x); void store_preset_name(int x, const gchar *name); void set_preset_level(int level); -GStrv query_preset_names(PresetBank bank); +GStrv query_preset_names(guint bank); +GString *get_current_preset(); #endif /* GDIGI_H */ diff --git a/gui.c b/gui.c index 74e3d6c..1329a05 100644 --- a/gui.c +++ b/gui.c @@ -23,6 +23,21 @@ extern EffectList effects[]; extern int n_effects; +static gboolean allow_send = FALSE; + +void show_error_message(GtkWidget *parent, gchar *message) +{ + g_return_if_fail(message != NULL); + + GtkWidget *msg = gtk_message_dialog_new(NULL, GTK_DIALOG_MODAL, + GTK_MESSAGE_ERROR, + GTK_BUTTONS_OK, + message); + + gtk_dialog_run(GTK_DIALOG(msg)); + gtk_widget_destroy(msg); +} + typedef struct { GtkWidget *widget; gint id; @@ -37,16 +52,20 @@ void value_changed_option_cb(GtkSpinButton *spinbutton, EffectSettings *setting) { g_return_if_fail(setting != NULL); - int val = gtk_spin_button_get_value_as_int(spinbutton); - set_option(setting->option, setting->position, val); + if (allow_send) { + gint val = gtk_spin_button_get_value_as_int(spinbutton); + set_option(setting->option, setting->position, val); + } } void toggled_cb(GtkToggleButton *button, Effect *effect) { g_return_if_fail(effect != NULL); - guint val = gtk_toggle_button_get_active(button); - set_option(effect->option, effect->position, val); + if (allow_send) { + guint val = gtk_toggle_button_get_active(button); + set_option(effect->option, effect->position, val); + } } static void widget_list_add(GList **list, GtkWidget *widget, gint id, gint position, gint value, gint x) @@ -73,7 +92,7 @@ GtkWidget *create_table(GList **list, EffectSettings *settings, gint amt) for (x = 0; xoption, settings->position, settings->id); child = g_object_get_data(G_OBJECT(widget), "active_child"); @@ -349,6 +368,8 @@ static void apply_widget_setting(WidgetListElem *el, SettingParam *param) static void apply_preset_to_gui(GList *list, Preset *preset) { + allow_send = FALSE; + GList *iter = preset->params; while (iter) { SettingParam *param = iter->data; @@ -357,6 +378,8 @@ static void apply_preset_to_gui(GList *list, Preset *preset) if (param != NULL) g_list_foreach(list, (GFunc)apply_widget_setting, param); } + + allow_send = TRUE; } static void action_store_cb(GtkAction *action) @@ -547,6 +570,15 @@ static void add_menubar(GList **list, GtkWidget *window, GtkWidget *vbox) g_object_unref(ui); } +static void apply_current_preset(GList *list) +{ + GString *msg = get_current_preset(); + Preset *preset = create_preset_from_data(msg); + g_string_free(msg, TRUE); + apply_preset_to_gui(list, preset); + preset_free(preset); +} + void create_window() { GtkWidget *window; @@ -587,6 +619,7 @@ void create_window() gtk_box_pack_start(GTK_BOX(hbox), widget, TRUE, TRUE, 2); } + apply_current_preset(list); gtk_widget_show_all(window); g_signal_connect(G_OBJECT(window), "delete_event", G_CALLBACK(gtk_main_quit), NULL); diff --git a/gui.h b/gui.h index c925d28..583e846 100644 --- a/gui.h +++ b/gui.h @@ -17,6 +17,7 @@ #ifndef GDIGI_GUI_H #define GDIGI_GUI_H +void show_error_message(GtkWidget *parent, gchar *message); void create_window(); #endif /* GDIGI_GUI_H */ diff --git a/preset.c b/preset.c index 7cbedc6..72c33bf 100644 --- a/preset.c +++ b/preset.c @@ -165,6 +165,54 @@ Preset *create_preset_from_xml_file(gchar *filename) return preset; } +Preset *create_preset_from_data(GString *data) +{ + gint total; + gint n; + gint id; + gint position; + guint value; + gint x; + gint tmp; + + x = 0x09; + n = 0; + total = (u_char)data->str[x]; + x++; + + Preset *preset = g_malloc(sizeof(Preset)); + preset->name = NULL; /* TODO */ + preset->params = NULL; + + do { + id = ((u_char)data->str[x] << 8) | (u_char)data->str[x+1]; + position = (u_char)data->str[x+2]; + x+=3; + value = data->str[x]; + x++; + if (value > 0x80) { + tmp = value & 0x7F; + value = 0; + gint i; + for (i=0; istr[x+i] << (8*(tmp-i-1))); + } + x+=tmp; + } + n++; + SettingParam *param = (SettingParam *)g_malloc(sizeof(SettingParam)); + param->id = id; + param->position = position; + param->value = value; + preset->params = g_list_prepend(preset->params, param); + g_message("%d ID %d Position %d Value %d", n, id, position, value); + } while ((x < data->len) && nparams = g_list_reverse(preset->params); + + return preset; +} + void preset_free(Preset *preset) { g_return_if_fail(preset != NULL); diff --git a/preset.h b/preset.h index fa33b1d..241f284 100644 --- a/preset.h +++ b/preset.h @@ -31,6 +31,7 @@ typedef struct { } Preset; Preset *create_preset_from_xml_file(gchar *filename); +Preset *create_preset_from_data(GString *data); void preset_free(Preset *preset); #endif /* GDIGI_PRESET_H */