diff options
author | Andrej Shadura <andrew.shadura@collabora.co.uk> | 2021-10-07 16:12:09 +0200 |
---|---|---|
committer | Andrej Shadura <andrew.shadura@collabora.co.uk> | 2021-10-07 16:12:09 +0200 |
commit | d4700c96fd551eea539921572c03842ecdc6c8c3 (patch) | |
tree | ac37ec6ef7d954feab5ad47c427e849f051da604 /gdigi.c |
Import Upstream version 0.2.0+hg20110905r195
Diffstat (limited to 'gdigi.c')
-rw-r--r-- | gdigi.c | 1241 |
1 files changed, 1241 insertions, 0 deletions
@@ -0,0 +1,1241 @@ +/* + * Copyright (c) 2009 Tomasz Moń <desowin@gmail.com> + * + * 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 <http://www.gnu.org/licenses>. + */ + +#include <stdio.h> +#include <gtk/gtk.h> +#include <gio/gio.h> +#include <getopt.h> +#include <alsa/asoundlib.h> +#include <alloca.h> +#include "gdigi.h" +#include "gui.h" + +static unsigned char device_id = 0x7F; +static unsigned char family_id = 0x7F; +static 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; + +/** + * 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; x<length; x++) { + checksum ^= array[x]; + } + + return checksum; +} + +/** + * Opens MIDI device. This function modifies global input and output variables. + * + * \return FALSE on success, TRUE on error. + **/ +gboolean open_device() +{ + int err; + + err = snd_rawmidi_open(&input, &output, device_port, SND_RAWMIDI_SYNC); + if (err) { + fprintf(stderr, "snd_rawmidi_open %s failed: %d\n", device_port, err); + return TRUE; + } + + err = snd_rawmidi_nonblock(output, 0); + if (err) { + fprintf(stderr, "snd_rawmidi_nonblock failed: %d\n", err); + return TRUE; + } + + snd_rawmidi_read(input, NULL, 0); /* trigger reading */ + + return FALSE; +} + +/** + * \param data data to be sent + * \param length data length + * + * Sends data to device. This function uses global output variable. + **/ +void send_data(char *data, int length) +{ + snd_rawmidi_write(output, data, length); +} + +/** + * \param data data to be packed + * \param len data length + * + * Packs data using method used on all newer DigiTech products. + * + * \return GString containing packed data + **/ +GString *pack_data(gchar *data, gint len) +{ + GString *packed; + gint i; + gint new_len; + unsigned char status; + gint offset; + gint status_byte; + + new_len = len + (len/7); + packed = g_string_sized_new(new_len); + status = 0; + offset = -1; + status_byte = 0; + + for (i=0; i<len; i++) { + if ((i % 7) == 0) { + packed->str[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; +} + +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"); + + switch (get_message_id(msg)) { + case ACK: + g_message("Received ACK"); + g_string_free(msg, TRUE); + return; + + case NACK: + g_message("Received NACK"); + g_string_free(msg, TRUE); + return; + + case RECEIVE_PARAMETER_VALUE: + unpack_message(msg); + SettingParam *param = setting_param_new_from_data(&msg->str[8], NULL); + g_message("Received parameter change ID: %d Position: %d Value: %d", param->id, param->position, param->value); + + 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) { + g_message("Loaded preset %d from bank %d", str[10], str[9]); + + GDK_THREADS_ENTER(); + g_timeout_add(0, apply_current_preset_to_gui, NULL); + GDK_THREADS_LEAVE(); + } else + g_message("%d %d moved to %d %d", str[9], str[10], str[11], str[12]); + default: + g_message("Received unhandled device notification"); + } + g_string_free(msg, TRUE); + 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); + } +} + +gpointer read_data_thread(gboolean *stop) +{ + /* This is mostly taken straight from alsa-utils-1.0.19 amidi/amidi.c + by Clemens Ladisch <clemens@ladisch.de> */ + 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, sizeof(buf), 0xFF); + + 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; (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 (*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)); + + 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<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; + } + } + + if (found == FALSE) + g_cond_wait(message_queue_cond, message_queue_mutex); + + } while (found == FALSE); + g_mutex_unlock(message_queue_mutex); + + unpack_message(data); + + return data; +} + +/** + * \param msg message to append value + * \param value value to append + * + * Packs value using scheme used on all newer DigiTech products. + **/ +void append_value(GString *msg, guint value) +{ + /* check how many bytes long the value is */ + guint temp = value; + gint n = 0; + do { + n++; + temp = temp >> 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<n; x++) { + g_string_append_printf(msg, "%c", + ((value >> (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; i<tmp; i++) { + value |= ((unsigned char)str[1+i] << (8*(tmp-i-1))); + } + + if (len != NULL) + *len += tmp; + } + + return value; +} + +/** + * Allocates memory for SettingParam. + * + * \return SettingParam which must be freed using setting_param_free. + **/ +SettingParam *setting_param_new() +{ + SettingParam *param = g_slice_new(SettingParam); + param->id = -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_message("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 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); + 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; ((x<data->len) && (n<n_total)); x++) { + if ((unsigned char)data->str[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; x<len && (found == FALSE); x++) { + data = g_queue_peek_nth(message_queue, x); + if (get_message_id(data) == id) { + found = TRUE; + g_queue_pop_nth(message_queue, x); + unpack_message(data); + list = g_list_append(list, data); + break; + } + } + + if (found == TRUE) { + int i; + int amt; + + switch (id) { + case RECEIVE_PRESET_START: + for (i = 10; (i < data->len) && 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); + return NULL; + } + + while (amt) { + g_message("%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); + 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}, + {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; + snd_card_next(&card_num); + + while (card_num > -1) { + char* name; + snd_card_get_longname(card_num, &name); + gint count = strspn(name,"DigiTech"); + if (count > 0) + { + number++; + *devices = g_list_append(*devices, GINT_TO_POINTER(card_num)); + } + snd_card_next(&card_num); + } + + 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_message("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; + if (get_digitech_devices(&devices) <= 0) { + g_message("Couldn't find DigiTech devices!"); + exit(EXIT_FAILURE); + } + device_port = g_strdup_printf("hw:%d,0,0", + GPOINTER_TO_INT(devices->data)); + g_list_free(devices); + g_message("Found device %s", device_port); + } else { + g_message("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, USB_POSITION, 1); + + gui_create(device); + gtk_main(); + gui_free(); + + /* disable GUI mode */ + set_option(GUI_MODE_ON_OFF, USB_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_message("%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; +} |