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:
- receiving a message directly sent to the object
- receiving a message sent to the first inlet of the object from
the outlet of another object
- receiving values sent to an inlet of the object from the outlet
of another object
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:
- a directory papou with
an optional package description file papou.jpkg
- a shared library named after a platform dependent convention
(such as libpapou.so for UNIX
platforms) containing the class definitions and whatsoever other modules
you want to bind to the system
- an initialization function named papou_config implemented by the
library and called when it is linked
- a directory help
containing a help summary patch and help patches for the classes
implemented
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.