4 a complex external: counter

The simple counter of the previous chapter can easily be extended to more complexity. It might be quite useful to be able to reset the counter to an initial value, to set upper and lower boundaries and to control the step-width. Each overrun should send a “bang”-Message to a second outlet and reset the counter to the initial value.

4.1 extended data space

typedef struct _counter {  
  t_object  x_obj;  
  t_int i_count;  
  t_float step;  
  t_int i_down, i_up;  
  t_outlet *f_out, *b_out;  
} t_counter;

The data space has been extended to hold variables for step width and upper and lower boundaries. Furthermore pointers for two outlets have been added.

4.2 extension of the class

The new class objects should have methods for different messages, like “set” and “reset”. Therefore the method space has to be extended too.

  counter_class = class_new(gensym("counter"),  
        (t_newmethod)counter_new,  
        0, sizeof(t_counter),  
        CLASS_DEFAULT,  
        A_GIMME, 0);

The class generator class_new has been extended by the argument A_GIMME. This enables a dynamic number of arguments to be passed at the instantiation of the object.

  class_addmethod(counter_class,  
        (t_method)counter_reset,  
        gensym("reset"), 0);

class_addmethod adds a method for an arbitrary selector to an class.

The first argument is the class the method (second argument) will be added to.

The third argument is the symbolic selector that should be associated with the method.

The remaining “0”-terminated arguments describe the list of atoms that follows the selector.

  class_addmethod(counter_class,  
        (t_method)counter_set, gensym("set"),  
        A_DEFFLOAT, 0);  
  class_addmethod(counter_class,  
        (t_method)counter_bound, gensym("bound"),  
        A_DEFFLOAT, A_DEFFLOAT, 0);

A method for “set” followed by a numerical value is added, as well as a method for the selector “bound” followed by two numerical values.

  class_sethelpsymbol(counter_class, gensym("help-counter"));

If a Pd-object is right-clicked, a help-patch describing the object-class can be opened. By default, this patch is located in the directory “doc/5.reference/” and is named like the symbolic class name.

An alternative help-patch can be defined with the class_sethelpsymbol-command.

4.3 construction of in- and outlets

When creating the object, several arguments should be passed by the user.

void *counter_new(t_symbol *s, int argc, t_atom *argv)

Because of the declaration of arguments in the class_new-function with A_GIMME, the constructor has following arguments:

t_symbol *sthe symbolic name,
that was used for object creation
int argc the number of arguments passed to the object
t_atom *argva pointer to a list of argc atoms
  t_float f1=0, f2=0;  
 
  x->step=1;  
  switch(argc){  
  default:  
  case 3:  
    x->step=atom_getfloat(argv+2);  
  case 2:  
    f2=atom_getfloat(argv+1);  
  case 1:  
    f1=atom_getfloat(argv);  
    break;  
  case 0:  
    break;  
  }  
  if (argc<2)f2=f1;  
  x->i_down = (f1<f2)?f1:f2;  
  x->i_up   = (f1>f2)?f1:f2;  
 
  x->i_count=x->i_down;

If three arguments are passed, these should be the lower boundary, the upper boundary and the step width.

If only two arguments are passed, the step-width defaults to “1”. If only one argument is passed, this should be the initial value of the counter with step-width of “1”.

  inlet_new(&x->x_obj, &x->x_obj.ob_pd,  
        gensym("list"), gensym("bound"));

The function inlet_new creates a new “active” inlet. “Active” means, that a class-method is called each time a message is sent to an “active” inlet.

Due to the software-architecture, the first inlet is always “active”.

The first two arguments of the inlet_new-function are pointers to the interna of the object and to the graphical presentation of the object.

The symbolic selector that is specified by the third argument is to be substituted by another symbolic selector (fourth argument) for this inlet.

Because of this substitution of selectors, a message on a certain right inlet can be treated as a message with a certain selector on the leftmost inlet.

This means:

  floatinlet_new(&x->x_obj, &x->step);

floatinlet_new generates a new “passive” inlet for numerical values. “Passive” inlets allow parts of the data space-memory to be written directly from outside. Therefore it is not possible to check for illegal inputs.

The first argument is a pointer to the internal infrastructure of the object. The second argument is the address in the data space-memory, where other objects can write too.

“Passive” inlets can be created for pointers, symbolic or numerical (floating point3) values.

  x->f_out = outlet_new(&x->x_obj, &s_float);  
  x->b_out = outlet_new(&x->x_obj, &s_bang);

The pointers returned by outlet_new have to be saved in the classdata space to be used later by the outlet-routines.

The order of the generation of inlets and outlets is important, since it corresponds to the order of inlets and outlets in the graphical representation of the object.

4.4 extended method space

The method for the “bang”-message has to full fill the more complex tasks.

void counter_bang(t_counter *x)  
{  
  t_float f=x->i_count;  
  t_int step = x->step;  
  x->i_count+=step;  
  if (x->i_down-x->i_up) {  
    if ((step>0) && (x->i_count > x->i_up)) {  
      x->i_count = x->i_down;  
      outlet_bang(x->b_out);  
    } else if (x->i_count < x->i_down) {  
      x->i_count = x->i_up;  
      outlet_bang(x->b_out);  
    }  
  }  
  outlet_float(x->f_out, f);  
}

Each outlet is identified by the outlet_...-functions via the pointer to this outlets.

The remaining methods still have to be implemented:

void counter_reset(t_counter *x)  
{  
  x->i_count = x->i_down;  
}  
 
void counter_set(t_counter *x, t_floatarg f)  
{  
  x->i_count = f;  
}  
 
void counter_bound(t_counter *x, t_floatarg f1, t_floatarg f2)  
{  
  x->i_down = (f1<f2)?f1:f2;  
  x->i_up   = (f1>f2)?f1:f2;  
}

4.5 the code: counter

#include "m_pd.h"  
 
static t_class *counter_class;  
 
typedef struct _counter {  
  t_object  x_obj;  
  t_int i_count;  
  t_float step;  
  t_int i_down, i_up;  
  t_outlet *f_out, *b_out;  
} t_counter;  
 
void counter_bang(t_counter *x)  
{  
  t_float f=x->i_count;  
  t_int step = x->step;  
  x->i_count+=step;  
 
  if (x->i_down-x->i_up) {  
    if ((step>0) && (x->i_count > x->i_up)) {  
      x->i_count = x->i_down;  
      outlet_bang(x->b_out);  
    } else if (x->i_count < x->i_down) {  
      x->i_count = x->i_up;  
      outlet_bang(x->b_out);  
    }  
  }  
 
  outlet_float(x->f_out, f);  
}  
 
void counter_reset(t_counter *x)  
{  
  x->i_count = x->i_down;  
}  
 
void counter_set(t_counter *x, t_floatarg f)  
{  
  x->i_count = f;  
}  
 
void counter_bound(t_counter *x, t_floatarg f1, t_floatarg f2)  
{  
  x->i_down = (f1<f2)?f1:f2;  
  x->i_up   = (f1>f2)?f1:f2;  
}  
 
void *counter_new(t_symbol *s, int argc, t_atom *argv)  
{  
  t_counter *x = (t_counter *)pd_new(counter_class);  
  t_float f1=0, f2=0;  
 
  x->step=1;  
  switch(argc){  
  default:  
  case 3:  
    x->step=atom_getfloat(argv+2);  
  case 2:  
    f2=atom_getfloat(argv+1);  
  case 1:  
    f1=atom_getfloat(argv);  
    break;  
  case 0:  
    break;  
  }  
  if (argc<2)f2=f1;  
 
  x->i_down = (f1<f2)?f1:f2;  
  x->i_up   = (f1>f2)?f1:f2;  
 
  x->i_count=x->i_down;  
 
  inlet_new(&x->x_obj, &x->x_obj.ob_pd,  
        gensym("list"), gensym("bound"));  
  floatinlet_new(&x->x_obj, &x->step);  
 
  x->f_out = outlet_new(&x->x_obj, &s_float);  
  x->b_out = outlet_new(&x->x_obj, &s_bang);  
 
  return (void *)x;  
}  
 
void counter_setup(void) {  
  counter_class = class_new(gensym("counter"),  
        (t_newmethod)counter_new,  
        0, sizeof(t_counter),  
        CLASS_DEFAULT,  
        A_GIMME, 0);  
 
  class_addbang  (counter_class, counter_bang);  
  class_addmethod(counter_class,  
        (t_method)counter_reset, gensym("reset"), 0);  
  class_addmethod(counter_class,  
        (t_method)counter_set, gensym("set"),  
        A_DEFFLOAT, 0);  
  class_addmethod(counter_class,  
        (t_method)counter_bound, gensym("bound"),  
        A_DEFFLOAT, A_DEFFLOAT, 0);  
 
  class_sethelpsymbol(counter_class, gensym("help-counter"));  
}

3That’s why the step-width of the classdata space is realized as t_float.