This text documents the internals of GNU/Linux Audio Mechanics (GLAME) Version 2.0.1 (26 April 2002), a capable and easily extensible free sound editor. This manual describes GLAME's internal design programming API for the benefit of developers.
More recent versions of this document as well as additional information about GLAME might be available via the project's homepage at http://glame.sourceforge.net.
This document is also available as a gzipped PostScript file: http://glame.sourceforge.net/manual-dev.ps.gz
Copyright © 2000, 2001 Richard Günther, Daniel Kobras See Copying, for details.
==How to extend GLAME==
==References==
Permission is granted to make and distribute verbatim copies of this manual provided the copyright notice and this permission notice are preserved on all copies.
Permission is granted to copy and distribute modified versions of this manual under the conditions for verbatim copying, provided also that the sections entitled “Copying” and “GNU General Public License” are included exactly as in the original, and provided that the entire resulting derived work is distributed under the terms of a permission notice identical to this one.
Permission is granted to copy and distribute translations of this manual into another language, under the above conditions for modified versions, except that this permission notice may be stated in a translation approved by the Free Software Foundation.
The filter subsystem has three independent APIs, one for the filter registry, one for filter programming and one for using the filters and connecting them to so called filter networks.
If you have any questions related to this document, please quote the relevant lines and send your comments per e-mail to glame-devel@glame.sourceforge.net (c/o richi).
The filter subsystem introduces the concepts of filters and networks of filters. The basic idea is to have streams transporting arbitrary data with attached properties floating through a network. This network comprises of so called filters which are connected to each other through filter pipes. The filters may modify the data in the streams and its properties. A filter can even merge two or more streams into one stream or split one stream into any number of streams containing the same data and the same properties by reference. To tune the operations on the stream each filter may be assigned a number of parameters. Each filter pipe can be assigned a number of parameters at both ends, too.
The API from the design philosophy standpoint is a cloning objects
out of another one. There is one main object, the filter_t
which contains filter_port_t
s through which filters get
connected using filter_pipe_t
s. A filter_t
can be
used both as container for other filters and as working part of another
group of filters, called filter network.
This means that you can set up a network of filters, and in turn convert this
filter network into an abstract filter which you can use in another filter
network by instantiating it.
The filter_t
object represents an instance of a filter. A filter
generally consists of a set of ports, parameters and child filters. Filters
on the same level in the hierarchy may be connected using pipes. Filters
are accessible to the user as plugins as soon as they are registered as
such. Plugins are used as reference instances from which actual filters
are cloned.
The following functions, often implemented using macros, are available
to obtain parts of the filter_t
object.
Using this function you get access to the filters name which is used to uniquely identify it among the childs of its parent filter.
Using this function you get access to the filters parameter database. You will need it to add, remove or query filter parameters using the API provided by the filter parameter subsystem.
Using this function you get access to the filters port database. You will need it to add, remove or query filter ports using the API provided by the filter port subsystem.
Using this function you get access to the filter scope signal emitter. Through this emitter you can receive signals sent out by the components of this filter. You can use the glame signal API to register signal handlers.
Using this function you get access to the filters property database. You will need it to add, remove or query filter properties using the API provided by the
filter_set_property()
andfilter_get_property()
functions.
Use this function to obtain the number of child filters associated with the filter f.
A filter in a set of connected filters may be in an inconsistent state, so an error number and string are provided to notify the outer world. Also macros to set and query this state are provided.
The filter error number returned by this function represents the actual state of the filter and is usually zero which means no error. For more information refer to the
filter_errstr()
function.
Using this function you get access to a human readable error string of the filter.
If this function returns a non-zero value the filter is in an inconsistent state.
Flags the filter with an error and the message msg.
Clears a previously set error condition and marks the filter as in a consistent state.
To query the role of a filter in a simple manner the following macros will help you. Others are defined, but the ones mentioned here are the only ones you are supposed to use.
Returns a non-zero value, if this filter instance is registered as a plugin.
Returns a non-zero value, if this filter instance is a child of another filter instance.
Returns a non-zero value, if this filter instance has childs.
Returns a non-zero value, if this filter instance does not have childs.
Returns a non-zero value, if this filter instance is busy.
To actually create a filter instance, to delete such or register it as a plugin the following functions are provided.
To create a new filter instance either the existing instance template is cloned, or in case
NULL
is provided a minimal one is created from scratch. The new filter instance is returned on success,NULL
on error.
The other way to create a new filter instance is to instantiate a previously registered filter instance – a plugin. The new filter instance is returned on success,
NULL
on error.
This function deletes a filter instance and all its sub-objects such as ports, parameters, pipes and child filters.
Sets the property named label to the value value. Properties of filters are for free use by the user and contents are saved/restored by
filter_to_string()
. Returns 0 on success, -1 on error.
Returns the value of the property named label or
NULL
, if no such property does exist.
This function tries to associate the given filter instance with the provided plugin. On success, zero is returned, -1 on error.
filter_to_string()
saves the current state of the filter into a string and returns a pointer to it. NULL is returned on error. You have tofree()
the returned string later. Use this function to save and potentially recreate a constructed network of which this filter is the container. The string representation is executable scheme code.
This function inserts the filter instance f into the (now turned into a network) filter instance net using the name name which you can use later to query the inserted filter out of the network. If name is not unique inside the network it gets adjusted. Returns 0 on success and -1 on error.
This function removes the filter instance f from its network thereby breaking all connections to/from it. Returns 0 on success and -1 on error.
This function expands the network f which has to be a member of a network itself thereby emptying the network and moving all nodes up one level. Returns 0 on success and -1 on error.
Collapses all nodes in the
NULL
terminated array of filters into a newly created network which is added using name to the parent of the nodes. Returns the created network on success and NULL on error.
Queries the network net for a filter uniquely named name and returns that instance. Returns
NULL
, if there is no such named instance inside the network.
You can iterate through all filter instances inside the network net using the following iterator (which acts like a for statement with the second parameter as running variable). Note that you may not delete instances in this loop!
Both filter ports and filter parameters are organized using databases which can store arbitrary key/object pairs. Keys have to be unique strings and objects can be queried specifying the right key. Also usually an iterator is provided to iterate through all items in the database.
All databases are homogenly typed, i.e. only one kind of object can be
stored in a database. For each such special database a database type
exists (filterparamdb_t
and filterportdb_t
) with the
corresponding object types (filter_param_t
and filter_port_t
).
In addition to these first class object databases there are databases
which store strings – the property databases which exist in each of
plugin_t
, filter_param_t
and filter_port_t
.
For general notes on how to access the databases see GLAME Database Interface.
The filter_param_t
object defines and contains a parameter used
at the filter and the filter pipe scope. To access parts of its structure
the following functions are provided.
Access the parameters label as used in the database.
Access the filter the parameter or its pipe is attached to.
Gets you access to the parameters signal emitter through which all signals are propagated upwards.
If you know that the parameter is attached to a database embedded into a filter pipe and you even know the end to which it is attached, you may use this functions to get access to this pipe.
Get a generic pointer to the actual value of the parameter.
To access a parameter value of a specified type the following helpers are provided. First check the actual type of the parameter, then use this non-typechecking functions.
Get the integer value of a long typed parameter.
Get the string value of a string typed parameter.
Get the float value of a double typed parameter.
As nearly every object, the filter parameter object has a property database whose elements you may access or modify using the following functions.
Get the value of the property stored in the parameters property database using the specified label.
Set or add the property label to the provided value. Returns 0 on success, -1 on error.
To define parameters and to set their values the following functions are provided.
To change the value of a parameter use the following function. Note that on a successful change 0 is returned and a GLSIG_PARAM_CHANGED signal is emitted. -1 is returned on an error such as memory shortage or a rejected change by the set() method of the param.
These functions are simple type-safe wrappers around the
filterparam_set()
function.
As filterparam_set() the following function tries to set the parameters value, but this time using the value encoded in the provided string. This is the counterpart to the
filterparam_to_string()
function.
To generate a string representation of the parameters value use the following function. The returned string has to be freed by the caller. NULL is be returned on error.
Redirects parameter set/query operations (by copy!) to the specified parameter. Note that this is only useful for redirections of externally visible parameters from a macro filter to one of its direct cilds. Returns 0 on success, -1 on error.
To add a new parameter (i.e. define it) use the following function through which you specify the parameters label, its type and its default value (see below for some convenience wrappers). Also any number of key/value pairs may be optionally specified and are stored into the parameters property database. You have to "finish" the property list by a
FILTERPARAM_END
argument even if you did not specify any property.
To ease the use of the
filterparamdb_add_param()
function with respect to specifying the default parameter value, the following wrappers are provided which take a typed fourth parameter. Nothing else changes.
To query a parameter out of the filter parameter database use the following function. If
NULL
is returned, the parameter does not exist.
To delete a parameter use the following function. If the paramter does not exist, nothing is done.
You can iterate through all parameters of a database using the following iterator (which acts like a for statement with the second parameter as running variable). Note that you may not delete parameters in this loop!
To just query the number of parameters stored in a parameter database use the following function.
Returns the label of the specified port.
Returns the type of the specified port. Supported port types are
FILTER_PORTTYPE_ANY
,FILTER_PORTTYPE_SAMPLE
andFILTER_PORTTYPE_FFT
.
Using this function you get access to the ports parameter database which is used to initialize the pipe parameter database on the ports end.
Returns 1, if the specified port is an input port, or 0, if this is not the case.
Returns 1, if the specified port is an output port, or 0, if this is not the case.
Gets you access to the ports signal emitter through which all signals originating from any connected pipe are propagated upwards.
Returns the filter to which the specified port is attached to.
Gets the value of the property specified by label out of the ports property database or
NULL
, if there is no such property.
Sets the property label to the specified value. Returns 0 on success, -1 on error.
Returns a connected pipe or
NULL
if no pipes are available. Use this in conjunction withfilterport_next_pipe()
to iterate through all connected pipes.
Returns the next connected pipe after pipe of the specified port. Returns
NULL
, if no further pipe is available.
A different way to iterate through all available pipes is using this iterator. But you may not delete pipes while iterating.
Redirects connections to this port to another port. Useful only for redirecting externally visible ports of a macro filter to one of its direct childs. Returns -1 on error, 0 on success.
The API which handles defining/setting/querying ports. All this is done
using a filter port database handle, which you can get using
filter_portdb()
.
To add a new port (i.e. define it) use the following function through which you specify the port label, its type and flags. Also any number of key/value pairs may be optionally specified and are stored into the ports property database. You have to "finish" the property list by a
FILTERPARAM_END
argument even if you did not specify any property.
To query a port out of the filter port database use the following function. If
NULL
is returned, the port does not exist.
To delete a port use the following function. If the paramter does not exist, nothing is done.
You can iterate through all ports of a database using the following iterator (which acts like a for statement with the second parameter as running variable). Note that you may not delete ports in this loop!
To just query the number of ports stored in a port database use the following function.
The filter pipe object represents a connection between two filter port objects.
Using this function you get access to the pipes signal emitter. See the glsignal manual for instructions on what to do with this.
Query the parameter database attached to the source end of the pipe.
Query the parameter database attached to the destination end of the pipe.
Sets the pipe type to sample and the sample pipe properties to the specified values.
For a sample typed pipe you have to query the pipes properties using these functions. The properties are the sample rate and the horizontal angle of the stream.
Sets the pipes type to FFT and the FFT pipe properties to the specified values.
For an FFT typed pipe you have to query the pipes properties using these functions. The properties are the sample rate and the horizontal angle of the stream. The blocksize and the oversampling factor specify the FFT.
Connect the two ports source and dest with a pipe, returns the created pipe on success, or NULL on error.
Breaks a previously established connection and deletes the associated pipe.
Constructing filter networks and reusing them as filters is one of the
powerful tools of GLAME. In this section you learn how to use networks
of filters you have constructed (see the previous section on how to
do this, look for filter_add_node()
and filterport_connect
).
First there are operations to actually do work using a filter network. They usually will launch lots of threads and while a filter network is operating you may not modify its structure.
filter_launch()
asynchronously starts the init phase of a previously constructed filter using a set of filter threads.filter_launch()
returnsNULL
if there were any problems launching the filter or a handle on success. Errors in the initialisation process of the filters'f()
methods are not reported byfilter_launch()
but can instead be obtained by usingfilter_wait()
orfilter_start()
. Processing of the data is not started untilfilter_start()
is called. You need to specify a hint for the buffersize used by the network.
This frees the handle returned by
filter_launch()
if you dont need it anymore.
With
filter_start()
you can start processing data on a previously launched filter. The function returns -1 if any error occured in the process of starting or in the filter threads.
filter_wait()
waits for a previously launched filter to finish processing.filter_wait()
returns 0 if the filter terminated regularly, and -1 if there were any errors, either in waiting for the filter or in processing the filter.
filter_is_ready()
queries if the network has finished processing. Instead offilter_wait()
this does not block. Returns 1, if the network has finished, 0 if it is still operating, -1 on error.
filter_terminate
kills a previously launched filter. It doesn't wait for it to finish processing all data.
The filter programming API consists of functions to receive, create and forward buffers, of functions to access the connections made to the filters ports, and of functions to get and set the filters parameters. The filter programming API also defines the semantics of the methods provided by the filters itself as they are used by the filter network. The filter programming API is designed to be thread safe.
Let's start with defining the semantics of the methods a filter and
its subobjects (ports and parameters) can provide.
The methods are stored in the operated on structures, respectively
filter_t
, filter_port_t
, filter_param_t
, but only
the f()
method inside the filter_t
structure is
mandatory. You have to set all other methods explicitly—sane defaults are
provided for them.
f()
is the main method of every filter, it's the only mandatory method.f()
does the filter work, i.e. it gets launched as thread once the filter starts operating. See below for what to do in this method. You have to return -1 if you don't like anything of the setup, which will terminate the whole network, or 0 if everything was ok and you are finished with processing.Required parts of the
f()
method are an initialization section which must be ended by a call of theFILTER_AFTER_INIT
macro and a cleanup section whichFILTER_BEFORE_CLEANUP
has to precede. In the initialization section you may not use any functions which may block on the network. You may return -1 at any point during the initialization indicating an error. Once your code hitsFILTER_AFTER_INIT
, returning 0 is mandatory though. In the init section, use macroFILTER_DO_CLEANUP
for a jump to the cleanup section,FILTER_ERROR_RETURN(const char *)
to return with an error message set to the specified string,FILTER_ERROR_CLEANUP(const char *)
for the equivalent that quits through the cleanup section likeFILTER_DO_CLEANUP
.In the main processing loop (if there is such in your filter) you are required to call
FILTER_CHECK_STOP
from time to time to check for an abort request. If such occured processing is continued afterFILTER_BEFORE_STOPCLEANUP
which should preceedeFILTER_BEFORE_CLEANUP
for obvious reasons. You should in general finish thef()
method by callingFILTER_RETURN
instead of justreturn 0;
to ensure proper return value in case of an error.For further advise see the Filter Tutorial.
init()
gets called by thefilter_add_node()
function after allocating a new instance of a filter (the so called node). You may do anything with the private field of the node and attach signal handlers to the nodes emitter, everything else is strictly private. If you return -1 the node is deleted andfilter_add_node()
will return an error. If everything is ok you should return 0.
connect()
is invoked by thefilterport_connect()
function.connect()
gets called at each connection request to a port, first the correspondingconnect()
method for the output port is called, if that succeeded, the one for the input port. Any side of the connection may reject the connection by returning -1 or accept it by returning 0. You should set up the pipe type and the corresponding fields in the type-specific union using the appropriate macros if being the source end of the pipe. You may modifiy the pipe's source or destination if you are the source or destination, i.e. perform redirections—but be careful. After the connection has been created aGLSIG_PIPE_CHANGED
signal is raised on the new pipe.
The
set()
method can be used to check a value which is to be setted against some conditions. The method is invocated before the change takes place and you may reject the change by returning -1. Returning 0 will do the parameter change and raise aGLSIG_PARAM_CHANGED
signal on the parameter.
Each filter has a signal emitter associated to it through which all
of the signals from its sub-objects are re-emitted. So you may want to add
signal handlers to the filters emitter. Currently there are four signals
that are passed along to the filter signal emitter. These are the
GLSIG_PARAM_CHANGED
, GLSIG_PARAM_DELETED
,
GLSIG_PIPE_CHANGED
and the GLSIG_PIPE_DELETED
signal.
Native signals sent out by the filters are the GLSIG_FILTER_CHANGED
and the GLSIG_FILTER_DELETED
signal.
%% FIXME - describe what to do and not to do in those signal handlers
Now what to do inside the f()
method? Receiving, modifying, creating, and
forwarding streams of data which are grouped into buffers is the answer.
Buffers generally can be in two states - private and shared. A shared buffer is
one with possible greater than one references. Once you send out a buffer possible
new references appear and you have to reconsider the state.
This leads us to the filter buffer API. A filter buffer is obtained using
one of the following functions:
fbuf_alloc()
will allocate a new buffer with space for size bytes. The list parameter is to keep track of allocated buffers for cleanup after failures. You may want to supply&n->launch_context->buffers
for list. This function can returnNULL
if the system is short on memory.
fbuf_realloc()
lets you change the size of the buffer fb to size. This ensures the buffer is private first (as changing the size is only allowed for private buffers) and if needed copies the buffer. This checking and copying is optimized into one function, so you really should use it if you need to. But beware - needingfbuf_realloc()
is a sign of a badly coded algorithm. Its probably only a good idea to use it if it gets called on special cases and makes the code a lot simpler. The only really fast path is shrinking an already private buffer. This function can returnNULL
if the system is short on memory in which case the provided buffer fb is not modified. On success a reference to the realloced buffer is returned and the provided buffer fb looses one reference.
fbuf_get()
receives the next filter buffer from the specified input pipe. It will returnNULL
atEOF
. You have to forward theEOF
mark.fbuf_get()
copes with aNULL
p by just returningNULL
.
All filter buffers are reference counted to allow zero-copy and copy-on-demand
operations. Both fbuf_alloc()
and fbuf_get()
will return with
one reference of the
buffer held. To get additional references or to drop one reference use the
following functions:
fbuf_ref()
will get you one additional reference. A reference will protect the buffer from being modified and from being destroyed. Once the reference count drops to zero, you may no longer access it or any of its contents.fbuf_ref()
does not copy the buffer.fbuf_ref()
ignoresNULL
fbs.
fbuf_unref()
will drop one reference. See above for more about references.fbuf_unref
ignoresNULL
fbs.
To get information on a filter buffer and to access its contents, use the following functions which are actually very fast macros:
fbuf_size()
returns the number of bytes in the filter buffer.fbuf_size()
returns 0 if fb isNULL
.
fbuf_buf()
returns a pointer to the buffer's contents.
If you want to modify a buffer directly rather than reading from a received one
and storing into a freshly allocated one, you have to make the buffer private.
This additional requirement makes copy-on-demand and zero-copy possible. Use
fbuf_make_private()
:
fbuf_make_private()
will return a private copy of the provided buffer which you may modify.fbuf_lock()
will not copy the buffer if you are the sole user of the buffer, i.e. the reference count is one. For aNULL
fbfbuf_mark_private()
returnsNULL
.
To forward a filter buffer you have to hold one reference for each output pipe you send the buffer to. Buffer sending is done using the following function:
fbuf_queue()
queues the specified filter buffer to the specified pipe. One reference gets eaten by this operation.fbuf_queue()
copes with aNULL
p by unref'ing fb.
For extended protocols on top of the fbuf
API have a look into
Working on SAMPLEs.
There are two extended protocol defined at the moment. Those are the sbuf
protocol which is just a very simple SAMPLE
-only
transportation protocol with no fields in the header actually used and
the fft protocol which shares all properties with the sbuf protocol apart
from the pipe type and the pipe properties.
So only the functions with parameters or semantics different from their
fbuf_*
equivalents are listed here. As for the rest, you may assume that
wrappers exist with the appropriate sbuf_*
name but the same parameters
and semantics as described in the fbuf_*
sections.
sbuf_alloc()
allocates a new buffer containing space for size numbers ofSAMPLE
s and assigns the buffer to the filter node n.
sbuf_size()
returns the size of the buffer as number ofSAMPLE
s.
sbuf_buf()
returns a pointer to the buffer containing theSAMPLE
s.
This part of the document is about how to write filters for GLAME. It only covers parts of this at the moment, but will evolve into a decent filter programming tutorial with your help.
When talking about filters in the following sections, we'll make use of various flavours of parameters and properties. Some filters export hooks to dynamically tune their internal behaviour. We call them parameters. Parameters generally are user-settable, while properties are only modified by the filters. But for terminal confusion, properties can be influenced by parameters of course.
We have parameters at three places: the filternode parameters, the pipe source parameters and the pipe destination parameters. In the following we will not make a difference between pipe source and destination parameters, we just call them pipe parameters. Remember though, that each pipe has two sets of parameters, one at the source end and one at the destination end.
As indicated by the name, filternode parameters are local to the filternode, the instance of a filter for which the parameter was defined. A good example for a filternode parameter is the frequency range of a bandpass filter. Pipe parameters are local to a pipe which is an instance of two ports—therefore two sets of parameters exist for a single pipe, each defined at one of the ports. Pipe parameters are local to the pipe, i.e. on automatic ports multiple sets of parameters exist. Pipe parameters are not port parameters. Pipe parameters are commonly used for e.g. gain values.
Pipe properties provide information shared between all data floating through the pipe. Pipe properties remain constant during a run of the filter network. Buffer properties—the information coded into the filter buffer headers—are local to a buffer and are not required to be constant in any way. Sampling rate is probably the most common example of a pipe property. A buffer property for example could be a timestamp.
Buffer property definitions are local to a filter protocol. Filter protocols expose additional per-buffer information to the filters by prepending a protocol specific header to the buffer. Multiple buffer properties can be defined in one header.
In GLAME there are four different kinds of filters. For each you have to care about different methods to ensure correct operation. The first three share (by definition) the following property: they all use nothing but SAMPLEs as their inputs/outputs.
So to begin with the fourth kind of filter, the one operating on types different from SAMPLE as well. For this kind you may need to re-implement every method in the filter struct. If you want to implement such a complex filter, contact glame-devel@lists.sourceforge.net for advice.
The first kind of filters summarizes those which have no input at all.
This is
the class of generators. Generators have to ensure that the pipes
connected to them are assigned the correct type and type parameters.
Therefore they all need to provide the connect_out()
method. If they
do have filter parameters which affect the output type/parameters, they also
have to provide the fixup_param()
method. Example for this class
of filters are the sine filter in waveform.c and the
read_file_f()
filter in file_io.c.
The second kind of filters comprises of those which have no output.
This is the class of sinks. Sinks don't have to care about
all that much. Everything they
need to setup can be done in the main filter method. So usually those
filters do not provide another method apart from f()
. An example for
this class of filters is the drop_f()
filter in
basic.c.
The third kind of filters are the inbetween filters which require
connected input and output channels. Let's call them
effects. Effects have to provide a connect_out()
method, if
they change any of the pipe properties (i.e. what comes out is
different from what comes in, like in a resample case). They also have
to provide the fixup_param()
and the fixup_pipe()
methods
if there are dependencies between the filter or port parameters and the
output pipe properties or between the input pipe properties and the
output pipe properties (different from a one-to-one mapping).
You need the following functions and methods:
<your_filtersetname>_register()
<your_filtersetname>_register()
which returns 0 on success and -1 on any error.
f()
f()
. You need this for each filter contained
in the filter set.
other filter methods
f()
function have to look like? Are there any restrictions?Yes, of course there are!
f()
should begin with checking the current setup for suitability:
look at the parameters and input types/formats. And it should set up
all necessary local things. After this initialisation the macro
FILTER_AFTER_INIT;
has to appear!
Before this macro you may simply return -1
to denote an error, returning with no error is not allowed.
After FILTER_AFTER_INIT;
you should do the actual filter work, i.e.
accept and send data through the ports.
The main part of the filter and the cleanup part (freeing of all
allocated local data, etc.) have to be separated by placing the macro
FILTER_BEFORE_CLEANUP;
.
Neither in the main part, nor in the cleanup part may you just return with
a return value of -1 (i.e. just fail). Instead you have to cleanup yourself,
including sending EOF
s to your output ports. So basically you may fail
in the initialisation part, but nowhere else. The cleanup section must end
with a call to FILTER_RETURN
.
For your convenience, there's a set of macros to ease the task of meeting
those constraints. FILTER_DO_CLEANUP;
is used to jump from the init
section to the cleanup section. It does not make the filter fail, however.
To return with an error, use FILTER_ERROR_RETURN(msg_string);
.
FILTER_ERROR_CLEANUP(msg_string);
makes the filter fail as well but jumps
to the cleanup section first. Obviously, FILTER_ERROR_RETURN();
and
FILTER_ERROR_CLEANUP();
may only be used in the init section.
FILTER_DO_CLEANUP;
can be handy in the main loop.
Another section primitive is the FILTER_CHECK_STOP;
macro which you
should use inside all operating loops to check for external stop, pause or
terminate queries. The corresponding cleanup section after
FILTER_BEFORE_STOPCLEANUP;
is jumped to if a terminating request has to be fulfilled.
Typical code looks like this:
static int myfilter_f(filternode_t *n) { (...) /* Initalisations, setups... */ if (!(bar = (bar_t *)malloc(sizeof(bar_t)))) FILTER_ERROR_RETURN("Not enough memory for bar!"); if (!(baz = open(mydev, myflags))) FILTER_ERROR_CLEANUP("Unable to open device!"); FILTER_AFTER_INIT; while (a < b) { FILTER_CHECK_STOP; (...) /* Do real work */ } FILTER_BEFORE_STOPCLEANUP; FILTER_BEFORE_CLEANUP; if (baz != -1) close(baz); free(bar); FILTER_RETURN; }
You may not use any of the ?buf_*()
functions in the init section (DEADLOCK!!!)
(Well, ?buf_alloc()
is allowed, if you really need it)
For more complex filters which require some sort of backlog of sample data or which modify an input stream the following issues have to be cared about:
You should not allocate a ringbuffer or backlog storage via malloc, neither should you simply copy the data—this is not necessary. In fact it is completely broken. You should instead just keep all the sbuf's around that you need later (of course ref'ing and unref'ing them at the appropriate time)
If your filter in principle would support in-place read-modify-write of
the data you should not allocate new buffers for the output using
sbuf_alloc()
. Instead you should grab the source buffer and do a
sbuf_make_private()
on it taking the returned pointer as the
“new” input buffer which you may modify now and queue as
output.
Once again, as this is a vital point:
If you do any modification of any buffer (including those which you just
allocated privately using sbuf_alloc()
or friends)
you must get the write-enabled buffer by calling sbuf_make_private()
and use the return value as the buffer to be written to!
To clarify the reference counting and locking issues, in the following several valid and invalid example uses of the API are given. They are valid for each of the filter buffer protocols such as the sbuf protocol.
Valid just-forward buffers from input to output:
buf = fbuf_get(in); fbuf_queue(out, buf);
This is valid because fbuf_get()
will get us a reference on the filter
buffer and fbuf_queue()
eats it, i.e. the reference gets forwarded, too.
Invalid attempt to forward a buffer to two outputs:
buf = fbuf_get(in); fbuf_queue(out1, buf); fbuf_queue(out2, buf);
This is invalid because you don't have any reference left after the
first fbuf_queue()
, i.e. there is no reference you can forward to the
second fbuf_queue()
.
Valid but possibly ineffective just-forwarding:
buf = fbuf_get(in); fbuf_ref(buf); fbuf_queue(out, buf); fbuf_unref(buf);
While being valid, this example illustrates ineffective use of references.
Since you don't need to touch the buffer after fbuf_queue()
,
you don't need to get an additional reference and neither drop it again
afterwards. Such use will cause a fbuf_make_private()
in the
destination filter to potentially copy the buffer while a perfectly valid
zero-copy operation was possible.
Invalid modifying and forwarding of a buffer:
buf = fbuf_get(in); fbuf_ref(buf); fbuf_buf(buf)[0] = 1; fbuf_unref(buf); fbuf_queue(out, buf);
This is invalid as the additional fbuf_ref()
does not provide you with
a private modifable buffer, but just ensures that nobody else does
write to or destroy the buffer (which one can't anyway as you are
holding a reference already—the one got by fbuf_get()
).
The correct solution is to do:
buf = fbuf_get(in); buf = fbuf_make_private(buf); fbuf_buf(buf)[0] = 1; fbuf_queue(out, buf);
For now look into src/hash/glsimd.h
if you can find useful
streamed (SIMD) operations you can use. Those usually exist in
efficient assembler versions.
The GLAME conversion layer is meant to provide a uniform and efficient API for conversion of audio data between different formats. It will mainly be useful to the various i/o plugins, i.e. plugins doing audio i/o, file i/o, swapfile i/o and the like. The conversion layer is capable of interleaving and de-interleaving audio streams. It also performs endianness and type conversions of individual samples. The user has to supply information on the layout of source and destination data, and the conversion layer will internally try to find the most optimized conversion path. Use of extended features of current hardware like 3Dnow, ISSE, or AltiVec is completely hidden from the caller.
This documentation describes the externally visible API the conversion layer presents to its callers. If you are about to write a new i/o plugin for GLAME, you should probably go on reading. If you want to hack up another optimised conversion routine for the processor of your choice, have a look at the source instead.
Send comments on the conversion layer or this documentation to glame-devel@glame.sourceforge.net (c/o nold).
All types and methods used by the GLAME conversion layer are prefixed
by gcv_
. Prototypes can be obtained via
#include <glame_conv.h>
As the GLAME conversion layer de facto presents an ABI to third-party plugins, it uses its own version scheme to denote changes in its interface structure. The version comprises of two numbers, generation and revision. The revision is incremented whenever an additional feature is added that doesn't break existing users. Incrementing the generation count indicates a change in interface layout that is not backward-compatible.
To obtain the version numbers of the conversion layer, call
to get generation and revision respectively.
This documentation describes the GLAME conversion layer interface generation 1, revision 0.
The GLAME conversion layer so far only handles tightly packed streams of audio data, i.e. padding is not supported yet. Furthermore, if there are multiple streams of audio data, each stream must carry the same number of interleave channels. Sample properties have to be equal among all data as well. Therefore a chunk of audio data is described by the following properties:
For example, a typical stereo chunk of the form ABABABAB... is described as one stream, two channels interleaved. Sample properties might be width 16 bit, type unsigned integer, little endian. (That's the most common layout of Wave/PCM files actually.)
Users of the conversion layer have to specify the layout of the source data, as well as the desired target layout. This is done using the following methods.
Gets an uninitialised layout handle, or drops all ressources associated to a valid layout handle, respectively.
Sets the number of individual audio streams on layout to num_stream. layout needs to be a valid layout handle returned by
gcv_get_layout()
. -1 is returned on error, 0 on success.
Sets the number of interleaved audio channels per stream. layout needs to be a valid layout handle returned by
gcv_get_layout()
. -1 is returned on error, 0 on success.
Sets the width of a single audio sample on layout to width bits. layout needs to be a valid layout handle returned by
gcv_get_layout()
. -1 is returned on error, 0 on success.
Sets the type of a single audio sample on layout to type. layout needs to be a valid layout handle returned by
gcv_get_layout()
. -1 is returned on error, 0 on success.gcv_type_t
is defined in glame_conv.h and may be one of
GCV_TYPE_INT
- for integer values;
GCV_TYPE_UINT
- for unsigned integer values;
GCV_TYPE_FLOAT
- for (signed) floating point values.
Sets the endianness of a single audio sample on layout to endian. layout needs to be a valid layout handle returned by
gcv_get_layout()
. -1 is returned on error, 0 on success.gcv_endian_t
is defined in glame_conv.h and may be one of
GCV_BIG_ENDIAN
GCV_LITTLE_ENDIAN
GCV_NATIVE_ENDIAN
The latter describing that the data is formatted in a machine's native endianness, which is determined at compile time.
Once layout of source and target data are set up, a set of conversions between the two of them has to be found. This step is performed transparently to the user when the layouts are registered with the conversion layer.
Gets a new handle on a conversion from source to target, or drops all associated ressources respectively.
gcv_get_conversion
returns a valid handle on success, orNULL
on error.
The conversion layer operates from memory to memory only. Writing to or
reading directly from files may be achieved via mmap()
. Sockets are
currently unsupported and must be handled via bounce buffers. This limitation
might be dropped in future versions. Internally, bounce buffers are only
allocated when necessary, i.e. if source and target layout match, the input
buffer is passed on unmodified, so there's no need to special case in
calling code.
Converts data from in to out according to previously registered layout settings defined via cv. spc is the number of samples per channel to be converted. in is an array of pointers to the input data streams. The array's size has to match the number of streams given in the source layout. out is an array of pointers to memory locations that shall be filled with the converted data. The array's size has to match the number of streams given in the target layout. If out is
NULL
, the target buffers will be allocated internally bygcv_do_conversion
, and a pointer to aNULL
-terminated array of pointers to the target buffers is returned. Calling code is responsible to free all buffers and the array itself in this case.gcv_do_conversion
returnsNULL
if an error was encountered, or a pointer to the array of pointers to the target buffers on success.
If you have any questions related to the covered (or uncovered but related) topics in this document, please quote the questionable part of this document and send the questions per e-mail to glame-devel@glame.sourceforge.net (c/o richi).
This is the documentation for the swapfile subsystem of GNU/Linux Audio Mechanics (GLAME). Swapfile was designed not specifically for GLAME but with the idea of storing of and operating on large datasets, specifically audio streams.
Swapfile provides a store for multiple independent sets of (not necessarily) one-dimensional data. Swapfile supports transparent and unlimited undo and redo of all operations.
The swapfile is modeled after a unix filesystem with some additional
features and some restrictions. First, the namespace of the filesystem
is flat, i.e. there are no directories. And the namespace is rather
corse, a file is identified by a unique number, a long
. Also
unlike files on normal filesystems a swapfile file cannot be memory
mapped without restrictions on size and offset. This is due
to the advanced features which are the capability to share parts of the
file with other files in a copy on write manner and the
sw_sendfile
operation which allows cutting, inserting and
overwriting one file with the contents of another file without actually
moving any data. Because of this features the internal structure of a
file cannot consist of a set of equally sized and aligned blocks, it
rather consists of a sequence of randomly sized clusters. The memory
mapping restriction is such that only a complete cluster can be mapped.
So let's jump into listing the functions of the swapfile API and document the semantics.
To initialize the swapfile subsystem you have to open an existing
swapfile which in turn you may create using either the gmkswap
utility or the swapfile_creat
function. After finishing operation
you should close it again using swapfile_close
.
swapfile_open()
opens the swapfile with the specified name and the specified flags (no flags are defined yet, use 0). Returns -1 if it was not possible to open the swapfile or the swapfile is in an inconsistent state.
swapfile_close()
closes the swapfile and updates the metadata state of the on-disk version of the swapfile. This puts the swapfile into a consistent state.
Tries to create an empty swapfile on name of the specified size.
Checks the swapfile on name for consistency and tries to correct all errors. Useful in case of an unclean swapfile or with force specified as 1 rather than 0. Returns 0 on success and -1 on error (which usually means non correctable errors).
Open the (flat) swapfile directory for reading. The stream is positioned at the first file. Like opendir(3), but without directory specification for obvious reason.
As the namespace is rather simple the equivalent to readdir(3) is just returning the names, no directory entry. Anything else is like readdir(3). If no further entries are available, -1 is returned.
Open a file like open(2) - flags can be
O_CREAT
,O_EXCL
,O_RDWR
,O_RDONLY
,O_WRONLY
with same semantics as open(2). Returns a file descriptor on success, -1 on error.
Changes the size of the file fd like ftruncate(2).
Tries to copy count bytes from the current position of in_fd to the current position of out_fd (updating both file pointer positions). The actual number of copied bytes is returned, or -1 on an error.
Two different modes are supported (may be or'ed together):
SWSENDFILE_INSERT
inserts into, rather than overwrites or extends the destination file,SWSENDFILE_CUT
removes copied data from the source file rather than leaving it unmodified. The destination file descriptor may beSW_NOFILE
, in that case no data is actually written (useful withSWSENDFILE_CUT
).
Update the file pointer position like lseek(2).
Like read(2), read count bytes from the current filepointer position to the array pointed to by buf.
Like write(2), write count bytes from buf starting at the current filepointer position.
Obtain information about the file - works like fstat(2), but with different struct stat. Also included is information about the actual (file pointer position, see
sw_lseek
) cluster which can be mapped usingsw_mmap
.struct sw_stat { long name; /* file name */ size_t size; /* file size in bytes */ int mode; /* active protection */ off_t offset; /* current file pointer position */ off_t cluster_start; /* start of current cluster */ off_t cluster_end; /* end of current cluster */ size_t cluster_size; /* size of current cluster */ };
Maps the actual (file pointer position, see
sw_lseek
andsw_fstat
) cluster into memory with parameters like mmap(2) - no size or offset need to be specified as they are determined by the actual cluster offset and size.
This is the transaction API which can be used to implement undoing and redoing operations. For operations to be transactioned, i.e. recorded on execution those operations need to be transaction aware. See the section on TXN Programming Interface on how to make your own operations transaction aware.
All transactions have to be named by an unique transaction id. A new
transaction can be started by txn_begin
which will give you
a new transaction id to which subtransactions can be attached. So usually
any transaction aware operation gets such parent transaction identifier
as argument. Note that ending a transaction via txn_end
does
not free the transaction. This is because you want to be able to use
the transaction for undoing or redoing the operations. If you dont
need to do this it is wise to delete the transaction via txn_delete
to free the memory associated with it.
You may miss a txn_redo
function for redoing a transaction. This
is by purpose as redo is just another kind of undo. Because of this
analogy the txn_undo
operation returns a new transaction which
can be used to undo this undo operation, i.e. providing a transaction
which does the redo operation.
Start a new transaction as child of the provided parent transaction (can be
TXN_NONE
if the transaction should be a independend one). Returns a transaction id or -1 on error. On error either the specified parent transaction does not exist, it has already an active child (violates transaction may not cross) or there is insufficient memory to allocate internal data structures.
End the specified transaction. Returns 0 on success and -1 on error. On error either the specified transaction does not exist, it is already ended or it has active child transactions.
Abort the specified transaction thereby aborting active child transactions and undoing all previous work.
txn_abort
itself is not undoable. Returns 0 on success, -1 on error which is usually an invalid supplied transaction id or an inactive transaction.
Undo applies the reverse transaction as a new transaction, so
txn_undo(txn_undo(id))
restores state after id, simply deleting the returned transaction id prevents redo, deleting the original id aftertxn_undo
prevents undo after redo.
Delete the specified (inactive) transaction and free all memory associated with it. Returns 0 on success and -1 on error which means you have supplied either an invalid or an active transaction.
Abort all active and delete all inactive transactions. This is mainly for cleanup purposes before program end or after crash. Note that this is not thread-safe by design, but you have to ensure proper locking yourself or hope to be lucky... (which is usually ok in case of normal program termination).
Well, for now you have to learn by reading existing transaction aware code which boils down to the swapfile subsystem. Well, for the impatient I have at least the following very brief tutorial cut&pasted from the txn.h headerfile:
struct my_txn_op { struct txn_op op; ... data to undo/delete my transaction }; int my_operation(txnid_t parent_tid, params...) { struct my_txn_op *op = malloc(sizeof(struct my_txn_op)); txnid_t my_tid; ... initialization stuff for my_operation my_tid = txn_start(parent_tid); ... my operation op->op.undo = my_txn_undo; op->op.del = my_txn_delete; op->... stuff to be able to undo/delete the transaction txn_finish(my_tid, &op->op); ... stuff }
But there are at least two useful functions in the transaction API that will help you making your operations transaction aware.
Finishes a transaction by providing the necessary undo and delete operations. Will fail if child transactions are there. The transaction will be ended as in
txn_end(txn_start(
id))
.
Finishes a transaction using an implementation that throws an exception, if the undo operation is required. The supplied message is written to stderr and a SIGSEGV will be raised.
This one I consider not that important, so either you have to consult the headerfile for documentation or wait for me to have lots of spare time.
The project structure management including track metadata and coupling with the swapfile/filter API.
The structure is build out of gpsm-items which can be either groups (gpsm_grp_t) or swapfile files (gpsm_swfile_t). Think of the structure as of a tree with leafs all being swapfile files (or equivalent types, if they ever will appear) and the interior nodes being groups. Groups (generic: items) have horizontal (samples) and vertical (tracks) extend, items have horizontal (samples) and vertical (track) positions.
Usage of the gpsm API is by the various GUI widgets that handle the swapfile and the filters such as the swapfile gui and the timeline gui. Also use by the scheme scripts is the preferred way to work on the swapfile. Consistent updating of all interfaces can be realized by registering appropriate signal handlers to the gpsm-items emitter. Signals are sent out by all gpsm API functions and have to be sent out manually by everyone operating on the swapfile/gpsm-items _not_ using the gpsm API functions.
Every item in the project tree has information of its parent, a signal emitter where you can attach handlers to, a label and last but not least a 2D position and a 2D size. Those elements can be accessed using the following functions:
Returns the parent item of the provided item or NULL, if the item is the root of a gpsm tree.
Returns the signal emitter of the provided item.
Returns the label of the provided item. Note that the result is strictly read-only.
These functions return the actual position (horizontal or vertical) or the actual size (horizontal or vertical) of the provided item. Both horizontal position and size are measured in samples. Both vertical position and size are measured in tracks. Positions and sizes are positive or zero.
Common operations you can carry out on an items are item destruction, insertion and removal, setting of the label and flattening.
Destroys the provided gpsm item. The item is first removed from its group, if necessary and then destructed in child-first order. Appropriate signals are send out for this operation, namely
GPSM_SIG_ITEM_REMOVE
(and theGPSM_SIG_GRP_REMOVEITEM
signal to the items group) if removal is required andGPSM_SIG_ITEM_DESTROY
.
Inserts the specified gpsm-item into the group at the specified position. Random (non-overlapping) h-/v-positioning is performed if you pass -1 to h-/v-position. May fail, as overlapping items are not allowed. Returns 0 on success and -1 on error. Appropriate signals are send out for this operation, namely
GPSM_SIG_GRP_NEWITEM
to the group.
Removes the specified gpsm-item from its current group. The items position will be (0,0) after this operation. If the item was not member of a group this is a NOP. Appropriate signals are send out for this operation, namely
GPSM_SIG_ITEM_REMOVE
and theGPSM_SIG_GRP_REMOVEITEM
signal to the items group.
Updates the label of the specified gpsm-item. Note that this will cause a
GPSM_SIG_ITEM_CHANGED
signal to be send out.
Flattens a gpsm item, that is, out of a possible deep tree of horizontally and vertically spreaded swfiles make a set of vertically aligned (read: starting at position zero and ending at the maximum position) swfiles. Returns a new group with new swfiles, one for each vertical track. The data is COWed from the original tree. In the special case of providing a swfile as item a new group with a COW copy of this item is returned (without paying attention to hposition of the item). On failure NULL is returned. Note that this feature greatly simplifies operations such as play and export (i.e. where you only want to _read_ from the files).
Group items form the back of the gpsm tree, they contain an arbitrary number of items positioned relative to their parent. You can access a groups items via the following generic iterators:
You can iterate through all items contained in the specified group using the iterator (which acts like a for statement with the second parameter as running variable). Note that you may not delete instances in this loop!
You can iterate through all items contained in the specified group using the iterator (which acts like a for statement with the second parameter as running variable). You may delete the actual item within this iterator, but you have to specify another dummy pointer to use for this to work.
There are a few operations specialized to work on group items only, namely group creation and tree searching.
Creates a new empty gpsm group with the specified label. You have to insert it into a gpsm group yourself. Returns a gpsm group or NULL on error.
Find a gpsm-grp by label in the subtree specified by root. The search is started at the item start (or at the root, if you specify NULL). You can find all occurences by specifying the previous result as start. Returns a gpsm-grp, if found or NULL, if not.
Find a gpsm-swfile by label in the subtree specified by root. The search is started at the item start (or at the root, if you specify NULL). You can find all occurences by specifying the previous result as start. Returns a gpsm-swfile, if found or NULL, if not.
Find a gpsm-swfile by filename in the subtree specified by root. The search is started at the item start (or at the root, if you specify NULL). You can find all occurences by specifying the previous result as start. Returns a gpsm-swfile, if found or NULL, if not. */
Find a gpsm-swfile by vposition in the subtree specified by root. The search is started at the item start (or at the root, if you specify NULL). You can find all occurences by specifying the previous result as start. Returns a gpsm-swfile, if found or NULL, if not.
The swfile items are the leafs of the gpsm tree, they are the connection between the backing store and the different views. Swfiles provide a swapfile filename, a samplerate and a position which can be accessed using the following functions:
Returns the swapfile filename of the gpsm swfile item.
Returns the samplerate of the gpsm swfile item.
Returns the stream position of the gpsm swfile item.
There exist a quite large number of specialized operations on swfile items, including file creation, copying and linking, setting of the data and notifying gpsm and its users about changes to the swapfile file data.
Creates a new spare swapfile to operate with. You have to insert it into a gpsm group yourself. Returns a gpsm-swfile or NULL on error.
Creates a new swapfile with contents from the swapfile specified by the gpsm-swfile. Returns a gpsm-swfile or NULL on error.
Creates a new gpsm-swfile with the swapfile of the specified gpsm-swfile as backing store. Returns a gpsm-swfile or NULL on error.
Updates the samplerate and/or position of the specified gpsm-swfile. Note that this information is per gpsm-swfile, not per swapfile! Note that this will cause a
GPSM_SIG_ITEM_CHANGED
signal to be send out.
After you've done an operation on a swapfile such as modifying or cutting/inserting via
sw_sendfile()
you have to notify the GPSM about this change. The swfiles sizes will be updated and appropriate signals will be send out. Note that it is generally better to make changes to a swapfile through gpsm functions (which dont exist at the moment...).
If you have done changes to a swapfile which you cannot (or would not like to) specify explicitly you can tell gpsm and its users to start from scratch with this file. Note that this is a costly operation and it is generally better to use the finer grained notify functions above.
There are a vast number of signals send out by the gpsm subsystem which
are described syntactically and semantically here. Signal names are
constructed with the prefix GPSM_SIG
the infix denoting the type
of object and the type of the first argument it is sent to such as
ITEM
, SWFILE
and GRP
and a suffix denoting the
semantics. A list of available signals follows.
The first group is the signals sent to all type of items (thus the
ITEM
infix).
GPSM_SIG_ITEM_CHANGED
has one parameter, the gpsm-item. The
signal will be sent out after a change to any of the items data
elements. Note that GPSM_SIG_ITEM_CHANGED
delivery is not
suppressed, if the change has a semantically more specific signal like
one of the GPSM_SIG_GRP_*
or GPSM_SIG_SWFILE_*
signals. Also a GPSM_SIG_ITEM_CHANGED
signal is sent out on item
re-position.
GPSM_SIG_ITEM_DESTROY
has one parameter, the gpsm-item. The
signal will be sent out before item destruction. Note that
items attached to a group will generally recieve a
GPSM_SIG_GRP_REMOVEITEM
signal before destruction, i.e.
gpsm_item_destroy()
will remove them first, then destruct.
GPSM_SIG_ITEM_REMOVE
has one parameter, the gpsm-item. The signal
will be sent out before item removal from its group. The
GPSM_SIG_GRP_REMOVEITEM
signal will be send out to the group
after this signal.
The second group is the signals sent to groups only (thus the GRP
infix).
Both GPSM_SIG_GRP_NEWITEM
and GPSM_SIG_GRP_REMOVEITEM
have
two parameters, the gpsm-grp as the first and the gpsm-item to be
inserted/removed as second one. The GPSM_SIG_GRP_REMOVEITEM
signal is sent out before item removal and after the item recieved the
GPSM_SIG_ITEM_REMOVE
signal, the NEWITEM
signal after item
addition.
NOTE: If the actual item is a group it is certainly possible for it to contain children!
NOTE2: You may want to attach/remove signal handlers to the item (and the possible childrens of a group)
The third and last group is the signals sent out to swfiles only (thus
the SWFILE
infix).
The GPSM_SIG_SWFILE_*
have three parameters, the first is the
gpsm-swfile itself, the second is a long position, the third a long size
specifying position and size of the inserted / cutted / changed data in
samples. This signal is sent _after_ the actual operation was carried
out on the swapfile.
GPSM provides an easy way to support undo and redo at the scope of a GPSM group (or a single swfile). To be able to do this you need to manually save the state of a group before operating on it, undo and redo then can automatically roll back to these saved states.
The maximum number of saved states can be configured by using the
following function (it is safe to call it before or after gpsm_init()
):
Changes (or just queries, if max < 0) the maximum number of states saved for undo/redo. Returns the actual set value.
The following functions treating with undo and redo are available:
Save the current state of the provided subtree for later undo. Returns 0 on success, -1 on failure.
Returns 1 if undo is pending for the subtree item and can be undone at this point, else returns 0.
Rolls back to the latest saved state of the provided subtree. Returns 0 on success, -1 on error (such as no undo pending or possible). Saves the actual state for later redo.
Rolls back to the latest saved state of the provided subtree. Returns 0 on success, -1 on error (such as no undo pending or possible). Does not save the actual state for later redo.
Returns 1 if redo is pending for the subtree item and can be redone at this point, else returns 0.
Rolls back to the state before the previous undo to the provided subtree. Returns 0 on success, -1 on error (such as no redo pending or possible). Saves the actual state for later undo.
Rolls back to the state before the previous undo to the provided subtree. Returns 0 on success, -1 on error (such as no redo pending or possible). Does not save the actual state for later undo.
Kills off the latest saved state of the provided subtree. Returns 0 on success, -1 on error (no pending undo or redo).
The plugin interface is very simple. There are actually two functions, one to add paths to the existing plugin path list and one to query the handle of a plugin.
This function will add path to the list of paths used to search plugins.
plugin_load()
will try to register all available plugins out of the specified file filename. If the specified file could not be opened or is not a valid plugin file, -1 is returned. On success, that is after registering zero or more plugins, 0 is returned.
This function will return a handle to the plugin with the name name. If the plugin is not already loaded it will be loaded from one of the paths in the path list.
plugin_get()
returnsNULL
if the plugin can't be found or an error occured during its initialization phase.
To manually add a plugin not contained in some shared library you should call the following function.
This function will add a new empty plugin to the database. You should populate its database using the
plugin_set()
function. On success a plugin handle is returned,NULL
is returned on failure.
To access parts of the plugin the following wrapper macros should be used
on the plugin_t
handle.
Using these functions you can set and query key/value pairs either stored through dynamic library symbols or through a little per plugin database.
plugin_query()
returnsNULL
, if there is no information about the specified key. As a general rule of dumb you should start your keywords with a '!' to prevent conflicts with dynamic symbols. Standard keywords are accessible using the definesPLUGIN_DESCRIPTION
,PLUGIN_PIXMAP
,PLUGIN_CATEGORY
and the internally used keywordsPLUGIN_FILTER
andPLUGIN_PARENT
.
To browse through all registered plugins you can use the following function.
plugin_next()
gets you the next plugin in the database or the first one if you supplyNULL
.NULL
is returned if no further plugins are available.
If you want to create a plugin, your dynamic object should contain the
following standard symbols with the described information attached. The
targeted subsystem may require additional defined symbols. Please refer
to the subsystems' documentation for information about those symbol
names and required contents. You should substitute the plugins name for
the plugin
prefix of the symbols to prevent symbol name clashes.
plugin_register
(int (*)(plugin_t *p))
which does everything
necessary to register anything in the plugin to any subsystem.
plugin_set
char *
should contain a list
of additional plugin
names inside the object file seperated
by spaces. You will need a seperate _register
symbol for each
of the specified additional plugin names. Use the PLUGIN_SET
macro to create a symbol like that. Provide two arguments, the first
should be an identifier matching the file name without filename extension,
the second one should be the space seperated plugin list string.
The purpose for this generic small database framework is to be able to have many databases with a very small footprint for their hook and reasonable minimum item size. The item query time is O(N). Note that there is no locking internal to a database - at least no guaranteed one, so you may want to have per database mutexes.
Only one "type" of items may be stored in the database - type is destinguished by the database operations, the copy and the delete operation. These operate on all items and such either need to find about the items type themself or assume equal types.
Note that embedding a gldb_t *
rather than a struct db_ops
*
in the item does allow more flexibility such as a single linked list
implementation or some other tricky use of the db/item framework.
The presented framework is not intended for direct use but rather for use as a basis for own item types with corresponding wrappers to the gldb API.
Still to be documented.
Still to be documented.
The external visible API of a generic database is the following (see the src/hash/gldb.h file):
These function operate on a whole database, respectively initialize an empty database, deletes all items of a database, copies all items from one database to another and tells about the number of items in the database.
These functions operate on a database item, respectively initializing it, deleting it or creating a copy of it.
These functions can be used to add an item with the specified label to the database, remove it out of the database, or to query a database item by specifying the label that was given at addition time.
Using this iterator you can iterate through all items stored in the specified database. You may not remove items while iterating, though.
Two generic specializations exist, the string database and the WORM database. Also the filter parameter database and the filter port database are specializations of the generic GLAME database framework, but they are not covered in this document.
Still to be documented.
Still to be documented.
Generic signals via callbacks (glsig_handler_t
), issuable from a
glsig_emitter. Signal masks are supported, as is a hierarchy of the
signal emitters.
WARNING! No explicit threading support is included, i.e. do your own locking (you have to protect the emitter against concurrent addition and removal of handlers and emit of signals). Also a handler will be invoked in the thread context of the emitter, not in the one which added the handler. If you want to emit signals from real signal handlers you have to ensure yourself that your handlers are signal safe - also they should be reentrant as parallel invocation from different threads is not protected against.
Brief description of the available API follows:
The type of the used callback functions through which signals are supposed to be handled.
Inits an emitter. No signal handlers are attached initially.
Adds a signal handler to the emitter using the specified sigmask and the callback handler. The priv data is stored in the handlers ->priv field which you should access using
glsig_handler_private()
.
Adds a redirector to the emitter. All signals raised from the emitter matching the specified signal mask will be raised again from the dest emitt
Emits the signal sig from the emitter and provides the varargs to the callbacks. Signals are emitted bottom to top in the hierarchy. It is safe to remove your signal handler during execution.
Copies all handlers from one emitter to another - same "private" data, of course. Can return -1 on memory shortage. Redirectors are not copied.
Copies all redirectors from one emitter to another - same "private" data, of course. Can return -1 on memory shortage. Normal handlers are not copied.
Removes and destroyes the specified handler from its emitter.
Removes and destroyes all signal handlers from the specified emitter. Use with care.
Executes the specified signal handler. Usually you dont want to use this, instead use
glsig_emit()
.
Inside signal handlers you may access the passed parameters (which you should know by type and count) with the following macros.
These macros initialize the provided variables with the passed parameters. Beware that passing types with
sizeof(type)
not equal tosizeof(void *)
is not safe on compilers other than gcc.
(glsig_callb_t)
: GLAME Signal Interface*sw_mmap
: File Operationschar
: Invoking data conversionsconnect
: Filter Methodsconnect_out
: Filter Categoriesf
: Filter Skeletonf
: Filter Methodsfbuf_alloc
: Doing Real Workfbuf_buf
: Examplesfbuf_buf
: Doing Real Workfbuf_get
: Examplesfbuf_get
: Doing Real Workfbuf_make_private
: Examplesfbuf_make_private
: Doing Real Workfbuf_queue
: Examplesfbuf_queue
: Doing Real Workfbuf_realloc
: Doing Real Workfbuf_ref
: Examplesfbuf_ref
: Doing Real Workfbuf_size
: Doing Real Workfbuf_unref
: Examplesfbuf_unref
: Doing Real Workfilter_add_node
: The Filter ObjectFILTER_AFTER_INIT
: Main Filter MethodFILTER_AFTER_INIT
: Filter MethodsFILTER_BEFORE_CLEANUP
: Main Filter MethodFILTER_BEFORE_CLEANUP
: Filter MethodsFILTER_BEFORE_STOPCLEANUP
: Main Filter MethodFILTER_BEFORE_STOPCLEANUP
: Filter Methodsfilter_buffer_t
: Doing Real WorkFILTER_CHECK_STOP
: Main Filter MethodFILTER_CHECK_STOP
: Filter Methodsfilter_clear_error
: The Filter Objectfilter_collapse
: The Filter Objectfilter_creat
: The Filter Objectfilter_delete
: The Filter ObjectFILTER_DO_CLEANUP
: Main Filter MethodFILTER_DO_CLEANUP
: Filter Methodsfilter_emitter
: The Filter Objectfilter_errno
: The Filter ObjectFILTER_ERROR_CLEANUP
: Main Filter MethodFILTER_ERROR_CLEANUP
: Filter MethodsFILTER_ERROR_RETURN
: Main Filter MethodFILTER_ERROR_RETURN
: Filter Methodsfilter_errstr
: The Filter Objectfilter_expand
: The Filter Objectfilter_foreach_node
: The Filter Objectfilter_get_node
: The Filter Objectfilter_get_property
: The Filter Objectfilter_has_error
: The Filter Objectfilter_instantiate
: The Filter ObjectFILTER_IS_LAUNCHED
: The Filter ObjectFILTER_IS_NETWORK
: The Filter ObjectFILTER_IS_NODE
: The Filter ObjectFILTER_IS_PART_OF_NETWORK
: The Filter ObjectFILTER_IS_PLUGIN
: The Filter Objectfilter_is_ready
: Filter Networksfilter_launch
: Filter Networksfilter_launchcontext_t
: Filter Networksfilter_launchcontext_unref
: Filter Networksfilter_name
: The Filter Objectfilter_nrnodes
: The Filter Objectfilter_param_t
: Filter Methodsfilter_param_t
: The Filter Parameter Objectfilter_paramdb
: The Filter Objectfilter_paramdb_t
: The Filter Port Objectfilter_paramdb_t
: The Filter Parameter Objectfilter_paramdb_t
: The Filter Objectfilter_pipe_t
: Doing Real Workfilter_pipe_t
: Filter Methodsfilter_pipe_t
: The Filter Pipe Objectfilter_pipe_t
: The Filter Parameter Objectfilter_port_t
: Filter Methodsfilter_port_t
: The Filter Port Objectfilter_portdb
: The Filter Objectfilter_portdb_t
: The Filter Port Objectfilter_portdb_t
: The Filter Objectfilter_propertydb
: The Filter Objectfilter_register
: The Filter Objectfilter_remove
: The Filter ObjectFILTER_RETURN
: Main Filter MethodFILTER_RETURN
: Filter Methodsfilter_set_error
: The Filter Objectfilter_set_property
: The Filter Objectfilter_start
: Filter Networksfilter_t
: Filter Methodsfilter_t
: Filter Networksfilter_t
: The Filter Parameter Objectfilter_t
: The Filter Objectfilter_terminate
: Filter Networksfilter_to_string
: The Filter Objectfilter_wait
: Filter Networksfilterparam_delete
: The Filter Parameter Objectfilterparam_emitter
: The Filter Parameter Objectfilterparam_filter
: The Filter Parameter Objectfilterparam_from_string
: The Filter Parameter Objectfilterparam_get_destpipe
: The Filter Parameter Objectfilterparam_get_property
: The Filter Parameter Objectfilterparam_get_sourcepipe
: The Filter Parameter Objectfilterparam_label
: The Filter Parameter Objectfilterparam_redirect
: The Filter Parameter Objectfilterparam_set
: The Filter Parameter Objectfilterparam_set_double
: The Filter Parameter Objectfilterparam_set_long
: The Filter Parameter Objectfilterparam_set_property
: The Filter Parameter Objectfilterparam_set_string
: The Filter Parameter Objectfilterparam_to_string
: The Filter Parameter Objectfilterparam_type
: The Filter Parameter Objectfilterparam_val
: The Filter Parameter Objectfilterparam_val_double
: The Filter Parameter Objectfilterparam_val_long
: The Filter Parameter Objectfilterparam_val_string
: The Filter Parameter Objectfilterparamdb_add_param
: The Filter Parameter Objectfilterparamdb_add_param_double
: The Filter Parameter Objectfilterparamdb_add_param_long
: The Filter Parameter Objectfilterparamdb_add_param_string
: The Filter Parameter Objectfilterparamdb_delete_param
: The Filter Parameter Objectfilterparamdb_foreach_param
: The Filter Parameter Objectfilterparamdb_get_param
: The Filter Parameter Objectfilterparamdb_nrparams
: The Filter Parameter Objectfilterpipe_delete
: The Filter Pipe Objectfilterpipe_dest
: The Filter Pipe Objectfilterpipe_emitter
: The Filter Pipe Objectfilterpipe_fft_bsize
: The Filter Pipe Objectfilterpipe_fft_hangle
: The Filter Pipe Objectfilterpipe_fft_osamp
: The Filter Pipe Objectfilterpipe_fft_rate
: The Filter Pipe Objectfilterpipe_sample_hangle
: The Filter Pipe Objectfilterpipe_sample_rate
: The Filter Pipe Objectfilterpipe_settype_fft
: The Filter Pipe Objectfilterpipe_settype_sample
: The Filter Pipe Objectfilterpipe_source
: The Filter Pipe Objectfilterpipe_sourceparamdb
: The Filter Pipe Objectfilterpipe_type
: The Filter Pipe Objectfilterport_connect
: The Filter Pipe Objectfilterport_delete
: The Filter Port Objectfilterport_emitter
: The Filter Port Objectfilterport_filter
: The Filter Port Objectfilterport_foreach_pipe
: The Filter Port Objectfilterport_get_pipe
: The Filter Port Objectfilterport_get_property
: The Filter Port Objectfilterport_is_input
: The Filter Port Objectfilterport_is_output
: The Filter Port Objectfilterport_label
: The Filter Port Objectfilterport_next_pipe
: The Filter Port Objectfilterport_nrpipes
: The Filter Port Objectfilterport_paramdb
: The Filter Port Objectfilterport_redirect
: The Filter Port Objectfilterport_set_property
: The Filter Port Objectfilterport_type
: The Filter Port Objectfilterportdb_add_port
: The Filter Port Objectfilterportdb_delete_port
: The Filter Port Objectfilterportdb_foreach_port
: The Filter Port Objectfilterportdb_get_port
: The Filter Port Objectfilterportdb_nrports
: The Filter Port Objectfitlerpipe_destparamdb
: The Filter Pipe Objectfixup_param
: Filter Categoriesfixup_pipe
: Filter Categoriesgcv_conv_t
: Invoking data conversionsgcv_conv_t
: Getting your custom-tailored conversion routinegcv_do_conversion
: Invoking data conversionsgcv_drop_conversion
: Getting your custom-tailored conversion routinegcv_drop_layout
: Describing the data layoutgcv_endian_t
: Describing the data layoutgcv_get_conversion
: Getting your custom-tailored conversion routinegcv_get_generation
: Versioninggcv_get_layout
: Describing the data layoutgcv_get_revision
: Versioninggcv_layout_t
: Getting your custom-tailored conversion routinegcv_layout_t
: Describing the data layoutgcv_set_channels
: Describing the data layoutgcv_set_endian
: Describing the data layoutgcv_set_streams
: Describing the data layoutgcv_set_type
: Describing the data layoutgcv_set_width
: Describing the data layoutgcv_type_t
: Describing the data layoutgcv_version_t
: Versioninggldb_add_item
: The GLAME Database APIgldb_copy
: The GLAME Database APIgldb_copy_item
: The GLAME Database APIgldb_delete
: The GLAME Database APIgldb_delete_item
: The GLAME Database APIgldb_foreach_item
: The GLAME Database APIgldb_init
: The GLAME Database APIgldb_init_item
: The GLAME Database APIgldb_item_t
: The GLAME Database APIgldb_nritems
: The GLAME Database APIgldb_query_item
: The GLAME Database APIgldb_remove_item
: The GLAME Database APIgldb_t
: The GLAME Database APIgldb_t
: The Filter Objectglsig_add_handler
: GLAME Signal Interfaceglsig_add_redirector
: GLAME Signal Interfaceglsig_callb_t
: GLAME Signal Interfaceglsig_copy_handlers
: GLAME Signal Interfaceglsig_copy_redirectors
: GLAME Signal Interfaceglsig_delete_all
: GLAME Signal Interfaceglsig_delete_handler
: GLAME Signal Interfaceglsig_emit
: GLAME Signal Interfaceglsig_emitter_t
: GLAME Signal Interfaceglsig_emitter_t
: Generic operations on itemsglsig_emitter_t
: The Filter Port Objectglsig_emitter_t
: The Filter Parameter Objectglsig_emitter_t
: The Filter Objectglsig_handler_exec
: GLAME Signal Interfaceglsig_handler_t
: GLAME Signal InterfaceGLSIGH_GETARGS1
: GLAME Signal InterfaceGLSIGH_GETARGS2
: GLAME Signal InterfaceGLSIGH_GETARGS3
: GLAME Signal InterfaceGLSIGH_GETARGS4
: GLAME Signal Interfacegpsm_find_grp_label
: The group itemgpsm_find_swfile_filename
: The group itemgpsm_find_swfile_label
: The group itemgpsm_find_swfile_vposition
: The group itemgpsm_flatten
: Generic operations on itemsgpsm_grp_foreach_item
: The group itemgpsm_grp_insert
: Generic operations on itemsgpsm_grp_safe_foreach_item
: The group itemgpsm_grp_t
: The group itemgpsm_grp_t
: Generic operations on itemsgpsm_invalidate_swapfile
: The swfile itemgpsm_item_destroy
: Generic operations on itemsgpsm_item_emitter
: Generic operations on itemsgpsm_item_hposition
: Generic operations on itemsgpsm_item_hsize
: Generic operations on itemsgpsm_item_label
: Generic operations on itemsgpsm_item_parent
: Generic operations on itemsgpsm_item_remove
: Generic operations on itemsgpsm_item_set_label
: Generic operations on itemsgpsm_item_t
: Undo and redo supportgpsm_item_t
: The group itemgpsm_item_t
: Generic operations on itemsgpsm_item_vposition
: Generic operations on itemsgpsm_item_vsize
: Generic operations on itemsgpsm_newgrp
: The group itemgpsm_newswfile
: The swfile itemgpsm_notify_swapfile_change
: The swfile itemgpsm_notify_swapfile_cut
: The swfile itemgpsm_notify_swapfile_insert
: The swfile itemgpsm_op_can_redo
: Undo and redo supportgpsm_op_can_undo
: Undo and redo supportgpsm_op_forget
: Undo and redo supportgpsm_op_prepare
: Undo and redo supportgpsm_op_redo
: Undo and redo supportgpsm_op_redo_and_forget
: Undo and redo supportgpsm_op_undo
: Undo and redo supportgpsm_op_undo_and_forget
: Undo and redo supportgpsm_set_max_saved_ops
: Undo and redo supportgpsm_swfile_cow
: The swfile itemgpsm_swfile_filename
: The swfile itemgpsm_swfile_link
: The swfile itemgpsm_swfile_position
: The swfile itemgpsm_swfile_samplerate
: The swfile itemgpsm_swfile_set
: The swfile itemgpsm_swfile_set_position
: The swfile itemgpsm_swfile_set_samplerate
: The swfile itemgpsm_swfile_t
: The swfile itemgpsm_swfile_t
: The group iteminit
: Filter MethodsINIT_GLSIG_EMITTER
: GLAME Signal Interfaceint
: Versioningplugin_add
: Plugin Interfaceplugin_add_path
: Plugin Interfaceplugin_get
: Plugin Interfaceplugin_load
: Plugin Interfaceplugin_name
: Plugin Interfaceplugin_next
: Plugin Interfaceplugin_query
: Plugin Interfaceplugin_set
: Plugin Interfaceplugin_t
: Plugin Interfaceplugin_t
: The Filter Objectsbuf_alloc
: Main Filter Methodsbuf_alloc
: Working on SAMPLEssbuf_buf
: Working on SAMPLEssbuf_make_private
: Main Filter Methodsbuf_size
: Working on SAMPLEsset
: Filter Methodsstruct sw_stat
: File Operationsstruct txn_op
: TXN Programming Interfacesw_close
: File Operationssw_closedir
: Namespace Operationssw_fstat
: File Operationssw_ftruncate
: File Operationssw_lseek
: File Operationssw_munmap
: File Operationssw_open
: File Operationssw_opendir
: Namespace Operationssw_read
: File Operationssw_readdir
: Namespace Operationssw_sendfile
: File Operationssw_unlink
: Namespace Operationssw_write
: File Operationsswapfile_close
: Initialisationswapfile_creat
: Initialisationswapfile_fsck
: Initialisationswapfile_open
: Initialisationswfd_t
: File Operationstxn_abort
: TXN User Interfacetxn_abort_and_delete_all
: TXN User Interfacetxn_delete
: TXN User Interfacetxn_end
: TXN User Interfacetxn_finish
: TXN Programming Interfacetxn_finish_unimplemented
: TXN Programming Interfacetxn_start
: TXN User Interfacetxn_undo
: TXN User Interfacetxnid_t
: TXN Programming Interfacetxnid_t
: TXN User Interfacetxnid_t
: File Operations