From 1f231e2e1ac9f568265da52bca4ca9d33d9a42cc Mon Sep 17 00:00:00 2001 From: Jaroslav Kysela Date: Tue, 13 Dec 2005 14:08:58 +0000 Subject: [PATCH] big ALSA LADSPA plugin rewrite for multiple channel LADSPA plugins This is major rewrite of the LADSPA plugin to satisfy requirements for the LADSPA plugins with multiple audio inputs and / or outputs. --- src/pcm/ladspa.h | 129 +++++- src/pcm/pcm_ladspa.c | 922 ++++++++++++++++++++++++++++++------------- 2 files changed, 763 insertions(+), 288 deletions(-) diff --git a/src/pcm/ladspa.h b/src/pcm/ladspa.h index 5e2fa98d..5c30a8a4 100644 --- a/src/pcm/ladspa.h +++ b/src/pcm/ladspa.h @@ -1,7 +1,7 @@ /* ladspa.h - Linux Audio Developer's Simple Plugin API Version 1.0[LGPL]. - Copyright (C) 2000-2001 Richard W.E. Furse, Paul Barton-Davis, + Linux Audio Developer's Simple Plugin API Version 1.1[LGPL]. + Copyright (C) 2000-2002 Richard W.E. Furse, Paul Barton-Davis, Stefan Westerfeld. This library is free software; you can redistribute it and/or @@ -22,6 +22,10 @@ #ifndef LADSPA_INCLUDED #define LADSPA_INCLUDED +#define LADSPA_VERSION "1.1" +#define LADSPA_VERSION_MAJOR 1 +#define LADSPA_VERSION_MINOR 1 + #ifdef __cplusplus extern "C" { #endif @@ -72,7 +76,10 @@ extern "C" { /* Fundamental data type passed in and out of plugin. This data type is used to communicate audio samples and control values. It is assumed that the plugin will work sensibly given any numeric input - value although it may have a preferred range (see hints below). */ + value although it may have a preferred range (see hints below). + + For audio it is generally assumed that 1.0f is the `0dB' reference + amplitude and is a `normal' signal level. */ typedef float LADSPA_Data; @@ -198,7 +205,7 @@ typedef int LADSPA_PortRangeHintDescriptor; bound of the valid range. If LADSPA_HINT_SAMPLE_RATE is also specified then the value of LowerBound should be multiplied by the sample rate. */ -#define LADSPA_HINT_BOUNDED_BELOW 0x1 +#define LADSPA_HINT_BOUNDED_BELOW 0x1 /* Hint LADSPA_HINT_BOUNDED_ABOVE indicates that the UpperBound field of the LADSPA_PortRangeHint should be considered meaningful. The @@ -206,14 +213,15 @@ typedef int LADSPA_PortRangeHintDescriptor; bound of the valid range. If LADSPA_HINT_SAMPLE_RATE is also specified then the value of UpperBound should be multiplied by the sample rate. */ -#define LADSPA_HINT_BOUNDED_ABOVE 0x2 +#define LADSPA_HINT_BOUNDED_ABOVE 0x2 /* Hint LADSPA_HINT_TOGGLED indicates that the data item should be considered a Boolean toggle. Data less than or equal to zero should be considered `off' or `false,' and data above zero should be considered `on' or `true.' LADSPA_HINT_TOGGLED may not be used in - conjunction with any other hint. */ -#define LADSPA_HINT_TOGGLED 0x4 + conjunction with any other hint except LADSPA_HINT_DEFAULT_0 or + LADSPA_HINT_DEFAULT_1. */ +#define LADSPA_HINT_TOGGLED 0x4 /* Hint LADSPA_HINT_SAMPLE_RATE indicates that any bounds specified should be interpreted as multiples of the sample rate. For @@ -221,12 +229,12 @@ typedef int LADSPA_PortRangeHintDescriptor; the sample rate) could be requested by this hint in conjunction with LowerBound = 0 and UpperBound = 0.5. Hosts that support bounds at all must support this hint to retain meaning. */ -#define LADSPA_HINT_SAMPLE_RATE 0x8 +#define LADSPA_HINT_SAMPLE_RATE 0x8 /* Hint LADSPA_HINT_LOGARITHMIC indicates that it is likely that the user will find it more intuitive to view values using a logarithmic scale. This is particularly useful for frequencies and gains. */ -#define LADSPA_HINT_LOGARITHMIC 0x10 +#define LADSPA_HINT_LOGARITHMIC 0x10 /* Hint LADSPA_HINT_INTEGER indicates that a user interface would probably wish to provide a stepped control taking only integer @@ -234,14 +242,97 @@ typedef int LADSPA_PortRangeHintDescriptor; integer range required to avoid floating point rounding errors. For instance, the integer set {0,1,2,3} might be described as [-0.1, 3.1]. */ -#define LADSPA_HINT_INTEGER 0x20 - -#define LADSPA_IS_HINT_BOUNDED_BELOW(x) ((x) & LADSPA_HINT_BOUNDED_BELOW) -#define LADSPA_IS_HINT_BOUNDED_ABOVE(x) ((x) & LADSPA_HINT_BOUNDED_ABOVE) -#define LADSPA_IS_HINT_TOGGLED(x) ((x) & LADSPA_HINT_TOGGLED) -#define LADSPA_IS_HINT_SAMPLE_RATE(x) ((x) & LADSPA_HINT_SAMPLE_RATE) -#define LADSPA_IS_HINT_LOGARITHMIC(x) ((x) & LADSPA_HINT_LOGARITHMIC) -#define LADSPA_IS_HINT_INTEGER(x) ((x) & LADSPA_HINT_INTEGER) +#define LADSPA_HINT_INTEGER 0x20 + +/* The various LADSPA_HINT_HAS_DEFAULT_* hints indicate a `normal' + value for the port that is sensible as a default. For instance, + this value is suitable for use as an initial value in a user + interface or as a value the host might assign to a control port + when the user has not provided one. Defaults are encoded using a + mask so only one default may be specified for a port. Some of the + hints make use of lower and upper bounds, in which case the + relevant bound or bounds must be available and + LADSPA_HINT_SAMPLE_RATE must be applied as usual. The resulting + default must be rounded if LADSPA_HINT_INTEGER is present. Default + values were introduced in LADSPA v1.1. */ +#define LADSPA_HINT_DEFAULT_MASK 0x3C0 + +/* This default values indicates that no default is provided. */ +#define LADSPA_HINT_DEFAULT_NONE 0x0 + +/* This default hint indicates that the suggested lower bound for the + port should be used. */ +#define LADSPA_HINT_DEFAULT_MINIMUM 0x40 + +/* This default hint indicates that a low value between the suggested + lower and upper bounds should be chosen. For ports with + LADSPA_HINT_LOGARITHMIC, this should be exp(log(lower) * 0.75 + + log(upper) * 0.25). Otherwise, this should be (lower * 0.75 + upper + * 0.25). */ +#define LADSPA_HINT_DEFAULT_LOW 0x80 + +/* This default hint indicates that a middle value between the + suggested lower and upper bounds should be chosen. For ports with + LADSPA_HINT_LOGARITHMIC, this should be exp(log(lower) * 0.5 + + log(upper) * 0.5). Otherwise, this should be (lower * 0.5 + upper * + 0.5). */ +#define LADSPA_HINT_DEFAULT_MIDDLE 0xC0 + +/* This default hint indicates that a high value between the suggested + lower and upper bounds should be chosen. For ports with + LADSPA_HINT_LOGARITHMIC, this should be exp(log(lower) * 0.25 + + log(upper) * 0.75). Otherwise, this should be (lower * 0.25 + upper + * 0.75). */ +#define LADSPA_HINT_DEFAULT_HIGH 0x100 + +/* This default hint indicates that the suggested upper bound for the + port should be used. */ +#define LADSPA_HINT_DEFAULT_MAXIMUM 0x140 + +/* This default hint indicates that the number 0 should be used. Note + that this default may be used in conjunction with + LADSPA_HINT_TOGGLED. */ +#define LADSPA_HINT_DEFAULT_0 0x200 + +/* This default hint indicates that the number 1 should be used. Note + that this default may be used in conjunction with + LADSPA_HINT_TOGGLED. */ +#define LADSPA_HINT_DEFAULT_1 0x240 + +/* This default hint indicates that the number 100 should be used. */ +#define LADSPA_HINT_DEFAULT_100 0x280 + +/* This default hint indicates that the Hz frequency of `concert A' + should be used. This will be 440 unless the host uses an unusual + tuning convention, in which case it may be within a few Hz. */ +#define LADSPA_HINT_DEFAULT_440 0x2C0 + +#define LADSPA_IS_HINT_BOUNDED_BELOW(x) ((x) & LADSPA_HINT_BOUNDED_BELOW) +#define LADSPA_IS_HINT_BOUNDED_ABOVE(x) ((x) & LADSPA_HINT_BOUNDED_ABOVE) +#define LADSPA_IS_HINT_TOGGLED(x) ((x) & LADSPA_HINT_TOGGLED) +#define LADSPA_IS_HINT_SAMPLE_RATE(x) ((x) & LADSPA_HINT_SAMPLE_RATE) +#define LADSPA_IS_HINT_LOGARITHMIC(x) ((x) & LADSPA_HINT_LOGARITHMIC) +#define LADSPA_IS_HINT_INTEGER(x) ((x) & LADSPA_HINT_INTEGER) + +#define LADSPA_IS_HINT_HAS_DEFAULT(x) ((x) & LADSPA_HINT_DEFAULT_MASK) +#define LADSPA_IS_HINT_DEFAULT_MINIMUM(x) (((x) & LADSPA_HINT_DEFAULT_MASK) \ + == LADSPA_HINT_DEFAULT_MINIMUM) +#define LADSPA_IS_HINT_DEFAULT_LOW(x) (((x) & LADSPA_HINT_DEFAULT_MASK) \ + == LADSPA_HINT_DEFAULT_LOW) +#define LADSPA_IS_HINT_DEFAULT_MIDDLE(x) (((x) & LADSPA_HINT_DEFAULT_MASK) \ + == LADSPA_HINT_DEFAULT_MIDDLE) +#define LADSPA_IS_HINT_DEFAULT_HIGH(x) (((x) & LADSPA_HINT_DEFAULT_MASK) \ + == LADSPA_HINT_DEFAULT_HIGH) +#define LADSPA_IS_HINT_DEFAULT_MAXIMUM(x) (((x) & LADSPA_HINT_DEFAULT_MASK) \ + == LADSPA_HINT_DEFAULT_MAXIMUM) +#define LADSPA_IS_HINT_DEFAULT_0(x) (((x) & LADSPA_HINT_DEFAULT_MASK) \ + == LADSPA_HINT_DEFAULT_0) +#define LADSPA_IS_HINT_DEFAULT_1(x) (((x) & LADSPA_HINT_DEFAULT_MASK) \ + == LADSPA_HINT_DEFAULT_1) +#define LADSPA_IS_HINT_DEFAULT_100(x) (((x) & LADSPA_HINT_DEFAULT_MASK) \ + == LADSPA_HINT_DEFAULT_100) +#define LADSPA_IS_HINT_DEFAULT_440(x) (((x) & LADSPA_HINT_DEFAULT_MASK) \ + == LADSPA_HINT_DEFAULT_440) typedef struct _LADSPA_PortRangeHint { @@ -428,7 +519,7 @@ typedef struct _LADSPA_Descriptor { this function pointer must be set to NULL. When it is provided, the function set_run_adding_gain() must be provided also. */ void (*run_adding)(LADSPA_Handle Instance, - unsigned long SampleCount); + unsigned long SampleCount); /* This method is a function pointer that sets the output gain for use when run_adding() is called (see above). If this function is @@ -440,7 +531,7 @@ typedef struct _LADSPA_Descriptor { run_adding() function is provided. When it is absent this function pointer must be set to NULL. */ void (*set_run_adding_gain)(LADSPA_Handle Instance, - LADSPA_Data Gain); + LADSPA_Data Gain); /* This is the counterpart to activate() (see above). If there is nothing for deactivate() to do then the plugin writer may provide diff --git a/src/pcm/pcm_ladspa.c b/src/pcm/pcm_ladspa.c index dfde01e2..d8cebe51 100644 --- a/src/pcm/pcm_ladspa.c +++ b/src/pcm/pcm_ladspa.c @@ -29,6 +29,7 @@ #include #include #include +#include #include "pcm_local.h" #include "pcm_plugin.h" @@ -48,37 +49,46 @@ typedef enum _snd_pcm_ladspa_policy { SND_PCM_LADSPA_POLICY_DUPLICATE /* duplicate bindings for all channels */ } snd_pcm_ladspa_policy_t; -typedef struct snd_pcm_ladspa_instance snd_pcm_ladspa_instance_t; - typedef struct { /* This field need to be the first */ snd_pcm_plugin_t plug; + /* Plugin custom fields */ struct list_head pplugins; struct list_head cplugins; - unsigned int instances_channels; - snd_pcm_ladspa_instance_t **finstances; + unsigned int channels; /* forced input channels, 0 = auto */ + unsigned int allocated; /* count of allocated samples */ + LADSPA_Data *zero[2]; /* zero input or dummy output */ } snd_pcm_ladspa_t; + +typedef struct { + unsigned int size; + unsigned int *array; +} snd_pcm_ladspa_array_t; + +typedef struct { + snd_pcm_ladspa_array_t channels; + snd_pcm_ladspa_array_t ports; + LADSPA_Data **m_data; + LADSPA_Data **data; +} snd_pcm_ladspa_eps_t; -struct snd_pcm_ladspa_instance { +typedef struct snd_pcm_ladspa_instance { struct list_head list; const LADSPA_Descriptor *desc; LADSPA_Handle *handle; - LADSPA_Data *m_data; - LADSPA_Data *in_data; - LADSPA_Data *out_data; unsigned int depth; - unsigned int channel; - unsigned int in_port; - unsigned int out_port; - snd_pcm_ladspa_instance_t *prev; - snd_pcm_ladspa_instance_t *next; -}; + snd_pcm_ladspa_eps_t input; + snd_pcm_ladspa_eps_t output; + struct snd_pcm_ladspa_instance *prev; + struct snd_pcm_ladspa_instance *next; +} snd_pcm_ladspa_instance_t; typedef struct { LADSPA_PortDescriptor pdesc; /* port description */ unsigned int port_bindings_size; /* size of array */ unsigned int *port_bindings; /* index = channel number, value = LADSPA port */ unsigned int controls_size; /* size of array */ + unsigned char *controls_initialized; /* initialized by ALSA user */ LADSPA_Data *controls; /* index = LADSPA control port index */ } snd_pcm_ladspa_plugin_io_t; @@ -90,11 +100,22 @@ typedef struct { const LADSPA_Descriptor *desc; snd_pcm_ladspa_plugin_io_t input; snd_pcm_ladspa_plugin_io_t output; - struct list_head instances; + struct list_head instances; /* one LADSPA plugin might be used multiple times */ } snd_pcm_ladspa_plugin_t; #endif /* DOC_HIDDEN */ +static unsigned int snd_pcm_ladspa_count_ports(snd_pcm_ladspa_plugin_t *lplug, + LADSPA_PortDescriptor pdesc) +{ + unsigned int res = 0, idx; + for (idx = 0; idx < lplug->desc->PortCount; idx++) { + if ((lplug->desc->PortDescriptors[idx] & pdesc) == pdesc) + res++; + } + return res; +} + static int snd_pcm_ladspa_find_port(unsigned int *res, snd_pcm_ladspa_plugin_t *lplug, LADSPA_PortDescriptor pdesc, @@ -146,10 +167,20 @@ static int snd_pcm_ladspa_find_port_idx(unsigned int *res, return 0; } +static void snd_pcm_ladspa_free_io(snd_pcm_ladspa_plugin_io_t *io) +{ + if (io->controls) + free(io->controls); + if (io->controls_initialized) + free(io->controls_initialized); +} + static void snd_pcm_ladspa_free_plugins(struct list_head *plugins) { while (!list_empty(plugins)) { snd_pcm_ladspa_plugin_t *plugin = list_entry(plugins->next, snd_pcm_ladspa_plugin_t, list); + snd_pcm_ladspa_free_io(&plugin->input); + snd_pcm_ladspa_free_io(&plugin->output); if (plugin->dl_handle) dlclose(plugin->dl_handle); if (plugin->filename) @@ -161,8 +192,16 @@ static void snd_pcm_ladspa_free_plugins(struct list_head *plugins) static void snd_pcm_ladspa_free(snd_pcm_ladspa_t *ladspa) { + unsigned int idx; + snd_pcm_ladspa_free_plugins(&ladspa->pplugins); snd_pcm_ladspa_free_plugins(&ladspa->cplugins); + for (idx = 0; idx < 2; idx++) { + if (ladspa->zero[idx]) + free(ladspa->zero[idx]); + ladspa->zero[idx] = NULL; + } + ladspa->allocated = 0; } static int snd_pcm_ladspa_close(snd_pcm_t *pcm) @@ -175,7 +214,7 @@ static int snd_pcm_ladspa_close(snd_pcm_t *pcm) static int snd_pcm_ladspa_hw_refine_cprepare(snd_pcm_t *pcm ATTRIBUTE_UNUSED, snd_pcm_hw_params_t *params) { - // snd_pcm_ladspa_t *ladspa = pcm->private_data; + snd_pcm_ladspa_t *ladspa = pcm->private_data; int err; snd_pcm_access_mask_t access_mask = { SND_PCM_ACCBIT_SHMN }; err = _snd_pcm_hw_param_set_mask(params, SND_PCM_HW_PARAM_ACCESS, @@ -188,19 +227,26 @@ static int snd_pcm_ladspa_hw_refine_cprepare(snd_pcm_t *pcm ATTRIBUTE_UNUSED, sn err = _snd_pcm_hw_params_set_subformat(params, SND_PCM_SUBFORMAT_STD); if (err < 0) return err; + if (ladspa->channels > 0 && pcm->stream == SND_PCM_STREAM_PLAYBACK) { + err = _snd_pcm_hw_param_set(params, SND_PCM_HW_PARAM_CHANNELS, ladspa->channels, 0); + if (err < 0) + return err; + } params->info &= ~(SND_PCM_INFO_MMAP | SND_PCM_INFO_MMAP_VALID); return 0; } static int snd_pcm_ladspa_hw_refine_sprepare(snd_pcm_t *pcm ATTRIBUTE_UNUSED, snd_pcm_hw_params_t *sparams) { - // snd_pcm_ladspa_t *ladspa = pcm->private_data; + snd_pcm_ladspa_t *ladspa = pcm->private_data; snd_pcm_access_mask_t saccess_mask = { SND_PCM_ACCBIT_MMAPN }; _snd_pcm_hw_params_any(sparams); _snd_pcm_hw_param_set_mask(sparams, SND_PCM_HW_PARAM_ACCESS, &saccess_mask); _snd_pcm_hw_params_set_format(sparams, SND_PCM_FORMAT_FLOAT); _snd_pcm_hw_params_set_subformat(sparams, SND_PCM_SUBFORMAT_STD); + if (ladspa->channels > 0 && pcm->stream == SND_PCM_STREAM_CAPTURE) + _snd_pcm_hw_param_set(sparams, SND_PCM_HW_PARAM_CHANNELS, ladspa->channels, 0); return 0; } @@ -263,12 +309,19 @@ static int snd_pcm_ladspa_hw_params(snd_pcm_t *pcm, snd_pcm_hw_params_t * params return 0; } +static void snd_pcm_ladspa_free_eps(snd_pcm_ladspa_eps_t *eps) +{ + if (eps->channels.array) + free(eps->channels.array); + if (eps->ports.array) + free(eps->ports.array); +} + static void snd_pcm_ladspa_free_instances(snd_pcm_t *pcm, snd_pcm_ladspa_t *ladspa, int cleanup) { struct list_head *list, *pos, *pos1, *next1; + unsigned int idx; - if (ladspa->instances_channels == 0) - return; list = pcm->stream == SND_PCM_STREAM_PLAYBACK ? &ladspa->pplugins : &ladspa->cplugins; list_for_each(pos, list) { snd_pcm_ladspa_plugin_t *plugin = list_entry(pos, snd_pcm_ladspa_plugin_t, list); @@ -279,9 +332,21 @@ static void snd_pcm_ladspa_free_instances(snd_pcm_t *pcm, snd_pcm_ladspa_t *lads if (cleanup) { if (plugin->desc->cleanup) plugin->desc->cleanup(instance->handle); - if (instance->m_data) - free(instance->m_data); + if (instance->input.m_data) { + for (idx = 0; idx < instance->input.channels.size; idx++) + if (instance->input.m_data[idx]) + free(instance->input.m_data[idx]); + free(instance->input.m_data); + } + if (instance->output.m_data) { + for (idx = 0; idx < instance->output.channels.size; idx++) + if (instance->output.m_data[idx]) + free(instance->output.m_data[idx]); + free(instance->output.m_data); + } list_del(&(instance->list)); + snd_pcm_ladspa_free_eps(&instance->input); + snd_pcm_ladspa_free_eps(&instance->output); free(instance); } else { if (plugin->desc->activate) @@ -292,58 +357,106 @@ static void snd_pcm_ladspa_free_instances(snd_pcm_t *pcm, snd_pcm_ladspa_t *lads assert(list_empty(&plugin->instances)); } } - if (cleanup) { - ladspa->instances_channels = 0; - if (ladspa->finstances) { - free(ladspa->finstances); - ladspa->finstances = NULL; - } - } } -static int snd_pcm_ladspa_connect(snd_pcm_ladspa_plugin_t *plugin ATTRIBUTE_UNUSED, - snd_pcm_ladspa_plugin_io_t *io, - snd_pcm_ladspa_instance_t *instance, - unsigned int channel, - unsigned int port) +static int snd_pcm_ladspa_add_to_carray(snd_pcm_ladspa_array_t *array, + unsigned int idx, + unsigned int val) { - if (instance->channel == NO_ASSIGN) - instance->channel = channel; - else if (instance->channel != channel) - return -EINVAL; - if (io->pdesc == LADSPA_PORT_OUTPUT) { - instance->out_port = port; - } else { - instance->in_port = port; - } - return 0; + unsigned int *narray; + unsigned int idx1; + + if (idx >= array->size) { + narray = realloc(array->array, sizeof(unsigned int) * (idx + 1)); + if (narray == NULL) + return -ENOMEM; + for (idx1 = array->size; idx1 < idx; idx1++) + narray[idx1] = NO_ASSIGN; + array->array = narray; + array->size = idx + 1; + array->array[idx] = val; + return 0; + } + if (array->array[idx] == NO_ASSIGN) + array->array[idx] = val; + else + return -EINVAL; + return 0; } -static int snd_pcm_ladspa_connect_plugin(snd_pcm_ladspa_plugin_t *plugin, - snd_pcm_ladspa_plugin_io_t *io, - snd_pcm_ladspa_instance_t *instance, - unsigned int idx) +static int snd_pcm_ladspa_add_to_array(snd_pcm_ladspa_array_t *array, + unsigned int idx, + unsigned int val) { - unsigned int port; + unsigned int *narray; + unsigned int idx1; + + if (idx >= array->size) { + narray = realloc(array->array, sizeof(unsigned int) * (idx + 1)); + if (narray == NULL) + return -ENOMEM; + for (idx1 = array->size; idx1 < idx; idx1++) + narray[idx1] = NO_ASSIGN; + array->array = narray; + array->size = idx + 1; + } + array->array[idx] = val; + return 0; +} + +static int snd_pcm_ladspa_connect_plugin1(snd_pcm_ladspa_plugin_t *plugin, + snd_pcm_ladspa_plugin_io_t *io, + snd_pcm_ladspa_eps_t *eps) +{ + unsigned int port, channels, idx; int err; assert(plugin->policy == SND_PCM_LADSPA_POLICY_NONE); - if (io->port_bindings_size > 0) { - if (idx >= io->port_bindings_size) - return instance->channel != NO_ASSIGN ? -EINVAL : 0; - port = io->port_bindings[idx]; - } else { - err = snd_pcm_ladspa_find_port(&port, plugin, io->pdesc | LADSPA_PORT_AUDIO, idx); - if (err < 0) - return instance->channel != NO_ASSIGN ? err : 0; + channels = io->port_bindings_size > 0 ? + io->port_bindings_size : + snd_pcm_ladspa_count_ports(plugin, io->pdesc | LADSPA_PORT_AUDIO); + for (idx = 0; idx < channels; idx++) { + if (io->port_bindings_size > 0) + port = io->port_bindings[idx]; + else { + err = snd_pcm_ladspa_find_port(&port, plugin, io->pdesc | LADSPA_PORT_AUDIO, idx); + if (err < 0) { + SNDERR("unable to find audio %s port %u plugin '%s'\n", io->pdesc & LADSPA_PORT_INPUT ? "input" : "output", idx, plugin->desc->Name); + return err; + } + } + err = snd_pcm_ladspa_add_to_carray(&eps->channels, idx, idx); + if (err < 0) { + SNDERR("unable to add channel %u for audio %s plugin '%s'\n", idx, io->pdesc & LADSPA_PORT_INPUT ? "input" : "output", plugin->desc->Name); + return err; + } + err = snd_pcm_ladspa_add_to_array(&eps->ports, idx, port); + if (err < 0) { + SNDERR("unable to add port %u for audio %s plugin '%s'\n", port, io->pdesc & LADSPA_PORT_INPUT ? "input" : "output", plugin->desc->Name); + return err; + } } - return snd_pcm_ladspa_connect(plugin, io, instance, idx, port); + return 0; } -static int snd_pcm_ladspa_connect_plugin_duplicate(snd_pcm_ladspa_plugin_t *plugin, - snd_pcm_ladspa_plugin_io_t *io, - snd_pcm_ladspa_instance_t *instance, - unsigned int idx) +static int snd_pcm_ladspa_connect_plugin(snd_pcm_ladspa_plugin_t *plugin, + snd_pcm_ladspa_instance_t *instance) +{ + int err; + + err = snd_pcm_ladspa_connect_plugin1(plugin, &plugin->input, &instance->input); + if (err < 0) + return err; + err = snd_pcm_ladspa_connect_plugin1(plugin, &plugin->output, &instance->output); + if (err < 0) + return err; + return 0; +} + +static int snd_pcm_ladspa_connect_plugin_duplicate1(snd_pcm_ladspa_plugin_t *plugin, + snd_pcm_ladspa_plugin_io_t *io, + snd_pcm_ladspa_eps_t *eps, + unsigned int idx) { unsigned int port; int err; @@ -353,10 +466,101 @@ static int snd_pcm_ladspa_connect_plugin_duplicate(snd_pcm_ladspa_plugin_t *plug port = io->port_bindings[0]; } else { err = snd_pcm_ladspa_find_port(&port, plugin, io->pdesc | LADSPA_PORT_AUDIO, 0); - if (err < 0) + if (err < 0) { + SNDERR("unable to find audio %s port %u plugin '%s'\n", io->pdesc & LADSPA_PORT_INPUT ? "input" : "output", (unsigned int)0, plugin->desc->Name); return err; + } } - return snd_pcm_ladspa_connect(plugin, io, instance, idx, port); + err = snd_pcm_ladspa_add_to_carray(&eps->channels, 0, idx); + if (err < 0) { + SNDERR("unable to add channel %u for audio %s plugin '%s'\n", idx, io->pdesc & LADSPA_PORT_INPUT ? "input" : "output", plugin->desc->Name); + return err; + } + err = snd_pcm_ladspa_add_to_array(&eps->ports, 0, port); + if (err < 0) { + SNDERR("unable to add port %u for audio %s plugin '%s'\n", port, io->pdesc & LADSPA_PORT_INPUT ? "input" : "output", plugin->desc->Name); + return err; + } + return 0; +} + +static int snd_pcm_ladspa_connect_plugin_duplicate(snd_pcm_ladspa_plugin_t *plugin, + snd_pcm_ladspa_plugin_io_t *in_io, + snd_pcm_ladspa_plugin_io_t *out_io, + snd_pcm_ladspa_instance_t *instance, + unsigned int idx) +{ + int err; + + err = snd_pcm_ladspa_connect_plugin_duplicate1(plugin, in_io, &instance->input, idx); + if (err < 0) + return err; + err = snd_pcm_ladspa_connect_plugin_duplicate1(plugin, out_io, &instance->output, idx); + if (err < 0) + return err; + return 0; +} + +static void snd_pcm_ladspa_get_default_cvalue(const LADSPA_Descriptor * desc, unsigned int port, LADSPA_Data *val) +{ + LADSPA_PortRangeHintDescriptor hdesc; + + hdesc = desc->PortRangeHints[port].HintDescriptor; + switch (hdesc & LADSPA_HINT_DEFAULT_MASK) { + case LADSPA_HINT_DEFAULT_MINIMUM: + *val = desc->PortRangeHints[port].LowerBound; + break; + case LADSPA_HINT_DEFAULT_LOW: + if (LADSPA_IS_HINT_LOGARITHMIC(hdesc)) { + *val = exp(log(desc->PortRangeHints[port].LowerBound) + * 0.75 + + log(desc->PortRangeHints[port].UpperBound) + * 0.25); + } else { + *val = (desc->PortRangeHints[port].LowerBound * 0.75) + + (desc->PortRangeHints[port].UpperBound * 0.25); + } + break; + case LADSPA_HINT_DEFAULT_MIDDLE: + if (LADSPA_IS_HINT_LOGARITHMIC(hdesc)) { + *val = sqrt(desc->PortRangeHints[port].LowerBound * + desc->PortRangeHints[port].UpperBound); + } else { + *val = 0.5 * + (desc->PortRangeHints[port].LowerBound + + desc->PortRangeHints[port].UpperBound); + } + break; + case LADSPA_HINT_DEFAULT_HIGH: + if (LADSPA_IS_HINT_LOGARITHMIC(hdesc)) { + *val = exp(log(desc->PortRangeHints[port].LowerBound) + * 0.25 + + log(desc->PortRangeHints[port].UpperBound) + * 0.75); + } else { + *val = (desc->PortRangeHints[port].LowerBound * 0.25) + + (desc->PortRangeHints[port].UpperBound * 0.75); + } + break; + case LADSPA_HINT_DEFAULT_MAXIMUM: + *val = desc->PortRangeHints[port].UpperBound; + break; + case LADSPA_HINT_DEFAULT_0: + *val = 0; + break; + case LADSPA_HINT_DEFAULT_1: + *val = 1; + break; + case LADSPA_HINT_DEFAULT_100: + *val = 100; + break; + case LADSPA_HINT_DEFAULT_440: + *val = 440; + break; + default: + *val = 0; /* reasonable default, if everything fails */ + break; + } } static int snd_pcm_ladspa_connect_controls(snd_pcm_ladspa_plugin_t *plugin, @@ -368,6 +572,8 @@ static int snd_pcm_ladspa_connect_controls(snd_pcm_ladspa_plugin_t *plugin, for (idx = midx = 0; idx < plugin->desc->PortCount; idx++) if ((plugin->desc->PortDescriptors[idx] & (io->pdesc | LADSPA_PORT_CONTROL)) == (io->pdesc | LADSPA_PORT_CONTROL)) { if (io->controls_size > midx) { + if (!io->controls_initialized[midx]) + snd_pcm_ladspa_get_default_cvalue(plugin->desc, idx, &io->controls[midx]); plugin->desc->connect_port(instance->handle, idx, &io->controls[midx]); } else { return -EINVAL; @@ -377,30 +583,66 @@ static int snd_pcm_ladspa_connect_controls(snd_pcm_ladspa_plugin_t *plugin, return 0; } +static int snd_pcm_ladspa_check_connect(snd_pcm_ladspa_plugin_t *plugin, + snd_pcm_ladspa_plugin_io_t *io, + snd_pcm_ladspa_eps_t *eps, + unsigned int depth) +{ + unsigned int idx, midx; + int err = 0; + + for (idx = midx = 0; idx < plugin->desc->PortCount; idx++) + if ((plugin->desc->PortDescriptors[idx] & (io->pdesc | LADSPA_PORT_AUDIO)) == (io->pdesc | LADSPA_PORT_AUDIO)) { + if (eps->channels.array[midx] == NO_ASSIGN) { + SNDERR("%s port for plugin %s depth %u is not connected\n", io->pdesc & LADSPA_PORT_INPUT ? "input" : "output", plugin->desc->Name, depth); + err++; + } + midx++; + } + if (err > 0) { + SNDERR("%i connection errors total\n", err); + return -EINVAL; + } + return 0; +} + static int snd_pcm_ladspa_allocate_instances(snd_pcm_t *pcm, snd_pcm_ladspa_t *ladspa) { struct list_head *list, *pos; unsigned int depth, idx, count; - snd_pcm_ladspa_instance_t *instance; + unsigned int in_channel, out_channel; + unsigned int in_channels, out_channels; + unsigned int in_ports, out_ports; + snd_pcm_ladspa_instance_t *instance = NULL; int err; - if (ladspa->instances_channels == 0) - return 0; list = pcm->stream == SND_PCM_STREAM_PLAYBACK ? &ladspa->pplugins : &ladspa->cplugins; + in_channels = ladspa->channels > 0 ? ladspa->channels : + (pcm->stream == SND_PCM_STREAM_PLAYBACK ? pcm->channels : ladspa->plug.gen.slave->channels); depth = 0; + out_channels = 0; list_for_each(pos, list) { snd_pcm_ladspa_plugin_t *plugin = list_entry(pos, snd_pcm_ladspa_plugin_t, list); + if (pos->next == list) /* last entry */ + out_channels = pcm->stream == SND_PCM_STREAM_PLAYBACK ? ladspa->plug.gen.slave->channels : pcm->channels; + in_ports = snd_pcm_ladspa_count_ports(plugin, LADSPA_PORT_INPUT | LADSPA_PORT_AUDIO); + out_ports = snd_pcm_ladspa_count_ports(plugin, LADSPA_PORT_OUTPUT | LADSPA_PORT_AUDIO); count = 1; - if (plugin->policy == SND_PCM_LADSPA_POLICY_DUPLICATE) - count = pcm->channels; - for (idx = 0; idx < count; idx++) { + if (plugin->policy == SND_PCM_LADSPA_POLICY_DUPLICATE) { + if (in_ports == 1 && out_ports == 1) + count = in_channels; + else + plugin->policy = SND_PCM_LADSPA_POLICY_NONE; + } + in_channel = 0; + out_channel = 0; + for (idx = 0; idx < count; idx++) { instance = (snd_pcm_ladspa_instance_t *)calloc(1, sizeof(snd_pcm_ladspa_instance_t)); if (instance == NULL) return -ENOMEM; instance->desc = plugin->desc; instance->handle = plugin->desc->instantiate(plugin->desc, pcm->rate); instance->depth = depth; - instance->channel = NO_ASSIGN; if (instance->handle == NULL) { SNDERR("Unable to create instance of LADSPA plugin '%s'", plugin->desc->Name); free(instance); @@ -410,113 +652,167 @@ static int snd_pcm_ladspa_allocate_instances(snd_pcm_t *pcm, snd_pcm_ladspa_t *l if (plugin->desc->activate) plugin->desc->activate(instance->handle); if (plugin->policy == SND_PCM_LADSPA_POLICY_DUPLICATE) { - err = snd_pcm_ladspa_connect_plugin_duplicate(plugin, &plugin->input, instance, idx); - if (err < 0) { - SNDERR("Unable to connect duplicate input port of plugin '%s' channel %u depth %u", plugin->desc->Name, idx, instance->depth); - return err; - } - err = snd_pcm_ladspa_connect_plugin_duplicate(plugin, &plugin->output, instance, idx); + err = snd_pcm_ladspa_connect_plugin_duplicate(plugin, &plugin->input, &plugin->output, instance, idx); if (err < 0) { - SNDERR("Unable to connect duplicate output port of plugin '%s' channel %u depth %u", plugin->desc->Name, idx, instance->depth); + SNDERR("Unable to connect duplicate port of plugin '%s' channel %u depth %u", plugin->desc->Name, idx, instance->depth); return err; } + } else { + err = snd_pcm_ladspa_connect_plugin(plugin, instance); + if (err < 0) { + SNDERR("Unable to connect plugin '%s' depth %u", plugin->desc->Name, depth); + return err; + } } err = snd_pcm_ladspa_connect_controls(plugin, &plugin->input, instance); assert(err >= 0); err = snd_pcm_ladspa_connect_controls(plugin, &plugin->output, instance); assert(err >= 0); } - if (plugin->policy == SND_PCM_LADSPA_POLICY_NONE) { - instance = list_entry(plugin->instances.next, snd_pcm_ladspa_instance_t, list); - for (idx = 0; idx < pcm->channels; idx++) { - err = snd_pcm_ladspa_connect_plugin(plugin, &plugin->input, instance, idx); - if (err < 0) { - SNDERR("Unable to connect input port of plugin '%s' channel %u depth %u", plugin->desc->Name, idx, depth); - return err; - } - err = snd_pcm_ladspa_connect_plugin(plugin, &plugin->output, instance, idx); - if (err < 0) { - SNDERR("Unable to connect output port of plugin '%s' channel %u depth %u", plugin->desc->Name, idx, depth); - return err; - } - } - } + err = snd_pcm_ladspa_check_connect(plugin, &plugin->input, &instance->input, depth); + if (err < 0) + return err; + err = snd_pcm_ladspa_check_connect(plugin, &plugin->output, &instance->output, depth); + if (err < 0) + return err; depth++; } return 0; } -static int snd_pcm_ladspa_allocate_imemory(snd_pcm_ladspa_instance_t *instance, size_t alloc_size) +static LADSPA_Data *snd_pcm_ladspa_allocate_zero(snd_pcm_ladspa_t *ladspa, unsigned int idx) { - if (instance->prev) - instance->in_data = instance->prev->out_data; - else - instance->in_data = NULL; - if (!instance->prev || - (instance->next && LADSPA_IS_INPLACE_BROKEN(instance->desc->Properties))) { - instance->m_data = (LADSPA_Data *)malloc(alloc_size * sizeof(LADSPA_Data)); - if (instance->m_data == NULL) - return -ENOMEM; - instance->out_data = instance->m_data; - } else { - instance->out_data = instance->in_data; - } - return 0; + if (ladspa->zero[idx] == NULL) + ladspa->zero[idx] = calloc(ladspa->allocated, sizeof(LADSPA_Data)); + return ladspa->zero[idx]; } static int snd_pcm_ladspa_allocate_memory(snd_pcm_t *pcm, snd_pcm_ladspa_t *ladspa) { struct list_head *list, *pos, *pos1; - snd_pcm_ladspa_instance_t *instance, *prev; - unsigned int channel; - int err; + snd_pcm_ladspa_instance_t *instance; + unsigned int channels = 16, nchannels; + unsigned int ichannels, ochannels; + void **pchannels, **npchannels; + unsigned int idx, chn; - if (ladspa->instances_channels == 0) - return 0; - ladspa->finstances = (snd_pcm_ladspa_instance_t **)calloc(ladspa->instances_channels, sizeof(snd_pcm_ladspa_instance_t *)); - if (ladspa->finstances == NULL) - return -ENOMEM; + ladspa->allocated = 2048; + if (pcm->buffer_size > ladspa->allocated) + ladspa->allocated = pcm->buffer_size; + if (pcm->stream == SND_PCM_STREAM_PLAYBACK) { + ichannels = pcm->channels; + ochannels = ladspa->plug.gen.slave->channels; + } else { + ichannels = ladspa->plug.gen.slave->channels; + ochannels = pcm->channels; + } + pchannels = calloc(1, sizeof(void *) * channels); + if (pchannels == NULL) + return -ENOMEM; list = pcm->stream == SND_PCM_STREAM_PLAYBACK ? &ladspa->pplugins : &ladspa->cplugins; - for (channel = 0; channel < ladspa->instances_channels; channel++) { - prev = NULL; - list_for_each(pos, list) { - snd_pcm_ladspa_plugin_t *plugin = list_entry(pos, snd_pcm_ladspa_plugin_t, list); - instance = NULL; - if (list_empty(&plugin->instances)) - continue; - list_for_each(pos1, &plugin->instances) { - instance = list_entry(pos1, snd_pcm_ladspa_instance_t, list); - if (instance->channel == NO_ASSIGN) { - SNDERR("channel %u is not assigned for plugin '%s' depth %u", instance->channel, plugin->desc->Name, instance->depth); - return -EINVAL; - } - if (instance->channel != channel) { - instance = NULL; - continue; - } - break; - } - if (instance == NULL) - continue; - if (ladspa->finstances[channel] == NULL) - ladspa->finstances[channel] = instance; - instance->prev = prev; - if (prev == NULL) { - prev = instance; - continue; /* nothing to do */ - } - prev->next = instance; + list_for_each(pos, list) { + snd_pcm_ladspa_plugin_t *plugin = list_entry(pos, snd_pcm_ladspa_plugin_t, list); + list_for_each(pos1, &plugin->instances) { + instance = list_entry(pos1, snd_pcm_ladspa_instance_t, list); + nchannels = channels; + for (idx = 0; idx < instance->input.channels.size; idx++) { + chn = instance->input.channels.array[idx]; + if (chn >= nchannels) + nchannels = chn + 1; + } + for (idx = 0; idx < instance->output.channels.size; idx++) { + chn = instance->output.channels.array[idx]; + if (chn >= nchannels) + nchannels = chn + 1; + } + if (nchannels != channels) { + npchannels = realloc(pchannels, nchannels * sizeof(void *)); + if (npchannels == NULL) { + free(pchannels); + return -ENOMEM; + } + for (idx = channels; idx < nchannels; idx++) + npchannels[idx] = NULL; + pchannels = npchannels; + } + assert(instance->input.data == NULL); + assert(instance->input.m_data == NULL); + assert(instance->output.data == NULL); + assert(instance->output.m_data == NULL); + instance->input.data = calloc(instance->input.channels.size, sizeof(void *)); + instance->input.m_data = calloc(instance->input.channels.size, sizeof(void *)); + instance->output.data = calloc(instance->output.channels.size, sizeof(void *)); + instance->output.m_data = calloc(instance->output.channels.size, sizeof(void *)); + if (instance->input.data == NULL || + instance->input.m_data == NULL || + instance->output.data == NULL || + instance->output.m_data == NULL) + return -ENOMEM; + for (idx = 0; idx < instance->input.channels.size; idx++) { + chn = instance->output.channels.array[idx]; + if (pchannels[chn] == NULL && chn < ichannels) { + instance->input.data[idx] = NULL; + continue; + } + instance->input.data[idx] = pchannels[chn]; + if (instance->input.data[idx] == NULL) { + instance->input.data[idx] = snd_pcm_ladspa_allocate_zero(ladspa, 0); + if (instance->input.data[idx] == NULL) + return -ENOMEM; + } + } + for (idx = 0; idx < instance->output.channels.size; idx++) { + chn = instance->output.channels.array[idx]; + /* FIXME/OPTIMIZE: check if we can remove double alloc */ + /* if LADSPA plugin has no broken inplace */ + instance->output.data[idx] = malloc(sizeof(LADSPA_Data) * ladspa->allocated); + if (instance->output.data[idx] == NULL) + return -ENOMEM; + pchannels[chn] = instance->output.m_data[idx] = instance->output.data[idx]; + } } } - for (channel = 0; channel < ladspa->instances_channels; channel++) { - instance = ladspa->finstances[channel]; - if (instance == NULL) - continue; - err = snd_pcm_ladspa_allocate_imemory(instance, pcm->buffer_size); - if (err < 0) - return err; - break; + /* OPTIMIZE: we have already allocated areas for ALSA output channels */ + /* next loop deallocates the last output LADSPA areas and connects */ + /* them to ALSA areas (NULL) or dummy area ladpsa->free[1] ; */ + /* this algorithm might be optimized to not allocate the last LADSPA outputs */ + list_for_each(pos, list) { + snd_pcm_ladspa_plugin_t *plugin = list_entry(pos, snd_pcm_ladspa_plugin_t, list); + list_for_each(pos1, &plugin->instances) { + instance = list_entry(pos1, snd_pcm_ladspa_instance_t, list); + for (idx = 0; idx < instance->output.channels.size; idx++) { + chn = instance->output.channels.array[idx]; + if (instance->output.data[idx] == pchannels[chn]) { + if (instance->output.m_data[idx]) { + free(instance->output.m_data[idx]); + instance->output.m_data[idx] = NULL; + } + if (chn < ochannels) { + instance->output.data[idx] = NULL; + } else { + instance->output.data[idx] = snd_pcm_ladspa_allocate_zero(ladspa, 1); + if (instance->output.data[idx] == NULL) + return -ENOMEM; + } + } + } + } + } +#if 0 + printf("zero[0] = %p\n", ladspa->zero[0]); + printf("zero[1] = %p\n", ladspa->zero[1]); + list_for_each(pos, list) { + snd_pcm_ladspa_plugin_t *plugin = list_entry(pos, snd_pcm_ladspa_plugin_t, list); + list_for_each(pos1, &plugin->instances) { + instance = list_entry(pos1, snd_pcm_ladspa_instance_t, list); + for (idx = 0; idx < instance->input.channels.size; idx++) + printf("%i:alloc-input%i: data = %p, m_data = %p\n", instance->depth, idx, instance->input.data[idx], instance->input.m_data[idx]); + for (idx = 0; idx < instance->output.channels.size; idx++) + printf("%i:alloc-output%i: data = %p, m_data = %p\n", instance->depth, idx, instance->output.data[idx], instance->output.m_data[idx]); + } } +#endif + free(pchannels); return 0; } @@ -525,21 +821,16 @@ static int snd_pcm_ladspa_init(snd_pcm_t *pcm) snd_pcm_ladspa_t *ladspa = pcm->private_data; int err; - if (pcm->channels != ladspa->instances_channels) { + snd_pcm_ladspa_free_instances(pcm, ladspa, 1); + err = snd_pcm_ladspa_allocate_instances(pcm, ladspa); + if (err < 0) { snd_pcm_ladspa_free_instances(pcm, ladspa, 1); - ladspa->instances_channels = pcm->channels; - err = snd_pcm_ladspa_allocate_instances(pcm, ladspa); - if (err < 0) { - snd_pcm_ladspa_free_instances(pcm, ladspa, 1); - return err; - } - err = snd_pcm_ladspa_allocate_memory(pcm, ladspa); - if (err < 0) { - snd_pcm_ladspa_free_instances(pcm, ladspa, 1); - return err; - } - } else { - snd_pcm_ladspa_free_instances(pcm, ladspa, 0); + return err; + } + err = snd_pcm_ladspa_allocate_memory(pcm, ladspa); + if (err < 0) { + snd_pcm_ladspa_free_instances(pcm, ladspa, 1); + return err; } return 0; } @@ -562,40 +853,55 @@ snd_pcm_ladspa_write_areas(snd_pcm_t *pcm, snd_pcm_uframes_t *slave_sizep) { snd_pcm_ladspa_t *ladspa = pcm->private_data; - unsigned int channel; + snd_pcm_ladspa_instance_t *instance; + struct list_head *pos, *pos1; + LADSPA_Data *data; + unsigned int idx, chn, size1, size2; if (size > *slave_sizep) size = *slave_sizep; -#if 0 // no processing - for testing purposes only + size2 = size; +#if 0 /* no processing - for testing purposes only */ snd_pcm_areas_copy(slave_areas, slave_offset, areas, offset, pcm->channels, size, pcm->format); #else - for (channel = 0; channel < ladspa->instances_channels; channel++) { - LADSPA_Data *data = (LADSPA_Data *)((char *)areas[channel].addr + (areas[channel].first / 8)); - snd_pcm_ladspa_instance_t *instance = ladspa->finstances[channel]; - data += offset; - if (instance == NULL) - snd_pcm_area_copy(&slave_areas[channel], slave_offset, - &areas[channel], offset, - size, SND_PCM_FORMAT_FLOAT); - while (instance) { - if (instance->in_data != NULL) - data = instance->in_data; - instance->desc->connect_port(instance->handle, instance->in_port, data); - if (instance->next == NULL) { - data = (LADSPA_Data *)((char *)slave_areas[channel].addr + (slave_areas[channel].first / 8)); - data += slave_offset; - } else if (instance->out_data != NULL) - data = instance->out_data; - instance->desc->connect_port(instance->handle, instance->out_port, data); - instance->desc->run(instance->handle, size); - instance = instance->next; - } + while (size > 0) { + size1 = size; + if (size1 > ladspa->allocated) + size1 = ladspa->allocated; + list_for_each(pos, &ladspa->pplugins) { + snd_pcm_ladspa_plugin_t *plugin = list_entry(pos, snd_pcm_ladspa_plugin_t, list); + list_for_each(pos1, &plugin->instances) { + instance = list_entry(pos1, snd_pcm_ladspa_instance_t, list); + for (idx = 0; idx < instance->input.channels.size; idx++) { + chn = instance->input.channels.array[idx]; + data = instance->input.data[idx]; + if (data == NULL) { + data = (LADSPA_Data *)((char *)areas[chn].addr + (areas[chn].first / 8)); + data += offset; + } + instance->desc->connect_port(instance->handle, instance->input.ports.array[idx], data); + } + for (idx = 0; idx < instance->output.channels.size; idx++) { + chn = instance->output.channels.array[idx]; + data = instance->output.data[idx]; + if (data == NULL) { + data = (LADSPA_Data *)((char *)slave_areas[chn].addr + (areas[chn].first / 8)); + data += slave_offset; + } + instance->desc->connect_port(instance->handle, instance->output.ports.array[idx], data); + } + instance->desc->run(instance->handle, size1); + } + } + offset += size1; + slave_offset += size1; + size -= size1; } #endif - *slave_sizep = size; - return size; + *slave_sizep = size2; + return size2; } static snd_pcm_uframes_t @@ -608,45 +914,62 @@ snd_pcm_ladspa_read_areas(snd_pcm_t *pcm, snd_pcm_uframes_t *slave_sizep) { snd_pcm_ladspa_t *ladspa = pcm->private_data; - unsigned int channel; + snd_pcm_ladspa_instance_t *instance; + struct list_head *pos, *pos1; + LADSPA_Data *data; + unsigned int idx, chn, size1, size2;; if (size > *slave_sizep) size = *slave_sizep; -#if 0 // no processing - for testing purposes only + size2 = size; +#if 0 /* no processing - for testing purposes only */ snd_pcm_areas_copy(areas, offset, slave_areas, slave_offset, pcm->channels, size, pcm->format); #else - for (channel = 0; channel < ladspa->instances_channels; channel++) { - LADSPA_Data *data = (LADSPA_Data *)((char *)slave_areas[channel].addr + (slave_areas[channel].first / 8)); - snd_pcm_ladspa_instance_t *instance = ladspa->finstances[channel]; - data += slave_offset; - if (instance == NULL) - snd_pcm_area_copy(&slave_areas[channel], slave_offset, - &areas[channel], offset, - size, SND_PCM_FORMAT_FLOAT); - while (instance) { - if (instance->in_data != NULL) - data = instance->in_data; - instance->desc->connect_port(instance->handle, instance->in_port, data); - if (instance->next == NULL) { - data = (LADSPA_Data *)((char *)areas[channel].addr + (areas[channel].first / 8)); - data += offset; - } else if (instance->out_data != NULL) - data = instance->out_data; - instance->desc->connect_port(instance->handle, instance->out_port, data); - instance->desc->run(instance->handle, size); - instance = instance->next; - } + while (size > 0) { + size1 = size; + if (size1 > ladspa->allocated) + size1 = ladspa->allocated; + list_for_each(pos, &ladspa->cplugins) { + snd_pcm_ladspa_plugin_t *plugin = list_entry(pos, snd_pcm_ladspa_plugin_t, list); + list_for_each(pos1, &plugin->instances) { + instance = list_entry(pos1, snd_pcm_ladspa_instance_t, list); + for (idx = 0; idx < instance->input.channels.size; idx++) { + chn = instance->input.channels.array[idx]; + data = instance->input.data[idx]; + if (data == NULL) { + data = (LADSPA_Data *)((char *)slave_areas[chn].addr + (areas[chn].first / 8)); + data += slave_offset; + } + instance->desc->connect_port(instance->handle, instance->input.ports.array[idx], data); + } + for (idx = 0; idx < instance->output.channels.size; idx++) { + chn = instance->output.channels.array[idx]; + data = instance->output.data[idx]; + if (data == NULL) { + data = (LADSPA_Data *)((char *)areas[chn].addr + (areas[chn].first / 8)); + data += offset; + } + instance->desc->connect_port(instance->handle, instance->output.ports.array[idx], data); + } + instance->desc->run(instance->handle, size1); + } + } + offset += size1; + slave_offset += size1; + size -= size1; } #endif - *slave_sizep = size; - return size; + *slave_sizep = size2; + return size2; } -static void snd_pcm_ladspa_dump_direction(snd_pcm_ladspa_plugin_io_t *io, snd_output_t *out) +static void snd_pcm_ladspa_dump_direction(snd_pcm_ladspa_plugin_t *plugin, + snd_pcm_ladspa_plugin_io_t *io, + snd_output_t *out) { - unsigned int idx; + unsigned int idx, midx; if (io->port_bindings_size == 0) goto __control; @@ -661,8 +984,34 @@ static void snd_pcm_ladspa_dump_direction(snd_pcm_ladspa_plugin_io_t *io, snd_ou if (io->controls_size == 0) return; snd_output_printf(out, " Control %s port initial values:\n", io->pdesc == LADSPA_PORT_INPUT ? "input" : "output"); - for (idx = 0; idx < io->controls_size; idx++) - snd_output_printf(out, " %i = %.8f\n", idx, io->controls[idx]); + for (idx = midx = 0; idx < plugin->desc->PortCount; idx++) { + if ((plugin->desc->PortDescriptors[idx] & (io->pdesc | LADSPA_PORT_CONTROL)) == (io->pdesc | LADSPA_PORT_CONTROL)) { + snd_output_printf(out, " %i \"%s\" = %.8f\n", idx, plugin->desc->PortNames[idx], io->controls[midx]); + midx++; + } + } +} + +static void snd_pcm_ladspa_dump_array(snd_output_t *out, + snd_pcm_ladspa_array_t *array, + snd_pcm_ladspa_plugin_t *plugin) +{ + unsigned int size = array->size; + unsigned int val, idx = 0; + + while (size-- > 0) { + if (idx > 0) { + snd_output_putc(out, ','); + snd_output_putc(out, ' '); + } + val = array->array[idx++]; + if (val == NO_ASSIGN) + snd_output_putc(out, '-'); + else + snd_output_printf(out, "%u", val); + if (plugin) + snd_output_printf(out, " \"%s\"", plugin->desc->PortNames[val]); + } } static void snd_pcm_ladspa_plugins_dump(struct list_head *list, snd_output_t *out) @@ -673,14 +1022,25 @@ static void snd_pcm_ladspa_plugins_dump(struct list_head *list, snd_output_t *ou snd_pcm_ladspa_plugin_t *plugin = list_entry(pos, snd_pcm_ladspa_plugin_t, list); snd_output_printf(out, " Policy: %s\n", plugin->policy == SND_PCM_LADSPA_POLICY_NONE ? "none" : "duplicate"); snd_output_printf(out, " Filename: %s\n", plugin->filename); + snd_output_printf(out, " Plugin Name: %s\n", plugin->desc->Name); + snd_output_printf(out, " Plugin Label: %s\n", plugin->desc->Label); + snd_output_printf(out, " Plugin Unique ID: %lu\n", plugin->desc->UniqueID); snd_output_printf(out, " Instances:\n"); list_for_each(pos2, &plugin->instances) { snd_pcm_ladspa_instance_t *in = (snd_pcm_ladspa_instance_t *) pos2; - snd_output_printf(out, " Depth: %i, Channel: %i, Input: %i, Output: %i\n", - in->depth, in->channel, in->in_port, in->out_port); + snd_output_printf(out, " Depth: %i\n", in->depth); + snd_output_printf(out, " InChannels: "); + snd_pcm_ladspa_dump_array(out, &in->input.channels, NULL); + snd_output_printf(out, "\n InPorts: "); + snd_pcm_ladspa_dump_array(out, &in->input.ports, plugin); + snd_output_printf(out, "\n OutChannels: "); + snd_pcm_ladspa_dump_array(out, &in->output.channels, NULL); + snd_output_printf(out, "\n OutPorts: "); + snd_pcm_ladspa_dump_array(out, &in->output.ports, plugin); + snd_output_printf(out, "\n"); } - snd_pcm_ladspa_dump_direction(&plugin->input, out); - snd_pcm_ladspa_dump_direction(&plugin->output, out); + snd_pcm_ladspa_dump_direction(plugin, &plugin->input, out); + snd_pcm_ladspa_dump_direction(plugin, &plugin->output, out); } } @@ -689,16 +1049,6 @@ static void snd_pcm_ladspa_dump(snd_pcm_t *pcm, snd_output_t *out) snd_pcm_ladspa_t *ladspa = pcm->private_data; snd_output_printf(out, "LADSPA PCM\n"); - snd_output_printf(out, " Instances setup:\n"); - snd_output_printf(out, " Channels: %i\n", ladspa->instances_channels); - if (ladspa->finstances) { - unsigned int idx; - for (idx = 0; idx < ladspa->instances_channels; idx++) { - snd_pcm_ladspa_instance_t *in = ladspa->finstances[idx]; - snd_output_printf(out, " Instance %i, Depth: %i, Channel: %i\n", - idx, in->depth, in->channel); - } - } snd_output_printf(out, " Playback:\n"); snd_pcm_ladspa_plugins_dump(&ladspa->pplugins, out); snd_output_printf(out, " Capture:\n"); @@ -946,51 +1296,61 @@ static int snd_pcm_ladspa_parse_ioconfig(snd_pcm_ladspa_plugin_t *lplug, } } } - if (controls) { + if (1) { unsigned int count = 0; LADSPA_Data *array; + unsigned char *initialized; unsigned long idx; - if (snd_config_get_type(controls) != SND_CONFIG_TYPE_COMPOUND) { - SNDERR("controls definition must be a compound"); - return -EINVAL; - } for (idx = 0; idx < lplug->desc->PortCount; idx++) if ((lplug->desc->PortDescriptors[idx] & (io->pdesc | LADSPA_PORT_CONTROL)) == (io->pdesc | LADSPA_PORT_CONTROL)) count++; array = (LADSPA_Data *)calloc(count, sizeof(LADSPA_Data)); if (!array) return -ENOMEM; + initialized = (unsigned char *)calloc(count, sizeof(unsigned char)); + if (!initialized) { + free(array); + return -ENOMEM; + } io->controls_size = count; + io->controls_initialized = initialized; io->controls = array; - snd_config_for_each(i, next, controls) { - snd_config_t *n = snd_config_iterator_entry(i); - const char *id; - long lval; - unsigned int port, uval; - double dval; - if (snd_config_get_id(n, &id) < 0) - continue; - err = safe_strtol(id, &lval); - if (err >= 0) { - err = snd_pcm_ladspa_find_port(&port, lplug, io->pdesc | LADSPA_PORT_CONTROL, lval); - } else { - err = snd_pcm_ladspa_find_sport(&port, lplug, io->pdesc | LADSPA_PORT_CONTROL, id); - } - if (err < 0) { - SNDERR("Unable to find an control port (%s)", id); - return err; - } - if (snd_config_get_ireal(n, &dval) < 0) { - SNDERR("Control port %s has not an float or integer value", id); - return err; - } - err = snd_pcm_ladspa_find_port_idx(&uval, lplug, io->pdesc | LADSPA_PORT_CONTROL, port); - if (err < 0) { - SNDERR("internal error"); - return err; - } - array[uval] = (LADSPA_Data)dval; - } + if (!(io->pdesc & LADSPA_PORT_OUTPUT)) { + if (snd_config_get_type(controls) != SND_CONFIG_TYPE_COMPOUND) { + SNDERR("controls definition must be a compound"); + return -EINVAL; + } + snd_config_for_each(i, next, controls) { + snd_config_t *n = snd_config_iterator_entry(i); + const char *id; + long lval; + unsigned int port, uval; + double dval; + if (snd_config_get_id(n, &id) < 0) + continue; + err = safe_strtol(id, &lval); + if (err >= 0) { + err = snd_pcm_ladspa_find_port(&port, lplug, io->pdesc | LADSPA_PORT_CONTROL, lval); + } else { + err = snd_pcm_ladspa_find_sport(&port, lplug, io->pdesc | LADSPA_PORT_CONTROL, id); + } + if (err < 0) { + SNDERR("Unable to find an control port (%s)", id); + return err; + } + if (snd_config_get_ireal(n, &dval) < 0) { + SNDERR("Control port %s has not an float or integer value", id); + return err; + } + err = snd_pcm_ladspa_find_port_idx(&uval, lplug, io->pdesc | LADSPA_PORT_CONTROL, port); + if (err < 0) { + SNDERR("internal error"); + return err; + } + initialized[uval] = 1; + array[uval] = (LADSPA_Data)dval; + } + } } return 0; } @@ -1145,6 +1505,7 @@ static int snd_pcm_ladspa_build_plugins(struct list_head *list, * \param pcmp Returns created PCM handle * \param name Name of PCM * \param ladspa_path The path for LADSPA plugins + * \param channels Force input channel count to LADSPA plugin chain, 0 = no force (auto) * \param ladspa_pplugins The playback configuration * \param ladspa_cplugins The capture configuration * \param slave Slave PCM handle @@ -1156,6 +1517,7 @@ static int snd_pcm_ladspa_build_plugins(struct list_head *list, */ int snd_pcm_ladspa_open(snd_pcm_t **pcmp, const char *name, const char *ladspa_path, + unsigned int channels, snd_config_t *ladspa_pplugins, snd_config_t *ladspa_cplugins, snd_pcm_t *slave, int close_slave) @@ -1182,6 +1544,7 @@ int snd_pcm_ladspa_open(snd_pcm_t **pcmp, const char *name, INIT_LIST_HEAD(&ladspa->pplugins); INIT_LIST_HEAD(&ladspa->cplugins); + ladspa->channels = channels; if (slave->stream == SND_PCM_STREAM_PLAYBACK) { err = snd_pcm_ladspa_build_plugins(&ladspa->pplugins, ladspa_path, ladspa_pplugins, reverse); @@ -1227,6 +1590,16 @@ can be either little or big-endian depending on architecture). The policy duplicate means that there must be only one binding definition for channel zero. This definition is automatically duplicated for all channels. +If the LADSPA plugin has multiple audio inputs or outputs the policy duplicate +is automatically switched to policy none. + +The plugin serialization works as expected. You can eventually use more +channels (inputs / outputs) inside the LADPSA plugin chain than processed +in the ALSA plugin chain. If ALSA channel does not exist for given LADSPA +input audio port, zero samples are given to this LADSPA port. On the output +side (ALSA next plugin input), the valid channels are checked, too. +If specific ALSA channel does not exist, the LADSPA output port is +connected to a dummy sample area. Instances of LADSPA plugins are created dynamically. @@ -1240,6 +1613,7 @@ pcm.name { # or pcm { } # Slave PCM definition } + [channels INT] # count input channels (input to LADSPA plugin chain) [path STR] # Path (directory) with LADSPA plugins plugins | # Definition for both directions playback_plugins | # Definition for playback direction @@ -1254,6 +1628,7 @@ pcm.name { C INT or STR # C - channel, INT - audio port index, STR - audio port name } controls { + # valid only in the input block I INT or REAL # I - control port index, INT or REAL - control value # or STR INT or REAL # STR - control port name, INT or REAL - control value @@ -1295,6 +1670,7 @@ int _snd_pcm_ladspa_open(snd_pcm_t **pcmp, const char *name, snd_pcm_t *spcm; snd_config_t *slave = NULL, *sconf; const char *path = NULL; + long channels = 0; snd_config_t *plugins = NULL, *pplugins = NULL, *cplugins = NULL; snd_config_for_each(i, next, conf) { snd_config_t *n = snd_config_iterator_entry(i); @@ -1311,6 +1687,14 @@ int _snd_pcm_ladspa_open(snd_pcm_t **pcmp, const char *name, snd_config_get_string(n, &path); continue; } + if (strcmp(id, "channels") == 0) { + snd_config_get_integer(n, &channels); + if (channels > 1024) + channels = 1024; + if (channels < 0) + channels = 0; + continue; + } if (strcmp(id, "plugins") == 0) { plugins = n; continue; @@ -1345,7 +1729,7 @@ int _snd_pcm_ladspa_open(snd_pcm_t **pcmp, const char *name, snd_config_delete(sconf); if (err < 0) return err; - err = snd_pcm_ladspa_open(pcmp, name, path, pplugins, cplugins, spcm, 1); + err = snd_pcm_ladspa_open(pcmp, name, path, channels, pplugins, cplugins, spcm, 1); if (err < 0) snd_pcm_close(spcm); return err; -- 2.47.1