The API presented here may be subject to small changes.
This first version of the documentation is adapted from the FTS Message System
Programming Guide
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:
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;
Attention !!! A 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 :
fts_symbol_t
data structure is discussed in details in FTS Kernel Reference Manual.
The arguments of a method are of type fts_atom_t
. The fts_atom_t
data structure is discussed in details in FTS Kernel Reference Manual.
Attention !!! 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.
Accessing Arguments
A method access its arguments via access macros, which handle both accessing the atoms and giving a default value if corresponding argument is optional.
Attention !!! 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 long
,
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.
List of Access Macros
Below is the list of available access macros :
fts_get_int_arg( AC, AT, N, DEFAULT)
int
fts_get_float_arg( AC, AT, N, DEFAULT)
float
fts_get_symbol_arg( AC, AT, N, DEFAULT)
fts_symbol_t
fts_get_string_arg( AC, AT, N, DEFAULT)
const char *
fts_get_ptr_arg( AC, AT, N, DEFAULT)
void *
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_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 :
fts_atom_t
data structure access macros
fts_outlet_send
function, passing it the arguments
array
Attention !!! 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.
Message Selectors
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:
fts_s_int
fts_s_float
fts_s_bang
fts_s_list
fts_s_symbol
fts_s_set
fts_s_sig
fts_s_put
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_new_symbol( const char *s)
returns a new symbol
containing string s. Note: the passed string is not copied.
fts_new_symbol_copy( const char *s)
returns a new symbol
containing string s. Note: the passed string is copied. This function
is used when the passed string is not a constant.
Method definition is done using the fts_method_define
function,
which is usually called when initializing the class. This function defines
to which message the method will be associated, and which kind of
arguments it expects.
fts_method_define
Functionfts_status_t fts_method_define_varargs( fts_class_t *class, int winlet, fts_symbol_t selector, fts_method_t method)
The arguments of fts_method_define_varargs
have the following meaning :
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_type_get_selector(fts_t_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.
init
MethodThe 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); }
release
and the redefining
MethodsAn 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 assist method allow an object to define a short help describing itself, and its inlet and outlet roles; this help will be shown in the status line when the mouse pass over the object or the object inlets and outlets.
The assist method receive either one or two arguments describing the help requested; the first is always a symbol between "object", "inlet" and "outlet"; in the case of a inlet or outlet, the following argument is the position of the inlet/outlet.
The message should be sent with the following function:
extern void fts_object_blip(fts_object_t *obj, const char *format, ...)Where obj should be the current object, the format argument is in
printf
syntax, and the following arguments are used with
format to produce the message.
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_outlet_type_define
Functionfts_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_type_get_selector( fts_t_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.
fts_class_init
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 :
The instantiation function contains at least the 3 following steps :
fts_class_init
function.
fts_method_define
previously described
fts_outlet_type_define
previously
described
The instantiation function can should return fts_Success
if
the class has been instantiate correctly, or
fts_CannotInstantiate
in case of errors.
The
fts_class_init
Functionfts_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 :
sizeof
of the object
structure
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_type_get_selector( fts_t_int), integer_int); /* message "int" on inlet 1 method */ fts_method_define_varargs( class, 1, fts_type_get_selector( fts_t_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_type_get_selector( fts_t_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 :
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; } }
$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 a_equivalence_function( int ac0, const fts_atom_t *at0, int ac1, const fts_atom_t *at1)
The arguments of a equivalence_function
have the following meaning :
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.
Existing Equivalence Functions
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 AI like 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 not currently documented.
Copyright © 1995,1999 IRCAM. All rights reserved. |