jMax jMax documentation

Developing jMax control objects


Disclaimer

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.


Introduction

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:


Objects

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.


Methods

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.

Method Signature

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 FTS Kernel Reference Manual.
ac
the number of arguments
at
the arguments array

Method Arguments

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)
gets an argument of type int
fts_get_float_arg( AC, AT, N, DEFAULT)
gets an argument of type float
fts_get_symbol_arg( AC, AT, N, DEFAULT)
gets an argument of type fts_symbol_t
fts_get_string_arg( AC, AT, N, DEFAULT)
gets an argument of type const char *
fts_get_ptr_arg( AC, AT, N, DEFAULT)
gets an argument of type void *

The macro arguments have the following meaning :

AC
the number of atoms in array AT
AT
the atom array
N
the index of the requested argument
DEFAULT
the default value for the requested argument

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.


Message Sending

Messages can be send on the outlets of an object, using the fts_outlet_send function.

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 :

object
the object from which outlet the message is send
woutlet
the number of the outlet on which the message is send
selector
the message selector, a symbol
argc
the count of arguments of the message
args
the arguments of the message, an array of atoms

Message Sending Example

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);
}

Message Arguments Building

Typically, sending a message is done in 2 phases :

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
"int"
fts_s_float
"float"
fts_s_bang
"bang"
fts_s_list
"list"
fts_s_symbol
"symbol"
fts_s_set
"set"
fts_s_sig
"sig"
fts_s_put
"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:


Messages and Methods

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.

The fts_method_define Function

fts_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 :

class
the class in which the definition is valid
winlet
the number of the inlet for which the definition is valid
selector
the associated message selector : when receiving a message having this selector, the method will be called with the message arguments, after arguments type checking
method
the method : a function having the signature already defined

Method Definition Example

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);
}


System Methods

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

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

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);
}

The release and the redefining Methods

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 assist Method

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.


Outlet Typing

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.

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 :

class
the class for which the type is defined
woutlet
the number of the outlet for which the type is defined
selector
the associated message selector

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.

Outlet Typing Example

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));
}


Classes

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.

Class Installation

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 :

name
the name of the class
instantiate_fun
the instantiation function of the class

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 Class Instantiation Function

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 :

class
the class that is to be defined
ac
the number of arguments
at
the arguments array. These are the creation arguments of the first object of the class, and are usually meaningless for a class.

The instantiation function contains at least the 3 following steps :

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 Function

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 :

class
the class which is being initialized
size
the size of the objects of the class. It is the sizeof of the object structure
ninlets
the number of inlets of the objects of the class
noutlets
the number of outlets of the objects of the class
user_data
this argument will be simply stored in the fts_class_t structure, giving a simple way to share datas between all the objects of a class

Class Instantiation Function Example

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;
}


Metaclasses

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.

Metaclass Instantiation Process

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 :

Equivalence Functions

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 :

ac0, ac1
the number of atoms in array at0, at1
at0, at1
the arrays of arguments, one being the creation arguments of object being created, 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.

Existing Equivalence Functions

The message system already provides a number of widely used equivalence functions. These functions are described in the table below.

fts_arg_equiv
return true (i.e. not zero) if the arguments are identical (same number, same types, same values).
fts_arg_type_equiv
return true (i.e. not zero) if the numbers and the types of the arguments are the same.
fts_first_arg_equiv
return true (i.e. not zero) if the first arguments are identical.
fts_narg_equiv
return true (i.e. not zero) if the numbers of arguments are the same.
fts_never_equiv
Always return false (i.e.zero). Using this equivalence function means that there will be a new class instantiated for each object instantiated. This is useful as a first "coarse" implementation of a metaclass.

Example of Equivalence Function

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;
}

Metaclasses Installation

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"));
}


Object And Class Properties

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.


Examples

Source of the integer object

Source of the bangbang object

Copyright © 1995,1999 IRCAM.
All rights reserved.