The first thing you must do is figure out the specifics of how you want your plugins to be organized.
It is usually easiest to write a struct
corresponding
to the handle of a plugin descriptor.
The structure may however you want; however, it is
necessary to store somewhere the various buffer pointers
the host has connected to us (via connect_port
).
So here is a typically example, from the ladspa.tgz
package,
filter.c
:
typedef struct {
LADSPA_Data m_fSampleRate;
/* filter-specific data deleted. */
/* Ports:
------ */
LADSPA_Data * m_pfCutoff;
LADSPA_Data * m_pfInput;
LADSPA_Data * m_pfOutput;
} SimpleFilter;
Once you have decided this, you must write the ladspa_descriptor
function. It takes an integer for the plugin index,
and returns the LADSPA_Descriptor
. It's exact implementation is up to
the plugin, but here is a fairly generic code outline:
static LADSPA_Descriptor* the_descriptor = NULL;
LADSPA_Descriptor* ladspa_descriptor(unsigned long index)
{
if (index != 0)
return NULL;
if (the_descriptor != NULL)
return the_descriptor;
the_descriptor = (LADSPA_Descriptor*) calloc(sizeof(LADSPA_Descriptor), 1);
if (the_descriptor == NULL)
return NULL;
... You must fill in the various fields of the_descriptor ...
return the_descriptor;
}
void _fini()
{
if (the_descriptor != NULL)
{
free(the_descriptor);
the_descriptor = NULL;
}
}
The data fields are fairly easy to fill. We describe the function pointers in more detail.
These are the functions which allocate and clear the plugin handle.
This must call malloc to allocate space for a plugin handle.
This should clear the state of the plugin. For example, it is important that calling deactivate, then activate should clear all the buffers.
Here is the simple filter connect_port implemenation:
void
connectPortToSimpleFilter(LADSPA_Handle Instance,
unsigned long Port,
LADSPA_Data * DataLocation) {
SimpleFilter * psFilter;
psFilter = (SimpleFilter *)Instance;
switch (Port) {
case SF_CUTOFF:
psFilter->m_pfCutoff = DataLocation;
break;
case SF_INPUT:
psFilter->m_pfInput = DataLocation;
break;
case SF_OUTPUT:
psFilter->m_pfOutput = DataLocation;
break;
}
}
The run
(and its run_adding
cousin) functions
both take unsigned long SampleCount
as an argument,
which is both the number of audio samples available
as input to each input-audio data port,
and the number of samples to produce for
each output-audio data port.
Of course, control port always have just a single LADSPA_Data
as input or output.
By default run
must operate if
the input and output buffers are at the
same address. However, if your plugin cannot conveniently
do that, just set LADSPA_PROPERTY_INPLACE_BROKEN
in the Properties
field.
In many cases, you are going to add the plugins
output to another buffer after regaining (ie you are mixing).
If the descriptor defines run_adding
you may use that
for some possible speed benefit.
If the descriptor's run_adding
function is NULL
you must be able to use the run
function instead.
You may wish to implement the deactivate
function
to undo any memory allocation done in activate
but
it is often unnecessary, and is usually left NULL
.
But you must implement cleanup
to deallocate the
plugin handle and the buffers it uses.
If you have data associated with a plugin type
(for example a large lookup table), you
will probably need to free it with the _fini
function, which is called for the plugin library
just before it is unloaded.
The process of building plugins is extremely nonportable, but we give commands to compile a LADSPA plugin using the GNU tools.
If your plugin is a simple C plugin, try using commands like:
gcc -fPIC -DPIC -Wall -O2 -c -o my_plugin.o my_plugin.c
ld -shared -o my_plugin.so my_plugin.o
If your plugin uses an external library, you must
add that to the ld
line:
ld -shared -o my_plugin.so my_plugin.o -lmy_library
The plugin will not load unless the library has been found.
If your plugin uses C++, it is far easier to use g++ to find the various library that are needed:
g++ -fPIC -DPIC -shared -nostartfiles -o my_plugin.so my_plugin.cc
Otherwise non-C++ hosts won't have the standard c++ libraries
automatically loaded for them.
If you use external libraries, add them on with -l
,
as above.
Shared libraries are searched for according to the environment
variable LD_LIBRARY_PATH
, then they use the system's
shared library table, which is generated by ldconfig
.
In some rare cases, you may wish to support the loading of multiple versions of the library which define different functions and interfaces.
You have basically two choices:
This has the disadvantage that the host must distinguish which version of the underlying library is present.
dlopen
and dlsym
to make a single
plugin that can load any version of its library.
This is more complex, but may be easier to use.