diff options
Diffstat (limited to 'src/plugins/ladspa/remix_ladspa.c')
-rw-r--r-- | src/plugins/ladspa/remix_ladspa.c | 1045 |
1 files changed, 1045 insertions, 0 deletions
diff --git a/src/plugins/ladspa/remix_ladspa.c b/src/plugins/ladspa/remix_ladspa.c new file mode 100644 index 0000000..cdb6984 --- /dev/null +++ b/src/plugins/ladspa/remix_ladspa.c @@ -0,0 +1,1045 @@ +/* + * LADSPA wrapper plugin for libremix + * + * Copyright (C) 2000 Conrad Parker + * Copyright (C) 2001 Commonwealth Scientific and Industrial Research + * Organisation (CSIRO), Australia. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +/* + * This file assumes that both LADSPA and libremix are built with + * an audio datatype of 'float'. + */ + +#include "config.h" + +#include <stdlib.h> +#include <stdio.h> +#include <sys/types.h> +#include <dirent.h> +#include <string.h> +#include <math.h> /* for ceil() */ + +#include <dlfcn.h> +#include <sys/stat.h> + +#define __REMIX_PLUGIN__ +#include <remix/remix.h> + +#include "ladspa.h" + +#define PATH_LEN 1024 + +/* Compile in support for inplace processing? */ +#define _PROCESS_INPLACE + +#ifdef _PROCESS_INPLACE +#define LADSPA_WRAPPER_IS_INPLACE_BROKEN(x) LADSPA_IS_INPLACE_BROKEN(x) +#else +#define LADSPA_WRAPPER_IS_INPLACE_BROKEN(x) (1L) +#endif + +#define LADSPA_IS_CONTROL_INPUT(x) (LADSPA_IS_PORT_INPUT(x) && LADSPA_IS_PORT_CONTROL(x)) +#define LADSPA_IS_AUDIO_INPUT(x) (LADSPA_IS_PORT_INPUT(x) && LADSPA_IS_PORT_AUDIO(x)) +#define LADSPA_IS_CONTROL_OUTPUT(x) (LADSPA_IS_PORT_OUTPUT(x) && LADSPA_IS_PORT_CONTROL(x)) +#define LADSPA_IS_AUDIO_OUTPUT(x) (LADSPA_IS_PORT_OUTPUT(x) && LADSPA_IS_PORT_AUDIO(x)) + +#define LADSPA_frames_to_bytes(f) (f * sizeof(LADSPA_Data)) + +static char * default_ladspa_path = "/usr/lib/ladspa:/usr/local/lib/ladspa:/opt/ladspa/lib"; + +#ifndef WIN32 +# define remix_stat_regular(mode) (S_ISREG((mode)) || S_ISLNK((mode))) +#else +# define remix_stat_regular(mode) ((mode) & S_IFREG) +#endif + +/* Dummy control output, used to connect all LADSPA control outputs to */ +static LADSPA_Data dummy_control_output; + +static CDList * modules_list = CD_EMPTY_LIST; +static int ladspa_wrapper_initialised = FALSE; + + +typedef struct _RemixLADSPA RemixLADSPA; + +struct _RemixLADSPA { + unsigned long samplerate; /* samplerate initialised at */ + LADSPA_Descriptor * d; + LADSPA_Handle * handle; + LADSPA_Data * control_inputs; +}; + +static RemixBase * remix_ladspa_optimise (RemixEnv * env, RemixBase * base); + +/* + * is_usable (d) + * + * Determine if a LADSPA_Descriptor * d is usable by this remix ladspa + * wrapper plugin. Currently this means that there is not more than 1 + * audio input or more than 1 audio output. + */ +static int +is_usable(const LADSPA_Descriptor * d) +{ + LADSPA_PortDescriptor pd; + int i; + int + nr_ai=0, /* audio inputs */ + nr_ao=0; /* audio outputs */ + + for (i=0; i < d->PortCount; i++) { + pd = d->PortDescriptors[i]; + if (LADSPA_IS_AUDIO_INPUT(pd)) + nr_ai++; + if (LADSPA_IS_AUDIO_OUTPUT(pd)) + nr_ao++; + } + + /* Sanity checks */ + if (! d->run) return FALSE; /* plugin does nothing! */ + if (! d->instantiate) return FALSE; /* plugin cannot be instantiated */ + if (! d->connect_port) return FALSE; /* plugin cannot be wired up */ + + if (nr_ao == 1 && nr_ai == 1) return TRUE; + if (nr_ao == 0 && nr_ai == 1) return TRUE; + if (nr_ao == 1 && nr_ai == 0) return TRUE; + + return FALSE; +} + +static RemixParameterType +convert_type (const LADSPA_PortRangeHintDescriptor prhd) +{ + if (LADSPA_IS_HINT_TOGGLED(prhd)) + return REMIX_TYPE_BOOL; + else if (LADSPA_IS_HINT_INTEGER(prhd)) + return REMIX_TYPE_INT; + else + return REMIX_TYPE_FLOAT; +} + +static RemixFlags +get_valid_mask (const LADSPA_PortRangeHintDescriptor prhd) +{ + RemixFlags ret = 0; + + if (LADSPA_IS_HINT_BOUNDED_BELOW(prhd)) + ret &= REMIX_RANGE_LOWER_BOUND_VALID; + if (LADSPA_IS_HINT_BOUNDED_ABOVE(prhd)) + ret &= REMIX_RANGE_UPPER_BOUND_VALID; + + return ret; +} + +static RemixParameterRange * +convert_constraint (const LADSPA_PortRangeHint * prh) +{ + RemixParameterRange * pr; + LADSPA_PortRangeHintDescriptor prhd = prh->HintDescriptor; + + if (LADSPA_IS_HINT_TOGGLED(prhd)) + return NULL; + + pr = malloc (sizeof (*pr)); + + pr->valid_mask = get_valid_mask (prhd); + + if (LADSPA_IS_HINT_INTEGER(prhd)) { + if (LADSPA_IS_HINT_BOUNDED_BELOW(prhd)) + pr->lower.s_int = (int)prh->LowerBound; + if (LADSPA_IS_HINT_BOUNDED_ABOVE(prhd)) + pr->upper.s_int = (int)prh->UpperBound; + } else { + if (LADSPA_IS_HINT_BOUNDED_BELOW(prhd)) + pr->lower.s_float = (float)prh->LowerBound; + if (LADSPA_IS_HINT_BOUNDED_ABOVE(prhd)) + pr->upper.s_float = (float)prh->UpperBound; + } + + return pr; +} + +static RemixBase * +remix_ladspa_replace_handle (RemixEnv * env, RemixBase * base) +{ + RemixPlugin * plugin = remix_base_get_plugin (env, base); + RemixLADSPA * al = (RemixLADSPA *) remix_base_get_instance_data (env, base); + LADSPA_Descriptor * d; + + if (al == NULL) { + remix_set_error (env, REMIX_ERROR_NOENTITY); + return RemixNone; + } + + if (al->d != NULL) + if (al->d->deactivate) al->d->deactivate (al->handle); + + al->samplerate = (unsigned long) remix_get_samplerate (env); + + al->d = d = (LADSPA_Descriptor *) plugin->plugin_data; + + if (d != NULL) { + al->handle = d->instantiate (d, al->samplerate); + if (d->activate) d->activate (al->handle); + } + + return base; +} + +static RemixBase * +remix_ladspa_init (RemixEnv * env, RemixBase * base, CDSet * parameters) +{ + RemixLADSPA * al = malloc (sizeof (*al)); + + remix_base_set_instance_data (env, base, al); + al->d = NULL; /* let this get set in replace_handle() */ + + remix_ladspa_replace_handle (env, base); + remix_ladspa_optimise (env, base); + return base; +} + +static RemixBase * +remix_ladspa_clone (RemixEnv * env, RemixBase * base) +{ + /* XXX: Most of this should really be handled in remix_base.c */ + RemixPlugin * plugin = remix_base_get_plugin (env, base); + RemixBase * new_base = remix_base_new (env); + remix_base_set_plugin (env, new_base, plugin); + remix_ladspa_init (env, new_base, CD_EMPTY_SET); + return new_base; +} + +static int +remix_ladspa_destroy (RemixEnv * env, RemixBase * base) +{ + RemixLADSPA * al = (RemixLADSPA *) remix_base_get_instance_data (env, base); + + if (al->d) { + if (al->d->deactivate) al->d->deactivate (al->handle); + } + + free (al->control_inputs); + free (al); + free (base); + + return 0; +} + +static int +remix_ladspa_ready (RemixEnv * env, RemixBase * base) +{ + unsigned long samplerate = (unsigned long)remix_get_samplerate (env); + RemixLADSPA * al = (RemixLADSPA *) remix_base_get_instance_data (env, base); + return (samplerate == al->samplerate); +} + +static RemixBase * +remix_ladspa_prepare (RemixEnv * env, RemixBase * base) +{ + remix_ladspa_replace_handle (env, base); + return base; + +} + +/* + * remix_ladspa_1_0: + * A RemixChunkChunkFunc for a mono LADSPA sound consumer. + */ +static RemixCount +remix_ladspa_1_0 (RemixEnv * env, RemixChunk * chunk, RemixCount offset, + RemixCount count, int channelname, void * data) +{ + RemixLADSPA * al = (RemixLADSPA *) data; + LADSPA_Descriptor * d; + LADSPA_PortDescriptor pd; + unsigned long port_i; + + d = al->d; + + /* Connect audio input */ + for (port_i = 0; port_i < d->PortCount; port_i++) { + pd = d->PortDescriptors[(int)port_i]; + if (LADSPA_IS_AUDIO_INPUT(pd)) { + d->connect_port (al->handle, port_i, &chunk->data[offset]); + } + } + + d->run (al->handle, count); + + return count; +} + +/* + * remix_ladspa_0_1: + * A RemixChunkChunkFunc for a mono LADSPA sound generator. + */ +static RemixCount +remix_ladspa_0_1 (RemixEnv * env, RemixChunk * chunk, RemixCount offset, + RemixCount count, int channelname, void * data) +{ + RemixLADSPA * al = (RemixLADSPA *) data; + LADSPA_Descriptor * d; + LADSPA_PortDescriptor pd; + unsigned long port_i; + + d = al->d; + + /* Connect audio output */ + for (port_i = 0; port_i < d->PortCount; port_i++) { + pd = d->PortDescriptors[(int)port_i]; + if (LADSPA_IS_AUDIO_OUTPUT(pd)) { + d->connect_port (al->handle, port_i, &chunk->data[offset]); + } + } + + d->run (al->handle, count); + + return count; +} + +/* + * remix_ladspa_1_1: + * A RemixChunkChunkFunc for filtering with a mono LADSPA plugin. + */ +static RemixCount +remix_ladspa_1_1 (RemixEnv * env, RemixChunk * src, RemixCount src_offset, + RemixChunk * dest, RemixCount dest_offset, + RemixCount count, int channelname, void * data) +{ + RemixLADSPA * al = (RemixLADSPA *) data; + LADSPA_Descriptor * d; + LADSPA_PortDescriptor pd; + unsigned long port_i; + + d = al->d; + + if (al == NULL) { + remix_set_error (env, REMIX_ERROR_INVALID); + return -1; + } + + for (port_i = 0; port_i < d->PortCount; port_i++) { + pd = d->PortDescriptors[(int)port_i]; + if (LADSPA_IS_AUDIO_INPUT(pd)) { + d->connect_port (al->handle, port_i, &src->data[src_offset]); + } + if (LADSPA_IS_AUDIO_OUTPUT(pd)) { + d->connect_port (al->handle, port_i, &dest->data[dest_offset]); + } + } + + d->run (al->handle, count); + + return count; +} + + +#if 0 +static void +ladspa_wrapper_apply_region (RemixEnv * env, gpointer pcmdata, sw_format * format, + gint nr_frames, + sw_param_set pset, void * custom_data) +{ + lm_custom * lm = (lm_custom *)custom_data; + const LADSPA_Descriptor * d = lm->d; + sw_param_spec * param_specs = lm->param_specs; + + LADSPA_Handle * handle; + LADSPA_Data ** input_buffers, ** output_buffers; + LADSPA_Data * mono_input_buffer=NULL; + LADSPA_Data * p; + LADSPA_Data * control_inputs; + LADSPA_Data dummy_control_output; + LADSPA_PortDescriptor pd; + long length_b; + unsigned long port_i; /* counter for iterating over ports */ + int i, j, n; + + /* The number of times the plugin will be run; ie. if the number of + * channels in the input pcmdata is greater than the number of + * audio ports on the ladspa plugin, the plugin will be run + * multiple times until enough output channels have been calculated. + */ + gint iterations; + + /* Enumerate the numbers of each type of port on the ladspa plugin */ + gint + nr_ci=0, /* control inputs */ + nr_ai=0, /* audio inputs */ + nr_co=0, /* control outputs */ + nr_ao=0; /* audio outputs */ + + /* The number of audio channels to be processed */ + gint nr_channels = format->channels; + + /* The number of input and output buffers to use */ + gint nr_i=0, nr_o=0; + + /* Counters for allocating input and output buffers */ + gint ibi=0, obi=0; + + + /* instantiate the ladspa plugin */ + handle = d->instantiate (d, (long)format->rate); + + /* Cache how many of each type of port this ladspa plugin has */ + for (port_i=0; port_i < d->PortCount; port_i++) { + pd = d->PortDescriptors[(int)port_i]; + if (LADSPA_IS_CONTROL_INPUT(pd)) + nr_ci++; + if (LADSPA_IS_AUDIO_INPUT(pd)) + nr_ai++; + if (LADSPA_IS_CONTROL_OUTPUT(pd)) + nr_co++; + if (LADSPA_IS_AUDIO_OUTPUT(pd)) + nr_ao++; + } + + /* Basic assumption of this wrapper plugin, which was + * checked above in is_usable(); nb. for future expansion + * much of this routine is written to accomodate this + * assumption being incorrect. + */ + g_assert (nr_ai == nr_ao); + + /* Basic assumption that this plugin has audio output. + * Also important as we are about to divide by nr_ao. + */ + g_assert (nr_ao > 0); + + iterations = (gint) ceil(((double)nr_channels) / ((double)nr_ao)); + + /* Numbers of input and output buffers: ensure + * nr_i >= nr_channels && nr_o >= nr_channels + */ + nr_i = iterations * nr_ai; + nr_o = iterations * nr_ao; + + if ((nr_channels == 1) && (nr_ai == 1) && (nr_ao >= 1)) { + /* + * Processing a mono sample with a mono filter. + * Attempt to do this in place. + */ + + /* Copy PCM data if this ladspa plugin cannot work inplace */ + if (LADSPA_WRAPPER_IS_INPLACE_BROKEN(d->Properties)) { + length_b = frames_to_bytes (format, nr_frames); + mono_input_buffer = g_malloc (length_b); + input_buffers = &mono_input_buffer; + } else { + input_buffers = (LADSPA_Data **)&pcmdata; + } + + output_buffers = (LADSPA_Data **)&pcmdata; + + } else { + length_b = LADSPA_frames_to_bytes (nr_frames); + + /* Allocate zeroed input buffers; these will remain zeroed + * if there aren't enough channels in the input pcmdata + * to use them. + */ + input_buffers = g_malloc (sizeof(LADSPA_Data *) * nr_i); + for (i=0; i < nr_i; i++) { + input_buffers[i] = g_malloc0 (length_b); + } + + output_buffers = g_malloc(sizeof(LADSPA_Data *) * nr_o); + + /* Create separate output buffers if this ladspa plugin cannot + * work inplace */ + if (LADSPA_WRAPPER_IS_INPLACE_BROKEN(d->Properties)) { + for (i=0; i < nr_o; i++) { + output_buffers[i] = g_malloc (length_b); + } + } else { + /* Re-use the input buffers, directly mapping them to + * corresponding output buffers + */ + for (i=0; i < MIN(nr_i, nr_o); i++) { + output_buffers[i] = input_buffers[i]; + } + /* Create some extra output buffers if nr_o > nr_i */ + for (; i < nr_o; i++) { + output_buffers[i] = g_malloc (length_b); + } + } + } + + /* Copy data into input buffers */ + if (nr_channels == 1) { + if (!LADSPA_WRAPPER_IS_INPLACE_BROKEN(d->Properties)) { + length_b = frames_to_bytes (format, nr_frames); + memcpy (input_buffers[0], pcmdata, length_b); + } /* else we're processing in-place, so we haven't needed to set + * up a separate input buffer; input_buffers[0] actually + * points to pcmdata hence we don't do any copying here. + */ + } else { + /* de-interleave multichannel data */ + + p = (LADSPA_Data *)pcmdata; + + for (n=0; n < nr_channels; n++) { + for (i=0; i < nr_frames; i++) { + input_buffers[n][i] = *p++; + } + } + } + + /* connect control ports */ + control_inputs = g_malloc (nr_ci * sizeof(LADSPA_Data)); + j=0; + for (port_i=0; port_i < d->PortCount; port_i++) { + pd = d->PortDescriptors[(int)port_i]; + if (LADSPA_IS_CONTROL_INPUT(pd)) { + /* do something with pset! */ + switch (param_specs[j].type) { + case SWEEP_TYPE_BOOL: + /* from ladspa.h: + * Data less than or equal to zero should be considered + * `off' or `false,' + * and data above zero should be considered `on' or `true.' + */ + control_inputs[j] = pset[j].b ? 1.0 : 0.0; + break; + case SWEEP_TYPE_INT: + control_inputs[j] = (LADSPA_Data)pset[j].i; + break; + case SWEEP_TYPE_FLOAT: + control_inputs[j] = pset[j].f; + break; + default: + /* This plugin should produce no other types */ + g_assert_not_reached (); + break; + } + d->connect_port (handle, port_i, &control_inputs[j]); + j++; + } + if (LADSPA_IS_CONTROL_OUTPUT(pd)) { + d->connect_port (handle, port_i, &dummy_control_output); + } + } + + /* run the plugin as many times as necessary */ + while (iterations--) { + + /* connect input and output audio buffers to the + * audio ports of the ladspa plugin */ + for (port_i=0; port_i < d->PortCount; port_i++) { + pd = d->PortDescriptors[(int)port_i]; + if (LADSPA_IS_AUDIO_INPUT(pd)) { + d->connect_port (handle, port_i, input_buffers[ibi++]); + } + if (LADSPA_IS_AUDIO_OUTPUT(pd)) { + d->connect_port (handle, port_i, output_buffers[obi++]); + } + } + + /* activate the ladspa plugin */ + if (d->activate) + d->activate (handle); + + /* run the ladspa plugin */ + d->run (handle, nr_frames); + + /* deactivate the ladspa plugin */ + if (d->deactivate) + d->deactivate (handle); + } + + /* re-interleave data */ + if (nr_channels > 1) { + p = (LADSPA_Data *)pcmdata; + + for (n=0; n < nr_channels; n++) { + for (i=0; i < nr_frames; i++) { + *p++ = output_buffers[n][i]; + } + } + } + + /* let the ladspa plugin clean up after itself */ + if (d->cleanup) + d->cleanup (handle); + + /* free the input and output buffers */ + if (control_inputs) g_free (control_inputs); + + if ((nr_channels == 1) && (nr_ai == 1) && (nr_ao >= 1)) { + if (LADSPA_WRAPPER_IS_INPLACE_BROKEN(d->Properties)) { + g_free (mono_input_buffer); + } + } else { + + /* free the output buffers */ + for (i=0; i < nr_o; i++) { + g_free (output_buffers[i]); + } + g_free (output_buffers); + + /* free the input buffers, if we created some */ + if (LADSPA_WRAPPER_IS_INPLACE_BROKEN(d->Properties)) { + for (i=0; i < nr_i; i++) { + g_free (input_buffers[i]); + } + } else { + /* inplace worked, but if (nr_i > nr_o), then + * we still need to free the last input buffers + */ + for (i=nr_o; i < nr_i; i++) { + g_free (input_buffers[i]); + } + } + g_free (input_buffers); + } +} +#endif + +static RemixBase * +remix_ladspa_connect_control_inputs (RemixEnv * env, RemixBase * base) +{ + RemixLADSPA * al = (RemixLADSPA *) remix_base_get_instance_data (env, base); + LADSPA_Descriptor * d; + LADSPA_PortDescriptor pd; + RemixParameter parameter; + RemixParameterType type; + int j; + unsigned long port_i; + + d = al->d; + + if (d == NULL) { + remix_set_error (env, REMIX_ERROR_NOENTITY); + return RemixNone; + } + + j=0; + for (port_i=0; port_i < d->PortCount; port_i++) { + pd = d->PortDescriptors[(int)port_i]; + if (LADSPA_IS_CONTROL_INPUT(pd)) { + type = remix_get_parameter_type (env, base, j); + parameter = remix_get_parameter (env, base, j); + switch (type) { + case REMIX_TYPE_BOOL: + /* from ladspa.h: + * Data less than or equal to zero should be considered + * `off' or `false,' + * and data above zero should be considered `on' or `true.' + */ + al->control_inputs[j] = parameter.s_bool ? 1.0 : 0.0; + break; + case REMIX_TYPE_INT: + al->control_inputs[j] = (LADSPA_Data)parameter.s_int; + break; + case REMIX_TYPE_FLOAT: + al->control_inputs[j] = parameter.s_float; + break; + default: + /* This plugin should produce no other types */ + break; + } + d->connect_port (al->handle, port_i, &al->control_inputs[j]); + j++; + } + if (LADSPA_IS_CONTROL_OUTPUT(pd)) { + d->connect_port (al->handle, port_i, &dummy_control_output); + } + } + + return base; +} + +static RemixCount +remix_ladspa_1_0_process (RemixEnv * env, RemixBase * base, RemixCount count, + RemixStream * input, RemixStream * output) +{ + RemixLADSPA * al = remix_base_get_instance_data (env, base); + remix_ladspa_connect_control_inputs (env, base); + return remix_stream_chunkfuncify (env, input, count, remix_ladspa_1_0, al); +} + +static RemixCount +remix_ladspa_0_1_process (RemixEnv * env, RemixBase * base, RemixCount count, + RemixStream * input, RemixStream * output) +{ + RemixLADSPA * al = remix_base_get_instance_data (env, base); + remix_ladspa_connect_control_inputs (env, base); + return remix_stream_chunkfuncify (env, output, count, remix_ladspa_0_1, al); +} + +static RemixCount +remix_ladspa_1_1_process (RemixEnv * env, RemixBase * base, RemixCount count, + RemixStream * input, RemixStream * output) +{ + RemixLADSPA * al = remix_base_get_instance_data (env, base); + remix_ladspa_connect_control_inputs (env, base); + return remix_stream_chunkchunkfuncify (env, input, output, count, + remix_ladspa_1_1, al); +} + +static RemixCount +remix_ladspa_process (RemixEnv * env, RemixBase * base, RemixCount count, + RemixStream * input, RemixStream * output) +{ + RemixPlugin * plugin = remix_base_get_plugin (env, base); + RemixLADSPA * al; + LADSPA_PortDescriptor pd; + unsigned long port_i; /* counter for iterating over ports */ + + /* Enumerate the numbers of each type of port on the ladspa plugin */ + int + nr_ci=0, /* control inputs */ + nr_ai=0, /* audio inputs */ + nr_co=0, /* control outputs */ + nr_ao=0; /* audio outputs */ + + if (plugin == RemixNone) { + remix_set_error (env, REMIX_ERROR_NOENTITY); + return -1; + } + + remix_ladspa_connect_control_inputs (env, base); + + al = remix_base_get_instance_data (env, base); + + /* Cache how many of each type of port this ladspa plugin has */ + for (port_i=0; port_i < al->d->PortCount; port_i++) { + pd = al->d->PortDescriptors[(int)port_i]; + if (LADSPA_IS_CONTROL_INPUT(pd)) + nr_ci++; + if (LADSPA_IS_AUDIO_INPUT(pd)) + nr_ai++; + if (LADSPA_IS_CONTROL_OUTPUT(pd)) + nr_co++; + if (LADSPA_IS_AUDIO_OUTPUT(pd)) + nr_ao++; + } + + if (nr_ai == 1 && nr_ao == 1) { + return remix_stream_chunkchunkfuncify (env, input, output, count, + remix_ladspa_1_1, al); + } else if (nr_ai == 1 && nr_ao == 0) { + return remix_stream_chunkfuncify (env, input, count, remix_ladspa_1_0, al); + } else if (nr_ai == 0 && nr_ao == 1) { + return remix_stream_chunkfuncify (env, output, count, remix_ladspa_0_1, al); + } else { + remix_set_error (env, REMIX_ERROR_INVALID); + return -1; + } +} + +static RemixCount +remix_ladspa_length (RemixEnv * env, RemixBase * base) +{ + return REMIX_COUNT_INFINITE; +} + +static struct _RemixMethods _remix_ladspa_1_0_methods = { + remix_ladspa_clone, + remix_ladspa_destroy, + remix_ladspa_ready, /* ready */ + remix_ladspa_prepare, /* prepare */ + remix_ladspa_1_0_process, + remix_ladspa_length, + NULL, /* seek */ + NULL, /* flush */ +}; + +static struct _RemixMethods _remix_ladspa_0_1_methods = { + remix_ladspa_clone, + remix_ladspa_destroy, + remix_ladspa_ready, /* ready */ + remix_ladspa_prepare, /* prepare */ + remix_ladspa_0_1_process, + remix_ladspa_length, + NULL, /* seek */ + NULL, /* flush */ +}; + +static struct _RemixMethods _remix_ladspa_1_1_methods = { + remix_ladspa_clone, + remix_ladspa_destroy, + remix_ladspa_ready, /* ready */ + remix_ladspa_prepare, /* prepare */ + remix_ladspa_1_1_process, + remix_ladspa_length, + NULL, /* seek */ + NULL, /* flush */ +}; + + +static struct _RemixMethods _remix_ladspa_methods = { + remix_ladspa_clone, + remix_ladspa_destroy, + remix_ladspa_ready, /* ready */ + remix_ladspa_prepare, /* prepare */ + remix_ladspa_process, + remix_ladspa_length, + NULL, /* seek */ + NULL, /* flush */ +}; + +static RemixBase * +remix_ladspa_optimise (RemixEnv * env, RemixBase * base) +{ + RemixLADSPA * al = remix_base_get_instance_data (env, base); + LADSPA_Descriptor * d; + LADSPA_PortDescriptor pd; + unsigned long port_i; /* counter for iterating over ports */ + + /* Enumerate the numbers of each type of port on the ladspa plugin */ + int + nr_ci=0, /* control inputs */ + nr_ai=0, /* audio inputs */ + nr_co=0, /* control outputs */ + nr_ao=0; /* audio outputs */ + + if (al == NULL) { + remix_set_error (env, REMIX_ERROR_NOENTITY); + return RemixNone; + } + + d = al->d; + + if (d == NULL) { + remix_set_error (env, REMIX_ERROR_NOENTITY); + return RemixNone; + } + + /* Cache how many of each type of port this ladspa plugin has */ + for (port_i=0; port_i < d->PortCount; port_i++) { + pd = d->PortDescriptors[(int)port_i]; + if (LADSPA_IS_CONTROL_INPUT(pd)) + nr_ci++; + if (LADSPA_IS_AUDIO_INPUT(pd)) + nr_ai++; + if (LADSPA_IS_CONTROL_OUTPUT(pd)) + nr_co++; + if (LADSPA_IS_AUDIO_OUTPUT(pd)) + nr_ao++; + } + + al->control_inputs = malloc (nr_ci * sizeof (LADSPA_Data)); + + if (nr_ai == 1 && nr_ao == 1) { + remix_base_set_methods (env, base, &_remix_ladspa_1_1_methods); + } else if (nr_ai == 1 && nr_ao == 0) { + remix_base_set_methods (env, base, &_remix_ladspa_1_0_methods); + } else if (nr_ai == 0 && nr_ao == 1) { + remix_base_set_methods (env, base, &_remix_ladspa_0_1_methods); + } else { + remix_base_set_methods (env, base, &_remix_ladspa_methods); + } + + return base; +} + + +/* + * ladspa_wrapper_load_plugins (dir, name, gl) + * + * form RemixPlugins to describe the ladspa plugin functions that + * are in the shared library file "dir/name" + */ +static CDList * +ladspa_wrapper_load_plugins (RemixEnv * env, char * dir, char * name) +{ + char path[PATH_LEN]; + void * module; + LADSPA_Descriptor_Function desc_func; + const LADSPA_Descriptor * d; + LADSPA_PortDescriptor pd; + int i, j, k; + int valid_mask; + RemixPlugin * plugin; + RemixMetaText * mt; + RemixParameterScheme * scheme; + CDList * l, * plugins = CD_EMPTY_LIST; +#define BUF_LEN 256 + static char buf[BUF_LEN]; + struct stat statbuf; + + snprintf (path, PATH_LEN, "%s/%s", dir, name); + + if (stat (path, &statbuf) == -1) return CD_EMPTY_LIST; + if (!remix_stat_regular (statbuf.st_mode)) return CD_EMPTY_LIST; + + module = dlopen (path, RTLD_NOW); + if (!module) return CD_EMPTY_LIST; + + /* Check that this module has not already been loaded (eg. if it is + * a symlink etc.) */ + for (l = modules_list; l; l = l->next) { + if (l->data.s_pointer == module) { + dlclose (module); + return CD_EMPTY_LIST; + } + } + + modules_list = cd_list_append (env, modules_list, CD_POINTER(module)); + + if ((desc_func = dlsym (module, "ladspa_descriptor"))) { + for (i=0; (d = desc_func (i)) != NULL; i++) { + + if (!is_usable(d)) + continue; + + remix_dprintf ("[ladspa_wrapper_load_plugins] adding %s [%lu] by %s\n", + d->Name, d->UniqueID, d->Maker); + + plugin = malloc (sizeof (*plugin)); + + mt = remix_meta_text_new (env); + + snprintf (buf, BUF_LEN, "ladspa::%lu", d->UniqueID); + remix_meta_text_set_identifier (env, mt, strdup (buf)); + + snprintf (buf, BUF_LEN, "Miscellaneous::%s", d->Name); + remix_meta_text_set_category (env, mt, strdup (buf)); + + remix_meta_text_set_copyright (env, mt, (char *)d->Copyright); + remix_meta_text_add_author (env, mt, (char *)d->Maker, NULL); + + plugin->metatext = mt; + + plugin->init_scheme = CD_EMPTY_SET; + plugin->process_scheme = CD_EMPTY_SET; + + k=0; + for (j=0; j < d->PortCount; j++) { + pd = d->PortDescriptors[j]; + if (LADSPA_IS_CONTROL_INPUT(pd)) { + scheme = malloc (sizeof (*scheme)); + + scheme->name = (char *)d->PortNames[j]; + scheme->description = (char *)d->PortNames[j]; + scheme->type = convert_type (d->PortRangeHints[j].HintDescriptor); + valid_mask = get_valid_mask (d->PortRangeHints[j].HintDescriptor); + if (valid_mask == 0) { + scheme->constraint_type = REMIX_CONSTRAINT_TYPE_NONE; + } else { + scheme->constraint_type = REMIX_CONSTRAINT_TYPE_RANGE; + scheme->constraint.range = + convert_constraint (&d->PortRangeHints[j]); + } + plugin->process_scheme = cd_set_insert (env, plugin->process_scheme, + k, CD_POINTER(scheme)); + k++; + } + } + + plugin->init = remix_ladspa_init; + + plugin->plugin_data = (void *)d; + + plugin->destroy = NULL; + + plugins = cd_list_append (env, plugins, CD_POINTER(plugin)); + } + } + + return plugins; + +#undef BUF_LEN +} + +/* + * ladspa_wrapper_load_dir (dir, gl) + * + * scan a directory "dirname" for LADSPA plugins, and attempt to load + * each of them. + */ +static CDList * +ladspa_wrapper_load_dir (RemixEnv * env, char * dirname) +{ + DIR * dir; + struct dirent * dirent; + CDList * plugins = CD_EMPTY_LIST, * l; + + if (!dirname) return plugins; + + dir = opendir (dirname); + if (!dir) { + return plugins; + } + + while ((dirent = readdir (dir)) != NULL) { + l = ladspa_wrapper_load_plugins (env, dirname, dirent->d_name); + plugins = cd_list_join (env, plugins, l); + } + + closedir (dir); + + return plugins; +} + +CDList * +remix_load (RemixEnv * env) +{ + CDList * plugins = CD_EMPTY_LIST, * l; + char * ladspa_path=NULL; + char * next_sep=NULL; + char * saved_lp=NULL; + + /* If this ladspa_wrapper module has already been initialised, don't + * initialise again until cleaned up. + */ + if (ladspa_wrapper_initialised) + return CD_EMPTY_LIST; + + ladspa_path = getenv ("LADSPA_PATH"); + if (!ladspa_path) + ladspa_path = saved_lp = strdup(default_ladspa_path); + + do { + next_sep = strchr (ladspa_path, ':'); + if (next_sep != NULL) *next_sep = '\0'; + + l = ladspa_wrapper_load_dir (env, ladspa_path); + plugins = cd_list_join (env, plugins, l); + + if (next_sep != NULL) ladspa_path = ++next_sep; + + } while ((next_sep != NULL) && (*next_sep != '\0')); + + ladspa_wrapper_initialised = TRUE; + + /* free string if dup'd for ladspa_path */ + if (saved_lp != NULL) free(saved_lp); + + return plugins; +} + +void +remix_unload (RemixEnv * env) +{ + CDList * l; + + if (!ladspa_wrapper_initialised) return; + + for (l = modules_list; l; l = l->next) { + dlclose(l->data.s_pointer); + } + + modules_list = NULL; +} + |