jMax jMax documentation

Developing jMax DSP objects


Disclaimer

The API presented here is subject to changes.

This first version of the documentation is adapted from the FTS DSP Objects Programming Guide and some inconsistancies may subsist.


Introduction

This document describes the FTS DSP objects programming interface. DSP objects are objects that computes samples and are scheduled at a fixed rate synchonous to the sample rate. DSP objects use the FTS message system, described in Control Objects Developer's Guide, but have special functions for sample computation and special methods for compilation of the DSP program.

The DSP program is a program written for a internal FTS DSP virtual machine, called FTL, that allows a better efficiency on DSP computations that a direct interpretation of the object network. It is constructed when needed. It consists of instructions with arguments, typically functions calls, and private data. The DSP program is executed each time a new buffer of samples is needed by the device that is associated with audio output.

FTS DSP objects differ from usual control objects by the following points :


Class instantiation function for DSP objects

DSP inlets and outlets declaration

A DSP class must declare which inlets and outlets are DSP inlets and outlets. To these inlets and outlets will be associated samples buffers to be used during samples computation.

This is done using the 2 following functions :

void fts_dsp_declare_inlet( fts_class_t *class, int winlet)

void fts_dsp_declare_outlet( fts_class_t *class, int woutlet)

The arguments of these functions have the following meaning :

class
a pointer to the DSP class
winlet
the number of the DSP inlet
woutlet
the number of the DSP outlet

DSP function declaration

A DSP class must declare its DSP function. This function will be called during the execution of the DSP program.

A DSP function has a special signature, which is the following :

void <my_dsp_function>( fts_word_t *args)

DSP functions are discussed in details later.

In a DSP class instantiation function, a DSP function is declared with the following function :

void fts_dsp_declare_function( fts_symbol_t *name, void (*dsp_function)(fts_word_t *))

The arguments of this function have the following meaning :

name
a symbol that will be used for later reference to this function
dsp_function
a pointer to the dsp function of the class
As the fts_symbol_t used as first argument to dsp_declare_function will be reused later, it is convenient to store it in a variable local to the class being defined. This is detailled in the example below.

DSP specific methods declaration

A DSP class must declare 3 methods that are mandatory : init, delete and put methods. This is done using the fts_method_define function, which is described in details in Control Objects Developer's Guide. These methods are associated with the system inlet, with the following selectors and arguments :
init
selector : fts_s_init, arguments: object dependent
delete
selector : fts_s_delete, arguments: none
put
selector : fts_s_put, arguments: 1 argument of type fts_ptr

Example of DSP class instantiation function

Below is the example of the instantiation function of a DSP object, doing scalar multiply then add, which can be usefull for instance for mixing.

typedef struct {
  fts_object_t obj;
  float s;
} sma_t;

static fts_symbol_t *sma_function = 0;

/* Declaration of DSP function : content will be detailled later */
static void 
sma_dsp_function( fts_word_t *args);

/* Declarations of methods (will be detailled later) */
static void sma_init( fts_object_t *o, int winlet, fts_symbol_t *s, int ac, const fts_atom_t *at);
static void sma_delete( fts_object_t *o, int winlet, fts_symbol_t *s, int ac, const fts_atom_t *at);
static void sma_put( fts_object_t *o, int winlet, fts_symbol_t *s, int ac, const fts_atom_t *at);
static void sma_set( fts_object_t *o, int winlet, fts_symbol_t *s, int ac, const fts_atom_t *at);

static fts_status_t
sma_instantiate(fts_class_t *cl, int ac, const fts_atom_t *at)
{
  fts_symbol_t a[2];

  /* class initialization : 2 inlets, 1 outlet */
  fts_class_init(cl, sizeof(sma_t), 2, 1, 0);

  /* definition of DSP specific methods */
  fts_method_define_varargs( cl, fts_SystemInlet, fts_s_init, sma_init);
  fts_method_define_varargs( cl, fts_SystemInlet, fts_s_delete, sma_delete);
  fts_method_define_varargs(cl, fts_SystemInlet, fts_s_put, sma_put);

  /* definition of other methods */
  fts_method_define_varargs(cl, 0, fts_s_int, sma_set);
  fts_method_define_varargs(cl, 0, fts_s_float, sma_set);

  /* declaration of DSP inlets and outlets */
  fts_dsp_declare_inlet(cl, 0);
  fts_dsp_declare_inlet(cl, 1);
  fts_dsp_declare_outlet(cl, 0);

  /* declare DSP function and keep the associated symbol */
  sma_function = fts_new_symbol("sma");
  fts_dsp_declare_function( sma_function, sma_dsp_function);
  
  return fts_Success;
}


Init and delete methods for DSP objects

Init method

The init methods of a DSP class have two tasks to perfom; first, to allocate in the FTL virtual machine any private data the DSP computation may need, the other is to declare the current object as a DSP object, i.e. has an object that can generate part of the DSP program. The private data is generated in this way:

  this->obj_ftl_data = ftl_data_alloc(sizeof(dsp_data_t));

dsp_data_t must be the C type of the data structure to be allocated. The function return a ftl_data_t value, that must be stored in the object for later use, namely to access and modifing the data, and to pass it to the DSP program in the put method; an object is free to allocate as many ftl data item as needed. The ftl data items should then be initialized to some default or initial values; see the paragraph Modifing The FTL Data. It should be stressed that a ftl_data_t value is not necessarly a C pointer, and the only was to access it are those explained in this chapter; see the paragraph Limitations of FTL Data items for details. The other task that init method of a DSP class must perform is to insert the object being initialized in the list of DSP objects. This list is maintained by the system for DSP program compilation.This is done by calling the following function :

void fts_dsp_add_object( fts_object_t *object)

The argument of this function is a pointer to the object that is to be inserted.

Delete method

The delete method of a DSP class must free all the ftl data items allocated in the init method, by calling the ftl_data_free function on each of them, like in:

  ftl_data_free(this->obj_ftl_data)

The delete method of a DSP class must also call the following function in order to remove the object being deleted from the list of DSP objects :

void fts_dsp_remove_object( fts_object_t *object)

The argument of this function is a pointer to the object that is to be removed. It must point to an object that has been inserted using function fts_dsp_add_object.

Modifing The FTL Data

The init method, or any other method can change the content of a ftl data item.

To get the pointer associated with a FTL data, the following macro is provided: ftl_data_get_ptr( DATA). The value of this macro is the pointer contained in the FTL data handle.

Limitations of FTL Data items

FTL data are not freely accessible C data structure; the type ftl_data_t is not a C pointer to the data structure; in general, there is no way for the DSP object to know the current content of a ftl data item; if a particular value or parameter is needed also in control computation, should be stored also inside the DSP object. The reason for these limitations is that ftl data items are entity that live in the DSP program execution; they may be implemented by memory in the same address space of the control computation, but they also reside in an other address space, in an other processor or in an other "logical time". In particular, in future multi-thread release of FTS, the DSP computation and the control computation will happen in separate threads, and they will not be necessarly synchronius; a particular ftl data item may actually correspond to the status seen by the control thread one o many scheduling loops before; this is why the control cannot directly access the DSP program memory. Also, in architecture with small caches (like the ISPW), DSP data can be dynamically rearranged to maximize locality.

Example of init and delete methods

Below is the example of the init and delete methods of the DSP object sma which has already been introduced.

static void 
sma_init( fts_object_t *o, int winlet, fts_symbol_t *s, int ac, const fts_atom_t *at)
{
  sma_t *this = (sma_t *)o;
  float *ptr;

  /* allocate ftl data */
  this->sma_ftl_data = ftl_data_alloc(sizeof(float));

  /* initializing the ftl data */
  ptr = (float *)ftl_data_get_ptr( this->sma_ftl_data);
  *ptr = fts_get_float_arg(at, ac, 1, 0.0);

  /* add object to the DSP graph */
  fts_dsp_add_object(o);
}

static void 
sma_delete( fts_object_t *o, int winlet, fts_symbol_t *s, int ac, const fts_atom_t *at)
{
  /* free ftl data */
  ftl_data_free(this->sma_ftl_data);

  /* remove object from the DSP graph */
  fts_dsp_remove_object(o);
}

Example of a method modifying ftl data

Below is the example of the set method of the DSP object sma which need to change a parameter used by the object DSP function.
static void 
sma_set( fts_object_t *o, int winlet, fts_symbol_t *s, int ac, const fts_atom_t *at)
{
  sma_t *this = (sma_t *)o;
  float *p;

  /* setting the ftl data */
  p = (float *)ftl_data_get_ptr( this->sma_ftl_data);
  *p = fts_get_float_arg( at, ac, 1, 0.0);
}

The DSP computation function

DSP function signature

A DSP function has a special signature, which is the following :

void ( fts_word_t *args)

A DSP function has a unique argument of type fts_word_t *. This type is an union containing either a int, a float, a fts_symbol_t or a pointer. The fts_word_t and fts_symbol_t structures are discussed in details in FTS Kernel Reference Manual .

DSP function arguments

The arguments of the DSP function are stored in an array of elements of type fts_word_t. This type is an union containing either a int, a float, a fts_symbol_t or an void pointer, specified as ftl_data_t in the put method. The fts_word_t and fts_symbol_t data structures are discussed in details in the FTS Kernel Reference Manual .

Arguments are accessed using the following macros :

fts_word_get_symbol( AP)
gets a value of type fts_symbol_t *
fts_word_get_string( AP)
gets a value of type const char *
fts_word_get_obj( AP)
gets a value of type void *
fts_word_get_int( AP)
gets a value of type int
fts_word_get_float( AP)
gets a value of type float

Example of DSP function

Below is the example of the DSP function of the sma object already described.

static void
sma_dsp_function( fts_word_t *args)
{
  float *in1 = (float *) fts_word_get_obj(args);     /* first input buffer */
  float *in2 = (float *) fts_word_get_obj(args + 1); /* second input buffer */
  float *out = (float *) fts_word_get_obj(args + 2); /* output buffer */
  float *ps = (float *) fts_word_get_obj(args + 3);  /* pointer to scalar */
  int n = fts_word_get_int(args + 4);              /* size of buffers */
  float s;
  int i;

  s = *ps;
  for ( i = 0; i < n; i++)
    out[i] = s * in1[i] + in2[i];
}
The previous code can be compared with the code performing the same task that would be found in a usual DSP library :

static void 
sma( float *src1, float *src2, float *out, float *s, int n)
{
  float s;
  int i;

  s = *ps;
  for ( i = 0; i < n; i++)
    out[i] = s * in1[i] + in2[i];
}


The put method

The put method is a method specific to DSP objects, that is called by the system when computing the DSP program. The put method must complete the 2 following tasks, in this order : The put method is called during the computation of the DSP program. As previously mentionned, it has one argument of type fts_Object. This argument is a pointer to a fts_dsp_descr_t structure containing the necessary information for DSP program building.

The fts_dsp_descr_t structure

The fts_dsp_descr_t structure is a opaque structure used by the DSP compiler to pass information about the code to be generated to the put method. The structure is passed to the put method (see below) as first argument, and can be accessed in this way:

  fts_dsp_descr_t *dsp = (fts_dsp_descr_t *)fts_get_obj_arg(at, ac, 0, 0);
n
Programmer should never access the structure directly, but should instead the access macros documented below; this macros allow to access the input and output sample buffers, their size and their corresponding sample rate. The sample buffers are referred by name; the buffer name are FTS symbol generated by the DSP compiler; they must be passed to the dsp function as name, the dsp compiler will then convert them to the correct float * pointer. In this release of FTS, all the inputs have the same size and sample rate; also all the inputs and outputs have the same size and sample rate.

The macros are:

fts_symbol_t *fts_dsp_get_input_name(fts_dsp_descr_t *desc, int in);

Return the name of the input sample buffer connected the input in.

int fts_dsp_get_input_size(fts_dsp_descr_t *desc, int in);

Return the size in sample of the input sample buffer connected the input in.

int fts_dsp_get_input_srate(fts_dsp_descr_t *desc, int in);

Return the sample rate in sample per seconds of the input sample buffer connected the input in.

int fts_dsp_get_is_input_null(fts_dsp_descr_t *desc, int in);

Return true iff the sample buffer connected the input in is the null buffer, i.e. if there are no signals connected to the input.

fts_symbol_t *fts_dsp_get_output_name(fts_dsp_descr_t *desc, int out);

Return the name of the output sample buffer connected the output out.

int fts_dsp_get_output_size(fts_dsp_descr_t *desc, int out);

Return the size in sample of the output sample buffer connected the output out.

int fts_dsp_get_output_srate(fts_dsp_descr_t *desc, int out);

Return the sample rate in sample per seconds of the output sample buffer connected the output out.

Inserting a call in the DSP program

After declaring buffers for its outputs, a DSP object must insert a call to its DSP function in the DSP program. This is done using the following function :

void fts_dsp_add_function( fts_symbol_t *name, int argc, fts_atom_t *argv)

The arguments of this function have the following meaning :

name
the name of the DSP function. This name must have been registered using the function fts_dsp_declare_function which have already been described.
argc
the count of arguments of the call
argv
the arguments array
The arguments to the DSP function call are passed as an array of atoms. This array of atoms is filled using the macros for filling atoms described in The FTS Kernel Reference Manual. The content of the atom is set by using the proper macro depending on argument type.

The corresponding atom of the arguments array must be filled using the macros described above, according to the following rules :

Example of a put method

Below is the code of the put method of the sma object already mentionned. The code of the DSP function of this object is given again in order to make reading easier.

static void
sma_put(fts_object_t *o, int winlet, fts_symbol_t *s, int ac, const fts_atom_t *at)
{
  sma_t *this = (sma_t *)o;
  fts_dsp_descr_t *dsp = (fts_dsp_descr_t *)fts_get_obj_arg(at, ac, 0, 0);
  fts_atom_t args[5];

  fts_set_ftl_data(args + 0, this->sma_ftl_data);
  fts_set_symbol(args + 1, fts_dsp_get_input_name(dsp, 0));
  fts_set_symbol(args + 2, fts_dsp_get_input_name(dsp, 1));
  fts_set_symbol(args + 3, fts_dsp_get_output_name(dsp, 0))
  fts_set_int(args + 4, fts_dsp_get_input_size(dsp, 0));

  fts_dsp_add_function(sma_function, 5, args);
}

static void
sma_dsp_function( fts_word_t *args)
{
  float *ptr = (float *) fts_word_get_obj(args + 0); /* pointer to scalar */
  float *in1 = (float *) fts_word_get_obj(args + 1); /* first input buffer */
  float *in2 = (float *) fts_word_get_obj(args + 2); /* second input buffer */
  float *out = (float *) fts_word_get_obj(args + 3); /* output buffer */
  int n = fts_word_get_int(args + 4); /* size of buffers */
  int i;

  for(i=0; i<n; i++)
    out[i] = *ptr * in1[i] + in2[i];
}

Copyright © 1995,1999 IRCAM.
All rights reserved.