next up previous contents
Next: pd's message-system Up: HOWTO write an External Previous: a complex external: counter   Contents

Subsections

a signal-external: pan~

Signalclasses are normal pd-classes, that offer additional methods for signals.

All methods and concepts that can be realized with normal objectclasses can therefore be realized with signalclasses too.

Per agreement, the symbolic names of signalclasses end with a tilde ~.

The class ``pan~'' shall demonstrate, how signalclasses are written.

A signal on the left inlet is mixed with a signal on the second inlet. Der mixing-factor between 0 and 1 is defined via a t_float-message on a third inlet.

variables of a signalclass

Since a signal-class is only an extended normal class, there are no principal differences between the dataspaces.

typedef struct _pan_tilde {
  t_object x_obj;

  t_sample f_pan;
  t_float  f;
} t_pan_tilde;

Only one variable f_pan for the mixing-factor of the panning-function is needed.

The other variable f is needed whenever a signal-inlet is needed too. If no signal but only a float-message is present at a signal-inlet, this variable is used to automatically convert the float to signal.

signal-classes

void pan_tilde_setup(void) {
  pan_tilde_class = class_new(gensym("pan~"),
        (t_newmethod)pan_tilde_new,
        0, sizeof(t_pan_tilde),
        CLASS_DEFAULT, 
        A_DEFFLOAT, 0);

  class_addmethod(pan_tilde_class,
        (t_method)pan_tilde_dsp, gensym("dsp"), 0);
  CLASS_MAINSIGNALIN(pan_tilde_class, t_pan_tilde, f);
}

A method for signal-processing has to be provided by each signalclass.

Whenever pd's audioengine is started, a message with the selector ``dsp'' is sent to each object. Each class that has a method for the ``dsp''-message is recognized as signalclass.

Signalclasses that want to provide signal-inlets have to declare this via the CLASS_MAINSIGNALIN-macro. This enables signals at the first (default) inlet. If more than one signal-inlet is needed, they have to be created explicitly in the constructor-method.

Inlets that are declared as signal-inlets cannot provide methods for t_float-messages any longer.

The first argument of the macro is a pointer to the signalclass. The second argument is the type of the class's dataspace.

The last argument is a dummy-variable out of the dataspace that is needed to replace non-existing signal at the signal-inlet(s) with t_float-messages.

construction of signal-inlets and -outlets

void *pan_tilde_new(t_floatarg f)
{
  t_pan_tilde *x = (t_pan_tilde *)pd_new(pan_tilde_class);

  x->f_pan = f;
  
  inlet_new(&x->x_obj, &x->x_obj.ob_pd, &s_signal, &s_signal);
  floatinlet_new (&x->x_obj, &x->f_pan);

  outlet_new(&x->x_obj, &s_signal);

  return (void *)x;
}

Additional signal-inlets are added like other inlets with the routine inlet_new. The last two arguments are references to the symbolic selector ``signal'' in the lookup-table.

Signal-outlets are also created like normal (message-)outlets, by setting the outlet-selector to ``signal''.

DSP-methods

Whenever pd's audioengine is turned on, all signal-objects declare their perform-routines that are to be added to the DSP-tree.

The ``dsp''-method has two arguments, the pointer to the class-dataspace, and a pointer to an array of signals.

The signals are arranged in the array in such way, that they are ordered in a clockwise way in the graphical representation of the object.4

void pan_tilde_dsp(t_pan_tilde *x, t_signal **sp)
{
  dsp_add(pan_tilde_perform, 5, x,
          sp[0]->s_vec, sp[1]->s_vec, sp[2]->s_vec, sp[0]->s_n);
}

dsp_add adds a perform-routine (as declared in the first argument) to the DSP-tree.

The second argument is the number of the following pointers to diverse variables. Which pointers to which variables are passed is not limited.

Here, sp[0] is the first in-signal, sp[1] represents the second in-signal and sp[3] points to the out-signal.

The structure t_signal contains a pointer to the its signal-vector ().s_vec (an array of samples of type t_sample), and the length of this signal-vector ().s_n.

Since all signalvectors of a patch (not including it's sub-patches) are of the same length, it is sufficient to get the length of one of these vectors.

perform-routine

The perform-routine is the DSP-heart of each signalclass.

A pointer to an integer-array is passed to it. This array contains the pointers, that were passed via dsp_add, which must be casted back to their real type.

The perform-routine has to return a pointer to integer, that points to the address behind the stored pointers of the routine. This means, that the return argument equals the argument of the perform-routine plus the number of pointervariables (as declared as the second argument of dsp_add) plus one.

t_int *pan_tilde_perform(t_int *w)
{
  t_pan_tilde *x = (t_pan_tilde *)(w[1]);
  t_sample  *in1 =    (t_sample *)(w[2]);
  t_sample  *in2 =    (t_sample *)(w[3]);
  t_sample  *out =    (t_sample *)(w[4]);
  int          n =           (int)(w[5]);

  t_sample f_pan = (x->f_pan<0)?0.0:(x->f_pan>1)?1.0:x->f_pan;

  while (n--) *out++ = (*in1++)*(1-f_pan)+(*in2++)*f_pan;

  return (w+6);
}

Each sample of the signalvectors is read and manipulated in the while-loop.

Optimization of the DSP-tree tries to avoid unnecessary copy-operations. Therefore it is possible, that in- and out-signal are located at the same address in the memory. In this case, the programmer has to be careful not to write into the out-signal before having read the in-signal to avoid overwriting data that is not yet saved.

the code: pan~

#include "m_pd.h"

static t_class *pan_tilde_class;

typedef struct _pan_tilde {
  t_object  x_obj;
  t_sample f_pan;
  t_sample f;
} t_pan_tilde;

t_int *pan_tilde_perform(t_int *w)
{
  t_pan_tilde *x = (t_pan_tilde *)(w[1]);
  t_sample  *in1 =    (t_sample *)(w[2]);
  t_sample  *in2 =    (t_sample *)(w[3]);
  t_sample  *out =    (t_sample *)(w[4]);
  int          n =           (int)(w[5]);
  t_sample f_pan = (x->f_pan<0)?0.0:(x->f_pan>1)?1.0:x->f_pan;

  while (n--) *out++ = (*in1++)*(1-f_pan)+(*in2++)*f_pan;

  return (w+6);
}

void pan_tilde_dsp(t_pan_tilde *x, t_signal **sp)
{
  dsp_add(pan_tilde_perform, 5, x,
          sp[0]->s_vec, sp[1]->s_vec, sp[2]->s_vec, sp[0]->s_n);
}

void *pan_tilde_new(t_floatarg f)
{
  t_pan_tilde *x = (t_pan_tilde *)pd_new(pan_tilde_class);

  x->f_pan = f;
  
  inlet_new(&x->x_obj, &x->x_obj.ob_pd, &s_signal, &s_signal);
  floatinlet_new (&x->x_obj, &x->f_pan);
  outlet_new(&x->x_obj, &s_signal);

  return (void *)x;
}

void pan_tilde_setup(void) {
  pan_tilde_class = class_new(gensym("pan~"),
        (t_newmethod)pan_tilde_new,
        0, sizeof(t_pan_tilde),
        CLASS_DEFAULT, 
        A_DEFFLOAT, 0);

  class_addmethod(pan_tilde_class,
        (t_method)pan_tilde_dsp, gensym("dsp"), 0);
  CLASS_MAINSIGNALIN(pan_tilde_class, t_pan_tilde, f);
}


next up previous contents
Next: pd's message-system Up: HOWTO write an External Previous: a complex external: counter   Contents
IOhannes m zmoelnig 2001-09-13