The FTS DSP Objects API Programming Guide.

This file documents the FTS DSP objects programming interface.

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 sampling rate. DSP objects use the FTS message system, described in
FTS Message System User 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.

Attention !!! The API described here is not completed. It should be quite stable, but some aspect (expecially DSP to Control communication) are not completely settled or defined; in general, what is documented in this documented will be supported by future releases.

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 dsp_sig_inlet( fts_class_t *class, int winlet)

void dsp_sig_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 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 FTS Message System User Guide . These methods are associated with the system inlet, with the following selectors and arguments :
init
selector : fts_s_init corresponding to symbol name "$init" arguments : object dependant
delete
selector : fts_s_delete corresponding to symbol name "$delete" arguments : none
put
selector : fts_s_put corresponding to symbol name "put" arguments : 1 argument of type fts_Object

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

static fts_symbol_t *vsma_function = 0;

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

/* Declarations of methods : contents will be detailled later  */
static void vsma_init( fts_object_t *o, int winlet, fts_symbol_t *s, 
           int ac, const fts_atom_t *at);
static void vsma_delete( fts_object_t *o, int winlet, fts_symbol_t *s, 
           int ac, const fts_atom_t *at);
static void vsma_put( fts_object_t *o, int winlet, fts_symbol_t *s, 
           int ac, const fts_atom_t *at);
static void vsma_set( fts_object_t *o, int winlet, fts_symbol_t *s, 
           int ac, const fts_atom_t *at);

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

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

  /* Definition of DSP specific methods */
  a[0] = fts_Symbol;
  a[1] = fts_Float|fts_Long|fts_OptArg;
  fts_method_define( cl, fts_SystemInlet, fts_s_init, vsma_init, 2, a);
  fts_method_define( cl, fts_SystemInlet, fts_s_delete, vsma_delete, 0, 0);
  a[0] = fts_Object;
  fts_method_define(cl, fts_SystemInlet, fts_s_put, vsma_put, 1, a);

  /* Definition of other methods */
  a[0] = fts_Long;
  fts_method_define(cl, 0, fts_s_int, vsma_set, 1, a);
  a[0] = fts_Float;
  fts_method_define(cl, 0, fts_s_float, vsma_set, 1, a);

  fts_method_define(cl, 0, fts_new_symbol("print"), vsma_print, 0, 0);

  /* Definition of DSP inlets and outlets */
  dsp_sig_inlet(cl, 0);
  dsp_sig_inlet(cl, 1);
  dsp_sig_outlet(cl, 0);

  /* Declare DSP function and keep the associated symbol */
  vsma_function = fts_new_symbol("vsma");
  dsp_declare_function( vsma_function, vsma_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_new(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 dsp_list_insert( 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 dsp_list_remove( 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 dsp_list_insert.

Modifing The FTL Data

The init method, or any other method can change the content of a ftl data item, by using two special macros. The ftl_data_copy macro change the whole content of the ftl data item, and is particularly, but not only, useful when the type of the ftl data item is a simple scalar type, like float or long; for example, to change a float data item the following code can be used:
 
  float f;

  f = (float) fts_get_float_long_arg( at, ac, 1, 0.0);
  ftl_data_copy(float, this->ftl_data, &f);
 
Note that the things to specify are the C type of the item, the data item itself, and the new value, passed by pointer. In the case where the ftl data item correspond to a C structure, a single field of the structure can be changed with the ftl_data_set special macro, like in:
 
  typedef struct 
  {
    ....
    float vecsize;
    .....
  } obj_control_t;

  f = (float) vecsize;
  ftl_data_set(obj_control_t, this->ftl_data, vecsize, &f); 
 
The argument are similar to ftl_data_set, with the addition of the field name.

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 vsma which has already been introduced.

static void
vsma_init( fts_object_t *o, int winlet, fts_symbol_t *s, 
           int ac, const fts_atom_t *at)
{
  vsma_t *this = (vsma_t *)o;
  float f;

  /* Allocate ftl data */

  this->vsma_ftl_data = ftl_data_new(float);

  /* initializing  ftl data */

  f = (float) fts_get_float_long_arg( at, ac, 1, 0.0);
  ftl_data_copy(float, this->vsma_ftl_data, &f);

  /* insert object in DSP objects list */

  dsp_list_insert(o);
}

static void
vsma_delete( fts_object_t *o, int winlet, fts_symbol_t *s, 
             int ac, const fts_atom_t *at)
{
  /* Freeing ftl data */

  ftl_data_free(this->vsma_ftl_data);

  /* remove object from DSP objects list */

  dsp_list_remove(o);
}

Example of a method modifying ftl data

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

  /* changing ftl data */

  f = (float) fts_get_float_long_arg( at, ac, 1, 0.0);
  ftl_data_copy(float, this->vsma_ftl_data, &f);
}

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 long, 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 long, 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_long( AP)
gets a value of type long
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 vsma object already described.

static void
vsma_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 */
  long n = fts_word_get_long(args + 4);              /* size of buffers */
  float s;
  long 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
vsma( float *src1, float *src2, float *out, float *s, int n)
{
  float s;
  long 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 sampling rate; also all the outputs have the same size and sampling rate, but the input size and sampling rate can be different from the output sampling rate; see
Up/Down Sampling objects for details.

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 sampling 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 sampling 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 dsp_add_funcall( 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 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.

To pass to the DSP function samples buffers allocated using the dsp_gen_outputs function, 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 vsma object already mentionned. The code of the DSP function of this object is given again in order to make reading easier.

static void
vsma_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 */
  long n = fts_word_get_long(args + 4);              /* size of buffers */
  float s;
  long i;

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

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

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


  dsp_add_funcall( vsma_function, 5, args);
}

Up/Down Sampling objects

It is possible to declare that an object is down or up sampling, and then that the compiler should allocate sample buffers of different dimension in input or output; in order to do that, it is necessary to declare the property fts_s_dsp_upsampling or fts_s_dsp_downsampling, with a value corresponding to the up/downsampling factor, in the object.

The are various way to declare such a property (for complete documentation look at The FTS Kernel Reference Manual.

The most simple case is when all the object of the same class have the same upsampling or downsampling factor as the up~ or down~ classes; in this case, you can declare the property at the class level, by calling the following function in the instantiate method of the class:

 fts_class_put_long_prop(cl, fts_s_dsp_upsampling, up_sampling_factor);
Or
 fts_class_put_long_prop(cl, fts_s_dsp_downsampling, down_sampling_factor);

The other simple case is when the up/down sampling factor is known for each object, depending for example on its argument, as the sig~ object; in this case you can decide the up/down sampling factor in the init method by calling one of the two following function:

 fts_object_put_long_prop(o, fts_s_dsp_upsampling, up_sampling_factor);
Or
 fts_object_put_long_prop(o, fts_s_dsp_downsampling, down_sampling_factor);

Most complex cases can be handled with class and global property daemons; refer to the The FTS Kernel Reference Manual.


A complete example

Source of the vsma object
The FTS DSP Objects Programming Interface User Guide.

Authors : Maurizio De Cecco, François Déchelle

Copyright © 1995 IRCAM.