/* * Copyright (c) 2009 Tomasz Moń * * 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; under version 3 of the License. * * 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 . */ #include #include #include #include #include #include #include "gdigi.h" #include "gdigi_xml.h" #include "gui.h" static unsigned char device_id = 0x7F; static unsigned char family_id = 0x7F; unsigned char product_id = 0x7F; static snd_rawmidi_t *output = NULL; static snd_rawmidi_t *input = NULL; static char *device_port = NULL; static GQueue *message_queue = NULL; static GMutex *message_queue_mutex = NULL; static GCond *message_queue_cond = NULL; static guint DebugFlags; gboolean debug_flag_is_set (debug_flags_t flags) { if (DebugFlags & flags) { return TRUE; } return FALSE; } gboolean set_debug_flags (const gchar *option_name, const gchar *value, gpointer data, GError **error) { if (strchr(value, 'd')) { DebugFlags |= DEBUG_MSG2DEV; } if (strchr(value, 'h')) { DebugFlags |= DEBUG_MSG2HOST; } if (strchr(value, 'm')) { DebugFlags |= DEBUG_MSG2DEV|DEBUG_MSG2HOST|DEBUG_GROUP; } if (strchr(value, 's')) { DebugFlags |= DEBUG_STARTUP; } if (strchr(value, 'H')) { DebugFlags |= DEBUG_HEX; } if (strchr(value, 'g')) { DebugFlags |= DEBUG_GROUP; } if (strchr(value, 'x')) { DebugFlags |= DEBUG_XML; } if (strchr(value, 'v')) { DebugFlags |= DEBUG_VERBOSE; } if (strchr(value, 'a')) { DebugFlags = -1; } return TRUE; } void debug_msg (debug_flags_t flags, char *fmt, ...) { char buf[1024]; if (flags & DebugFlags) { va_list ap; va_start(ap, fmt); vsnprintf(buf, 1024, fmt, ap); va_end(ap); fprintf(stderr, "%s\n", buf); } } /* * Format a value according to the xml setting. * Returns an allocated buffer that must be freed by the caller. */ GString * format_value (XmlSettings *xml, guint value) { GString *buf = g_string_sized_new(1); EffectValues *values = NULL; ValueType vtype; gchar *suffix = ""; gdouble step = 1.0; gint offset = 0; gboolean decimal = FALSE; values = xml->values; vtype = values->type; while ((vtype & VALUE_TYPE_EXTRA) && value_is_extra(values, value)) { values = values->extra; vtype = values->type; } vtype &= ~VALUE_TYPE_EXTRA; if (vtype & VALUE_TYPE_OFFSET) { offset = values->offset; vtype &= ~VALUE_TYPE_OFFSET; } if (vtype & VALUE_TYPE_STEP) { step = values->step; vtype &= ~VALUE_TYPE_STEP; } if (vtype & VALUE_TYPE_SUFFIX) { suffix = values->suffix; vtype &= ~VALUE_TYPE_SUFFIX; } if (vtype & VALUE_TYPE_DECIMAL) { decimal = TRUE; vtype &= ~VALUE_TYPE_DECIMAL; } switch (vtype) { case VALUE_TYPE_LABEL: { char *textp = map_xml_value(xml, values, value); if (!textp) { g_warning("%s: Unable to map %s value %d for id %d position %d", __FUNCTION__, xml->label, value, xml->id, xml->position); textp = ""; } g_string_printf(buf, "%s", textp); break; } case VALUE_TYPE_PLAIN: { if (decimal) { double dvalue = ((gint)value + offset) * step; g_string_printf(buf, "%0.2f%s", dvalue, suffix); } else { gint ivalue = ((gint)value + offset) * step; g_string_printf(buf, "%d%s", ivalue, suffix); } break; } case VALUE_TYPE_NONE: g_string_printf(buf, "%s", ""); break; case VALUE_TYPE_POSID: g_string_printf(buf, "%d", value); break; default: g_warning("Unhandled value type %d", vtype); break; } return buf; } GString * format_ipv (guint id, guint pos, guint val) { GString *buf = g_string_sized_new(1); GString *vec_buf = g_string_sized_new(1); XmlSettings *xml = get_xml_settings(id, pos); GString *val_buf; if (!xml) { g_warning("Failed to find xml settings for position %d id %d.", pos, id); g_string_printf(buf, "%s", "error"); return buf; } val_buf = format_value(xml, val); g_string_printf(vec_buf, "(%d, %d, %d)", pos, id, val); g_string_printf(buf, "%-16s %s: %s: %s", vec_buf->str, get_position(pos), xml->label, val_buf->str); g_string_free(vec_buf, TRUE); g_string_free(val_buf, TRUE); return buf; } /** * Registers an error quark for gdigi if necessary. * * \return error quark used for gdigi errors **/ static GQuark gdigi_error_quark() { static GQuark quark = 0; if (quark == 0) { quark = g_quark_from_static_string("gdigi-error"); } return quark; } /** * \param array data to calculate checksum * \param length data length * * Calculates message checksum. * * \return calculated checksum. **/ static char calculate_checksum(gchar *array, gint length) { int x; int checksum = 0; for (x = 0; xstr[status_byte] = status; status = 0; status_byte = packed->len; g_string_append_c(packed, '\0'); } g_string_append_c(packed, (data[i] & 0x7F)); status |= (data[i] & 0x80) >> ((i%7) + 1); } packed->str[status_byte] = status; return packed; } static void message_free_func(GString *msg, gpointer user_data) { g_string_free(msg, TRUE); } /** * \param msg message to unpack * * Unpacks message data. This function modifies given GString. **/ static void unpack_message(GString *msg) { int offset; int x; int i; unsigned char status; unsigned char *str; gboolean stop = FALSE; g_return_if_fail(msg != NULL); g_return_if_fail(msg->len > 9); offset = 1; x = 0; i = 8; str = (unsigned char*)msg->str; do { offset += 8; status = str[offset-1]; for (x=0; x<7 && !stop; x++) { if (offset+x >= msg->len) { i++; stop = TRUE; break; } if (str[offset+x] == 0xF7) { if (x == 0) { str[i] = status; i++; } str[i] = 0xF7; i++; stop = TRUE; break; } str[i] = (((status << (x+1)) & 0x80) | str[x+offset]); i++; } } while (!stop); g_string_truncate(msg, i); } /** * \param msg SysEx message * * Checks message ID. * * \return MessageID, or -1 on error. **/ 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; } #define HEX_WIDTH 26 static gboolean modifier_linkable_list_request_pending = FALSE; void push_message(GString *msg) { MessageID msgid = get_message_id(msg); if (((unsigned char)msg->str[0] == 0xF0) && ((unsigned char)msg->str[msg->len-1] == 0xF7)) { debug_msg(DEBUG_VERBOSE, "Pushing correct message!"); } else { g_warning("Pushing incorrect message!"); } int x; if (debug_flag_is_set(DEBUG_HEX)) { for (x = 0; xlen; x++) { if (x && (x % HEX_WIDTH) == 0) { printf("\n"); } printf("%02x ", (unsigned char)msg->str[x]); } if (x % HEX_WIDTH) { printf("\n"); } } debug_msg(DEBUG_VERBOSE, "Received %s", get_message_name(msgid)); SettingParam *param; switch (msgid) { case ACK: g_string_free(msg, TRUE); return; case NACK: g_warning("Received NACK!"); g_string_free(msg, TRUE); return; case RECEIVE_PARAMETER_VALUE: { unpack_message(msg); param = setting_param_new_from_data(&msg->str[8], NULL); if (debug_flag_is_set(DEBUG_MSG2HOST)) { GString *ipv = format_ipv(param->id, param->position, param->value); debug_msg(DEBUG_MSG2HOST, "RECEIVE_PARAMETER_VALUE\n%s", ipv->str); g_string_free(ipv, TRUE); } GDK_THREADS_ENTER(); apply_setting_param_to_gui(param); GDK_THREADS_LEAVE(); setting_param_free(param); g_string_free(msg, TRUE); return; } case RECEIVE_DEVICE_NOTIFICATION: unpack_message(msg); unsigned char *str = (unsigned char*)msg->str; switch (str[8]) { case NOTIFY_PRESET_MOVED: if (str[11] == PRESETS_EDIT_BUFFER && str[12] == 0) { GDK_THREADS_ENTER(); g_timeout_add(0, apply_current_preset_to_gui, NULL); GDK_THREADS_LEAVE(); debug_msg(DEBUG_MSG2HOST, "RECEIVE_DEVICE_NOTIFICATION: Loaded preset " "%d from bank %d", str[10], str[9]); } else { debug_msg(DEBUG_MSG2HOST, "RECEIVE_DEVICE_NOTIFICATION: %d %d moved to " "%d %d", str[9], str[10], str[11], str[12]); } break; case NOTIFY_MODIFIER_GROUP_CHANGED: { int i; if (debug_flag_is_set(DEBUG_HEX)) { printf("\n"); for (i = 0; i < msg->len; i++) { printf(" %02x", (unsigned char) str[i]); } printf("\n"); } debug_msg(DEBUG_MSG2HOST, "NOTIFY_MODIFIER_GROUP_CHANGED: Modifier group " "id %d changed", (str[9] << 8) | (str[10])); if (!modifier_linkable_list_request_pending) { send_message(REQUEST_MODIFIER_LINKABLE_LIST, "\x00\x01", 2); modifier_linkable_list_request_pending = TRUE; } break; } default: g_warning("Received unhandled device notification 0x%x", str[11]); } g_string_free(msg, TRUE); return; case RECEIVE_GLOBAL_PARAMETERS: unpack_message(msg); gint tot, n, x; tot = (unsigned char)msg->str[9]; if (debug_flag_is_set(DEBUG_HEX)) { for (n = 0; n < msg->len; n++) { printf("%02x ",(unsigned char) msg->str[n]); } printf("\n"); } n = 0; x = 10; do { param = setting_param_new_from_data(&msg->str[x], &x); debug_msg(DEBUG_MSG2HOST, "RECEIVE_GLOBAL_PARAMETERS ID: %5d " "Position: %2.1d Value: %6.1d: %s", param->id, param->position, param->value, "XXX"); GDK_THREADS_ENTER(); apply_setting_param_to_gui(param); GDK_THREADS_LEAVE(); setting_param_free(param); } while ( (x < msg->len) && n < tot); g_string_free(msg, TRUE); return; case RECEIVE_MODIFIER_LINKABLE_LIST: modifier_linkable_list_request_pending = FALSE; unpack_message(msg); tot = (unsigned char)msg->str[9]; if (debug_flag_is_set(DEBUG_HEX)) { for (n = 0; n < msg->len; n++) { printf("%02x ",(unsigned char) msg->str[n]); } printf("\n"); } update_modifier_linkable_list(msg); g_string_free(msg, TRUE); GDK_THREADS_ENTER(); create_modifier_group(EXP_POSITION, EXP_ASSIGN1); create_modifier_group(LFO1_POSITION, LFO_TYPE); create_modifier_group(LFO2_POSITION, LFO_TYPE); GDK_THREADS_LEAVE(); return; default: g_mutex_lock(message_queue_mutex); g_queue_push_tail(message_queue, msg); g_cond_signal(message_queue_cond); g_mutex_unlock(message_queue_mutex); break; } } gpointer read_data_thread(gboolean *stop) { /* This is mostly taken straight from alsa-utils-1.0.19 amidi/amidi.c by Clemens Ladisch */ int err; int npfds; struct pollfd *pfds; GString *string = NULL; npfds = snd_rawmidi_poll_descriptors_count(input); pfds = alloca(npfds * sizeof(struct pollfd)); snd_rawmidi_poll_descriptors(input, pfds, npfds); do { unsigned char buf[256]; int i, length; 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, '\0', sizeof(buf)); err = poll(pfds, npfds, 200); if (err < 0 && errno == EINTR) break; if (err < 0) { g_error("poll failed: %s", strerror(errno)); break; } if ((err = snd_rawmidi_poll_descriptors_revents(input, pfds, npfds, &revents)) < 0) { g_error("cannot get poll events: %s", snd_strerror(errno)); break; } if (revents & (POLLERR | POLLHUP)) break; if (!(revents & POLLIN)) continue; err = snd_rawmidi_read(input, buf, sizeof(buf)); if (err == -EAGAIN) continue; if (err < 0) { g_error("cannot read: %s", snd_strerror(err)); break; } length = 0; for (i = 0; i < err; ++i) if ((unsigned char)buf[i] != 0xFE) /* ignore active sensing */ buf[length++] = buf[i]; i = 0; while (i < length) { int pos; int bytes; if (string == NULL) { while (buf[i] != 0xF0 && i < length) i++; } pos = i; for (bytes = 0; (bytesstr[string->len-1] == 0xF7) { /* push message on stack */ push_message(string); string = NULL; } } } while (*stop == FALSE); if (string) { g_string_free(string, TRUE); string = NULL; } return NULL; } /** * \param procedure procedure ID * \param data unpacked message data * \param len data length * * Creates SysEx message then sends it. This function uses folowing global variables: device_id, family_id and product_id. **/ 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) { GString *tmp = pack_data(data, len); g_string_append_len(msg, tmp->str, tmp->len); g_string_free(tmp, TRUE); } g_string_append_printf(msg, "%c\xF7", calculate_checksum(&msg->str[1], msg->len - 1)); debug_msg(DEBUG_VERBOSE, "Sending %s len %d", get_message_name(procedure), len); send_data(msg->str, msg->len); g_string_free(msg, TRUE); } /** * \param id MessageID of requested message * * Reads data from message queue until message with matching id is found. * * \return GString containing unpacked message. **/ GString *get_message_by_id(MessageID id) { GString *data = NULL; guint x, len; gboolean found = FALSE; g_mutex_lock(message_queue_mutex); do { len = g_queue_get_length(message_queue); for (x = 0; x> 8; } while (temp); if (n == 1) { if (value & 0x80) n = 2; else g_string_append_printf(msg, "%c", value); } if (n > 1) { gint x; g_string_append_printf(msg, "%c", (n | 0x80)); for (x=0; x> (8*(n-x-1))) & 0xFF)); } } } /** * \param str pointer to value to unpack * \param len return location for how many bytes value is encoded on (length is added to current value) * * Unpacks value using scheme used on all newer DigiTech products. * * \return unpacked value **/ guint unpack_value(gchar *str, int *len) { guint value; gint tmp; value = (unsigned char)str[0]; if (len != NULL) *len += 1; if (value > 0x80) { tmp = value & 0x7F; value = 0; gint i; for (i = 0; iid = -1; param->position = -1; param->value = -1; return param; } /** * \param str pointer to setting param in message * \param len return location for how many bytes value is encoded on (length is added to current value) * * Creates SettingParam basing on data. * This function expects str to point on: * -Parameter ID - 2 bytes * -Parameter position - 1 byte * -Parameter value - var * * \return newly created SettingParam which must be freed using setting_param_free. **/ SettingParam *setting_param_new_from_data(gchar *str, gint *len) { gint id; gint position; guint value; id = ((unsigned char)str[0] << 8) | (unsigned char)str[1]; position = (unsigned char)str[2]; if (len != NULL) *len += 3; value = unpack_value(&str[3], len); SettingParam *param = g_slice_new(SettingParam); param->id = id; param->position = position; param->value = value; return param; } /** * \param param SettingParam to be freed * * Frees all memory used by SettingParam. **/ void setting_param_free(SettingParam *param) { g_slice_free(SettingParam, param); } /** * Allocates memory for SettingGenetx. * * \return SettingGenetx which must be freed using setting_genetx_free. **/ SettingGenetx *setting_genetx_new() { SettingGenetx *genetx = g_slice_new(SettingGenetx); /* Older patches don't specify GeNetX version */ genetx->version = GENETX_VERSION_1; genetx->type = GENETX_TYPE_NOT_SET; genetx->channel = -1; genetx->name = NULL; genetx->data = NULL; return genetx; } /** * \param genetx SettingGenetx to be freed * * Frees all memory used by SettingGenetx. **/ void setting_genetx_free(SettingGenetx *genetx) { g_free(genetx->name); if (genetx->data != NULL) { g_string_free(genetx->data, TRUE); } g_slice_free(SettingGenetx, genetx); } /** * \param version GeNetX version * \param type GeNetX type * * Retrieves SectionID for specified GeNetX version and type. * * \return SectionID specified by version and type, or -1 on error. **/ SectionID get_genetx_section_id(gint version, gint type) { if (version == GENETX_VERSION_1) { if (type == GENETX_TYPE_AMP) { return SECTION_GENETX_AMP; } else if (type == GENETX_TYPE_CABINET) { return SECTION_GENETX_CABINET; } } else if (version == GENETX_VERSION_2) { if (type == GENETX_TYPE_AMP) { return SECTION_GENETX2_AMP; } else if (type == GENETX_TYPE_CABINET) { return SECTION_GENETX2_CABINET; } } g_warning("This version of gdigi don't know what to do with this " "GeNetX version (%d) and type (%d)", version, type); return -1; } /** * \param id Parameter ID * \param position Parameter position * \param value Parameter value * * Forms SysEx message to request parameter then sends it to device. **/ void get_option(guint id, guint position) { GString *msg = g_string_sized_new(9); debug_msg(DEBUG_MSG2DEV, "REQUEST_PARAMETER_VALUE: id %d position %d", id, position); g_string_append_printf(msg, "%c%c%c", ((id & 0xFF00) >> 8), (id & 0xFF), position); send_message(REQUEST_PARAMETER_VALUE, msg->str, msg->len); g_string_free(msg, TRUE); } /** * \param id Parameter ID * \param position Parameter position * \param value Parameter value * * Forms SysEx message to set parameter then sends it to device. **/ void set_option(guint id, guint position, guint value) { GString *msg = g_string_sized_new(9); g_string_append_printf(msg, "%c%c%c", ((id & 0xFF00) >> 8), (id & 0xFF), position); append_value(msg, value); if (debug_flag_is_set(DEBUG_MSG2DEV)) { GString *ipv = format_ipv(id, position, value); debug_msg(DEBUG_MSG2DEV, "RECEIVE_PARAMETER_VALUE\n%s", ipv->str); g_string_free(ipv, TRUE); } send_message(RECEIVE_PARAMETER_VALUE, msg->str, msg->len); g_string_free(msg, TRUE); } /** * \param section data section ID * \param bank section-specific bank number * \param index index of the desired object within the bank * \param name object name * \param data GString containing object data * * Forms RECEIVE_OBJECT SysEx message then sends it to device. **/ void send_object(SectionID section, guint bank, guint index, gchar *name, GString *data) { GString *msg = g_string_new(NULL); gint len = data->len; g_string_append_printf(msg, "%c%c%c%c%s%c%c%c", section, bank, ((index & 0xFF00) >> 8), (index & 0xFF), name, 0 /* NULL terminated string */, ((len & 0xFF00) >> 8), (len & 0xFF)); g_string_append_len(msg, data->str, data->len); send_message(RECEIVE_OBJECT, msg->str, msg->len); g_string_free(msg, TRUE); } /** * \param params GList containing SettingParam * * Forms RECEIVE_PRESET_PARAMETERS SysEx message then sends it to device. **/ void send_preset_parameters(GList *params) { GString *msg = g_string_sized_new(500); GList *iter = params; gint len = g_list_length(iter); g_string_append_printf(msg, "%c%c", ((len & 0xFF00) >> 8), (len & 0xFF)); while (iter) { SettingParam *param = (SettingParam *) iter->data; iter = iter->next; g_string_append_printf(msg, "%c%c%c", ((param->id & 0xFF00) >> 8), (param->id & 0xFF), param->position); append_value(msg, param->value); }; send_message(RECEIVE_PRESET_PARAMETERS, msg->str, msg->len); g_string_free(msg, TRUE); } /** * \param bank preset bank * \param x preset index * * Switches to selected preset. **/ void switch_preset(guint bank, guint x) { GString *msg = g_string_sized_new(6); g_string_append_printf(msg, "%c%c%c%c%c%c", bank, x, /* source */ PRESETS_EDIT_BUFFER, 0, /* destination */ 0, /* keep existing name */ 1); /* load */ send_message(MOVE_PRESET, msg->str, msg->len); g_string_free(msg, TRUE); } /** * \param x preset index * \param name preset name * * Stores current edit buffer in user presets bank. **/ void store_preset_name(int x, const gchar *name) { GString *msg = g_string_sized_new(6); g_string_append_printf(msg, "%c%c%c%c%s%c%c", PRESETS_EDIT_BUFFER, 0, /* source */ PRESETS_USER, x, /* destination */ name, 0, /* name */ 1); /* load */ send_message(MOVE_PRESET, msg->str, msg->len); g_string_free(msg, TRUE); } /** * \param x preset index * \param name preset name * * Sets preset name. **/ void set_preset_name(int x, gchar *name) { GString *msg = g_string_sized_new(12); g_string_append_printf(msg, "%c%c%s%c", PRESETS_USER, /* preset bank */ x, /* preset index */ name, 0); /* name */ send_message(RECEIVE_PRESET_NAME, msg->str, msg->len); g_string_free(msg, TRUE); } /** * \param bank preset bank * * Queries preset names. * * \return GStrv which must be freed with g_strfreev, or NULL on error. **/ GStrv query_preset_names(gchar bank) { GString *data = NULL; int x; /* used to iterate over whole reply */ int n = 0; /* current preset number */ int n_total; /* total number of presets */ gchar **str_array = NULL; /* query user preset names */ send_message(REQUEST_PRESET_NAMES, &bank, 1); /* read reply */ data = get_message_by_id(RECEIVE_PRESET_NAMES); if (data != NULL) { if (data->len >= 10) { n_total = data->str[9]; str_array = g_new(gchar*, n_total + 1); str_array[n_total] = NULL; } for (x=10; ((xlen) && (nstr[x] == 0xF7) /* every message ends with 0xF7 */ break; str_array[n] = g_strdup(&data->str[x]); x += strlen(str_array[n]); n++; } g_string_free(data, TRUE); } return str_array; } /** * Reads multiple messages and puts them into GList. * * \param id MessageID starting message sequence * * \return GList with SysEx messages, which must be freed using message_list_free. **/ GList *get_message_list(MessageID id) { GString *data = NULL; GList *list = NULL; guint x, len; gboolean found = FALSE; gboolean done = FALSE; g_mutex_lock(message_queue_mutex); do { len = g_queue_get_length(message_queue); for (x = 0; xlen) && data->str[i]; i++); amt = (unsigned char)data->str[i+2]; break; case RECEIVE_BULK_DUMP_START: amt = ((unsigned char)data->str[8] << 8) | (unsigned char)data->str[9]; break; default: g_error("get_message_list() doesn't support followning id: %d", id); g_string_free(data, TRUE); g_list_free(list); g_assert(!"BUG"); return NULL; } while (amt) { debug_msg(DEBUG_VERBOSE, "%d messages left", amt); data = g_queue_pop_nth(message_queue, x); if (data == NULL) { g_cond_wait(message_queue_cond, message_queue_mutex); } else { unpack_message(data); list = g_list_append(list, data); amt--; } } done = TRUE; } else { /* id not found in message queue */ g_cond_wait(message_queue_cond, message_queue_mutex); } } while (done == FALSE); g_mutex_unlock(message_queue_mutex); return list; } /** * \param list list to be freed * * Frees all memory used by message list. **/ void message_list_free(GList *list) { g_return_if_fail(list != NULL); g_list_foreach(list, (GFunc) message_free_func, NULL); g_list_free(list); } /** * Queries current edit buffer. * * \return GList with preset SysEx messages, which must be freed using message_list_free. **/ GList *get_current_preset() { send_message(REQUEST_PRESET, "\x04\x00", 2); return get_message_list(RECEIVE_PRESET_START); } /** * Creates backup file. * * \param file backup file handle * \param error a GError * * \return FALSE on success, TRUE on error. **/ static gboolean create_backup_file(GFile *file, GError **error) { GFileOutputStream *output = NULL; GList *list = NULL, *iter = NULL; const gchar header[] = {'\x01', '\x00'}; gsize written; gboolean val; if (error) *error = NULL; output = g_file_create(file, G_FILE_CREATE_NONE, NULL, error); if (output == NULL) return TRUE; if (error) *error = NULL; val = g_output_stream_write_all(G_OUTPUT_STREAM(output), header, sizeof(header), &written, NULL, error); if (val == FALSE) { g_object_unref(output); return TRUE; } send_message(REQUEST_BULK_DUMP, "\x00", 1); list = get_message_list(RECEIVE_BULK_DUMP_START); for (iter = list; iter; iter = g_list_next(iter)) { GString *str; guchar id; /* message id */ guint32 len; /* message length */ str = (GString*) iter->data; id = get_message_id(str); if (error) *error = NULL; val = g_output_stream_write_all(G_OUTPUT_STREAM(output), &id, sizeof(id), &written, NULL, error); if (val == FALSE) { message_list_free(list); g_object_unref(output); return TRUE; } len = GUINT32_TO_LE(str->len - 10); if (error) *error = NULL; val = g_output_stream_write_all(G_OUTPUT_STREAM(output), &len, sizeof(len), &written, NULL, error); if (val == FALSE) { message_list_free(list); g_object_unref(output); return TRUE; } if (error) *error = NULL; val = g_output_stream_write_all(G_OUTPUT_STREAM(output), &str->str[8], str->len - 10, &written, NULL, error); if (val == FALSE) { message_list_free(list); g_object_unref(output); return TRUE; } } message_list_free(list); if (error) *error = NULL; val = g_output_stream_close(G_OUTPUT_STREAM(output), NULL, error); g_object_unref(output); return !val; } /** * Restores backup file. * * \param filename backup filename * \param error a GError * * \return FALSE on success, TRUE on error. **/ static gboolean restore_backup_file(const gchar *filename, GError **error) { gchar *data; gsize length; gsize x; if (g_file_get_contents(filename, &data, &length, error) == FALSE) return TRUE; if (error) *error = NULL; if (!(data[0] == 0x01 && data[1] == 0x00)) { g_free(data); g_set_error_literal(error, gdigi_error_quark(), 0, "Magic byte doesn't match"); return TRUE; } x = 0x02; while (x < length) { gchar id; guint32 len; id = data[x]; x++; if (x+4 <= length) { len = GUINT32_FROM_LE(*((guint32*) &data[x])); x += 4; } else { g_free(data); g_set_error_literal(error, gdigi_error_quark(), 0, "Unexpected end of data"); return TRUE; } if (x+len <= length) { send_message(id, &data[x], len); x += len; } else { g_free(data); g_set_error_literal(error, gdigi_error_quark(), 0, "Unexpected end of data"); return TRUE; } } g_free(data); return FALSE; } /** * \param device_id Variable to hold device ID * \param family_id Variable to hold family ID * \param product_id Variable to hold product ID * * Requests device information. * * \return TRUE on success, FALSE on error. **/ static gboolean request_who_am_i(unsigned char *device_id, unsigned char *family_id, unsigned char *product_id) { send_message(REQUEST_WHO_AM_I, "\x7F\x7F\x7F", 3); GString *data = get_message_by_id(RECEIVE_WHO_AM_I); if ((data != NULL) && (data->len > 11)) { *device_id = data->str[8]; *family_id = data->str[9]; *product_id = data->str[10]; g_string_free(data, TRUE); debug_msg(DEBUG_STARTUP, "Found device id %d family %d product id %d.", *device_id, *family_id, *product_id); return TRUE; } return FALSE; } static void request_device_configuration() { gint os_major, os_minor; gint cpu_major, cpu_minor; gint protocol_version; gint current_bank, current_preset; gint media_card; send_message(REQUEST_DEVICE_CONFIGURATION, NULL, 0); GString *data = get_message_by_id(RECEIVE_DEVICE_CONFIGURATION); if (data->len > 14) { os_major = data->str[8]; os_minor = (((data->str[9] & 0xF0) >> 4) * 10) | (data->str[9] & 0x0F); g_message("OS version: %d.%d", os_major, os_minor); cpu_major = data->str[10]; cpu_minor = data->str[11]; g_message("CPU version: %d.%d", cpu_major, cpu_minor); protocol_version = data->str[12]; g_message("Protocol version: %d", protocol_version); current_bank = data->str[13]; current_preset = data->str[14]; g_message("Active bank: %d", current_bank); g_message("Active preset: %d", current_preset); if (os_major >= 1) { media_card = data->str[15]; g_message("Media card present: %d", media_card); } } } #ifndef DOXYGEN_SHOULD_SKIP_THIS static GOptionEntry options[] = { {"device", 'd', G_OPTION_FLAG_IN_MAIN, G_OPTION_ARG_STRING, &device_port, "MIDI device port to use", NULL}, {"debug-flags ", 'D', G_OPTION_FLAG_IN_MAIN, G_OPTION_ARG_CALLBACK, set_debug_flags, " any of a, d, g, h, m, s, x, v:\n" " " "a: Everything.\n" " " "d: Messages to the device.\n" " " "g: Group messages.\n" " " "h: Dump message contents in hex.\n" " " "m: All messages.\n" " " "s: Startup.\n" " " "x: Debug xml parsing/writing.\n" " " "v: Additional verbosity.\n" , NULL}, {NULL} }; #endif /* DOXYGEN_SHOULD_SKIP_THIS */ /** * \param[out] devices GList containing numbers (packed into pointers) * of connected DigiTech devices * * Checks available soundcards for DigiTech devices. * * \return the number of connected DigiTech devices. **/ static gint get_digitech_devices(GList **devices) { gint card_num = -1; gint number = 0; while (!snd_card_next(&card_num) && (card_num > -1)) { char* name; snd_card_get_longname(card_num, &name); if (strspn(name,"DigiTech") > 0) { number++; *devices = g_list_append(*devices, GINT_TO_POINTER(card_num)); } free(name); } return number; } int main(int argc, char *argv[]) { GError *error = NULL; GOptionContext *context; static gboolean stop_read_thread = FALSE; GThread *read_thread = NULL; g_thread_init(NULL); gdk_threads_init(); context = g_option_context_new(NULL); g_option_context_add_main_entries(context, options, NULL); g_option_context_add_group(context, gtk_get_option_group(TRUE)); if (!g_option_context_parse(context, &argc, &argv, &error)) { g_warning("option parsing failed: %s\n", error->message); g_error_free(error); g_option_context_free(context); exit(EXIT_FAILURE); } if (device_port == NULL) { /* port not given explicitly in commandline - search for devices */ GList *devices = NULL; GList *device = NULL; int num_devices = 0; int chosen_device = 0; if ((num_devices = get_digitech_devices(&devices)) <= 0) { g_warning("Couldn't find DigiTech devices!"); exit(EXIT_FAILURE); } if (num_devices > 1) { chosen_device = select_device_dialog(devices); if (chosen_device < 0) { show_error_message(NULL, "No device chosen"); exit(EXIT_FAILURE); } } device = g_list_nth(devices, chosen_device); device_port = g_strdup_printf("hw:%d,0,0", GPOINTER_TO_INT(device->data)); g_list_free(devices); debug_msg(DEBUG_STARTUP, "Found device %s.", device_port); } else { debug_msg(DEBUG_STARTUP, "Using device %s.", device_port); } g_option_context_free(context); if (open_device() == TRUE) { show_error_message(NULL, "Failed to open MIDI device"); } else { message_queue = g_queue_new(); message_queue_mutex = g_mutex_new(); message_queue_cond = g_cond_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) { show_error_message(NULL, "No suitable reply from device"); } else { Device *device = NULL; if (get_device_info(device_id, family_id, product_id, &device) == FALSE) { if (unsupported_device_dialog(&device) == FALSE) { g_message("Shutting down"); } } if (device != NULL) { /* enable GUI mode */ set_option(GUI_MODE_ON_OFF, GLOBAL_POSITION, 1); gui_create(device); gtk_main(); gui_free(); /* disable GUI mode */ set_option(GUI_MODE_ON_OFF, GLOBAL_POSITION, 0); } } } 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_queue_get_length(message_queue)) { g_warning("%d unread messages in queue", g_queue_get_length(message_queue)); g_queue_foreach(message_queue, (GFunc) message_free_func, NULL); g_queue_free(message_queue); } if (output != NULL) { snd_rawmidi_drain(output); snd_rawmidi_close(output); } if (input != NULL) { snd_rawmidi_drain(input); snd_rawmidi_close(input); } return EXIT_SUCCESS; }