The API presented here may be subject to small changes.
This first version of the documentation is adapted from the Ircam jMax documentation and some inconsistencies may subsist.
This documentation introduces the jMax objects API, known in Max-Opcode as the “external” objects API. As a consequence of the client/server architecture of jMax, these objects are loaded and executes in the jMax server, called FTS. The documentation will then use the term FTS message system to denote the server objects API.
The FTS message system combines an object system and a message passing system. It is a new definition of Max/FTS message system already implemented in Max/Macintosh and in previous versions of Max/FTS on the ISPW.
The main concepts of FTS message system are:
and a set of connections used for message sending between objects
of arguments which are atoms
inlets and a set of emitters, the outlets. An inlet receives messages and messages can be emitted on outlets
method is a C function with a particular signature
regarding the object model
The main point to get is that in FTS, each object in a class have the same behaviour of the other objects in the class, in terms of number of inlets and outlets and in terms of messages the object can respond to.
This is in apparent contradiction with the fact that objects with the same name (like trigger, for example) can have a different number of inlets and outlets; this is actually handled by the metaclass/class hierarchy. An object name correspond to a metaclass; instances of the same name with different behaviour actually have different classes, but the same metaclass.
[Apply to jMax 0.7 and above]. In jMax Phoenix, object developers have some more freedom on how to write objects: the API allows a style where the metaclass/class relationship is explicitly handled (the traditional jMax API), and an API (or more precisely an API extension) that allows developers to ignore the metaclass/class hierarchy and to program in a way that is very close to the old Max/FTS or to the Puredata API. The result is the same, in the second case the object system take in care the housekeeping of the metaclass/class hierarchy. This page is being updated with all the documentation about the API extension.
A FTS object is an abstraction that store information and can receive and emit messages. To an object is allocated some memory storing its state as a C structure.
typedef struct
{
fts_object_t o; /* MUST BE FIRST STRUCTURE MEMBER */
long n; /* the state of a integer object */
} integer_t;
An FTS object must always have a first structure member of type fts_object_t. This implements in C the fact that all FTS objects inherits from the fts_object_t type.
The only “system” member of the structure which is made mandatory by the FTS object system is the first member of type fts_object_t. The other members of the structure are “user” members and are not handled by the system.
An number of functions or macros are provided to get some of the object characteristic:
int fts_object_get_outlets_number(fts_object_t *obj);
Return the number of outlets the object obj have.
int fts_object_get_inlets_number(fts_object_t *obj);
Return the number of inlets the object obj have.
const char *fts_object_get_class_name(fts_object_t *obj);
Return as a C string the name of the object class; to be used only for user messages.
A method is a C-function that will be called when an object receives a message on one of its inlets. It has a standard signature, and all the methods access their arguments the same way.
All FTS methods have the same signature, i.e. the same number of arguments of the same types. This signature is the following :
void method(fts_object_t *object, int winlet, fts_symbol_t selector, int ac, const fts_atom_t *at)
The arguments of the method have the following meaning :
* object: a pointer to the object receiving the message * winlet: the number of the inlet on which message was received * selector: the message selector, a symbol. The fts_symbol_t
data structure is discussed in details in the FTS Kernel Reference Manual.
* ac: the number of arguments * at: the arguments array
The arguments of a method are of type fts_atom_t. The fts_atom_t data structure is discussed in details in the FTS Kernel Reference Manual.
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.
A method access its arguments via access macros, which handle both accessing the atoms and giving a default value if corresponding argument is optional.
It is strongly recommended to use the arguments access
macros. Accessing directly the members of the fts_atom_t structure
is guaranteed not to work across different releases of FTS, because fts_atom_t
implementation may change.
This is an example of a simple method, taking one argument of type int,
with a default value of 0, which stores the value of this argument in the state
of the object :
void integer_in1(fts_object_t *object, int winlet, fts_symbol_t selector,
int ac, const fts_atom_t *at)
{
integer_t *this = (integer_t *)object;
this->n = fts_get_int_arg(ac, at, 0, 0);
}
In this method, the object argument, typed as an fts_object_t * is in fact a pointer to an object of type integer_t (defined earlier), which first structure member is of type fts_object_t. This explains the cast made at beginning of method.
Below is the list of available access macros :
The macro arguments have the following meaning :
All these macros have the same semantic : if N is less than AC, return the value of atom N in array AT, else return value DEFAULT.
Messages can be send on the outlets of an object, using the fts_outlet_send function.
fts_status_t fts_outlet_send(fts_object_t *object, int woutlet, fts_symbol_t selector, int argc, const fts_atom_t *args)
The arguments of fts_outlet_send have the following meaning :
Message sending is most of the time done in the body of a method, as in the following example, a method sending on the outlet the content of the state of the object :
void integer_bang(fts_object_t *object, int winlet, fts_symbol_t selector,
int ac, const fts_atom_t *at)
{
integer_t *this = (integer_t *)object;
fts_atom_t a;
fts_set_int(&a, this->n);
fts_outlet_send(object, 0, fts_s_int, 1, &a);
}
Typically, sending a message is done in 2 phases :
the fts_atom_t data structure access macros
array
It is strongly recommended to use the atoms setting macros to format the arguments. Accessing directly the members of the fts_atom_t structure is guaranteed not to work across different releases of FTS, because fts_atom_t implementation may change.
A message selector is a fts_symbol_t. The fts_symbol_t data structure is not documented yet.
There is a number of predefined selectors which are defined in the header files for the message system. Below is the list of already defined selectors:
If a selector which is not already defined is needed, the fts_new_symbol or fts_new_symbol_copy functions generates a new symbol:
fts_symbol_t fts_new_symbol(const char *s)
returns a new symbol containing string s; the passed string is not copied, and should be a string literal.
fts_symbol_t fts_new_symbol_copy(const char *s)
returns a new symbol containing string s. This function is used when the passed string is not a constant.
[Apply to jMax 0.7 and above] There are two ways to define a method in jMax Phoenix.
Methods can be define at the class level, by using the fts_method_define_* family of functions, which are called when initializing the class, or they can be define in the object initialisation method, by using the fts_object_method_define_* family of functions. Typically, you use class level method definition in classes written using the metaclass/class oriented API, and the object level method definition when you use the fts_class_new function, but nothing prevent using the two declaration style in the same object, if this make sense to you.
A method definition function to which message the method will be associated, and which kind of arguments it expects.
Note that with respect to puredata and the other Max like applications, all the methods have
exactly the same C signature, and there are no restrictions on which and how many messages/methods
you can install on which inlet.
Actually, the object system provide an undocumented set of functions to install methods
with arbitrary C signature on inlets, for special purpose applications.
This API can be documented if there is interest.
A number of convenience functions and macros allows the definition of a method for a class. They share the meaning of most of the arguments, and provide slightly different behaviour.
fts_status_t fts_method_define(fts_class_t *class, int winlet, fts_symbol_t selector, fts_method_t method, int ac, fts_symbol_t *at) fts_status_t fts_method_define_varargs(fts_class_t *class, int winlet, fts_symbol_t selector, fts_method_t method) fts_status_t fts_method_define_optargs(fts_class_t *class, int winlet, fts_symbol_t selector, fts_method_t method, int ac, fts_symbol_t *at, int margs)
The fts_method_define function define a method that accept a message composed of one selector and a fixed number of arguments of a fixed type (for messages like int, float, complex, or n-uples of a given structure).
The fts_method_define_varargs function define a method that accept a message composed of one selector and a variable number of arguments (for messages like list).
The fts_method_define_optargs function define a method that accept a message composed of one selector a fixed number of arguments of a fixed type followed by a variable number of arguments (the more generic case).
The arguments have the following meaning :
selector, the method will be called with the message arguments, after arguments type checking.
A number of message specific functions (actually, macros) simplify life for standard types.
fts_status_t fts_method_define_int(fts_class_t *class, int winlet, fts_method_t method); fts_status_t fts_method_define_float(fts_class_t *class, int winlet, fts_method_t method); fts_status_t fts_method_define_number(fts_class_t *class, int winlet, fts_method_t method); fts_status_t fts_method_define_symbol(fts_class_t *class, int winlet, fts_method_t method); fts_status_t fts_method_define_bang(fts_class_t *class, int winlet, fts_method_t method); fts_status_t fts_method_define_list(fts_class_t *class, int winlet, fts_method_t method); fts_status_t fts_method_define_anything(fts_class_t *class, int winlet, fts_method_t method);
These macros are simply shortcuts to call the above functions, their use is not mandatory, but
may make the code easier to read.
The number version install the same method for ints and floats.
The anything version, and in general installing a method for the fts_s_anything selector
will install a catch all method on the inlet that will be called for any message received.
[Applicable starting from jMax Phoenix 0.7] A number of convenience functions and macros allows the definition of a method for an object. Methods will actually be installed in the object class, and the object system will take care of keeping a globally consistent class structure. They share the meaning of most of the arguments, and provide slightly different behaviour.
fts_status_t fts_object_method_define(fts_object_t *object, int winlet, fts_symbol_t selector, fts_method_t method, int ac, fts_symbol_t *at) fts_status_t fts_object_method_define_varargs(fts_object_t *object, int winlet, fts_symbol_t selector, fts_method_t method) fts_status_t fts_object_method_define_optargs(fts_object_t *object, int winlet, fts_symbol_t selector, fts_method_t method, int ac, fts_symbol_t *at, int margs)
The fts_object_method_define function define a method that accept a message composed of one selector and a fixed number of arguments of a fixed type (for messages like int, float, complex, or n-uples of a given structure).
The fts_object_method_define_varargs function define a method that accept a message composed of one selector and a variable number of arguments (for messages like list).
The fts_object_method_define_optargs function define a method that accept a message composed of one selector a fixed number of arguments of a fixed type followed by a variable number of arguments (the more generic case).
The arguments have the following meaning :
selector, the method will be called with the message arguments, after arguments type checking.
A number of message specific functions (actually, macros) simplify life for standard types.
fts_status_t fts_object_method_define_int(fts_object_t *object, int winlet, fts_method_t method); fts_status_t fts_object_method_define_float(fts_object_t *object, int winlet, fts_method_t method); fts_status_t fts_object_method_define_number(fts_object_t *object, int winlet, fts_method_t method); fts_status_t fts_object_method_define_symbol(fts_object_t *object, int winlet, fts_method_t method); fts_status_t fts_object_method_define_bang(fts_object_t *object, int winlet, fts_method_t method); fts_status_t fts_object_method_define_list(fts_object_t *object, int winlet, fts_method_t method); fts_status_t fts_object_method_define_anything(fts_object_t *object, int winlet, fts_method_t method);
These macros are simply shortcuts to call the above functions, their use is not mandatory, but
may make the code easier to read.
The number version install the same method for ints and floats.
The anything version, and in general installing a method for the fts_s_anything selector
will install a catch all method on the inlet that will be called for any message received.
Below is an example showing the installation of the method integer_in1 already defined for the integer object. This method is defined for inlet number 1 and takes one argument of type int.
fts_status_t integer_instantiate(fts_class_t *class, int ac, fts_atom_t *at)
{
fts_method_define_varargs(class, 1, fts_s_int, integer_in1);
}
Some of the methods of a class can be “system” methods, handling system level functions. These functions are now : object initialization and deletion, objects connection and disconnection.
All the system methods are associated to messages received on the system inlet, which is a symbolic constant defined by the message system. Except this, system methods are strictly identical to standard methods.
The init method handles the initialization of an object, but doesn't need to handle memory allocation. It is very similar to a C++ constructor. If it is defined, it is called automatically by the system when an object is created.
The init method arguments, are the object creation arguments, including the class name; i.e. using FTS with the standard Max editor, the arguments of the init arguments are the content of the object box. The declaration of the types of these so-called creation arguments must take into account this feature.
The selector of the init method is the predefined symbol fts_s_init.
Below is the example of the init method for the integer object already defined. This method just initialize the state of the object with the value of its argument.
void integer_init(fts_object_t *object, int winlet, fts_symbol_t selector,
int ac, const fts_atom_t *at)
{
integer_t *this = (integer_t *)object;
post("initializing an object of class %s", fts_symbol_name(fts_get_symbol_arg(ac, at, 0, 0)));
this->n = fts_get_int_arg(ac, at, 1, 0);
}
This method is installed by the following code :
fts_status_t integer_instantiate(fts_class_t *class, int ac, fts_atom_t *at)
{
fts_method_define_varargs(class, fts_SystemInlet, fts_s_init, integer_init);
}
The init method can detect an error in its argument; in order to signal the error, the following function should be called:
extern void fts_object_set_error(fts_object_t *obj, const char *format, ...);
The obj argument should be the object being initialized; the format argument is a string in the “printf” format, followed by a variable number of arguments; the user interface will then show the object as an invalid object (greyed) and the string passed by format and the following arguments will be used as error message for the object.
The delete method handles the destruction of an object, but doesn't need to handle memory de-allocation. It is very similar to a C++ destructor. If it is defined, it is called automatically by the system when an object is deleted, without arguments.
The selector of the delete method is the predefined symbol fts_s_delete.
A delete method can be installed for the integer object by the following code :
fts_status_t integer_instantiate(fts_class_t *class, int ac, fts_atom_t *at)
{
fts_method_define_varargs(class, fts_SystemInlet, fts_s_delete, integer_delete);
}
An object can be redefined by an other one as the result of two kind of actions: the direct editing of the object in the user interface, or an implicit change of arguments to the change of a patcher variable value.
In both case, the object is substited with a newly instantiated one; before deleting the old one, the system give an opportunity to copy some of the state of the old object to the new one; this can be very convenient if this state is a set of persistent data, like a table, that we don't want to loose just because one secondary parameter of the object changed; or for example, to allow reusing an edited data set on a slightly different object.
This opportunity is implemented by a modified deleting sequence; if the object being substitued implement a “release” method, then this is called; in this method the object should free global resources, like a global name, but it should not free internal resources, like a table content; then the system send to the newly created object a “redefining” (the fts_s_redefining C variable is defined) message to the system inlet, with as only argument the old object; the message is sent after the init message; it is the responsability of the method implementation to check that the old object is of a meaningful type; finally, the “delete” message is sent to the old object. If the old object do not define a method for the “release”, just the standard “delete” message is sent, and no “redefining” is tryed.
The “redefining” method should not send messages, either from the old or from the new object, because there is no guarantee that the connections structure are consistent at this point in time for either of two objects.
The outlets of an object can be statically typed. If an outlet is going to send only one kind of message selector, this selector and the types of associated arguments can be declared using the fts_outlet_type_define function.
fts_status_t fts_outlet_type_define_varargs(fts_class_t *class, int woutlet, fts_symbol_t selector)
The arguments of fts_outlet_type_define_varargs have the following meaning :
The type of an outlet will be used by the system to do a type checking, at connection-time and at run-time (when needed).
Outlet typing do not affect the efficiency of method calling; the method dispatching use a entry dynamic cache that optimize methods calls for all objects, not only statically type ones.
The type of an outlet is unique : if an outlet is supposed to send different kinds of messages, then it must not be typed; this means that the symbol fts_s_anything is not accepted as argument to this function.
The following code defines the type of the outlet for the integer object. The outlet number 1 is defined to send only one type of message, with selector int and one argument of type int.
fts_status_t integer_instantiate(fts_class_t *class, int ac, fts_atom_t *at)
{
fts_outlet_type_define_varargs(class, 0, fts_s_int);
}
The definition of a class is the definition of :
It comes from this definition that all objects of a class have same number of inlets and outlets. The mechanism for having variable number of inlets and outlets is the metaclass described below.
When a new class is installed, it is not fully initialized. The complete initialization will be done on demand, at the first instantiation of the class (i.e. when the first object of the class is created). The function that is responsible for this task is called the instantiation function.
A class is installed with the fts_class_install function, that declares its name and its instantiation function.
fts_status_t fts_class_install(fts_symbol_t name, fts_instantiate_fun_t instantiate_fun)
The arguments of fts_class_install have the following meaning :
For simplicity, aliases (usually abbreviations) can be defined for a class name, using the fts_class_alias function:
void fts_class_alias(fts_symbol_t new_name, fts_symbol_t old_name)
After a call to fts_class_alias, the name new_name, when used as a class name, will be automatically substituted by old_name.
Below is an example of use of the fts_class_install and fts_class_alias functions:
void integer_config(void)
{
/* Install the metaclass "integer" */
fts_class_install(fts_new_symbol("integer"), integer_instantiate);
/* Register "i" to be an alias for "integer" */
fts_class_alias(fts_new_symbol("i"), fts_new_symbol("integer"));
}
The signature of a class instantiation function is the following :
fts_status_t my_class_instantiate(fts_class_t *class, int ac, fts_atom_t *at)
The arguments of the class instantiation function have the following meaning :
are usually meaningless for a class.
The instantiation function contains at least the 3 following steps :
of outlets. This is done by the fts_class_init function.
described
The instantiation function can should return fts_Success if the class has been instantiate correctly, or fts_CannotInstantiate in case of errors.
fts_status_t fts_class_init(fts_class_t *class, unsigned int size, int ninlets, int noutlets, void *user_data)
The arguments of fts_class_init have the following meaning :
structure
a simple way to share datas between all the objects of a class
Below is the example of the instantiation function of the integer object already defined. This instantiation function first initialize the class, then defines all the methods of the class, then defines the type of the outlet. The objects of this class have 2 inlets and 1 outlet :
fts_status_t integer_instantiate(fts_class_t *class, int ac, fts_atom_t *at)
{
fts_class_init(class, sizeof(integer_t), 2, 1, 0);
/* init method */
fts_method_define_varargs(class, fts_SystemInlet, fts_s_init, integer_init);
/* message "int" on inlet 0 method */
fts_method_define_varargs(class, 0, fts_s_int, integer_int);
/* message "int" on inlet 1 method */
fts_method_define_varargs(class, 1, fts_s_int, integer_in1);
/* message "bang" on inlet 0 method */
fts_method_define_varargs(class, 0, fts_s_bang, integer_bang, 0, 0);
/* outlet type is int */
fts_outlet_type_define_varargs(class, 0, fts_s_int);
return fts_Success;
}
A metaclass is a definition of a set of classes having common behavior and sharing the same instantiation function. To a metaclass is associated a base of already instantiated classes. A class is instantiated when an object is created, following the metaclass instantiation process described bellow.
The metaclass instantiation process is based on the following assumption : the decision that 2 objects are in the same class can be made by comparing the objects creation arguments only.
A metaclass is instantiated on demand, when an object is created, through the following steps. The base of classes of the metaclass is searched for a candidate class by a matching based on the creation arguments of the object. This matching uses the equivalence function, with the following algorithm :
for all classes of the base
{
if (equivalence_function(creation_arguments(current_class),
creation_arguments(object_to_be_created))
{
/* object to be created is of the current class */
return current_class;
}
}
If no match is found, a class is instantiated : the class instantiation function is called, its arguments being the object creation arguments. This class is stored in the base of classes of the meta-class together with the creation arguments of the object, for later use by the equivalence function. Then, memory is allocated for the object and if defined, the init method is called for this object.
The equivalence function is used by the message system at object creation time. It is used to decide if 2 objects are in the same class or not.
int an_equivalence_function(int ac0, const fts_atom_t *at0, int ac1, const fts_atom_t *at1)
The arguments of an equivalence_function have the following meaning :
the other being the creation arguments of the class in the class base
The equivalence function should return true if the two set of arguments can correspond to the same instance class, i.e. if they are equivalent with respect to the class system.
The message system already provides a number of widely used equivalence functions. These functions are described in the table below.
Below is the example of the bangbang class : the bangbang object has 1 inlet, receiving a bang, and a certain number of outlets. The number of outlets is given by the creation argument, which is of type int. When receiving a bang message on its inlet, the bangbang object outputs a bang message on all its outlets, starting from the last.
The bangbang class is a metaclass, with a simple equivalence function : it simply compares the identity of the arguments, which must be of type int.
static int bangbang_equiv(int ac0, const fts_atom_t *at0,
int ac1, const fts_atom_t *at1)
{
if (ac0 == 1 && ac1 == 1
&& fts_is_int(at0) && fts_is_int(at1)
&& fts_get_int(at0) == fts_get_int(at1))
return 1;
else
return 0;
}
typedef struct {
fts_object_t o;
int noutlets;
} bangbang_t;
static void bangbang_bang(fts_object_t *object, int winlet, fts_symbol_t selector,
int ac, const fts_atom_t *at)
{
bangbang_t *this;
int i;
this = (bangbang_t *)object;
for (i = this->noutlets-1; i >= 0; i--)
fts_outlet_send(object, i, fts_s_bang, 0, 0);
}
static void bangbang_init(fts_object_t *object, int winlet, fts_symbol_t selector,
int ac, const fts_atom_t *at)
{
bangbang_t *this;
this->noutlets = fts_get_int_arg(ac, at, 1, 2);
}
static fts_status_t bangbang_instantiate(fts_class_t *class, int ac, const fts_atom_t *at)
{
int i, noutlets;
fts_symbol_t t[2];
if ((ac >= 1) && fts_is_int(at))
noutlets = fts_get_int(at);
else
noutlets = 1;
fts_class_init(class, sizeof(bangbang_t), 1, noutlets, 0);
fts_method_define_varargs(class, 0, fts_s_bang, bangbang_bang);
fts_method_define_varargs(class, fts_SystemInlet, fts_s_init, bangbang_init);
for (i = 0; i < noutlets; i++)
fts_outlet_type_define(class, i, fts_s_bang, 0, 0);
return fts_Success;
}
A metaclass is installed with the following function:
fts_metaclass_t *fts_metaclass_create(fts_symbol_t name, fts_instantiate_fun_t instantiate_function, fts_equiv_fun_t equiv_function)
The metaclass is named name; instantiate_function will be used to instantiate new classes, and equiv_function will be its equivalence function.
As for classes, metaclasses can be aliased, using the fts_metaclass_alias function:
void fts_metaclass_alias(fts_symbol_t new_name, fts_symbol_t old_name)
This function has the same behavior as fts_class_alias.
Below is an example of a metaclass installation:
void bangbang_config(void)
{
/* Install the metaclass "bangbang" */
fts_metaclass_install(fts_new_symbol("bangbang"), bangbang_instantiate, bangbang_equiv);
/* Register "bb" to be an alias for "bangbang" */
fts_metaclass_alias(fts_new_symbol("bb"), fts_new_symbol("bangbang"));
}
In FTS each object and class can have properties associated with it; a property is simply a pair name value, where the name is an FTS symbol, and the value is any FTS value; a property is a kind of dynamic storage tied to the object; a class can provide default values for properties, that are valid for all the object instance of the class.
Also, the property system is extended with standard constraint propagation and daemon techniques: putting a property or getting a property from an object can transparently activate a number of functions that propagate the properties in the object network.
This subsystem is currently used in FTS to implement some experimental optimization and features in the DSP compiler.
Its API is stble but is not currently documented (soon to be), check the properties.h file in the sources.