changeset 149:fd9c0a5871ac trunk

[svn] - new and IMPROVED paranormal visualization studio
author nenolod
date Mon, 30 Oct 2006 23:02:33 -0800
parents 9d9fc9e1de48
children a089e72f4c25
files ChangeLog configure.ac src/paranormal/Makefile src/paranormal/actuators.c src/paranormal/actuators.h src/paranormal/builtins.c src/paranormal/cfg.c src/paranormal/cmaps.c src/paranormal/containers.c src/paranormal/containers.h src/paranormal/freq.c src/paranormal/general.c src/paranormal/paranormal.c src/paranormal/paranormal.h src/paranormal/plugin.c src/paranormal/pn_utils.h src/paranormal/presets.c src/paranormal/presets.h src/paranormal/wave.c src/paranormal/xform.c
diffstat 20 files changed, 3122 insertions(+), 3 deletions(-) [+]
line wrap: on
line diff
--- a/ChangeLog	Mon Oct 30 22:39:27 2006 -0800
+++ b/ChangeLog	Mon Oct 30 23:02:33 2006 -0800
@@ -1,3 +1,11 @@
+2006-10-31 06:39:27 +0000  William Pitcock <nenolod@nenolod.net>
+  revision [296]
+  - nuke paranormal (i will put a different paranormal back soon, no worries)
+  
+  trunk/configure.ac |    6 +++---
+  1 file changed, 3 insertions(+), 3 deletions(-)
+
+
 2006-10-31 06:35:55 +0000  William Pitcock <nenolod@nenolod.net>
   revision [294]
   - some tweaks
--- a/configure.ac	Mon Oct 30 22:39:27 2006 -0800
+++ b/configure.ac	Mon Oct 30 23:02:33 2006 -0800
@@ -1032,9 +1032,9 @@
 	have_xspf="no"
 ])
 
-#if test "$have_paranormal" = "yes"; then
-#	VISUALIZATION_PLUGINS="$VISUALIZATION_PLUGINS paranormal"
-#fi
+if test "$have_paranormal" = "yes"; then
+	VISUALIZATION_PLUGINS="$VISUALIZATION_PLUGINS paranormal"
+fi
 
 if test "$have_xspf" = "yes"; then
 	CONTAINER_PLUGINS="$CONTAINER_PLUGINS xspf"
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/paranormal/Makefile	Mon Oct 30 23:02:33 2006 -0800
@@ -0,0 +1,28 @@
+include ../../mk/rules.mk
+include ../../mk/init.mk
+
+OBJECTIVE_LIBS = libparanormal$(SHARED_SUFFIX)
+
+LIBDIR = $(plugindir)/$(VISUALIZATION_PLUGIN_DIR)
+
+LIBADD = $(GTK_LIBS) $(XML_LIBS) $(SDL_LIBS)
+
+SOURCES =			\
+	actuators.c		\
+	builtins.c		\
+	cfg.c			\
+	cmaps.c			\
+	containers.c		\
+	freq.c			\
+	general.c		\
+	paranormal.c		\
+	plugin.c		\
+	presets.c		\
+	wave.c			\
+	xform.c
+
+OBJECTS = ${SOURCES:.c=.o}
+
+CFLAGS += $(PICFLAGS) $(GTK_CFLAGS) $(ARCH_DEFINES) $(XML_CPPFLAGS) $(SDL_CFLAGS) -I../../intl -I../..
+
+include ../../mk/objective.mk
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/paranormal/actuators.c	Mon Oct 30 23:02:33 2006 -0800
@@ -0,0 +1,177 @@
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#include <glib.h>
+
+#include "actuators.h"
+//#include "containers.h"
+
+/* FIXME: container options override containees - fix this? */
+/* FIXME: add actuator groups (by a group name string) */
+
+/* FIXME: add support for copying containers' children (optionally) */
+struct pn_actuator *
+copy_actuator (const struct pn_actuator *a)
+{
+  struct pn_actuator *actuator;
+  int i;    
+
+  actuator = g_new (struct pn_actuator, 1);
+
+  actuator->desc = a->desc;
+
+  /* Make an options table */
+  if (actuator->desc->option_descs)
+    {
+      /* count the options */
+      for (i=0; actuator->desc->option_descs[i].name; i++);
+
+      actuator->options = g_new (struct pn_actuator_option, i);
+      for (i=0; actuator->desc->option_descs[i].name; i++)
+	{
+	  actuator->options[i].desc = &actuator->desc->option_descs[i];
+
+	  /* copy the default options */
+	  switch (actuator->desc->option_descs[i].type)
+	    {
+	    case OPT_TYPE_INT:
+	    case OPT_TYPE_COLOR_INDEX:
+	    case OPT_TYPE_FLOAT:
+	    case OPT_TYPE_COLOR:
+	    case OPT_TYPE_BOOLEAN:
+	      memcpy (&actuator->options[i].val,
+		      &a->options[i].val,
+		      sizeof (union actuator_option_val));
+	      break;
+	    case OPT_TYPE_STRING:
+	    default:
+	      break;
+	    }
+	}
+
+      /* the NULL option */
+      actuator->options[i].desc = NULL;
+    }
+  else
+    actuator->options = NULL;
+
+  if (actuator->desc->init)
+    actuator->desc->init (&actuator->data);
+
+  return actuator;
+}
+
+struct pn_actuator_desc *
+get_actuator_desc (const char *name)
+{
+  int i;
+
+  for (i=0; builtin_table[i]; i++)
+    if (! g_strcasecmp (name, builtin_table[i]->name))
+      break;
+
+  /* actuator not found */
+  if (! builtin_table[i])
+    return NULL;
+
+  return builtin_table[i];
+}
+
+struct pn_actuator *
+create_actuator (const char *name)
+{
+  int i;
+  struct pn_actuator_desc *desc;
+  struct pn_actuator *actuator;
+
+  /* find the actuatoreration */
+  desc = get_actuator_desc (name);
+
+  if (! desc)
+    return NULL;
+
+  actuator = g_new (struct pn_actuator, 1);
+  actuator->desc = desc;
+
+  /* Make an options table */
+  if (actuator->desc->option_descs)
+    {
+      /* count the options */
+      for (i=0; actuator->desc->option_descs[i].name; i++);
+
+      actuator->options = g_new (struct pn_actuator_option, i);
+      for (i=0; actuator->desc->option_descs[i].name; i++)
+	{
+	  actuator->options[i].desc = &actuator->desc->option_descs[i];
+
+	  /* copy the default options */
+	  switch (actuator->desc->option_descs[i].type)
+	    {
+	    case OPT_TYPE_INT:
+	    case OPT_TYPE_COLOR_INDEX:
+	    case OPT_TYPE_FLOAT:
+	    case OPT_TYPE_COLOR:
+	    case OPT_TYPE_BOOLEAN:
+	      memcpy (&actuator->options[i].val,
+		      &actuator->desc->option_descs[i].default_val,
+		      sizeof (union actuator_option_val));
+	      break;
+	    case OPT_TYPE_STRING:
+	      /* NOTE: It's not realloc'ed so don't free it */
+	      actuator->options[i].val.sval =
+		actuator->desc->option_descs[i].default_val.sval;
+	      break;
+	    }
+	}
+
+      /* the NULL option */
+      actuator->options[i].desc = NULL;
+    }
+  else
+    actuator->options = NULL;
+
+  if (actuator->desc->init)
+    actuator->desc->init (&actuator->data);
+
+  return actuator;
+}
+
+void
+destroy_actuator (struct pn_actuator *actuator)
+{
+  int i;
+
+  if (actuator->desc->cleanup)
+    actuator->desc->cleanup (actuator->data);
+
+  /* find any option val's that need to be freed */
+  if (actuator->options)
+    for (i=0; actuator->options[i].desc; i++)
+      switch (actuator->options[i].desc->type)
+	{
+	case OPT_TYPE_INT:
+	case OPT_TYPE_FLOAT:
+	case OPT_TYPE_COLOR:
+	case OPT_TYPE_COLOR_INDEX:
+	case OPT_TYPE_BOOLEAN:
+	  break;
+	case OPT_TYPE_STRING:
+	  if (actuator->options[i].val.sval
+	      != actuator->options[i].desc->default_val.sval)
+	    g_free ((char *)actuator->options[i].val.sval);
+	}
+
+  g_free (actuator->options);
+  g_free (actuator);
+}
+
+void
+exec_actuator (struct pn_actuator *actuator)
+{
+  g_assert (actuator);
+  g_assert (actuator->desc);
+  g_assert (actuator->desc->exec);
+  actuator->desc->exec (actuator->options, actuator->data);
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/paranormal/actuators.h	Mon Oct 30 23:02:33 2006 -0800
@@ -0,0 +1,98 @@
+/* FIXME: rename actuators to pn_actuators */
+/* FIXME: add a color type to the OPT_TYPE's */
+
+#ifndef _ACTUATORS_H
+#define _ACTUATORS_H
+
+#include <glib.h>
+#include <SDL.h>
+
+/* Helper macros for actuator functions */
+#define PN_ACTUATOR_INIT_FUNC(f) ((void (*) (gpointer *data))f)
+#define PN_ACTUATOR_CLEANUP_FUNC(f) ((void (*) (gpointer data))f)
+#define PN_ACTUATOR_EXEC_FUNC(f) \
+((void (*) (const struct pn_actuator_option *opts, gpointer data))f)
+
+struct pn_color
+{
+  guchar r, g, b;
+  guchar spluzz; /* gotta look like an SDL_Color */
+};
+
+
+union actuator_option_val
+{
+  int ival;
+  float fval;
+  const char *sval;
+  struct pn_color cval;
+  gboolean bval;
+};
+
+/* A actuator's option description */
+struct pn_actuator_option_desc
+{
+  const char *name;
+  const char *doc; /* option documentation */
+  enum
+  {
+    OPT_TYPE_INT = 0,
+    OPT_TYPE_FLOAT = 1,
+    OPT_TYPE_STRING = 2,
+    OPT_TYPE_COLOR = 3,
+    OPT_TYPE_COLOR_INDEX = 4, /* uses ival */
+    OPT_TYPE_BOOLEAN = 5
+  } type;
+  union actuator_option_val default_val;
+};
+
+/* The actual option instance */
+struct pn_actuator_option
+{
+  const struct pn_actuator_option_desc *desc;
+  union actuator_option_val val;
+};
+
+/* An operation's description */
+struct pn_actuator_desc
+{
+  const char *name;
+  const char *doc; /* documentation txt */
+  const enum
+  {
+    ACTUATOR_FLAG_CONTAINER = 1<<0
+  } flags;
+
+  /* A null terminating (ie a actuator_option_desc == {0,...,0})
+     array - OPTIONAL (optional fields can be NULL) */
+  const struct pn_actuator_option_desc *option_descs;
+
+  /* Init function - data points to the actuator.data - OPTIONAL */
+  void (*init) (gpointer *data);
+  
+  /* Cleanup actuatortion - OPTIONAL */
+  void (*cleanup) (gpointer data);
+
+  /* Execute actuatortion - REQUIRED (duh!) */
+  void (*exec) (const struct pn_actuator_option *opts, gpointer data);
+};
+
+/* An actual operation instance */
+struct pn_actuator
+{
+  const struct pn_actuator_desc *desc;
+  struct pn_actuator_option *options;
+  gpointer data;
+};
+
+/* The array containing all operations (see builtins.c) */
+extern struct pn_actuator_desc *builtin_table[];
+
+/* functions for actuators */
+struct pn_actuator_desc *get_actuator_desc (const char *name);
+struct pn_actuator *copy_actuator (const struct pn_actuator *a);
+struct pn_actuator *create_actuator (const char *name);
+void destroy_actuator (struct pn_actuator *actuator);
+void exec_actuator (struct pn_actuator *actuator);
+
+#endif /* _ACTUATORS_H */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/paranormal/builtins.c	Mon Oct 30 23:02:33 2006 -0800
@@ -0,0 +1,68 @@
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#include "actuators.h"
+
+#define DECLARE_ACTUATOR(a) extern struct pn_actuator_desc builtin_##a;
+
+/* **************** containers **************** */
+
+DECLARE_ACTUATOR (container_simple);
+DECLARE_ACTUATOR (container_once);
+DECLARE_ACTUATOR (container_cycle);
+
+/* **************** cmaps **************** */
+
+DECLARE_ACTUATOR (cmap_bwgradient);
+DECLARE_ACTUATOR (cmap_gradient);
+
+/* **************** freq **************** */
+DECLARE_ACTUATOR (freq_dots);
+DECLARE_ACTUATOR (freq_drops);
+
+/* **************** general **************** */
+DECLARE_ACTUATOR (general_fade);
+DECLARE_ACTUATOR (general_blur);
+
+/* **************** wave **************** */
+DECLARE_ACTUATOR (wave_horizontal);
+DECLARE_ACTUATOR (wave_vertical);
+DECLARE_ACTUATOR (wave_normalize);
+DECLARE_ACTUATOR (wave_smooth);
+DECLARE_ACTUATOR (wave_radial);
+
+/* **************** xform **************** */
+DECLARE_ACTUATOR (xform_spin);
+DECLARE_ACTUATOR (xform_ripple);
+DECLARE_ACTUATOR (xform_bump_spin);
+
+/* **************** builtin_table **************** */
+struct pn_actuator_desc *builtin_table[] =
+{
+  /* **************** containers **************** */
+  &builtin_container_simple,
+  &builtin_container_once,
+  &builtin_container_cycle,
+  /* **************** cmaps **************** */
+  &builtin_cmap_bwgradient,
+  &builtin_cmap_gradient,
+  /* **************** freq **************** */
+  &builtin_freq_dots,
+  &builtin_freq_drops,
+  /* **************** general **************** */
+  &builtin_general_fade,
+  &builtin_general_blur,
+  /* **************** wave **************** */
+  &builtin_wave_horizontal,
+  &builtin_wave_vertical,
+  &builtin_wave_normalize,
+  &builtin_wave_smooth,
+  &builtin_wave_radial,
+  /* **************** xform **************** */
+  &builtin_xform_spin,
+  &builtin_xform_ripple,
+  &builtin_xform_bump_spin,
+  /* **************** the end! **************** */
+  NULL
+};
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/paranormal/cfg.c	Mon Oct 30 23:02:33 2006 -0800
@@ -0,0 +1,625 @@
+/* FIXME: prevent the user from dragging something above the root
+   actuator */
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#include <glib.h>
+#include <gtk/gtk.h>
+
+#include "paranormal.h"
+#include "actuators.h"
+#include "containers.h"
+#include "presets.h"
+
+/* DON'T CALL pn_fatal_error () IN HERE!!! */
+
+/* Actuator page stuffs */
+static GtkWidget *cfg_dialog, *actuator_tree, *option_frame, *actuator_option_table;
+static GtkWidget *actuator_add_opmenu, *actuator_add_button, *actuator_remove_button;
+static GtkCTreeNode *selected_actuator_node;
+static GtkTooltips *actuator_tooltips;
+
+/* This is used so that actuator_row_data_destroyed_cb won't free
+   the actuator associated w/ the node since we're going to be using it */
+gboolean destroy_row_data = TRUE;
+
+static void
+actuator_row_data_destroyed_cb (struct pn_actuator *a)
+{
+  if (a && destroy_row_data)
+    destroy_actuator (a);
+}
+
+static void
+add_actuator (struct pn_actuator *a, GtkCTreeNode *parent, gboolean copy)
+{
+  GtkCTreeNode *node;
+  GSList *l;
+
+  g_assert (cfg_dialog);
+  g_assert (actuator_tree);
+  g_assert (actuator_option_table);
+
+  node = gtk_ctree_insert_node (GTK_CTREE (actuator_tree), parent,
+				NULL, (gchar**)&a->desc->name, 0,
+				NULL, NULL, NULL, NULL,
+				a->desc->flags & ACTUATOR_FLAG_CONTAINER
+				? FALSE : TRUE,
+				TRUE);
+
+  if (a->desc->flags & ACTUATOR_FLAG_CONTAINER)
+    for (l=*(GSList **)a->data; l; l = l->next)
+      {
+	add_actuator (l->data, node, copy);
+      }
+
+  if (copy)
+    a = copy_actuator (a);
+  else if (a->desc->flags & ACTUATOR_FLAG_CONTAINER)
+    container_unlink_actuators (a);
+
+  gtk_ctree_node_set_row_data_full (GTK_CTREE (actuator_tree), node, a,
+				    ((GtkDestroyNotify) actuator_row_data_destroyed_cb));
+}
+
+static void
+int_changed_cb (GtkSpinButton *sb, int *i)
+{
+  *i = gtk_spin_button_get_value_as_int (sb);
+}
+
+static void
+float_changed_cb (GtkSpinButton *sb, float *f)
+{
+  *f = gtk_spin_button_get_value_as_float (sb);
+}
+
+static void
+string_changed_cb (GtkEditable *t, char **s)
+{
+  if (*s != gtk_object_get_data (GTK_OBJECT (t), "DEFAULT_OP_STRING"))
+    g_free (*s);
+
+  *s = gtk_editable_get_chars (t, 0, -1);
+}
+
+static void
+color_changed_cb (GtkSpinButton *sb, guchar *c)
+{
+  *c = gtk_spin_button_get_value_as_int (sb);
+}
+
+static void
+boolean_changed_cb (GtkToggleButton *tb, gboolean *b)
+{
+  *b = gtk_toggle_button_get_active (tb);
+}
+
+static void
+row_select_cb (GtkCTree *ctree, GtkCTreeNode *node,
+	       gint column, gpointer data)
+{
+  struct pn_actuator *a;
+  int opt_count = 0, i, j;
+  GtkWidget *w;
+  GtkObject *adj;
+
+  a = (struct pn_actuator *)gtk_ctree_node_get_row_data (ctree, node);
+
+  /* count the actuator's options (plus one) */
+  if (a->desc->option_descs)
+    while (a->desc->option_descs[opt_count++].name);
+  else
+    opt_count = 1;
+
+  gtk_table_resize (GTK_TABLE (actuator_option_table), opt_count, 2);
+
+  /* Actuator name */
+  gtk_frame_set_label (GTK_FRAME (option_frame), a->desc->name);
+
+  /* Actuator description */
+  w = gtk_label_new (a->desc->doc);
+  gtk_label_set_line_wrap (GTK_LABEL (w), TRUE);
+  gtk_label_set_justify (GTK_LABEL (w), GTK_JUSTIFY_LEFT);
+  gtk_misc_set_alignment (GTK_MISC (w), 0, .5);
+  gtk_widget_show (w);
+  gtk_table_attach (GTK_TABLE (actuator_option_table), w, 0, 2, 0, 1,
+		    GTK_EXPAND | GTK_SHRINK | GTK_FILL, 0,
+		    3, 3);
+
+  /* now add the options */
+  for (i=1, j=0; i<opt_count; j++, i++)
+    {
+      w = gtk_label_new (a->desc->option_descs[j].name);
+      gtk_widget_show (w);
+      gtk_table_attach (GTK_TABLE (actuator_option_table), w,
+			0, 1, i, i+1,
+			GTK_SHRINK | GTK_FILL, 0,
+			3, 3);
+      switch (a->desc->option_descs[j].type)
+	{
+	case OPT_TYPE_INT:
+	  adj = gtk_adjustment_new (a->options[j].val.ival,
+				    G_MININT, G_MAXINT,
+				    1, 2, 0);
+	  w = gtk_spin_button_new (GTK_ADJUSTMENT (adj), 1.0, 0);
+	  gtk_signal_connect (GTK_OBJECT (w), "changed",
+			      GTK_SIGNAL_FUNC (int_changed_cb),
+			      &a->options[j].val.ival);
+	  break;
+	case OPT_TYPE_FLOAT:
+	  adj = gtk_adjustment_new (a->options[j].val.fval,
+				    -G_MAXFLOAT, G_MAXFLOAT,
+				    1, 2, 0);
+	  w = gtk_spin_button_new (GTK_ADJUSTMENT (adj), 1.0, 5);
+	  gtk_signal_connect (GTK_OBJECT (w), "changed",
+			      GTK_SIGNAL_FUNC (float_changed_cb),
+			      &a->options[j].val.fval);
+	  break;
+	case OPT_TYPE_STRING:
+	  w = gtk_entry_new ();
+	  gtk_widget_show (w);
+	  gtk_entry_set_text (GTK_ENTRY (w), a->options[j].val.sval);
+	  gtk_object_set_data (GTK_OBJECT (w), "DEFAULT_OP_STRING",
+			       (gpointer)a->desc->option_descs[j].default_val.sval);
+	  gtk_signal_connect (GTK_OBJECT (w), "changed",
+			      GTK_SIGNAL_FUNC (string_changed_cb),
+			      &a->options[j].val.sval);
+	  break;
+	case OPT_TYPE_COLOR:
+	  {
+	    /* FIXME: add some color preview */
+	    GtkWidget *hbox;
+	    hbox = gtk_hbox_new (FALSE, 0);
+	    adj = gtk_adjustment_new (a->options[j].val.cval.r,
+				      0, 255,
+				      1, 2, 0);
+	    w = gtk_spin_button_new (GTK_ADJUSTMENT (adj), 1.0, 0);
+	    gtk_widget_show (w);
+	    gtk_signal_connect (GTK_OBJECT (w), "changed",
+			       GTK_SIGNAL_FUNC (color_changed_cb),
+			       &a->options[j].val.cval.r);
+	    gtk_tooltips_set_tip (actuator_tooltips, w,
+				  a->desc->option_descs[j].doc, NULL);
+	    gtk_box_pack_start (GTK_BOX (hbox), w, TRUE, TRUE, 0);
+	    adj = gtk_adjustment_new (a->options[j].val.cval.g,
+				      0, 255,
+				      1, 2, 0);
+	    w = gtk_spin_button_new (GTK_ADJUSTMENT (adj), 1.0, 0);
+	    gtk_widget_show (w);
+	    gtk_signal_connect (GTK_OBJECT (w), "changed",
+			       GTK_SIGNAL_FUNC (color_changed_cb),
+			       &a->options[j].val.cval.g);
+	    gtk_tooltips_set_tip (actuator_tooltips, w,
+				  a->desc->option_descs[j].doc, NULL);
+	    gtk_box_pack_start (GTK_BOX (hbox), w, TRUE, TRUE, 6);
+	    adj = gtk_adjustment_new (a->options[j].val.cval.b,
+				      0, 255,
+				      1, 2, 0);
+	    w = gtk_spin_button_new (GTK_ADJUSTMENT (adj), 1.0, 0);
+	    gtk_widget_show (w);
+	    gtk_signal_connect (GTK_OBJECT (w), "changed",
+			       GTK_SIGNAL_FUNC (color_changed_cb),
+			       &a->options[j].val.cval.b);
+	    gtk_tooltips_set_tip (actuator_tooltips, w,
+				  a->desc->option_descs[j].doc, NULL);
+	    gtk_box_pack_start (GTK_BOX (hbox), w, TRUE, TRUE, 0);
+	    w = hbox;
+	  }
+	  break;	  
+	case OPT_TYPE_COLOR_INDEX:
+	  adj = gtk_adjustment_new (a->options[j].val.ival,
+				    0, 255,
+				    1, 2, 0);
+	  w = gtk_spin_button_new (GTK_ADJUSTMENT (adj), 1.0, 0);
+	  gtk_signal_connect (GTK_OBJECT (w), "changed",
+			      GTK_SIGNAL_FUNC (int_changed_cb),
+			      &a->options[j].val.ival);
+	  break;
+	case OPT_TYPE_BOOLEAN:
+	  w = gtk_check_button_new ();
+	  gtk_widget_show (w);
+	  gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (w),
+					a->options[j].val.bval);
+	  gtk_signal_connect (GTK_OBJECT (w), "clicked",
+			      GTK_SIGNAL_FUNC (boolean_changed_cb),
+			      &a->options[j].val.bval);
+	  break;
+	}
+      gtk_widget_show (w);
+      gtk_tooltips_set_tip (actuator_tooltips, w,
+			    a->desc->option_descs[j].doc, NULL);
+      gtk_table_attach (GTK_TABLE (actuator_option_table), w,
+			1, 2, i, i+1,
+			GTK_EXPAND | GTK_SHRINK | GTK_FILL,
+			0,
+			3, 3);
+    }
+
+  gtk_widget_set_sensitive (actuator_remove_button, TRUE);
+  gtk_widget_set_sensitive (actuator_add_button, a->desc->flags & ACTUATOR_FLAG_CONTAINER
+			? TRUE : FALSE);
+
+  selected_actuator_node = node;
+}
+
+static void
+table_remove_all_cb (GtkWidget *widget, gpointer data)
+{
+  gtk_container_remove (GTK_CONTAINER (actuator_option_table),
+			widget);
+}
+
+static void
+row_unselect_cb (GtkCTree *ctree, GList *node, gint column,
+		 gpointer user_data)
+{
+  gtk_frame_set_label (GTK_FRAME (option_frame), NULL);
+
+  gtk_container_foreach (GTK_CONTAINER (actuator_option_table),
+			 table_remove_all_cb, NULL);
+
+  /* Can't remove something if nothing's selected */
+  gtk_widget_set_sensitive (actuator_remove_button, FALSE);
+
+  selected_actuator_node = NULL;
+}
+
+static void
+add_actuator_cb (GtkButton *button, gpointer data)
+{
+  char *actuator_name;
+  struct pn_actuator *a;
+  
+  gtk_label_get (GTK_LABEL (GTK_BIN (actuator_add_opmenu)->child),
+		 &actuator_name);
+
+  a = create_actuator (actuator_name);
+  g_assert (a);
+
+  add_actuator (a, selected_actuator_node, FALSE);
+}
+
+static void
+remove_actuator_cb (GtkButton *button, gpointer data)
+{
+  if (selected_actuator_node)
+    gtk_ctree_remove_node (GTK_CTREE (actuator_tree),
+			   selected_actuator_node);
+}
+
+/* Connect a node to its parent and replace the row data with
+   a copy of the node */
+static void
+connect_actuators_cb (GtkCTree *ctree, GtkCTreeNode *node,
+		      struct pn_actuator **root_ptr)
+{
+  struct pn_actuator *actuator, *parent, *copy;
+
+  actuator = (struct pn_actuator *) gtk_ctree_node_get_row_data (ctree, node);
+  if (GTK_CTREE_ROW (node)->parent)
+    {
+      /* Connect it to the parent */
+      parent = (struct pn_actuator *)
+	gtk_ctree_node_get_row_data (ctree, GTK_CTREE_ROW (node)->parent);
+      container_add_actuator (parent, actuator);
+    }
+  else
+    /* This is the root node; still gotta copy it, but we need to
+       save the original to *root_ptr */
+    *root_ptr = actuator;
+
+  /* we don't want our copy getting destroyed */
+  destroy_row_data = FALSE;
+
+  copy = copy_actuator (actuator);
+  gtk_ctree_node_set_row_data_full (ctree, node, copy,
+				    ((GtkDestroyNotify)actuator_row_data_destroyed_cb));
+
+  /* Ok, now you can destroy it */
+  destroy_row_data = TRUE;
+}
+
+/* Extract (and connect) the actuators in the tree */
+static struct pn_actuator *
+extract_actuator (void)
+{
+  GtkCTreeNode *root, *selected;
+  struct pn_actuator *root_actuator = NULL;
+
+  root = gtk_ctree_node_nth (GTK_CTREE (actuator_tree), 0);
+  if (root)
+    gtk_ctree_post_recursive (GTK_CTREE (actuator_tree), root,
+			      GTK_CTREE_FUNC (connect_actuators_cb),
+			      &root_actuator);
+
+  if (selected_actuator_node)
+    {
+      selected = selected_actuator_node;
+      gtk_ctree_unselect (GTK_CTREE (actuator_tree), GTK_CTREE_NODE (selected));
+      gtk_ctree_select (GTK_CTREE (actuator_tree), GTK_CTREE_NODE (selected));
+    }
+
+  return root_actuator;
+}
+
+/* If selector != NULL, then it's 'OK', otherwise it's 'Cancel' */
+static void
+load_sel_cb (GtkButton *button, GtkFileSelection *selector)
+{
+  if (selector)
+    {
+      char *fname;
+      struct pn_actuator *a;
+      GtkCTreeNode *root;
+
+      fname = gtk_file_selection_get_filename (selector);
+      a = load_preset (fname);
+      if (! a)
+	pn_error ("Unable to load file: \"%s\"", fname);
+      else
+	{
+	  if ((root = gtk_ctree_node_nth (GTK_CTREE (actuator_tree), 0)))
+	    gtk_ctree_remove_node (GTK_CTREE (actuator_tree), root);
+	  add_actuator (a, NULL, FALSE);
+	}
+    }
+
+  gtk_widget_set_sensitive (cfg_dialog, TRUE);
+}
+
+static void
+load_button_cb (GtkButton *button, gpointer data)
+{
+  GtkWidget *selector;
+  
+  selector = gtk_file_selection_new ("Load Preset");
+
+  gtk_signal_connect (GTK_OBJECT (GTK_FILE_SELECTION (selector)->ok_button),
+		      "clicked", GTK_SIGNAL_FUNC (load_sel_cb), selector);
+  gtk_signal_connect (GTK_OBJECT (GTK_FILE_SELECTION (selector)->cancel_button),
+		      "clicked", GTK_SIGNAL_FUNC (load_sel_cb), NULL);
+
+  gtk_signal_connect_object (GTK_OBJECT (GTK_FILE_SELECTION (selector)->ok_button),
+			     "clicked", GTK_SIGNAL_FUNC (gtk_widget_destroy),
+			     (gpointer) selector);
+  gtk_signal_connect_object (GTK_OBJECT (GTK_FILE_SELECTION (selector)->cancel_button),
+			     "clicked", GTK_SIGNAL_FUNC (gtk_widget_destroy),
+			     (gpointer) selector);
+
+  gtk_widget_set_sensitive (cfg_dialog, FALSE);
+  gtk_widget_show (selector);
+}
+
+static void
+save_sel_cb (GtkButton *button, GtkFileSelection *selector)
+{
+  if (selector)
+    {
+      char *fname;
+      struct pn_actuator *a;
+
+      fname = gtk_file_selection_get_filename (selector);
+      a = extract_actuator ();
+
+      if (! save_preset (fname, a))
+	pn_error ("unable to save preset to file: %s", fname);
+    }
+
+  gtk_widget_set_sensitive (cfg_dialog, TRUE);
+}
+
+static void
+save_button_cb (GtkButton *button, gpointer data)
+{
+  GtkWidget *selector;
+
+  selector = gtk_file_selection_new ("Save Preset");
+
+  gtk_signal_connect (GTK_OBJECT (GTK_FILE_SELECTION (selector)->ok_button),
+		      "clicked", GTK_SIGNAL_FUNC (save_sel_cb), selector);
+  gtk_signal_connect (GTK_OBJECT (GTK_FILE_SELECTION (selector)->cancel_button),
+		      "clicked", GTK_SIGNAL_FUNC (save_sel_cb), NULL);
+
+  gtk_signal_connect_object (GTK_OBJECT (GTK_FILE_SELECTION (selector)->ok_button),
+			     "clicked", GTK_SIGNAL_FUNC (gtk_widget_destroy),
+			     (gpointer) selector);
+  gtk_signal_connect_object (GTK_OBJECT (GTK_FILE_SELECTION (selector)->cancel_button),
+			     "clicked", GTK_SIGNAL_FUNC (gtk_widget_destroy),
+			     (gpointer) selector);
+
+  gtk_widget_set_sensitive (cfg_dialog, FALSE);
+  gtk_widget_show (selector);
+}
+
+static void
+apply_settings (void)
+{
+  struct pn_rc rc;
+
+  rc.actuator = extract_actuator ();
+
+  pn_set_rc (&rc);
+}
+
+static void
+apply_button_cb (GtkButton *button, gpointer data)
+{
+  apply_settings ();
+}
+
+static void
+ok_button_cb (GtkButton *button, gpointer data)
+{
+  apply_settings ();
+  gtk_widget_hide (cfg_dialog);
+}
+
+static void
+cancel_button_cb (GtkButton *button, gpointer data)
+{
+  gtk_widget_destroy (cfg_dialog);
+  cfg_dialog = NULL;
+}
+
+void
+pn_configure (void)
+{
+  GtkWidget *notebook, *label, *scrollwindow, *menu, *menuitem;
+  GtkWidget *paned, *vbox, *table, *bbox, *button;
+  int i;
+  
+
+  if (! cfg_dialog)
+    {
+      /* The dialog */
+      cfg_dialog = gtk_dialog_new ();
+      gtk_window_set_title (GTK_WINDOW (cfg_dialog), "Configuration - "
+			    PACKAGE " " VERSION);
+      gtk_widget_set_usize (cfg_dialog, 530, 370);
+      gtk_container_border_width (GTK_CONTAINER (cfg_dialog), 8);
+      gtk_signal_connect_object (GTK_OBJECT (cfg_dialog), "delete-event",
+				 GTK_SIGNAL_FUNC (gtk_widget_hide),
+				 GTK_OBJECT (cfg_dialog));
+
+      /* The notebook */
+      notebook = gtk_notebook_new ();
+      gtk_widget_show (notebook);
+      gtk_box_pack_start (GTK_BOX (GTK_DIALOG (cfg_dialog)->vbox), notebook,
+			  TRUE, TRUE, 0);
+
+      /* Actuator page */
+      paned = gtk_hpaned_new ();
+      gtk_widget_show (paned);
+      label = gtk_label_new ("Actuators");
+      gtk_widget_show (label);
+      gtk_notebook_append_page (GTK_NOTEBOOK (notebook), paned, label);
+      vbox = gtk_vbox_new (FALSE, 3);
+      gtk_widget_show (vbox);
+      gtk_paned_pack1 (GTK_PANED (paned), vbox, TRUE, FALSE);
+      scrollwindow = gtk_scrolled_window_new (NULL, NULL);
+      gtk_widget_show (scrollwindow);
+      gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrollwindow),
+				      GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
+      gtk_box_pack_start (GTK_BOX (vbox), scrollwindow, TRUE, TRUE, 3);
+      actuator_tree = gtk_ctree_new (1, 0);
+      gtk_widget_show (actuator_tree);
+      gtk_ctree_set_reorderable (GTK_CTREE (actuator_tree), TRUE);
+      gtk_signal_connect (GTK_OBJECT (actuator_tree), "tree-select-row",
+			  GTK_SIGNAL_FUNC (row_select_cb), NULL);
+      gtk_signal_connect (GTK_OBJECT (actuator_tree), "tree-unselect-row",
+			  GTK_SIGNAL_FUNC (row_unselect_cb), NULL);
+      gtk_scrolled_window_add_with_viewport (GTK_SCROLLED_WINDOW (scrollwindow),
+					     actuator_tree);
+      table = gtk_table_new (3, 2, TRUE);
+      gtk_widget_show (table);
+      gtk_box_pack_start (GTK_BOX (vbox), table, FALSE, FALSE, 3);      
+      actuator_add_opmenu = gtk_option_menu_new ();
+      gtk_widget_show (actuator_add_opmenu);
+      menu = gtk_menu_new ();
+      gtk_widget_show (menu);
+      for (i=0; builtin_table[i]; i++)
+	{
+	  /* FIXME: Add actuator group support */
+	  menuitem = gtk_menu_item_new_with_label (builtin_table[i]->name);
+	  gtk_widget_show (menuitem);
+	  gtk_menu_append (GTK_MENU (menu), menuitem);
+	}
+      gtk_option_menu_set_menu (GTK_OPTION_MENU (actuator_add_opmenu), menu);
+      gtk_table_attach (GTK_TABLE (table), actuator_add_opmenu,
+			0, 2, 0, 1,
+			GTK_EXPAND | GTK_SHRINK | GTK_FILL, 0,
+			3, 3);
+      actuator_add_button = gtk_button_new_with_label ("Add");
+      gtk_widget_show (actuator_add_button);
+      gtk_signal_connect (GTK_OBJECT (actuator_add_button), "clicked",
+			  GTK_SIGNAL_FUNC (add_actuator_cb), NULL);
+      gtk_table_attach (GTK_TABLE (table), actuator_add_button,
+			0, 1, 1, 2,
+			GTK_EXPAND | GTK_SHRINK | GTK_FILL, 0,
+			3, 3);
+      actuator_remove_button = gtk_button_new_with_label ("Remove");
+      gtk_widget_set_sensitive (actuator_remove_button, FALSE);
+      gtk_widget_show (actuator_remove_button);
+      gtk_signal_connect (GTK_OBJECT (actuator_remove_button), "clicked",
+			  GTK_SIGNAL_FUNC (remove_actuator_cb), NULL);
+      gtk_table_attach (GTK_TABLE (table), actuator_remove_button,
+			1, 2, 1, 2,
+			GTK_EXPAND | GTK_SHRINK | GTK_FILL, 0,
+			3, 3);
+      button = gtk_button_new_with_label ("Load");
+      gtk_widget_show (button);
+      gtk_signal_connect (GTK_OBJECT (button), "clicked",
+			  GTK_SIGNAL_FUNC (load_button_cb), NULL);
+      gtk_table_attach (GTK_TABLE (table), button,
+			0, 1, 2, 3,
+			GTK_EXPAND | GTK_SHRINK | GTK_FILL, 0,
+			3, 3);
+      button = gtk_button_new_with_label ("Save");
+      gtk_widget_show (button);
+      gtk_signal_connect (GTK_OBJECT (button), "clicked",
+			  GTK_SIGNAL_FUNC (save_button_cb), NULL);
+      gtk_table_attach (GTK_TABLE (table), button,
+			1, 2, 2, 3,
+			GTK_EXPAND | GTK_SHRINK | GTK_FILL, 0,
+			3, 3);
+
+      /* Option table */
+      option_frame = gtk_frame_new (NULL);
+      gtk_widget_show (option_frame);
+      gtk_container_set_border_width (GTK_CONTAINER (option_frame), 3);
+      gtk_paned_pack2 (GTK_PANED (paned), option_frame, TRUE, TRUE);
+      scrollwindow = gtk_scrolled_window_new (NULL, NULL);
+      gtk_widget_show (scrollwindow);
+      gtk_container_set_border_width (GTK_CONTAINER (scrollwindow), 3);
+      gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrollwindow),
+				      GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
+      gtk_container_add (GTK_CONTAINER (option_frame), scrollwindow);
+      actuator_option_table = gtk_table_new (0, 2, FALSE);
+      gtk_widget_show (actuator_option_table);
+      gtk_scrolled_window_add_with_viewport (GTK_SCROLLED_WINDOW (scrollwindow),
+					     actuator_option_table);
+      gtk_paned_set_position (GTK_PANED (paned), 0);
+      actuator_tooltips = gtk_tooltips_new ();
+      gtk_tooltips_enable (actuator_tooltips);
+
+      /* Build the initial actuator actuator_tree */
+      if (pn_rc->actuator)
+	{
+	  add_actuator (pn_rc->actuator, NULL, TRUE);
+	  gtk_widget_set_sensitive (actuator_add_button, FALSE);
+	}
+
+      /* OK / Apply / Cancel */
+      bbox = gtk_hbutton_box_new ();
+      gtk_widget_show (bbox);
+      gtk_button_box_set_layout (GTK_BUTTON_BOX (bbox), GTK_BUTTONBOX_END);
+      gtk_button_box_set_spacing (GTK_BUTTON_BOX (bbox), 8);
+      gtk_button_box_set_child_size (GTK_BUTTON_BOX (bbox), 64, 0);
+      gtk_box_pack_start (GTK_BOX (GTK_DIALOG (cfg_dialog)->action_area),
+			  bbox, FALSE, FALSE, 0);
+      button = gtk_button_new_with_label ("OK");
+      gtk_widget_show (button);
+      gtk_button_set_relief (GTK_BUTTON (button), GTK_RELIEF_NORMAL);
+      gtk_signal_connect (GTK_OBJECT (button), "clicked",
+			  GTK_SIGNAL_FUNC (ok_button_cb), NULL);
+      gtk_box_pack_start (GTK_BOX (bbox), button, FALSE, FALSE, 0);
+      button = gtk_button_new_with_label ("Apply");
+      gtk_widget_show (button);
+      gtk_button_set_relief (GTK_BUTTON (button), GTK_RELIEF_NORMAL);
+      gtk_signal_connect (GTK_OBJECT (button), "clicked",
+			  GTK_SIGNAL_FUNC (apply_button_cb), NULL);
+      gtk_box_pack_start (GTK_BOX (bbox), button, FALSE, FALSE, 0);
+      button = gtk_button_new_with_label ("Cancel");
+      gtk_widget_show (button);
+      gtk_button_set_relief (GTK_BUTTON (button), GTK_RELIEF_NORMAL);
+      gtk_signal_connect (GTK_OBJECT (button), "clicked",
+			  GTK_SIGNAL_FUNC (cancel_button_cb), NULL);
+      gtk_box_pack_start (GTK_BOX (bbox), button, FALSE, FALSE, 0);
+    }
+
+  gtk_widget_show (cfg_dialog);
+  gtk_widget_grab_focus (cfg_dialog);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/paranormal/cmaps.c	Mon Oct 30 23:02:33 2006 -0800
@@ -0,0 +1,93 @@
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#include <glib.h>
+
+#include "paranormal.h"
+#include "actuators.h"
+
+#define STD_CMAP_OPTS { "low_index", "The lowest index of the \
+color map that should be altered", OPT_TYPE_COLOR_INDEX, { ival: 0 } },\
+{ "high_index", "The highest index of the color map that should be \
+altered", OPT_TYPE_COLOR_INDEX, { ival: 255 } }
+
+static struct pn_color black = {0, 0, 0};
+static struct pn_color white = {255, 255, 255};
+
+/* **************** cmap generation funcs **************** */
+static void
+cmap_gen_gradient (int step, const struct pn_color *a,
+		   const struct pn_color *b,
+		   struct pn_color *c)
+{
+  c->r = a->r + step * ((((float)b->r) - ((float)a->r)) / 256.0);
+  c->g = a->g + step * ((((float)b->g) - ((float)a->g)) / 256.0);
+  c->b = a->b + step * ((((float)b->b) - ((float)a->b)) / 256.0);
+}
+
+/* **************** cmap_gradient **************** */
+static struct pn_actuator_option_desc cmap_gradient_opts[] =
+{
+  STD_CMAP_OPTS,
+  { "lcolor", "The low color used in the gradient generation",
+    OPT_TYPE_COLOR, { cval: {0, 0, 0} } },
+  { "hcolor", "The high color used in the gradient generation",
+    OPT_TYPE_COLOR, { cval: {0, 0, 0} } },
+  { 0 }
+};
+
+static void
+cmap_gradient_exec (const struct pn_actuator_option *opts,
+		    gpointer data)
+{
+  int i;
+
+  for (i=opts[0].val.ival; i<=opts[1].val.ival; i++)
+    cmap_gen_gradient (((i-opts[0].val.ival)<<8)/(opts[1].val.ival
+						  - opts[0].val.ival),
+		       &opts[2].val.cval, &opts[3].val.cval,
+		       &pn_image_data->cmap[i]);
+}
+
+struct pn_actuator_desc builtin_cmap_gradient =
+{
+  "cmap_gradient",
+  "Sets the colormap to a gradient going from <lcolor> to "
+  "<hcolor>",
+  0, cmap_gradient_opts,
+  NULL, NULL, cmap_gradient_exec
+};
+
+/* **************** cmap_bwgradient **************** */
+static struct pn_actuator_option_desc cmap_bwgradient_opts[] =
+{
+  STD_CMAP_OPTS,
+  { "color", "The intermediate color to use in the gradient",
+    OPT_TYPE_COLOR, { cval: {191, 191, 191} } },
+  { 0 }
+};
+
+static void
+cmap_bwgradient_exec (const struct pn_actuator_option *opts,
+		      gpointer data)
+{
+  int i;
+
+  for (i=opts[0].val.ival; i<128 && i<=opts[1].val.ival; i++)
+    cmap_gen_gradient (i<<1, &black, &opts[2].val.cval,
+		       &pn_image_data->cmap[i]);
+
+  for (i=128; i<256 && i<=opts[1].val.ival; i++)
+    cmap_gen_gradient ((i-128)<<1, &opts[2].val.cval, &white,
+		       &pn_image_data->cmap[i]);
+}
+    
+struct pn_actuator_desc builtin_cmap_bwgradient =
+{
+  "cmap_bwgradient",
+  "Sets the colormap to a gradient going from black to "
+  "while, via an intermediate color",
+  0, cmap_bwgradient_opts,
+  NULL, NULL, cmap_bwgradient_exec
+};
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/paranormal/containers.c	Mon Oct 30 23:02:33 2006 -0800
@@ -0,0 +1,202 @@
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#include <stdio.h>
+
+#include <glib.h>
+
+#include "actuators.h"
+
+/* **************** all containers **************** */
+
+/* Add a actuator to a container (end of list) */
+void
+container_add_actuator (struct pn_actuator *container, struct pn_actuator *a)
+{
+  g_assert (container->desc->flags & ACTUATOR_FLAG_CONTAINER);
+  g_assert (a);
+
+  *((GSList **)container->data) =
+    g_slist_append (*(GSList **)container->data, a);
+}  
+
+void
+container_remove_actuator (struct pn_actuator *container, struct pn_actuator *a)
+{
+  g_assert (container->desc->flags & ACTUATOR_FLAG_CONTAINER);
+  g_assert (a);
+
+  *((GSList **)container->data) =
+    g_slist_remove (*(GSList **)container->data, a);
+} 
+
+/* clear the containee list */
+void
+container_unlink_actuators (struct pn_actuator *container)
+{
+  g_assert (container->desc->flags & ACTUATOR_FLAG_CONTAINER);
+
+  g_slist_free (*(GSList **)container->data);
+  *(GSList **)container->data = NULL;
+}
+
+/* this does NOT free data */
+static void
+container_cleanup (GSList** data)
+{
+  GSList *child;
+
+  for (child = *data; child; child = child->next)
+    destroy_actuator ((struct pn_actuator *) child->data);
+
+  g_slist_free (*data);
+}
+
+/* **************** container_simple **************** */
+static void
+container_simple_init (GSList ***data)
+{
+  *data = g_new0 (GSList *, 1);
+}
+
+static void
+container_simple_cleanup (GSList **data)
+{
+  container_cleanup (data);
+  g_free (data);
+}
+
+static void
+container_simple_exec (const struct pn_actuator_option *opts,
+		     GSList **data)
+{
+  GSList *child;
+
+  for (child = *data; child; child = child->next)
+    exec_actuator ((struct pn_actuator *) child->data);
+}
+
+struct pn_actuator_desc builtin_container_simple =
+{
+  "container_simple",
+  "A simple (unconditional) container\n\n"
+  "This is usually used as the root actuator of a list",
+  ACTUATOR_FLAG_CONTAINER, NULL,
+  PN_ACTUATOR_INIT_FUNC (container_simple_init),
+  PN_ACTUATOR_CLEANUP_FUNC (container_simple_cleanup),
+  PN_ACTUATOR_EXEC_FUNC (container_simple_exec)
+};
+
+/* **************** container_once **************** */
+struct container_once_data
+{
+  GSList *children; /* This MUST be first! */
+
+  gboolean done;
+};
+
+static void
+container_once_init (struct container_once_data **data)
+{
+  *data = g_new0 (struct container_once_data, 1);
+}
+
+static void
+container_once_cleanup (GSList **data)
+{
+  container_cleanup (data);
+  g_free (data);
+}
+
+static void
+container_once_exec (const struct pn_actuator_option *opts,
+		     struct container_once_data *data)
+{
+  if (! data->done)
+    {
+      GSList *child;
+
+      for (child = data->children; child; child = child->next)
+	exec_actuator ((struct pn_actuator *) child->data);
+
+      data->done = TRUE;
+    }
+}
+
+struct pn_actuator_desc builtin_container_once =
+{
+  "container_once",
+  "A container whose contents get executed exactly once.\n\n"
+  "This is often used to set initial graphics states such as the\n"
+  "pixel depth, or to display some text (such as credits)",
+  ACTUATOR_FLAG_CONTAINER, NULL,
+  PN_ACTUATOR_INIT_FUNC (container_once_init),
+  PN_ACTUATOR_CLEANUP_FUNC (container_once_cleanup),
+  PN_ACTUATOR_EXEC_FUNC (container_once_exec)
+};
+
+/* **************** container_cycle ***************** */
+static struct pn_actuator_option_desc container_cycle_opts[] =
+{
+  { "change_interval", "The number of seconds between changing the "
+    "child to be executed", OPT_TYPE_INT, { ival: 20 } },
+  { "random", "Whether or not the change should be random",
+    OPT_TYPE_BOOLEAN, { bval: TRUE } },
+  { 0 }
+};
+
+struct container_cycle_data
+{
+  GSList *children;
+  GSList *current;
+  int last_change;
+};
+
+static void
+container_cycle_init (gpointer *data)
+{
+  *data = g_new0 (struct container_cycle_data, 1);
+}
+
+static void
+container_cycle_cleanup (gpointer data)
+{
+  container_cleanup (data);
+  g_free (data);
+}
+
+static void
+container_cycle_exec (const struct pn_actuator_option *opts,
+		      gpointer data)
+{
+  struct container_cycle_data *cdata = (struct container_cycle_data*)data;
+  int now;
+
+  now = SDL_GetTicks ();
+
+  if (now - cdata->last_change
+      > opts[0].val.ival * 1000)
+    {
+      cdata->last_change = now;
+
+      /* FIXME: add randomization support */
+      if (cdata->current)
+	cdata->current = cdata->current->next;
+    }
+
+  if (! cdata->current)
+    cdata->current = cdata->children;
+
+  if (cdata->current)
+    exec_actuator ((struct pn_actuator*)cdata->current->data);
+}
+
+struct pn_actuator_desc builtin_container_cycle =
+{
+  "container_cycle",
+  "A container that alternates which of its children is executed;  it "
+  "can either change children randomly or go in order.",
+  ACTUATOR_FLAG_CONTAINER, container_cycle_opts,
+  container_cycle_init, container_cycle_cleanup, container_cycle_exec
+};
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/paranormal/containers.h	Mon Oct 30 23:02:33 2006 -0800
@@ -0,0 +1,8 @@
+#ifndef _CONTAINERS_H
+#define _CONTAINERS_H
+
+void container_add_actuator (struct pn_actuator *container, struct pn_actuator *a);
+void container_remove_actuator (struct pn_actuator *container, struct pn_actuator *a);
+void container_unlink_actuators (struct pn_actuator *container);
+
+#endif /* _CONTAINERS_H */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/paranormal/freq.c	Mon Oct 30 23:02:33 2006 -0800
@@ -0,0 +1,62 @@
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#include <stdlib.h>
+#include <stdio.h>
+
+#include <glib.h>
+
+#include "paranormal.h"
+#include "actuators.h"
+#include "pn_utils.h"
+
+/* **************** freq_dots **************** */
+/* FIXME: take this piece of crap out */
+static void
+freq_dots_exec (const struct pn_actuator_option *opts,
+		gpointer data)
+{
+  int i, basex;
+
+  basex = (pn_image_data->width>>1)-128;
+  for (i=basex < 0 ? -basex : 0 ; i < 256; i++)
+    {
+      pn_image_data->surface[0][PN_IMG_INDEX (basex+i, (pn_image_data->height>>1)
+					      - CAP (pn_sound_data->freq_data[0][i], 120))]
+	= 0xff;
+      pn_image_data->surface[0][PN_IMG_INDEX (basex+256-i, (pn_image_data->height>>1)
+					      + CAP (pn_sound_data->freq_data[1][i], 120))]
+	= 0xff;
+    }
+}
+
+struct pn_actuator_desc builtin_freq_dots =
+{
+  "freq_dots",
+  "Draws dots varying vertically with the freqency data.",
+  0, NULL,
+  NULL, NULL, freq_dots_exec
+};
+
+/* **************** freq_drops **************** */
+static void
+freq_drops_exec (const struct pn_actuator_option *opts,
+		gpointer data)
+{
+  int i,j;
+  
+  for (i=0; i<256; i++)
+    for (j=0; j<pn_sound_data->freq_data[0][i]>>3; i++)
+      pn_image_data->surface[0][PN_IMG_INDEX (rand() % pn_image_data->width,
+					      rand() % pn_image_data->height)]
+	= 0xff;
+}
+
+struct pn_actuator_desc builtin_freq_drops =
+{
+  "freq_drops",
+  "Draws dots at random on the image (louder music = more dots)",
+  0, NULL,
+  NULL, NULL, freq_drops_exec
+};
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/paranormal/general.c	Mon Oct 30 23:02:33 2006 -0800
@@ -0,0 +1,96 @@
+/* FIXME: what to name this file? */
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#include "paranormal.h"
+#include "actuators.h"
+#include "pn_utils.h"
+
+/* **************** general_fade **************** */
+static struct pn_actuator_option_desc general_fade_opts[] =
+{
+  { "amount", "The amount by which the color index of each "
+    "pixel should be decreased by each frame (MAX 255)",
+    OPT_TYPE_INT, { ival: 3 } },
+  { 0 }
+};
+
+static void
+general_fade_exec (const struct pn_actuator_option *opts,
+	   gpointer data)
+{
+  int amt = opts[0].val.ival > 255 || opts[0].val.ival < 0 ?
+    3 : opts[0].val.ival;
+  int i, j;
+
+  for (j=0; j<pn_image_data->height; j++)
+    for (i=0; i<pn_image_data->width; i++)
+      pn_image_data->surface[0][PN_IMG_INDEX (i, j)] =
+	CAPLO (pn_image_data->surface[0][PN_IMG_INDEX (i, j)]
+	       - amt, 0);
+}
+
+struct pn_actuator_desc builtin_general_fade =
+{
+  "general_fade", "Decreases the color index of each pixel",
+  0, general_fade_opts,
+  NULL, NULL, general_fade_exec
+};
+
+/* **************** general_blur **************** */
+/* FIXME: add a variable radius */
+/* FIXME: SPEEEED */
+static void
+general_blur_exec (const struct pn_actuator_option *opts,
+	   gpointer data)
+{
+  int i,j;
+  register guchar *srcptr = pn_image_data->surface[0];
+  register guchar *destptr = pn_image_data->surface[1];
+  register int sum;
+
+  for (j=0; j<pn_image_data->height; j++)
+    for (i=0; i<pn_image_data->width; i++)
+      {
+	sum = *(srcptr)<<2;
+
+	/* top */
+	if (j > 0)
+	  {
+	    sum += *(srcptr-pn_image_data->width)<<1;
+	    if (i > 0)
+	      sum += *(srcptr-pn_image_data->width-1);
+	    if (i < pn_image_data->width-1)
+	      sum += *(srcptr-pn_image_data->width+1);
+	  }
+	/* bottom */
+	if (j < pn_image_data->height-1)
+	  {
+	    sum += *(srcptr+pn_image_data->width)<<1;
+	    if (i > 0)
+	      sum += *(srcptr+pn_image_data->width-1);
+	    if (i < pn_image_data->width-1)
+	      sum += *(srcptr+pn_image_data->width+1);
+	  }
+	/* left */
+	if (i > 0)
+	  sum += *(srcptr-1)<<1;
+	/* right */
+	if (i < pn_image_data->width-1)
+	  sum += *(srcptr+1)<<1;
+
+	*destptr++ = (guchar)(sum >> 4);
+	srcptr++;
+      }
+
+  pn_swap_surfaces ();
+}
+
+struct pn_actuator_desc builtin_general_blur = 
+{
+  "general_blur", "A simple 1 pixel radius blur",
+  0, NULL,
+  NULL, NULL, general_blur_exec
+};
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/paranormal/paranormal.c	Mon Oct 30 23:02:33 2006 -0800
@@ -0,0 +1,205 @@
+/* FIXME: add fullscreen / screenshots */
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <math.h>
+
+#include <SDL.h>
+
+#include "paranormal.h"
+#include "actuators.h"
+
+/* SDL stuffs */
+static SDL_Surface *screen;
+
+/* Globals */
+struct pn_rc         *pn_rc;
+struct pn_image_data *pn_image_data;
+struct pn_sound_data *pn_sound_data;
+
+/* Trig Pre-Computes */
+float sin_val[360];
+float cos_val[360];
+
+/* **************** drawing doodads **************** */
+
+static void
+blit_to_screen (void)
+{
+  int j;
+
+  SDL_LockSurface (screen);
+
+  /* FIXME: add scaling support */
+
+  SDL_SetPalette (screen, SDL_LOGPAL|SDL_PHYSPAL,
+		  (SDL_Color*)pn_image_data->cmap, 0, 256);
+
+  for (j=0; j<pn_image_data->height; j++)
+      memcpy (screen->pixels + j*screen->pitch,
+	      pn_image_data->surface[0] + j*pn_image_data->width,
+	      pn_image_data->width);
+
+
+  SDL_UnlockSurface (screen);
+
+  SDL_UpdateRect (screen, 0, 0, 0, 0);
+}
+
+static void
+resize_video (guint w, guint h)
+{
+  pn_image_data->width = w;
+  pn_image_data->height = h;
+
+  if (pn_image_data->surface[0])
+    g_free (pn_image_data->surface[0]);
+  if (pn_image_data->surface[1])
+    g_free (pn_image_data->surface[1]);
+
+  pn_image_data->surface[0] = g_malloc0 (w * h);
+  pn_image_data->surface[1] = g_malloc0 (w * h);
+
+  screen = SDL_SetVideoMode (w, h, 8, SDL_HWSURFACE |
+			     SDL_HWPALETTE | SDL_RESIZABLE);
+  if (! screen)
+    pn_fatal_error ("Unable to create a new SDL window: %s",
+		    SDL_GetError ());
+}
+
+static void
+take_screenshot (void)
+{
+  char fname[32];
+  struct stat buf;
+  int i=0;
+
+  do
+    sprintf (fname, "pn_%05d.bmp", ++i);
+  while (stat (fname, &buf) == 0);
+
+  SDL_SaveBMP (screen, fname);
+}
+
+/* FIXME: This should resize the video to a user-set
+   fullscreen res */
+static void
+toggle_fullscreen (void)
+{
+  SDL_WM_ToggleFullScreen (screen);  
+  if (SDL_ShowCursor (SDL_QUERY) == SDL_ENABLE)
+    SDL_ShowCursor (SDL_DISABLE);
+  else
+    SDL_ShowCursor (SDL_ENABLE);
+}
+
+/* **************** basic renderer management **************** */
+void
+pn_init (void)
+{
+  int i;
+
+  pn_sound_data = g_new0 (struct pn_sound_data, 1);
+  pn_image_data = g_new0 (struct pn_image_data, 1);
+
+  if (SDL_Init (SDL_INIT_VIDEO | SDL_INIT_TIMER | SDL_INIT_NOPARACHUTE) < 0)
+    pn_fatal_error ("Unable to initialize SDL: %s", SDL_GetError ());
+
+  resize_video (640, 360);
+
+  SDL_WM_SetCaption (PACKAGE " " VERSION, PACKAGE);
+
+  for(i=0; i<360; i++)
+    {
+      sin_val[i] = sin(i*(M_PI/180.0));
+      cos_val[i] = cos(i*(M_PI/180.0));
+    }
+}
+
+void
+pn_cleanup (void)
+{
+  SDL_FreeSurface (screen);
+  SDL_Quit ();
+
+
+  if (pn_image_data)
+    {
+      if (pn_image_data->surface[0])
+	g_free (pn_image_data->surface[0]);
+      if (pn_image_data->surface[1])
+	g_free (pn_image_data->surface[1]);
+      g_free (pn_image_data);
+    }
+  if (pn_sound_data)
+    g_free (pn_sound_data);
+}
+
+/* Renders one frame and handles the SDL window */
+void
+pn_render (void)
+{
+  SDL_Event event;
+
+  /* Handle window events */
+  while (SDL_PollEvent (&event))
+    {
+      switch (event.type)
+	{
+	case SDL_QUIT:
+	  pn_quit ();
+	  g_assert_not_reached ();
+	case SDL_KEYDOWN:
+	  switch (event.key.keysym.sym)
+	    {
+	    case SDLK_ESCAPE:
+	      pn_quit ();
+	      g_assert_not_reached ();
+	    case SDLK_RETURN:
+	      if (event.key.keysym.mod & (KMOD_ALT | KMOD_META))
+		toggle_fullscreen ();
+	      break;
+	    case SDLK_BACKQUOTE:
+	      take_screenshot ();
+	      break;
+	    default:
+              break;
+	    }
+	  break;
+	case SDL_VIDEORESIZE:
+	  resize_video (event.resize.w, event.resize.h);	  
+	  break;
+	}
+    }
+
+  if (pn_rc->actuator)
+    {
+      exec_actuator (pn_rc->actuator);
+      blit_to_screen ();
+    }
+}
+
+/* this MUST be called if a builtin's output is to surface[1]
+   (by the builtin, after it is done) */
+void
+pn_swap_surfaces (void)
+{
+  guchar *tmp = pn_image_data->surface[0];
+  pn_image_data->surface[0] = pn_image_data->surface[1];
+  pn_image_data->surface[1] = tmp;
+}
+
+
+
+
+
+
+
+
+
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/paranormal/paranormal.h	Mon Oct 30 23:02:33 2006 -0800
@@ -0,0 +1,53 @@
+#ifndef _PARANORMAL_H
+#define _PARANORMAL_H
+
+#include <glib.h>
+
+#include "actuators.h"
+
+struct pn_sound_data
+{
+  gint16 pcm_data[2][512];
+  gint16 freq_data[2][256];
+};
+
+struct pn_image_data
+{
+  int width, height;
+  struct pn_color cmap[256];
+  guchar *surface[2];
+};
+
+/* The executable (ie xmms.c or standalone.c)
+   is responsible for allocating this and filling
+   it with default/saved values */
+struct pn_rc
+{
+  struct pn_actuator *actuator;
+};
+
+/* core funcs */
+void pn_init (void);
+void pn_cleanup (void);
+void pn_render (void);
+void pn_swap_surfaces (void);
+
+/* Implemented elsewhere (ie xmms.c or standalone.c) */
+void pn_set_rc ();
+void pn_fatal_error (const char *fmt, ...);
+void pn_error (const char *fmt, ...);
+void pn_quit (void);
+
+/* Implimented in cfg.c */
+void pn_configure (void);
+
+/* globals used for rendering */
+extern struct pn_rc         *pn_rc;
+extern struct pn_sound_data *pn_sound_data;
+extern struct pn_image_data *pn_image_data;
+
+/* global trig pre-computes */
+extern float sin_val[360];
+extern float cos_val[360];
+
+#endif /* _PARANORMAL_H */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/paranormal/plugin.c	Mon Oct 30 23:02:33 2006 -0800
@@ -0,0 +1,478 @@
+/* FIXME: issues with not uniniting variables between
+   enables?  I wasn't too careful about that, but it
+   seems to work fine.  If there are problems perhaps
+   look for a bug there?
+*/
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <stdarg.h>
+#include <memory.h>
+#include <math.h>
+#include <setjmp.h>
+
+#include <glib.h>
+#include <gtk/gtk.h>
+#include <audacious/plugin.h>
+#include <SDL/SDL.h>
+#include <SDL/SDL_thread.h>
+
+#include "paranormal.h"
+#include "actuators.h"
+#include "presets.h"
+#include "containers.h"
+
+/* Error reporting dlg */
+static GtkWidget *err_dialog;
+
+/* Draw thread stuff */
+/* FIXME: Do I need mutex for pn_done? */
+static SDL_Thread *draw_thread = NULL;
+static SDL_mutex *sound_data_mutex;
+static SDL_mutex *config_mutex;
+
+static gboolean pn_done = FALSE;
+jmp_buf quit_jmp;
+gboolean timeout_set = FALSE;
+guint quit_timeout;
+
+/* Sound stuffs */
+static gboolean new_pcm_data = FALSE;
+static gboolean new_freq_data = FALSE;
+static gint16 tmp_pcm_data[2][512];
+static gint16 tmp_freq_data[2][256];
+
+/* XMMS interface */
+static void pn_xmms_init (void);
+static void pn_xmms_cleanup (void);
+static void pn_xmms_about (void);
+static void pn_xmms_configure (void);
+static void pn_xmms_render_pcm (gint16 data[2][512]);
+static void pn_xmms_render_freq (gint16 data[2][256]);
+
+static VisPlugin pn_vp = 
+{
+  NULL,
+  NULL,
+  0,
+  "Paranormal Visualization Studio " VERSION,
+  2,
+  2,
+  pn_xmms_init,
+  pn_xmms_cleanup,
+  pn_xmms_about,
+  pn_xmms_configure,
+  NULL, /* disable_plugin */
+  NULL, /* pn_xmms_playback_start */
+  NULL, /* pn_xmms_playback_stop */
+  pn_xmms_render_pcm,
+  pn_xmms_render_freq
+};
+
+VisPlugin *
+get_vplugin_info (void)
+{
+  return &pn_vp;
+}
+
+static void
+load_pn_rc (void)
+{
+  struct pn_actuator *a, *b;
+
+  if (! pn_rc)
+      pn_rc = g_new0 (struct pn_rc, 1);
+
+  /* load a default preset */
+  pn_rc->actuator = create_actuator ("container_simple");
+  if (! pn_rc->actuator) goto ugh;
+  a = create_actuator ("container_once");
+  if (! a) goto ugh;
+  b = create_actuator ("cmap_bwgradient");
+  if (! b) goto ugh;
+  b->options[2].val.cval.r = b->options[2].val.cval.g = 0;
+  container_add_actuator (a, b);
+  container_add_actuator (pn_rc->actuator, a);
+  a = create_actuator ("general_fade");
+  if (! a) goto ugh;
+  container_add_actuator (pn_rc->actuator, a);
+  a = create_actuator ("xform_spin");
+  if (! a) goto ugh;
+  a->options[0].val.fval = -4.0;
+  a->options[2].val.fval = 0.9;
+  container_add_actuator (pn_rc->actuator, a);
+  a = create_actuator ("wave_horizontal");
+  if (! a) goto ugh;
+  container_add_actuator (pn_rc->actuator, a);
+  a = create_actuator ("general_blur");
+  if (! a) goto ugh;
+  container_add_actuator (pn_rc->actuator, a);
+
+  return;
+
+ ugh:
+  if (pn_rc->actuator)
+    destroy_actuator (pn_rc->actuator);
+  pn_error ("Error loading default preset");
+}
+
+static int
+draw_thread_fn (gpointer data)
+{
+  pn_init ();
+
+  /* Used when pn_quit is called from this thread */
+  if (setjmp (quit_jmp) != 0)
+    pn_done = TRUE;
+
+  while (! pn_done)
+    {
+      SDL_mutexP (sound_data_mutex);
+      if (new_freq_data)
+	{
+	  memcpy (pn_sound_data->freq_data, tmp_freq_data,
+		  sizeof (gint16) * 2 * 256);
+	  new_freq_data = FALSE;
+	}
+      if (new_pcm_data)
+	{
+	  memcpy (pn_sound_data->pcm_data, tmp_pcm_data,
+		  sizeof (gint16) * 2 * 512);
+	  new_freq_data = FALSE;
+	}
+      SDL_mutexV (sound_data_mutex);
+      SDL_mutexP (config_mutex);
+      pn_render ();
+      SDL_mutexV (config_mutex);
+    }
+
+  /* Just in case a pn_quit () was called in the loop */
+/*    SDL_mutexV (sound_data_mutex); */
+
+  pn_cleanup ();
+
+  return 0;
+}
+
+/* Is there a better way to do this? this = messy
+   It appears that calling disable_plugin () in some
+   thread other than the one that called pn_xmms_init ()
+   causes a seg fault :( */
+static int
+quit_timeout_fn (gpointer data)
+{
+  if (pn_done)
+    {
+      pn_vp.disable_plugin (&pn_vp);
+      return FALSE;
+    }
+
+  return TRUE;
+}
+
+static void
+pn_xmms_init (void)
+{
+  /* If it isn't already loaded, load the run control */
+  load_pn_rc ();
+
+  sound_data_mutex = SDL_CreateMutex ();
+  config_mutex = SDL_CreateMutex ();
+  if (! sound_data_mutex)
+    pn_fatal_error ("Unable to create a new mutex: %s",
+		    SDL_GetError ());
+
+  pn_done = FALSE;
+  draw_thread = SDL_CreateThread (draw_thread_fn, NULL);
+  if (! draw_thread)
+    pn_fatal_error ("Unable to create a new thread: %s",
+		    SDL_GetError ());
+
+  /* Add a gtk timeout to test for quits */
+  quit_timeout = gtk_timeout_add (1000, quit_timeout_fn, NULL);
+  timeout_set = TRUE;
+}
+
+static void
+pn_xmms_cleanup (void)
+{
+  if (timeout_set)
+    {
+      gtk_timeout_remove (quit_timeout);
+      timeout_set = FALSE;
+    }
+
+  if (draw_thread)
+    {
+      pn_done = TRUE;
+      SDL_WaitThread (draw_thread, NULL);
+      draw_thread = NULL;
+    }
+
+  if (sound_data_mutex)
+    {
+      SDL_DestroyMutex (sound_data_mutex);
+      sound_data_mutex = NULL;
+    }
+
+  if (config_mutex)
+    {
+      SDL_DestroyMutex (config_mutex);
+      config_mutex = NULL;
+    }
+}
+
+static void
+about_close_clicked(GtkWidget *w, GtkWidget **window)
+{
+	gtk_widget_destroy(*window);
+	*window=NULL;
+}
+
+static void
+about_closed(GtkWidget *w, GdkEvent *e, GtkWidget **window)
+{
+	about_close_clicked(w,window);
+}
+
+static void
+pn_xmms_about (void)
+{
+  static GtkWidget *window=NULL;
+  GtkWidget *vbox, *buttonbox, *close, *label;
+
+  if(window)
+    return;
+
+  window=gtk_window_new(GTK_WINDOW_TOPLEVEL);
+  gtk_window_set_title(GTK_WINDOW(window), "Paranormal Visualization Studio " VERSION);
+  gtk_window_set_policy(GTK_WINDOW(window), FALSE, FALSE, FALSE);
+
+  vbox=gtk_vbox_new(FALSE, 4);
+  gtk_container_add(GTK_CONTAINER(window), vbox);
+  gtk_container_set_border_width(GTK_CONTAINER(vbox), 8);
+  gtk_widget_show(vbox);
+
+  label=gtk_label_new("\n\n Paranormal Visualization Studio " " VERSION "\n\n\
+Copyright (C) 2006, William Pitcock. <nenolod -at- nenolod.net>\n\
+Copyright (C) 2001, Jamie Gennis. (jgennis@mindspring.com)\n\
+\n\
+This program is free software; you can redistribute it and/or modify\n\
+it under the terms of the GNU General Public License as published by\n\
+the Free Software Foundation; either version 2 of the License, or\n\
+(at your option) any later version.\n\
+\n\
+This program is distributed in the hope that it will be useful,\n\
+but WITHOUT ANY WARRANTY; without even the implied warranty of\n\
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n\
+GNU General Public License for more details.\n\
+\n\
+You should have received a copy of the GNU General Public License\n\
+along with this program; if not, write to the Free Software\n\
+Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307\n\
+USA");
+
+  gtk_box_pack_start(GTK_BOX(vbox), label, FALSE, FALSE, 8);
+  gtk_widget_show(label);
+	
+  buttonbox=gtk_hbutton_box_new();
+  gtk_box_pack_end(GTK_BOX(vbox), buttonbox, FALSE, FALSE,8);
+  gtk_widget_show(buttonbox);
+	
+  close=gtk_button_new_with_label("Close");
+  GTK_WIDGET_SET_FLAGS(close, GTK_CAN_DEFAULT);
+  gtk_window_set_default(GTK_WINDOW(window), close);
+  gtk_hbutton_box_set_layout_default(GTK_BUTTONBOX_END);
+  gtk_box_pack_end(GTK_BOX(buttonbox), close, FALSE, FALSE,8);
+  gtk_widget_show(close);
+	
+  gtk_signal_connect(GTK_OBJECT(close), "clicked", GTK_SIGNAL_FUNC(about_close_clicked), &window);
+  gtk_signal_connect(GTK_OBJECT(window), "delete-event", GTK_SIGNAL_FUNC(about_closed), &window);
+
+  gtk_widget_show(window);
+}
+
+static void
+pn_xmms_configure (void)
+{
+  load_pn_rc ();
+
+  /* We should already have a GDK_THREADS_ENTER
+     but we need to give it config_mutex */
+  if (config_mutex)
+    SDL_mutexP (config_mutex);
+
+  pn_configure ();
+
+  if (config_mutex)
+    SDL_mutexV (config_mutex);
+}
+
+static void
+pn_xmms_render_pcm (gint16 data[2][512])
+{
+  SDL_mutexP (sound_data_mutex);
+  memcpy (tmp_pcm_data, data, sizeof (gint16) * 2 * 512);
+  new_pcm_data = TRUE;
+  SDL_mutexV (sound_data_mutex);
+}
+
+static void
+pn_xmms_render_freq (gint16 data[2][256])
+{
+  SDL_mutexP (sound_data_mutex);
+  memcpy (tmp_freq_data, data, sizeof (gint16) * 2 * 256);
+  new_freq_data = TRUE;
+  SDL_mutexV (sound_data_mutex);
+}
+
+/* **************** paranormal.h stuff **************** */
+
+void
+pn_set_rc (struct pn_rc *new_rc)
+{
+  if (config_mutex)
+    SDL_mutexP (config_mutex);
+
+  if (pn_rc->actuator)
+    destroy_actuator (pn_rc->actuator);
+  pn_rc->actuator = new_rc->actuator;
+
+  if (config_mutex)
+    SDL_mutexV (config_mutex);
+}
+
+void
+pn_fatal_error (const char *fmt, ...)
+{
+  char *errstr;
+  va_list ap;
+  GtkWidget *dialog;
+  GtkWidget *close, *label;
+
+  /* Don't wanna try to lock GDK if we already have it */
+  if (draw_thread && SDL_ThreadID () == SDL_GetThreadID (draw_thread))
+    GDK_THREADS_ENTER ();
+
+  /* now report the error... */
+  va_start (ap, fmt);
+  errstr = g_strdup_vprintf (fmt, ap);
+  va_end (ap);
+
+  dialog=gtk_dialog_new();
+  gtk_window_set_title(GTK_WINDOW(dialog), "Error - " PACKAGE " " VERSION);
+  gtk_container_border_width (GTK_CONTAINER (dialog), 8);
+
+  label=gtk_label_new(errstr);
+  fprintf (stderr, "%s\n", errstr);
+  g_free (errstr);
+
+  close = gtk_button_new_with_label ("Close");
+  gtk_signal_connect_object (GTK_OBJECT (close), "clicked",
+			     GTK_SIGNAL_FUNC (gtk_widget_destroy),
+			     GTK_OBJECT (dialog));
+
+  gtk_box_pack_start (GTK_BOX (GTK_DIALOG (dialog)->vbox), label, FALSE,
+		      FALSE, 0);
+  gtk_box_pack_start (GTK_BOX (GTK_DIALOG (dialog)->action_area), close,
+		      FALSE, FALSE, 0);
+  gtk_widget_show (label);
+  gtk_widget_show (close);
+
+  gtk_widget_show (dialog);
+  gtk_widget_grab_focus (dialog);
+
+  if (draw_thread && SDL_ThreadID () == SDL_GetThreadID (draw_thread))
+    GDK_THREADS_LEAVE ();
+
+  pn_quit ();
+}
+
+
+void
+pn_error (const char *fmt, ...)
+{
+  char *errstr;
+  va_list ap;
+  static GtkWidget *text;
+  static GtkTextBuffer *textbuf;
+
+  /* now report the error... */
+  va_start (ap, fmt);
+  errstr = g_strdup_vprintf (fmt, ap);
+  va_end (ap);
+  fprintf (stderr, PACKAGE ": %s\n", errstr);
+
+  /* This is the easiest way of making sure we don't
+     get stuck trying to lock a mutex that this thread
+     already owns since this fn can be called from either
+     thread */
+  if (draw_thread && SDL_ThreadID () == SDL_GetThreadID (draw_thread))
+    GDK_THREADS_ENTER ();
+
+  if (! err_dialog)
+    {
+      GtkWidget *close;
+
+      err_dialog=gtk_dialog_new();
+      gtk_window_set_title (GTK_WINDOW (err_dialog), "Error - " PACKAGE " " VERSION);
+      gtk_window_set_policy (GTK_WINDOW (err_dialog), FALSE, FALSE, FALSE);
+      gtk_widget_set_usize (err_dialog, 400, 200);
+      gtk_container_border_width (GTK_CONTAINER (err_dialog), 8);
+
+      textbuf = gtk_text_buffer_new(NULL);
+      text = gtk_text_view_new_with_buffer (textbuf);
+
+      close = gtk_button_new_with_label ("Close");
+      gtk_signal_connect_object (GTK_OBJECT (close), "clicked",
+				 GTK_SIGNAL_FUNC (gtk_widget_hide),
+				 GTK_OBJECT (err_dialog));
+      gtk_signal_connect_object (GTK_OBJECT (err_dialog), "delete-event",
+				 GTK_SIGNAL_FUNC (gtk_widget_hide),
+				 GTK_OBJECT (err_dialog));
+
+      gtk_box_pack_start (GTK_BOX (GTK_DIALOG (err_dialog)->vbox), text, FALSE,
+			  FALSE, 0);
+      gtk_box_pack_start (GTK_BOX (GTK_DIALOG (err_dialog)->action_area), close,
+			  FALSE, FALSE, 0);
+      gtk_widget_show (text);
+      gtk_widget_show (close);
+    }
+
+  gtk_text_buffer_set_text(GTK_TEXT_BUFFER(textbuf), errstr, -1);
+  g_free (errstr);
+
+  gtk_widget_show (err_dialog);
+  gtk_widget_grab_focus (err_dialog);
+
+  if (draw_thread && SDL_ThreadID () == SDL_GetThreadID (draw_thread))
+    GDK_THREADS_LEAVE ();
+}
+
+
+/* This is confusing...
+   Don't call this from anywhere but the draw thread or
+   the initialization xmms thread (ie NOT the xmms sound
+   data functions) */
+void
+pn_quit (void)
+{
+  if (draw_thread && SDL_ThreadID () == SDL_GetThreadID (draw_thread))
+    {
+      /* We're in the draw thread so be careful */
+      longjmp (quit_jmp, 1);
+    }
+  else
+    {
+      /* We're not in the draw thread, so don't sweat it...
+	 addendum: looks like we have to bend over backwards (forwards?)
+	 for xmms here too */
+      pn_vp.disable_plugin (&pn_vp);
+      while (1)
+	gtk_main_iteration ();
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/paranormal/pn_utils.h	Mon Oct 30 23:02:33 2006 -0800
@@ -0,0 +1,15 @@
+#ifndef _PN_UTILS_H
+#define _PN_UTILS_H
+
+#ifndef M_PI
+#define M_PI 3.14159265358979323846
+#endif
+
+#define CAP(i,c) (i > c ? c : i < -(c) ? -(c) : i)
+#define CAPHILO(i,h,l) (i > h ? h : i < l ? l : i)
+#define CAPHI(i,h) (i > h ? h : i)
+#define CAPLO(i,l) (i < l ? l : i)
+
+#define PN_IMG_INDEX(x,y) ((x) + (pn_image_data->width * (y)))
+
+#endif /* _PN_UTILS_H */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/paranormal/presets.c	Mon Oct 30 23:02:33 2006 -0800
@@ -0,0 +1,261 @@
+/* FIXME: add documentation support to preset files */
+/* FIXME: add multiple-presets-per-file support (maybe) */
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#include <stdlib.h>
+#include <ctype.h>
+#include <string.h>
+#include <errno.h>
+
+#include <glib.h>
+#include <libxml/parser.h>
+#include <libxml/xmlmemory.h>
+
+#include "paranormal.h"
+#include "actuators.h"
+#include "containers.h"
+
+/* cur->name should be the actuator name */
+static void
+parse_actuator (xmlNodePtr cur, struct pn_actuator *a)
+{
+  int i;
+  xmlChar *content;
+  struct pn_actuator *child;
+
+  for (cur = cur->xmlChildrenNode; cur; cur = cur->next)
+    {
+      if (xmlIsBlankNode (cur) || cur->type != XML_ELEMENT_NODE)
+	continue;
+
+      /* see if it's an option */
+      for (i=0; a->options && a->options[i].desc; i++)
+	if (! xmlStrcmp (cur->name,
+			 (const xmlChar *) a->options[i].desc->name))
+	  break;
+
+      if (a->options && a->options[i].desc)
+	{
+	  /* it is an option, so let's set it! */
+	  content = xmlNodeGetContent (cur);
+
+	  /* FIXME: warning? */
+	  if (! content)
+	    continue;
+
+	  /* FIXME: perhaps do a little better job of error checking? */
+	  switch (a->options[i].desc->type)
+	    {
+	    case OPT_TYPE_INT:
+	      a->options[i].val.ival = (int)strtol (content, NULL, 0);
+	      break;
+	    case OPT_TYPE_FLOAT:
+	      a->options[i].val.fval = (float)strtod (content, NULL);
+	      break;
+	    case OPT_TYPE_STRING:
+	      a->options[i].val.sval = g_strdup (content);
+	      break;
+	    case OPT_TYPE_COLOR:
+	      {
+		guint r,g,b;
+		char *s = content+1;
+		r = strtoul (s, &s, 0);
+		if (r > 255 || ! (s = strchr (s, ',')))
+		  goto bad_color;
+		g = strtoul (s+1, &s, 0);
+		if (g > 255 || ! (s = strchr (s, ',')))
+		  goto bad_color;
+		b = strtoul (s+1, NULL, 0);
+		if (b > 255)
+		  goto bad_color;
+
+		a->options[i].val.cval.r = (guchar)r;
+		a->options[i].val.cval.g = (guchar)g;
+		a->options[i].val.cval.b = (guchar)b;
+
+		break;
+	      }
+	    bad_color:
+	      pn_error ("parse error: invalid color value: option \"%s\" ignored.\n"
+			"  correct syntax: (r,g,b) where r, g, and b are the\n"
+			"  red, green, and blue components of the "
+			"color, respectively", cur->name);
+	      break;
+	      
+	    case OPT_TYPE_COLOR_INDEX:
+	      {
+		int c = (int)strtol (content, NULL, 0);
+		if (c < 0 || c > 255)
+		  pn_error ("parse error: invalid color index \"%s\" (%d): option ignored.\n"
+			    "  the value must be between 0 and 255",
+			    cur->name, c);
+		else
+		  a->options[i].val.ival = c;
+		break;
+	      }
+	    case OPT_TYPE_BOOLEAN:
+	      {
+		char *c, *d;
+		for (c=content; isspace (*c); c++);
+		for (d=c; !isspace(*d); d++);
+		*d = '\0';
+		if (g_strcasecmp (c, "true") == 0)
+		  a->options[i].val.bval = TRUE;
+		else if (g_strcasecmp (c, "false") == 0)
+		  a->options[i].val.bval = FALSE;
+		else
+		  pn_error ("parse error: invalid boolean value \"%s\" (%s): option ignored.\n"
+			    "  the value must be either 'true' or 'false'",
+			    cur->name, c);
+	      }
+	    }
+
+	  /* gotta free content */
+	  xmlFree (content);
+	}
+      /* See if we have a child actuator */
+      else if (a->desc->flags & ACTUATOR_FLAG_CONTAINER
+	       && (child = create_actuator (cur->name)))
+	{
+	  container_add_actuator (a, child);
+	  parse_actuator (cur, child);
+	}
+      else
+	/* We have an error */
+	pn_error ("parse error: unknown entity \"%s\": ignored.", cur->name);
+    }
+}
+
+struct pn_actuator *
+load_preset (const char *filename)
+{
+  xmlDocPtr doc;
+  xmlNodePtr cur;
+  struct pn_actuator *a = NULL;
+
+  doc = xmlParseFile (filename);
+  if (! doc)
+    return NULL;
+
+  cur = xmlDocGetRootElement (doc);
+  if (! cur)
+    xmlFreeDoc (doc);
+
+  if (xmlStrcmp (cur->name, (const xmlChar *) "paranormal_preset"))
+    {
+      xmlFreeDoc (doc);
+      return NULL;
+    }
+
+  for (cur = cur->children; cur; cur = cur->next)
+    {
+      if (xmlIsBlankNode (cur) || cur->type != XML_ELEMENT_NODE)
+	continue;
+
+      /* if (...) { ... } else if (is_documentation [see top of file]) ... else */
+      {
+	a = create_actuator (cur->name);
+
+	/* FIXME: warn? */
+	if (! a)
+	  continue;
+
+	parse_actuator (cur, a);
+	break;
+      }
+    }
+
+  /* Don't need this any longer */
+  xmlFreeDoc (doc);
+
+  return a;
+}
+
+/* FIXME: do the file writing w/ error checking */
+static gboolean
+save_preset_recursive (FILE *file, const struct pn_actuator *actuator,
+		       int recursion_depth)
+{
+  int i;
+  GSList *child;
+
+  /* open this actuator */
+  fprintf (file, "%*s<%s>\n", recursion_depth, "", actuator->desc->name);
+
+  /* options */
+  if (actuator->options)
+    for (i=0; actuator->options[i].desc; i++)
+      {
+	fprintf (file, "%*s <%s> ", recursion_depth, "",
+		 actuator->desc->option_descs[i].name);
+	switch (actuator->desc->option_descs[i].type)
+	  {
+	  case OPT_TYPE_INT:
+	  case OPT_TYPE_COLOR_INDEX:
+	    fprintf (file, "%d", actuator->options[i].val.ival);
+	    break;
+	  case OPT_TYPE_FLOAT:
+	    fprintf (file, "%.5f", actuator->options[i].val.fval);
+	    break;
+	  case OPT_TYPE_STRING:
+	    fprintf (file, "%s", actuator->options[i].val.sval);
+	    break;
+	  case OPT_TYPE_COLOR:
+	    fprintf (file, "%d, %d, %d", actuator->options[i].val.cval.r,
+		     actuator->options[i].val.cval.g,
+		     actuator->options[i].val.cval.b);
+	    break;
+	  case OPT_TYPE_BOOLEAN:
+	    if (actuator->options[i].val.bval)
+	      fprintf (file, "TRUE");
+	    else
+	      fprintf (file, "FALSE");
+	    break;
+	  }
+	fprintf (file, " </%s>\n", actuator->desc->option_descs[i].name);
+      }
+
+  /* children */
+  if (actuator->desc->flags & ACTUATOR_FLAG_CONTAINER)
+    for (child = *(GSList **)actuator->data; child; child = child->next)
+      if (! save_preset_recursive (file, (struct pn_actuator*) child->data,
+				   recursion_depth+1))
+	return FALSE;
+
+  /* close the actuator */
+  fprintf (file, "%*s</%s>\n", recursion_depth, "", actuator->desc->name);
+
+  return TRUE;
+}
+
+gboolean
+save_preset (const char *filename, const struct pn_actuator *actuator)
+{
+  FILE *file;
+
+  file = fopen (filename, "w");
+  if (! file)
+    {
+      pn_error ("fopen: %s", strerror (errno));
+      return FALSE;
+    }
+
+  fprintf (file, "<?xml version=\"1.0\"?>\n\n<paranormal_preset>\n");
+
+  if (actuator)
+    if (! save_preset_recursive (file, actuator, 1))
+      {
+	fclose (file);
+	return FALSE;
+      }
+
+  fprintf (file, "</paranormal_preset>");
+
+  fclose (file);
+
+  return TRUE;
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/paranormal/presets.h	Mon Oct 30 23:02:33 2006 -0800
@@ -0,0 +1,11 @@
+#ifndef _PRESETS_H
+#define _PRESETS_H
+
+#include <glib.h>
+
+#include "actuators.h"
+
+struct pn_actuator *load_preset (const char *filename);
+gboolean save_preset (const char *filename, const struct pn_actuator *actuator);
+
+#endif /* _PRESETS_H */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/paranormal/wave.c	Mon Oct 30 23:02:33 2006 -0800
@@ -0,0 +1,248 @@
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#include "paranormal.h"
+#include "actuators.h"
+#include "pn_utils.h"
+
+/* **************** wave_horizontal **************** */
+struct pn_actuator_option_desc wave_horizontal_opts[] =
+{
+  {"channels", "Which sound channels to use: negative = channel 1, \npositive = channel 2, "
+   "zero = both (two wave-forms.)", OPT_TYPE_INT, {ival: -1} },
+  { 0 }
+};
+
+static void
+wave_horizontal_exec (const struct pn_actuator_option *opts,
+		    gpointer data)
+{
+  int i;
+  int channel = ( opts[0].val.ival < 0 ) ? 0 : 1;
+
+  for (i=0; i<pn_image_data->width; i++) {
+
+    /*single channel, centered horz.*/
+    if ( opts[0].val.ival ) {
+      pn_image_data->surface[0][PN_IMG_INDEX (i, (pn_image_data->height>>1)
+					      - CAP (pn_sound_data->pcm_data[channel]
+						     [i*512/pn_image_data->width]>>8,
+						     (pn_image_data->height>>1)-1))]
+	= 0xff;
+    }
+
+    /*both channels, at 1/4 and 3/4 of the screen*/
+    else {
+      pn_image_data->surface[0][PN_IMG_INDEX( i, (pn_image_data->height>>2) -
+					      CAP( (pn_sound_data->pcm_data[0]
+						    [i*512/pn_image_data->width]>>9),
+						   (pn_image_data->height>>2)-1))]
+	= 0xff;
+
+      pn_image_data->surface[0][PN_IMG_INDEX( i, 3*(pn_image_data->height>>2) -
+					      CAP( (pn_sound_data->pcm_data[1]
+						    [i*512/pn_image_data->width]>>9),
+						   (pn_image_data->height>>2)-1))]
+	= 0xff;
+    }
+  }
+}
+
+struct pn_actuator_desc builtin_wave_horizontal =
+{
+  "wave_horizontal", "Draws one or two waveforms horizontally across "
+  "the drawing surface",
+  0, wave_horizontal_opts,
+  NULL, NULL, wave_horizontal_exec
+};
+
+/* **************** wave_vertical **************** */
+struct pn_actuator_option_desc wave_vertical_opts[] =
+{
+  {"channels", "Which sound channels to use: negative = channel 1, \npositive = channel 2, "
+   "zero = both (two wave-forms.)", OPT_TYPE_INT, {ival: -1} },
+  { 0 }
+};
+
+static void
+wave_vertical_exec (const struct pn_actuator_option *opts,
+		    gpointer data)
+{
+  int i;
+  int channel = ( opts[0].val.ival < 0 ) ? 0 : 1;
+
+  for (i=0; i<pn_image_data->height; i++) {
+    if ( opts[0].val.ival ) {
+      pn_image_data->surface[0][PN_IMG_INDEX ((pn_image_data->width>>1)
+					      - CAP (pn_sound_data->pcm_data[channel]
+						     [i*512/pn_image_data->height]>>8,
+						     (pn_image_data->width>>1)-1), i)]
+	= 0xff;
+    }
+    else {
+      pn_image_data->surface[0][PN_IMG_INDEX ((pn_image_data->width>>2) 
+					      - CAP (pn_sound_data->pcm_data[0]
+						     [i*512/pn_image_data->height]>>9,
+						     (pn_image_data->width>>2)-1), i)]
+	= 0xff;
+      pn_image_data->surface[0][PN_IMG_INDEX ((3*pn_image_data->width>>2) 
+						 -CAP (pn_sound_data->pcm_data[1]
+						       [i*512/pn_image_data->height]>>9,
+						       (pn_image_data->width>>2)-1), i)]
+			       
+	= 0xff;				      
+    }
+  }
+}
+
+
+struct pn_actuator_desc builtin_wave_vertical =
+{
+  "wave_vertical", "Draws one or two waveforms vertically across "
+  "the drawing surface",
+  0, wave_vertical_opts,
+  NULL, NULL, wave_vertical_exec
+};
+
+/* FIXME: allow for only 1 channel for wave_normalize & wave_smooth */
+/* **************** wave_normalize **************** */
+static struct pn_actuator_option_desc wave_normalize_opts[] =
+{
+  { "height", "If positive, the height, in pixels, to which the waveform will be "
+    "normalized; if negative, hfrac is used", OPT_TYPE_INT, { ival: -1 } },
+  { "hfrac", "If positive, the fraction of the horizontal image size to which the "
+    "waveform will be normalized; if negative, vfrac is used",
+    OPT_TYPE_FLOAT, { fval: -1 } },
+  { "vfrac", "If positive, the fraction of the vertical image size to which the "
+    "waveform will be normalized",
+    OPT_TYPE_FLOAT, { fval: .125 } },
+  { "channels", "Which sound channel(s) to normalize: negative = channel 1,\n"
+    "\tpositive = channel 2, 0 = both channels.",
+    OPT_TYPE_INT, { ival: 0 } },
+  { 0 }
+};
+
+static void
+wave_normalize_exec (const struct pn_actuator_option *opts,
+		     gpointer data)
+{
+  int i, j, max=0;
+  float denom;
+
+  for (j=0; j<2; j++)
+    {
+      if ( !(opts[3].val.ival) || (opts[3].val.ival < 0 && j == 0) ||
+	   (opts[3].val.ival > 0 && j == 1) ) {
+	
+	for (i=0; i<512; i++)
+	  if (abs(pn_sound_data->pcm_data[j][i]) > max)
+	    max = abs(pn_sound_data->pcm_data[j][i]);
+	
+	if (opts[0].val.ival > 0)
+	  denom = max/(opts[0].val.ival<<8);
+	else if (opts[1].val.fval > 0)
+	  denom = max/(opts[1].val.fval * (pn_image_data->width<<8));
+	else
+	  denom = max/(opts[2].val.fval * (pn_image_data->height<<8));
+	
+	if (denom > 0)
+	  for (i=0; i<512; i++)
+	    pn_sound_data->pcm_data[j][i]
+	      /= denom;
+      }
+    }
+}
+
+struct pn_actuator_desc builtin_wave_normalize =
+{
+  "wave_normalize", "Normalizes the waveform data used by the wave_* actuators",
+  0, wave_normalize_opts,
+  NULL, NULL, wave_normalize_exec
+};
+
+/* **************** wave_smooth **************** */
+struct pn_actuator_option_desc wave_smooth_opts[] = 
+{
+  { "channels", "Which sound channel(s) to smooth: negative = channel 1, \n"
+    "\tpositive = channel 2, 0 = both channels.",
+    OPT_TYPE_INT, { ival: 0 } },
+  {0}
+};
+
+static void
+wave_smooth_exec (const struct pn_actuator_option *opts,
+		  gpointer data)
+{
+  int i, j, k;
+  gint16 tmp[512];
+
+  for (j=0; j<2; j++)
+    {
+      if ( !(opts[0].val.ival) || (opts[0].val.ival < 0 && j == 0) ||
+	   (opts[0].val.ival > 0 && j == 1) ) { 
+	
+	for (i=4; i<508; i++)
+	  {
+	    k = (pn_sound_data->pcm_data[j][i]<<3)
+	      + (pn_sound_data->pcm_data[j][i+1]<<2)
+	      + (pn_sound_data->pcm_data[j][i-1]<<2)
+	      + (pn_sound_data->pcm_data[j][i+2]<<2)
+	      + (pn_sound_data->pcm_data[j][i-2]<<2)
+	      + (pn_sound_data->pcm_data[j][i+3]<<1)
+	      + (pn_sound_data->pcm_data[j][i-3]<<1)
+	      + (pn_sound_data->pcm_data[j][i+4]<<1)
+	      + (pn_sound_data->pcm_data[j][i-4]<<1);
+	    tmp[i] = k >> 5;
+	  }
+	memcpy (pn_sound_data->pcm_data[j]+4, tmp, sizeof (gint16) * 504);
+      } 
+    }
+}
+
+struct pn_actuator_desc builtin_wave_smooth =
+{
+  "wave_smooth", "Smooth out the waveform data used by the wave_* actuators",
+  0, wave_smooth_opts,
+  NULL, NULL, wave_smooth_exec
+};
+
+/* **************** wave_radial **************** */
+static struct pn_actuator_option_desc wave_radial_opts[] =
+{
+  { "base_radius", " ", 
+    OPT_TYPE_FLOAT, { fval: 0 } },
+  { 0 }
+};
+
+static void
+wave_radial_exec (const struct pn_actuator_option *opts,
+		  gpointer data)
+{
+  int i, x, y;
+  
+  for(i=0; i<360; i++)
+    {
+      x = (pn_image_data->width>>1)
+	+ (opts[0].val.fval + (pn_sound_data->pcm_data[0][(int)(i*(512.0/360.0))]>>8))
+	* cos_val[i];
+      y = (pn_image_data->height>>1) 
+	+ (opts[0].val.fval + (pn_sound_data->pcm_data[0][(int)(i*(512.0/360.0))]>>8))
+	* sin_val[i];
+
+      pn_image_data->surface[0][PN_IMG_INDEX (CAPHILO(x,pn_image_data->width,0),
+					      CAPHILO(y,pn_image_data->height,0))]
+	= 0xff;
+    }
+};	       
+
+struct pn_actuator_desc builtin_wave_radial =
+{
+  "wave_radial", "Draws a single waveform varying"
+  " radially from the center of the image",
+  0, wave_radial_opts,
+  NULL, NULL, wave_radial_exec
+};
+
+
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/paranormal/xform.c	Mon Oct 30 23:02:33 2006 -0800
@@ -0,0 +1,383 @@
+/* FIXME: allow for only using an xform on part of the img? */
+/* FIXME: perhaps combine these into a single vector field
+   so that only 1 apply_xform needs to be done for as many
+   of these as someone wants to use */
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#include <math.h>
+
+#include <glib.h>
+
+#include "paranormal.h"
+#include "actuators.h"
+#include "pn_utils.h"
+
+struct xform_vector
+{
+  guint offset; /* the offset of the top left pixel */
+  guint16 w; /* 4:4:4:4 NE, NW, SE, SW pixel weights
+		  The total should be 16 */
+
+  /* if offset < 0 then w is the color index to
+     which the pixel will be set */
+};
+
+static void
+xfvec (float x, float y, struct xform_vector *v)
+{
+  float xd, yd;
+  int weight[4];
+
+  if (x >= pn_image_data->width-1 || y >= pn_image_data->height-1
+      || x < 0 || y < 0)
+    {
+      v->offset = -1;
+      v->w = 0;
+      return;
+    }
+
+  v->offset = PN_IMG_INDEX (floor(x), floor(y));
+
+  xd = x - floor (x);
+  yd = y - floor (y);
+
+  weight[3] = xd * yd * 16;
+  weight[2] = (1-xd) * yd * 16;
+  weight[1] = xd * (1-yd) * 16;
+  weight[0] = 16 - weight[3] - weight[2] - weight[1]; /* just in case */
+
+  v->w = (weight[0]<<12) | (weight[1]<<8) | (weight[2]<<4) | weight[3];
+}
+
+static void
+apply_xform (struct xform_vector *vfield)
+{
+  int i;
+  struct xform_vector *v;
+  register guchar *srcptr;
+  register guchar *destptr;
+  register int color;
+
+  for (i=0, v=vfield, destptr=pn_image_data->surface[1];
+       i<pn_image_data->width*pn_image_data->height;
+       i++, v++, destptr++)
+    {
+      /* off the screen */
+      if (v->offset < 0)
+	{
+	  *destptr = (guchar)v->w;
+	  continue;
+	}
+
+      srcptr = pn_image_data->surface[0] + v->offset;
+
+      /* exactly on the pixel */
+      if (v->w == 0)
+	  *destptr = *srcptr;
+      
+      /* gotta blend the points */
+      else
+	{
+	  color =  *srcptr * (v->w>>12);
+	  color += *++srcptr * ((v->w>>8) & 0x0f);
+	  color += *(srcptr+=pn_image_data->width) * (v->w & 0x0f);
+	  color += *(--srcptr) * ((v->w>>4) & 0x0f);
+	  color >>= 4;
+	  *destptr = (guchar)color;
+	}
+    }
+}
+
+/* **************** xform_spin **************** */
+/* FIXME: Describe these better, how they are backwards */
+/* FIXME: better name? */
+struct pn_actuator_option_desc xform_spin_opts[] =
+{
+  { "angle", "The angle of rotation", OPT_TYPE_FLOAT, { fval: -8.0 } },
+  { "r_add", "The number of pixels by which the r coordinate will be "
+    "increased (before scaling)", OPT_TYPE_FLOAT, { fval: 0.0 } },
+  { "r_scale", "The amount by which the r coordinate of each pixel will "
+    "be scaled", OPT_TYPE_FLOAT, { fval: 1.0 } },
+  { 0 }
+};
+
+struct xform_spin_data
+{
+  int width, height;
+  struct xform_vector *vfield;
+};
+
+static void
+xform_spin_init (gpointer *data)
+{
+  *data = g_new0 (struct xform_spin_data, 1);
+}
+
+static void
+xform_spin_cleanup (gpointer data)
+{
+  struct xform_spin_data *d = (struct xform_spin_data *) data;
+
+  
+  if (d)
+    {
+      if (d->vfield)
+	g_free (d->vfield);
+      g_free (d);
+    }
+}
+
+static void
+xform_spin_exec (const struct pn_actuator_option *opts,
+		 gpointer data)
+{
+  struct xform_spin_data *d = (struct xform_spin_data*)data;
+  float i, j;
+
+  if (d->width != pn_image_data->width
+      || d->height != pn_image_data->height)
+    {
+      d->width = pn_image_data->width;
+      d->height = pn_image_data->height;
+
+      if (d->vfield)
+	g_free (d->vfield);
+
+      d->vfield = g_malloc (sizeof(struct xform_vector)
+			    * d->width * d->height);
+
+      for (j=-(pn_image_data->height>>1)+1; j<=pn_image_data->height>>1; j++)
+	for (i=-(pn_image_data->width>>1); i<pn_image_data->width>>1; i++)
+	  {
+	    float r, t = 0;
+	    float x, y;
+
+	    r = sqrt (i*i + j*j);
+	    if (r)
+	      t = asin (j/r);
+	    if (i < 0)
+	      t = M_PI - t;
+
+	    t += opts[0].val.fval * M_PI/180.0; 
+	    r += opts[1].val.fval;
+	    r *= opts[2].val.fval;
+
+	    x = (r * cos (t)) + (pn_image_data->width>>1);
+	    y = (pn_image_data->height>>1) - (r * sin (t));
+
+	    xfvec (x, y, &d->vfield
+		   [PN_IMG_INDEX ((pn_image_data->width>>1)+(int)rint(i),
+				  ((pn_image_data->height>>1)-(int)rint(j)))]);
+	  }
+    }
+
+  apply_xform (d->vfield);
+  pn_swap_surfaces ();
+}
+
+struct pn_actuator_desc builtin_xform_spin =
+{
+  "xform_spin", "Rotates and radially scales the image",
+  0, xform_spin_opts,
+  xform_spin_init, xform_spin_cleanup, xform_spin_exec
+};
+
+/* **************** xform_ripple **************** */
+struct pn_actuator_option_desc xform_ripple_opts[] =
+{
+  { "angle", "The angle of rotation", OPT_TYPE_FLOAT, { fval: 0 } },
+  { "ripples", "The number of ripples that fit on the screen "
+    "(horizontally)", OPT_TYPE_FLOAT, { fval: 8 } },
+  { "base_speed", "The minimum number of pixels to move each pixel",
+    OPT_TYPE_FLOAT, { fval: 1 } },
+  { "mod_speed", "The maximum number of pixels by which base_speed"
+    " will be modified", OPT_TYPE_FLOAT, { fval: 1 } },
+  { 0 }
+};
+
+struct xform_ripple_data
+{
+  int width, height;
+  struct xform_vector *vfield;
+};
+
+static void
+xform_ripple_init (gpointer *data)
+{
+  *data = g_new0 (struct xform_ripple_data, 1);
+}
+
+static void
+xform_ripple_cleanup (gpointer data)
+{
+  struct xform_ripple_data *d = (struct xform_ripple_data*) data;
+
+  if (d)
+    {
+      if (d->vfield)
+	g_free (d->vfield);
+      g_free (d);
+    }
+}
+
+static void
+xform_ripple_exec (const struct pn_actuator_option *opts,
+		 gpointer data)
+{
+  struct xform_ripple_data *d = (struct xform_ripple_data*)data;
+  float i, j;
+
+  if (d->width != pn_image_data->width
+      || d->height != pn_image_data->height)
+    {
+      d->width = pn_image_data->width;
+      d->height = pn_image_data->height;
+
+      if (d->vfield)
+	g_free (d->vfield);
+
+      d->vfield = g_malloc (sizeof(struct xform_vector)
+			    * d->width * d->height);
+
+      for (j=-(pn_image_data->height>>1)+1; j<=pn_image_data->height>>1; j++)
+	for (i=-(pn_image_data->width>>1); i<pn_image_data->width>>1; i++)
+	  {
+	    float r, t = 0;
+	    float x, y;
+
+	    r = sqrt (i*i + j*j);
+	    if (r)
+	      t = asin (j/r);
+	    if (i < 0)
+	      t = M_PI - t;
+
+	    t += opts[0].val.fval * M_PI/180.0; 
+
+	    if (r > 4)//(pn_image_data->width/(2*opts[1].val.fval)))
+	      r -=  opts[2].val.fval + (opts[3].val.fval/2) *
+		(1 + sin ((r/(pn_image_data->width/(2*opts[1].val.fval)))*M_PI));
+/*  	    else if (r > 4) */
+/*  	      r *= r/(pn_image_data->width/opts[1].val.fval); */
+	    else /* don't let it explode */
+	      r = 1000000;
+
+
+	    x = (r * cos (t)) + (pn_image_data->width>>1);
+	    y = (pn_image_data->height>>1) - (r * sin (t));
+
+	    xfvec (x, y, &d->vfield
+		   [PN_IMG_INDEX ((pn_image_data->width>>1)+(int)rint(i),
+				  ((pn_image_data->height>>1)-(int)rint(j)))]);
+	  }
+    }
+
+  apply_xform (d->vfield);
+  pn_swap_surfaces ();
+}
+
+struct pn_actuator_desc builtin_xform_ripple =
+{
+  "xform_ripple", "Creates an ripple effect",
+  0, xform_ripple_opts,
+  xform_ripple_init, xform_ripple_cleanup, xform_ripple_exec
+};
+
+/* **************** xform_bump_spin **************** */
+struct pn_actuator_option_desc xform_bump_spin_opts[] =
+{
+  { "angle", "The angle of rotation", OPT_TYPE_FLOAT, { fval: 0 } },
+  { "bumps", "The number of bumps that on the image",
+    OPT_TYPE_FLOAT, { fval: 8 } },
+  { "base_scale", "The base radial scale",
+    OPT_TYPE_FLOAT, { fval: 0.95 } },
+  { "mod_scale", "The maximum amount that should be "
+    "added to the base_scale to create the 'bump' effect",
+    OPT_TYPE_FLOAT, { fval: .1 } },
+  { 0 }
+};
+
+struct xform_bump_spin_data
+{
+  int width, height;
+  struct xform_vector *vfield;
+};
+
+static void
+xform_bump_spin_init (gpointer *data)
+{
+  *data = g_new0 (struct xform_bump_spin_data, 1);
+}
+
+static void
+xform_bump_spin_cleanup (gpointer data)
+{
+  struct xform_bump_spin_data *d = (struct xform_bump_spin_data*) data;
+
+  if (d)
+    {
+      if (d->vfield)
+	g_free (d->vfield);
+      g_free (d);
+    }
+}
+
+static void
+xform_bump_spin_exec (const struct pn_actuator_option *opts,
+		 gpointer data)
+{
+  struct xform_bump_spin_data *d = (struct xform_bump_spin_data*)data;
+  float i, j;
+
+  if (d->width != pn_image_data->width
+      || d->height != pn_image_data->height)
+    {
+      d->width = pn_image_data->width;
+      d->height = pn_image_data->height;
+
+      if (d->vfield)
+	g_free (d->vfield);
+
+      d->vfield = g_malloc (sizeof(struct xform_vector)
+			    * d->width * d->height);
+
+      for (j=-(pn_image_data->height>>1)+1; j<=pn_image_data->height>>1; j++)
+	for (i=-(pn_image_data->width>>1); i<pn_image_data->width>>1; i++)
+	  {
+	    float r, t = 0;
+	    float x, y;
+
+	    r = sqrt (i*i + j*j);
+	    if (r)
+	      t = asin (j/r);
+	    if (i < 0)
+	      t = M_PI - t;
+
+	    t += opts[0].val.fval * M_PI/180.0;
+
+	    r *=  opts[2].val.fval + opts[3].val.fval
+	      * (1 + sin (t*opts[1].val.fval));
+
+	    x = (r * cos (t)) + (pn_image_data->width>>1);
+	    y = (pn_image_data->height>>1) - (r * sin (t));
+
+	    xfvec (x, y, &d->vfield
+		   [PN_IMG_INDEX ((pn_image_data->width>>1)+(int)rint(i),
+				  ((pn_image_data->height>>1)-(int)rint(j)))]);
+	  }
+    }
+
+  apply_xform (d->vfield);
+  pn_swap_surfaces ();
+}
+
+struct pn_actuator_desc builtin_xform_bump_spin =
+{
+  "xform_bump_spin", "Rotate the image at a varying speed to create "
+  "the illusion of bumps",
+  0, xform_bump_spin_opts,
+  xform_bump_spin_init, xform_bump_spin_cleanup, xform_bump_spin_exec
+};
+