/*
 * test-ladspa 0.1 - test app for LADSPA plugins, partly based on "demolition".
 * Copyright (C) 2009 Damon Chaplin <damon@gnome.org>
 * Released under the GNU General Public License version 2 or later.
 * "demolition" Copyright (C) 2002,2003 Nick Lamb <njl195@zepler.org.uk>
 *
 * Compile with:
 * gcc `pkg-config --cflags --libs gmodule-2.0` -lm -o test-ladspa test-ladspa.c
 *
 * Simply run it with './test-ladspa' to test all plugins.
 * Run it with './test-ladspa -p PACKAGE' to test a particular package.
 * (Run ./test-ladspa --help to see the list of known packages.)
 *
 * Use the -f option to fork and run tests in child processes, so that the
 * test continue even of one of them crashes or gets into an infinite loop.
 *
 * Run it with 'valgrind --tool=memcheck ./test-ladspa' to spot memory errors.
 */
#define _ISOC99_SOURCE

#include <stdlib.h>
#include <stdio.h>
#include <stdarg.h>
#include <string.h>
#include <signal.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <math.h>
#include <glib.h>
#include <gmodule.h>
#include <ladspa.h>

/* The number of seconds allowed for a test to complete, before we assume
   it is in an infinite loop and kill it (when using the -f option). */
#define PLUGIN_TEST_TIMEOUT	10

/* The default places to look for LADSPA plugins, if LADSPA_PATH isn't set. */
#define DEFAULT_LADSPA_PATH	"/usr/lib/ladspa:/usr/local/lib/ladspa"

/* The sample rate to pass to the plugins. We don't output audio so it's not
   too important. */
#define SAMPLE_RATE		48000

/* The default number of samples to use for the run. */
#define NUM_SAMPLES		256

typedef enum {
  CONTROL_DEFAULT,
  CONTROL_LOWER_BOUND,
  CONTROL_UPPER_BOUND,
  CONTROL_0,
  CONTROL_FLOAT_MAX,
  CONTROL_NEGATIVE_FLOAT_MAX,
  CONTROL_FLOAT_MIN,
  CONTROL_NEGATIVE_FLOAT_MIN,
  CONTROL_INFINITY,
  CONTROL_NEGATIVE_INFINITY
} ControlInput;

static char *package = NULL;
static char *library = NULL;
static int plugin_id = -1;
static char **file_list = NULL;
static gboolean fork_to_test = FALSE;

static int num_plugins_tested = 0;
static int num_errors = 0;
static gboolean plugin_has_errors;

/* These are the lists of files containing the plugins in the various
   packages. I got most of these using 'rpm -ql <PACKAGE>'. There may be
   more packages around that I haven't got installed at present. */
static char *acweight_file_list[] = { "acwplug.so", NULL };
static char *amb_file_list[] = { "ambisonic1.so", "ambisonic2.so", NULL };
static char *autotalent_file_list[] = { "autotalent.so", NULL };
static char *blop_file_list[] = { 
  "adsr_1653.so", "adsr_1680.so", "amp_1654.so", "branch_1673.so",
  "dahdsr_2021.so", "difference_2030.so", "fmod_1656.so",
  "interpolator_1660.so", "lp4pole_1671.so", "parabola_1649_data.so",
  "product_1668.so", "pulse_1645.so", "quantiser100_2029.so",
  "quantiser20_2027.so", "quantiser50_2028.so", "random_1661.so",
  "ratio_2034.so", "sawtooth_1641_data.so", "sawtooth_1641.so",
  "sequencer16_1677.so", "sequencer32_1676.so", "sequencer64_1675.so",
  "square_1643_data.so", "square_1643.so", "sum_1665.so",
  "sync_pulse_2023.so", "sync_square_1678.so", "tracker_2025.so",
  "triangle_1649.so",
  NULL
};
static char *calf_file_list[] = { "calf.so", NULL };
static char *caps_file_list[] = { "caps.so", NULL };
static char *cmt_file_list[] = { "cmt.so", NULL };
static char *fil_file_list[] = { "filters.so", NULL };
static char *invada_file_list[] = { "inv_compressor.so", "inv_erreverb.so",
				    "inv_filter.so", "inv_input.so",
				    "inv_tube.so", NULL };
static char *ladspa_file_list[] = { "amp.so", "delay.so", "filter.so",
				    "noise.so", "sine.so", NULL };
static char *mcp_file_list[] = { "cs_chorus.so", "cs_phaser.so",
				 "mvchpf24.so", "mvclpf24.so", NULL };
static char *njl_file_list[] = { "eir_1923.so", "noise_1921.so",
				 "noise_1922.so", "risset_1924.so", NULL };
static char *rev_file_list[] = { "g2reverb.so", NULL };
static char *swh_file_list[] = {
  "alias_1407.so", "allpass_1895.so", "am_pitchshift_1433.so", "amp_1181.so", 
  "analogue_osc_1416.so", "bandpass_a_iir_1893.so", "bandpass_iir_1892.so", 
  "bode_shifter_1431.so", "bode_shifter_cv_1432.so", "butterworth_1902.so", 
  "chebstortion_1430.so", "comb_1190.so", "comb_1887.so",
  "comb_splitter_1411.so", "const_1909.so", "crossover_dist_1404.so", 
  "dc_remove_1207.so", "decay_1886.so", "decimator_1202.so", "declip_1195.so", 
  "delay_1898.so", "delayorama_1402.so", "diode_1185.so", "divider_1186.so", 
  "dj_eq_1901.so", "dj_flanger_1438.so", "dyson_compress_1403.so", 
  "fad_delay_1192.so", "fast_lookahead_limiter_1913.so", "flanger_1191.so", 
  "fm_osc_1415.so", "foldover_1213.so", "foverdrive_1196.so",
  "freq_tracker_1418.so", "gate_1410.so", "giant_flange_1437.so",
  "gong_1424.so", "gong_beater_1439.so", "gsm_1215.so", "gverb_1216.so", 
  "hard_limiter_1413.so", "harmonic_gen_1220.so", "hermes_filter_1200.so", 
  "highpass_iir_1890.so", "hilbert_1440.so", "imp_1199.so", "impulse_1885.so", 
  "inv_1429.so", "karaoke_1409.so", "latency_1914.so", "lcr_delay_1436.so", 
  "lowpass_iir_1891.so", "ls_filter_1908.so", "matrix_ms_st_1421.so", 
  "matrix_spatialiser_1422.so", "matrix_st_ms_1420.so", "mbeq_1197.so", 
  "mod_delay_1419.so", "multivoice_chorus_1201.so", "notch_iir_1894.so", 
  "phasers_1217.so", "pitch_scale_1193.so", "pitch_scale_1194.so", 
  "plate_1423.so", "pointer_cast_1910.so", "rate_shifter_1417.so", 
  "retro_flange_1208.so", "revdelay_1605.so", "ringmod_1188.so", 
  "satan_maximiser_1408.so", "sc1_1425.so", "sc2_1426.so", "sc3_1427.so", 
  "sc4_1882.so", "sc4m_1916.so", "se4_1883.so", "shaper_1187.so", 
  "sifter_1210.so", "sin_cos_1881.so", "single_para_1203.so", 
  "sinus_wavewrapper_1198.so", "smooth_decimate_1414.so", "split_1406.so", 
  "step_muxer_1212.so", "surround_encoder_1401.so", "svf_1214.so", 
  "tape_delay_1211.so", "transient_1206.so", "triple_para_1204.so", 
  "valve_1209.so", "valve_rect_1405.so", "vynil_1905.so",
  "wave_terrain_1412.so", "xfade_1915.so", "zm1_1428.so", 
  NULL
};
static char *tap_file_list[] = {
  "tap_autopan.so", "tap_chorusflanger.so", "tap_deesser.so", "tap_doubler.so", 
  "tap_dynamics_m.so", "tap_dynamics_st.so", "tap_echo.so", "tap_eq.so", 
  "tap_eqbw.so", "tap_limiter.so", "tap_pinknoise.so", "tap_pitch.so", 
  "tap_reflector.so", "tap_reverb.so", "tap_rotspeak.so", "tap_sigmoid.so", 
  "tap_tremolo.so", "tap_tubewarmth.so", "tap_vibrato.so",
  NULL
};
static char *vcf_file_list[] = { "vcf_cv_in.so", "vcf.so", NULL };
static char *vco_file_list[] = { "blvco.so", "vco_sawpulse.so", NULL };


static void
log_error (const char *format,
	   ...)
{
  va_list args;

  va_start (args, format);
  fprintf (stderr, "### ERROR: ");
  vfprintf (stderr, format, args);
  fflush (stderr);
  va_end (args);

  plugin_has_errors = TRUE;
}


static void
log_warning (const char *format,
	     ...)
{
  va_list args;

  va_start (args, format);
  fprintf (stderr, "### WARNING: ");
  vfprintf (stderr, format, args);
  fflush (stderr);
  va_end (args);
}


static gpointer
create_buffer_for_port (LADSPA_Descriptor *descriptor,
			gint               port_num,
			ControlInput       control_inputs,
			gint               audio_buffer_size,
			gfloat             audio_sample_value)
{
  LADSPA_PortDescriptor port_descriptor;
  const LADSPA_PortRangeHint *port_range_hint;
  LADSPA_PortRangeHintDescriptor hint_descriptor;
  gfloat *buffer, default_value = 0.0F, minimum, maximum, value;
  gint i;

  port_descriptor = descriptor->PortDescriptors[port_num];
  port_range_hint = &descriptor->PortRangeHints[port_num];
  hint_descriptor = port_range_hint->HintDescriptor;

  if (port_descriptor & LADSPA_PORT_AUDIO)
    {
      buffer = g_new (float, audio_buffer_size);
      for (i = 0; i < audio_buffer_size; i++)
	buffer[i] = audio_sample_value;
    }
  else
    {
      buffer = g_new (float, 1);

      minimum = port_range_hint->LowerBound;
      if (LADSPA_IS_HINT_SAMPLE_RATE (hint_descriptor))
	minimum *= SAMPLE_RATE;

      maximum = port_range_hint->UpperBound;
      if (LADSPA_IS_HINT_SAMPLE_RATE (hint_descriptor))
	maximum *= SAMPLE_RATE;

      /* Calculate the default value. See ladspa.h for the formulas.
	 We could try testing plugins with all kinds of input data later. */
      if (LADSPA_IS_HINT_HAS_DEFAULT (hint_descriptor))
	{
	  if (LADSPA_IS_HINT_DEFAULT_MINIMUM (hint_descriptor))
	    default_value = minimum;
	  else if (LADSPA_IS_HINT_DEFAULT_MAXIMUM (hint_descriptor))
	    default_value = maximum;
	  else if (LADSPA_IS_HINT_DEFAULT_0 (hint_descriptor))
	    default_value = 0;
	  else if (LADSPA_IS_HINT_DEFAULT_1 (hint_descriptor))
	    default_value = 1;
	  else if (LADSPA_IS_HINT_DEFAULT_100 (hint_descriptor))
	    default_value = 100;
	  else if (LADSPA_IS_HINT_DEFAULT_440 (hint_descriptor))
	    default_value = 440;

	  else if (LADSPA_IS_HINT_DEFAULT_LOW (hint_descriptor))
	    {
	      if (LADSPA_IS_HINT_LOGARITHMIC (hint_descriptor))
		default_value = exp (log (minimum) * 0.75 + log (maximum) * 0.25);
	      else
		default_value = (minimum * 0.75 + maximum * 0.25);
	    }
	  else if (LADSPA_IS_HINT_DEFAULT_MIDDLE (hint_descriptor))
	    {
	      if (LADSPA_IS_HINT_LOGARITHMIC (hint_descriptor))
		default_value = exp (log (minimum) * 0.5 + log (maximum) * 0.5);
	      else
		default_value = (minimum * 0.5 + maximum * 0.5);
	    }
	  else if (LADSPA_IS_HINT_DEFAULT_HIGH (hint_descriptor))
	    {
	      if (LADSPA_IS_HINT_LOGARITHMIC (hint_descriptor))
		default_value = exp (log (minimum) * 0.25 + log (maximum) * 0.75);
	      else
		default_value = (minimum * 0.25 + maximum * 0.75);
	    }
	}

      switch (control_inputs)
	{
	case CONTROL_DEFAULT:
	  value = default_value;
	  break;
	case CONTROL_LOWER_BOUND:
	  value = minimum;
	  break;
	case CONTROL_UPPER_BOUND:
	  value = maximum;
	  break;
	case CONTROL_0:
	  value = 0.0f;
	  break;
	case CONTROL_FLOAT_MAX:
	  value = FLT_MAX;
	  break;
	case CONTROL_NEGATIVE_FLOAT_MAX:
	  value = -FLT_MAX;
	  break;
	case CONTROL_FLOAT_MIN:
	  value = FLT_MIN;
	  break;
	case CONTROL_NEGATIVE_FLOAT_MIN:
	  value = -FLT_MIN;
	  break;
	case CONTROL_INFINITY:
	  value = INFINITY;
	  break;
	case CONTROL_NEGATIVE_INFINITY:
	  value = -INFINITY;
	  break;
	}

      *buffer = value;
    }

  return buffer;
}


static gboolean
connect_ports (LADSPA_Descriptor  *descriptor,
	       LADSPA_Handle       instance,
	       GList             **buffers,
	       ControlInput        control_inputs,
	       gint                audio_buffer_size,
	       gfloat              audio_sample_value)
{
  gint num_ports, port_num;
  gpointer buffer;

  num_ports = descriptor->PortCount;
  for (port_num = 0; port_num < num_ports; port_num++)
    {
      buffer = create_buffer_for_port (descriptor, port_num, control_inputs,
				       audio_buffer_size, audio_sample_value);
      descriptor->connect_port (instance, port_num, buffer);
      *buffers = g_list_prepend (*buffers, buffer);
    }

  return TRUE;
}


/* Returns TRUE if a plugin should be skipped. */
static gboolean
skip_file (const gchar       *filename)
{
  gint i;

  if (library)
    return (!strcmp (filename, library)) ? FALSE :  TRUE;

  if (file_list)
    {
      for (i = 0; file_list[i]; i++)
	{
	  if (!strcmp (file_list[i], filename))
	    return FALSE;
	}
      return TRUE;
    }

  return FALSE;
}


static void
free_buffers (GList **buffers)
{
  GList *elem;

  for (elem = *buffers; elem; elem = elem->next)
    g_free (elem->data);
  g_list_free (*buffers);
  *buffers = NULL;
}


static void
test_plugin_info (LADSPA_Descriptor *descriptor)
{
  LADSPA_PortDescriptor port;
  LADSPA_PortRangeHint hint;
  LADSPA_PortRangeHintDescriptor hintd;
  const gchar *p;
  gint port_num;

  if (descriptor->UniqueID > 0x1000000)
    log_error ("Invalid UniqueID, >= 0x1000000\n");

  if (descriptor->Label == NULL)
    log_error ("Label is NULL\n");

  for (p = descriptor->Label; *p; p++)
    {
      if (isspace (*p))
	log_error ("Label contains illegal white-space character\n");
    }

  if (descriptor->Name == NULL)
    log_error ("Name is NULL\n");

  if (descriptor->Maker == NULL)
    log_error ("Maker is NULL\n");

  if (descriptor->Copyright == NULL)
    log_error ("Copyright is NULL\n");

  for (port_num = 0; port_num < descriptor->PortCount; port_num++)
    {
      port = descriptor->PortDescriptors[port_num];
      hint = descriptor->PortRangeHints[port_num];
      hintd = hint.HintDescriptor;

      if (LADSPA_IS_PORT_INPUT (port) && LADSPA_IS_PORT_OUTPUT (port))
	log_error ("Port %lu is both INPUT and OUTPUT\n", port_num);
      if (!LADSPA_IS_PORT_INPUT (port) && !LADSPA_IS_PORT_OUTPUT (port))
	log_error ("Port %lu is neither INPUT nor OUTPUT\n", port_num);

      if (LADSPA_IS_PORT_CONTROL (port) && LADSPA_IS_PORT_AUDIO (port))
	log_error ("Port %lu is both CONTROL and AUDIO\n", port_num);
      if (!LADSPA_IS_PORT_CONTROL (port) && !LADSPA_IS_PORT_AUDIO (port))
	log_error ("Port %lu is neither CONTROL nor AUDIO\n", port_num);

      if (LADSPA_IS_HINT_BOUNDED_BELOW (hintd)
	  && LADSPA_IS_HINT_BOUNDED_ABOVE (hintd)
	  && hint.LowerBound > hint.UpperBound)
	log_error ("Port %lu has LowerBound larger than UpperBound\n",
		   port_num);

      if (LADSPA_IS_HINT_TOGGLED (hintd))
	{
	  if (hintd & ~(LADSPA_HINT_TOGGLED | LADSPA_HINT_DEFAULT_0
			| LADSPA_HINT_DEFAULT_1))
	    log_error ("Port %lu has an invalid combination of hints for a TOGGLED port\n", port_num);

	  if (LADSPA_IS_HINT_HAS_DEFAULT (hintd)
	      && !LADSPA_IS_HINT_DEFAULT_0 (hintd)
	      && !LADSPA_IS_HINT_DEFAULT_1 (hintd))
	    log_error ("Port %lu has an invalid default for a TOGGLED port\n",
		       port_num);
	}
    }

  if (descriptor->run_adding && !descriptor->set_run_adding_gain)
    log_error ("run_adding() implemented without set_run_adding_gain()\n");

  if (!descriptor->run_adding && descriptor->set_run_adding_gain)
    log_error ("set_run_adding_gain() implemented without run_adding()\n");
}


static gboolean
test_lifecycle (LADSPA_Descriptor *descriptor)
{
  LADSPA_Handle instance;
  GList *buffers = NULL;
  gboolean ok;

  g_printerr ("RUNNING TEST: Instantiate, Cleanup\n");
  instance = descriptor->instantiate (descriptor, SAMPLE_RATE);
  if (!instance)
    {
      log_error ("Unable to instantiate plugin\n");
      return FALSE;
    }
  descriptor->cleanup (instance);

  g_printerr ("RUNNING TEST: Instantiate, Activate, Deactivate, Cleanup\n");
  instance = descriptor->instantiate (descriptor, SAMPLE_RATE);
  /* We shouldn't really have to connect the ports but quite a few plugins
     will crash if we don't. */
#if 1
  connect_ports (descriptor, instance, &buffers, CONTROL_DEFAULT, 1, 0.0);
#endif
  if (descriptor->activate) descriptor->activate (instance);
  if (descriptor->deactivate) descriptor->deactivate (instance);
  free_buffers (&buffers);
  descriptor->cleanup (instance);

  g_printerr ("RUNNING TEST: Run once, for 1 sample\n");
  instance = descriptor->instantiate (descriptor, SAMPLE_RATE);
  connect_ports (descriptor, instance, &buffers, CONTROL_DEFAULT, 1, 0.0);
  if (descriptor->activate) descriptor->activate (instance);
  descriptor->run (instance, 1);
  if (descriptor->deactivate) descriptor->deactivate (instance);
  free_buffers (&buffers);
  descriptor->cleanup (instance);

  g_printerr ("RUNNING TEST: Activate, Run, Deactivate, Activate, Run, Deactivate\n");
  instance = descriptor->instantiate (descriptor, SAMPLE_RATE);
  connect_ports (descriptor, instance, &buffers, CONTROL_DEFAULT, 1, 0.0);
  if (descriptor->activate) descriptor->activate (instance);
  descriptor->run (instance, 1);
  if (descriptor->deactivate) descriptor->deactivate (instance);
  if (descriptor->activate) descriptor->activate (instance);
  descriptor->run (instance, 1);
  if (descriptor->deactivate) descriptor->deactivate (instance);
  free_buffers (&buffers);
  descriptor->cleanup (instance);

  return TRUE;
}


static void
test_run_sample_nums (LADSPA_Descriptor *descriptor)
{
  LADSPA_Handle instance;
  GList *buffers = NULL;

  instance = descriptor->instantiate (descriptor, SAMPLE_RATE);

  g_printerr ("RUNNING TEST: Run for 4096 samples\n");
  connect_ports (descriptor, instance, &buffers, CONTROL_DEFAULT, 4096, 0.0);
  if (descriptor->activate) descriptor->activate (instance);
  descriptor->run (instance, 4096);
  free_buffers (&buffers);

  g_printerr ("RUNNING TEST: Run for 16 samples\n");
  connect_ports (descriptor, instance, &buffers, CONTROL_DEFAULT, 16, 0.0);
  descriptor->run (instance, 16);
  free_buffers (&buffers);

  g_printerr ("RUNNING TEST: Run for 1 sample\n");
  connect_ports (descriptor, instance, &buffers, CONTROL_DEFAULT, 1, 0.0);
  descriptor->run (instance, 1);

  if (descriptor->deactivate) descriptor->deactivate (instance);
  free_buffers (&buffers);
  descriptor->cleanup (instance);
}


static void
test_control_inputs_normal_range (LADSPA_Descriptor *descriptor)
{
  LADSPA_Handle instance;
  GList *buffers = NULL;

  g_printerr ("RUNNING TEST: Control inputs set to lower bound\n");
  instance = descriptor->instantiate (descriptor, SAMPLE_RATE);
  connect_ports (descriptor, instance, &buffers, CONTROL_LOWER_BOUND, NUM_SAMPLES, 0.0);
  if (descriptor->activate) descriptor->activate (instance);
  descriptor->run (instance, NUM_SAMPLES);
  if (descriptor->deactivate) descriptor->deactivate (instance);
  free_buffers (&buffers);
  descriptor->cleanup (instance);

  g_printerr ("RUNNING TEST: Control inputs set to upper bound\n");
  instance = descriptor->instantiate (descriptor, SAMPLE_RATE);
  connect_ports (descriptor, instance, &buffers, CONTROL_UPPER_BOUND, NUM_SAMPLES, 0.0);
  if (descriptor->activate) descriptor->activate (instance);
  descriptor->run (instance, NUM_SAMPLES);
  if (descriptor->deactivate) descriptor->deactivate (instance);
  free_buffers (&buffers);
  descriptor->cleanup (instance);
}


static void
test_control_inputs_extreme_range (LADSPA_Descriptor *descriptor)
{
  LADSPA_Handle instance;
  GList *buffers = NULL;

  g_printerr ("RUNNING TEST: Control inputs set to 0.0\n");
  instance = descriptor->instantiate (descriptor, SAMPLE_RATE);
  connect_ports (descriptor, instance, &buffers, CONTROL_0, NUM_SAMPLES, 0.0);
  if (descriptor->activate) descriptor->activate (instance);
  descriptor->run (instance, NUM_SAMPLES);
  if (descriptor->deactivate) descriptor->deactivate (instance);
  free_buffers (&buffers);
  descriptor->cleanup (instance);

  g_printerr ("RUNNING TEST: Control inputs set to FLOAT MAX\n");
  instance = descriptor->instantiate (descriptor, SAMPLE_RATE);
  connect_ports (descriptor, instance, &buffers, CONTROL_FLOAT_MAX, NUM_SAMPLES, 0.0);
  if (descriptor->activate) descriptor->activate (instance);
  descriptor->run (instance, NUM_SAMPLES);
  if (descriptor->deactivate) descriptor->deactivate (instance);
  free_buffers (&buffers);
  descriptor->cleanup (instance);

  g_printerr ("RUNNING TEST: Control inputs set to -FLOAT MAX\n");
  instance = descriptor->instantiate (descriptor, SAMPLE_RATE);
  connect_ports (descriptor, instance, &buffers, CONTROL_NEGATIVE_FLOAT_MAX, NUM_SAMPLES, 0.0);
  if (descriptor->activate) descriptor->activate (instance);
  descriptor->run (instance, NUM_SAMPLES);
  if (descriptor->deactivate) descriptor->deactivate (instance);
  free_buffers (&buffers);
  descriptor->cleanup (instance);

  g_printerr ("RUNNING TEST: Control inputs set to FLOAT MIN\n");
  instance = descriptor->instantiate (descriptor, SAMPLE_RATE);
  connect_ports (descriptor, instance, &buffers, CONTROL_FLOAT_MIN, NUM_SAMPLES, 0.0);
  if (descriptor->activate) descriptor->activate (instance);
  descriptor->run (instance, NUM_SAMPLES);
  if (descriptor->deactivate) descriptor->deactivate (instance);
  free_buffers (&buffers);
  descriptor->cleanup (instance);

  g_printerr ("RUNNING TEST: Control inputs set to -FLOAT MIN\n");
  instance = descriptor->instantiate (descriptor, SAMPLE_RATE);
  connect_ports (descriptor, instance, &buffers, CONTROL_NEGATIVE_FLOAT_MIN, NUM_SAMPLES, 0.0);
  if (descriptor->activate) descriptor->activate (instance);
  descriptor->run (instance, NUM_SAMPLES);
  if (descriptor->deactivate) descriptor->deactivate (instance);
  free_buffers (&buffers);
  descriptor->cleanup (instance);

  g_printerr ("RUNNING TEST: Control inputs set to INFINITY\n");
  instance = descriptor->instantiate (descriptor, SAMPLE_RATE);
  connect_ports (descriptor, instance, &buffers, CONTROL_INFINITY, NUM_SAMPLES, 0.0);
  if (descriptor->activate) descriptor->activate (instance);
  descriptor->run (instance, NUM_SAMPLES);
  if (descriptor->deactivate) descriptor->deactivate (instance);
  free_buffers (&buffers);
  descriptor->cleanup (instance);

  g_printerr ("RUNNING TEST: Control inputs set to -INFINITY\n");
  instance = descriptor->instantiate (descriptor, SAMPLE_RATE);
  connect_ports (descriptor, instance, &buffers, CONTROL_NEGATIVE_INFINITY, NUM_SAMPLES, 0.0);
  if (descriptor->activate) descriptor->activate (instance);
  descriptor->run (instance, NUM_SAMPLES);
  if (descriptor->deactivate) descriptor->deactivate (instance);
  free_buffers (&buffers);
  descriptor->cleanup (instance);
}


static void
test_audio_inputs (LADSPA_Descriptor *descriptor)
{
  LADSPA_Handle instance;
  GList *buffers = NULL;

  g_printerr ("RUNNING TEST: Audio inputs set to -1\n");
  instance = descriptor->instantiate (descriptor, SAMPLE_RATE);
  connect_ports (descriptor, instance, &buffers, CONTROL_DEFAULT, NUM_SAMPLES, -1.0);
  if (descriptor->activate) descriptor->activate (instance);
  descriptor->run (instance, NUM_SAMPLES);
  if (descriptor->deactivate) descriptor->deactivate (instance);
  free_buffers (&buffers);
  descriptor->cleanup (instance);

  g_printerr ("RUNNING TEST: Audio inputs set to 2\n");
  instance = descriptor->instantiate (descriptor, SAMPLE_RATE);
  connect_ports (descriptor, instance, &buffers, CONTROL_DEFAULT, NUM_SAMPLES, 2);
  if (descriptor->activate) descriptor->activate (instance);
  descriptor->run (instance, NUM_SAMPLES);
  if (descriptor->deactivate) descriptor->deactivate (instance);
  free_buffers (&buffers);
  descriptor->cleanup (instance);

  g_printerr ("RUNNING TEST: Audio inputs set to INFINITY\n");
  instance = descriptor->instantiate (descriptor, SAMPLE_RATE);
  connect_ports (descriptor, instance, &buffers, CONTROL_DEFAULT, NUM_SAMPLES, INFINITY);
  if (descriptor->activate) descriptor->activate (instance);
  descriptor->run (instance, NUM_SAMPLES);
  if (descriptor->deactivate) descriptor->deactivate (instance);
  free_buffers (&buffers);
  descriptor->cleanup (instance);
}


/* Returns 0 for OK, 1 if the plugin has errors. */
static int
test_ladspa_plugin (const gchar       *filename,
		    LADSPA_Descriptor *descriptor)
{
  g_printerr ("###############################################################################\n");
  g_printerr ("TESTING: %s (%i:%s)\n",
	   descriptor->Name ? descriptor->Name : "Unnamed",
	   descriptor->UniqueID, filename);

  plugin_has_errors = FALSE;

  test_plugin_info (descriptor);
  if (test_lifecycle (descriptor))
    {
      test_run_sample_nums (descriptor);
      test_control_inputs_normal_range (descriptor);
      test_control_inputs_extreme_range (descriptor);
      test_audio_inputs (descriptor);
    }

  return plugin_has_errors ? 1 : 0;
}


static void
scan_ladspa_plugin (gchar             *pathname,
		    const gchar       *filename)
{
  GModule *module;
  LADSPA_Descriptor_Function ladspa_func;
  LADSPA_Descriptor *descriptor;
  gpointer symbol;
  gint i, status;
  pid_t pid;

  module = g_module_open (pathname, G_MODULE_BIND_LAZY);
  if (!module)
    return;

  /* Lookup the special "ladspa_descriptor" symbol. */
  if (g_module_symbol (module, "ladspa_descriptor", &symbol))
    {
      ladspa_func = (LADSPA_Descriptor_Function) symbol;

      /* Get the LADSPA descriptor for each module in the library. */
      for (i = 0; ; i++)
	{
	  descriptor = (LADSPA_Descriptor*) ladspa_func (i);
	  if (!descriptor)
	    break;

	  /* If a specific ID is specified skip any others. */
	  if (plugin_id != -1 && descriptor->UniqueID != plugin_id)
	    continue;

	  num_plugins_tested++;

	  if (fork_to_test)
	    {
	      pid = fork ();
	      if (pid)
		{
		  waitpid (pid, &status, 0);
		  if (WIFSIGNALED (status))
		    {
		      switch (WTERMSIG (status))
			{
			case SIGINT:
			  log_error ("Interrupted\n");
			  break;
			case SIGSEGV:
			  log_error ("SEGV\n");
			  break;
			case SIGFPE:
			  log_error ("Floating Point Exception\n");
			  break;
			case SIGALRM:
			  log_error ("Timeout (Possible Infinite Loop)\n");
			  break;
			default:
			  log_error ("Unexpected Signal: %u\n",
				     WTERMSIG (status));
			  break;
			}
		    }

		  /* If the child process didn't exit normally with an OK
		     status, assume an error occurred. */
		  if (!WIFEXITED (status) || WEXITSTATUS (status) != 0)
		    num_errors++;
		}
	      else
		{
		  alarm (PLUGIN_TEST_TIMEOUT);
		  status = test_ladspa_plugin (filename, descriptor);
		  exit (status);
		}
	    }
	  else
	    {
	      status = test_ladspa_plugin (filename, descriptor);
	      if (status != 0)
		num_errors++;
	    }
	}
    }

  g_module_close (module);
}


static void
scan_ladspa_directory (gchar   *directory)
{
  GDir *dir;
  const char *filename;
  char *pathname;

  dir = g_dir_open (directory, 0, NULL);
  if (!dir)
    return;

  /* Step through the files in the LADSPA plugin directory. */
  while ((filename = g_dir_read_name (dir)))
    {
      if (skip_file (filename))
	{
#if 0
	  g_printerr ("Skipping file: %s\n", filename);
#endif
	  continue;
	}

      pathname = g_strdup_printf ("%s%c%s", directory, G_DIR_SEPARATOR,
				  filename);
      scan_ladspa_plugin (pathname, filename);
      g_free (pathname);
    }

  g_dir_close (dir);
}


static void
test_ladspa_plugins ()
{
  const gchar *path;
  gchar **directories;
  gint directory_num;

  /* Use the $LADSPA_PATH environment variable, or the default path if that
     hasn't been set. */
  path = g_getenv ("LADSPA_PATH");
  if (!path)
    path = DEFAULT_LADSPA_PATH;

  /* Split the path into an array of directories. */
  directories = g_strsplit (path, ":", -1);

  /* Scan each directory in turn. */
  for (directory_num = 0; directories[directory_num]; directory_num++)
    scan_ladspa_directory (directories[directory_num]);

  /* Free the array. */
  g_strfreev (directories);
}


/* Command-line options. */
static GOptionEntry entries[] = 
{
  { "fork", 'f', 0, G_OPTION_ARG_NONE, &fork_to_test,
    "Fork and test each plugin in a separate child process", NULL },
  { "package", 'p', 0, G_OPTION_ARG_STRING, &package,
    "Package to test (acweight, amb, autotalent, blop, calf, caps, cmt, fil, invada, ladspa, mcp, njl, rev, swh, tap, vcf or vco)", "PACKAGE" },
  { "library", 'l', 0, G_OPTION_ARG_STRING, &library,
    "Library to test", "LIBRARY" },
  { "id", 'i', 0, G_OPTION_ARG_INT, &plugin_id,
    "Plugin ID to test", "ID" },
  { NULL }
};

int
main (int argc, char *argv[])
{
  GOptionContext *context;
  GError *error = NULL;

  context = g_option_context_new ("- test LADSPA plugins");
  g_option_context_add_main_entries (context, entries, NULL);
  if (!g_option_context_parse (context, &argc, &argv, &error))
    {
      g_printerr ("Command-Line Option Parsing Failed: %s\n", error->message);
      exit (1);
    }

  /* If a package is specified, set the appropriate file list to scan. */
  if (package)
    {
      if (!strcmp (package, "acweight"))
	file_list = acweight_file_list;
      else if (!strcmp (package, "amb"))
	file_list = amb_file_list;
      else if (!strcmp (package, "autotalent"))
	file_list = autotalent_file_list;
      else if (!strcmp (package, "blop"))
	file_list = blop_file_list;
      else if (!strcmp (package, "calf"))
	file_list = calf_file_list;
      else if (!strcmp (package, "caps"))
	file_list = caps_file_list;
      else if (!strcmp (package, "cmt"))
	file_list = cmt_file_list;
      else if (!strcmp (package, "fil"))
	file_list = fil_file_list;
      else if (!strcmp (package, "invada"))
	file_list = invada_file_list;
      else if (!strcmp (package, "ladspa"))
	file_list = ladspa_file_list;
      else if (!strcmp (package, "mcp"))
	file_list = mcp_file_list;
      else if (!strcmp (package, "njl"))
	file_list = njl_file_list;
      else if (!strcmp (package, "rev"))
	file_list = rev_file_list;
      else if (!strcmp (package, "swh"))
	file_list = swh_file_list;
      else if (!strcmp (package, "tap"))
	file_list = tap_file_list;
      else if (!strcmp (package, "vcf"))
	file_list = vcf_file_list;
      else if (!strcmp (package, "vco"))
	file_list = vco_file_list;
      else
	{
	  g_printerr ("Unknown Package: %s\n", package);
	  exit (1);
	}
    }

  test_ladspa_plugins ();

  g_printerr ("###############################################################################\n");

  /* Print summary of errors. */
  g_printerr ("\nPlugins Tested: %i\n", num_plugins_tested);
  g_printerr ("Plugins With Errors: %i\n", num_errors);

  return 0;
}
