/* * Copyright (C) 2003 Steve Harris * * 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; either version 2 of the License, or * (at your option) any later version. * * 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. * * $Id: state.c,v 1.58 2004/11/02 05:40:16 joq Exp $ */ #include #include #include #include #include #include #include #include #include #include #include #include "config.h" #include "main.h" #include "geq.h" #include "spectrum.h" #include "state.h" #include "io.h" #include "process.h" #include "scenes.h" #include "hdeq.h" #include "compressor-ui.h" #include "help.h" /* A scene value to indicate that loading failed */ #define LOAD_ERROR -2 /* The smallest value that counts as a change, should be approximately * epsilon+delta */ #define MIN_CHANGE (FLT_EPSILON + FLT_EPSILON) float s_value[S_SIZE]; static float s_target[S_SIZE]; static int s_duration[S_SIZE]; static int s_changed[S_SIZE]; static GtkAdjustment *s_adjustment[S_SIZE]; static s_callback_func s_callback[S_SIZE]; static char *errstr = NULL; static s_state *last_state = NULL; static int last_changed = S_NONE; static GList *history = NULL; static GList *undo_pos = NULL; static int suppress_feedback = 0; static int saved_scene; static float crossfade_time = 1.0; static void s_set_events(int id, float value); void s_update_title(); void s_history_add(const char *description); void s_save_global_int(xmlDocPtr doc, char *symbol, int value); void s_save_global_float(xmlDocPtr doc, char *symbol, float value); void s_save_global_gang(xmlDocPtr doc, char *p, int band, gboolean value); static const gchar *filename = NULL; /* global session parmaeters read from the XML file */ typedef struct { int scene; int mode; float freq; float lgain; float hgain; float ct; int gang_at[XO_BANDS]; int gang_re[XO_BANDS]; int gang_th[XO_BANDS]; int gang_ra[XO_BANDS]; int gang_kn[XO_BANDS]; int gang_ma[XO_BANDS]; } xml_global_params; void state_init() { unsigned int i; for (i=0; i= 0 && id < S_SIZE); s_callback[id] = callback; } void s_set_adjustment(int id, GtkAdjustment *adjustment) { assert(id >= 0 && id < S_SIZE); s_adjustment[id] = adjustment; } void s_set_value_ui(int id, float value) { s_value[id] = value; if (suppress_feedback) { return; } assert(id >= 0 && id < S_SIZE); if (last_changed != id) { s_history_add(g_strdup_printf("%s = %f", s_description[id], s_value[id])); } last_state->value[id] = value; #if 0 /* This code is confusing in use, so I've removed it - swh */ if (value - MIN_CHANGE < last_state->value[id] && value + MIN_CHANGE > last_state->value[id]) { last_changed = S_NONE; } else { last_changed = id; } #else last_changed = id; #endif if (s_callback[id]) { (*s_callback[id])(id, value); } } void s_set_value(int id, float value, int duration) { /* We dont want to call this yet... s_set_value_ui(id, value); */ s_duration[id] = duration; s_target[id] = value; if (s_adjustment[id]) { gtk_adjustment_set_value(s_adjustment[id], value); } } void s_set_value_block(float *values, int base, int count) { int i; for (i = 0 ; i < count ; i++) { s_value[base + i] = values[i]; } last_changed = base; //s_set_events(base, values[i]); } void s_set_value_no_history(int id, float value) { suppress_feedback++; s_value[id] = value; s_target[id] = value; s_set_events(id, value); suppress_feedback--; } void s_clear_history() { GList *p; for (p=history; p; p=p->next) { free(p->data); } g_list_free(history); history = NULL; s_history_add("Initial state"); undo_pos = history->next; //s_restore_state((s_state *)history->data); last_changed = S_LOAD; } void s_history_add(const char *description) { s_state *ns; GList *it; ns = malloc(sizeof(s_state)); ns->description = (char *)description; memcpy(ns->value, s_value, S_SIZE * sizeof(float)); if (undo_pos) { it = undo_pos->next; while (it) { free(it->data); it = it->next; } undo_pos->next = NULL; } history = g_list_append(history, ns); undo_pos = g_list_last(history); /* printf("add %s\n", description); */ last_state = ns; } void s_history_add_state(s_state state) { s_state *ns; GList *it; ns = malloc(sizeof(s_state)); ns->description = (char *)state.description; memcpy(ns->value, state.value, S_SIZE * sizeof(float)); if (undo_pos) { it = undo_pos->next; while (it) { free(it->data); it = it->next; } undo_pos->next = NULL; } history = g_list_append(history, ns); undo_pos = g_list_last(history); /* printf("add %s\n", description); */ last_state = ns; } static unsigned int compute_state_crc (s_state *state) { unsigned int checksum, i; unsigned char *buf; unsigned int crc_table[256] = {0x00000000,0x77073096,0xEE0E612C,0x990951BA,0x076DC419,0x706AF48F, 0xE963A535,0x9E6495A3,0x0EDB8832,0x79DCB8A4,0xE0D5E91E,0x97D2D988, 0x09B64C2B,0x7EB17CBD,0xE7B82D07,0x90BF1D91,0x1DB71064,0x6AB020F2, 0xF3B97148,0x84BE41DE,0x1ADAD47D,0x6DDDE4EB,0xF4D4B551,0x83D385C7, 0x136C9856,0x646BA8C0,0xFD62F97A,0x8A65C9EC,0x14015C4F,0x63066CD9, 0xFA0F3D63,0x8D080DF5,0x3B6E20C8,0x4C69105E,0xD56041E4,0xA2677172, 0x3C03E4D1,0x4B04D447,0xD20D85FD,0xA50AB56B,0x35B5A8FA,0x42B2986C, 0xDBBBC9D6,0xACBCF940,0x32D86CE3,0x45DF5C75,0xDCD60DCF,0xABD13D59, 0x26D930AC,0x51DE003A,0xC8D75180,0xBFD06116,0x21B4F4B5,0x56B3C423, 0xCFBA9599,0xB8BDA50F,0x2802B89E,0x5F058808,0xC60CD9B2,0xB10BE924, 0x2F6F7C87,0x58684C11,0xC1611DAB,0xB6662D3D,0x76DC4190,0x01DB7106, 0x98D220BC,0xEFD5102A,0x71B18589,0x06B6B51F,0x9FBFE4A5,0xE8B8D433, 0x7807C9A2,0x0F00F934,0x9609A88E,0xE10E9818,0x7F6A0DBB,0x086D3D2D, 0x91646C97,0xE6635C01,0x6B6B51F4,0x1C6C6162,0x856530D8,0xF262004E, 0x6C0695ED,0x1B01A57B,0x8208F4C1,0xF50FC457,0x65B0D9C6,0x12B7E950, 0x8BBEB8EA,0xFCB9887C,0x62DD1DDF,0x15DA2D49,0x8CD37CF3,0xFBD44C65, 0x4DB26158,0x3AB551CE,0xA3BC0074,0xD4BB30E2,0x4ADFA541,0x3DD895D7, 0xA4D1C46D,0xD3D6F4FB,0x4369E96A,0x346ED9FC,0xAD678846,0xDA60B8D0, 0x44042D73,0x33031DE5,0xAA0A4C5F,0xDD0D7CC9,0x5005713C,0x270241AA, 0xBE0B1010,0xC90C2086,0x5768B525,0x206F85B3,0xB966D409,0xCE61E49F, 0x5EDEF90E,0x29D9C998,0xB0D09822,0xC7D7A8B4,0x59B33D17,0x2EB40D81, 0xB7BD5C3B,0xC0BA6CAD,0xEDB88320,0x9ABFB3B6,0x03B6E20C,0x74B1D29A, 0xEAD54739,0x9DD277AF,0x04DB2615,0x73DC1683,0xE3630B12,0x94643B84, 0x0D6D6A3E,0x7A6A5AA8,0xE40ECF0B,0x9309FF9D,0x0A00AE27,0x7D079EB1, 0xF00F9344,0x8708A3D2,0x1E01F268,0x6906C2FE,0xF762575D,0x806567CB, 0x196C3671,0x6E6B06E7,0xFED41B76,0x89D32BE0,0x10DA7A5A,0x67DD4ACC, 0xF9B9DF6F,0x8EBEEFF9,0x17B7BE43,0x60B08ED5,0xD6D6A3E8,0xA1D1937E, 0x38D8C2C4,0x4FDFF252,0xD1BB67F1,0xA6BC5767,0x3FB506DD,0x48B2364B, 0xD80D2BDA,0xAF0A1B4C,0x36034AF6,0x41047A60,0xDF60EFC3,0xA867DF55, 0x316E8EEF,0x4669BE79,0xCB61B38C,0xBC66831A,0x256FD2A0,0x5268E236, 0xCC0C7795,0xBB0B4703,0x220216B9,0x5505262F,0xC5BA3BBE,0xB2BD0B28, 0x2BB45A92,0x5CB36A04,0xC2D7FFA7,0xB5D0CF31,0x2CD99E8B,0x5BDEAE1D, 0x9B64C2B0,0xEC63F226,0x756AA39C,0x026D930A,0x9C0906A9,0xEB0E363F, 0x72076785,0x05005713,0x95BF4A82,0xE2B87A14,0x7BB12BAE,0x0CB61B38, 0x92D28E9B,0xE5D5BE0D,0x7CDCEFB7,0x0BDBDF21,0x86D3D2D4,0xF1D4E242, 0x68DDB3F8,0x1FDA836E,0x81BE16CD,0xF6B9265B,0x6FB077E1,0x18B74777, 0x88085AE6,0xFF0F6A70,0x66063BCA,0x11010B5C,0x8F659EFF,0xF862AE69, 0x616BFFD3,0x166CCF45,0xA00AE278,0xD70DD2EE,0x4E048354,0x3903B3C2, 0xA7672661,0xD06016F7,0x4969474D,0x3E6E77DB,0xAED16A4A,0xD9D65ADC, 0x40DF0B66,0x37D83BF0,0xA9BCAE53,0xDEBB9EC5,0x47B2CF7F,0x30B5FFE9, 0xBDBDF21C,0xCABAC28A,0x53B39330,0x24B4A3A6,0xBAD03605,0xCDD70693, 0x54DE5729,0x23D967BF,0xB3667A2E,0xC4614AB8,0x5D681B02,0x2A6F2B94, 0xB40BBE37,0xC30C8EA1,0x5A05DF1B,0x2D02EF8D}; checksum = ~0; buf = (unsigned char *) state->value; for (i = 0 ; i < S_SIZE * sizeof (float) ; i++) checksum = crc_table[(checksum ^ buf[i]) & 0xff] ^ (checksum >> 8); checksum ^= ~0; return (checksum); } void s_undo() { GList *undo_next; int scene, crc[2]; s_state *st[2]; if (!undo_pos) { return; } undo_next = g_list_previous(undo_pos); if (!undo_next) { return; } undo_pos = undo_next; s_restore_state((s_state *)undo_pos->data); scene = get_previous_scene_num (); if (scene >= 0) { st[0] = get_scene (scene); st[1] = (s_state *) undo_pos->data; crc[0] = compute_state_crc (st[0]); crc[1] = compute_state_crc (st[1]); if (crc[0] == crc[1]) set_scene_button (scene); } set_EQ_curve_values (0, 0.0); last_changed = S_LOAD; } void s_redo() { gboolean restore; int scene, crc[2]; s_state *st[2]; restore = FALSE; if (undo_pos) { if (undo_pos->next) { undo_pos = g_list_next(undo_pos); restore = TRUE; } } else { undo_pos = history; undo_pos = g_list_next(undo_pos); restore = TRUE; } if (restore) { s_restore_state((s_state *)undo_pos->data); scene = get_previous_scene_num (); if (scene >= 0) { st[0] = get_scene (scene); st[1] = (s_state *) undo_pos->data; crc[0] = compute_state_crc (st[0]); crc[1] = compute_state_crc (st[1]); if (crc[0] == crc[1]) { set_scene_button (scene); } else { set_scene_warning_button (scene); } } set_EQ_curve_values (0, 0.0); } } /* Negative time will use the default "crossfade time" which may come from the command line -c option. */ void s_crossfade_to_state(s_state *state, float time) { int i, duration, milliseconds; if (time < 0.0) time = crossfade_time; suspend_ganging (); milliseconds = time * 1000.0 + 50; g_timeout_add (milliseconds, unsuspend_ganging, NULL); /* printf("restore %s\n", state->description); */ duration = (int)(sample_rate * time); suppress_feedback++; for (i=0; ivalue[i]; s_duration[i] = duration; //s_set_events(i, state->value[i]); } suppress_feedback--; } void s_restore_state(s_state *state) { s_crossfade_to_state (state, 0.003); } static void s_set_events(int id, float value) { if (s_callback[id]) { (*s_callback[id])(id, value); } if (s_adjustment[id]) { gtk_adjustment_set_value(s_adjustment[id], value); } } void s_set_description(int id, const char *desc) { if (last_changed != id) { s_history_add(desc); } last_changed = id; } void s_save_session_from_ui (GtkWidget *w, gpointer user_data) { GtkFileSelection *file_selector = (GtkFileSelection *) user_data; s_save_session(gtk_file_selection_get_filename (GTK_FILE_SELECTION (file_selector))); } void s_save_session (const char *fname) { xmlDocPtr doc; xmlNodePtr rootnode, node, sc_node; unsigned int i, j; int curr_scene; char tmp[256]; /* Check to see if we have been passed a filename, if not fall back to * previous one */ if (fname) { filename = fname; s_update_title(); } if (!filename) { errstr = g_strdup_printf("No filename found at %s:%d, not saving\n", __FILE__, __LINE__); message (GTK_MESSAGE_WARNING, errstr); free(errstr); } /* Need to save this scene number. */ curr_scene = get_current_scene (); xmlSetCompressMode(5); doc = xmlNewDoc("1.0"); rootnode = xmlNewDocRawNode(doc, NULL, "jam-param-list", NULL); xmlSetProp(rootnode, "version", VERSION); xmlDocSetRootElement(doc, rootnode); node = xmlNewText("\n"); xmlAddChild(rootnode, node); s_save_global_int(doc, "mode", process_get_spec_mode()); s_save_global_int(doc, "freq", get_spectrum_freq()); s_save_global_float(doc, "ct", crossfade_time); s_save_global_float(doc, "lgain", hdeq_get_lower_gain()); s_save_global_float(doc, "hgain", hdeq_get_upper_gain()); /* record the current gang state of the compressor controls */ for (i = 0 ; i < XO_BANDS ; i++) { s_save_global_gang(doc, "at", i, comp_at_ganged(i)); s_save_global_gang(doc, "re", i, comp_re_ganged(i)); s_save_global_gang(doc, "th", i, comp_th_ganged(i)); s_save_global_gang(doc, "ra", i, comp_ra_ganged(i)); s_save_global_gang(doc, "kn", i, comp_kn_ganged(i)); s_save_global_gang(doc, "ma", i, comp_ma_ganged(i)); } /* Save current active state */ for (i=0; ivalue[i]); xmlSetProp(node, "name", s_symbol[i]); xmlSetProp(node, "value", tmp); xmlAddChild(sc_node, node); node = xmlNewText("\n"); xmlAddChild(sc_node, node); } } xmlSaveFile(filename, doc); xmlFreeDoc(doc); } /* declare SAX handlers */ void s_startElement(void *user_data, const xmlChar *name, const xmlChar **attrs); static void s_warning(void *user_data, const char *msg, ...); static void s_error(void *user_data, const char *msg, ...); void s_load_session_from_ui (GtkWidget *w, gpointer user_data) { GtkFileSelection *file_selector = (GtkFileSelection *) user_data; s_load_session(gtk_file_selection_get_filename (GTK_FILE_SELECTION (file_selector))); } void s_load_session (const char *fname) { xmlSAXHandlerPtr handler; int scene = -1; int fd; int i; xml_global_params gp; saved_scene = -1; unset_scene_buttons (); if (fname) { filename = fname; } if (filename == NULL) { filename = default_session; } /* Check to see if file is readable */ if ((fd = open(filename, O_RDONLY)) >= 0) { close(fd); } else { errstr = g_strdup_printf("Error opening '%s'", filename); message (GTK_MESSAGE_WARNING, errstr); perror(errstr); free(errstr); if (fname == NULL) { exit(1); } } s_update_title(); handler = calloc(1, sizeof(xmlSAXHandler)); handler->startElement = s_startElement; handler->warning = s_warning; handler->error = s_error; /* set the gp struct to some sensible defualts incase values aren't set in * the XML file */ gp.scene = scene; gp.mode = SPEC_POST_EQ; gp.freq = 10; gp.lgain = -12.0; gp.hgain = 12.0; gp.ct = 1.0; for (i = 0 ; i < XO_BANDS ; i++) { gp.gang_at[i] = FALSE; gp.gang_re[i] = FALSE; gp.gang_th[i] = FALSE; gp.gang_ra[i] = FALSE; gp.gang_kn[i] = FALSE; gp.gang_ma[i] = FALSE; } /* run the SAX parser */ scene_init(); xmlSAXUserParseFile(handler, &gp, filename); if (gp.scene == LOAD_ERROR) { errstr = g_strdup_printf("Loading file '%s' failed", filename); message (GTK_MESSAGE_WARNING, errstr); perror(errstr); free(errstr); return; } s_history_add(g_strdup_printf("Load %s", filename)); last_changed = S_LOAD; free(handler); /* global params are read into the gp struct, and set in the UI code from * here */ process_set_spec_mode (gp.mode); set_spectrum_freq (gp.freq); hdeq_set_upper_gain (gp.hgain); geq_set_range (geq_get_adjustment(0)->lower, gp.hgain); hdeq_set_lower_gain (gp.lgain); geq_set_range (gp.lgain, geq_get_adjustment(0)->upper); s_set_crossfade_time (gp.ct); /* This is the active scene. */ if (saved_scene < 100) { set_scene (saved_scene); } else { set_num_scene_warning_button(saved_scene); } if (!fname) { filename = NULL; } /* Set the ganging after the scene otherwise it will try to gang move the scene values. */ for (i = 0 ; i < XO_BANDS ; i++) { if (gp.gang_at[i]) comp_gang_at (i); if (gp.gang_re[i]) comp_gang_re (i); if (gp.gang_th[i]) comp_gang_th (i); if (gp.gang_ra[i]) comp_gang_ra (i); if (gp.gang_kn[i]) comp_gang_kn (i); if (gp.gang_ma[i]) comp_gang_ma (i); } hdeq_set_xover (); set_EQ_curve_values (0, 0.0); s_clear_history(); } void s_startElement(void *user_data, const xmlChar *name, const xmlChar **attrs) { const xmlChar **p; unsigned int i, found = 0; const char *symbol = NULL, *value = NULL, *index = NULL; xml_global_params *gp = user_data; int active = 0; int changed = 0; if (!strcmp(name, "jam-param-list")) { return; } if (!strcmp(name, "scene")) { const char *sname = NULL; for (p=attrs; p && *p; p+=2) { if (!strcmp(*p, "name")) { sname = *(p+1); } else if (!strcmp(*p, "number")) { gp->scene = atoi(*(p+1)); } else if (!strcmp(*p, "active") && !strcmp(*(p+1), "true")) { active = 1; } else if (!strcmp(*p, "changed") && !strcmp(*(p+1), "true")) { changed = 1; } } /* Set the active scene number to be set after parsing all of the scenes in the XML file. */ if (active) { saved_scene = gp->scene; set_scene(gp->scene); } if (changed) { saved_scene = gp->scene + 100; set_num_scene_warning_button(changed_scene_no(gp->scene)); } if (sname && gp->scene > -1) { set_scene(gp->scene); set_scene_name(gp->scene, sname); } return; } /* if its a global setting */ if (!strcmp(name, "global")) { /* find the name, value and index attributes */ for (p=attrs; p && *p; p+=2) { if (!strcmp(*p, "name")) { symbol = *(p+1); } else if (!strcmp(*p, "value")) { value = *(p+1); } else if (!strcmp(*p, "index")) { index = *(p+1); } } if (!strcmp(symbol, "mode")) { gp->mode = atoi(value); } else if (!strcmp(symbol, "freq")) { gp->freq = atof(value); } else if (!strcmp(symbol, "lgain")) { gp->lgain = atof(value); } else if (!strcmp(symbol, "hgain")) { gp->hgain = atof(value); } else if (!strcmp(symbol, "ct")) { gp->ct = atof(value); } else if ((const char *)strstr(symbol, "gang_") == symbol) { int ind = index ? atoi(index) : -1; int val = atoi(value); if (ind >= 0 && ind < XO_BANDS) { if (!strcmp(symbol, "gang_at")) { gp->gang_at[ind] = val; } else if (!strcmp(symbol, "gang_re")) { gp->gang_re[ind] = val; } else if (!strcmp(symbol, "gang_th")) { gp->gang_th[ind] = val; } else if (!strcmp(symbol, "gang_ra")) { gp->gang_ra[ind] = val; } else if (!strcmp(symbol, "gang_kn")) { gp->gang_kn[ind] = val; } else if (!strcmp(symbol, "gang_ma")) { gp->gang_ma[ind] = val; } else { errstr = g_strdup_printf("Unhandled gang: %s\n", symbol); message (GTK_MESSAGE_WARNING, errstr); free(errstr); } } else { errstr = g_strdup_printf("Unhandled index: %s\n", index); message (GTK_MESSAGE_WARNING, errstr); free(errstr); } } else { errstr = g_strdup_printf("Unhandled global paramter: %s\n", symbol); message (GTK_MESSAGE_WARNING, errstr); free(errstr); } return; } /* Check its a parameter element */ if (strcmp(name, "parameter")) { errstr = g_strdup_printf("Unhandled element: %s\n", name); message (GTK_MESSAGE_WARNING, errstr); free(errstr); } /* Find the name and value attributes */ for (p=attrs; p && *p; p+=2) { if (!strcmp(*p, "name")) { symbol = *(p+1); } else if (!strcmp(*p, "value")) { value = *(p+1); } } /* Find the matching symbol, this is horribly inefficient */ for (i=0; iscene == -1) { s_value[i] = atof(value); suppress_feedback++; s_set_events(i, s_value[i]); suppress_feedback--; } else { s_state *st = get_scene(gp->scene); if (st) { st->value[i] = atof(value); } else { errstr = g_strdup_printf("Bad scene number %d\n", gp->scene); message (GTK_MESSAGE_WARNING, errstr); free(errstr); } } //printf("load %s = %g\n", symbol, s_value[i]); found = 1; break; } } if (!found) { /* We need to disregard stereo-balance settings as these have been removed but may still be in old .jam files. */ if (!strstr (symbol ,"stereo-balance")) { errstr = g_strdup_printf("Unknown symbol: %s in element %s\n", symbol, name); message (GTK_MESSAGE_WARNING, errstr); free(errstr); } } } static void s_warning(void *user_data, const char *msg, ...) { va_list args; char *fmt; va_start(args, msg); fmt = g_strdup_printf("XML parser warning: %s", msg); vfprintf(stderr, fmt, args); free(fmt); va_end(args); } static void s_error(void *user_data, const char *msg, ...) { va_list args; char *fmt; va_start(args, msg); fmt = g_strdup_printf("XML parser error: %s", msg); vfprintf(stderr, fmt, args); free(fmt); va_end(args); *((int *)user_data) = LOAD_ERROR; } void s_crossfade(const int nframes) { unsigned int i; for (i=0; i nframes) { s_value[i] += ((float)nframes / (float)s_duration[i]) * (s_target[i] - s_value[i]); } else { s_value[i] = s_target[i]; s_duration[i] = 0; } s_changed[i] = 1; } } } void s_crossfade_ui() { unsigned int i; suppress_feedback++; for (i=0; i