Developer Guide - Writing a plugin

Writing a plugin

The following section dissects a particular plugin source file - plugins/xfade.c. When I finish writing up the developer documentation, there'll be more useful information here. Sorry it's not here yet.

Please note also that this source file is from the 0.2.0 release of gAlan, and so may be slightly less than current. Most of the differences will be small details however - the general idea is the same. (For instance, since 0.2.0 an extra parameter, gboolean prefer has been added to gen_new_generatorclass() and gencomp_register_generatorclass - this to deal with a problem that arose once there were two plugins, oss_output and esd_output, both fighting over who owned the class tag audio_out.)

The C-to-HTML translation was done by a nice little perl script, code2html.

Dissecting xfade.c

Standard headers - I include these in more-or-less every C program I write.
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <stddef.h>

GDK/GTK headers.
#include <gdk/gdk.h>
#include <gtk/gtk.h>

Local headers - global.h is mandatory (includes GLIB etc).
#include "global.h"
#include "generator.h"
#include "comp.h"
#include "control.h"
#include "gencomp.h"

Convenience macros - make it easy to see what this file is for, at a glance. Here we define the class name of the generator and the location in the menu tree it should be placed under.
#define GENERATOR_CLASS_NAME	"xfade"
#define GENERATOR_CLASS_PATH	"Levels/Cross-fader"

These macros are mnemonics for the various connectors attached to instances of the xfade class. The SIG_ macros name the audio-signal channels, and the EVT_ macros name the event connections. NUM_EVENT_INPUTS and NUM_EVENT_OUTPUTS are used when calling gen_new_generatorclass().
#define SIG_LEFT		0
#define SIG_RIGHT		1

#define SIG_OUTPUT		0

#define EVT_AMOUNT		0
#define NUM_EVENT_INPUTS	1

#define NUM_EVENT_OUTPUTS	0

This struct is used to hold per-instance class-specific data. For the xfade generator, we only need the balance level. Other plugins hold some quite involved information here.
typedef struct Data {
  gdouble amount;
} Data;

Instance initialiser. Note that it allocates a new struct Data and assigns it to g->data! The initialiser must return TRUE if it succeeded.
PRIVATE gboolean init_instance(Generator *g) {
  Data *data = malloc(sizeof(Data));

  g_return_val_if_fail(data != NULL, 0);
  g->data = data;

  data->amount = 0;

  return TRUE;
}

Instance destructor. Cleans up the per-instance data structure created by init_instance.
PRIVATE void destroy_instance(Generator *g) {
  free(g->data);
}

The "unpickler" - this routine resurrects an object that has been saved to disk. It's basically the "object load" routine for this class.
PRIVATE void unpickle_instance(Generator *g, ObjectStoreItem *item, ObjectStore *db) {
  Data *data = malloc(sizeof(Data));
  g_return_if_fail(data != NULL);
  g->data = data;

  data->amount = objectstore_item_get_double(item, "xfade_amount", 0);
}

The "pickler" - saves an object's persistent state out to a file. Both the pickler and the unpickler use the routines provided by objectstore.h.
PRIVATE void pickle_instance(Generator *g, ObjectStoreItem *item, ObjectStore *db) {
  Data *data = g->data;
  objectstore_item_set_double(item, "xfade_amount", data->amount);
}

The interesting part - the output generator. This routine fills the buffer it is passed with an audio signal, in this case calculated from the two inputs. For xfade.c, the calculation is straightforward.

Note that the routine should return TRUE if it generated any audio - an output-generating routine returning FALSE is assumed to be generating a buffer-full of silence (all zeroes). This is used to save a little processor time when a generator goes quiet.
PRIVATE gboolean output_generator(Generator *g, SAMPLE *buf, int buflen) {
  Data *data = g->data;
  SAMPLE bufl[MAXIMUM_REALTIME_STEP];
  SAMPLE bufr[MAXIMUM_REALTIME_STEP];
  gdouble lfact, rfact;
  int i;
  gboolean resl, resr;

  resl = gen_read_realtime_input(g, SIG_LEFT, -1, bufl, buflen);
  resr = gen_read_realtime_input(g, SIG_RIGHT, -1, bufr, buflen);

  if (!resl && !resr)
    return FALSE;

  if (!resl)
    memset(bufl, 0, sizeof(SAMPLE) * buflen);

  if (!resr)
    memset(bufr, 0, sizeof(SAMPLE) * buflen);

  if (data->amount > 0) {
    rfact = 1;
    lfact = 1 - data->amount;
  } else {
    lfact = 1;
    rfact = 1 + data->amount;
  }

  for (i = 0; i < buflen; i++)
    buf[i] = bufl[i] * lfact + bufr[i] * rfact;
  return TRUE;
}

This routine is the callback invoked when an AEvent is received on the EVT_AMOUNT channel. It changes the internal state data to match the information sent in the AEvent.
PRIVATE void evt_amount_handler(Generator *g, AEvent *event) {
  ((Data *) g->data)->amount = MAX(-1, MIN(1, event->d.number));
}

This array provides information to the core app about the input signals that instances of this class should have. The two fields in each record are the name of the signal-connector, and the flags - whether the signal is a realtime or randomaccess signal.
PRIVATE InputSignalDescriptor input_sigs[] = {
  { "Left", SIG_FLAG_REALTIME },
  { "Right", SIG_FLAG_REALTIME },
  { NULL, }
};

This array provides information about the outbound signals. Note the reference to output_generator - it is here that callbacks are bound to outbound signals.
PRIVATE OutputSignalDescriptor output_sigs[] = {
  { "Output", SIG_FLAG_REALTIME, { output_generator, } },
  { NULL, }
};

This array describes the controls that each instance makes available. See control.h for further information.
PRIVATE ControlDescriptor controls[] = {
  { CONTROL_KIND_KNOB, "fade", -1,1,0.01,0.01, 0,TRUE, 1,EVT_AMOUNT,
    NULL,NULL, control_double_updater, (gpointer) offsetof(Data, amount) },
  { CONTROL_KIND_NONE, }
};

This routine registers the plugin with the generator-registry by calling gen_new_generatorclass. It then configures the event inputs and outputs (the signal inputs and outputs are dealt with by the InputSignalDescriptor and OutputSignalDescriptor arrays). Finally, it registers itself with the GUI by calling gencomp_register_generatorclass.
PRIVATE void setup_class(void) {
  GeneratorClass *k = gen_new_generatorclass(GENERATOR_CLASS_NAME,
					     NUM_EVENT_INPUTS, NUM_EVENT_OUTPUTS,
					     input_sigs, output_sigs, controls,
					     init_instance, destroy_instance,
					     unpickle_instance, pickle_instance);

  gen_configure_event_input(k, EVT_AMOUNT, "Fade", evt_amount_handler);

  gencomp_register_generatorclass(k, GENERATOR_CLASS_PATH, NULL, NULL);
}

This is the main entry point for the plugin. It must be named after the pattern init_plugin_<pluginname>, where the plugin name is the name of the .so/.dll without the final extension.
PUBLIC void init_plugin_xfade(void) {
  setup_class();
}

Next Section