diff --git a/CMakeLists.txt b/CMakeLists.txt index ebaa833a2..83869dd30 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -52,6 +52,7 @@ option(SWIG_PHP "Enable SWIG PHP bindings" OFF) option(SWIG_PYTHON "Enable SWIG Python bindings" OFF) option(SWIG_RUBY "Enable SWIG Ruby bindings" OFF) option(SWIG_TCL "Enable SWIG Tcl bindings" OFF) +option(USE_LV2 "Enable LV2 features" ON) if(WIN32) option(WINDOWS_DEPLOY "Install exes/libs directly to prefix (no subdir /bin)" ON) @@ -247,6 +248,10 @@ if(MOD_JACKRACK) list(APPEND MLT_SUPPORTED_COMPONENTS jackrack) endif() +if(USE_LV2) + pkg_check_modules(lilv IMPORTED_TARGET lilv-0) +endif() + if(MOD_KDENLIVE) list(APPEND MLT_SUPPORTED_COMPONENTS kdenlive) endif() @@ -629,5 +634,6 @@ add_feature_info("SWIG: PHP" SWIG_PHP "") add_feature_info("SWIG: Python" SWIG_PYTHON "") add_feature_info("SWIG: Ruby" SWIG_RUBY "") add_feature_info("SWIG: Tcl" SWIG_TCL "") +add_feature_info("lv2: LV2 Plugins support" USE_LV2 "") feature_summary(WHAT ALL INCLUDE_QUIET_PACKAGES FATAL_ON_MISSING_REQUIRED_PACKAGES) diff --git a/src/modules/jackrack/CMakeLists.txt b/src/modules/jackrack/CMakeLists.txt index 6632de500..34c8ae17f 100644 --- a/src/modules/jackrack/CMakeLists.txt +++ b/src/modules/jackrack/CMakeLists.txt @@ -16,6 +16,11 @@ if(TARGET JACK::JACK) install(FILES consumer_jack.yml DESTINATION ${MLT_INSTALL_DATA_DIR}/jackrack) endif() +if(USE_LV2) + target_compile_definitions(mltjackrack PRIVATE WITH_LV2) + install(FILES filter_lv2.yml producer_lv2.yml DESTINATION ${MLT_INSTALL_DATA_DIR}/jackrack) +endif() + if(GPL AND TARGET PkgConfig::xml AND TARGET PkgConfig::glib AND ladspa_h_FOUND) target_sources(mltjackrack PRIVATE jack_rack.c jack_rack.h @@ -40,6 +45,13 @@ if(GPL AND TARGET PkgConfig::xml AND TARGET PkgConfig::glib AND ladspa_h_FOUND) target_sources(mltjackrack PRIVATE filter_jackrack.c) install(FILES filter_jackrack.yml DESTINATION ${MLT_INSTALL_DATA_DIR}/jackrack) endif() + + if(USE_LV2 AND TARGET PkgConfig::lilv) + target_link_libraries(mltjackrack PRIVATE PkgConfig::lilv) + target_sources(mltjackrack PRIVATE filter_lv2.c producer_lv2.c lv2_rack.c lv2_plugin.c lv2_process.c lv2_plugin_settings.c) + install(FILES filter_lv2.yml producer_lv2.yml DESTINATION ${MLT_INSTALL_DATA_DIR}/jackrack) + endif() + endif() set_target_properties(mltjackrack PROPERTIES LIBRARY_OUTPUT_DIRECTORY "${MLT_MODULE_OUTPUT_DIRECTORY}") diff --git a/src/modules/jackrack/factory.c b/src/modules/jackrack/factory.c index 3b9939a12..498e7c12c 100644 --- a/src/modules/jackrack/factory.c +++ b/src/modules/jackrack/factory.c @@ -1,6 +1,6 @@ /* * factory.c -- the factory method interfaces - * Copyright (C) 2003-2022 Meltytech, LLC + * Copyright (C) 2003-2024 Meltytech, LLC * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -49,8 +49,38 @@ extern mlt_producer producer_ladspa_init(mlt_profile profile, const char *id, char *arg); +#ifdef WITH_LV2 + +#include + +/* lv2 extenstions */ +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +extern mlt_filter filter_lv2_init(mlt_profile profile, + mlt_service_type type, + const char *id, + char *arg); +extern mlt_producer producer_lv2_init(mlt_profile profile, + mlt_service_type type, + const char *id, + char *arg); +#endif + plugin_mgr_t *g_jackrack_plugin_mgr = NULL; +#ifdef WITH_LV2 +lv2_mgr_t *g_lv2_plugin_mgr = NULL; +#endif + static void add_port_to_metadata(mlt_properties p, plugin_desc_t *desc, int j) { LADSPA_Data sample_rate = 48000; @@ -211,8 +241,172 @@ static mlt_properties metadata(mlt_service_type type, const char *id, char *data return result; } +#ifdef WITH_LV2 + +static void lv2_add_port_to_metadata(mlt_properties p, lv2_plugin_desc_t *desc, int j) +{ + LADSPA_PortRangeHintDescriptor hint_descriptor = desc->port_range_hints[j].HintDescriptor; + + mlt_properties_set(p, "title", desc->port_names[j]); + if (LADSPA_IS_HINT_INTEGER(hint_descriptor)) { + mlt_properties_set(p, "type", "integer"); + mlt_properties_set_int(p, + "default", + (int) desc->def_values[j]); + mlt_properties_set_double(p, + "minimum", + (int) desc->min_values[j]); + mlt_properties_set_double(p, + "maximum", + (int) desc->max_values[j]); + } else if (LADSPA_IS_HINT_TOGGLED(hint_descriptor)) { + mlt_properties_set(p, "type", "boolean"); + mlt_properties_set_int(p, + "default", + desc->def_values[j]); + } else { + mlt_properties_set(p, "type", "float"); + mlt_properties_set_double(p, + "default", + desc->def_values[j]); + mlt_properties_set_double(p, + "minimum", + desc->min_values[j]); + mlt_properties_set_double(p, + "maximum", + desc->max_values[j]); + + } + + if (LADSPA_IS_HINT_ENUMERATION(hint_descriptor)) + { + /* WIP */ + } + + if (LADSPA_IS_HINT_LOGARITHMIC(hint_descriptor)) + mlt_properties_set(p, "scale", "log"); + mlt_properties_set(p, "mutable", "yes"); + mlt_properties_set(p, "animation", "yes"); +} + +static mlt_properties lv2_metadata(mlt_service_type type, const char *id, char *data) +{ + char file[PATH_MAX]; + if (type == mlt_service_filter_type) { + + snprintf(file, + PATH_MAX, + "%s/lv2/%s", + mlt_environment("MLT_DATA"), + strncmp(id, "lv2.", 4) ? data : "filter_lv2.yml"); + } else { + snprintf(file, + PATH_MAX, + "%s/lv2/%s", + mlt_environment("MLT_DATA"), + strncmp(id, "lv2.", 4) ? data : "producer_lv2.yml"); + } + mlt_properties result = mlt_properties_parse_yaml(file); + + if (!strncmp(id, "lv2.", 4)) { + // Annotate the yaml properties with lv2 control port info. + lv2_plugin_desc_t *desc = lv2_mgr_get_any_desc(g_lv2_plugin_mgr, (char *) &id[4]); + + if (desc) { + + mlt_properties params = mlt_properties_new(); + mlt_properties p; + char key[20]; + int i; + + mlt_properties_set(result, "identifier", id); + mlt_properties_set(result, "title", desc->name); + mlt_properties_set(result, "creator", desc->maker ? desc->maker : "unknown"); + mlt_properties_set(result, "description", "LADSPA plugin"); + mlt_properties_set_data(result, + "parameters", + params, + 0, + (mlt_destructor) mlt_properties_close, + NULL); + for (i = 0; i < desc->control_port_count; i++) { + int j = desc->control_port_indicies[i]; + p = mlt_properties_new(); + snprintf(key, sizeof(key), "%d", mlt_properties_count(params)); + mlt_properties_set_data(params, + key, + p, + 0, + (mlt_destructor) mlt_properties_close, + NULL); + snprintf(key, sizeof(key), "%d", j); + mlt_properties_set(p, "identifier", key); + lv2_add_port_to_metadata(p, desc, j); + mlt_properties_set(p, "mutable", "yes"); + } + for (i = 0; i < desc->status_port_count; i++) { + int j = desc->status_port_indicies[i]; + p = mlt_properties_new(); + snprintf(key, sizeof(key), "%d", mlt_properties_count(params)); + mlt_properties_set_data(params, + key, + p, + 0, + (mlt_destructor) mlt_properties_close, + NULL); + snprintf(key, sizeof(key), "%d[*]", j); + mlt_properties_set(p, "identifier", key); + lv2_add_port_to_metadata(p, desc, j); + mlt_properties_set(p, "readonly", "yes"); + } + + p = mlt_properties_new(); + snprintf(key, sizeof(key), "%d", mlt_properties_count(params)); + mlt_properties_set_data(params, key, p, 0, (mlt_destructor) mlt_properties_close, NULL); + mlt_properties_set(p, "identifier", "instances"); + mlt_properties_set(p, "title", "Instances"); + mlt_properties_set(p, + "description", + "The number of instances of the plugin that are in use.\n" + "MLT will create the number of plugins that are required " + "to support the number of audio channels.\n" + "Status parameters (readonly) are provided for each instance " + "and are accessed by specifying the instance number after the " + "identifier (starting at zero).\n" + "e.g. 9[0] provides the value of status 9 for the first instance."); + mlt_properties_set(p, "type", "integer"); + mlt_properties_set(p, "readonly", "yes"); + + if (type == mlt_service_filter_type) { + + p = mlt_properties_new(); + snprintf(key, sizeof(key), "%d", mlt_properties_count(params)); + mlt_properties_set_data(params, + key, + p, + 0, + (mlt_destructor) mlt_properties_close, + NULL); + mlt_properties_set(p, "identifier", "wetness"); + mlt_properties_set(p, "title", "Wet/Dry"); + mlt_properties_set(p, "type", "float"); + mlt_properties_set_double(p, "default", 1); + mlt_properties_set_double(p, "minimum", 0); + mlt_properties_set_double(p, "maximum", 1); + mlt_properties_set(p, "mutable", "yes"); + mlt_properties_set(p, "animation", "yes"); + } + } + } + + return result; +} + +#endif + MLT_REPOSITORY { + #ifdef GPL GSList *list; g_jackrack_plugin_mgr = plugin_mgr_new(); @@ -235,6 +429,39 @@ MLT_REPOSITORY } mlt_factory_register_for_clean_up(g_jackrack_plugin_mgr, (mlt_destructor) plugin_mgr_destroy); +#ifdef WITH_LV2 + g_lv2_plugin_mgr = lv2_mgr_new(); + + for (list = g_lv2_plugin_mgr->all_plugins; list; list = g_slist_next(list)) { + + lv2_plugin_desc_t *desc = (lv2_plugin_desc_t *) list->data; + char *s = NULL; + s = calloc(1, strlen("lv2.") + strlen (desc->uri) + 1); + + sprintf(s, "lv2.%s", desc->uri); + + char *str_ptr = strchr(s, ':'); + while (str_ptr != NULL) + { + *str_ptr++ = '<'; + str_ptr = strchr(str_ptr, ':'); + } + + if (desc->has_input) { + MLT_REGISTER(mlt_service_filter_type, s, filter_lv2_init); + MLT_REGISTER_METADATA(mlt_service_filter_type, s, lv2_metadata, NULL); + } else { + MLT_REGISTER(mlt_service_producer_type, s, producer_lv2_init); + MLT_REGISTER_METADATA(mlt_service_producer_type, s, lv2_metadata, NULL); + } + + if (s) + { + free(s); + } + } +#endif + #ifdef WITH_JACK MLT_REGISTER(mlt_service_filter_type, "jack", filter_jackrack_init); MLT_REGISTER_METADATA(mlt_service_filter_type, "jack", metadata, "filter_jack.yml"); diff --git a/src/modules/jackrack/filter_lv2.c b/src/modules/jackrack/filter_lv2.c new file mode 100644 index 000000000..5904ab636 --- /dev/null +++ b/src/modules/jackrack/filter_lv2.c @@ -0,0 +1,279 @@ +/* + * filter_ladspa.c -- filter audio through LADSPA plugins + * Copyright (C) 2004-2018 Meltytech, LLC + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include + +#include "lv2_rack.h" + +#define BUFFER_LEN (10000) +#define MAX_SAMPLE_COUNT (4096) + +static lv2_rack_t *initialise_lv2_rack(mlt_properties properties, int channels) +{ + lv2_rack_t *lv2rack = NULL; + char *resource = mlt_properties_get(properties, "resource"); + if (!resource && mlt_properties_get(properties, "src")) + resource = mlt_properties_get(properties, "src"); + + char *plugin_id = NULL; + plugin_id = mlt_properties_get (properties, "_pluginid"); + + // Start LV2Rack + if (resource || plugin_id) { + + // Create LV2Rack without Jack client name so that it only uses LV2 + lv2rack = lv2_rack_new(NULL, channels); + mlt_properties_set_data(properties, + "lv2rack", + lv2rack, + 0, + (mlt_destructor) lv2_rack_destroy, + NULL); + + if (resource) + // Load LV2 Rack XML file + lv2_rack_open_file(lv2rack, resource); + else if (plugin_id) { + // Load one LV2 plugin by its URI + + char *id = plugin_id; + lv2_plugin_desc_t *desc = lv2_mgr_get_any_desc(lv2rack->plugin_mgr, id); + + lv2_plugin_t *plugin; + if (desc && (plugin = lv2_rack_instantiate_plugin(lv2rack, desc))) { + + plugin->enabled = TRUE; + lv2_process_add_plugin(lv2rack->procinfo, plugin); + mlt_properties_set_int(properties, "instances", plugin->copies); + } else { + //mlt_log_error(properties, "failed to load plugin %lu\n", id); + mlt_log_error(properties, "failed to load plugin `%s`\n", id); + return lv2rack; + } + + if (plugin && plugin->desc && plugin->copies == 0) { + // Calculate the number of channels that will work with this plugin + int request_channels = plugin->desc->channels; + while (request_channels < channels) + request_channels += plugin->desc->channels; + + if (request_channels != channels) { + // Try to load again with a compatible number of channels. + mlt_log_warning( + properties, + "Not compatible with %d channels. Requesting %d channels instead.\n", + channels, + request_channels); + lv2rack = initialise_lv2_rack(properties, request_channels); + } else { + mlt_log_error(properties, "Invalid plugin configuration: `%s`\n", id); + return lv2rack; + } + } + + if (plugin && plugin->desc && plugin->copies) + mlt_log_debug(properties, + "Plugin Initialized. Channels: %lu\tCopies: %d\tTotal: %lu\n", + plugin->desc->channels, + plugin->copies, + lv2rack->channels); + } + } + return lv2rack; +} + +/** Get the audio. +*/ + +static int lv2_get_audio(mlt_frame frame, + void **buffer, + mlt_audio_format *format, + int *frequency, + int *channels, + int *samples) +{ + int error = 0; + + // Get the filter service + mlt_filter filter = mlt_frame_pop_audio(frame); + + // Get the filter properties + mlt_properties filter_properties = MLT_FILTER_PROPERTIES(filter); + + // Check if the channel configuration has changed + int prev_channels = mlt_properties_get_int(filter_properties, "_prev_channels"); + if (prev_channels != *channels) { + if (prev_channels) { + mlt_log_info(MLT_FILTER_SERVICE(filter), + "Channel configuration changed. Old: %d New: %d.\n", + prev_channels, + *channels); + mlt_properties_set_data(filter_properties, + "lv2rack", + NULL, + 0, + (mlt_destructor) NULL, + NULL); + } + mlt_properties_set_int(filter_properties, "_prev_channels", *channels); + } + + // Initialise LV2 if needed + lv2_rack_t *lv2rack = mlt_properties_get_data(filter_properties, "lv2rack", NULL); + if (lv2rack == NULL) { + lv2_sample_rate = *frequency; // global inside lv2_rack + + lv2rack = initialise_lv2_rack(filter_properties, *channels); + } + + char *plugin_id = NULL; + plugin_id = mlt_properties_get (filter_properties, "_pluginid"); + + if (lv2rack && lv2rack->procinfo && lv2rack->procinfo->chain && plugin_id) { + + + lv2_plugin_t *plugin = lv2rack->procinfo->chain; + LADSPA_Data value; + int i, c; + mlt_position position = mlt_filter_get_position(filter, frame); + mlt_position length = mlt_filter_get_length2(filter, frame); + + // Get the producer's audio + *format = mlt_audio_float; + mlt_frame_get_audio(frame, buffer, format, frequency, channels, samples); + + // Resize the buffer if necessary. + if (*channels < lv2rack->channels) { + // Add extra channels to satisfy the plugin. + // Extra channels in the buffer will be ignored by downstream services. + int old_size = mlt_audio_format_size(*format, *samples, *channels); + int new_size = mlt_audio_format_size(*format, *samples, lv2rack->channels); + uint8_t *new_buffer = mlt_pool_alloc(new_size); + memcpy(new_buffer, *buffer, old_size); + // Put silence in extra channels. + memset(new_buffer + old_size, 0, new_size - old_size); + mlt_frame_set_audio(frame, new_buffer, *format, new_size, mlt_pool_release); + *buffer = new_buffer; + } + + for (i = 0; i < plugin->desc->control_port_count; i++) { + // Apply the control port values + char key[20]; + value = plugin->desc->def_values[plugin->desc->control_port_indicies[i]]; + snprintf(key, sizeof(key), "%d", (int) plugin->desc->control_port_indicies[i]); + + if (mlt_properties_get(filter_properties, key)) + value = mlt_properties_anim_get_double(filter_properties, key, position, length); + for (c = 0; c < plugin->copies; c++) + { + plugin->holders[c].control_memory[i] = value; + } + } + + plugin->wet_dry_enabled = mlt_properties_get(filter_properties, "wetness") != NULL; + if (plugin->wet_dry_enabled) { + value = mlt_properties_anim_get_double(filter_properties, "wetness", position, length); + for (c = 0; c < lv2rack->channels; c++) + plugin->wet_dry_values[c] = value; + } + + // Configure the buffers + LADSPA_Data **input_buffers = mlt_pool_alloc(sizeof(LADSPA_Data *) * lv2rack->channels); + LADSPA_Data **output_buffers = mlt_pool_alloc(sizeof(LADSPA_Data *) * lv2rack->channels); + + // Some plugins crash with too many frames (samples). + // So, feed the plugin with N samples per loop iteration. + int samples_offset = 0; + int sample_count = MIN(*samples, MAX_SAMPLE_COUNT); + for (i = 0; samples_offset < *samples; i++) { + int j = 0; + for (; j < lv2rack->channels; j++) + output_buffers[j] = input_buffers[j] = (LADSPA_Data *) *buffer + j * (*samples) + + samples_offset; + sample_count = MIN(*samples - samples_offset, MAX_SAMPLE_COUNT); + // Do LV2 processing + error = process_lv2(lv2rack->procinfo, sample_count, input_buffers, output_buffers); + + samples_offset += MAX_SAMPLE_COUNT; + } + + mlt_pool_release(input_buffers); + mlt_pool_release(output_buffers); + + // read the status port values + for (i = 0; i < plugin->desc->status_port_count; i++) { + char key[20]; + int p = plugin->desc->status_port_indicies[i]; + for (c = 0; c < plugin->copies; c++) { + snprintf(key, sizeof(key), "%d[%d]", p, c); + value = plugin->holders[c].status_memory[i]; + mlt_properties_set_double(filter_properties, key, value); + } + } + } else { + // Nothing to do. + error = mlt_frame_get_audio(frame, buffer, format, frequency, channels, samples); + } + + return error; +} + +/** Filter processing. +*/ + +static mlt_frame filter_process(mlt_filter this, mlt_frame frame) +{ + if (mlt_frame_is_test_audio(frame) == 0) { + mlt_frame_push_audio(frame, this); + mlt_frame_push_audio(frame, lv2_get_audio); + } + + return frame; +} + +/** Constructor for the filter. +*/ +mlt_filter filter_lv2_init(mlt_profile profile, mlt_service_type type, const char *id, char *arg) +{ + mlt_filter this = mlt_filter_new(); + /* mlt_filter this = mlt_factory_filter(profile, id, arga); */ + + if (this != NULL) { + mlt_properties properties = MLT_FILTER_PROPERTIES(this); + this->process = filter_process; + mlt_properties_set(properties, "resource", arg); + if (!strncmp(id, "lv2.", 4)) + { + mlt_properties_set(properties, "_pluginid", id + 4); + } + } + + return this; +} diff --git a/src/modules/jackrack/filter_lv2.yml b/src/modules/jackrack/filter_lv2.yml new file mode 100644 index 000000000..95ccaa739 --- /dev/null +++ b/src/modules/jackrack/filter_lv2.yml @@ -0,0 +1,25 @@ +schema_version: 7.0 +type: filter +identifier: lv2 +title: LV2 +version: 1 +license: GPLv2 +language: en +url: http://www.lv2.org/ +creator: mr.fantastic +tags: + - Audio +description: Process audio using LV2 plugins. +notes: > + Automatically adapts to the number of channels and sampling rate of the consumer. +bugs: + - Some effects have a temporal side-effect that may not work well. + +parameters: + - identifier: resource + argument: yes + title: JACK Rack XML file + type: string + description: > + Runs a JACK Rack project to process audio through a stack of + LV2 filters without using JACK. diff --git a/src/modules/jackrack/lv2_plugin.c b/src/modules/jackrack/lv2_plugin.c new file mode 100644 index 000000000..e86a143dd --- /dev/null +++ b/src/modules/jackrack/lv2_plugin.c @@ -0,0 +1,587 @@ +/* + * JACK Rack + * + * Original: + * Copyright (C) Robert Ham 2002, 2003 (node@users.sourceforge.net) + * + * Modification for MLT: + * Copyright (C) 2004-2021 Meltytech, LLC + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include +#include +#include +#include +#include +#include + +#include + +#include "lv2_plugin.h" +#include "lv2_rack.h" +#include "lv2_process.h" +#include "framework/mlt_log.h" + +#define CONTROL_FIFO_SIZE 128 + +extern const LV2_Feature* features[]; + +#ifdef WITH_JACK +/* swap over the jack ports in two plugins */ +static void +plugin_swap_aux_ports (lv2_plugin_t * plugin, lv2_plugin_t * other) +{ + guint copy; + jack_port_t ** aux_ports_tmp; + + for (copy = 0; copy < plugin->copies; copy++) + { + aux_ports_tmp = other->holders[copy].aux_ports; + other->holders[copy].aux_ports = plugin->holders[copy].aux_ports; + plugin->holders[copy].aux_ports = aux_ports_tmp; + } +} +#endif + +/** connect up the LV2 instance's input buffers to the previous + plugin's audio memory. make sure to check that plugin->prev + exists. */ +void +lv2_plugin_connect_input_ports (lv2_plugin_t * plugin, LADSPA_Data ** inputs) +{ + gint copy; + unsigned long channel; + unsigned long rack_channel; + + if (!plugin || !inputs) + return; + + rack_channel = 0; + for (copy = 0; copy < plugin->copies; copy++) + { + for (channel = 0; channel < plugin->desc->channels; channel++) + { + lilv_instance_connect_port(plugin->holders[copy].instance, plugin->desc->audio_input_port_indicies[channel], + inputs[rack_channel]); + + rack_channel++; + } + } + + plugin->audio_input_memory = inputs; +} + + +/** connect up a plugin's output ports to its own audio_output_memory output memory */ +void +lv2_plugin_connect_output_ports (lv2_plugin_t * plugin) +{ + gint copy; + unsigned long channel; + unsigned long rack_channel = 0; + + if (!plugin) + return; + + + for (copy = 0; copy < plugin->copies; copy++) + { + for (channel = 0; channel < plugin->desc->channels; channel++) + { + lilv_instance_connect_port(plugin->holders[copy].instance, plugin->desc->audio_output_port_indicies[channel], plugin->audio_output_memory[rack_channel]); + + rack_channel++; + } + } +} + + +void +lv2_process_add_plugin (lv2_process_info_t * procinfo, lv2_plugin_t * plugin) +{ + + /* sort out list pointers */ + plugin->next = NULL; + plugin->prev = procinfo->chain_end; + + if (procinfo->chain_end) + procinfo->chain_end->next = plugin; + else + procinfo->chain = plugin; + + procinfo->chain_end = plugin; + +} + +/** remove a plugin from the chain */ +lv2_plugin_t * +lv2_process_remove_plugin (lv2_process_info_t * procinfo, lv2_plugin_t *plugin) +{ + /* sort out chain pointers */ + if (plugin->prev) + plugin->prev->next = plugin->next; + else + procinfo->chain = plugin->next; + + if (plugin->next) + plugin->next->prev = plugin->prev; + else + procinfo->chain_end = plugin->prev; + +#ifdef WITH_JACK + /* sort out the aux ports */ + if (procinfo->jack_client && plugin->desc->aux_channels > 0) + { + lv2_plugin_t * other; + + for (other = plugin->next; other; other = other->next) + if (other->desc->id == plugin->desc->id) + plugin_swap_aux_ports (plugin, other); + } +#endif + + return plugin; +} + +/** enable/disable a plugin */ +void +lv2_process_ablise_plugin (lv2_process_info_t * procinfo, lv2_plugin_t *plugin, gboolean enable) +{ + plugin->enabled = enable; +} + +/** enable/disable a plugin */ +void +lv2_process_ablise_plugin_wet_dry (lv2_process_info_t * procinfo, lv2_plugin_t *plugin, gboolean enable) +{ + plugin->wet_dry_enabled = enable; +} + +/** move a plugin up or down one place in the chain */ +void +lv2_process_move_plugin (lv2_process_info_t * procinfo, lv2_plugin_t *plugin, gint up) +{ + /* other plugins in the chain */ + lv2_plugin_t *pp = NULL, *p, *n, *nn = NULL; + + /* note that we should never receive an illogical move request + ie, there will always be at least 1 plugin before for an up + request or 1 plugin after for a down request */ + + /* these are pointers to the plugins surrounding the specified one: + { pp, p, plugin, n, nn } which makes things much clearer than + tptr, tptr2 etc */ + p = plugin->prev; + if (p) pp = p->prev; + n = plugin->next; + if (n) nn = n->next; + + if (up) + { + if (!p) + return; + + if (pp) + pp->next = plugin; + else + procinfo->chain = plugin; + + p->next = n; + p->prev = plugin; + + plugin->prev = pp; + plugin->next = p; + + if (n) + n->prev = p; + else + procinfo->chain_end = p; + + } + else + { + if (!n) + return; + + if (p) + p->next = n; + else + procinfo->chain = n; + + n->prev = p; + n->next = plugin; + + plugin->prev = n; + plugin->next = nn; + + if (nn) + nn->prev = plugin; + else + procinfo->chain_end = plugin; + } + +#ifdef WITH_JACK + if (procinfo->jack_client && plugin->desc->aux_channels > 0) + { + lv2_plugin_t * other; + other = up ? plugin->next : plugin->prev; + + /* swap around the jack ports */ + if (other->desc->id == plugin->desc->id) + plugin_swap_aux_ports (plugin, other); + } +#endif +} + +/** exchange an existing plugin for a newly created one */ +lv2_plugin_t * +lv2_process_change_plugin (lv2_process_info_t * procinfo, + lv2_plugin_t *plugin, lv2_plugin_t * new_plugin) +{ + new_plugin->next = plugin->next; + new_plugin->prev = plugin->prev; + + if (plugin->prev) + plugin->prev->next = new_plugin; + else + procinfo->chain = new_plugin; + + if (plugin->next) + plugin->next->prev = new_plugin; + else + procinfo->chain_end = new_plugin; + +#ifdef WITH_JACK + /* sort out the aux ports */ + if (procinfo->jack_client && plugin->desc->aux_channels > 0) + { + lv2_plugin_t * other; + + for (other = plugin->next; other; other = other->next) + if (other->desc->id == plugin->desc->id) + plugin_swap_aux_ports (plugin, other); + } +#endif + + return plugin; +} + + +/****************************************** + ************* non RT stuff *************** + ******************************************/ + +static int +lv2_plugin_instantiate (const LilvPlugin *plugin, + unsigned long plugin_index, + gint copies, + LilvInstance **instances) +{ + gint i; + + for (i = 0; i < copies; i++) + { + instances[i] = lilv_plugin_instantiate(plugin, lv2_sample_rate, features); + + if (!instances[i]) + { + unsigned long d; + for (d = 0; d < i; d++) + lilv_instance_free(instances[d]); + return 1; + } + } + + return 0; +} + +#ifdef WITH_JACK + +static void +lv2_plugin_create_aux_ports (lv2_plugin_t * plugin, guint copy, lv2_rack_t * lv2_rack) +{ + lv2_plugin_desc_t * desc; + unsigned long aux_channel = 1; + unsigned long plugin_index = 1; + unsigned long i; + char port_name[64]; + char * plugin_name; + char * ptr; + lv2_holder_t * holder; + + desc = plugin->desc; + holder = plugin->holders + copy; + + holder->aux_ports = g_malloc (sizeof (jack_port_t *) * desc->aux_channels); + + /* make the plugin name jack worthy */ + ptr = plugin_name = g_strndup (plugin->desc->name, 7); + while (*ptr != '\0') + { + if (*ptr == ' ') + *ptr = '_'; + else + *ptr = tolower (*ptr); + + ptr++; + } + + /* + for (list = lv2_rack->slots; list; list = g_list_next (list)) + { + slot = (plugin_slot_t *) list->data; + + if (slot->plugin->desc->id == plugin->desc->id) + plugin_index++; + } + */ + + for (i = 0; i < desc->aux_channels; i++, aux_channel++) + { + sprintf (port_name, "%s_%ld-%d_%c%ld", + plugin_name, + plugin_index, + copy + 1, + desc->aux_are_input ? 'i' : 'o', + aux_channel); + + holder->aux_ports[i] = + jack_port_register (lv2_rack->procinfo->jack_client, + port_name, + JACK_DEFAULT_AUDIO_TYPE, + desc->aux_are_input ? JackPortIsInput : JackPortIsOutput, + 0); + + if (!holder->aux_ports[i]) + { + mlt_log_panic( NULL, "Could not register jack port '%s'; aborting\n", port_name); + } + } + + g_free (plugin_name); +} + +#endif + +static void +lv2_plugin_init_holder (const lv2_plugin_t *plugin, + guint copy, + const LilvInstance *instance, + lv2_rack_t *lv2_rack) +{ + unsigned long i; + lv2_plugin_desc_t * desc; + lv2_holder_t * holder; + + desc = plugin->desc; + holder = plugin->holders + copy; + holder->instance = instance; + + if (desc->control_port_count > 0) + { + holder->ui_control_fifos = g_malloc (sizeof (lff_t) * desc->control_port_count); + holder->control_memory = g_malloc (sizeof (LADSPA_Data) * desc->control_port_count); + } + else + { + holder->ui_control_fifos = NULL; + holder->control_memory = NULL; + } + + for (i = 0; i < desc->control_port_count; i++) + { + lff_init (holder->ui_control_fifos + i, CONTROL_FIFO_SIZE, sizeof (LADSPA_Data)); + + if (!isnan (plugin->desc->def_values[desc->control_port_indicies[i]])) + { + holder->control_memory[i] = plugin->desc->def_values[desc->control_port_indicies[i]]; + } + else if (!isnan (plugin->desc->min_values[desc->control_port_indicies[i]])) + { + holder->control_memory[i] = plugin->desc->min_values[desc->control_port_indicies[i]]; + } + else if (!isnan (plugin->desc->max_values[desc->control_port_indicies[i]])) + { + holder->control_memory[i] = plugin->desc->max_values[desc->control_port_indicies[i]]; + } + else + { + holder->control_memory[i] = 0.0; + } + + lilv_instance_connect_port(instance, desc->control_port_indicies[i], &holder->control_memory[i]); + } + + if (desc->status_port_count > 0) + { + holder->status_memory = g_malloc (sizeof (LADSPA_Data) * desc->status_port_count); + } + else + { + holder->status_memory = NULL; + } + + for (i = 0; i < desc->status_port_count; i++) + { + lilv_instance_connect_port(instance, desc->status_port_indicies[i], holder->status_memory + i); + } + +#ifdef WITH_JACK + if (lv2_rack->procinfo->jack_client && plugin->desc->aux_channels > 0) + lv2_plugin_create_aux_ports (plugin, copy, lv2_rack); +#endif + + lilv_instance_activate(instance); +} + +lv2_plugin_t * +lv2_plugin_new (lv2_plugin_desc_t * desc, lv2_rack_t * lv2_rack) +{ + LilvInstance **instances; + gint copies; + unsigned long i; + int err; + lv2_plugin_t * plugin; + + /* open the plugin */ + plugin = g_malloc (sizeof (lv2_plugin_t)); + + char *str_ptr = strchr(desc->uri, '<'); + while (str_ptr != NULL) + { + *str_ptr++ = ':'; + str_ptr = strchr(str_ptr, '<'); + } + + plugin->lv2_plugin_uri = lilv_new_uri(lv2_rack->plugin_mgr->lv2_world, desc->uri); + plugin->lv2_plugin = lilv_plugins_get_by_uri(lv2_rack->plugin_mgr->plugin_list, plugin->lv2_plugin_uri); + + str_ptr = strchr(desc->uri, ':'); + while (str_ptr != NULL) + { + *str_ptr++ = '<'; + str_ptr = strchr(str_ptr, ':'); + } + + /* create the instances */ + copies = lv2_plugin_desc_get_copies (desc, lv2_rack->channels); + instances = g_malloc (sizeof (LADSPA_Handle) * copies); + + err = lv2_plugin_instantiate (plugin->lv2_plugin, desc->index, copies, instances); + + if (err) + { + g_free (instances); + return NULL; + } + + plugin->desc = desc; + plugin->copies = copies; + plugin->enabled = FALSE; + plugin->next = NULL; + plugin->prev = NULL; + plugin->wet_dry_enabled = FALSE; + plugin->lv2_rack = lv2_rack; + + /* create audio memory and wet/dry stuff */ + plugin->audio_output_memory = g_malloc (sizeof (LADSPA_Data *) * lv2_rack->channels); + plugin->wet_dry_fifos = g_malloc (sizeof (lff_t) * lv2_rack->channels); + plugin->wet_dry_values = g_malloc (sizeof (LADSPA_Data) * lv2_rack->channels); + + for (i = 0; i < lv2_rack->channels; i++) + { + plugin->audio_output_memory[i] = g_malloc (sizeof (LADSPA_Data) * lv2_buffer_size); + lff_init (plugin->wet_dry_fifos + i, CONTROL_FIFO_SIZE, sizeof (LADSPA_Data)); + plugin->wet_dry_values[i] = 1.0; + } + + /* create holders and fill them out */ + plugin->holders = g_malloc (sizeof (lv2_holder_t) * copies); + for (i = 0; i < copies; i++) + lv2_plugin_init_holder (plugin, i, instances[i], lv2_rack); + + return plugin; +} + + +void +lv2_plugin_destroy (lv2_plugin_t * plugin) +{ + unsigned long i, j; + int err = 0; + + /* destroy holders */ + for (i = 0; i < plugin->copies; i++) + { + lilv_instance_deactivate(plugin->holders[i].instance); + + if (plugin->desc->control_port_count > 0) + { + for (j = 0; j < plugin->desc->control_port_count; j++) + { + lff_free (plugin->holders[i].ui_control_fifos + j); + } + g_free (plugin->holders[i].ui_control_fifos); + g_free (plugin->holders[i].control_memory); + } + + if (plugin->desc->status_port_count > 0) + { + g_free (plugin->holders[i].status_memory); + } + +#ifdef WITH_JACK + /* aux ports */ + if (plugin->lv2_rack->procinfo->jack_client && plugin->desc->aux_channels > 0) + { + for (j = 0; j < plugin->desc->aux_channels; j++) + { + err = jack_port_unregister (plugin->lv2_rack->procinfo->jack_client, + plugin->holders[i].aux_ports[j]); + + if (err) + mlt_log_warning( NULL, "%s: could not unregister jack port\n", __FUNCTION__); + } + + g_free (plugin->holders[i].aux_ports); + } +#endif + } + + g_free (plugin->holders); + + for (i = 0; i < plugin->lv2_rack->channels; i++) + { + g_free (plugin->audio_output_memory[i]); + lff_free (plugin->wet_dry_fifos + i); + } + + g_free (plugin->audio_output_memory); + g_free (plugin->wet_dry_fifos); + g_free (plugin->wet_dry_values); + + if (err) + { + mlt_log_warning( NULL, "%s: error closing shared object '%s': %s\n", + __FUNCTION__, plugin->desc->uri, dlerror ()); + } + + g_free (plugin); +} + + +/* EOF */ diff --git a/src/modules/jackrack/lv2_plugin.h b/src/modules/jackrack/lv2_plugin.h new file mode 100644 index 000000000..8444e9946 --- /dev/null +++ b/src/modules/jackrack/lv2_plugin.h @@ -0,0 +1,94 @@ +/* + * JACK Rack + * + * Original: + * Copyright (C) Robert Ham 2002, 2003 (node@users.sourceforge.net) + * + * Modification for MLT: + * Copyright (C) 2004-2021 Meltytech, LLC + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#ifndef __LV2_PLUGIN_H__ +#define __LV2_PLUGIN_H__ + +#include +#include +#include +#ifdef WITH_JACK +#include +#endif + +#include "lv2_process.h" +#include "plugin_desc.h" + +typedef struct _lv2_holder lv2_holder_t; +typedef struct _lv2_plugin lv2_plugin_t; + +struct _lv2_holder +{ + LilvInstance *instance; + lff_t * ui_control_fifos; + LADSPA_Data * control_memory; + LADSPA_Data * status_memory; + +#ifdef WITH_JACK + jack_port_t ** aux_ports; +#endif +}; + +struct _lv2_plugin +{ + lv2_plugin_desc_t * desc; + gint enabled; + + gint copies; + lv2_holder_t * holders; + LADSPA_Data ** audio_input_memory; + LADSPA_Data ** audio_output_memory; + + gboolean wet_dry_enabled; + /* 1.0 = all wet, 0.0 = all dry, 0.5 = 50% wet/50% dry */ + LADSPA_Data * wet_dry_values; + lff_t * wet_dry_fifos; + + lv2_plugin_t * next; + lv2_plugin_t * prev; + + LilvNode * lv2_plugin_uri; + LilvPlugin * lv2_plugin; + struct _lv2_rack * lv2_rack; +}; + + +void lv2_process_add_plugin (lv2_process_info_t *, lv2_plugin_t *plugin); +lv2_plugin_t * lv2_process_remove_plugin (lv2_process_info_t *, lv2_plugin_t *plugin); +void lv2_process_ablise_plugin (lv2_process_info_t *, lv2_plugin_t *plugin, gboolean able); +void lv2_process_ablise_plugin_wet_dry (lv2_process_info_t *, lv2_plugin_t *plugin, gboolean enable); +void lv2_process_move_plugin (lv2_process_info_t *, lv2_plugin_t *plugin, gint up); +lv2_plugin_t * lv2_process_change_plugin (lv2_process_info_t *, lv2_plugin_t *plugin, lv2_plugin_t * new_plugin); + +struct _lv2_rack; +struct _ui; + +lv2_plugin_t * lv2_plugin_new (lv2_plugin_desc_t * plugin_desc, struct _lv2_rack * lv2_rack); +void lv2_plugin_destroy (lv2_plugin_t * plugin); + +void lv2_plugin_connect_input_ports (lv2_plugin_t * plugin, LADSPA_Data ** inputs); +void lv2_plugin_connect_output_ports (lv2_plugin_t * plugin); + + +#endif /* __LV2_PLUGIN_H__ */ diff --git a/src/modules/jackrack/lv2_plugin_settings.c b/src/modules/jackrack/lv2_plugin_settings.c new file mode 100644 index 000000000..0d7dcd979 --- /dev/null +++ b/src/modules/jackrack/lv2_plugin_settings.c @@ -0,0 +1,395 @@ +/* + * JACK Rack + * + * Original: + * Copyright (C) Robert Ham 2002, 2003 (node@users.sourceforge.net) + * + * Modification for MLT: + * Copyright (C) 2004-2014 Meltytech, LLC + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#ifndef _GNU_SOURCE +#define _GNU_SOURCE +#endif +#include + +#include "lv2_plugin_settings.h" + + +static void +lv2_settings_set_to_default (lv2_settings_t * settings, guint32 sample_rate) +{ + unsigned long control; + guint copy; + LADSPA_Data value; + + for (control = 0; control < settings->desc->control_port_count; control++) + { + value = settings->desc->def_values[settings->desc->control_port_indicies[control]]; + + for (copy = 0; copy < settings->copies; copy++) + { + settings->control_values[copy][control] = value; + } + + settings->locks[control] = TRUE; + } +} + +lv2_settings_t * +lv2_settings_new (lv2_plugin_desc_t * desc, unsigned long channels, guint32 sample_rate) +{ + lv2_settings_t * settings; + unsigned long channel; + guint copies; + + settings = g_malloc (sizeof (lv2_settings_t)); + copies = lv2_plugin_desc_get_copies (desc, channels); + + settings->sample_rate = sample_rate; + settings->desc = desc; + settings->copies = copies; + settings->channels = channels; + settings->lock_all = TRUE; + settings->enabled = FALSE; + settings->locks = NULL; + settings->control_values = NULL; + settings->wet_dry_enabled = FALSE; + settings->wet_dry_locked = TRUE; + + /* control settings */ + if (desc->control_port_count > 0) + { + guint copy; + + settings->locks = g_malloc (sizeof (gboolean) * desc->control_port_count); + + settings->control_values = g_malloc (sizeof (LADSPA_Data *) * copies); + for (copy = 0; copy < copies; copy++) + { + settings->control_values[copy] = g_malloc (sizeof (LADSPA_Data) * desc->control_port_count); + } + + lv2_settings_set_to_default (settings, sample_rate); + } + + /* wet/dry settings */ + settings->wet_dry_values = g_malloc (sizeof (LADSPA_Data) * channels); + for (channel = 0; channel < channels; channel++) + settings->wet_dry_values[channel] = 1.0; + + return settings; +} + +lv2_settings_t * +lv2_settings_dup (lv2_settings_t * other) +{ + lv2_settings_t * settings; + lv2_plugin_desc_t * desc; + unsigned long channel; + + settings = g_malloc (sizeof (lv2_settings_t)); + + settings->sample_rate = other->sample_rate; + settings->desc = other->desc; + settings->copies = lv2_settings_get_copies (other); + settings->channels = lv2_settings_get_channels (other); + settings->wet_dry_enabled = lv2_settings_get_wet_dry_enabled (other); + settings->wet_dry_locked = lv2_settings_get_wet_dry_locked (other); + settings->lock_all = lv2_settings_get_lock_all (other); + settings->enabled = lv2_settings_get_enabled (other); + settings->locks = NULL; + settings->control_values = NULL; + + desc = other->desc; + + if (desc->control_port_count > 0) + { + guint copy; + unsigned long control; + + settings->locks = g_malloc (sizeof (gboolean) * desc->control_port_count); + for (control = 0; control < desc->control_port_count; control++) + lv2_settings_set_lock (settings, control, lv2_settings_get_lock (other, control)); + + settings->control_values = g_malloc (sizeof (LADSPA_Data *) * settings->copies); + for (copy = 0; copy < settings->copies; copy++) + { + settings->control_values[copy] = g_malloc (sizeof (LADSPA_Data) * desc->control_port_count); + + for (control = 0; control < desc->control_port_count; control++) + { + settings->control_values[copy][control] = lv2_settings_get_control_value (other, copy, control); + } + } + } + + settings->wet_dry_values = g_malloc (sizeof (LADSPA_Data) * settings->channels); + for (channel = 0; channel < settings->channels; channel++) + settings->wet_dry_values[channel] = lv2_settings_get_wet_dry_value (other, channel); + + return settings; +} + +void +lv2_settings_destroy (lv2_settings_t * settings) +{ + if (settings->desc->control_port_count > 0) + { + guint i; + for (i = 0; i < settings->copies; i++) + g_free (settings->control_values[i]); + + g_free (settings->control_values); + g_free (settings->locks); + } + + g_free (settings->wet_dry_values); + + g_free (settings); +} + +static void +lv2_settings_set_copies (lv2_settings_t * settings, guint copies) +{ + guint copy; + guint last_copy; + unsigned long control; + + if (copies <= settings->copies) + return; + + last_copy = settings->copies - 1; + + settings->control_values = g_realloc (settings->control_values, + sizeof (LADSPA_Data *) * copies); + + /* copy over the last settings to the new copies */ + for (copy = settings->copies; copy < copies; copy++) + { + for (control = 0; control < settings->desc->control_port_count; control++) + { + settings->control_values[copy][control] = + settings->control_values[last_copy][control]; + } + } + + settings->copies = copies; +} + +static void +lv2_settings_set_channels (lv2_settings_t * settings, unsigned long channels) +{ + unsigned long channel; + LADSPA_Data last_value; + + if (channels <= settings->channels) + return; + + settings->wet_dry_values = g_realloc (settings->wet_dry_values, sizeof (LADSPA_Data) * channels); + + last_value = settings->wet_dry_values[settings->channels - 1]; + + for (channel = settings->channels; channel < channels; channel++) + settings->wet_dry_values[channel] = last_value; + + settings->channels = channels; +} + +void +lv2_settings_set_sample_rate (lv2_settings_t * settings, guint32 sample_rate) +{ + LADSPA_Data old_sample_rate; + LADSPA_Data new_sample_rate; + + g_return_if_fail (settings != NULL); + + if (settings->sample_rate == sample_rate) + return; + + if (settings->desc->control_port_count > 0) + { + unsigned long control; + guint copy; + + new_sample_rate = (LADSPA_Data) sample_rate; + old_sample_rate = (LADSPA_Data) settings->sample_rate; + + for (control = 0; control < settings->desc->control_port_count; control++) + { + for (copy = 0; copy < settings->copies; copy++) + { + if (LADSPA_IS_HINT_SAMPLE_RATE (settings->desc->port_range_hints[control].HintDescriptor)) + { + settings->control_values[copy][control] = + (settings->control_values[copy][control] / old_sample_rate) * new_sample_rate; + } + } + } + } + + settings->sample_rate = sample_rate; +} + +void +lv2_settings_set_control_value (lv2_settings_t * settings, guint copy, unsigned long control_index, LADSPA_Data value) +{ + g_return_if_fail (settings != NULL); + g_return_if_fail (control_index < settings->desc->control_port_count); + + if (copy >= settings->copies) + lv2_settings_set_copies (settings, copy + 1); + + settings->control_values[copy][control_index] = value; +} + +void +lv2_settings_set_lock (lv2_settings_t * settings, unsigned long control_index, gboolean locked) +{ + g_return_if_fail (settings != NULL); + g_return_if_fail (control_index < settings->desc->control_port_count); + + settings->locks[control_index] = locked; +} + +void +lv2_settings_set_lock_all (lv2_settings_t * settings, gboolean lock_all) +{ + g_return_if_fail (settings != NULL); + + settings->lock_all = lock_all; +} + +void +lv2_settings_set_enabled (lv2_settings_t * settings, gboolean enabled) +{ + g_return_if_fail (settings != NULL); + + settings->enabled = enabled; +} + +void +lv2_settings_set_wet_dry_enabled (lv2_settings_t * settings, gboolean enabled) +{ + g_return_if_fail (settings != NULL); + + settings->wet_dry_enabled = enabled; +} + +void +lv2_settings_set_wet_dry_locked (lv2_settings_t * settings, gboolean locked) +{ + g_return_if_fail (settings != NULL); + + settings->wet_dry_locked = locked; +} + +void +lv2_settings_set_wet_dry_value (lv2_settings_t * settings, unsigned long channel, LADSPA_Data value) +{ + g_return_if_fail (settings != NULL); + + if (channel >= settings->channels) + lv2_settings_set_channels (settings, channel + 1); + + settings->wet_dry_values[channel] = value; +} + + +LADSPA_Data +lv2_settings_get_control_value (lv2_settings_t * settings, guint copy, unsigned long control_index) +{ + g_return_val_if_fail (settings != NULL, NAN); + g_return_val_if_fail (control_index < settings->desc->control_port_count, NAN); + + if (copy >= settings->copies) + lv2_settings_set_copies (settings, copy - 1); + + return settings->control_values[copy][control_index]; +} + +gboolean +lv2_settings_get_lock (const lv2_settings_t * settings, unsigned long control_index) +{ + g_return_val_if_fail (settings != NULL, FALSE); + + return settings->locks[control_index]; +} + +gboolean +lv2_settings_get_lock_all (const lv2_settings_t * settings) +{ + g_return_val_if_fail (settings != NULL, FALSE); + + return settings->lock_all; +} + +gboolean +lv2_settings_get_enabled (const lv2_settings_t * settings) +{ + g_return_val_if_fail (settings != NULL, FALSE); + + return settings->enabled; +} + +guint +lv2_settings_get_copies (const lv2_settings_t * settings) +{ + g_return_val_if_fail (settings != NULL, 0); + + return settings->copies; +} + + +unsigned long +lv2_settings_get_channels (const lv2_settings_t * settings) +{ + g_return_val_if_fail (settings != NULL, 0); + + return settings->channels; +} + +gboolean +lv2_settings_get_wet_dry_enabled (const lv2_settings_t * settings) +{ + g_return_val_if_fail (settings != NULL, FALSE); + + return settings->wet_dry_enabled; +} + +gboolean +lv2_settings_get_wet_dry_locked (const lv2_settings_t * settings) +{ + g_return_val_if_fail (settings != NULL, FALSE); + + return settings->wet_dry_locked; +} + +LADSPA_Data +lv2_settings_get_wet_dry_value (lv2_settings_t * settings, unsigned long channel) +{ + g_return_val_if_fail (settings != NULL, NAN); + + if (channel >= settings->channels) + lv2_settings_set_channels (settings, channel + 1); + + return settings->wet_dry_values[channel]; +} + + + +/* EOF */ diff --git a/src/modules/jackrack/lv2_plugin_settings.h b/src/modules/jackrack/lv2_plugin_settings.h new file mode 100644 index 000000000..71b8205e6 --- /dev/null +++ b/src/modules/jackrack/lv2_plugin_settings.h @@ -0,0 +1,75 @@ +/* + * JACK Rack + * + * Original: + * Copyright (C) Robert Ham 2002, 2003 (node@users.sourceforge.net) + * + * Modification for MLT: + * Copyright (C) 2004-2014 Meltytech, LLC + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#ifndef __LV2_PLUGIN_SETTINGS_H__ +#define __LV2_PLUGIN_SETTINGS_H__ + +#include +#include + +#include "plugin_mgr.h" +#include "plugin_desc.h" + +typedef struct _lv2_settings lv2_settings_t; + +struct _lv2_settings +{ + guint32 sample_rate; + lv2_plugin_desc_t * desc; + guint copies; + LADSPA_Data ** control_values; + gboolean * locks; + gboolean lock_all; + gboolean enabled; + unsigned long channels; + gboolean wet_dry_enabled; + gboolean wet_dry_locked; + LADSPA_Data * wet_dry_values; +}; + +lv2_settings_t * lv2_settings_new (lv2_plugin_desc_t * desc, unsigned long channels, guint32 sample_rate); +lv2_settings_t * lv2_settings_dup (lv2_settings_t * settings); +void lv2_settings_destroy (lv2_settings_t * settings); + +void lv2_settings_set_control_value (lv2_settings_t * settings, guint copy, unsigned long control_index, LADSPA_Data value); +void lv2_settings_set_lock (lv2_settings_t * settings, unsigned long control_index, gboolean locked); +void lv2_settings_set_lock_all (lv2_settings_t * settings, gboolean lock_all); +void lv2_settings_set_enabled (lv2_settings_t * settings, gboolean enabled); +void lv2_settings_set_wet_dry_enabled (lv2_settings_t * settings, gboolean enabled); +void lv2_settings_set_wet_dry_locked (lv2_settings_t * settings, gboolean locked); +void lv2_settings_set_wet_dry_value (lv2_settings_t * settings, unsigned long channel, LADSPA_Data value); + +LADSPA_Data lv2_settings_get_control_value (lv2_settings_t * settings, guint copy, unsigned long control_index); +gboolean lv2_settings_get_lock (const lv2_settings_t * settings, unsigned long control_index); +gboolean lv2_settings_get_lock_all (const lv2_settings_t * settings); +gboolean lv2_settings_get_enabled (const lv2_settings_t * settings); +guint lv2_settings_get_copies (const lv2_settings_t * settings); +unsigned long lv2_settings_get_channels (const lv2_settings_t * settings); +gboolean lv2_settings_get_wet_dry_enabled (const lv2_settings_t * settings); +gboolean lv2_settings_get_wet_dry_locked (const lv2_settings_t * settings); +LADSPA_Data lv2_settings_get_wet_dry_value (lv2_settings_t * settings, unsigned long channel); + +void lv2_settings_set_sample_rate (lv2_settings_t * settings, guint32 sample_rate); + +#endif /* __LV2_PLUGIN_SETTINGS_H__ */ diff --git a/src/modules/jackrack/lv2_process.c b/src/modules/jackrack/lv2_process.c new file mode 100644 index 000000000..e009f44ad --- /dev/null +++ b/src/modules/jackrack/lv2_process.c @@ -0,0 +1,628 @@ +/* + * JACK Rack + * + * Original: + * Copyright (C) Robert Ham 2002, 2003 (node@users.sourceforge.net) + * + * Modification for MLT: + * Copyright (C) 2004-2021 Meltytech, LLC + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include +/* #ifdef WITH_JACK + #include + #endif */ +#include +#include +#include +#include +#include +#include +#include + +#include "lv2_process.h" +#include "lock_free_fifo.h" +#include "lv2_plugin.h" +#include "lv2_rack.h" +#include "framework/mlt_log.h" + +#ifndef _ +#define _(x) x +#endif + +extern pthread_mutex_t g_activate_mutex; + +#define USEC_PER_SEC 1000000 +#define MSEC_PER_SEC 1000 +#define TIME_RUN_SKIP_COUNT 5 +#define MAX_BUFFER_SIZE 4096 + +jack_nframes_t lv2_sample_rate; +jack_nframes_t lv2_buffer_size; + +/* #ifdef WITH_JACK + + static void + jack_shutdown_cb (void * data) + { + lv2_process_info_t * procinfo = data; + + procinfo->quit = TRUE; + } + + #endif */ + +/** process messages for plugins' control ports */ +void lv2_process_control_port_messages (lv2_process_info_t * procinfo) { + lv2_plugin_t * plugin; + unsigned long control; + unsigned long channel; + gint copy; + + if (!procinfo->chain) return; + + for (plugin = procinfo->chain; plugin; plugin = plugin->next) + { + if (plugin->desc->control_port_count > 0) + for (control = 0; control < plugin->desc->control_port_count; control++) + for (copy = 0; copy < plugin->copies; copy++) + { + while (lff_read (plugin->holders[copy].ui_control_fifos + control, + plugin->holders[copy].control_memory + control) == 0); + } + + if (plugin->wet_dry_enabled) + for (channel = 0; channel < procinfo->channels; channel++) + { + while (lff_read (plugin->wet_dry_fifos + channel, + plugin->wet_dry_values + channel) == 0); + } + } +} + +/* #ifdef WITH_JACK + + static int get_jack_buffers (lv2_process_info_t * procinfo, jack_nframes_t frames) { + unsigned long channel; + + for (channel = 0; channel < procinfo->channels; channel++) + { + procinfo->jack_input_buffers[channel] = jack_port_get_buffer (procinfo->jack_input_ports[channel], frames); + if (!procinfo->jack_input_buffers[channel]) + { + mlt_log_verbose( NULL, "%s: no jack buffer for input port %ld\n", __FUNCTION__, channel); + return 1; + } + + procinfo->jack_output_buffers[channel] = jack_port_get_buffer (procinfo->jack_output_ports[channel], frames); + if (!procinfo->jack_output_buffers[channel]) + { + mlt_log_verbose( NULL, "%s: no jack buffer for output port %ld\n", __FUNCTION__, channel); + return 1; + } + } + + return 0; + } + + #endif */ + +lv2_plugin_t * +lv2_get_first_enabled_plugin (lv2_process_info_t * procinfo) +{ + lv2_plugin_t * first_enabled; + + if (!procinfo->chain) return NULL; + + for (first_enabled = procinfo->chain; + first_enabled; + first_enabled = first_enabled->next) + { + if (first_enabled->enabled) return first_enabled; + } + + return NULL; +} + +lv2_plugin_t * +lv2_get_last_enabled_plugin (lv2_process_info_t * procinfo) +{ + lv2_plugin_t * last_enabled; + + if (!procinfo->chain) return NULL; + + for (last_enabled = procinfo->chain_end; + last_enabled; + last_enabled = last_enabled->prev) + { + if (last_enabled->enabled) return last_enabled; + } + + return NULL; +} + +void +lv2_connect_chain (lv2_process_info_t * procinfo, jack_nframes_t frames) +{ + lv2_plugin_t * first_enabled, * last_enabled, * plugin; + gint copy; + unsigned long channel; + if (!procinfo->chain) return; + + first_enabled = lv2_get_first_enabled_plugin (procinfo); + if (!first_enabled) return; + + last_enabled = lv2_get_last_enabled_plugin (procinfo); + + /* sort out the aux ports */ + plugin = first_enabled; + do + { + if (plugin->desc->aux_channels > 0 && plugin->enabled) + { +#ifdef WITH_JACK + if (procinfo->jack_client) + { + for (copy = 0; copy < plugin->copies; copy++) + for (channel = 0; channel < plugin->desc->aux_channels; channel++) + lilv_instance_connect_port(plugin->holders[copy].instance, plugin->desc->audio_aux_port_indicies[channel], jack_port_get_buffer (plugin->holders[copy].aux_ports[channel], frames)); + } + else +#endif + { + for (copy = 0; copy < frames; copy++) + procinfo->silent_buffer[copy] = 0.0; + + for (copy = 0; copy < plugin->copies; copy++) + for (channel = 0; channel < plugin->desc->aux_channels; channel++) + lilv_instance_connect_port(plugin->holders[copy].instance, plugin->desc->audio_aux_port_indicies[channel], procinfo->silent_buffer); + } + } + } + while ( (plugin != last_enabled) && (plugin = plugin->next) ); + + /* ensure that all the of the enabled plugins are connected to their memory */ + lv2_plugin_connect_output_ports (first_enabled); + if (first_enabled != last_enabled) + { + lv2_plugin_connect_input_ports (last_enabled, last_enabled->prev->audio_output_memory); + for (plugin = first_enabled->next; plugin; plugin = plugin->next) + { + if (plugin->enabled) + { + lv2_plugin_connect_input_ports (plugin, plugin->prev->audio_output_memory); + lv2_plugin_connect_output_ports (plugin); + } + } + } + + /* input buffers for first plugin */ + if( plugin->desc->has_input ) + lv2_plugin_connect_input_ports (first_enabled, procinfo->jack_input_buffers); +} + +void +lv2_process_chain (lv2_process_info_t * procinfo, jack_nframes_t frames) +{ + lv2_plugin_t * first_enabled; + lv2_plugin_t * last_enabled = NULL; + lv2_plugin_t * plugin; + unsigned long channel; + unsigned long i; + +#ifdef WITH_JACK + if (procinfo->jack_client) + { + LADSPA_Data zero_signal[frames]; + guint copy; + + /* set the zero signal to zero */ + for (channel = 0; channel < frames; channel++) + zero_signal[channel] = 0.0; + + /* possibly set aux output channels to zero if they're not enabled */ + for (plugin = procinfo->chain; plugin; plugin = plugin->next) + if (!plugin->enabled && + plugin->desc->aux_channels > 0 && + !plugin->desc->aux_are_input) + for (copy = 0; copy < plugin->copies; copy++) + for (channel = 0; channel < plugin->desc->aux_channels; channel++) + memcpy (jack_port_get_buffer (plugin->holders[copy].aux_ports[channel], frames), + zero_signal, sizeof (LADSPA_Data) * frames); + } +#endif + + first_enabled = lv2_get_first_enabled_plugin (procinfo); + + /* no chain; just copy input to output */ + if (!procinfo->chain || !first_enabled) + { + unsigned long channel; + for (channel = 0; channel < procinfo->channels; channel++) + { + memcpy (procinfo->jack_output_buffers[channel], + procinfo->jack_input_buffers[channel], + sizeof(LADSPA_Data) * frames); + } + return; + } + + /* all past here is guaranteed to have at least 1 enabled plugin */ + + last_enabled = lv2_get_last_enabled_plugin (procinfo); + + for (plugin = first_enabled; + plugin; + plugin = plugin->next) + { + if (plugin->enabled) + { + for (i = 0; i < plugin->copies; i++) + lilv_instance_run(plugin->holders[i].instance, frames); + + if (plugin->wet_dry_enabled) + for (channel = 0; channel < procinfo->channels; channel++) + for (i = 0; i < frames; i++) + { + plugin->audio_output_memory[channel][i] *= plugin->wet_dry_values[channel]; + plugin->audio_output_memory[channel][i] += plugin->audio_input_memory[channel][i] * (1.0 - plugin->wet_dry_values[channel]); + } + + if (plugin == last_enabled) + break; + } + else + { + + /* copy the data through */ + for (i = 0; i < procinfo->channels; i++) + memcpy (plugin->audio_output_memory[i], + plugin->prev->audio_output_memory[i], + sizeof(LADSPA_Data) * frames); + } + } + + /* copy the last enabled data to the jack ports */ + for (i = 0; i < procinfo->channels; i++) + memcpy (procinfo->jack_output_buffers[i], + last_enabled->audio_output_memory[i], + sizeof(LADSPA_Data) * frames); + +} + +int process_lv2 (lv2_process_info_t * procinfo, jack_nframes_t frames, + LADSPA_Data ** inputs, LADSPA_Data ** outputs) { + unsigned long channel; + + if (!procinfo) + { + mlt_log_error( NULL, "%s: no process_info from jack!\n", __FUNCTION__); + return 1; + } + + if (procinfo->quit == TRUE) + return 1; + + lv2_process_control_port_messages (procinfo); + + for (channel = 0; channel < procinfo->channels; channel++) + { + if(lv2_get_first_enabled_plugin (procinfo)->desc->has_input) + { + procinfo->jack_input_buffers[channel] = inputs[channel]; + if (!procinfo->jack_input_buffers[channel]) + { + mlt_log_verbose( NULL, "%s: no jack buffer for input port %ld\n", __FUNCTION__, channel); + return 1; + } + } + procinfo->jack_output_buffers[channel] = outputs[channel]; + if (!procinfo->jack_output_buffers[channel]) + { + mlt_log_verbose( NULL, "%s: no jack buffer for output port %ld\n", __FUNCTION__, channel); + return 1; + } + } + + lv2_connect_chain (procinfo, frames); + + lv2_process_chain (procinfo, frames); + + return 0; +} + +#ifdef WITH_JACK + +/* int process_jack (jack_nframes_t frames, void * data) { + int err; + lv2_process_info_t * procinfo; + + procinfo = (lv2_process_info_t *) data; + + if (!procinfo) + { + mlt_log_error( NULL, "%s: no process_info from jack!\n", __FUNCTION__); + return 1; + } + + if (procinfo->port_count == 0) + return 0; + + if (procinfo->quit == TRUE) + return 1; + + lv2_process_control_port_messages (procinfo); + + err = get_jack_buffers (procinfo, frames); + if (err) + { + mlt_log_warning( NULL, "%s: failed to get jack ports, not processing\n", __FUNCTION__); + return 0; + } + + lv2_connect_chain (procinfo, frames); + + lv2_process_chain (procinfo, frames); + + return 0; + } */ + + + +/******************************************* + ************** non RT stuff *************** + *******************************************/ + +/* static int + lv2_process_info_connect_jack (lv2_process_info_t * procinfo) + { + mlt_log_info( NULL, _("Connecting to JACK server with client name '%s'\n"), procinfo->jack_client_name); + + procinfo->jack_client = jack_client_open (procinfo->jack_client_name, JackNullOption, NULL); + + if (!procinfo->jack_client) + { + mlt_log_warning( NULL, "%s: could not create jack client; is the jackd server running?\n", __FUNCTION__); + return 1; + } + + mlt_log_verbose( NULL, _("Connected to JACK server\n")); + + jack_set_process_callback (procinfo->jack_client, process_jack, procinfo); + jack_on_shutdown (procinfo->jack_client, jack_shutdown_cb, procinfo); + + return 0; + } */ + +static void +lv2_process_info_connect_port (lv2_process_info_t * procinfo, + gshort in, + unsigned long port_index, + const char * port_name) +{ + const char ** jack_ports; + unsigned long jack_port_index; + int err; + char * full_port_name; + + jack_ports = jack_get_ports (procinfo->jack_client, NULL, NULL, + JackPortIsPhysical | (in ? JackPortIsOutput : JackPortIsInput)); + + if (!jack_ports) + return; + + for (jack_port_index = 0; + jack_ports[jack_port_index] && jack_port_index <= port_index; + jack_port_index++) + { + if (jack_port_index != port_index) + continue; + + full_port_name = g_strdup_printf ("%s:%s", procinfo->jack_client_name, port_name); + + mlt_log_debug( NULL, _("Connecting ports '%s' and '%s'\n"), full_port_name, jack_ports[jack_port_index]); + + err = jack_connect (procinfo->jack_client, + in ? jack_ports[jack_port_index] : full_port_name, + in ? full_port_name : jack_ports[jack_port_index]); + + if (err) + mlt_log_warning( NULL, "%s: error connecting ports '%s' and '%s'\n", + __FUNCTION__, full_port_name, jack_ports[jack_port_index]); + else + mlt_log_info( NULL, _("Connected ports '%s' and '%s'\n"), full_port_name, jack_ports[jack_port_index]); + + free (full_port_name); + } + + free (jack_ports); +} + +static int +lv2_process_info_set_port_count (lv2_process_info_t * procinfo, + unsigned long port_count, gboolean connect_inputs, gboolean connect_outputs) +{ + unsigned long i; + char * port_name; + jack_port_t ** port_ptr; + gshort in; + + if (procinfo->port_count >= port_count) + return -1; + + if (procinfo->port_count == 0) + { + procinfo->jack_input_ports = g_malloc (sizeof (jack_port_t *) * port_count); + procinfo->jack_output_ports = g_malloc (sizeof (jack_port_t *) * port_count); + + procinfo->jack_input_buffers = g_malloc (sizeof (LADSPA_Data *) * port_count); + procinfo->jack_output_buffers = g_malloc (sizeof (LADSPA_Data *) * port_count); + } + else + { + procinfo->jack_input_ports = g_realloc (procinfo->jack_input_ports, sizeof (jack_port_t *) * port_count); + procinfo->jack_output_ports = g_realloc (procinfo->jack_output_ports, sizeof (jack_port_t *) * port_count); + + procinfo->jack_input_buffers = g_realloc (procinfo->jack_input_buffers, sizeof (LADSPA_Data *) * port_count); + procinfo->jack_output_buffers = g_realloc (procinfo->jack_output_buffers, sizeof (LADSPA_Data *) * port_count); + } + + for (i = procinfo->port_count; i < port_count; i++) + { + for (in = 0; in < 2; in++) + { + port_name = g_strdup_printf ("%s_%ld", in ? "in" : "out", i + 1); + + //mlt_log_debug( NULL, _("Creating %s port %s\n"), in ? "input" : "output", port_name); + + port_ptr = (in ? &procinfo->jack_input_ports[i] + : &procinfo->jack_output_ports[i]); + + *port_ptr = jack_port_register (procinfo->jack_client, + port_name, + JACK_DEFAULT_AUDIO_TYPE, + in ? JackPortIsInput : JackPortIsOutput, + 0); + + if (!*port_ptr) + { + mlt_log_error( NULL, "%s: could not register port '%s'; aborting\n", + __FUNCTION__, port_name); + return 1; + } + + //mlt_log_debug( NULL, _("Created %s port %s\n"), in ? "input" : "output", port_name); + + if ((in && connect_inputs) || (!in && connect_outputs)) + lv2_process_info_connect_port (procinfo, in, i, port_name); + + g_free (port_name); + } + } + + procinfo->port_count = port_count; + + return 0; +} + +#endif + +void +lv2_process_info_set_channels (lv2_process_info_t * procinfo, + unsigned long channels, gboolean connect_inputs, gboolean connect_outputs) +{ +#ifdef WITH_JACK + lv2_process_info_set_port_count (procinfo, channels, connect_inputs, connect_outputs); +#endif + procinfo->channels = channels; +} + +lv2_process_info_t * +lv2_process_info_new (const char * client_name, unsigned long rack_channels, + gboolean connect_inputs, gboolean connect_outputs) +{ + lv2_process_info_t * procinfo; + char * jack_client_name; + int err; + + procinfo = g_malloc (sizeof (lv2_process_info_t)); + + procinfo->chain = NULL; + procinfo->chain_end = NULL; +#ifdef WITH_JACK + procinfo->jack_client = NULL; + procinfo->port_count = 0; + procinfo->jack_input_ports = NULL; + procinfo->jack_output_ports = NULL; +#endif + procinfo->channels = rack_channels; + procinfo->quit = FALSE; + + if ( client_name == NULL ) + { + lv2_sample_rate = 48000; // should be set externally before calling process_lv2 + lv2_buffer_size = MAX_BUFFER_SIZE; + procinfo->silent_buffer = g_malloc (sizeof (LADSPA_Data) * lv2_buffer_size ); + procinfo->jack_input_buffers = g_malloc (sizeof (LADSPA_Data *) * rack_channels); + procinfo->jack_output_buffers = g_malloc (sizeof (LADSPA_Data *) * rack_channels); + + return procinfo; + } + + /* sort out the client name */ + procinfo->jack_client_name = jack_client_name = strdup (client_name); + for (err = 0; jack_client_name[err] != '\0'; err++) + { + if (jack_client_name[err] == ' ') + jack_client_name[err] = '_'; + else if (!isalnum (jack_client_name[err])) + { /* shift all the chars up one (to remove the non-alphanumeric char) */ + int i; + for (i = err; jack_client_name[i] != '\0'; i++) + jack_client_name[i] = jack_client_name[i + 1]; + } + else if (isupper (jack_client_name[err])) + jack_client_name[err] = tolower (jack_client_name[err]); + } + +/* #ifdef WITH_JACK + err = lv2_process_info_connect_jack (procinfo); + if (err) + { + /\* g_free (procinfo); *\/ + return NULL; + /\* abort (); *\/ + } + + lv2_sample_rate = jack_get_sample_rate (procinfo->jack_client); + lv2_buffer_size = jack_get_sample_rate (procinfo->jack_client); + + jack_set_process_callback (procinfo->jack_client, process_jack, procinfo); + pthread_mutex_lock( &g_activate_mutex ); + jack_on_shutdown (procinfo->jack_client, jack_shutdown_cb, procinfo); + pthread_mutex_unlock( &g_activate_mutex ); + + jack_activate (procinfo->jack_client); + + err = lv2_process_info_set_port_count (procinfo, rack_channels, connect_inputs, connect_outputs); + if (err) + return NULL; + #endif */ + + return procinfo; +} + +void +lv2_process_info_destroy (lv2_process_info_t * procinfo) { +#ifdef WITH_JACK + if (procinfo->jack_client) + { + jack_deactivate (procinfo->jack_client); + jack_client_close (procinfo->jack_client); + } + g_free (procinfo->jack_input_ports); + g_free (procinfo->jack_output_ports); +#endif + g_free (procinfo->jack_input_buffers); + g_free (procinfo->jack_output_buffers); + g_free (procinfo->silent_buffer); + g_free (procinfo); +} + +void lv2_process_quit (lv2_process_info_t * procinfo) { + procinfo->quit = TRUE; +} diff --git a/src/modules/jackrack/lv2_process.h b/src/modules/jackrack/lv2_process.h new file mode 100644 index 000000000..f9bf4e13e --- /dev/null +++ b/src/modules/jackrack/lv2_process.h @@ -0,0 +1,84 @@ +/* + * JACK Rack + * + * Original: + * Copyright (C) Robert Ham 2002, 2003 (node@users.sourceforge.net) + * + * Modification for MLT: + * Copyright (C) 2004-2021 Meltytech, LLC + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#ifndef __JLH_PROCESS_H__ +#define __JLH_PROCESS_H__ + +#include +#ifdef WITH_JACK +#include +#endif +#include + +#include "lock_free_fifo.h" + +typedef struct _lv2_process_info lv2_process_info_t; + +/** this is what gets passed to the process() callback and contains all + the data the process callback will need */ +struct _lv2_process_info { + + /** the plugin instance chain */ + struct _lv2_plugin * chain; + struct _lv2_plugin * chain_end; + +#ifdef WITH_JACK + jack_client_t * jack_client; + unsigned long port_count; + jack_port_t ** jack_input_ports; + jack_port_t ** jack_output_ports; +#endif + + unsigned long channels; + LADSPA_Data ** jack_input_buffers; + LADSPA_Data ** jack_output_buffers; + LADSPA_Data * silent_buffer; + + char * jack_client_name; + int quit; +}; + +#ifndef WITH_JACK +typedef guint32 jack_nframes_t; +#endif +extern jack_nframes_t lv2_sample_rate; +extern jack_nframes_t lv2_buffer_size; + +lv2_process_info_t * lv2_process_info_new (const char * client_name, + unsigned long rack_channels, gboolean connect_inputs, gboolean connect_outputs); +void lv2_process_info_destroy (lv2_process_info_t * procinfo); + +void lv2_process_info_set_channels (lv2_process_info_t * procinfo, + unsigned long channels, gboolean connect_inputs, gboolean connect_outputs); + +int process_lv2 (lv2_process_info_t * procinfo, jack_nframes_t frames, + LADSPA_Data ** inputs, LADSPA_Data ** outputs); + +/* #ifdef WITH_JACK + int process_jack (jack_nframes_t frames, void * data); + #endif */ + +void lv2_process_quit (lv2_process_info_t * procinfo); + +#endif /* __JLH_PROCESS_H__ */ diff --git a/src/modules/jackrack/lv2_rack.c b/src/modules/jackrack/lv2_rack.c new file mode 100644 index 000000000..e6d32e603 --- /dev/null +++ b/src/modules/jackrack/lv2_rack.c @@ -0,0 +1,380 @@ +/* + * LV2 Rack + * + * Original: + * Copyright (C) Robert Ham 2002, 2003 (node@users.sourceforge.net) + * + * Modification for MLT: + * Copyright (C) 2004-2014 Meltytech, LLC + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "lv2_rack.h" +#include "lock_free_fifo.h" +#include "lv2_plugin_settings.h" +#include "framework/mlt_log.h" + +#ifndef _ +#define _(x) x +#endif +#define _x (const xmlChar*) +#define _s (const char*) + +extern lv2_mgr_t *g_lv2_plugin_mgr; + +lv2_rack_t * +lv2_rack_new (const char * client_name, unsigned long channels) +{ + lv2_rack_t *rack; + + rack = g_malloc (sizeof (lv2_rack_t)); + rack->saved_plugins = NULL; + rack->channels = channels; + rack->procinfo = lv2_process_info_new (client_name, channels, FALSE, FALSE); + if (!rack->procinfo) { + g_free (rack); + return NULL; + } + + rack->plugin_mgr = g_lv2_plugin_mgr; + lv2_mgr_set_plugins (rack->plugin_mgr, channels); + + return rack; +} + + +void +lv2_rack_destroy (lv2_rack_t * lv2_rack) +{ + lv2_process_quit (lv2_rack->procinfo); + // plugin_mgr is shared and global now, so we do not destroy it with each instance + // lv2_mgr_destroy (lv2_rack->plugin_mgr); + lv2_process_info_destroy (lv2_rack->procinfo); + g_slist_free (lv2_rack->saved_plugins); + g_free (lv2_rack); +} + +lv2_plugin_t * +lv2_rack_instantiate_plugin (lv2_rack_t * lv2_rack, lv2_plugin_desc_t * desc) +{ + lv2_plugin_t * plugin; + + /* check whether or not the plugin is RT capablex and confirm with the user if it isn't */ + if (!LADSPA_IS_HARD_RT_CAPABLE(desc->properties)) { + mlt_log_info( NULL, "Plugin not RT capable. The plugin '%s' does not describe itself as being capable of real-time operation. You may experience drop outs or jack may even kick us out if you use it.\n", + desc->name); + } + + /* create the plugin */ + plugin = lv2_plugin_new (desc, lv2_rack); + + if (!plugin) { + mlt_log_error( NULL, "Error loading file plugin '%s' from file '%s'\n", + desc->name, desc->uri); + } + + return plugin; +} + + +void +lv2_rack_add_saved_plugin (lv2_rack_t * lv2_rack, saved_plugin_t * saved_plugin) +{ + lv2_plugin_t * plugin = lv2_rack_instantiate_plugin (lv2_rack, saved_plugin->settings->desc); + if (!plugin) + { + mlt_log_warning( NULL, "%s: could not instantiate object file '%s'\n", + __FUNCTION__, saved_plugin->settings->desc->uri); + return; + } + lv2_rack->saved_plugins = g_slist_append (lv2_rack->saved_plugins, saved_plugin); + lv2_process_add_plugin (lv2_rack->procinfo, plugin); + lv2_rack_add_plugin (lv2_rack, plugin); +} + + +void +lv2_rack_add_plugin (lv2_rack_t * lv2_rack, lv2_plugin_t * plugin) +{ + saved_plugin_t * saved_plugin = NULL; + GSList * list; + unsigned long control, channel; + LADSPA_Data value; + guint copy; + + /* see if there's any saved settings that match the plugin id */ + for (list = lv2_rack->saved_plugins; list; list = g_slist_next (list)) + { + saved_plugin = list->data; + + if (saved_plugin->settings->desc->id == plugin->desc->id) + { + /* process the settings! */ + lv2_rack->saved_plugins = g_slist_remove (lv2_rack->saved_plugins, saved_plugin); + break; + } + saved_plugin = NULL; + } + + if ( !saved_plugin ) + return; + + /* initialize plugin parameters */ + plugin->enabled = lv2_settings_get_enabled (saved_plugin->settings); + plugin->wet_dry_enabled = lv2_settings_get_wet_dry_enabled (saved_plugin->settings); + + for (control = 0; control < saved_plugin->settings->desc->control_port_count; control++) + for (copy = 0; copy < plugin->copies; copy++) + { + value = lv2_settings_get_control_value (saved_plugin->settings, copy, control); + plugin->holders[copy].control_memory[control] = value; +//mlt_log_debug( NULL, "setting control value %s (%d) = %f\n", saved_plugin->settings->desc->port_names[control], copy, value); +// lff_write (plugin->holders[copy].ui_control_fifos + control, &value); + } + if (plugin->wet_dry_enabled) + for (channel = 0; channel < lv2_rack->channels; channel++) + { + value = lv2_settings_get_wet_dry_value (saved_plugin->settings, channel); + plugin->wet_dry_values[channel] = value; +//mlt_log_debug( NULL, "setting wet/dry value %d = %f\n", channel, value); +// lff_write (plugin->wet_dry_fifos + channel, &value); + } +} + + +static void +saved_rack_parse_plugin (lv2_rack_t * lv2_rack, saved_rack_t * saved_rack, saved_plugin_t * saved_plugin, + const char * filename, xmlNodePtr plugin) +{ + lv2_plugin_desc_t * desc; + lv2_settings_t * settings = NULL; + xmlNodePtr node; + xmlNodePtr sub_node; + xmlChar *content; + unsigned long control = 0; +#ifdef _WIN32 + xmlFreeFunc xmlFree = NULL; + xmlMemGet( &xmlFree, NULL, NULL, NULL); +#endif + + for (node = plugin->children; node; node = node->next) + { + if (xmlStrcmp (node->name, _x("id")) == 0) + { + content = xmlNodeGetContent (node); + + desc = lv2_mgr_get_any_desc (lv2_rack->plugin_mgr, _s(content)); + + if (!desc) + { + mlt_log_verbose( NULL, _("The file '%s' contains an unknown plugin with ID '%s'; skipping\n"), filename, _s(content)); + xmlFree (content); + return; + } + + xmlFree (content); + + settings = lv2_settings_new (desc, saved_rack->channels, saved_rack->sample_rate); + } + else if (xmlStrcmp (node->name, _x("enabled")) == 0) + { + content = xmlNodeGetContent (node); + lv2_settings_set_enabled (settings, xmlStrcmp (content, _x("true")) == 0 ? TRUE : FALSE); + xmlFree (content); + } + else if (xmlStrcmp (node->name, _x("wet_dry_enabled")) == 0) + { + content = xmlNodeGetContent (node); + lv2_settings_set_wet_dry_enabled (settings, xmlStrcmp (content, _x("true")) == 0 ? TRUE : FALSE); + xmlFree (content); + } + else if (xmlStrcmp (node->name, _x("wet_dry_locked")) == 0) + { + content = xmlNodeGetContent (node); + lv2_settings_set_wet_dry_locked (settings, xmlStrcmp (content, _x("true")) == 0 ? TRUE : FALSE); + xmlFree (content); + } + else if (xmlStrcmp (node->name, _x("wet_dry_values")) == 0) + { + unsigned long channel = 0; + + for (sub_node = node->children; sub_node; sub_node = sub_node->next) + { + if (xmlStrcmp (sub_node->name, _x("value")) == 0) + { + content = xmlNodeGetContent (sub_node); + lv2_settings_set_wet_dry_value (settings, channel, strtod (_s(content), NULL)); + xmlFree (content); + + channel++; + } + } + } + else if (xmlStrcmp (node->name, _x("lockall")) == 0) + { + content = xmlNodeGetContent (node); + lv2_settings_set_lock_all (settings, xmlStrcmp (content, _x("true")) == 0 ? TRUE : FALSE); + xmlFree (content); + } + else if (xmlStrcmp (node->name, _x("controlrow")) == 0) + { + gint copy = 0; + + for (sub_node = node->children; sub_node; sub_node = sub_node->next) + { + if (xmlStrcmp (sub_node->name, _x("lock")) == 0) + { + content = xmlNodeGetContent (sub_node); + lv2_settings_set_lock (settings, control, xmlStrcmp (content, _x("true")) == 0 ? TRUE : FALSE); + xmlFree (content); + } + else if (xmlStrcmp (sub_node->name, _x("value")) == 0) + { + content = xmlNodeGetContent (sub_node); + lv2_settings_set_control_value (settings, copy, control, strtod (_s(content), NULL)); + xmlFree (content); + copy++; + } + } + + control++; + } + } + + if (settings) + saved_plugin->settings = settings; +} + +static void +saved_rack_parse_lv2rack (lv2_rack_t * lv2_rack, saved_rack_t * saved_rack, const char * filename, xmlNodePtr lv2rack) +{ + xmlNodePtr node; + xmlChar *content; + saved_plugin_t * saved_plugin; +#ifdef _WIN32 + xmlFreeFunc xmlFree = NULL; + xmlMemGet( &xmlFree, NULL, NULL, NULL); +#endif + + for (node = lv2rack->children; node; node = node->next) + { + if (xmlStrcmp (node->name, _x("channels")) == 0) + { + content = xmlNodeGetContent (node); + saved_rack->channels = strtoul (_s(content), NULL, 10); + xmlFree (content); + } + else if (xmlStrcmp (node->name, _x("samplerate")) == 0) + { + content = xmlNodeGetContent (node); + saved_rack->sample_rate = strtoul (_s(content), NULL, 10); + xmlFree (content); + } + else if (xmlStrcmp (node->name, _x("plugin")) == 0) + { + saved_plugin = g_malloc0 (sizeof (saved_plugin_t)); + saved_rack->plugins = g_slist_append (saved_rack->plugins, saved_plugin); + saved_rack_parse_plugin (lv2_rack, saved_rack, saved_plugin, filename, node); + } + } +} + +static saved_rack_t * +saved_rack_new (lv2_rack_t * lv2_rack, const char * filename, xmlDocPtr doc) +{ + xmlNodePtr node; + saved_rack_t *saved_rack; + + /* create the saved rack */ + saved_rack = g_malloc (sizeof (saved_rack_t)); + saved_rack->plugins = NULL; + saved_rack->sample_rate = 48000; + saved_rack->channels = 2; + + for (node = doc->children; node; node = node->next) + { + if (xmlStrcmp (node->name, _x("lv2rack")) == 0) + saved_rack_parse_lv2rack (lv2_rack, saved_rack, filename, node); + } + + return saved_rack; +} + +static void +saved_rack_destroy (saved_rack_t * saved_rack) +{ + GSList * list; + + for (list = saved_rack->plugins; list; list = g_slist_next (list)) + lv2_settings_destroy (((saved_plugin_t *) list->data)->settings); + g_slist_free (saved_rack->plugins); + g_free (saved_rack); +} + + +int +lv2_rack_open_file (lv2_rack_t * lv2_rack, const char * filename) +{ + xmlDocPtr doc; + saved_rack_t * saved_rack; + GSList * list; + saved_plugin_t * saved_plugin; + + doc = xmlParseFile (filename); + if (!doc) + { + mlt_log_error( NULL, _("Could not parse file '%s'\n"), filename); + return 1; + } + + if (xmlStrcmp ( ((xmlDtdPtr)doc->children)->name, _x("lv2rack")) != 0) + { + mlt_log_error( NULL, _("The file '%s' is not a JACK Rack settings file\n"), filename); + return 1; + } + + saved_rack = saved_rack_new (lv2_rack, filename, doc); + xmlFreeDoc (doc); + + if (!saved_rack) + return 1; + + for (list = saved_rack->plugins; list; list = g_slist_next (list)) + { + saved_plugin = list->data; + + lv2_settings_set_sample_rate (saved_plugin->settings, lv2_sample_rate); + + lv2_rack_add_saved_plugin (lv2_rack, saved_plugin); + } + + saved_rack_destroy (saved_rack); + + return 0; +} + + +/* EOF */ diff --git a/src/modules/jackrack/lv2_rack.h b/src/modules/jackrack/lv2_rack.h new file mode 100644 index 000000000..831565ce8 --- /dev/null +++ b/src/modules/jackrack/lv2_rack.h @@ -0,0 +1,73 @@ +/* + * LV2 Rack + * + * Based on the Jack Rack module + * + * Original: + * Copyright (C) Robert Ham 2002, 2003 (node@users.sourceforge.net) + * + * Modification for MLT: + * Copyright (C) 2004-2014 Meltytech, LLC + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#ifndef __LV2_RACK_H__ +#define __LV2_RACK_H__ + +#include +#include + +#include "lv2_plugin.h" +#include "plugin_mgr.h" +#include "lv2_plugin_settings.h" +#include "lv2_process.h" + +typedef struct _saved_plugin saved_plugin_t; + +struct _saved_plugin +{ + lv2_settings_t *settings; +}; + +typedef struct _saved_rack saved_rack_t; + +struct _saved_rack +{ + unsigned long channels; + jack_nframes_t sample_rate; + GSList * plugins; +}; + +typedef struct _lv2_rack lv2_rack_t; + +struct _lv2_rack +{ + lv2_mgr_t * plugin_mgr; + lv2_process_info_t * procinfo; + unsigned long channels; + GSList * saved_plugins; +}; + +lv2_rack_t * lv2_rack_new (const char * client_name, unsigned long channels); +void lv2_rack_destroy (lv2_rack_t * lv2_rack); + +int lv2_rack_open_file (lv2_rack_t * lv2_rack, const char * filename); +void lv2_rack_add_plugin (lv2_rack_t * lv2_rack, lv2_plugin_t * plugin); +void lv2_rack_add_saved_plugin (lv2_rack_t * lv2_rack, struct _saved_plugin * saved_plugin); + +lv2_plugin_t * lv2_rack_instantiate_plugin (lv2_rack_t * lv2_rack, lv2_plugin_desc_t * desc); + +#endif /* __LV2_RACK_H__ */ diff --git a/src/modules/jackrack/lv2_urid_helper.h b/src/modules/jackrack/lv2_urid_helper.h new file mode 100644 index 000000000..6c62c2d27 --- /dev/null +++ b/src/modules/jackrack/lv2_urid_helper.h @@ -0,0 +1,67 @@ +/* + * LV2 URID Helpers + * + * Adapted from lv2bench.c from lilv project https://drobilla.net/software/lilv.html - http://gitlab.com/lv2/lilv.git + * + * Copyright (C) David Robillard 2012-2019 (d@drobilla.net) + * Copyright (C) mr.fantastic (mrfantastic@firemail.cc) and MLT project 2024 + * + * ISC License + * SPDX-License-Identifier: ISC + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH + * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, + * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM + * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR + * OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR + * PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef __LV2_URID_HELPER_H__ +#define __LV2_URID_HELPER_H__ + +typedef struct { + char** uris; + size_t n_uris; +} URITable; + +static void +uri_table_init(URITable* table) +{ + table->uris = NULL; + table->n_uris = 0; +} + +static LV2_URID +uri_table_map(LV2_URID_Map_Handle handle, const char* uri) +{ + URITable* table = (URITable*)handle; + for (size_t i = 0; i < table->n_uris; ++i) { + if (!strcmp(table->uris[i], uri)) { + return i + 1; + } + } + + const size_t len = strlen(uri); + table->uris = (char**)realloc(table->uris, ++table->n_uris * sizeof(char*)); + table->uris[table->n_uris - 1] = (char*)malloc(len + 1); + memcpy(table->uris[table->n_uris - 1], uri, len + 1); + return table->n_uris; +} + +static const char* +uri_table_unmap(LV2_URID_Map_Handle handle, LV2_URID urid) +{ + URITable* table = (URITable*)handle; + if (urid > 0 && urid <= table->n_uris) { + return table->uris[urid - 1]; + } + return NULL; +} + +#endif /* __LV2_URID_HELPER_H__ */ diff --git a/src/modules/jackrack/lv2blacklist.txt b/src/modules/jackrack/lv2blacklist.txt new file mode 100644 index 000000000..e69de29bb diff --git a/src/modules/jackrack/plugin_desc.c b/src/modules/jackrack/plugin_desc.c index f37df2f7d..d3adc32ba 100644 --- a/src/modules/jackrack/plugin_desc.c +++ b/src/modules/jackrack/plugin_desc.c @@ -39,6 +39,19 @@ else \ (property) = NULL; +#ifdef WITH_LV2 + +extern LilvNode *lv2_input_class; +extern LilvNode *lv2_output_class; +extern LilvNode *lv2_audio_class; +extern LilvNode *lv2_control_class; +extern LilvNode *lv2_atom_class; +extern LilvNode *lv2_integer_property; +extern LilvNode *lv2_logarithmic_property; +extern LilvNode *lv2_toggled_property; +extern LilvNode *lv2_enumeration_property; + +#endif void plugin_desc_set_ports (plugin_desc_t * pd, @@ -452,4 +465,364 @@ plugin_desc_get_copies (plugin_desc_t * pd, unsigned long rack_channels) return copies; } +#ifdef WITH_LV2 +static void +lv2_plugin_desc_init (lv2_plugin_desc_t * pd) +{ + pd->uri = NULL; + pd->id = 0; + pd->name = NULL; + pd->maker = NULL; + pd->properties = 0; + pd->channels = 0; + pd->port_count = 0; + pd->port_descriptors = NULL; + pd->port_range_hints = NULL; + pd->audio_input_port_indicies = NULL; + pd->audio_output_port_indicies = NULL; + pd->audio_aux_port_indicies = NULL; + pd->control_port_count = 0; + pd->control_port_indicies = NULL; + pd->status_port_count = 0; + pd->status_port_indicies = NULL; + pd->aux_channels = 0; + pd->aux_are_input = TRUE; + pd->has_input = TRUE; +} + +void +lv2_plugin_desc_set_uri (lv2_plugin_desc_t * pd, const char * uri) +{ + set_string_property (pd->uri, uri); +} + +void +lv2_plugin_desc_set_index (lv2_plugin_desc_t * pd, unsigned long index) +{ + pd->index = index; +} + + +void +lv2_plugin_desc_set_id (lv2_plugin_desc_t * pd, unsigned long id) +{ + pd->id = id; +} + +void +lv2_plugin_desc_set_name (lv2_plugin_desc_t * pd, const char * name) +{ + set_string_property (pd->name, name); +} + +void +lv2_plugin_desc_set_maker (lv2_plugin_desc_t * pd, const char * maker) +{ + set_string_property (pd->maker, maker); +} + +void +lv2_plugin_desc_set_properties (lv2_plugin_desc_t * pd, LADSPA_Properties properties) +{ + pd->properties = properties; +} + +static void +lv2_plugin_desc_set_port_counts (lv2_plugin_desc_t * pd) +{ + unsigned long i; + unsigned long icount = 0; + unsigned long ocount = 0; + + for (i = 0; i < pd->port_count; i++) + { + /* if (LADSPA_IS_PORT_ATOM (pd->port_descriptors[i])) + { + if(yes) printf("%li atom port\n", i); + } + else */ if (LADSPA_IS_PORT_AUDIO (pd->port_descriptors[i])) + { + if (LADSPA_IS_PORT_INPUT (pd->port_descriptors[i])) + { + plugin_desc_add_audio_port_index (&pd->audio_input_port_indicies, &icount, i); + } + else + { + plugin_desc_add_audio_port_index (&pd->audio_output_port_indicies, &ocount, i); + } + } + else + { + if (LADSPA_IS_PORT_OUTPUT (pd->port_descriptors[i])) + { + pd->status_port_count++; + if (pd->status_port_count == 0) + pd->status_port_indicies = g_malloc (sizeof (unsigned long) * pd->status_port_count); + else + pd->status_port_indicies = g_realloc (pd->status_port_indicies, + sizeof (unsigned long) * pd->status_port_count); + pd->status_port_indicies[pd->status_port_count - 1] = i; + } + else + { + pd->control_port_count++; + if (pd->control_port_count == 0) + pd->control_port_indicies = g_malloc (sizeof (unsigned long) * pd->control_port_count); + else + pd->control_port_indicies = g_realloc (pd->control_port_indicies, + sizeof (unsigned long) * pd->control_port_count); + pd->control_port_indicies[pd->control_port_count - 1] = i; + } + } + } + + if (icount == ocount) + pd->channels = icount; + else if( icount == 0 ) + { + pd->channels = ocount; + pd->has_input = FALSE; + } + else + { /* deal with auxiliary ports */ + unsigned long ** port_indicies; + unsigned long port_count; + unsigned long i, j; + + if (icount > ocount) + { + pd->channels = ocount; + pd->aux_channels = icount - ocount; + pd->aux_are_input = TRUE; + port_indicies = &pd->audio_input_port_indicies; + port_count = icount; + } + else + { + pd->channels = icount; + pd->aux_channels = ocount - icount; + pd->aux_are_input = FALSE; + port_indicies = &pd->audio_output_port_indicies; + port_count = ocount; + } + + /* allocate indices */ + pd->audio_aux_port_indicies = g_malloc (sizeof (unsigned long) * pd->aux_channels); + + /* copy indices */ + for (i = pd->channels, j = 0; i < port_count; i++, j++) + pd->audio_aux_port_indicies[j] = (*port_indicies)[i]; + + /* shrink the main indices to only have channels indices */ + *port_indicies = g_realloc (*port_indicies, sizeof (unsigned long) * pd->channels); + } +} + +static void +lv2_plugin_desc_free_ports (lv2_plugin_desc_t * pd) +{ + if (pd->port_count) + { + g_free (pd->port_descriptors); + g_free (pd->port_range_hints); + g_free (pd->audio_input_port_indicies); + g_free (pd->audio_output_port_indicies); + g_free (pd->port_names); + g_free (pd->control_port_indicies); + g_free (pd->status_port_indicies); + g_free (pd->audio_aux_port_indicies); + pd->port_descriptors = NULL; + pd->port_range_hints = NULL; + pd->audio_input_port_indicies = NULL; + pd->audio_output_port_indicies = NULL; + pd->port_names = NULL; + pd->control_port_indicies = NULL; + pd->status_port_indicies = NULL; + pd->audio_aux_port_indicies = NULL; + + pd->port_count = 0; + } +} + +static void +lv2_plugin_desc_free (lv2_plugin_desc_t * pd) +{ + g_free (pd->def_values); + g_free (pd->min_values); + g_free (pd->max_values); + lv2_plugin_desc_set_uri (pd, NULL); + lv2_plugin_desc_set_name (pd, NULL); + lv2_plugin_desc_set_maker (pd, NULL); + lv2_plugin_desc_free_ports (pd); +} + +void +lv2_plugin_desc_destroy (lv2_plugin_desc_t * pd) +{ + lv2_plugin_desc_free (pd); + g_free (pd); +} + +lv2_plugin_desc_t * +lv2_plugin_desc_new () +{ + lv2_plugin_desc_t * pd; + pd = g_malloc (sizeof (lv2_plugin_desc_t)); + lv2_plugin_desc_init (pd); + return pd; +} + +void +lv2_plugin_desc_set_ports (lv2_plugin_desc_t * pd, + unsigned long port_count, + const LADSPA_PortDescriptor * port_descriptors, + const LADSPA_PortRangeHint * port_range_hints, + const char * const * port_names) +{ + unsigned long i; + + lv2_plugin_desc_free_ports (pd); + + if (!port_count) + return; + + pd->port_count = port_count; + pd->port_descriptors = g_malloc (sizeof (LADSPA_PortDescriptor) * port_count); + pd->port_range_hints = g_malloc (sizeof (LADSPA_PortRangeHint) * port_count); + pd->port_names = g_malloc (sizeof (char *) * port_count); + + memcpy (pd->port_descriptors, port_descriptors, sizeof (LADSPA_PortDescriptor) * port_count); + memcpy (pd->port_range_hints, port_range_hints, sizeof (LADSPA_PortRangeHint) * port_count); + + for (i = 0; i < port_count; i++) + pd->port_names[i] = g_strdup (port_names[i]); + + lv2_plugin_desc_set_port_counts (pd); +} + +lv2_plugin_desc_t * +lv2_plugin_desc_new_with_descriptor (const char * uri, + unsigned long index, + const LilvPlugin * plugin) +{ + lv2_plugin_desc_t * pd; + pd = lv2_plugin_desc_new (); + + LilvNode *val = NULL; + + char *str_ptr = strchr(uri, ':'); + while (str_ptr != NULL) + { + *str_ptr++ = '<'; + str_ptr = strchr(str_ptr, ':'); + } + + lv2_plugin_desc_set_uri (pd, uri); + + str_ptr = strchr(uri, '<'); + while (str_ptr != NULL) + { + *str_ptr++ = ':'; + str_ptr = strchr(str_ptr, '<'); + } + + lv2_plugin_desc_set_index (pd, index); + + val = lilv_plugin_get_name (plugin); + + lv2_plugin_desc_set_name (pd, lilv_node_as_string (val)); + + lv2_plugin_desc_set_maker (pd, lilv_node_as_string (lilv_plugin_get_author_name (plugin))); + + int PortCount = lilv_plugin_get_num_ports (plugin); + char **PortNames = calloc (PortCount, sizeof (char *)); + LADSPA_PortDescriptor *port_descriptors = calloc (PortCount, sizeof (LADSPA_PortDescriptor)); + LADSPA_PortRangeHint *PortRangeHints = calloc (PortCount, sizeof (LADSPA_PortRangeHint)); + + pd->min_values = calloc (PortCount, sizeof (LADSPA_Data)), pd->max_values = calloc (PortCount, sizeof (LADSPA_Data)), pd->def_values = calloc (PortCount, sizeof (LADSPA_Data)); + + lilv_plugin_get_port_ranges_float(plugin, pd->min_values, pd->max_values, pd->def_values); + + int i; + for (i = 0; i < PortCount; ++i) + { + const LilvPort *port = lilv_plugin_get_port_by_index(plugin, i); + + if(lilv_port_is_a(plugin, port, lv2_audio_class)) + { + port_descriptors[i] |= LADSPA_PORT_AUDIO; + } + if(lilv_port_is_a(plugin, port, lv2_input_class)) + { + port_descriptors[i] |= LADSPA_PORT_INPUT; + } + if(lilv_port_is_a(plugin, port, lv2_output_class)) + { + port_descriptors[i] |= LADSPA_PORT_OUTPUT; + } + if(lilv_port_is_a(plugin, port, lv2_control_class)) + { + port_descriptors[i] |= LADSPA_PORT_CONTROL; + } + if (lilv_port_is_a(plugin, port, lv2_atom_class)) + { + port_descriptors[i] |= LADSPA_PORT_ATOM; + } + + if (lilv_port_has_property(plugin, port, lv2_integer_property)) + { + PortRangeHints[i].HintDescriptor |= LADSPA_HINT_INTEGER; + } + + if (lilv_port_has_property(plugin, port, lv2_logarithmic_property)) + { + PortRangeHints[i].HintDescriptor |= LADSPA_HINT_LOGARITHMIC; + } + + if (lilv_port_has_property(plugin, port, lv2_toggled_property)) + { + PortRangeHints[i].HintDescriptor |= LADSPA_HINT_TOGGLED; + } + + if (lilv_port_has_property(plugin, port, lv2_enumeration_property)) + { + PortRangeHints[i].HintDescriptor |= LADSPA_HINT_ENUMERATION; + } + + PortRangeHints[i].LowerBound = pd->min_values[i]; + PortRangeHints[i].UpperBound = pd->max_values[i]; + + PortNames[i] = (char *) lilv_node_as_string (lilv_port_get_name(plugin, port)); + } + + lv2_plugin_desc_set_ports (pd, + PortCount, + port_descriptors, + PortRangeHints, + (const char * const *) PortNames); + + free (PortNames); + free (port_descriptors); + + return pd; +} + +gint +lv2_plugin_desc_get_copies (lv2_plugin_desc_t * pd, unsigned long rack_channels) +{ + gint copies = 1; + + if (pd->channels > rack_channels) + return 0; + + while (pd->channels * copies < rack_channels) + copies++; + + if (pd->channels * copies > rack_channels) + return 0; + + return copies; +} +#endif + /* EOF */ diff --git a/src/modules/jackrack/plugin_desc.h b/src/modules/jackrack/plugin_desc.h index 75bb53b7b..610aa0c67 100644 --- a/src/modules/jackrack/plugin_desc.h +++ b/src/modules/jackrack/plugin_desc.h @@ -28,6 +28,28 @@ #include #include +#ifdef WITH_LV2 + +#define LADSPA_PORT_ATOM 10 +#define LADSPA_IS_PORT_ATOM(x) ((x) & LADSPA_PORT_ATOM) +#define LADSPA_HINT_ENUMERATION LADSPA_HINT_DEFAULT_LOW +#define LADSPA_IS_HINT_ENUMERATION(x) ((x) & LADSPA_HINT_ENUMERATION) + +#include + +/* lv2 extenstions */ +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#endif + typedef struct _plugin_desc plugin_desc_t; struct _plugin_desc @@ -84,4 +106,62 @@ LADSPA_Data plugin_desc_change_control_value (plugin_desc_t *, unsigned long, LA gint plugin_desc_get_copies (plugin_desc_t * pd, unsigned long rack_channels); +#ifdef WITH_LV2 +typedef struct _lv2_plugin_desc lv2_plugin_desc_t; + +struct _lv2_plugin_desc +{ + char * uri; /* file name */ + unsigned long index; + unsigned long id; + char * name; + char * maker; + LADSPA_Properties properties; + gboolean rt; + + unsigned long channels; + + gboolean aux_are_input; + unsigned long aux_channels; + + unsigned long port_count; + LADSPA_PortDescriptor * port_descriptors; + LADSPA_PortRangeHint * port_range_hints; + char ** port_names; + + unsigned long * audio_input_port_indicies; + unsigned long * audio_output_port_indicies; + + unsigned long * audio_aux_port_indicies; + + unsigned long control_port_count; + unsigned long * control_port_indicies; + + unsigned long status_port_count; + unsigned long * status_port_indicies; + + float *def_values, *min_values, *max_values; + + gboolean has_input; +}; + +lv2_plugin_desc_t * lv2_plugin_desc_new_with_descriptor (const char * uri, + unsigned long index, + const LilvPlugin * plugin); +void lv2_plugin_desc_destroy (lv2_plugin_desc_t * pd); +lv2_plugin_desc_t * lv2_plugin_desc_new (); +void lv2_plugin_desc_set_uri (lv2_plugin_desc_t * pd, const char * uri); +void lv2_plugin_desc_set_index (lv2_plugin_desc_t * pd, unsigned long index); +void lv2_plugin_desc_set_id (lv2_plugin_desc_t * pd, unsigned long id); +void lv2_plugin_desc_set_name (lv2_plugin_desc_t * pd, const char * name); +void lv2_plugin_desc_set_maker (lv2_plugin_desc_t * pd, const char * maker); +void lv2_plugin_desc_set_properties (lv2_plugin_desc_t * pd, LADSPA_Properties properties); + +struct _lv2_plugin * lv2_plugin_desc_instantiate (lv2_plugin_desc_t * pd); + +LADSPA_Data lv2_plugin_desc_change_control_value (lv2_plugin_desc_t *, unsigned long, LADSPA_Data, guint32, guint32); + +gint lv2_plugin_desc_get_copies (lv2_plugin_desc_t * pd, unsigned long rack_channels); +#endif + #endif /* __JR_PLUGIN_DESC_H__ */ diff --git a/src/modules/jackrack/plugin_mgr.c b/src/modules/jackrack/plugin_mgr.c index ab29f38a7..c3e2ef7d4 100644 --- a/src/modules/jackrack/plugin_mgr.c +++ b/src/modules/jackrack/plugin_mgr.c @@ -41,6 +41,65 @@ #include "framework/mlt_log.h" #include "framework/mlt_factory.h" +#ifdef WITH_LV2 + +#include + +/* lv2 extenstions */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "lv2/buf-size/buf-size.h" +#include "lv2/parameters/parameters.h" +#include + +#include "lv2_urid_helper.h" + +LilvNode *lv2_input_class; +LilvNode *lv2_output_class; +LilvNode *lv2_audio_class; +LilvNode *lv2_control_class; +LilvNode *lv2_atom_class; +LilvNode *lv2_integer_property; +LilvNode *lv2_logarithmic_property; +LilvNode *lv2_toggled_property; +LilvNode *lv2_enumeration_property; + +static LV2_URID urid_atom_Float; +static LV2_URID urid_atom_Int; +static LV2_URID urid_bufsz_minBlockLength; +static LV2_URID urid_bufsz_maxBlockLength; +static LV2_URID urid_param_sampleRate; +static LV2_URID urid_bufsz_sequenceSize; +static LV2_URID urid_ui_updateRate; +static LV2_URID urid_ui_scaleFactor; + +static URITable uri_table; + +static float lv2opt_sample_rate = 48000.000000; +static uint32_t lv2opt_block_length = 4096; +static size_t lv2opt_midi_buf_size = 32768; +static float lv2opt_ui_update_hz = 60.000000; +static float lv2opt_ui_scale_factor = 1.000000; + +static LV2_URID_Map map = {&uri_table, uri_table_map}; +static LV2_Feature map_feature = {LV2_URID_MAP_URI, &map}; +static LV2_URID_Unmap unmap = {&uri_table, uri_table_unmap}; +static LV2_Feature unmap_feature = {LV2_URID_UNMAP_URI, &unmap}; +static LV2_Feature boundedBlockLength_feature = {LV2_BUF_SIZE__boundedBlockLength, NULL}; +static LV2_Options_Option lv2_options_features[7]; +static LV2_Feature options_feature = {LV2_OPTIONS__options, (void *) lv2_options_features}; + +const LV2_Feature* features[] = {&map_feature, &unmap_feature, &options_feature, &boundedBlockLength_feature, NULL}; + +#endif + static gboolean plugin_is_valid (const LADSPA_Descriptor * descriptor) { @@ -350,5 +409,220 @@ plugin_mgr_get_any_desc (plugin_mgr_t * plugin_mgr, unsigned long id) return plugin_mgr_find_desc (plugin_mgr, plugin_mgr->all_plugins, id); } +#ifdef WITH_LV2 + +static gint +lv2_mgr_sort (gconstpointer a, gconstpointer b) +{ + const lv2_plugin_desc_t * da; + const lv2_plugin_desc_t * db; + da = (const lv2_plugin_desc_t *) a; + db = (const lv2_plugin_desc_t *) b; + + return strcasecmp (da->name, db->name); +} + +static void +lv2_mgr_get_uri_plugins (lv2_mgr_t * plugin_mgr, const LilvPlugin * plugin) +{ + unsigned long plugin_index = 0; + lv2_plugin_desc_t * desc; + + desc = lv2_plugin_desc_new_with_descriptor (lilv_node_as_uri (lilv_plugin_get_uri (plugin)), plugin_index, plugin); + plugin_mgr->all_plugins = g_slist_append (plugin_mgr->all_plugins, desc); + plugin_index++; + plugin_mgr->plugin_count++; +} + +static void +lv2_mgr_get_dir_plugins (lv2_mgr_t * plugin_mgr) +{ + LILV_FOREACH (plugins, i, plugin_mgr->plugin_list) + { + const LilvPlugin *p = lilv_plugins_get (plugin_mgr->plugin_list, i); + lv2_mgr_get_uri_plugins (plugin_mgr, p); + } +} + +static void +lv2_mgr_get_path_plugins (lv2_mgr_t * plugin_mgr) +{ + lv2_mgr_get_dir_plugins (plugin_mgr); +} + +lv2_mgr_t * +lv2_mgr_new () +{ + lv2_mgr_t * pm; + char dirname[PATH_MAX]; + + pm = g_malloc (sizeof (lv2_mgr_t)); + pm->all_plugins = NULL; + pm->plugins = NULL; + pm->plugin_count = 0; + + pm->lv2_world = lilv_world_new (); + lilv_world_load_all (pm->lv2_world); + pm->plugin_list = (LilvPlugins *) lilv_world_get_all_plugins (pm->lv2_world); + + lv2_input_class = lilv_new_uri (pm->lv2_world, LILV_URI_INPUT_PORT); + lv2_output_class = lilv_new_uri (pm->lv2_world, LILV_URI_OUTPUT_PORT); + lv2_audio_class = lilv_new_uri (pm->lv2_world, LILV_URI_AUDIO_PORT); + lv2_control_class = lilv_new_uri (pm->lv2_world, LILV_URI_CONTROL_PORT); + lv2_atom_class = lilv_new_uri (pm->lv2_world, LV2_ATOM__AtomPort); + lv2_integer_property = lilv_new_uri (pm->lv2_world, LV2_CORE__integer); + lv2_logarithmic_property = lilv_new_uri (pm->lv2_world, LV2_PORT_PROPS__logarithmic); + lv2_toggled_property = lilv_new_uri (pm->lv2_world, LV2_CORE__toggled); + lv2_enumeration_property = lilv_new_uri (pm->lv2_world, LV2_CORE__enumeration); + + uri_table_init(&uri_table); + + urid_atom_Float = uri_table_map(&uri_table, LV2_ATOM__Float); + urid_atom_Int = uri_table_map(&uri_table, LV2_ATOM__Int); + urid_bufsz_minBlockLength = uri_table_map(&uri_table, LV2_BUF_SIZE__minBlockLength); + urid_bufsz_maxBlockLength = uri_table_map(&uri_table, LV2_BUF_SIZE__maxBlockLength); + urid_bufsz_sequenceSize = uri_table_map(&uri_table, LV2_BUF_SIZE__sequenceSize); + urid_ui_updateRate = uri_table_map(&uri_table, LV2_UI__updateRate); + urid_ui_scaleFactor = uri_table_map(&uri_table, LV2_UI__scaleFactor); + urid_param_sampleRate = uri_table_map(&uri_table, LV2_PARAMETERS__sampleRate); + + + + lv2_options_features[0].context = LV2_OPTIONS_INSTANCE; + lv2_options_features[0].subject = 0; + lv2_options_features[0].key = urid_param_sampleRate; + lv2_options_features[0].size = sizeof(float); + lv2_options_features[0].type = urid_atom_Float; + lv2_options_features[0].value = &lv2opt_sample_rate; + + lv2_options_features[1].context = LV2_OPTIONS_INSTANCE; + lv2_options_features[1].subject = 0; + lv2_options_features[1].key = urid_bufsz_minBlockLength; + lv2_options_features[1].size = sizeof(int32_t); + lv2_options_features[1].type = urid_atom_Int; + lv2_options_features[1].value = &lv2opt_block_length; + + lv2_options_features[2].context = LV2_OPTIONS_INSTANCE; + lv2_options_features[2].subject = 0; + lv2_options_features[2].key = urid_bufsz_maxBlockLength; + lv2_options_features[2].size = sizeof(int32_t); + lv2_options_features[2].type = urid_atom_Int; + lv2_options_features[2].value = &lv2opt_block_length; + + lv2_options_features[3].context = LV2_OPTIONS_INSTANCE; + lv2_options_features[3].subject = 0; + lv2_options_features[3].key = urid_bufsz_sequenceSize; + lv2_options_features[3].size = sizeof(int32_t); + lv2_options_features[3].type = urid_atom_Int; + lv2_options_features[3].value = &lv2opt_midi_buf_size; + + lv2_options_features[4].context = LV2_OPTIONS_INSTANCE; + lv2_options_features[4].subject = 0; + lv2_options_features[4].key = urid_ui_updateRate; + lv2_options_features[4].size = sizeof(float); + lv2_options_features[4].type = urid_atom_Float; + lv2_options_features[4].value = &lv2opt_ui_update_hz; + + lv2_options_features[5].context = LV2_OPTIONS_INSTANCE; + lv2_options_features[5].subject = 0; + lv2_options_features[5].key = urid_ui_scaleFactor; + lv2_options_features[5].size = sizeof(float); + lv2_options_features[5].type = urid_atom_Float; + lv2_options_features[5].value = &lv2opt_ui_scale_factor; + + lv2_options_features[6].context = LV2_OPTIONS_INSTANCE; + lv2_options_features[6].subject = 0; + lv2_options_features[6].key = 0; + lv2_options_features[6].size = 0; + lv2_options_features[6].type = 0; + lv2_options_features[6].value = NULL; + + snprintf (dirname, PATH_MAX, "%s/jackrack/lv2blacklist.txt", mlt_environment ("MLT_DATA")); + pm->blacklist = mlt_properties_load (dirname); + lv2_mgr_get_path_plugins (pm); + + if (!pm->all_plugins) + mlt_log_warning( NULL, "No LV2 plugins were found!\n\nCheck your LV2_PATH environment variable.\n"); + else + pm->all_plugins = g_slist_sort (pm->all_plugins, lv2_mgr_sort); + + return pm; +} + +void +lv2_mgr_destroy (lv2_mgr_t * plugin_mgr) +{ + GSList * list; + + for (list = plugin_mgr->all_plugins; list; list = g_slist_next (list)) + lv2_plugin_desc_destroy ((lv2_plugin_desc_t *) list->data); + + g_slist_free (plugin_mgr->plugins); + g_slist_free (plugin_mgr->all_plugins); + mlt_properties_close (plugin_mgr->blacklist); + + lilv_node_free (lv2_input_class); + lilv_node_free (lv2_output_class); + lilv_node_free (lv2_audio_class); + lilv_node_free (lv2_control_class); + lilv_node_free (lv2_atom_class); + lilv_node_free (lv2_integer_property); + lilv_node_free (lv2_logarithmic_property); + lilv_node_free (lv2_toggled_property); + lilv_node_free (lv2_enumeration_property); + + lilv_world_free (plugin_mgr->lv2_world); + free (plugin_mgr); +} + +void +lv2_mgr_set_plugins (lv2_mgr_t * plugin_mgr, unsigned long rack_channels) +{ + GSList * list; + lv2_plugin_desc_t * desc; + + /* clear the current plugins */ + g_slist_free (plugin_mgr->plugins); + plugin_mgr->plugins = NULL; + + for (list = plugin_mgr->all_plugins; list; list = g_slist_next (list)) + { + desc = (lv2_plugin_desc_t *) list->data; + + if (desc->channels > 0 && lv2_plugin_desc_get_copies (desc, rack_channels) != 0) + plugin_mgr->plugins = g_slist_append (plugin_mgr->plugins, desc); + } +} + +static lv2_plugin_desc_t * +lv2_mgr_find_desc (lv2_mgr_t * plugin_mgr, GSList * plugins, char *id) +{ + GSList * list; + lv2_plugin_desc_t * desc; + + for (list = plugins; list; list = g_slist_next (list)) + { + desc = (lv2_plugin_desc_t *) list->data; + + if (!strcmp(desc->uri, id)) + return desc; + + } + + return NULL; +} + +lv2_plugin_desc_t * +lv2_mgr_get_desc (lv2_mgr_t * plugin_mgr, char *id) +{ + return lv2_mgr_find_desc (plugin_mgr, plugin_mgr->plugins, id); +} + +lv2_plugin_desc_t * +lv2_mgr_get_any_desc (lv2_mgr_t * plugin_mgr, char *id) +{ + return lv2_mgr_find_desc (plugin_mgr, plugin_mgr->all_plugins, id); +} +#endif /* EOF */ diff --git a/src/modules/jackrack/plugin_mgr.h b/src/modules/jackrack/plugin_mgr.h index 7bf8de2f7..6bfdd039b 100644 --- a/src/modules/jackrack/plugin_mgr.h +++ b/src/modules/jackrack/plugin_mgr.h @@ -30,6 +30,10 @@ #include "plugin_desc.h" #include "framework/mlt_properties.h" +#ifdef WITH_LV2 +#include +#endif + typedef struct _plugin_mgr plugin_mgr_t; struct _plugin_mgr @@ -41,6 +45,22 @@ struct _plugin_mgr mlt_properties blacklist; }; +#ifdef WITH_LV2 +typedef struct _lv2_mgr lv2_mgr_t; + +struct _lv2_mgr +{ + LilvWorld *lv2_world; + LilvPlugins *plugin_list; + + GSList * all_plugins; /* this contain instances of lv2_plugin_desc_t */ + + GSList * plugins; + unsigned long plugin_count; + mlt_properties blacklist; +}; +#endif + struct _ui; plugin_mgr_t * plugin_mgr_new (); @@ -51,4 +71,14 @@ void plugin_mgr_set_plugins (plugin_mgr_t * plugin_mgr, unsigned long rack_chann plugin_desc_t * plugin_mgr_get_desc (plugin_mgr_t * plugin_mgr, unsigned long id); plugin_desc_t * plugin_mgr_get_any_desc (plugin_mgr_t * plugin_mgr, unsigned long id); +#ifdef WITH_LV2 +lv2_mgr_t * lv2_mgr_new (); +void lv2_mgr_destroy (lv2_mgr_t * plugin_mgr); + +void lv2_mgr_set_plugins (lv2_mgr_t * plugin_mgr, unsigned long rack_channels); + +lv2_plugin_desc_t * lv2_mgr_get_desc (lv2_mgr_t * plugin_mgr, char *id); +lv2_plugin_desc_t * lv2_mgr_get_any_desc (lv2_mgr_t * plugin_mgr, char *id); +#endif + #endif /* __JR_PLUGIN_MANAGER_H__ */ diff --git a/src/modules/jackrack/producer_lv2.c b/src/modules/jackrack/producer_lv2.c new file mode 100644 index 000000000..b1ac9d3ff --- /dev/null +++ b/src/modules/jackrack/producer_lv2.c @@ -0,0 +1,229 @@ +/* + * producer_ladspa.c -- LADSPA plugin producer + * Copyright (C) 2013-2014 Meltytech, LLC + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include +#include +#include + +#include +#include +#include +#include + +#include + +#include "lv2_rack.h" + +#define BUFFER_LEN 10000 + +/** One-time initialization of lv2 rack. +*/ + +static lv2_rack_t *initialise_lv2_rack(mlt_properties properties, int channels) +{ + lv2_rack_t *lv2rack = NULL; + //unsigned long plugin_id = mlt_properties_get_int64(properties, "_pluginid"); + char *plugin_id = NULL; + plugin_id = mlt_properties_get(properties, "_pluginid"); + + // Start LV2Rack + if (plugin_id) { + // Create LV2Rack without Jack client name so that it only uses LV2 + lv2rack = lv2_rack_new(NULL, channels); + mlt_properties_set_data(properties, + "_lv2rack", + lv2rack, + 0, + (mlt_destructor) lv2_rack_destroy, + NULL); + + // Load one LV2 plugin by its URI + lv2_plugin_desc_t *desc = lv2_mgr_get_any_desc(lv2rack->plugin_mgr, plugin_id); + lv2_plugin_t *plugin; + + if (desc && (plugin = lv2_rack_instantiate_plugin(lv2rack, desc))) { + plugin->enabled = TRUE; + plugin->wet_dry_enabled = FALSE; + lv2_process_add_plugin(lv2rack->procinfo, plugin); + mlt_properties_set_int(properties, "instances", plugin->copies); + } else { + mlt_log_error(properties, "failed to load plugin %s\n", plugin_id); + } + } + + return lv2rack; +} + +static int producer_get_audio(mlt_frame frame, + void **buffer, + mlt_audio_format *format, + int *frequency, + int *channels, + int *samples) +{ + // Get the producer service + mlt_producer producer = mlt_properties_get_data(MLT_FRAME_PROPERTIES(frame), + "_producer_lv2", + NULL); + mlt_properties producer_properties = MLT_PRODUCER_PROPERTIES(producer); + int size = 0; + LADSPA_Data **output_buffers = NULL; + int i = 0; + + // Initialize LV2 if needed + lv2_rack_t *lv2rack = mlt_properties_get_data(producer_properties, "_lv2rack", NULL); + if (!lv2rack) { + lv2_sample_rate = *frequency; // global inside lv2_rack + lv2rack = initialise_lv2_rack(producer_properties, *channels); + } + + if (lv2rack) { + // Correct the returns if necessary + *samples = *samples <= 0 ? 1920 : *samples; + *channels = *channels <= 0 ? 2 : *channels; + *frequency = *frequency <= 0 ? 48000 : *frequency; + *format = mlt_audio_float; + + if (lv2rack->procinfo && lv2rack->procinfo->chain) { + lv2_plugin_t *plugin = lv2rack->procinfo->chain; + LADSPA_Data value; + int index, c; + mlt_position position = mlt_frame_get_position(frame); + mlt_position length = mlt_producer_get_length(producer); + + for (index = 0; index < plugin->desc->control_port_count; index++) { + // Apply the control port values + char key[20]; + value = plugin->desc->def_values[plugin->desc->control_port_indicies[index]]; + snprintf(key, sizeof(key), "%d", (int) plugin->desc->control_port_indicies[index]); + + if (mlt_properties_get(producer_properties, key)) + value = mlt_properties_anim_get_double(producer_properties, + key, + position, + length); + for (c = 0; c < plugin->copies; c++) + plugin->holders[c].control_memory[index] = value; + } + } + + // Calculate the size of the buffer + size = *samples * *channels * sizeof(float); + + // Allocate the buffer + *buffer = mlt_pool_alloc(size); + + // Initialize the LV2 output buffer. + output_buffers = mlt_pool_alloc(sizeof(LADSPA_Data *) * *channels); + for (i = 0; i < *channels; i++) { + output_buffers[i] = (LADSPA_Data *) *buffer + i * *samples; + } + + // Do LV2 processing + process_lv2(lv2rack->procinfo, *samples, NULL, output_buffers); + mlt_pool_release(output_buffers); + + // Set the buffer for destruction + mlt_frame_set_audio(frame, *buffer, *format, size, mlt_pool_release); + + char *plugin_id = NULL; + plugin_id = mlt_properties_get (producer_properties, "_pluginid"); + + if (lv2rack && lv2rack->procinfo && lv2rack->procinfo->chain + && plugin_id) { + lv2_plugin_t *plugin = lv2rack->procinfo->chain; + LADSPA_Data value; + int i, c; + for (i = 0; i < plugin->desc->status_port_count; i++) { + // read the status port values + char key[20]; + int p = plugin->desc->status_port_indicies[i]; + for (c = 0; c < plugin->copies; c++) { + snprintf(key, sizeof(key), "%d[%d]", p, c); + value = plugin->holders[c].status_memory[i]; + mlt_properties_set_double(producer_properties, key, value); + } + } + } + } + + return 0; +} + +static int producer_get_frame(mlt_producer producer, mlt_frame_ptr frame, int index) +{ + // Generate a frame + *frame = mlt_frame_init(MLT_PRODUCER_SERVICE(producer)); + + // Check that we created a frame and initialize it + if (*frame != NULL) { + // Obtain properties of frame + mlt_properties frame_properties = MLT_FRAME_PROPERTIES(*frame); + + // Update timecode on the frame we're creating + mlt_frame_set_position(*frame, mlt_producer_position(producer)); + + // Save the producer to be used in get_audio + mlt_properties_set_data(frame_properties, "_producer_lv2", producer, 0, NULL, NULL); + + // Push the get_audio method + mlt_frame_push_audio(*frame, producer_get_audio); + } + + // Calculate the next time code + mlt_producer_prepare_next(producer); + + return 0; +} + +/** Destructor for the producer. +*/ + +static void producer_close(mlt_producer producer) +{ + producer->close = NULL; + mlt_producer_close(producer); + free(producer); +} + +/** Constructor for the producer. +*/ + +mlt_producer producer_lv2_init(mlt_profile profile, + mlt_service_type type, + const char *id, + char *arg) +{ + // Create a new producer object + mlt_producer producer = mlt_producer_new(profile); + + if (producer != NULL) { + mlt_properties properties = MLT_PRODUCER_PROPERTIES(producer); + + producer->get_frame = producer_get_frame; + producer->close = (mlt_destructor) producer_close; + + // Save the plugin ID. + if (!strncmp(id, "lv2.", 4)) { + mlt_properties_set(properties, "_pluginid", id + 4); + } + + } + return producer; +} diff --git a/src/modules/jackrack/producer_lv2.yml b/src/modules/jackrack/producer_lv2.yml new file mode 100644 index 000000000..2cb19b490 --- /dev/null +++ b/src/modules/jackrack/producer_lv2.yml @@ -0,0 +1,13 @@ +schema_version: 7.0 +type: producer +identifier: lv2 +title: LV2 +version: 1 +license: GPLv2 +language: en +tags: + - Audio +description: Generate audio using LV2 plugins. +notes: > + Automatically adapts to the number of channels and sampling rate of the consumer. +