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.
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.
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.
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''.
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.
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.
#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); }