Sfidl Manual

= Sfidl Documentation = Author: Stefan Westerfeld

This document gives an overview over Sfidl, which is used in Beast for two purposes:

it possible to access the Bse functionality from C, C++, Scheme and possibly other languages. specifying all necessary information to generate a useful GUI automatically.
 * 1) Describe the API in a language independant fashion, to make
 * 1) Provide an easy way to fully define the API of  plugin modules,

Overview
To provide an API to more than one programming language, we first specify the API in a language independant fashion. Therefore, we use an .idl file, which contains a description what a class or procedure looks like.

Then, it is possible to provide an implementation in C or C++.

Finally, for different languages, code to access that implementation can be generated automatically.

Design goals
We tried to accomplish a number of different design goals during the SFI design.


 * SFI should act as strict isolation between the (realtime) Bse core, containing the implementation, and the user. The core should run either in a seperate thread, or in a seperate process.
 * Adding functionality to the implementation (such as new methods, new classes, new procedures, new procedure parameters, new record fields) should generally not affect a client's ability to talk to the core. That is, updating the core to a newer version should not force recompilation of the client code.
 * Access to the Bse code should be possible from various programming languages.
 * Writing plugins should be as easy as possible.

The Sfidl command line utility
Sfidl is the interface definition language compiler for Sfi, a manual page is available as sfidl.1, and the IDL file format is detailed in the next sections.

The Basics: Comments and Namespaces
The syntax of .idl files is similar to C++ and/or CORBA IDL. In the following sections the elements will be described. First of all, you need to know that everything needs to be declared within the scope of a namespace. This avoids collisions.

The syntax for namespace is

Namespaces can also be nested. There is an additional using keyword, which can be used to avoid using the namespace qualifier "::" to refer to something from a different namespace:

As you see, primitive types (like Int) are declared within the Sfi namespace. C and C++ style comments work as usually.

Simple primitive types
SFI provides a number of predefined primitive data types. These are:


 * void
 * no value (this is only used for return values of procedures/methods which have no return value)


 * Sfi::Bool
 * a boolean value, which can be either true or false


 * Sfi::Int
 * a 32bit signed integer


 * Sfi::Num
 * a 64bit signed integer


 * Sfi::Real
 * a 64bit double floating point value


 * Sfi::String
 * a character string


 * Sfi::BBlock
 * a block of bytes (optimized for bulk data transfer)


 * Sfi::FBlock
 * a block of 32bit floating point values (optimized for bulk data transfer)


 * Sfi::Rec
 * a special type, which can hold any record (for records see the Composite Types below)

Choices
In addition to these simple primitive types, it is possible to define a choice as follows:

This means that a value of type WaveForm can hold one of these choices. Extra information can be supplied with each choice value (both optional):


 * A number that will be used by core language binding implementations (e.g. if in C or C++ the choice is implemenated as an enum type),
 * A user readable string, which can be used in GUIs to describe the value of the choice to the user (this string also is translatable).

There is no guarantee what number the interfacing code using the choice in C and C++ will see for a particular WaveForm (i.e. WAVE_FORM_SINE could be 3 in client code). This makes it possible to add choice values or change their number even after the client got compiled. (Internally the communication is done by passing the choice value as string).

There is one exception: the Neutral keyword indicates that the number should be 0 for both, client code and implementation. In C/C++, this allows writing

Composite Types
More complex data types can be constructed from these simple data types. There are two composite types: records and sequences.

Sequences are used for a sequence of multiple values of the same data type. The syntax is:

A sequence may then hold 0, 1 or N values of the same type. The name "ints" here is only used for the C language, where ints contains the actual data and n_ints contains the number of items in the sequence.

Records can be used for a sequence of values of different data types (they behave similar to structures in C/C++).

In opposition to sequences, NULL (or nil or whatever is adequate for the language you are using) is a possible value for records, that is, a record value either contains all of the above fields, or it is a NULL pointer.

For sequences, NULL is not a valid value (you can always use empty sequences there).

Parameter specifications: Note that you can, and probably should add parameter specifications for the fields in a sequence and a record. However, we describe parameter specifications seperately below.

Prototyping
In some cases you know that a certain type will be defined later, but you can't give it's definition already. Perhaps this occurs because two types, often classes, need each other in their definition. Perhaps this also occurs because you want seperate things in seperate .idl files in a special way.

You can use prototypes in this case, for all types the IDL compile understands.

Classes
IDL defined APIs consist mostly of classes. SFI supports single inheritance, so a set of simple classes might look like this (not taken from the Bse class hierarchy):

As far as this example goes, we have only added the bare minimum. It is however possible (and probably desirable) to add defaults and documentation to the methods, as demonstrated in this simple example:

Return and argument values referring to instances of classes (in this example samples) can be NULL. So load_sample in this example would probably return a valid sample, if the sample file could be loaded successfully, and NULL otherwise.

Various aspects of valid filenames are specified through the assignment of a parameter specification to filename. Parameter specifications are described in detail in the section PARAM SPECS.

Signals
Signals provide the possibility for objects (instances of classes) to emit events. A user can connect to the signal, and whenever a signal gets emitted, his callback gets called. In our example it would be possible to add two signals to AudioObject, one where it emits the current position regularily while playing, and one that gets emitted when it is done playing.

Properties
Another important element commonly found in classes are properties. Usually, they are logically grouped to improve readability in GUIs.

Although the above example isn't comprehensive in this regard, properties can be of any IDL type. The aforementioned groups come with translatable names, and properties may support a good chunk of definitions besides just their type and name.

In the example, alevel1 and alevel2 use the simplest form possible to define a property, just the types and names are given. Displaying a property like this in GUIs will provide unattractive results, to say the least.

For this reason, Sfidl allows type specific constructors, and by convention these constructors expect a translatable label and a translatable description as first two arguments and an option string as last argument. The constructors are provided by the Sfidl language bindings, so their availability differs dependant on that. In the above example, the percentage and frequency constructors, both of type Real and provided by Bse, are used and expect these arguments:


 * a translatable label,
 * a translatable description,
 * a default value (for Perc within 0 .. 100, for Freuquency within minimum and maximum),
 * a minimum value (Freuquency only),
 * a maximum value (Freuquency only),
 * an option argument.

Finally, obalance is an example for leaving out the constructor name, in this case the constructor name is assumed to be the type name, so the following two lines are fully equivalent:

More on property constructors provided by Bse can be found as part of the plugin development guide in the property section and the property option section.

Streams
For classes that do audio processing (module objects), it is necessary to specify streams, which will transport the audio data into the module, and the resulting audio data out of it. Generally there are three types of streams:


 * IStreams contain input audio signal for the module
 * OStreams contain output audio signal from the module
 * JStreams contain 0, 1 or more input audio signals to the module

As you see, the syntax is quite straight forward, containing a variable name and the specification of the translatable user visible strings, which contain the name of the stream, and a blurb describing what it does.

Procedures
Procedures can be thought of as methods-without-a-class. A classic example is:

which converts a midi note to a frequency, using a given fine tune. The same syntactic elements for specifying more details (parameter speccifications, Info strings) that are valid for methods can be used here as well.