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 :
put
method
this special method is the method that will generate the code pertinen to this
object in the DSP program
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 :
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 :
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
delete
put
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; }
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.
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
A DSP function has a unique argument of type
Arguments are accessed using the following macros :
The macros are:
The arguments of this function have the following meaning :
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:
The
ftl_data_free(this->obj_ftl_data)
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)
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:
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
float f;
f = (float) fts_get_float_long_arg( at, ac, 1, 0.0);
ftl_data_copy(float, this->ftl_data, &f);
ftl_data_set
special macro, like in:
The argument are similar to
typedef struct
{
....
float vecsize;
.....
} obj_control_t;
f = (float) vecsize;
ftl_data_set(obj_control_t, this->ftl_data, vecsize, &f);
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 *
.
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 .
fts_word_get_symbol
( AP)
fts_symbol_t *
fts_word_get_string
( AP)
const char *
fts_word_get_obj
( AP)
void *
fts_word_get_long
( AP)
long
fts_word_get_float
( AP)
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
The fts_dsp_descr_t
structurefts_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:
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
fts_dsp_descr_t *dsp = (fts_dsp_descr_t *)fts_get_obj_arg(at, ac, 0, 0);
n
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.
fts_symbol_t *fts_dsp_get_input_name(fts_dsp_descr_t *desc, int in);
in
. int fts_dsp_get_input_size(fts_dsp_descr_t *desc, int in);
in
. int fts_dsp_get_input_srate(fts_dsp_descr_t *desc, int in);
in
. int fts_dsp_get_is_input_null(fts_dsp_descr_t *desc, int in);
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);
out
. int fts_dsp_get_output_size(fts_dsp_descr_t *desc, int out);
out
. int fts_dsp_get_output_srate(fts_dsp_descr_t *desc, int out);
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 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.
dsp_declare_function
which have already been described.
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 :
long
and its value to the
length
member of the dsp_signal
structure. For example, to set
the fourth argument to the size of the first input buffer :
fts_set_long( args+1, fts_dsp_get_input_size(dsp, 0);
name
member of the dsp_signal
structure. For example, to set
the first argument to the float array of the first input buffer :
fts_set_symbol (args, fts_dsp_get_input_name(dsp, 0);
The reason for this is that the buffers are not allocated during DSP program building, but after finishing the DSP program building. The buffer names are used as handles to pass them to the DSP program interpreter, which keeps its internal symbol tables.
fts_set_ftl_data
macro must be used, like in:
fts_set_ftl_data (args, this->vsma_ftl_data);
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:
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
Most complex cases can be handled with class and global property daemons; refer
to the
The FTS Kernel Reference Manual.
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.
Or
fts_class_put_long_prop(cl, fts_s_dsp_upsampling, up_sampling_factor);
fts_class_put_long_prop(cl, fts_s_dsp_downsampling, down_sampling_factor);
init
method
by calling one of the two following function:
Or
fts_object_put_long_prop(o, fts_s_dsp_upsampling, up_sampling_factor);
fts_object_put_long_prop(o, fts_s_dsp_downsampling, down_sampling_factor);
A complete example
Source of the vsma
object
Authors : Maurizio De Cecco, François Déchelle
Copyright © 1995 IRCAM.