FTS classes

FTS, the server engine of jMax, has a small C written object system including a simple garbage collector (by reference count). Basically any typed values apart from integers, floats and symbols are objects. The most known examples of FTS classes might still be those of the objects inside a patcher such as sliders, buttons or oscillators. But as well vectors or MIDI events, rather sent between the objects in a patcher than themselves instantiated in a patcher window, are typical examples of FTS classes.

Classes can implement methods for messages and inlets of an object and declare outlets. Methods of an object can be called by:
Methods can output values from an outlet of the object or return values to the FTS object system.



Packages

An FTS class always lives in package. Packages bundle components dedicated to the use with FTS to be distributed and dynamically linked to the running system when required. A package named papou basically consists of the following:

Class structure

Since we are in C your FTS class will be represented by a C structure. In order to be an FTS class this structure must have the structure fts_object_t as first field and is typically defined as a type named after the class:
  typedef struct
{
  fts_object_t o;

  /* here follows the fields of your class */

} coucou_t;
In order make your class useable you must declare it to FTS by a call like this:
  fts_class_install(fts_new_symbol("coucou"), coucou_instantiate);

Make sure that this call is executed by the initialization function of the package it belongs to.

The class instantiation function (here coucou_instantiate) contains the initialization and definition of the class. The most essential call of this function is fts_class_init, which is usually followed by message, inlet and outlet declarations for the class:
  static void
coucou_instantiate(fts_class_t *cl)
{
fts_class_init(cl, sizeof(coucou_t), coucou_init, coucou_delete);

/* ... message, inlet and outlet declarations */
}

The fts_class_init declares the size of an object of the given class (is the size of the C structure from above) as well as an optional constructor and de-constructor method (both can be set to NULL).


Constructor and deconstructor

A class of objects which have to be initialized or allocate additional memory at creation (and deallocate when deleted) has to implement a constructor and deconstructor. Both are implemented in the same way as methods (see down). The constructor is called with the instantiation arguments of the object.


Method declaration

Methods in FTS can be invoked by messages or via inlets. Even if messages can be send to the first inlet of an object they are handled as if they were directly sent to the object. The declaration (and dispatch) of a method invoked by a message with argument values is almost identical to that of a method invoked by values sent to an inlet. The former is declared for a given message symbol and argument type, the latter for an inlet and value type. The following functions are used in the body of the class instantiation function:
  fts_class_message(cl, fts_new_symbol("set"), NULL, coucou_set)

 fts_class_inlet(cl, 0, NULL, coucou_set)
Regarding these two example declarations a message set to an object of the given class cl here is equivalent to sending only its argument value into the first inlet (index 0). Both invoke the method coucou_set.

If NULL is given as argument type, as in the example, the method will be invoked for a single argument of any type.

A method with multiple argument values can be declared in two ways: either as method for a single value of the type fts_tuple_class receiving the values as a single tuple atom or as a varargs method receiving the values in an array (ac, at). If necessary the creation of a tuple object or the unpacking of a tuple object to an array of arguments is implicitly handled by the FTS method dispatcher.

For the declaration of varargs methods the following function calls without argument type are used:
  fts_class_message_varargs(cl, fts_new_symbol("set"), coucou_set_varargs)

 fts_class_inlet_varargs(cl, 0, coucou_set_varargs)
A set of macros is provided for the declaration of methods for the most often used data types:
  fts_class_message_int(cl, fts_new_symbol("set"), coucou_set_number)
fts_class_message_float(cl, fts_new_symbol("set"), coucou_set_number)
fts_class_message_number(cl, fts_new_symbol("set"), coucou_set_number)
fts_class_message_symbol(cl, fts_new_symbol("set"), coucou_set_symbol)
fts_class_message_tuple(cl, fts_new_symbol("set"), coucou_set_tuple)
fts_class_message_atom(cl, fts_new_symbol("set"), coucou_set_any_atom)

 fts_class_inlet_int(cl, 0, coucou_set_number)
fts_class_inlet_float(cl, 0, coucou_set_number)
fts_class_inlet_number(cl, 0, coucou_set_number)
fts_class_inlet_symbol(cl, 0, coucou_set_symbol)
fts_class_inlet_tuple(cl, 0, coucou_set_tuple)
fts_class_inlet_atom(cl, 0, coucou_set)
The ..._atom functions are equivalent to the calls with NULL as argument type. The functions ..._number are strictly equivalent with an int declaration followed by a float declaration.

For methods called without argument values the following decalarations are used:
  fts_class_message_void(cl, fts_new_symbol("doit"), coucou_doit)

 fts_class_inlet_void(cl, 0, coucou_doit) /* same as fts_class_inlet_bang */

Note that the former MAX bang message is strictly equivalent to sending a single void value or no values at all (but do sending anyway) to an inlet.

A method declared for a varargs is called for any number and type of arguments as well as without arguments (or bang) if no other method is declared for the same message or inlet.

One can not declare for the same message or inlet a varargs method and a method for an atom of an arbitrary type (giving NULL as argument type or using the ..._atom call for the declaration). In the same way the declaration of a varargs mathod is mutual exclusive with the declaration of a tuple method for the same message or inlet.


Inlets and outlets

The declaration of a method for an inlet automatically adds an inlet to the class if necassary. The function fts_class_inlet_thru adds a passive inlet to the class, which doesn't correspond to a method call:
  fts_class_inlet_thru(cl, 0)
Outlets have to be declared using the function fts_class_outlet with the class, the index of the outlet and a type class:
  fts_class_outlet(cl, 0, fts_void_class)
fts_class_outlet_varargs(cl, 0)
fts_class_outlet_message(cl, 0)
fts_class_outlet_thru(cl, 0)
Here the NULL value indicates an untyped outlet. The function fts_class_outlet_thru adds an untyped outlet to the class.

For the most used outlet types the following short cuts are provided:
  fts_class_outlet_void(cl, 0) /* same as fts_class_outlet_bang */
fts_class_outlet_int(cl, 0)
fts_class_outlet_float(cl, 0)
fts_class_outlet_number(cl, 0)
fts_class_outlet_symbol(cl, 0)
fts_class_outlet_atom(cl, 0)
Even if the number of inlets and outlets of a class is determined by the declarations from above, an object of a given class can have any number of inlets and outlets. By default an object has the number of inlets and outlets given in the definition of the class. Two functions are provided in order to change the number of inlets and outlet of an object, which are called with the object and the number of inlets or outlets desired:
  fts_object_set_inlets_number(o, n)
fts_object_set_outlets_number(o, n)
Two corresponding functions return the current number of in lets or outlets as an integer:
  fts_object_get_inlets_number(o)
fts_object_get_outlets_number(o)


Method implementation

In general methods are statically declared functions of the following shape:
  static void
coucou_set_number(fts_object_t *o, int winlet, fts_symbol_t s, int ac, const fts_atom_t *at)
{
coucou_t *self = (coucou_t *)o;
int n = fts_get_number_int(at);

if(n > 0)
self->count = n;
else
self->count = 0;
}
The first argument (o) of a method is the object itself, which is typically casted in the first line of the function to the class' data type in order to access the fields of the C structure. The second argument (winlet) is the index of the inlet if the method was invoked via an inlet or the negative value fts_message_inlet if the method is invoked via an inlet. The third argument (s) is the message symbol for a method invoked by a message or NULL otherwise. The fourth and fifth arguments (ac and at) are size and values of the array of arguments passed to the method as atoms.

Note that the arguments are passed by the message system as an array of constant atoms. This means that a method cannot modify its arguments. Modifying the arguments of a method in the body of the method can lead to unpredictable side effects. If this is needed for convenience, the arguments must first be copied.

Atoms are generic FTS values which can have any of the data types: int, float (in C 64-bit double format), symbol, object (pointer to fts_object_t). The following functions are provided to access the atom values:
  fts_get_int(at) /* get int value from int atom */
fts_get_float(at) /* get double value from float atom */
fts_get_number_int(at) /* get int value from int or float atom */
fts_get_number_float(at) /* get double value from int or float atom */
fts_get_symbol(at) /* get symbol from symbol atom */
fts_get_object(at) /* get object from object atom */
For testing the type of an arbitrary atom the following boolean functions are provided:
  fts_is_int(at)
fts_is_float(at)
fts_is_symbol(at)
fts_is_object(at)
The type of an atom is an FTS class (fts_class_t *). Pseudo classes are provided for the primitive types int, float and symbol. For atoms with an object (actually a pointer to an object) as value the atom type is the class of the object.

If the method coucou_set_number from above is declared for number arguments only, the method will only be called with a single float or int atom as argument. The function fts_get_number_int returns an int value of the argument value at[0].

The arguments of varargs methods are not type checked by the FTS method patcher when sending messages or values between objects. A method declared for varargs must check the number and type of the arguments in the message body:
  static void
coucou_set_varargs(fts_object_t *o, int winlet, fts_symbol_t s, int ac, const fts_atom_t *at)
{
coucou_t *self = (coucou_t *)o;

if(ac > 0 && fts_is_number(at))
n = fts_get_number_int(at);
else
n = 0;

if(n > 0)
self->count = n;
else
self->count = 0;
}

Method invocation

Methods usually change the state of the object and/or send output from an object or return a value. Basically there are two contexts for the invokation of methods: the values and messages passed between connected objects and the direct sending of a message to an object using the function fts_send_message.

Methods which are sending output via outlets use the following functions called with the object is self and the index of outlet:
  fts_outlet_void(o, 0, n) /* same as fts_outlet_bang */
fts_outlet_int(o, 0, n)
fts_outlet_float(o, 0, f)
fts_outlet_symbol(o, 0, s)
fts_outlet_object(o, 0, obj)
fts_outlet_atom(o, 0, a)
fts_outlet_varargs(o, 0, out_ac, out_at)
fts_outlet_message(o, 0, out_s, out_ac, out_at);
If an object outputs from multiple outlets at the same time, by convention this is done in right to left order (highest to lowest index).
  {
fts_atom_t *store;
int i, n;

/* ... */

for(i=n-1; i>=0; i--)
fts_outlet_atom(o, i, store + i);
}
Alternatively a method of a given object can be invoked by sending a message using the following functions:
  fts_send_message(target, mess_s, mess_ac, mess_at)

 fts_send_message_varargs(cl, mess_s, mess_ac, mess_at)
The second function only invokes methods declared as varargs and ignores any further method declaration for the same message.

Methods invoked in this way can return values using the following functions with a pointer to an atom or a value:
  fts_return(ap)
fts_return_int(n)
fts_return_float(f)
fts_return_symbol(s)
fts_return_object(obj)
It is the reponsibitity of the calling routine to clear the global return value of the FTS object system and to interpret the return value after having sent the message.
  {
fts_object_t *target;
int size;

/* ... */

/* first void return value */
fts_set_void(fts_get_return_value());

/* send the message */
fts_send_message(target, fts_new_symbol("size"), 0, NULL):

/* get return value */
if(!fts_is_void(fts_get_return_value()))
size = fts_get_int(fts_get_return_value());
else
size = -1;

/* ... */

}

Error handling

In order to signal an error a method (as well as a constructor) can use the function fts_object_error. The function is called with the object and a format string such as in post or fprintf.

  static void
coucou_init(fts_object_t *o, int winlet, fts_symbol_t s, int ac, const fts_atom_t *at)
{
coucou_t *self = (coucou_t *)o;

self->count = 0;

if(ac > 0)
{
if(fts_is_number(at))
self->count = fts_get_number_int(at);
else
fts_object_error(o, "bad argument %s for object coucou", fts_class_get_name(fts_get_class(at)));
}
}
Any call of this function during the creation of the object (in the creator or a method called by the creator) will result in an instantiation error. All other calls are handled as runtime errors.