Upto: Table of Contents of full book "Programming and Using Linux Sound"

LADSPA

LADSPA (Linux Audio Plug-Ins) is a set of plugins that may be used by applictions to add effects such as delays and filters.

Resources

Files

All files

Introduction

LADPSA is a set of plugins that may be used by applications. It is designed with simplicity in mind, so is only capable of a limited number of effects. Nevertheless, these can be quite wide-ranging and are sufficient for a large variety of applications.

User level tools

LADSPA plugins live in a directory defaulting to /usr/lib/ladspa. This can be controlled by the environment variable LADSPA_PATH. This directory will contain a set of .so files as LADSPA plugins.

Each plugin contains information about itself, and the set of plugins can be inspected by running the command line tool listplugins. By installing just LADPSA, the default plugins are

	
/usr/lib/ladspa/amp.so:
	Mono Amplifier (1048/amp_mono)
	Stereo Amplifier (1049/amp_stereo)
/usr/lib/ladspa/delay.so:
	Simple Delay Line (1043/delay_5s)
/usr/lib/ladspa/filter.so:
	Simple Low Pass Filter (1041/lpf)
	Simple High Pass Filter (1042/hpf)
/usr/lib/ladspa/sine.so:
	Sine Oscillator (Freq:audio, Amp:audio) (1044/sine_faaa)
	Sine Oscillator (Freq:audio, Amp:control) (1045/sine_faac)
	Sine Oscillator (Freq:control, Amp:audio) (1046/sine_fcaa)
	Sine Oscillator (Freq:control, Amp:control) (1047/sine_fcac)
/usr/lib/ladspa/noise.so:
	White Noise Source (1050/noise_white)
	
      

More detailed information about each plugin can be found from the tool analyseplugin. For example for the amp plugin,

	
$analyseplugin amp

Plugin Name: "Mono Amplifier"
Plugin Label: "amp_mono"
Plugin Unique ID: 1048
Maker: "Richard Furse (LADSPA example plugins)"
Copyright: "None"
Must Run Real-Time: No
Has activate() Function: No
Has deactivate() Function: No
Has run_adding() Function: No
Environment: Normal or Hard Real-Time
Ports:	"Gain" input, control, 0 to ..., default 1, logarithmic
	"Input" input, audio
	"Output" output, audio

Plugin Name: "Stereo Amplifier"
Plugin Label: "amp_stereo"
Plugin Unique ID: 1049
Maker: "Richard Furse (LADSPA example plugins)"
Copyright: "None"
Must Run Real-Time: No
Has activate() Function: No
Has deactivate() Function: No
Has run_adding() Function: No
Environment: Normal or Hard Real-Time
Ports:	"Gain" input, control, 0 to ..., default 1, logarithmic
	"Input (Left)" input, audio
	"Output (Left)" output, audio
	"Input (Right)" input, audio
	"Output (Right)" output, audio
	
      

A simple test of each plugin can be performed using applyplugin. When run with no arguments it gives a usage message:

	
$applyplugin 
Usage:	applyplugin [flags] <input Wave file> <output Wave file>
	<LADSPA plugin file name> <plugin label> <Control1> <Control2>...
	[<LADSPA plugin file name> <plugin label> <Control1> <Control2>...]...
Flags:	-s<seconds>  Add seconds of silence after end of input file.
	
      

This takes an input and an output WAV file as first and second parameters. The next ones are the names of the .so file and the plugin label chosen. This is followed by values of the controls. For the amp plugin, the file name is amp.so, the stereo plugin is amp_stereo and there is only one control for gain as a value between zero and one. To halve the volume of a file containing stereo WAV data,

	
applyplugin 54154.wav tmp.wav amp.so amp_stereo 0.5
	
      

The type LADSPA_Descriptor

Coomunication between an application and a LADSPA plugin takes place through a data structure of type LADSPA_Descriptor. This has fields that contain all of the information that is shown by listplugins and analyseplugins. In addition, it contains fields that control memory layout, whether or not it supports hard realtime, etc.

unsigned long UniqueID
Each plugin must have a unique ID within the LADSPA system.
const char * Label
This is the label used to refer to the plugin within the LADSPA system
const char * Name
This is the "user friendly" name of the plugin. For example, the amp file (shown later) contains two plugins, the mono amplifier has ID 1048, label "amp_mono" and name "Mono Amplifier", while the stereo amplifier has ID 1049, label "amp_stereo" and name "Stereo Amplifier"
const char * Maker, * Copyright
Obvious
unsigned long PortCount
This indicates the number of ports (input AND output) present on the plugin
const LADSPA_PortDescriptor * PortDescriptors
This member indicates an array of port descriptors. Valid indices vary from 0 to PortCount-1
const char * const * PortNames
This member indicates an array of null-terminated strings describing ports. For example, the mono amplifer has two input ports and one output port labelled "Gain", "Input" and "Output". The Input port has PortDescriptor (LADSPA_PORT_INPUT | LADSPA_PORT_AUDIO) while the Output port has PortDescriptor (LADSPA_PORT_OUTPUT | LADSPA_PORT_AUDIO)
LADSPA_PortRangeHint * PortRangeHints
This is an array of type LADSPA_PortRangeHint, one element for each port. This allows the plugin to pass information such as whether it has a value that is bounded above or below, and if so what is that bound, as to whether it should be treated as a Boolean value, and so on. These hints could be used by, say, a GUI to give a visual control display for the plugin.

Additionally, it contains fields that are function pointers, which are called by the LADSPA runtime to initialise the plugin, handle data and clean up. These fields are

instantiate
This takes the sample rate as parameter. It is responsible for general instantiation of the plugin, setting local parameters, allocating memory, etc. It returns a pointer to a plugin-specific data structure containing all of the information relating to that plugin. This pointer will be passed as the first parameter to the other functions so that they can retrieve information for this plugin.
connect_port
This takes three parameters, the second and third being the port number and the address on which data will be readable/writable respectively. The plugin is expected to read/write data from the LADSPA runtime using this address only for each port. it will be called before run or run_adding.
activate/deactivate
These may be called to re-initialise the plugin state. They may be NULL.
run
This function is where all of the plugin'a real work is done. Its second parameter is the number of samples that are ready to read/write.
cleanup
Obvious

Other function fields are normally set to NULL.

Loading a plugin

An application can load a plugin by calling loadLADSPAPluginLibrary with one parameter which is the name of the plugin file. Note that there is no LADSPA library - LADPSA suuplies a header file ladspa.h and the distribution may include a file load.c which implements loadLADSPAPluginLibrary (it searches the directories in the LADSPA_PATH).

When a plugin is loaded by dlopen, the function _init is called with no parameters. This may be used to setup the plugin and build, for example, the LADSPA_Descriptor.

A DLL must have an entry point that you can hook into. For LADSPA, each plugin must define a function LADSPA_Descriptor * ladspa_descriptor(unsigned long Index). The values for indices 0, 1, ... are the LADSPA_Descriptor's for each of the plugins included in the file.

The amp program

The code for the amp program is

/* amp.c

   Free software by Richard W.E. Furse. Do with as you will. No
   warranty.

   This LADSPA plugin provides simple mono and stereo amplifiers.

   This file has poor memory protection. Failures during malloc() will
   not recover nicely. */

/*****************************************************************************/

#include <stdlib.h>
#include <string.h>

/*****************************************************************************/

#include "ladspa.h"

/*****************************************************************************/

/* The port numbers for the plugin: */

#define AMP_CONTROL 0
#define AMP_INPUT1  1
#define AMP_OUTPUT1 2
#define AMP_INPUT2  3
#define AMP_OUTPUT2 4

/*****************************************************************************/

/* The structure used to hold port connection information and state
   (actually gain controls require no further state). */

typedef struct {

  /* Ports:
     ------ */

  LADSPA_Data * m_pfControlValue;
  LADSPA_Data * m_pfInputBuffer1;
  LADSPA_Data * m_pfOutputBuffer1;
  LADSPA_Data * m_pfInputBuffer2;  /* (Not used for mono) */
  LADSPA_Data * m_pfOutputBuffer2; /* (Not used for mono) */

} Amplifier;

/*****************************************************************************/

/* Construct a new plugin instance. */
LADSPA_Handle 
instantiateAmplifier(const LADSPA_Descriptor * Descriptor,
		     unsigned long             SampleRate) {
  return malloc(sizeof(Amplifier));
}

/*****************************************************************************/

/* Connect a port to a data location. */
void 
connectPortToAmplifier(LADSPA_Handle Instance,
		       unsigned long Port,
		       LADSPA_Data * DataLocation) {

  Amplifier * psAmplifier;

  psAmplifier = (Amplifier *)Instance;
  switch (Port) {
  case AMP_CONTROL:
    psAmplifier->m_pfControlValue = DataLocation;
    break;
  case AMP_INPUT1:
    psAmplifier->m_pfInputBuffer1 = DataLocation;
    break;
  case AMP_OUTPUT1:
    psAmplifier->m_pfOutputBuffer1 = DataLocation;
    break;
  case AMP_INPUT2:
    /* (This should only happen for stereo.) */
    psAmplifier->m_pfInputBuffer2 = DataLocation;
    break;
  case AMP_OUTPUT2:
    /* (This should only happen for stereo.) */
    psAmplifier->m_pfOutputBuffer2 = DataLocation;
    break;
  }
}

/*****************************************************************************/

void 
runMonoAmplifier(LADSPA_Handle Instance,
		 unsigned long SampleCount) {
  
  LADSPA_Data * pfInput;
  LADSPA_Data * pfOutput;
  LADSPA_Data fGain;
  Amplifier * psAmplifier;
  unsigned long lSampleIndex;

  psAmplifier = (Amplifier *)Instance;

  pfInput = psAmplifier->m_pfInputBuffer1;
  pfOutput = psAmplifier->m_pfOutputBuffer1;
  fGain = *(psAmplifier->m_pfControlValue);

  for (lSampleIndex = 0; lSampleIndex < SampleCount; lSampleIndex++) 
    *(pfOutput++) = *(pfInput++) * fGain;
}

/*****************************************************************************/

void 
runStereoAmplifier(LADSPA_Handle Instance,
		   unsigned long SampleCount) {
  
  LADSPA_Data * pfInput;
  LADSPA_Data * pfOutput;
  LADSPA_Data fGain;
  Amplifier * psAmplifier;
  unsigned long lSampleIndex;

  psAmplifier = (Amplifier *)Instance;

  fGain = *(psAmplifier->m_pfControlValue);

  pfInput = psAmplifier->m_pfInputBuffer1;
  pfOutput = psAmplifier->m_pfOutputBuffer1;
  for (lSampleIndex = 0; lSampleIndex < SampleCount; lSampleIndex++) 
    *(pfOutput++) = *(pfInput++) * fGain;

  pfInput = psAmplifier->m_pfInputBuffer2;
  pfOutput = psAmplifier->m_pfOutputBuffer2;
  for (lSampleIndex = 0; lSampleIndex < SampleCount; lSampleIndex++) 
    *(pfOutput++) = *(pfInput++) * fGain;
}

/*****************************************************************************/

/* Throw away a simple delay line. */
void 
cleanupAmplifier(LADSPA_Handle Instance) {
  free(Instance);
}

/*****************************************************************************/

LADSPA_Descriptor * g_psMonoDescriptor = NULL;
LADSPA_Descriptor * g_psStereoDescriptor = NULL;

/*****************************************************************************/

/* _init() is called automatically when the plugin library is first
   loaded. */
void 
_init() {

  char ** pcPortNames;
  LADSPA_PortDescriptor * piPortDescriptors;
  LADSPA_PortRangeHint * psPortRangeHints;

  g_psMonoDescriptor
    = (LADSPA_Descriptor *)malloc(sizeof(LADSPA_Descriptor));
  g_psStereoDescriptor 
    = (LADSPA_Descriptor *)malloc(sizeof(LADSPA_Descriptor));

  if (g_psMonoDescriptor) {
  
    g_psMonoDescriptor->UniqueID
      = 1048;
    g_psMonoDescriptor->Label
      = strdup("amp_mono");
    g_psMonoDescriptor->Properties
      = LADSPA_PROPERTY_HARD_RT_CAPABLE;
    g_psMonoDescriptor->Name 
      = strdup("Mono Amplifier");
    g_psMonoDescriptor->Maker
      = strdup("Richard Furse (LADSPA example plugins)");
    g_psMonoDescriptor->Copyright
      = strdup("None");
    g_psMonoDescriptor->PortCount
      = 3;
    piPortDescriptors
      = (LADSPA_PortDescriptor *)calloc(3, sizeof(LADSPA_PortDescriptor));
    g_psMonoDescriptor->PortDescriptors
      = (const LADSPA_PortDescriptor *)piPortDescriptors;
    piPortDescriptors[AMP_CONTROL]
      = LADSPA_PORT_INPUT | LADSPA_PORT_CONTROL;
    piPortDescriptors[AMP_INPUT1]
      = LADSPA_PORT_INPUT | LADSPA_PORT_AUDIO;
    piPortDescriptors[AMP_OUTPUT1]
      = LADSPA_PORT_OUTPUT | LADSPA_PORT_AUDIO;
    pcPortNames
      = (char **)calloc(3, sizeof(char *));
    g_psMonoDescriptor->PortNames 
      = (const char **)pcPortNames;
    pcPortNames[AMP_CONTROL]
      = strdup("Gain");
    pcPortNames[AMP_INPUT1]
      = strdup("Input");
    pcPortNames[AMP_OUTPUT1]
      = strdup("Output");
    psPortRangeHints = ((LADSPA_PortRangeHint *)
			calloc(3, sizeof(LADSPA_PortRangeHint)));
    g_psMonoDescriptor->PortRangeHints
      = (const LADSPA_PortRangeHint *)psPortRangeHints;
    psPortRangeHints[AMP_CONTROL].HintDescriptor
      = (LADSPA_HINT_BOUNDED_BELOW 
	 | LADSPA_HINT_LOGARITHMIC
	 | LADSPA_HINT_DEFAULT_1);
    psPortRangeHints[AMP_CONTROL].LowerBound 
      = 0;
    psPortRangeHints[AMP_INPUT1].HintDescriptor
      = 0;
    psPortRangeHints[AMP_OUTPUT1].HintDescriptor
      = 0;
    g_psMonoDescriptor->instantiate 
      = instantiateAmplifier;
    g_psMonoDescriptor->connect_port 
      = connectPortToAmplifier;
    g_psMonoDescriptor->activate
      = NULL;
    g_psMonoDescriptor->run
      = runMonoAmplifier;
    g_psMonoDescriptor->run_adding
      = NULL;
    g_psMonoDescriptor->set_run_adding_gain
      = NULL;
    g_psMonoDescriptor->deactivate
      = NULL;
    g_psMonoDescriptor->cleanup
      = cleanupAmplifier;
  }
  
  if (g_psStereoDescriptor) {
    
    g_psStereoDescriptor->UniqueID
      = 1049;
    g_psStereoDescriptor->Label
      = strdup("amp_stereo");
    g_psStereoDescriptor->Properties
      = LADSPA_PROPERTY_HARD_RT_CAPABLE;
    g_psStereoDescriptor->Name 
      = strdup("Stereo Amplifier");
    g_psStereoDescriptor->Maker
      = strdup("Richard Furse (LADSPA example plugins)");
    g_psStereoDescriptor->Copyright
      = strdup("None");
    g_psStereoDescriptor->PortCount
      = 5;
    piPortDescriptors
      = (LADSPA_PortDescriptor *)calloc(5, sizeof(LADSPA_PortDescriptor));
    g_psStereoDescriptor->PortDescriptors
      = (const LADSPA_PortDescriptor *)piPortDescriptors;
    piPortDescriptors[AMP_CONTROL]
      = LADSPA_PORT_INPUT | LADSPA_PORT_CONTROL;
    piPortDescriptors[AMP_INPUT1]
      = LADSPA_PORT_INPUT | LADSPA_PORT_AUDIO;
    piPortDescriptors[AMP_OUTPUT1]
      = LADSPA_PORT_OUTPUT | LADSPA_PORT_AUDIO;
    piPortDescriptors[AMP_INPUT2]
      = LADSPA_PORT_INPUT | LADSPA_PORT_AUDIO;
    piPortDescriptors[AMP_OUTPUT2]
      = LADSPA_PORT_OUTPUT | LADSPA_PORT_AUDIO;
    pcPortNames
      = (char **)calloc(5, sizeof(char *));
    g_psStereoDescriptor->PortNames 
      = (const char **)pcPortNames;
    pcPortNames[AMP_CONTROL]
      = strdup("Gain");
    pcPortNames[AMP_INPUT1]
      = strdup("Input (Left)");
    pcPortNames[AMP_OUTPUT1]
      = strdup("Output (Left)");
    pcPortNames[AMP_INPUT2]
      = strdup("Input (Right)");
    pcPortNames[AMP_OUTPUT2]
      = strdup("Output (Right)");
    psPortRangeHints = ((LADSPA_PortRangeHint *)
			calloc(5, sizeof(LADSPA_PortRangeHint)));
    g_psStereoDescriptor->PortRangeHints
      = (const LADSPA_PortRangeHint *)psPortRangeHints;
    psPortRangeHints[AMP_CONTROL].HintDescriptor
      = (LADSPA_HINT_BOUNDED_BELOW 
	 | LADSPA_HINT_LOGARITHMIC
	 | LADSPA_HINT_DEFAULT_1);
    psPortRangeHints[AMP_CONTROL].LowerBound 
      = 0;
    psPortRangeHints[AMP_INPUT1].HintDescriptor
      = 0;
    psPortRangeHints[AMP_OUTPUT1].HintDescriptor
      = 0;
    psPortRangeHints[AMP_INPUT2].HintDescriptor
      = 0;
    psPortRangeHints[AMP_OUTPUT2].HintDescriptor
      = 0;
    g_psStereoDescriptor->instantiate 
      = instantiateAmplifier;
    g_psStereoDescriptor->connect_port 
      = connectPortToAmplifier;
    g_psStereoDescriptor->activate
      = NULL;
    g_psStereoDescriptor->run
      = runStereoAmplifier;
    g_psStereoDescriptor->run_adding
      = NULL;
    g_psStereoDescriptor->set_run_adding_gain
      = NULL;
    g_psStereoDescriptor->deactivate
      = NULL;
    g_psStereoDescriptor->cleanup
      = cleanupAmplifier;
  }
}

/*****************************************************************************/

void
deleteDescriptor(LADSPA_Descriptor * psDescriptor) {
  unsigned long lIndex;
  if (psDescriptor) {
    free((char *)psDescriptor->Label);
    free((char *)psDescriptor->Name);
    free((char *)psDescriptor->Maker);
    free((char *)psDescriptor->Copyright);
    free((LADSPA_PortDescriptor *)psDescriptor->PortDescriptors);
    for (lIndex = 0; lIndex < psDescriptor->PortCount; lIndex++)
      free((char *)(psDescriptor->PortNames[lIndex]));
    free((char **)psDescriptor->PortNames);
    free((LADSPA_PortRangeHint *)psDescriptor->PortRangeHints);
    free(psDescriptor);
  }
}

/*****************************************************************************/

/* _fini() is called automatically when the library is unloaded. */
void
_fini() {
  deleteDescriptor(g_psMonoDescriptor);
  deleteDescriptor(g_psStereoDescriptor);
}

/*****************************************************************************/

/* Return a descriptor of the requested plugin type. There are two
   plugin types available in this library (mono and stereo). */
const LADSPA_Descriptor * 
ladspa_descriptor(unsigned long Index) {
  /* Return the requested descriptor or null if the index is out of
     range. */
  switch (Index) {
  case 0:
    return g_psMonoDescriptor;
  case 1:
    return g_psStereoDescriptor;
  default:
    return NULL;
  }
}

/*****************************************************************************/

/* EOF */

      

The analysePlugin client

The analysePlugin client loads a plugin file and then does a dump of all the information given about each plugin in the file. It is really just a long-winded plough through all the possibilities, printing out information. Its code is:

	/* analyseplugin.c

   Free software by Richard W.E. Furse. Do with as you will. No
   warranty. */

/*****************************************************************************/

#include <dlfcn.h>
#include <math.h>
#include <stdio.h>
#include <string.h>

/*****************************************************************************/

#include "ladspa.h"

#include "utils.h"

/*****************************************************************************/

/* Returns 0 if all goes well, otherwise returns 1. Label may be null
   indicating `all plugins.' */
static int
analysePlugin(const char * pcPluginFilename,
	      const char * pcPluginLabel,
	      const int bVerbose) {

  LADSPA_Descriptor_Function pfDescriptorFunction;
  const LADSPA_Descriptor * psDescriptor;
  unsigned long lPluginIndex;
  unsigned long lPortIndex;
  unsigned long lSpaceIndex;
  unsigned long lSpacePadding1;
  unsigned long lSpacePadding2;
  unsigned long lLength;
  void * pvPluginHandle;
  LADSPA_PortRangeHintDescriptor iHintDescriptor;
  LADSPA_Data fBound;
  LADSPA_Data fDefault;

  pvPluginHandle = loadLADSPAPluginLibrary(pcPluginFilename);

  dlerror();
  pfDescriptorFunction 
    = (LADSPA_Descriptor_Function)dlsym(pvPluginHandle, "ladspa_descriptor");
  if (!pfDescriptorFunction) {
    const char * pcError = dlerror();
    if (pcError) 
      fprintf(stderr,
	      "Unable to find ladspa_descriptor() function in plugin file "
	      "\"%s\": %s.\n"
	      "Are you sure this is a LADSPA plugin file?\n", 
	      pcPluginFilename,
	      pcError);
    return 1;
  }

  lSpacePadding1 = 0;
  lSpacePadding2 = 0;
  if (!bVerbose) {
    for (lPluginIndex = 0;; lPluginIndex++) {
      psDescriptor = pfDescriptorFunction(lPluginIndex);
      if (!psDescriptor)
	break;
      if (pcPluginLabel != NULL)
	if (strcmp(pcPluginLabel, psDescriptor->Label) != 0)
	  continue;
      
      lLength = strlen(psDescriptor->Label);
      if (lSpacePadding1 < lLength)
	lSpacePadding1 = lLength;

      lLength = (long)(log10(psDescriptor->UniqueID)) + 1;
      if (lSpacePadding2 < lLength)
	lSpacePadding2 = lLength;
    }
    lSpacePadding1 += 2;
    lSpacePadding2 += 2;
  }

  for (lPluginIndex = 0;; lPluginIndex++) {

    psDescriptor = pfDescriptorFunction(lPluginIndex);
    if (!psDescriptor)
      break;
    if (pcPluginLabel != NULL)
      if (strcmp(pcPluginLabel, psDescriptor->Label) != 0)
	continue;

    if (!bVerbose) {
      printf("%s", psDescriptor->Label);
      for (lSpaceIndex = strlen(psDescriptor->Label);
	   lSpaceIndex < lSpacePadding1;
	   lSpaceIndex++)
	putchar(' ');
      printf("%lu", psDescriptor->UniqueID);
      for (lSpaceIndex = (long)(log10(psDescriptor->UniqueID)) + 1;
	   lSpaceIndex < lSpacePadding2;
	   lSpaceIndex++)
	putchar(' ');
      puts(psDescriptor->Name);
    }
    else {

      putchar('\n');
      
      printf("Plugin Name: \"%s\"\n", psDescriptor->Name);
      printf("Plugin Label: \"%s\"\n", psDescriptor->Label);
      printf("Plugin Unique ID: %lu\n", psDescriptor->UniqueID);
      printf("Maker: \"%s\"\n", psDescriptor->Maker);
      printf("Copyright: \"%s\"\n", psDescriptor->Copyright);
      
      printf("Must Run Real-Time: ");
      if (LADSPA_IS_REALTIME(psDescriptor->Properties))
	printf("Yes\n");
      else
	printf("No\n");
      
      printf("Has activate() Function: ");
      if (psDescriptor->activate != NULL)
	printf("Yes\n");
      else
	printf("No\n");    
      printf("Has deactivate() Function: ");
      if (psDescriptor->deactivate != NULL)
	printf("Yes\n");
      else
	printf("No\n");
      printf("Has run_adding() Function: ");
      if (psDescriptor->run_adding != NULL)
	printf("Yes\n");
      else
	printf("No\n");
      
      if (psDescriptor->instantiate == NULL)
	printf("ERROR: PLUGIN HAS NO INSTANTIATE FUNCTION.\n");
      if (psDescriptor->connect_port == NULL)
	printf("ERROR: PLUGIN HAS NO CONNECT_PORT FUNCTION.\n");
      if (psDescriptor->run == NULL)
	printf("ERROR: PLUGIN HAS NO RUN FUNCTION.\n");
      if (psDescriptor->run_adding != NULL
	  && psDescriptor->set_run_adding_gain == NULL)
	printf("ERROR: PLUGIN HAS RUN_ADDING FUNCTION BUT "
	       "NOT SET_RUN_ADDING_GAIN.\n");
      if (psDescriptor->run_adding == NULL
	  && psDescriptor->set_run_adding_gain != NULL)
	printf("ERROR: PLUGIN HAS SET_RUN_ADDING_GAIN FUNCTION BUT "
	       "NOT RUN_ADDING.\n");
      if (psDescriptor->cleanup == NULL)
	printf("ERROR: PLUGIN HAS NO CLEANUP FUNCTION.\n");
      
      printf("Environment: ");
      if (LADSPA_IS_HARD_RT_CAPABLE(psDescriptor->Properties))
	printf("Normal or Hard Real-Time\n");
      else
	printf("Normal\n");
      
      if (LADSPA_IS_INPLACE_BROKEN(psDescriptor->Properties))
	printf("This plugin cannot use in-place processing. "
	       "It will not work with all hosts.\n");
      
      printf("Ports:");

      if (psDescriptor->PortCount == 0)
	printf("\tERROR: PLUGIN HAS NO PORTS.\n");
      
      for (lPortIndex = 0; 
	   lPortIndex < psDescriptor->PortCount; 
	   lPortIndex++) {
	
	printf("\t\"%s\" ", psDescriptor->PortNames[lPortIndex]);
	
	if (LADSPA_IS_PORT_INPUT
	    (psDescriptor->PortDescriptors[lPortIndex])
	    && LADSPA_IS_PORT_OUTPUT
	    (psDescriptor->PortDescriptors[lPortIndex]))
	  printf("ERROR: INPUT AND OUTPUT");
	else if (LADSPA_IS_PORT_INPUT
		 (psDescriptor->PortDescriptors[lPortIndex]))
	  printf("input");
	else if (LADSPA_IS_PORT_OUTPUT
		 (psDescriptor->PortDescriptors[lPortIndex]))
	  printf("output");
	else 
	  printf("ERROR: NEITHER INPUT NOR OUTPUT");
	
	if (LADSPA_IS_PORT_CONTROL
	    (psDescriptor->PortDescriptors[lPortIndex])
	    && LADSPA_IS_PORT_AUDIO
	    (psDescriptor->PortDescriptors[lPortIndex]))
	  printf(", ERROR: CONTROL AND AUDIO");
	else if (LADSPA_IS_PORT_CONTROL
		 (psDescriptor->PortDescriptors[lPortIndex]))
	  printf(", control");
	else if (LADSPA_IS_PORT_AUDIO
		 (psDescriptor->PortDescriptors[lPortIndex]))
	  printf(", audio");
	else 
	  printf(", ERROR: NEITHER CONTROL NOR AUDIO");
	
	iHintDescriptor 
	  = psDescriptor->PortRangeHints[lPortIndex].HintDescriptor;
	
	if (LADSPA_IS_HINT_BOUNDED_BELOW(iHintDescriptor)
	    || LADSPA_IS_HINT_BOUNDED_ABOVE(iHintDescriptor)) {
	  printf(", ");
	  if (LADSPA_IS_HINT_BOUNDED_BELOW(iHintDescriptor)) {
	    fBound = psDescriptor->PortRangeHints[lPortIndex].LowerBound;
	    if (LADSPA_IS_HINT_SAMPLE_RATE(iHintDescriptor) && fBound != 0) 
	      printf("%g*srate", fBound);
	    else
	      printf("%g", fBound);
	  }
	  else
	    printf("...");
	  printf(" to ");
	  if (LADSPA_IS_HINT_BOUNDED_ABOVE(iHintDescriptor)) {
	    fBound = psDescriptor->PortRangeHints[lPortIndex].UpperBound;
	    if (LADSPA_IS_HINT_SAMPLE_RATE(iHintDescriptor) && fBound != 0)
	      printf("%g*srate", fBound);
	    else
	      printf("%g", fBound);
	  }
	  else
	    printf("...");
	}
	
	if (LADSPA_IS_HINT_TOGGLED(iHintDescriptor)) {
	  if ((iHintDescriptor 
	       | LADSPA_HINT_DEFAULT_0
	       | LADSPA_HINT_DEFAULT_1)
	       != (LADSPA_HINT_TOGGLED 
		   | LADSPA_HINT_DEFAULT_0
		   | LADSPA_HINT_DEFAULT_1))
	    printf(", ERROR: TOGGLED INCOMPATIBLE WITH OTHER HINT");
	  else
	    printf(", toggled");
	}
      
	switch (iHintDescriptor & LADSPA_HINT_DEFAULT_MASK) {
	case LADSPA_HINT_DEFAULT_NONE:
	  break;
	case LADSPA_HINT_DEFAULT_MINIMUM:
	  fDefault = psDescriptor->PortRangeHints[lPortIndex].LowerBound;
	  if (LADSPA_IS_HINT_SAMPLE_RATE(iHintDescriptor) && fDefault != 0) 
	    printf(", default %g*srate", fDefault);
	  else 
	    printf(", default %g", fDefault);
	  break;
	case LADSPA_HINT_DEFAULT_LOW:
	  if (LADSPA_IS_HINT_LOGARITHMIC(iHintDescriptor)) {
	    fDefault 
	      = exp(log(psDescriptor->PortRangeHints[lPortIndex].LowerBound) 
		    * 0.75
		    + log(psDescriptor->PortRangeHints[lPortIndex].UpperBound) 
		    * 0.25);
	  }
	  else {
	    fDefault 
	      = (psDescriptor->PortRangeHints[lPortIndex].LowerBound
		 * 0.75
		 + psDescriptor->PortRangeHints[lPortIndex].UpperBound
		 * 0.25);
	  }
	  if (LADSPA_IS_HINT_SAMPLE_RATE(iHintDescriptor) && fDefault != 0) 
	    printf(", default %g*srate", fDefault);
	  else 
	    printf(", default %g", fDefault);
	  break;
	case LADSPA_HINT_DEFAULT_MIDDLE:
	  if (LADSPA_IS_HINT_LOGARITHMIC(iHintDescriptor)) {
	    fDefault 
	      = sqrt(psDescriptor->PortRangeHints[lPortIndex].LowerBound
		     * psDescriptor->PortRangeHints[lPortIndex].UpperBound);
	  }
	  else {
	    fDefault 
	      = 0.5 * (psDescriptor->PortRangeHints[lPortIndex].LowerBound
		       + psDescriptor->PortRangeHints[lPortIndex].UpperBound);
	  }
	  if (LADSPA_IS_HINT_SAMPLE_RATE(iHintDescriptor) && fDefault != 0) 
	    printf(", default %g*srate", fDefault);
	  else 
	    printf(", default %g", fDefault);
	  break;
	case LADSPA_HINT_DEFAULT_HIGH:
	  if (LADSPA_IS_HINT_LOGARITHMIC(iHintDescriptor)) {
	    fDefault 
	      = exp(log(psDescriptor->PortRangeHints[lPortIndex].LowerBound) 
		    * 0.25
		    + log(psDescriptor->PortRangeHints[lPortIndex].UpperBound) 
		    * 0.75);
	  }
	  else {
	    fDefault 
	      = (psDescriptor->PortRangeHints[lPortIndex].LowerBound
		 * 0.25
		 + psDescriptor->PortRangeHints[lPortIndex].UpperBound
		 * 0.75);
	  }
	  if (LADSPA_IS_HINT_SAMPLE_RATE(iHintDescriptor) && fDefault != 0) 
	    printf(", default %g*srate", fDefault);
	  else 
	    printf(", default %g", fDefault);
	  break;
	case LADSPA_HINT_DEFAULT_MAXIMUM:
	  fDefault = psDescriptor->PortRangeHints[lPortIndex].UpperBound;
	  if (LADSPA_IS_HINT_SAMPLE_RATE(iHintDescriptor) && fDefault != 0) 
	    printf(", default %g*srate", fDefault);
	  else 
	    printf(", default %g", fDefault);
	  break;
	case LADSPA_HINT_DEFAULT_0:
	  printf(", default 0");
	  break;
	case LADSPA_HINT_DEFAULT_1:
	  printf(", default 1");
	  break;
	case LADSPA_HINT_DEFAULT_100:
	  printf(", default 100");
	  break;
	case LADSPA_HINT_DEFAULT_440:
	  printf(", default 440");
	  break;
	default:
	  printf(", UNKNOWN DEFAULT CODE");
	  /* (Not necessarily an error - may be a newer version.) */
	  break;
	}
	
	if (LADSPA_IS_HINT_LOGARITHMIC(iHintDescriptor))
	  printf(", logarithmic");
	
	if (LADSPA_IS_HINT_INTEGER(iHintDescriptor))
	  printf(", integer");
	
	putchar('\n');
      }
    }
  }

  if (bVerbose)
    putchar('\n');

  unloadLADSPAPluginLibrary(pvPluginHandle);
  
  return(0);
}

/*****************************************************************************/

int 
main(const int iArgc, const char ** ppcArgv) {
  
  const char * pcPluginName = NULL;
  const char * pcPluginLabel = NULL;
  int bVerbose = 1;

  /* Check for a flag, but only at the start. Cannot get use getopt()
     as it gets thoroughly confused when faced with negative numbers
     on the command line. */
  switch (iArgc) {
  case 2:
    if (strcmp(ppcArgv[1], "-h") != 0) {
      pcPluginName = ppcArgv[1];
      pcPluginLabel = NULL;
    }
    break;
  case 3:
    if (strcmp(ppcArgv[1], "-l") == 0) {
      pcPluginName = ppcArgv[2];
      pcPluginLabel = NULL;
      bVerbose = 0;
    }
    else {
      pcPluginName = ppcArgv[1];
      pcPluginLabel = ppcArgv[2];
    }
    break;
  case 4:
    if (strcmp(ppcArgv[1], "-l") == 0) {
      pcPluginName = ppcArgv[2];
      pcPluginLabel = ppcArgv[3];
      bVerbose = 0;
    }
    break;
  }

  if (!pcPluginName) {
    fprintf(stderr, 
	    "Usage:\tanalyseplugin [flags] <LADSPA plugin file name> "
	    "[<plugin label>].\n"
	    "Flags:"
	    "-l  Produce a summary list rather than a verbose report.\n"
            "Note that the LADSPA_PATH environment variable is used "
            "to help find plugins.\n");
    return(1);
  }

  return analysePlugin(pcPluginName, pcPluginLabel, bVerbose);
}

/*****************************************************************************/

/* EOF */

      

A mono amplifier client

The applyplugin program shows how clients can use LADSPA plugins in a general way. Unfortunately, the necessary generality makes it harder to see what is being done. In this section we just look at a simple client that uses the amp_mono plugin to halve the volume of a file.

From running analyseplugin on the amp.so file we can find that it contains a mono and stereo plugin. In the following program the main function loads the plugin file, gets a handle to the ladspa_descriptor structure and then looks through the descriptors until it finds the amp_mono plugin.

We know there are three ports: control, input and output, so we look through the list of ports to assign indices and connect the relevant arrays to the plugin descriptor. The control port only needs the address of a float value which is the amount of amplification that will occur.

The run_plugin function then just loops, reading samples from the imput file, applying the plugin and writing to the output file. I've used the libsndfile library to simplify reading and writing files in whatever format they are in. I've also used the load.c file from the LADSPA package to simplify loading the plugin library.

The program is mono_amp.c:


#include <stdlib.h>
#include <stdio.h>
#include <ladspa.h>
#include <dlfcn.h>
#include <sndfile.h>

#include "utils.h"

const LADSPA_Descriptor * psDescriptor;
LADSPA_Descriptor_Function pfDescriptorFunction;
LADSPA_Handle handle;

// choose the mono plugin from the amp file
char *pcPluginFilename = "amp.so";
char *pcPluginLabel = "amp_mono";

long lInputPortIndex = -1;
long lOutputPortIndex = -1;

SNDFILE* pInFile;
SNDFILE* pOutFile;

// for the amplifier, the sample rate doesn't really matter
#define SAMPLE_RATE 44100

// the buffer size isn't really important either
#define BUF_SIZE 2048
LADSPA_Data pInBuffer[BUF_SIZE];
LADSPA_Data pOutBuffer[BUF_SIZE];

// How much we are amplifying the sound by
LADSPA_Data control = 0.5f;

char *pInFilePath = "/home/local/antialize-wkhtmltopdf-7cb5810/scripts/static-build/linux-local/qts/demos/mobile/quickhit/plugins/LevelTemplate/sound/enableship.wav";
char *pOutFilePath = "tmp.wav";

void open_files() {
    // using libsndfile functions for easy read/write
    SF_INFO sfinfo;

    sfinfo.format = 0;
    pInFile = sf_open(pInFilePath, SFM_READ, &sfinfo);
    if (pInFile == NULL) {
	perror("can't open input file");
	exit(1);
    }

    pOutFile = sf_open(pOutFilePath, SFM_WRITE, &sfinfo);
    if (pOutFile == NULL) {
	perror("can't open output file");
	exit(1);
    }
}

sf_count_t fill_input_buffer() {
    return sf_read_float(pInFile, pInBuffer, BUF_SIZE);
}

void empty_output_buffer(sf_count_t numread) {
    sf_write_float(pOutFile, pOutBuffer, numread);
}

void run_plugin() {
    sf_count_t numread;

    open_files();

    // it's NULL for the amp plugin
    if (psDescriptor->activate != NULL)
	psDescriptor->activate(handle);

    while ((numread = fill_input_buffer()) > 0) {
	printf("Num read %d\n", numread);
	psDescriptor->run(handle, numread);
	empty_output_buffer(numread);
    }
}

int main(int argc, char *argv[]) {
    int lPluginIndex;

    void *pvPluginHandle = loadLADSPAPluginLibrary(pcPluginFilename);
    dlerror();

    pfDescriptorFunction 
	= (LADSPA_Descriptor_Function)dlsym(pvPluginHandle, "ladspa_descriptor");
    if (!pfDescriptorFunction) {
	const char * pcError = dlerror();
	if (pcError) 
	    fprintf(stderr,
		    "Unable to find ladspa_descriptor() function in plugin file "
		    "\"%s\": %s.\n"
		    "Are you sure this is a LADSPA plugin file?\n", 
		    pcPluginFilename,
		    pcError);
	return 1;
    }

    for (lPluginIndex = 0;; lPluginIndex++) {
	psDescriptor = pfDescriptorFunction(lPluginIndex);
	if (!psDescriptor)
	    break;
	if (pcPluginLabel != NULL) {
	    if (strcmp(pcPluginLabel, psDescriptor->Label) != 0)
		continue;
	}
	// got mono_amp

	handle = psDescriptor->instantiate(psDescriptor, SAMPLE_RATE);
	if (handle == NULL) {
	    fprintf(stderr, "Can't instantiate plugin %s\n", pcPluginLabel);
	    exit(1);
	}

	// get ports
	int lPortIndex;
	printf("Num ports %lu\n", psDescriptor->PortCount);
	for (lPortIndex = 0; 
	     lPortIndex < psDescriptor->PortCount; 
	     lPortIndex++) {
	    if (LADSPA_IS_PORT_INPUT
		(psDescriptor->PortDescriptors[lPortIndex])
		&& LADSPA_IS_PORT_AUDIO
		(psDescriptor->PortDescriptors[lPortIndex])) {
		printf("input %d\n", lPortIndex);
		lInputPortIndex = lPortIndex;

		psDescriptor->connect_port(handle,
					   lInputPortIndex, pInBuffer);
	    } else if (LADSPA_IS_PORT_OUTPUT
		       (psDescriptor->PortDescriptors[lPortIndex])
		       && LADSPA_IS_PORT_AUDIO
		       (psDescriptor->PortDescriptors[lPortIndex])) {
		printf("output %d\n", lPortIndex);
		lOutputPortIndex = lPortIndex;

		psDescriptor->connect_port(handle,
					   lOutputPortIndex, pOutBuffer);
	    }

	    if (LADSPA_IS_PORT_CONTROL
		(psDescriptor->PortDescriptors[lPortIndex])) {
		printf("control %d\n", lPortIndex);
		psDescriptor->connect_port(handle,			    
					   lPortIndex, &control);
	    }
	}
	// we've got what we wanted, get out of this loop
	break;
    }

    if ((psDescriptor == NULL) ||
	(lInputPortIndex == -1) ||
	(lOutputPortIndex == -1)) {
	fprintf(stderr, "Can't find plugin information\n");
	exit(1);
    }

    run_plugin();

    exit(0);
}

      

it is run just by calling mono_amp, no arguments.

A stereo amplifer with GUI

The amp file contains a stereo amplifier as well as a mono amplifier. This causes several differences to managing the plugin. there are now two input ports and two output ports, but still only one control port for the amplification factor. We need an array of input ports and an array of output ports. This just adds a little complexity.

The major difference is in handling the streams: libsndfile returns frames of sound, with the two channels of a stereo signal interleaved. These have to be split out into separate channels for each input port, and then the two output ports have to interleaved back together.

Adding a GUI such as GTK is fairly straightforward. The following code just shows a slider to control the volume. The GUI code and the LADSPA code must obviously run in different (POSIX) threads. There is really only one tricky point: the control value is not supposed to change during execution of the run function. This could be protected by locks, but in this case that is too heavyweight: just keep a copy of the control as modified by the slider, and bring that across before each call to run.

The code is written to use GTK v3 and is:

#include <gtk/gtk.h>

#include <stdlib.h>
#include <stdio.h>
#include <ladspa.h>
#include <dlfcn.h>
#include <sndfile.h>

#include "utils.h"

gint count = 0;
char buf[5];

pthread_t ladspa_thread;

const LADSPA_Descriptor * psDescriptor;
LADSPA_Descriptor_Function pfDescriptorFunction;
LADSPA_Handle handle;

// choose the mono plugin from the amp file
char *pcPluginFilename = "amp.so";
char *pcPluginLabel = "amp_stereo";

long lInputPortIndex = -1;
long lOutputPortIndex = -1;

int inBufferIndex = 0;
int outBufferIndex = 0;

SNDFILE* pInFile;
SNDFILE* pOutFile;

// for the amplifier, the sample rate doesn't really matter
#define SAMPLE_RATE 44100

// the buffer size isn't really important either
#define BUF_SIZE 2048
LADSPA_Data pInStereoBuffer[2*BUF_SIZE];
LADSPA_Data pOutStereoBuffer[2*BUF_SIZE];
LADSPA_Data pInBuffer[2][BUF_SIZE];
LADSPA_Data pOutBuffer[2][BUF_SIZE];

// How much we are amplifying the sound by
// We aren't allowed to change the control values
// during execution of run(). We could put a lock
// around run() or simpler, change the value of
// control only outside of run()
LADSPA_Data control;
LADSPA_Data pre_control = 0.2f;

char *pInFilePath = "/home/newmarch/Music/karaoke/nights/nightsinwhite-0.wav";
char *pOutFilePath = "tmp.wav";

void open_files() {
    // using libsndfile functions for easy read/write
    SF_INFO sfinfo;

    sfinfo.format = 0;
    pInFile = sf_open(pInFilePath, SFM_READ, &sfinfo);
    if (pInFile == NULL) {
	perror("can't open input file");
	exit(1);
    }

    pOutFile = sf_open(pOutFilePath, SFM_WRITE, &sfinfo);
    if (pOutFile == NULL) {
	perror("can't open output file");
	exit(1);
    }
}

sf_count_t fill_input_buffer() {
    int numread = sf_read_float(pInFile, pInStereoBuffer, 2*BUF_SIZE);

    // split frames into samples for each channel
    int n;
    for (n = 0; n < numread; n += 2) {
	pInBuffer[0][n/2] = pInStereoBuffer[n];
	pInBuffer[1][n/2] = pInStereoBuffer[n+1];
    }
    return numread/2;
}

void empty_output_buffer(sf_count_t numread) {
    // combine output samples back into frames
    int n;
    for (n = 0; n < 2*numread; n += 2) {
	pOutStereoBuffer[n] = pOutBuffer[0][n/2];
	pOutStereoBuffer[n+1] = pOutBuffer[1][n/2];
    }

    sf_write_float(pOutFile, pOutStereoBuffer, 2*numread);
}

gpointer run_plugin(gpointer args) {
    sf_count_t numread;

    // it's NULL for the amp plugin
    if (psDescriptor->activate != NULL)
	psDescriptor->activate(handle);

    while ((numread = fill_input_buffer()) > 0) {
	// reset control outside of run()
	control = pre_control;

	psDescriptor->run(handle, numread);
	empty_output_buffer(numread);
	usleep(1000);
    }
    printf("Plugin finished!\n");
}

void setup_ladspa() {
    int lPluginIndex;

    void *pvPluginHandle = loadLADSPAPluginLibrary(pcPluginFilename);
    dlerror();

    pfDescriptorFunction 
	= (LADSPA_Descriptor_Function)dlsym(pvPluginHandle, "ladspa_descriptor");
    if (!pfDescriptorFunction) {
	const char * pcError = dlerror();
	if (pcError) 
	    fprintf(stderr,
		    "Unable to find ladspa_descriptor() function in plugin file "
		    "\"%s\": %s.\n"
		    "Are you sure this is a LADSPA plugin file?\n", 
		    pcPluginFilename,
		    pcError);
	exit(1);
    }

    for (lPluginIndex = 0;; lPluginIndex++) {
	psDescriptor = pfDescriptorFunction(lPluginIndex);
	if (!psDescriptor)
	    break;
	if (pcPluginLabel != NULL) {
	    if (strcmp(pcPluginLabel, psDescriptor->Label) != 0)
		continue;
	}
	// got mono_amp

	handle = psDescriptor->instantiate(psDescriptor, SAMPLE_RATE);
	if (handle == NULL) {
	    fprintf(stderr, "Can't instantiate plugin %s\n", pcPluginLabel);
	    exit(1);
	}

	// get ports
	int lPortIndex;
	printf("Num ports %lu\n", psDescriptor->PortCount);
	for (lPortIndex = 0; 
	     lPortIndex < psDescriptor->PortCount; 
	     lPortIndex++) {
	    if (LADSPA_IS_PORT_AUDIO
		(psDescriptor->PortDescriptors[lPortIndex])) {
		if (LADSPA_IS_PORT_INPUT
		    (psDescriptor->PortDescriptors[lPortIndex])) {
		    printf("input %d\n", lPortIndex);
		    lInputPortIndex = lPortIndex;
		    
		    psDescriptor->connect_port(handle,
					       lInputPortIndex, pInBuffer[inBufferIndex++]);
		} else if (LADSPA_IS_PORT_OUTPUT
			   (psDescriptor->PortDescriptors[lPortIndex])) {
		    printf("output %d\n", lPortIndex);
		    lOutputPortIndex = lPortIndex;
		    
		    psDescriptor->connect_port(handle,
					       lOutputPortIndex, pOutBuffer[outBufferIndex++]);
		}
	    }

	    if (LADSPA_IS_PORT_CONTROL
		(psDescriptor->PortDescriptors[lPortIndex])) {
		printf("control %d\n", lPortIndex);
		psDescriptor->connect_port(handle,			    
					   lPortIndex, &control);
	    }
	}
	// we've got what we wanted, get out of this loop
	break;
    }

    if ((psDescriptor == NULL) ||
	(lInputPortIndex == -1) ||
	(lOutputPortIndex == -1)) {
	fprintf(stderr, "Can't find plugin information\n");
	exit(1);
    }

    open_files();

    pthread_create(&ladspa_thread, NULL, run_plugin, NULL);
}

void slider_change(GtkAdjustment *adj,  gpointer data)
{
    count++;

    pre_control = gtk_adjustment_get_value(adj);
    //gtk_label_set_text(GTK_LABEL(label), buf);
}

int main(int argc, char** argv) {

    //GtkWidget *label;
    GtkWidget *window;
    GtkWidget *frame;
    GtkWidget *slider;
    GtkAdjustment *adjustment;

    setup_ladspa();

    gtk_init(&argc, &argv);

    window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
    gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);
    gtk_window_set_default_size(GTK_WINDOW(window), 250, 80);
    gtk_window_set_title(GTK_WINDOW(window), "Volume");

    frame = gtk_fixed_new();
    gtk_container_add(GTK_CONTAINER(window), frame);

    adjustment = gtk_adjustment_new(1.0,
                               0.0,
                               2.0,
                               0.1,
                               1.0,
                               0.0);
    slider = gtk_scale_new(GTK_ORIENTATION_HORIZONTAL, 
			 adjustment);
    gtk_widget_set_size_request(slider, 240, 5);
    gtk_fixed_put(GTK_FIXED(frame), slider, 5, 20);


    
    //label = gtk_label_new("0");
    //gtk_fixed_put(GTK_FIXED(frame), label, 190, 58); 

    gtk_widget_show_all(window);

    g_signal_connect(window, "destroy",
		     G_CALLBACK (gtk_main_quit), NULL);

    g_signal_connect(adjustment, "value-changed", 
		     G_CALLBACK(slider_change), NULL);

    gtk_main();

    return 0;
}
      

      

It is run just by calling stereo_amp, no arguments.

Conclusion

LADSPA is a commonly used framework for audio effects plugins. This chapter has considered the programming model and also some command line tools.


Copyright © Jan Newmarch, jan@newmarch.name
Creative Commons License
"Programming and Using Linux Sound - in depth" by Jan Newmarch is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License .
Based on a work at https://jan.newmarch.name/LinuxSound/ .

If you like this book, please contribute using PayPal

Or Flattr me:
Flattr this book