| /** |
| * \file pcm/pcm_ladspa.c |
| * \ingroup PCM_Plugins |
| * \brief ALSA Plugin <-> LADSPA Plugin Interface |
| * \author Jaroslav Kysela <perex@perex.cz> |
| * \author Jaroslav Kysela <perex@perex.cz> |
| * \date 2001,2006 |
| */ |
| /* |
| * PCM - LADSPA integration plugin |
| * Copyright (c) 2001-2006 by Jaroslav Kysela <perex@perex.cz> |
| * Copyright (c) 2005 by Jaroslav Kysela <perex@perex.cz> |
| * |
| * |
| * 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.1 of |
| * the License, or (at your option) any later version. |
| * |
| * This program 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 |
| * |
| * |
| * perex@perex.cz 2005/12/13 |
| * The LADSPA plugin rewrite was sponsored by MediaNet AG |
| * http://www.medianet.ag |
| */ |
| |
| #include <dirent.h> |
| #include <locale.h> |
| #include <math.h> |
| #include "pcm_local.h" |
| #include "pcm_plugin.h" |
| |
| #include "ladspa.h" |
| |
| #ifndef PIC |
| /* entry for static linking */ |
| const char *_snd_module_pcm_ladspa = ""; |
| #endif |
| |
| #ifndef DOC_HIDDEN |
| |
| #define NO_ASSIGN 0xffffffff |
| |
| typedef enum _snd_pcm_ladspa_policy { |
| SND_PCM_LADSPA_POLICY_NONE, /* use bindings only */ |
| SND_PCM_LADSPA_POLICY_DUPLICATE /* duplicate bindings for all channels */ |
| } snd_pcm_ladspa_policy_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 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; |
| |
| typedef struct snd_pcm_ladspa_instance { |
| struct list_head list; |
| const LADSPA_Descriptor *desc; |
| LADSPA_Handle *handle; |
| unsigned int depth; |
| 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; |
| |
| typedef struct { |
| struct list_head list; |
| snd_pcm_ladspa_policy_t policy; |
| char *filename; |
| void *dl_handle; |
| const LADSPA_Descriptor *desc; |
| snd_pcm_ladspa_plugin_io_t input; |
| snd_pcm_ladspa_plugin_io_t output; |
| 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, |
| unsigned int port_idx) |
| { |
| unsigned long idx; |
| |
| for (idx = 0; idx < lplug->desc->PortCount; idx++) |
| if ((lplug->desc->PortDescriptors[idx] & pdesc) == pdesc) { |
| if (port_idx == 0) { |
| *res = idx; |
| return 0; |
| } |
| port_idx--; |
| } |
| return -EINVAL; |
| } |
| |
| static int snd_pcm_ladspa_find_sport(unsigned int *res, |
| snd_pcm_ladspa_plugin_t *lplug, |
| LADSPA_PortDescriptor pdesc, |
| const char *port_name) |
| { |
| unsigned long idx; |
| |
| for (idx = 0; idx < lplug->desc->PortCount; idx++) |
| if ((lplug->desc->PortDescriptors[idx] & pdesc) == pdesc && |
| !strcmp(lplug->desc->PortNames[idx], port_name)) { |
| *res = idx; |
| return 0; |
| } |
| return -EINVAL; |
| } |
| |
| static int snd_pcm_ladspa_find_port_idx(unsigned int *res, |
| snd_pcm_ladspa_plugin_t *lplug, |
| LADSPA_PortDescriptor pdesc, |
| unsigned int port) |
| { |
| unsigned long idx; |
| unsigned int r = 0; |
| |
| if (port >= lplug->desc->PortCount) |
| return -EINVAL; |
| for (idx = 0; idx < port; idx++) |
| if ((lplug->desc->PortDescriptors[idx] & pdesc) == pdesc) |
| r++; |
| *res = r; |
| return 0; |
| } |
| |
| static void snd_pcm_ladspa_free_io(snd_pcm_ladspa_plugin_io_t *io) |
| { |
| free(io->controls); |
| 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); |
| free(plugin->filename); |
| list_del(&plugin->list); |
| free(plugin); |
| } |
| } |
| |
| 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++) { |
| free(ladspa->zero[idx]); |
| ladspa->zero[idx] = NULL; |
| } |
| ladspa->allocated = 0; |
| } |
| |
| static int snd_pcm_ladspa_close(snd_pcm_t *pcm) |
| { |
| snd_pcm_ladspa_t *ladspa = pcm->private_data; |
| |
| snd_pcm_ladspa_free(ladspa); |
| return snd_pcm_generic_close(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; |
| 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, |
| &access_mask); |
| if (err < 0) |
| return err; |
| err = _snd_pcm_hw_params_set_format(params, SND_PCM_FORMAT_FLOAT); |
| if (err < 0) |
| return err; |
| 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_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; |
| } |
| |
| static int snd_pcm_ladspa_hw_refine_schange(snd_pcm_t *pcm ATTRIBUTE_UNUSED, snd_pcm_hw_params_t *params, |
| snd_pcm_hw_params_t *sparams) |
| { |
| int err; |
| unsigned int links = (SND_PCM_HW_PARBIT_CHANNELS | |
| SND_PCM_HW_PARBIT_RATE | |
| SND_PCM_HW_PARBIT_PERIOD_SIZE | |
| SND_PCM_HW_PARBIT_BUFFER_SIZE | |
| SND_PCM_HW_PARBIT_PERIODS | |
| SND_PCM_HW_PARBIT_PERIOD_TIME | |
| SND_PCM_HW_PARBIT_BUFFER_TIME | |
| SND_PCM_HW_PARBIT_TICK_TIME); |
| err = _snd_pcm_hw_params_refine(sparams, links, params); |
| if (err < 0) |
| return err; |
| return 0; |
| } |
| |
| static int snd_pcm_ladspa_hw_refine_cchange(snd_pcm_t *pcm ATTRIBUTE_UNUSED, snd_pcm_hw_params_t *params, |
| snd_pcm_hw_params_t *sparams) |
| { |
| int err; |
| unsigned int links = (SND_PCM_HW_PARBIT_CHANNELS | |
| SND_PCM_HW_PARBIT_RATE | |
| SND_PCM_HW_PARBIT_PERIOD_SIZE | |
| SND_PCM_HW_PARBIT_BUFFER_SIZE | |
| SND_PCM_HW_PARBIT_PERIODS | |
| SND_PCM_HW_PARBIT_PERIOD_TIME | |
| SND_PCM_HW_PARBIT_BUFFER_TIME | |
| SND_PCM_HW_PARBIT_TICK_TIME); |
| err = _snd_pcm_hw_params_refine(params, links, sparams); |
| if (err < 0) |
| return err; |
| return 0; |
| } |
| |
| static int snd_pcm_ladspa_hw_refine(snd_pcm_t *pcm, snd_pcm_hw_params_t *params) |
| { |
| return snd_pcm_hw_refine_slave(pcm, params, |
| snd_pcm_ladspa_hw_refine_cprepare, |
| snd_pcm_ladspa_hw_refine_cchange, |
| snd_pcm_ladspa_hw_refine_sprepare, |
| snd_pcm_ladspa_hw_refine_schange, |
| snd_pcm_generic_hw_refine); |
| } |
| |
| static int snd_pcm_ladspa_hw_params(snd_pcm_t *pcm, snd_pcm_hw_params_t * params) |
| { |
| // snd_pcm_ladspa_t *ladspa = pcm->private_data; |
| int err = snd_pcm_hw_params_slave(pcm, params, |
| snd_pcm_ladspa_hw_refine_cchange, |
| snd_pcm_ladspa_hw_refine_sprepare, |
| snd_pcm_ladspa_hw_refine_schange, |
| snd_pcm_generic_hw_params); |
| if (err < 0) |
| return err; |
| return 0; |
| } |
| |
| static void snd_pcm_ladspa_free_eps(snd_pcm_ladspa_eps_t *eps) |
| { |
| free(eps->channels.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; |
| |
| 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); |
| list_for_each_safe(pos1, next1, &plugin->instances) { |
| snd_pcm_ladspa_instance_t *instance = list_entry(pos1, snd_pcm_ladspa_instance_t, list); |
| if (plugin->desc->deactivate) |
| plugin->desc->deactivate(instance->handle); |
| if (cleanup) { |
| if (plugin->desc->cleanup) |
| plugin->desc->cleanup(instance->handle); |
| if (instance->input.m_data) { |
| for (idx = 0; idx < instance->input.channels.size; 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++) |
| free(instance->output.m_data[idx]); |
| free(instance->output.m_data); |
| } |
| free(instance->input.data); |
| free(instance->output.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) |
| plugin->desc->activate(instance->handle); |
| } |
| } |
| if (cleanup) { |
| assert(list_empty(&plugin->instances)); |
| } |
| } |
| } |
| |
| static int snd_pcm_ladspa_add_to_carray(snd_pcm_ladspa_array_t *array, |
| unsigned int idx, |
| unsigned int val) |
| { |
| 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_add_to_array(snd_pcm_ladspa_array_t *array, |
| unsigned int idx, |
| unsigned int val) |
| { |
| 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, idx1; |
| int err; |
| |
| assert(plugin->policy == SND_PCM_LADSPA_POLICY_NONE); |
| channels = io->port_bindings_size > 0 ? |
| io->port_bindings_size : |
| snd_pcm_ladspa_count_ports(plugin, io->pdesc | LADSPA_PORT_AUDIO); |
| for (idx = idx1 = 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'", io->pdesc & LADSPA_PORT_INPUT ? "input" : "output", idx, plugin->desc->Name); |
| return err; |
| } |
| } |
| if (port == NO_ASSIGN) |
| continue; |
| err = snd_pcm_ladspa_add_to_carray(&eps->channels, idx1, idx); |
| if (err < 0) { |
| SNDERR("unable to add channel %u for audio %s plugin '%s'", idx, io->pdesc & LADSPA_PORT_INPUT ? "input" : "output", plugin->desc->Name); |
| return err; |
| } |
| err = snd_pcm_ladspa_add_to_array(&eps->ports, idx1, port); |
| if (err < 0) { |
| SNDERR("unable to add port %u for audio %s plugin '%s'", port, io->pdesc & LADSPA_PORT_INPUT ? "input" : "output", plugin->desc->Name); |
| return err; |
| } |
| idx1++; |
| } |
| return 0; |
| } |
| |
| 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; |
| |
| assert(plugin->policy == SND_PCM_LADSPA_POLICY_DUPLICATE); |
| if (io->port_bindings_size > 0) { |
| port = io->port_bindings[0]; |
| } else { |
| err = snd_pcm_ladspa_find_port(&port, plugin, io->pdesc | LADSPA_PORT_AUDIO, 0); |
| if (err < 0) { |
| SNDERR("unable to find audio %s port %u plugin '%s'", io->pdesc & LADSPA_PORT_INPUT ? "input" : "output", (unsigned int)0, plugin->desc->Name); |
| return err; |
| } |
| } |
| 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'", 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'", 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, |
| snd_pcm_ladspa_plugin_io_t *io, |
| snd_pcm_ladspa_instance_t *instance) |
| { |
| unsigned long idx, midx; |
| |
| 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; |
| } |
| midx++; |
| } |
| 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", io->pdesc & LADSPA_PORT_INPUT ? "input" : "output", plugin->desc->Name, depth); |
| err++; |
| } |
| midx++; |
| } |
| if (err > 0) { |
| SNDERR("%i connection errors total", 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; |
| unsigned int in_channels; |
| unsigned int in_ports, out_ports; |
| snd_pcm_ladspa_instance_t *instance = NULL; |
| int err; |
| |
| 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; |
| list_for_each(pos, list) { |
| snd_pcm_ladspa_plugin_t *plugin = list_entry(pos, snd_pcm_ladspa_plugin_t, list); |
| 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) { |
| if (in_ports == 1 && out_ports == 1) |
| count = in_channels; |
| else |
| plugin->policy = SND_PCM_LADSPA_POLICY_NONE; |
| } |
| 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; |
| if (instance->handle == NULL) { |
| SNDERR("Unable to create instance of LADSPA plugin '%s'", plugin->desc->Name); |
| free(instance); |
| return -EINVAL; |
| } |
| list_add_tail(&instance->list, &plugin->instances); |
| if (plugin->policy == SND_PCM_LADSPA_POLICY_DUPLICATE) { |
| err = snd_pcm_ladspa_connect_plugin_duplicate(plugin, &plugin->input, &plugin->output, instance, idx); |
| if (err < 0) { |
| 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->desc->activate) |
| plugin->desc->activate(instance->handle); |
| } |
| 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 LADSPA_Data *snd_pcm_ladspa_allocate_zero(snd_pcm_ladspa_t *ladspa, unsigned int idx) |
| { |
| 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; |
| unsigned int channels = 16, nchannels; |
| unsigned int ichannels, ochannels; |
| void **pchannels, **npchannels; |
| unsigned int idx, chn; |
| |
| 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; |
| 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]; |
| assert(instance->input.ports.array[idx] != NO_ASSIGN); |
| if (chn >= nchannels) |
| nchannels = chn + 1; |
| } |
| for (idx = 0; idx < instance->output.channels.size; idx++) { |
| chn = instance->output.channels.array[idx]; |
| assert(instance->output.ports.array[idx] != NO_ASSIGN); |
| 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) { |
| free(pchannels); |
| 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) { |
| free(pchannels); |
| 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) { |
| free(pchannels); |
| return -ENOMEM; |
| } |
| pchannels[chn] = instance->output.m_data[idx] = instance->output.data[idx]; |
| } |
| } |
| } |
| /* 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]) { |
| 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) { |
| free(pchannels); |
| 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; |
| } |
| |
| static int snd_pcm_ladspa_init(snd_pcm_t *pcm) |
| { |
| snd_pcm_ladspa_t *ladspa = pcm->private_data; |
| int err; |
| |
| 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); |
| 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; |
| } |
| |
| static int snd_pcm_ladspa_hw_free(snd_pcm_t *pcm) |
| { |
| snd_pcm_ladspa_t *ladspa = pcm->private_data; |
| |
| snd_pcm_ladspa_free_instances(pcm, ladspa, 1); |
| return snd_pcm_generic_hw_free(pcm); |
| } |
| |
| static snd_pcm_uframes_t |
| snd_pcm_ladspa_write_areas(snd_pcm_t *pcm, |
| const snd_pcm_channel_area_t *areas, |
| snd_pcm_uframes_t offset, |
| snd_pcm_uframes_t size, |
| const snd_pcm_channel_area_t *slave_areas, |
| snd_pcm_uframes_t slave_offset, |
| snd_pcm_uframes_t *slave_sizep) |
| { |
| snd_pcm_ladspa_t *ladspa = pcm->private_data; |
| 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; |
| 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 |
| 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 = size2; |
| return size2; |
| } |
| |
| static snd_pcm_uframes_t |
| snd_pcm_ladspa_read_areas(snd_pcm_t *pcm, |
| const snd_pcm_channel_area_t *areas, |
| snd_pcm_uframes_t offset, |
| snd_pcm_uframes_t size, |
| const snd_pcm_channel_area_t *slave_areas, |
| snd_pcm_uframes_t slave_offset, |
| snd_pcm_uframes_t *slave_sizep) |
| { |
| snd_pcm_ladspa_t *ladspa = pcm->private_data; |
| 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; |
| 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 |
| 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 = size2; |
| return size2; |
| } |
| |
| 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, midx; |
| |
| if (io->port_bindings_size == 0) |
| goto __control; |
| snd_output_printf(out, " Audio %s port bindings:\n", io->pdesc == LADSPA_PORT_INPUT ? "input" : "output"); |
| for (idx = 0; idx < io->port_bindings_size; idx++) { |
| if (io->port_bindings[idx] == NO_ASSIGN) |
| snd_output_printf(out, " %i -> NONE\n", idx); |
| else |
| snd_output_printf(out, " %i -> %i\n", idx, io->port_bindings[idx]); |
| } |
| __control: |
| 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 = 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 && val != NO_ASSIGN) |
| snd_output_printf(out, " \"%s\"", plugin->desc->PortNames[val]); |
| } |
| } |
| |
| static void snd_pcm_ladspa_plugins_dump(struct list_head *list, snd_output_t *out) |
| { |
| struct list_head *pos, *pos2; |
| |
| list_for_each(pos, list) { |
| 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\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, &plugin->input, out); |
| snd_pcm_ladspa_dump_direction(plugin, &plugin->output, out); |
| } |
| } |
| |
| 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, " Playback:\n"); |
| snd_pcm_ladspa_plugins_dump(&ladspa->pplugins, out); |
| snd_output_printf(out, " Capture:\n"); |
| snd_pcm_ladspa_plugins_dump(&ladspa->cplugins, out); |
| if (pcm->setup) { |
| snd_output_printf(out, "Its setup is:\n"); |
| snd_pcm_dump_setup(pcm, out); |
| } |
| snd_output_printf(out, "Slave: "); |
| snd_pcm_dump(ladspa->plug.gen.slave, out); |
| } |
| |
| static const snd_pcm_ops_t snd_pcm_ladspa_ops = { |
| .close = snd_pcm_ladspa_close, |
| .info = snd_pcm_generic_info, |
| .hw_refine = snd_pcm_ladspa_hw_refine, |
| .hw_params = snd_pcm_ladspa_hw_params, |
| .hw_free = snd_pcm_ladspa_hw_free, |
| .sw_params = snd_pcm_generic_sw_params, |
| .channel_info = snd_pcm_generic_channel_info, |
| .dump = snd_pcm_ladspa_dump, |
| .nonblock = snd_pcm_generic_nonblock, |
| .async = snd_pcm_generic_async, |
| .mmap = snd_pcm_generic_mmap, |
| .munmap = snd_pcm_generic_munmap, |
| .query_chmaps = snd_pcm_generic_query_chmaps, |
| .get_chmap = snd_pcm_generic_get_chmap, |
| .set_chmap = snd_pcm_generic_set_chmap, |
| }; |
| |
| static int snd_pcm_ladspa_check_file(snd_pcm_ladspa_plugin_t * const plugin, |
| const char *filename, |
| const char *label, |
| const unsigned long ladspa_id) |
| { |
| void *handle; |
| |
| assert(filename); |
| handle = dlopen(filename, RTLD_LAZY); |
| if (handle) { |
| LADSPA_Descriptor_Function fcn = (LADSPA_Descriptor_Function)dlsym(handle, "ladspa_descriptor"); |
| if (fcn) { |
| long idx; |
| const LADSPA_Descriptor *d; |
| for (idx = 0; (d = fcn(idx)) != NULL; idx++) { |
| /* |
| * avoid locale problems - see ALSA bug#1553 |
| */ |
| #if 0 |
| if (strcmp(label, d->Label)) |
| continue; |
| #else |
| char *labellocale; |
| struct lconv *lc; |
| if (label != NULL) { |
| lc = localeconv (); |
| labellocale = malloc (strlen (label) + 1); |
| if (labellocale == NULL) { |
| dlclose(handle); |
| return -ENOMEM; |
| } |
| strcpy (labellocale, label); |
| if (strrchr(labellocale, '.')) |
| *strrchr (labellocale, '.') = *lc->decimal_point; |
| if (strcmp(label, d->Label) && strcmp(labellocale, d->Label)) { |
| free(labellocale); |
| continue; |
| } |
| free (labellocale); |
| } |
| #endif |
| if (ladspa_id > 0 && d->UniqueID != ladspa_id) |
| continue; |
| plugin->filename = strdup(filename); |
| if (plugin->filename == NULL) { |
| dlclose(handle); |
| return -ENOMEM; |
| } |
| plugin->dl_handle = handle; |
| plugin->desc = d; |
| return 1; |
| } |
| } |
| dlclose(handle); |
| } |
| return -ENOENT; |
| } |
| |
| static int snd_pcm_ladspa_check_dir(snd_pcm_ladspa_plugin_t * const plugin, |
| const char *path, |
| const char *label, |
| const unsigned long ladspa_id) |
| { |
| DIR *dir; |
| struct dirent * dirent; |
| int len = strlen(path), err; |
| int need_slash; |
| char *filename; |
| |
| if (len < 1) |
| return 0; |
| need_slash = path[len - 1] != '/'; |
| |
| dir = opendir(path); |
| if (!dir) |
| return -ENOENT; |
| |
| while (1) { |
| dirent = readdir(dir); |
| if (!dirent) { |
| closedir(dir); |
| return 0; |
| } |
| |
| filename = malloc(len + strlen(dirent->d_name) + 1 + need_slash); |
| if (filename == NULL) { |
| closedir(dir); |
| return -ENOMEM; |
| } |
| strcpy(filename, path); |
| if (need_slash) |
| strcat(filename, "/"); |
| strcat(filename, dirent->d_name); |
| err = snd_pcm_ladspa_check_file(plugin, filename, label, ladspa_id); |
| free(filename); |
| if (err < 0 && err != -ENOENT) { |
| closedir(dir); |
| return err; |
| } |
| if (err > 0) { |
| closedir(dir); |
| return 1; |
| } |
| } |
| /* never reached */ |
| return 0; |
| } |
| |
| static int snd_pcm_ladspa_look_for_plugin(snd_pcm_ladspa_plugin_t * const plugin, |
| const char *path, |
| const char *label, |
| const long ladspa_id) |
| { |
| const char *c; |
| size_t l; |
| int err; |
| |
| for (c = path; (l = strcspn(c, ": ")) > 0; ) { |
| char name[l + 1]; |
| char *fullpath; |
| memcpy(name, c, l); |
| name[l] = 0; |
| err = snd_user_file(name, &fullpath); |
| if (err < 0) |
| return err; |
| err = snd_pcm_ladspa_check_dir(plugin, fullpath, label, ladspa_id); |
| free(fullpath); |
| if (err < 0) |
| return err; |
| if (err > 0) |
| return 0; |
| c += l; |
| if (!*c) |
| break; |
| c++; |
| } |
| return -ENOENT; |
| } |
| |
| static int snd_pcm_ladspa_add_default_controls(snd_pcm_ladspa_plugin_t *lplug, |
| snd_pcm_ladspa_plugin_io_t *io) |
| { |
| unsigned int count = 0; |
| LADSPA_Data *array; |
| unsigned char *initialized; |
| unsigned long idx; |
| |
| 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; |
| |
| return 0; |
| } |
| |
| static int snd_pcm_ladspa_parse_controls(snd_pcm_ladspa_plugin_t *lplug, |
| snd_pcm_ladspa_plugin_io_t *io, |
| snd_config_t *controls) |
| { |
| snd_config_iterator_t i, next; |
| int err; |
| |
| 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; |
| } |
| io->controls_initialized[uval] = 1; |
| io->controls[uval] = (LADSPA_Data)dval; |
| } |
| |
| return 0; |
| } |
| |
| static int snd_pcm_ladspa_parse_bindings(snd_pcm_ladspa_plugin_t *lplug, |
| snd_pcm_ladspa_plugin_io_t *io, |
| snd_config_t *bindings) |
| { |
| unsigned int count = 0; |
| unsigned int *array; |
| snd_config_iterator_t i, next; |
| int err; |
| |
| if (snd_config_get_type(bindings) != SND_CONFIG_TYPE_COMPOUND) { |
| SNDERR("bindings definition must be a compound"); |
| return -EINVAL; |
| } |
| snd_config_for_each(i, next, bindings) { |
| snd_config_t *n = snd_config_iterator_entry(i); |
| const char *id; |
| long channel; |
| if (snd_config_get_id(n, &id) < 0) |
| continue; |
| err = safe_strtol(id, &channel); |
| if (err < 0 || channel < 0) { |
| SNDERR("Invalid channel number: %s", id); |
| return -EINVAL; |
| } |
| if (lplug->policy == SND_PCM_LADSPA_POLICY_DUPLICATE && channel > 0) { |
| SNDERR("Wrong channel specification for duplicate policy"); |
| return -EINVAL; |
| } |
| if (count < (unsigned int)(channel + 1)) |
| count = (unsigned int)(channel + 1); |
| } |
| if (count > 0) { |
| array = (unsigned int *)malloc(count * sizeof(unsigned int)); |
| if (! array) |
| return -ENOMEM; |
| memset(array, 0xff, count * sizeof(unsigned int)); |
| io->port_bindings_size = count; |
| io->port_bindings = array; |
| snd_config_for_each(i, next, bindings) { |
| snd_config_t *n = snd_config_iterator_entry(i); |
| const char *id, *sport; |
| long channel, port; |
| if (snd_config_get_id(n, &id) < 0) |
| continue; |
| err = safe_strtol(id, &channel); |
| if (err < 0 || channel < 0) { |
| assert(0); /* should never happen */ |
| return -EINVAL; |
| } |
| err = snd_config_get_integer(n, &port); |
| if (err >= 0) { |
| err = snd_pcm_ladspa_find_port(&array[channel], lplug, io->pdesc | LADSPA_PORT_AUDIO, port); |
| if (err < 0) { |
| SNDERR("Unable to find an audio port (%li) for channel %s", port, id); |
| return err; |
| } |
| continue; |
| } |
| err = snd_config_get_string(n, &sport); |
| if (err < 0) { |
| SNDERR("Invalid LADSPA port field type for %s", id); |
| return -EINVAL; |
| } |
| err = snd_pcm_ladspa_find_sport(&array[channel], lplug, io->pdesc | LADSPA_PORT_AUDIO, sport); |
| if (err < 0) { |
| SNDERR("Unable to find an audio port (%s) for channel %s", sport, id); |
| return err; |
| } |
| } |
| } |
| |
| return 0; |
| } |
| |
| static int snd_pcm_ladspa_parse_ioconfig(snd_pcm_ladspa_plugin_t *lplug, |
| snd_pcm_ladspa_plugin_io_t *io, |
| snd_config_t *conf) |
| { |
| snd_config_iterator_t i, next; |
| snd_config_t *bindings = NULL, *controls = NULL; |
| int err; |
| |
| /* always add default controls for both input and output */ |
| err = snd_pcm_ladspa_add_default_controls(lplug, io); |
| if (err < 0) { |
| SNDERR("error adding default controls"); |
| return err; |
| } |
| |
| if (conf == NULL) { |
| return 0; |
| } |
| |
| if (snd_config_get_type(conf) != SND_CONFIG_TYPE_COMPOUND) { |
| SNDERR("input or output definition must be a compound"); |
| return -EINVAL; |
| } |
| snd_config_for_each(i, next, conf) { |
| snd_config_t *n = snd_config_iterator_entry(i); |
| const char *id; |
| if (snd_config_get_id(n, &id) < 0) |
| continue; |
| if (strcmp(id, "bindings") == 0) { |
| bindings = n; |
| continue; |
| } |
| if (strcmp(id, "controls") == 0) { |
| controls = n; |
| continue; |
| } |
| } |
| |
| /* ignore values of parameters for output controls */ |
| if (controls && !(io->pdesc & LADSPA_PORT_OUTPUT)) { |
| err = snd_pcm_ladspa_parse_controls(lplug, io, controls); |
| if (err < 0) |
| return err; |
| } |
| |
| if (bindings) { |
| err = snd_pcm_ladspa_parse_bindings(lplug, io, bindings); |
| if (err < 0) |
| return err; |
| } |
| |
| |
| return 0; |
| } |
| |
| static int snd_pcm_ladspa_add_plugin(struct list_head *list, |
| const char *path, |
| snd_config_t *plugin, |
| int reverse) |
| { |
| snd_config_iterator_t i, next; |
| const char *label = NULL, *filename = NULL; |
| long ladspa_id = 0; |
| int err; |
| snd_pcm_ladspa_plugin_t *lplug; |
| snd_pcm_ladspa_policy_t policy = SND_PCM_LADSPA_POLICY_DUPLICATE; |
| snd_config_t *input = NULL, *output = NULL; |
| |
| snd_config_for_each(i, next, plugin) { |
| snd_config_t *n = snd_config_iterator_entry(i); |
| const char *id; |
| if (snd_config_get_id(n, &id) < 0) |
| continue; |
| if (strcmp(id, "label") == 0) { |
| err = snd_config_get_string(n, &label); |
| if (err < 0) |
| return err; |
| continue; |
| } |
| if (strcmp(id, "id") == 0) { |
| err = snd_config_get_integer(n, &ladspa_id); |
| if (err < 0) |
| return err; |
| continue; |
| } |
| if (strcmp(id, "filename") == 0) { |
| err = snd_config_get_string(n, &filename); |
| if (err < 0) |
| return err; |
| continue; |
| } |
| if (strcmp(id, "input") == 0) { |
| input = n; |
| continue; |
| } |
| if (strcmp(id, "output") == 0) { |
| output = n; |
| continue; |
| } |
| if (strcmp(id, "policy") == 0) { |
| const char *str; |
| err = snd_config_get_string(n, &str); |
| if (err < 0) { |
| SNDERR("policy field must be a string"); |
| return err; |
| } |
| if (strcmp(str, "none") == 0) |
| policy = SND_PCM_LADSPA_POLICY_NONE; |
| else if (strcmp(str, "duplicate") == 0) |
| policy = SND_PCM_LADSPA_POLICY_DUPLICATE; |
| else { |
| SNDERR("unknown policy definition"); |
| return -EINVAL; |
| } |
| continue; |
| } |
| } |
| if (label == NULL && ladspa_id <= 0) { |
| SNDERR("no plugin label or id"); |
| return -EINVAL; |
| } |
| lplug = (snd_pcm_ladspa_plugin_t *)calloc(1, sizeof(snd_pcm_ladspa_plugin_t)); |
| if (lplug == NULL) |
| return -ENOMEM; |
| lplug->policy = policy; |
| lplug->input.pdesc = LADSPA_PORT_INPUT; |
| lplug->output.pdesc = LADSPA_PORT_OUTPUT; |
| INIT_LIST_HEAD(&lplug->instances); |
| if (filename) { |
| err = snd_pcm_ladspa_check_file(lplug, filename, label, ladspa_id); |
| if (err < 0) { |
| SNDERR("Unable to load plugin '%s' ID %li, filename '%s'", label, ladspa_id, filename); |
| free(lplug); |
| return err; |
| } |
| } else { |
| err = snd_pcm_ladspa_look_for_plugin(lplug, path, label, ladspa_id); |
| if (err < 0) { |
| SNDERR("Unable to find or load plugin '%s' ID %li, path '%s'", label, ladspa_id, path); |
| free(lplug); |
| return err; |
| } |
| } |
| if (!reverse) { |
| list_add_tail(&lplug->list, list); |
| } else { |
| list_add(&lplug->list, list); |
| } |
| err = snd_pcm_ladspa_parse_ioconfig(lplug, &lplug->input, input); |
| if (err < 0) |
| return err; |
| err = snd_pcm_ladspa_parse_ioconfig(lplug, &lplug->output, output); |
| if (err < 0) |
| return err; |
| return 0; |
| } |
| |
| static int snd_pcm_ladspa_build_plugins(struct list_head *list, |
| const char *path, |
| snd_config_t *plugins, |
| int reverse) |
| { |
| snd_config_iterator_t i, next; |
| int idx = 0, hit, err; |
| |
| if (plugins == NULL) /* nothing TODO */ |
| return 0; |
| if (snd_config_get_type(plugins) != SND_CONFIG_TYPE_COMPOUND) { |
| SNDERR("plugins must be defined inside a compound"); |
| return -EINVAL; |
| } |
| do { |
| hit = 0; |
| snd_config_for_each(i, next, plugins) { |
| snd_config_t *n = snd_config_iterator_entry(i); |
| const char *id; |
| long i; |
| if (snd_config_get_id(n, &id) < 0) |
| continue; |
| err = safe_strtol(id, &i); |
| if (err < 0) { |
| SNDERR("id of field %s is not an integer", id); |
| return err; |
| } |
| if (i == idx) { |
| idx++; |
| err = snd_pcm_ladspa_add_plugin(list, path, n, reverse); |
| if (err < 0) |
| return err; |
| hit = 1; |
| } |
| } |
| } while (hit); |
| if (list_empty(list)) { |
| SNDERR("empty plugin list is not accepted"); |
| return -EINVAL; |
| } |
| return 0; |
| } |
| |
| /** |
| * \brief Creates a new LADSPA<->ALSA Plugin |
| * \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 |
| * \param close_slave When set, the slave PCM handle is closed with copy PCM |
| * \retval zero on success otherwise a negative error code |
| * \warning Using of this function might be dangerous in the sense |
| * of compatibility reasons. The prototype might be freely |
| * changed in future. |
| */ |
| 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) |
| { |
| snd_pcm_t *pcm; |
| snd_pcm_ladspa_t *ladspa; |
| int err, reverse = 0; |
| |
| assert(pcmp && (ladspa_pplugins || ladspa_cplugins) && slave); |
| |
| if (!ladspa_path && !(ladspa_path = getenv("LADSPA_PATH"))) |
| return -ENOENT; |
| ladspa = calloc(1, sizeof(snd_pcm_ladspa_t)); |
| if (!ladspa) |
| return -ENOMEM; |
| snd_pcm_plugin_init(&ladspa->plug); |
| ladspa->plug.init = snd_pcm_ladspa_init; |
| ladspa->plug.read = snd_pcm_ladspa_read_areas; |
| ladspa->plug.write = snd_pcm_ladspa_write_areas; |
| ladspa->plug.undo_read = snd_pcm_plugin_undo_read_generic; |
| ladspa->plug.undo_write = snd_pcm_plugin_undo_write_generic; |
| ladspa->plug.gen.slave = slave; |
| ladspa->plug.gen.close_slave = close_slave; |
| |
| 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); |
| if (err < 0) { |
| snd_pcm_ladspa_free(ladspa); |
| return err; |
| } |
| } |
| if (slave->stream == SND_PCM_STREAM_CAPTURE) { |
| if (ladspa_cplugins == ladspa_pplugins) |
| reverse = 1; |
| err = snd_pcm_ladspa_build_plugins(&ladspa->cplugins, ladspa_path, ladspa_cplugins, reverse); |
| if (err < 0) { |
| snd_pcm_ladspa_free(ladspa); |
| return err; |
| } |
| } |
| |
| err = snd_pcm_new(&pcm, SND_PCM_TYPE_LADSPA, name, slave->stream, slave->mode); |
| if (err < 0) { |
| snd_pcm_ladspa_free(ladspa); |
| return err; |
| } |
| pcm->ops = &snd_pcm_ladspa_ops; |
| pcm->fast_ops = &snd_pcm_plugin_fast_ops; |
| pcm->private_data = ladspa; |
| pcm->poll_fd = slave->poll_fd; |
| pcm->poll_events = slave->poll_events; |
| pcm->tstamp_type = slave->tstamp_type; |
| snd_pcm_set_hw_ptr(pcm, &ladspa->plug.hw_ptr, -1, 0); |
| snd_pcm_set_appl_ptr(pcm, &ladspa->plug.appl_ptr, -1, 0); |
| *pcmp = pcm; |
| |
| return 0; |
| } |
| |
| /*! \page pcm_plugins |
| |
| \section pcm_plugins_ladpsa Plugin: LADSPA <-> ALSA |
| |
| This plugin allows to apply a set of LADPSA plugins. |
| The input and output format is always #SND_PCM_FORMAT_FLOAT (note: this type |
| 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. |
| |
| \code |
| pcm.name { |
| type ladspa # ALSA<->LADSPA PCM |
| slave STR # Slave name |
| # or |
| slave { # Slave definition |
| pcm STR # Slave 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 |
| capture_plugins { # Definition for capture direction |
| N { # Configuration for LADPSA plugin N |
| [id INT] # LADSPA plugin ID (for example 1043) |
| [label STR] # LADSPA plugin label (for example 'delay_5s') |
| [filename STR] # Full filename of .so library with LADSPA plugin code |
| [policy STR] # Policy can be 'none' or 'duplicate' |
| input | output { |
| bindings { |
| 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 |
| } |
| } |
| } |
| } |
| } |
| \endcode |
| |
| \subsection pcm_plugins_ladspa_funcref Function reference |
| |
| <UL> |
| <LI>snd_pcm_ladspa_open() |
| <LI>_snd_pcm_ladspa_open() |
| </UL> |
| |
| */ |
| |
| /** |
| * \brief Creates a new LADSPA<->ALSA PCM |
| * \param pcmp Returns created PCM handle |
| * \param name Name of PCM |
| * \param root Root configuration node |
| * \param conf Configuration node with LADSPA<->ALSA PCM description |
| * \param stream Stream type |
| * \param mode Stream mode |
| * \retval zero on success otherwise a negative error code |
| * \warning Using of this function might be dangerous in the sense |
| * of compatibility reasons. The prototype might be freely |
| * changed in future. |
| */ |
| int _snd_pcm_ladspa_open(snd_pcm_t **pcmp, const char *name, |
| snd_config_t *root, snd_config_t *conf, |
| snd_pcm_stream_t stream, int mode) |
| { |
| snd_config_iterator_t i, next; |
| int err; |
| 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); |
| const char *id; |
| if (snd_config_get_id(n, &id) < 0) |
| continue; |
| if (snd_pcm_conf_generic_id(id)) |
| continue; |
| if (strcmp(id, "slave") == 0) { |
| slave = n; |
| continue; |
| } |
| if (strcmp(id, "path") == 0) { |
| 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; |
| } |
| if (strcmp(id, "playback_plugins") == 0) { |
| pplugins = n; |
| continue; |
| } |
| if (strcmp(id, "capture_plugins") == 0) { |
| cplugins = n; |
| continue; |
| } |
| SNDERR("Unknown field %s", id); |
| return -EINVAL; |
| } |
| if (!slave) { |
| SNDERR("slave is not defined"); |
| return -EINVAL; |
| } |
| if (plugins) { |
| if (pplugins || cplugins) { |
| SNDERR("'plugins' definition cannot be combined with 'playback_plugins' or 'capture_plugins'"); |
| return -EINVAL; |
| } |
| pplugins = plugins; |
| cplugins = plugins; |
| } |
| err = snd_pcm_slave_conf(root, slave, &sconf, 0); |
| if (err < 0) |
| return err; |
| err = snd_pcm_open_slave(&spcm, root, sconf, stream, mode, conf); |
| snd_config_delete(sconf); |
| if (err < 0) |
| return err; |
| err = snd_pcm_ladspa_open(pcmp, name, path, channels, pplugins, cplugins, spcm, 1); |
| if (err < 0) |
| snd_pcm_close(spcm); |
| return err; |
| } |
| #ifndef DOC_HIDDEN |
| SND_DLSYM_BUILD_VERSION(_snd_pcm_ladspa_open, SND_PCM_DLSYM_VERSION); |
| #endif |