From a19e8ad406ae690a8df3ceeda75639e85ce9aa78 Mon Sep 17 00:00:00 2001 From: Dan Dennedy Date: Wed, 14 Aug 2024 13:37:44 -0700 Subject: [PATCH] support VST2 plugins in jackrack module (#1013) --- CMakeLists.txt | 2 + src/modules/jackrack/CMakeLists.txt | 18 +- src/modules/jackrack/factory.c | 218 +++++++ src/modules/jackrack/filter_vst2.c | 265 ++++++++ src/modules/jackrack/filter_vst2.yml | 15 + src/modules/jackrack/plugin_desc.c | 342 ++++++++++- src/modules/jackrack/plugin_desc.h | 72 +++ src/modules/jackrack/plugin_mgr.c | 467 ++++++++++++++ src/modules/jackrack/plugin_mgr.h | 23 + src/modules/jackrack/producer_vst2.c | 240 ++++++++ src/modules/jackrack/producer_vst2.yml | 13 + src/modules/jackrack/vestige.h | 332 ++++++++++ src/modules/jackrack/vst2_context.c | 168 +++++ src/modules/jackrack/vst2_context.h | 74 +++ src/modules/jackrack/vst2_plugin.c | 601 ++++++++++++++++++ src/modules/jackrack/vst2_plugin.h | 99 +++ src/modules/jackrack/vst2_plugin_settings.c | 366 +++++++++++ src/modules/jackrack/vst2_plugin_settings.h | 83 +++ src/modules/jackrack/vst2_process.c | 639 ++++++++++++++++++++ src/modules/jackrack/vst2_process.h | 88 +++ 20 files changed, 4123 insertions(+), 2 deletions(-) create mode 100644 src/modules/jackrack/filter_vst2.c create mode 100644 src/modules/jackrack/filter_vst2.yml create mode 100644 src/modules/jackrack/producer_vst2.c create mode 100644 src/modules/jackrack/producer_vst2.yml create mode 100644 src/modules/jackrack/vestige.h create mode 100644 src/modules/jackrack/vst2_context.c create mode 100644 src/modules/jackrack/vst2_context.h create mode 100644 src/modules/jackrack/vst2_plugin.c create mode 100644 src/modules/jackrack/vst2_plugin.h create mode 100644 src/modules/jackrack/vst2_plugin_settings.c create mode 100644 src/modules/jackrack/vst2_plugin_settings.h create mode 100644 src/modules/jackrack/vst2_process.c create mode 100644 src/modules/jackrack/vst2_process.h diff --git a/CMakeLists.txt b/CMakeLists.txt index ef587bcae..4409ca7df 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -22,6 +22,7 @@ option(MOD_GLAXNIMATE "Enable Glaxnimate module (Qt5)" OFF) option(MOD_GLAXNIMATE_QT6 "Enable Glaxnimate module (Qt6)" OFF) option(MOD_JACKRACK "Enable JACK Rack module" ON) option(USE_LV2 "Enable LV2 features" ON) +option(USE_VST2 "Enable LV2 features" ON) option(MOD_KDENLIVE "Enable Kdenlive module" ON) option(MOD_NDI "Enable NDI module" OFF) option(MOD_NORMALIZE "Enable Normalize module (GPL)" ON) @@ -641,5 +642,6 @@ 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 "") +add_feature_info("vst2: VST2 Plugins support" USE_VST2 "") 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 0d7beb1e9..f717e27d2 100644 --- a/src/modules/jackrack/CMakeLists.txt +++ b/src/modules/jackrack/CMakeLists.txt @@ -16,11 +16,16 @@ if(TARGET JACK::JACK) install(FILES consumer_jack.yml DESTINATION ${MLT_INSTALL_DATA_DIR}/jackrack) endif() -if(USE_LV2) +if(USE_LV2 AND GPL) target_compile_definitions(mltjackrack PRIVATE WITH_LV2) install(FILES filter_lv2.yml producer_lv2.yml DESTINATION ${MLT_INSTALL_DATA_DIR}/jackrack) endif() +if(USE_VST2 AND GPL) + target_compile_definitions(mltjackrack PRIVATE WITH_VST2) + install(FILES filter_vst2.yml producer_vst2.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 @@ -58,6 +63,17 @@ if(GPL AND TARGET PkgConfig::xml AND TARGET PkgConfig::glib AND ladspa_h_FOUND) install(FILES filter_lv2.yml producer_lv2.yml DESTINATION ${MLT_INSTALL_DATA_DIR}/jackrack) endif() + if(USE_VST2) + target_sources(mltjackrack PRIVATE + filter_vst2.c producer_vst2.c + vst2_context.c vst2_context.h + vst2_plugin.c vst2_plugin.h + vst2_process.c vst2_process.h + vst2_plugin_settings.c vst2_plugin_settings.h + vestige.h) + install(FILES filter_vst2.yml producer_vst2.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 89c66eeff..038aecc9d 100644 --- a/src/modules/jackrack/factory.c +++ b/src/modules/jackrack/factory.c @@ -75,12 +75,31 @@ extern mlt_producer producer_lv2_init(mlt_profile profile, char *arg); #endif +#ifdef WITH_VST2 + +#include "vestige.h" + +extern mlt_filter filter_vst2_init(mlt_profile profile, + mlt_service_type type, + const char *id, + char *arg); +extern mlt_producer producer_vst2_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 +#ifdef WITH_VST2 +vst2_mgr_t *g_vst2_plugin_mgr = NULL; +#endif + static void add_port_to_metadata(mlt_properties p, plugin_desc_t *desc, int j) { LADSPA_Data sample_rate = 48000; @@ -441,6 +460,181 @@ static mlt_properties lv2_metadata(mlt_service_type type, const char *id, char * #endif +#ifdef WITH_VST2 + +static void vst2_add_port_to_metadata(mlt_properties p, vst2_plugin_desc_t *desc, int j) +{ + LADSPA_Data sample_rate = 48000; + 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", + vst2_plugin_desc_get_default_control_value( + desc, + j - (desc->effect->numInputs + desc->effect->numOutputs), + sample_rate)); + } else if (LADSPA_IS_HINT_TOGGLED(hint_descriptor)) { + mlt_properties_set(p, "type", "boolean"); + mlt_properties_set_int(p, + "default", + vst2_plugin_desc_get_default_control_value( + desc, + j - (desc->effect->numInputs + desc->effect->numOutputs), + sample_rate)); + } else { + mlt_properties_set(p, "type", "float"); + mlt_properties_set_double(p, + "default", + vst2_plugin_desc_get_default_control_value( + desc, + j - (desc->effect->numInputs + desc->effect->numOutputs), + sample_rate)); + mlt_properties_set_double(p, "minimum", 0.0); + mlt_properties_set_double(p, "maximum", 1.0); + } + /* set upper and lower, possibly adjusted to the sample rate */ + if (LADSPA_IS_HINT_BOUNDED_BELOW(hint_descriptor)) { + LADSPA_Data lower = desc->port_range_hints[j].LowerBound; + if (LADSPA_IS_HINT_SAMPLE_RATE(hint_descriptor)) + lower *= sample_rate; + if (LADSPA_IS_HINT_LOGARITHMIC(hint_descriptor)) { + if (lower < FLT_EPSILON) + lower = FLT_EPSILON; + } + mlt_properties_set_double(p, "minimum", lower); + } + if (LADSPA_IS_HINT_BOUNDED_ABOVE(hint_descriptor)) { + LADSPA_Data upper = desc->port_range_hints[j].UpperBound; + if (LADSPA_IS_HINT_SAMPLE_RATE(hint_descriptor)) + upper *= sample_rate; + mlt_properties_set_double(p, "maximum", upper); + } + 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 vst2_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/jackrack/%s", + mlt_environment("MLT_DATA"), + strncmp(id, "vst2.", 5) ? data : "filter_vst2.yml"); + } else { + snprintf(file, + PATH_MAX, + "%s/jackrack/%s", + mlt_environment("MLT_DATA"), + strncmp(id, "vst2.", 5) ? data : "producer_vst2.yml"); + } + mlt_properties result = mlt_properties_parse_yaml(file); + + if (!strncmp(id, "vst2.", 5)) { + // Annotate the yaml properties with ladspa control port info. + vst2_plugin_desc_t *desc = vst2_mgr_get_any_desc(g_vst2_plugin_mgr, + strtol(id + 5, NULL, 10)); + + 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", "VST2 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 - (desc->effect->numInputs + desc->effect->numOutputs)); + mlt_properties_set(p, "identifier", key); + vst2_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); + vst2_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 @@ -499,6 +693,30 @@ MLT_REPOSITORY } #endif +#ifdef WITH_VST2 + + g_vst2_plugin_mgr = vst2_mgr_new(); + + for (list = g_vst2_plugin_mgr->all_plugins; list; list = g_slist_next(list)) { + vst2_plugin_desc_t *desc = (vst2_plugin_desc_t *) list->data; + char *s = malloc(strlen("vst2.") + 21); + + sprintf(s, "vst2.%lu", desc->id); + + if (desc->has_input) { + MLT_REGISTER(mlt_service_filter_type, s, filter_vst2_init); + MLT_REGISTER_METADATA(mlt_service_filter_type, s, vst2_metadata, NULL); + } else { + MLT_REGISTER(mlt_service_producer_type, s, producer_vst2_init); + MLT_REGISTER_METADATA(mlt_service_producer_type, s, vst2_metadata, NULL); + } + + free(s); + } + mlt_factory_register_for_clean_up(g_vst2_plugin_mgr, (mlt_destructor) vst2_mgr_destroy); + +#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_vst2.c b/src/modules/jackrack/filter_vst2.c new file mode 100644 index 000000000..1cb482cc3 --- /dev/null +++ b/src/modules/jackrack/filter_vst2.c @@ -0,0 +1,265 @@ +/* + * filter_vst2.c -- filter audio through VST2 plugins + * Copyright (C) 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 + * 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 "vst2_context.h" + +#define BUFFER_LEN (10000) +#define MAX_SAMPLE_COUNT (4096) + +static vst2_context_t *initialise_vst2_context(mlt_properties properties, int channels) +{ + vst2_context_t *vst2context = NULL; + char *resource = mlt_properties_get(properties, "resource"); + if (!resource && mlt_properties_get(properties, "src")) + resource = mlt_properties_get(properties, "src"); + + // Start Vst2context + if (resource || mlt_properties_get_int64(properties, "_pluginid")) { + // Create Vst2context without Jack client name so that it only uses VST2 + vst2context = vst2_context_new(NULL, channels); + + mlt_properties_set_data(properties, + "vst2context", + vst2context, + 0, + (mlt_destructor) vst2_context_destroy, + NULL); + + if (mlt_properties_get_int64(properties, "_pluginid")) { + // Load one VST2 plugin by its UniqueID + unsigned long id = mlt_properties_get_int64(properties, "_pluginid"); + vst2_plugin_desc_t *desc = vst2_mgr_get_any_desc(vst2context->plugin_mgr, id); + + vst2_plugin_t *plugin; + if (desc && (plugin = vst2_context_instantiate_plugin(vst2context, desc))) { + plugin->enabled = TRUE; + vst2_process_add_plugin(vst2context->procinfo, plugin); + mlt_properties_set_int(properties, "instances", plugin->copies); + } else { + mlt_log_error(properties, "failed to load plugin %lu\n", id); + return vst2context; + } + + 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); + vst2context = initialise_vst2_context(properties, request_channels); + } else { + mlt_log_error(properties, "Invalid plugin configuration: %lu\n", id); + return vst2context; + } + } + + if (plugin && plugin->desc && plugin->copies) + mlt_log_debug(properties, + "Plugin Initialized. Channels: %lu\tCopies: %d\tTotal: %lu\n", + plugin->desc->channels, + plugin->copies, + vst2context->channels); + } + } + return vst2context; +} + +/** Get the audio. +*/ + +static int vst2_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, + "vst2context", + NULL, + 0, + (mlt_destructor) NULL, + NULL); + } + mlt_properties_set_int(filter_properties, "_prev_channels", *channels); + } + + // Initialise VST2 if needed + vst2_context_t *vst2context = mlt_properties_get_data(filter_properties, "vst2context", NULL); + if (vst2context == NULL) { + vst2_sample_rate = *frequency; // global inside jack_rack + vst2context = initialise_vst2_context(filter_properties, *channels); + } + + if (vst2context && vst2context->procinfo && vst2context->procinfo->chain + && mlt_properties_get_int64(filter_properties, "_pluginid")) { + vst2_plugin_t *plugin = vst2context->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 < vst2context->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, vst2context->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 = vst2_plugin_desc_get_default_control_value(plugin->desc, i, vst2_sample_rate); + snprintf(key, sizeof(key), "%d", 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++) { + if (plugin->holders[c].control_memory[i] != value) { + plugin->holders[c].control_memory[i] = value; + plugin->holders[c] + .effect->setParameter(plugin->holders[c].effect, + plugin->desc->control_port_indicies[i] + - (plugin->holders[c].effect->numInputs + + plugin->holders[c].effect->numOutputs), + plugin->holders[c].control_memory[i]); + } + } + } + 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 < vst2context->channels; c++) + plugin->wet_dry_values[c] = value; + } + + // Configure the buffers + LADSPA_Data **input_buffers = mlt_pool_alloc(sizeof(LADSPA_Data *) * vst2context->channels); + LADSPA_Data **output_buffers = mlt_pool_alloc(sizeof(LADSPA_Data *) * vst2context->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 < vst2context->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 VST2 processing + error = process_vst2(vst2context->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, vst2_get_audio); + } + + return frame; +} + +/** Constructor for the filter. +*/ + +mlt_filter filter_vst2_init(mlt_profile profile, mlt_service_type type, const char *id, char *arg) +{ + mlt_filter this = mlt_filter_new(); + if (this != NULL) { + mlt_properties properties = MLT_FILTER_PROPERTIES(this); + this->process = filter_process; + mlt_properties_set(properties, "resource", arg); + if (!strncmp(id, "vst2.", 5)) + mlt_properties_set(properties, "_pluginid", id + 5); + } + return this; +} diff --git a/src/modules/jackrack/filter_vst2.yml b/src/modules/jackrack/filter_vst2.yml new file mode 100644 index 000000000..ff0652eab --- /dev/null +++ b/src/modules/jackrack/filter_vst2.yml @@ -0,0 +1,15 @@ +schema_version: 7.0 +type: filter +identifier: vst2 +title: VST2 +version: 1 +license: GPLv2 +language: en +url: https://www.steinberg.net/en/company/technologies.html +creator: mr.fantastic +tags: + - Audio +description: Process audio using VST2 plugins. +notes: > + Automatically adapts to the number of channels and sampling rate of the consumer. + diff --git a/src/modules/jackrack/plugin_desc.c b/src/modules/jackrack/plugin_desc.c index b16b5f6ed..3511df4d1 100644 --- a/src/modules/jackrack/plugin_desc.c +++ b/src/modules/jackrack/plugin_desc.c @@ -22,11 +22,11 @@ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ +#include #include #include #include -#include "plugin.h" #include "plugin_desc.h" #define set_string_property(property, value) \ @@ -741,4 +741,344 @@ gint lv2_plugin_desc_get_copies(lv2_plugin_desc_t *pd, unsigned long rack_channe } #endif +#ifdef WITH_VST2 + +void vst2_plugin_desc_set_ports(vst2_plugin_desc_t *pd, + unsigned long port_count, + const LADSPA_PortDescriptor *port_descriptors, + const LADSPA_PortRangeHint *port_range_hints, + const char *const *port_names); + +static void vst2_plugin_desc_init(vst2_plugin_desc_t *pd) +{ + pd->object_file = 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; +} + +static void vst2_plugin_desc_free_ports(vst2_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 vst2_plugin_desc_free(vst2_plugin_desc_t *pd) +{ + vst2_plugin_desc_set_object_file(pd, NULL); + vst2_plugin_desc_set_name(pd, NULL); + vst2_plugin_desc_set_maker(pd, NULL); + vst2_plugin_desc_free_ports(pd); +} + +vst2_plugin_desc_t *vst2_plugin_desc_new() +{ + vst2_plugin_desc_t *pd; + pd = g_malloc(sizeof(vst2_plugin_desc_t)); + vst2_plugin_desc_init(pd); + return pd; +} + +vst2_plugin_desc_t *vst2_plugin_desc_new_with_descriptor(const char *object_file, + unsigned long index, + AEffect *effect) +{ + vst2_plugin_desc_t *pd; + pd = vst2_plugin_desc_new(); + + vst2_plugin_desc_set_object_file(pd, object_file); + vst2_plugin_desc_set_index(pd, index); + vst2_plugin_desc_set_id(pd, effect->uniqueID); + + static char strBuf[1024]; + effect->dispatcher (effect, effGetEffectName, 0, 0, strBuf, 0); + vst2_plugin_desc_set_name(pd, strBuf); + + effect->dispatcher (effect, effGetVendorString, 0, 0, strBuf, 0); + vst2_plugin_desc_set_maker(pd, strBuf); + + int PortCount = effect->numInputs + effect->numOutputs + effect->numParams; + char **PortNames = calloc(PortCount, sizeof(char *)); + LADSPA_PortDescriptor *port_descriptors = calloc(PortCount, sizeof(LADSPA_PortDescriptor)); + LADSPA_PortRangeHint *PortRangeHints = calloc(PortCount, sizeof(LADSPA_PortRangeHint)); + + pd->def_values = calloc(PortCount, sizeof(LADSPA_Data)); + + int j; + for (j = 0; j < effect->numInputs; ++j) + { + strBuf[0] = '\0'; + sprintf (strBuf, "Input %d", j); + PortNames[j] = strdup(strBuf); + port_descriptors[j] |= LADSPA_PORT_AUDIO; + port_descriptors[j] |= LADSPA_PORT_INPUT; + PortRangeHints[j].LowerBound = 0.0f; + PortRangeHints[j].UpperBound = 1.0f; + } + + for (; j < effect->numInputs+effect->numOutputs; ++j) + { + strBuf[0] = '\0'; + sprintf (strBuf, "Output %d", j); + PortNames[j] = strdup(strBuf); + port_descriptors[j] |= LADSPA_PORT_AUDIO; + port_descriptors[j] |= LADSPA_PORT_OUTPUT; + PortRangeHints[j].LowerBound = 0.0f; + PortRangeHints[j].UpperBound = 1.0f; + } + + + int i; + for (i = j; i < PortCount; ++i) { + strBuf[0] = '\0'; + effect->dispatcher(effect, effGetParamName, i-(effect->numInputs+effect->numOutputs), 0, strBuf, 0); + PortNames[i] = strdup(strBuf); + port_descriptors[i] |= LADSPA_PORT_CONTROL; + PortRangeHints[i].LowerBound = 0.0f; + PortRangeHints[i].UpperBound = 1.0f; + } + + vst2_plugin_desc_set_ports(pd, + PortCount, + port_descriptors, + PortRangeHints, + (const char *const *) PortNames); + + pd->effect = effect; + pd->rt = TRUE; + + return pd; +} + +void vst2_plugin_desc_destroy(vst2_plugin_desc_t *pd) +{ + vst2_plugin_desc_free(pd); + g_free(pd); +} + +void vst2_plugin_desc_set_object_file(vst2_plugin_desc_t *pd, const char *object_file) +{ + set_string_property(pd->object_file, object_file); +} + +void vst2_plugin_desc_set_index(vst2_plugin_desc_t *pd, unsigned long index) +{ + pd->index = index; +} + +void vst2_plugin_desc_set_id(vst2_plugin_desc_t *pd, unsigned long id) +{ + pd->id = id; +} + +void vst2_plugin_desc_set_name(vst2_plugin_desc_t *pd, const char *name) +{ + set_string_property(pd->name, name); +} + +void vst2_plugin_desc_set_maker(vst2_plugin_desc_t *pd, const char *maker) +{ + set_string_property(pd->maker, maker); +} + +void vst2_plugin_desc_set_properties(vst2_plugin_desc_t *pd, LADSPA_Properties properties) +{ + pd->properties = properties; +} + +static void vst2_plugin_desc_add_audio_port_index(unsigned long **indices, + unsigned long *current_port_count, + unsigned long index) +{ + (*current_port_count)++; + + if (*current_port_count == 0) + *indices = g_malloc(sizeof(unsigned long) * *current_port_count); + else + *indices = g_realloc(*indices, sizeof(unsigned long) * *current_port_count); + + (*indices)[*current_port_count - 1] = index; +} + +static void vst2_plugin_desc_set_port_counts(vst2_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_AUDIO(pd->port_descriptors[i])) { + if (LADSPA_IS_PORT_INPUT(pd->port_descriptors[i])) + { + vst2_plugin_desc_add_audio_port_index(&pd->audio_input_port_indicies, &icount, i); + } + else + { + vst2_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); + } +} + +void vst2_plugin_desc_set_ports(vst2_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; + + vst2_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]); + + vst2_plugin_desc_set_port_counts(pd); +} + +LADSPA_Data vst2_plugin_desc_get_default_control_value(vst2_plugin_desc_t *pd, + unsigned long port_index, + guint32 sample_rate) +{ + return pd->effect->getParameter(pd->effect, port_index); +} + +LADSPA_Data vst2_plugin_desc_change_control_value(vst2_plugin_desc_t *pd, + unsigned long control_index, + LADSPA_Data value, + guint32 old_sample_rate, + guint32 new_sample_rate) +{ + if (LADSPA_IS_HINT_SAMPLE_RATE(pd->port_range_hints[control_index].HintDescriptor)) { + LADSPA_Data old_sr, new_sr; + + old_sr = (LADSPA_Data) old_sample_rate; + new_sr = (LADSPA_Data) new_sample_rate; + + value /= old_sr; + value *= new_sr; + } + + return value; +} + +gint vst2_plugin_desc_get_copies(vst2_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 c3b78e5c1..938001bee 100644 --- a/src/modules/jackrack/plugin_desc.h +++ b/src/modules/jackrack/plugin_desc.h @@ -50,6 +50,12 @@ #include #endif +#ifdef WITH_VST2 + +#include "vestige.h" + +#endif + typedef struct _plugin_desc plugin_desc_t; struct _plugin_desc @@ -168,4 +174,70 @@ LADSPA_Data lv2_plugin_desc_change_control_value( gint lv2_plugin_desc_get_copies(lv2_plugin_desc_t *pd, unsigned long rack_channels); #endif +#ifdef WITH_VST2 + +typedef struct _vst2_plugin_desc vst2_plugin_desc_t; + +struct _vst2_plugin_desc +{ + char *object_file; + unsigned long index; + unsigned long id; + char *name; + char *maker; + LADSPA_Properties properties; + AEffect *effect; + 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; + + gboolean has_input; +}; + +vst2_plugin_desc_t *vst2_plugin_desc_new(); +vst2_plugin_desc_t *vst2_plugin_desc_new_with_descriptor(const char *object_file, + unsigned long index, + AEffect *descriptor); +void vst2_plugin_desc_destroy(vst2_plugin_desc_t *pd); + +void vst2_plugin_desc_set_object_file(vst2_plugin_desc_t *pd, const char *object_file); +void vst2_plugin_desc_set_index(vst2_plugin_desc_t *pd, unsigned long index); +void vst2_plugin_desc_set_id(vst2_plugin_desc_t *pd, unsigned long id); +void vst2_plugin_desc_set_name(vst2_plugin_desc_t *pd, const char *name); +void vst2_plugin_desc_set_maker(vst2_plugin_desc_t *pd, const char *maker); +void vst2_plugin_desc_set_properties(vst2_plugin_desc_t *pd, LADSPA_Properties properties); + +struct _vst2 *vst2_plugin_desc_instantiate(vst2_plugin_desc_t *pd); + +LADSPA_Data vst2_plugin_desc_get_default_control_value(vst2_plugin_desc_t *pd, + unsigned long port_index, + guint32 sample_rate); +LADSPA_Data vst2_plugin_desc_change_control_value( + vst2_plugin_desc_t *, unsigned long, LADSPA_Data, guint32, guint32); + +gint vst2_plugin_desc_get_copies(vst2_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 24391770e..f43f7dc78 100644 --- a/src/modules/jackrack/plugin_mgr.c +++ b/src/modules/jackrack/plugin_mgr.c @@ -113,6 +113,84 @@ const LV2_Feature *features[] = #endif +#ifdef WITH_VST2 + +#include "vestige.h" + +#define audioMasterGetOutputSpeakerArrangement audioMasterGetSpeakerArrangement +#define effFlagsProgramChunks (1 << 5) +#define effSetProgramName 4 +#define effGetParamLabel 6 +#define effGetParamDisplay 7 +#define effGetVu 9 +#define effEditDraw 16 +#define effEditMouse 17 +#define effEditKey 18 +#define effEditSleep 21 +#define effIdentify 22 +#define effGetChunk 23 +#define effSetChunk 24 +#define effCanBeAutomated 26 +#define effString2Parameter 27 +#define effGetNumProgramCategories 28 +#define effGetProgramNameIndexed 29 +#define effCopyProgram 30 +#define effConnectInput 31 +#define effConnectOutput 32 +#define effGetInputProperties 33 +#define effGetOutputProperties 34 +#define effGetCurrentPosition 36 +#define effGetDestinationBuffer 37 +#define effOfflineNotify 38 +#define effOfflinePrepare 39 +#define effOfflineRun 40 +#define effProcessVarIo 41 +#define effSetSpeakerArrangement 42 +#define effSetBlockSizeAndSampleRate 43 +#define effSetBypass 44 +#define effGetErrorText 46 +#define effVendorSpecific 50 +#define effGetTailSize 52 +#define effGetIcon 54 +#define effSetViewPosition 55 +#define effKeysRequired 57 +#define effEditKeyDown 59 +#define effEditKeyUp 60 +#define effSetEditKnobMode 61 +#define effGetMidiProgramName 62 +#define effGetCurrentMidiProgram 63 +#define effGetMidiProgramCategory 64 +#define effHasMidiProgramsChanged 65 +#define effGetMidiKeyName 66 +#define effGetSpeakerArrangement 69 +#define effSetTotalSampleToProcess 73 +#define effSetPanLaw 74 +#define effBeginLoadBank 75 +#define effBeginLoadProgram 76 +#define effSetProcessPrecision 77 +#define effGetNumMidiInputChannels 78 +#define effGetNumMidiOutputChannels 79 +#define kVstAutomationOff 1 +#define kVstAutomationReadWrite 4 +#define kVstProcessLevelUnknown 0 +#define kVstProcessLevelUser 1 +#define kVstProcessLevelRealtime 2 +#define kVstProcessLevelOffline 4 +#define kVstProcessPrecision32 0 +#define kVstVersion 2400 + +#if defined(CARLA_OS_WIN) && defined(__cdecl) +# define VSTCALLBACK __cdecl +#else +# define VSTCALLBACK +#endif + +typedef AEffect* (*VST_Function)(audioMasterCallback); + +#define kVstVersion 2400 + +#endif + static gboolean plugin_is_valid(const LADSPA_Descriptor *descriptor) { unsigned long i; @@ -617,4 +695,393 @@ lv2_plugin_desc_t *lv2_mgr_get_any_desc(lv2_mgr_t *plugin_mgr, char *id) } #endif +#ifdef WITH_VST2 + +static gboolean vst2_is_valid(const AEffect *effect) +{ + /* unsigned long icount = 0; */ + unsigned long ocount = 0; + + /* icount = effect->numInputs; */ + ocount = effect->numOutputs; + + if (effect->magic == kEffectMagic) + return FALSE; + + if (ocount == 0) + return FALSE; + + return TRUE; +} + +static intptr_t mlt_vst_hostCanDo(const char* const feature) +{ + mlt_log_info(NULL, "mlt_vst_hostCanDo(\"%s\")", feature); + + if (strcmp(feature, "supplyIdle") == 0) + return 1; + if (strcmp(feature, "sendVstEvents") == 0) + return 1; + if (strcmp(feature, "sendVstMidiEvent") == 0) + return 1; + if (strcmp(feature, "sendVstMidiEventFlagIsRealtime") == 0) + return 1; + if (strcmp(feature, "sendVstTimeInfo") == 0) + return 1; + if (strcmp(feature, "receiveVstEvents") == 0) + return 1; + if (strcmp(feature, "receiveVstMidiEvent") == 0) + return 1; + if (strcmp(feature, "receiveVstTimeInfo") == 0) + return -1; + if (strcmp(feature, "reportConnectionChanges") == 0) + return -1; + if (strcmp(feature, "acceptIOChanges") == 0) + return 1; + if (strcmp(feature, "sizeWindow") == 0) + return 1; + if (strcmp(feature, "offline") == 0) + return -1; + if (strcmp(feature, "openFileSelector") == 0) + return -1; + if (strcmp(feature, "closeFileSelector") == 0) + return -1; + if (strcmp(feature, "startStopProcess") == 0) + return 1; + if (strcmp(feature, "supportShell") == 0) + return 1; + if (strcmp(feature, "shellCategory") == 0) + return 1; + if (strcmp(feature, "NIMKPIVendorSpecificCallbacks") == 0) + return -1; + + // unimplemented + mlt_log_error(NULL, "mlt_vst_hostCanDo(\"%s\") - unknown feature", feature); + return 0; +} + +static intptr_t VSTCALLBACK +mlt_vst_audioMasterCallback(AEffect* effect, int32_t opcode, int32_t index, intptr_t value, void* ptr, float opt) +{ + switch (opcode) + { + case audioMasterAutomate: + return 1; + + case audioMasterVersion: + return kVstVersion; + + case audioMasterCurrentId: + return 0; + + case audioMasterGetVendorString: + strcpy((char*)ptr, "MRF"); + return 1; + + case audioMasterGetProductString: + strcpy((char*)ptr, "No Organization"); + return 1; + + case audioMasterGetVendorVersion: + return 0x01; + + case audioMasterCanDo: + return mlt_vst_hostCanDo((const char*)ptr); + + case audioMasterGetLanguage: + return kVstLangEnglish; + } + + return 0; +} + +static void vst2_mgr_get_object_file_plugins(vst2_mgr_t *vst2_mgr, const char *filename) +{ + const char *dlerr; + void *dl_handle; + VST_Function vstFn = NULL; + AEffect *effect = NULL; + unsigned long vst2_index; + vst2_plugin_desc_t *desc, *other_desc = NULL; + GSList *list; + gboolean exists; + /* int err; */ + + /* open the object file */ + dl_handle = dlopen(filename, RTLD_LAZY); + if (!dl_handle) { + mlt_log_info(NULL, + "%s: error opening shared object file '%s': %s\n", + __FUNCTION__, + filename, + dlerror()); + return; + } + + /* get the get_descriptor function */ + dlerror(); /* clear the error report */ + + vstFn = (VST_Function) dlsym (dl_handle, "VSTPluginMain"); + if (vstFn == NULL) + vstFn = (VST_Function) dlsym (dl_handle, "main_macho"); + if (vstFn == NULL) + vstFn = (VST_Function) dlsym (dl_handle, "main"); + + if (vstFn == NULL) + return; + + effect = vstFn(mlt_vst_audioMasterCallback); + + dlerr = dlerror(); + if (dlerr) { + mlt_log_info(NULL, + "%s: error finding {VSTPluginMain, main_macho, main} symbol in object file '%s': %s\n", + __FUNCTION__, + filename, + dlerr); + dlclose(dl_handle); + return; + } + + + vst2_index = 0; + /* while ((descriptor = get_descriptor(vst2_index))) */ + if (effect != NULL) + { + if (!vst2_is_valid(effect)) { + vst2_index++; + //continue; + } + + /* check it doesn't already exist */ + exists = FALSE; + for (list = vst2_mgr->all_plugins; list; list = g_slist_next(list)) { + other_desc = (vst2_plugin_desc_t *) list->data; + + if (other_desc->id == effect->uniqueID) { + exists = TRUE; + break; + } + } + + if (exists) { + mlt_log_info(NULL, + "Plugin %d exists in both '%s' and '%s'; using version in '%s'\n", + effect->uniqueID, + other_desc->object_file, + filename, + other_desc->object_file); + vst2_index++; + //continue; + } + + desc = vst2_plugin_desc_new_with_descriptor(filename, vst2_index, effect); + vst2_mgr->all_plugins = g_slist_append(vst2_mgr->all_plugins, desc); + vst2_index++; + vst2_mgr->plugin_count++; + + /* print in the splash screen */ + /* mlt_log_verbose( NULL, "Loaded plugin '%s'\n", desc->name); */ + } + + /* WIP temporarily disabled */ + /* err = dlclose(dl_handle); */ + /* if (err) { + mlt_log_warning(NULL, + "%s: error closing object file '%s': %s\n", + __FUNCTION__, + filename, + dlerror()); + } */ +} + + +static gint vst2_mgr_sort(gconstpointer a, gconstpointer b) +{ + const vst2_plugin_desc_t *da; + const vst2_plugin_desc_t *db; + da = (const vst2_plugin_desc_t *) a; + db = (const vst2_plugin_desc_t *) b; + + return strcasecmp(da->name, db->name); +} + +static void vst2_mgr_get_dir_plugins(vst2_mgr_t *vst2_mgr, const char *dir) +{ + DIR *dir_stream; + struct dirent *dir_entry; + char *file_name; + int err; + size_t dirlen; + + dir_stream = opendir(dir); + if (!dir_stream) { + /* mlt_log_warning( NULL, "%s: error opening directory '%s': %s\n", + __FUNCTION__, dir, strerror (errno)); */ + return; + } + + dirlen = strlen(dir); + + while ((dir_entry = readdir(dir_stream))) { + struct stat info; + + if (strcmp(dir_entry->d_name, ".") == 0 + || mlt_properties_get(vst2_mgr->blacklist, dir_entry->d_name) + || strcmp(dir_entry->d_name, "..") == 0) + continue; + + file_name = g_malloc(dirlen + 1 + strlen(dir_entry->d_name) + 1); + + strcpy(file_name, dir); + if (file_name[dirlen - 1] == '/') + strcpy(file_name + dirlen, dir_entry->d_name); + else { + file_name[dirlen] = '/'; + strcpy(file_name + dirlen + 1, dir_entry->d_name); + } + + stat(file_name, &info); + if (S_ISDIR(info.st_mode)) + { + vst2_mgr_get_dir_plugins(vst2_mgr, file_name); + } + else { + char *ext = strrchr(file_name, '.'); + if (ext + && (strcmp(ext, ".so") == 0 || strcasecmp(ext, ".dll") == 0 + || strcmp(ext, ".dylib") == 0 || strcasecmp(ext, ".vst") == 0)) { + vst2_mgr_get_object_file_plugins(vst2_mgr, file_name); + } + } + + g_free(file_name); + } + + err = closedir(dir_stream); + if (err) + mlt_log_warning(NULL, + "%s: error closing directory '%s': %s\n", + __FUNCTION__, + dir, + strerror(errno)); +} + +static void vst2_mgr_get_path_plugins(vst2_mgr_t *vst2_mgr) +{ + char *vst_path, *dir; + + vst_path = g_strdup(getenv("VST_PATH")); +#ifdef _WIN32 + if (!vst_path) { + vst_path = malloc(strlen(mlt_environment("MLT_APPDIR")) + strlen("\\lib\\vst") + 1); + strcpy(vst_path, mlt_environment("MLT_APPDIR")); + strcat(vst_path, "\\lib\\vst"); + } +#elif defined(RELOCATABLE) +#ifdef __APPLE__ +#define VST2_SUBDIR "/PlugIns/vst" +#else +#define VST2_SUBDIR "/lib/vst" +#endif + { + vst_path = malloc(strlen(mlt_environment("MLT_APPDIR")) + strlen(VST2_SUBDIR) + 1); + strcpy(vst_path, mlt_environment("MLT_APPDIR")); + strcat(vst_path, VST2_SUBDIR); + } +#else + if (!vst_path) + vst_path = g_strdup("/usr/local/lib/vst:/usr/lib/vst:/usr/lib64/vst"); +#endif + + for (dir = strtok(vst_path, MLT_DIRLIST_DELIMITER); dir; + dir = strtok(NULL, MLT_DIRLIST_DELIMITER)) + { + vst2_mgr_get_dir_plugins(vst2_mgr, dir); + } + + g_free(vst_path); +} + +vst2_mgr_t *vst2_mgr_new() +{ + vst2_mgr_t *pm; + char dirname[PATH_MAX]; + + pm = g_malloc(sizeof(vst2_mgr_t)); + pm->all_plugins = NULL; + pm->plugins = NULL; + pm->plugin_count = 0; + + snprintf(dirname, PATH_MAX, "%s/jackrack/blacklist.txt", mlt_environment("MLT_DATA")); + pm->blacklist = mlt_properties_load(dirname); + vst2_mgr_get_path_plugins(pm); + + if (!pm->all_plugins) + mlt_log_warning( + NULL, "No VST2 plugins were found!\n\nCheck your VST_PATH environment variable.\n"); + else + pm->all_plugins = g_slist_sort(pm->all_plugins, vst2_mgr_sort); + + return pm; +} + +void vst2_mgr_destroy(vst2_mgr_t *vst2_mgr) +{ + GSList *list; + + for (list = vst2_mgr->all_plugins; list; list = g_slist_next(list)) + vst2_plugin_desc_destroy((vst2_plugin_desc_t *) list->data); + + g_slist_free(vst2_mgr->plugins); + g_slist_free(vst2_mgr->all_plugins); + mlt_properties_close(vst2_mgr->blacklist); + free(vst2_mgr); +} + +void vst2_mgr_set_plugins(vst2_mgr_t *vst2_mgr, unsigned long rack_channels) +{ + GSList *list; + vst2_plugin_desc_t *desc; + + /* clear the current plugins */ + g_slist_free(vst2_mgr->plugins); + vst2_mgr->plugins = NULL; + for (list = vst2_mgr->all_plugins; list; list = g_slist_next(list)) { + desc = (vst2_plugin_desc_t *) list->data; + + if (vst2_plugin_desc_get_copies(desc, rack_channels) != 0) + vst2_mgr->plugins = g_slist_append(vst2_mgr->plugins, desc); + } +} + +static vst2_plugin_desc_t *vst2_mgr_find_desc(vst2_mgr_t *vst2_mgr, + GSList *plugins, + unsigned long id) +{ + GSList *list; + vst2_plugin_desc_t *desc; + + for (list = plugins; list; list = g_slist_next(list)) { + desc = (vst2_plugin_desc_t *) list->data; + + if (desc->id == id) + return desc; + } + + return NULL; +} + +vst2_plugin_desc_t *vst2_mgr_get_desc(vst2_mgr_t *vst2_mgr, unsigned long id) +{ + return vst2_mgr_find_desc(vst2_mgr, vst2_mgr->plugins, id); +} + +vst2_plugin_desc_t *vst2_mgr_get_any_desc(vst2_mgr_t *vst2_mgr, unsigned long id) +{ + return vst2_mgr_find_desc(vst2_mgr, vst2_mgr->all_plugins, id); +} + +#endif + /* EOF */ diff --git a/src/modules/jackrack/plugin_mgr.h b/src/modules/jackrack/plugin_mgr.h index 83fc14a40..37c70e0a0 100644 --- a/src/modules/jackrack/plugin_mgr.h +++ b/src/modules/jackrack/plugin_mgr.h @@ -61,6 +61,19 @@ struct _lv2_mgr }; #endif +#ifdef WITH_VST2 +typedef struct _vst2_mgr vst2_mgr_t; + +struct _vst2_mgr +{ + GSList *all_plugins; + + GSList *plugins; + unsigned long plugin_count; + mlt_properties blacklist; +}; +#endif + struct _ui; plugin_mgr_t *plugin_mgr_new(); @@ -81,4 +94,14 @@ 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 +#ifdef WITH_VST2 +vst2_mgr_t *vst2_mgr_new(); +void vst2_mgr_destroy(vst2_mgr_t *plugin_mgr); + +void vst2_mgr_set_plugins(vst2_mgr_t *vst2_mgr, unsigned long rack_channels); + +vst2_plugin_desc_t *vst2_mgr_get_desc(vst2_mgr_t *vst2_mgr, unsigned long id); +vst2_plugin_desc_t *vst2_mgr_get_any_desc(vst2_mgr_t *vst2_mgr, unsigned long id);; +#endif + #endif /* __JR_PLUGIN_MANAGER_H__ */ diff --git a/src/modules/jackrack/producer_vst2.c b/src/modules/jackrack/producer_vst2.c new file mode 100644 index 000000000..e02766c15 --- /dev/null +++ b/src/modules/jackrack/producer_vst2.c @@ -0,0 +1,240 @@ +/* + * producer_vst2.c -- VST2 plugin producer + * Copyright (C) 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 + * 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 "vst2_context.h" + +#define BUFFER_LEN 10000 + +/** One-time initialization of jack rack. +*/ + +static vst2_context_t *initialise_vst2_context(mlt_properties properties, int channels) +{ + vst2_context_t *vst2context = NULL; + unsigned long plugin_id = mlt_properties_get_int64(properties, "_pluginid"); + + // Start Vst2context + if (plugin_id) { + // Create Vst2context without Jack client name so that it only uses LADSPA + vst2context = vst2_context_new(NULL, channels); + mlt_properties_set_data(properties, + "_vst2context", + vst2context, + 0, + (mlt_destructor) vst2_context_destroy, + NULL); + + // Load one LADSPA plugin by its UniqueID + vst2_plugin_desc_t *desc = vst2_mgr_get_any_desc(vst2context->plugin_mgr, plugin_id); + vst2_plugin_t *plugin; + + if (desc && (plugin = vst2_context_instantiate_plugin(vst2context, desc))) { + plugin->enabled = TRUE; + plugin->wet_dry_enabled = FALSE; + vst2_process_add_plugin(vst2context->procinfo, plugin); + mlt_properties_set_int(properties, "instances", plugin->copies); + } else { + mlt_log_error(properties, "failed to load plugin %lu\n", plugin_id); + } + } + + return vst2context; +} + +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_vst2", + NULL); + mlt_properties producer_properties = MLT_PRODUCER_PROPERTIES(producer); + int size = 0; + LADSPA_Data **output_buffers = NULL; + int i = 0; + + // Initialize VST2 if needed + vst2_context_t *vst2context = mlt_properties_get_data(producer_properties, "_vst2context", NULL); + if (!vst2context) { + vst2_sample_rate = *frequency; // global inside jack_rack + vst2context = initialise_vst2_context(producer_properties, *channels); + } + + if (vst2context) { + // 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 (vst2context->procinfo && vst2context->procinfo->chain) { + vst2_plugin_t *plugin = vst2context->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 = vst2_plugin_desc_get_default_control_value(plugin->desc, + index, + vst2_sample_rate); + snprintf(key, sizeof(key), "%d", 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++) { + if (plugin->holders[c].control_memory[index] != value) { + plugin->holders[c].control_memory[index] = value; + plugin->holders[c] + .effect->setParameter(plugin->holders[c].effect, + plugin->desc->control_port_indicies[index] + - (plugin->holders[c].effect->numInputs + + plugin->holders[c].effect->numOutputs), + plugin->holders[c].control_memory[index]); + } + } + } + } + + // Calculate the size of the buffer + size = *samples * *channels * sizeof(float); + + // Allocate the buffer + *buffer = mlt_pool_alloc(size); + + // Initialize the LADSPA 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 VST2 processing + process_vst2(vst2context->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); + + if (vst2context && vst2context->procinfo && vst2context->procinfo->chain + && mlt_properties_get_int64(producer_properties, "_pluginid")) { + vst2_plugin_t *plugin = vst2context->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_vst2", 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_vst2_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, "vst2.", 5)) { + mlt_properties_set(properties, "_pluginid", id + 5); + } + + // Make sure the plugin ID is valid. + unsigned long plugin_id = mlt_properties_get_int64(properties, "_pluginid"); + if (plugin_id < 1000 || plugin_id > 0x00FFFFFF) { + producer_close(producer); + producer = NULL; + } + } + return producer; +} diff --git a/src/modules/jackrack/producer_vst2.yml b/src/modules/jackrack/producer_vst2.yml new file mode 100644 index 000000000..31daef7b9 --- /dev/null +++ b/src/modules/jackrack/producer_vst2.yml @@ -0,0 +1,13 @@ +schema_version: 7.0 +type: producer +identifier: vst2 +title: VST2 +version: 1 +license: GPLv2 +language: en +tags: + - Audio +description: Generate audio using VST2 plugins. +notes: > + Automatically adapts to the number of channels and sampling rate of the consumer. + diff --git a/src/modules/jackrack/vestige.h b/src/modules/jackrack/vestige.h new file mode 100644 index 000000000..8f0435344 --- /dev/null +++ b/src/modules/jackrack/vestige.h @@ -0,0 +1,332 @@ +/* + * IMPORTANT: The author of Carla has no connection with the + * author of the VeSTige VST-compatibility header, has had no + * involvement in its creation. + * + * The VeSTige header is included in this package in the good-faith + * belief that it has been cleanly and legally reverse engineered + * without reference to the official VST SDK and without its + * developer(s) having agreed to the VST SDK license agreement. + */ + +/* + * simple header to allow VeSTige compilation and eventually work + * + * Copyright (c) 2006 Javier Serrano Polo + * + * 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 (see COPYING); if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301 USA. + * + */ +#include +#ifndef _VESTIGE_H +#define _VESTIGE_H + +#if !(defined(WIN32) || defined(_WIN32) || defined(__WIN32__)) +#define __cdecl +#endif + +#define CCONST(a, b, c, d) \ + ((((int) a) << 24) | (((int) b) << 16) | (((int) c) << 8) | (((int) d) << 0)) + +#define audioMasterAutomate 0 +#define audioMasterVersion 1 +#define audioMasterCurrentId 2 +#define audioMasterIdle 3 +#define audioMasterPinConnected 4 +// unsupported? 5 +#define audioMasterWantMidi 6 +#define audioMasterGetTime 7 +#define audioMasterProcessEvents 8 +#define audioMasterSetTime 9 +#define audioMasterTempoAt 10 +#define audioMasterGetNumAutomatableParameters 11 +#define audioMasterGetParameterQuantization 12 +#define audioMasterIOChanged 13 +#define audioMasterNeedIdle 14 +#define audioMasterSizeWindow 15 +#define audioMasterGetSampleRate 16 +#define audioMasterGetBlockSize 17 +#define audioMasterGetInputLatency 18 +#define audioMasterGetOutputLatency 19 +#define audioMasterGetPreviousPlug 20 +#define audioMasterGetNextPlug 21 +#define audioMasterWillReplaceOrAccumulate 22 +#define audioMasterGetCurrentProcessLevel 23 +#define audioMasterGetAutomationState 24 +#define audioMasterOfflineStart 25 +#define audioMasterOfflineRead 26 +#define audioMasterOfflineWrite 27 +#define audioMasterOfflineGetCurrentPass 28 +#define audioMasterOfflineGetCurrentMetaPass 29 +#define audioMasterSetOutputSampleRate 30 +// unsupported? 31 +#define audioMasterGetSpeakerArrangement 31 // deprecated in 2.4? +#define audioMasterGetVendorString 32 +#define audioMasterGetProductString 33 +#define audioMasterGetVendorVersion 34 +#define audioMasterVendorSpecific 35 +#define audioMasterSetIcon 36 +#define audioMasterCanDo 37 +#define audioMasterGetLanguage 38 +#define audioMasterOpenWindow 39 +#define audioMasterCloseWindow 40 +#define audioMasterGetDirectory 41 +#define audioMasterUpdateDisplay 42 +#define audioMasterBeginEdit 43 +#define audioMasterEndEdit 44 +#define audioMasterOpenFileSelector 45 +#define audioMasterCloseFileSelector 46 // currently unused +#define audioMasterEditFile 47 // currently unused +#define audioMasterGetChunkFile 48 // currently unused +#define audioMasterGetInputSpeakerArrangement 49 // currently unused + +#define effFlagsHasEditor 1 +#define effFlagsCanReplacing (1 << 4) // very likely +#define effFlagsIsSynth (1 << 8) // currently unused + +#define effOpen 0 +#define effClose 1 +#define effSetProgram 2 +#define effGetProgram 3 +#define effGetProgramName 5 +#define effGetParamName 8 +#define effSetSampleRate 10 +#define effSetBlockSize 11 +#define effMainsChanged 12 +#define effEditGetRect 13 +#define effEditOpen 14 +#define effEditClose 15 +#define effEditIdle 19 +#define effEditTop 20 +#define effProcessEvents 25 +#define effGetPlugCategory 35 +#define effGetEffectName 45 +#define effGetVendorString 47 +#define effGetProductString 48 +#define effGetVendorVersion 49 +#define effCanDo 51 +#define effIdle 53 +#define effGetParameterProperties 56 +#define effGetVstVersion 58 +#define effShellGetNextPlugin 70 +#define effStartProcess 71 +#define effStopProcess 72 + +#define effBeginSetProgram 67 +#define effEndSetProgram 68 + +#ifdef WORDS_BIGENDIAN +// "VstP" +#define kEffectMagic 0x50747356 +#else +// "PtsV" +#define kEffectMagic 0x56737450 +#endif + +#define kVstLangEnglish 1 +#define kVstMidiType 1 + +struct RemoteVstPlugin; + +#define kVstTransportChanged 1 +#define kVstTransportPlaying (1 << 1) +#define kVstTransportCycleActive (1 << 2) +#define kVstTransportRecording (1 << 3) + +#define kVstAutomationWriting (1 << 6) +#define kVstAutomationReading (1 << 7) + +#define kVstNanosValid (1 << 8) +#define kVstPpqPosValid (1 << 9) +#define kVstTempoValid (1 << 10) +#define kVstBarsValid (1 << 11) +#define kVstCyclePosValid (1 << 12) +#define kVstTimeSigValid (1 << 13) +#define kVstSmpteValid (1 << 14) +#define kVstClockValid (1 << 15) + +struct _VstMidiEvent +{ + // 00 + int type; + // 04 + int byteSize; + // 08 + int deltaFrames; + // 0c? + int flags; + // 10? + int noteLength; + // 14? + int noteOffset; + // 18 + char midiData[4]; + // 1c? + char detune; + // 1d? + char noteOffVelocity; + // 1e? + char reserved1; + // 1f? + char reserved2; +}; + +typedef struct _VstMidiEvent VstMidiEvent; + +struct _VstEvent +{ + char dump[sizeof(VstMidiEvent)]; +}; + +typedef struct _VstEvent VstEvent; + +struct _VstEvents +{ + // 00 + int numEvents; + // 04 + void *reserved; + // 08 + VstEvent *events[2]; +}; + +enum Vestige2StringConstants { + VestigeMaxNameLen = 64, + VestigeMaxLabelLen = 64, + VestigeMaxShortLabelLen = 8, + VestigeMaxCategLabelLen = 24, + VestigeMaxFileNameLen = 100 +}; + +enum VstPlugCategory { + kPlugCategUnknown = 0, + kPlugCategEffect, + kPlugCategSynth, + kPlugCategAnalysis, + kPlugCategMastering, + kPlugCategSpacializer, + kPlugCategRoomFx, + kPlugSurroundFx, + kPlugCategRestoration, + kPlugCategOfflineProcess, + kPlugCategShell, + kPlugCategGenerator, + kPlugCategMaxCount +}; + +typedef struct _VstEvents VstEvents; + +struct _VstParameterProperties +{ + float stepFloat; /* float step */ + float smallStepFloat; /* small float step */ + float largeStepFloat; /* large float step */ + char label[VestigeMaxLabelLen]; /* parameter label */ + int32_t flags; /* @see VstParameterFlags */ + int32_t minInteger; /* integer minimum */ + int32_t maxInteger; /* integer maximum */ + int32_t stepInteger; /* integer step */ + int32_t largeStepInteger; /* large integer step */ + char shortLabel[VestigeMaxShortLabelLen]; /* short label, recommended: 6 + delimiter */ + int16_t displayIndex; /* index where this parameter should be displayed (starting with 0) */ + int16_t category; /* 0: no category, else group index + 1 */ + int16_t numParametersInCategory; /* number of parameters in category */ + int16_t reserved; /* zero */ + char categoryLabel[VestigeMaxCategLabelLen]; /* category label, e.g. "Osc 1" */ + char future[16]; /* reserved for future use */ +}; + +typedef struct _VstParameterProperties VstParameterProperties; + +enum VstParameterFlags { + kVstParameterIsSwitch = 1 << 0, /* parameter is a switch (on/off) */ + kVstParameterUsesIntegerMinMax = 1 << 1, /* minInteger, maxInteger valid */ + kVstParameterUsesFloatStep = 1 << 2, /* stepFloat, smallStepFloat, largeStepFloat valid */ + kVstParameterUsesIntStep = 1 << 3, /* stepInteger, largeStepInteger valid */ + kVstParameterSupportsDisplayIndex = 1 << 4, /* displayIndex valid */ + kVstParameterSupportsDisplayCategory = 1 << 5, /* category, etc. valid */ + kVstParameterCanRamp = 1 << 6 /* set if parameter value can ramp up/down */ +}; + +struct _AEffect +{ + // Never use virtual functions!!! + // 00-03 + int magic; + // dispatcher 04-07 + intptr_t(__cdecl *dispatcher)(struct _AEffect *, int, int, intptr_t, void *, float); + // process, quite sure 08-0b + void(__cdecl *process)(struct _AEffect *, float **, float **, int); + // setParameter 0c-0f + void(__cdecl *setParameter)(struct _AEffect *, int, float); + // getParameter 10-13 + float(__cdecl *getParameter)(struct _AEffect *, int); + // programs 14-17 + int numPrograms; + // Params 18-1b + int numParams; + // Input 1c-1f + int numInputs; + // Output 20-23 + int numOutputs; + // flags 24-27 + int flags; + // Fill somewhere 28-2b + void *ptr1; + void *ptr2; + int initialDelay; + // Zeroes 30-33 34-37 38-3b + char empty2[4 + 4]; + // 1.0f 3c-3f + float unkown_float; + // An object? pointer 40-43 + void *object; + // Zeroes 44-47 + void *user; + // Id 48-4b + int32_t uniqueID; + // plugin version 4c-4f + int32_t version; + // processReplacing 50-53 + void(__cdecl *processReplacing)(struct _AEffect *, float **, float **, int); +}; + +typedef struct _AEffect AEffect; + +typedef struct _VstTimeInfo +{ + /* info from online documentation of VST provided by Steinberg */ + + double samplePos; + double sampleRate; + double nanoSeconds; + double ppqPos; + double tempo; + double barStartPos; + double cycleStartPos; + double cycleEndPos; + int32_t timeSigNumerator; + int32_t timeSigDenominator; + int32_t smpteOffset; + int32_t smpteFrameRate; + int32_t samplesToNextClock; + int32_t flags; + +} VstTimeInfo; + +typedef intptr_t(__cdecl *audioMasterCallback)(AEffect *, int32_t, int32_t, intptr_t, void *, float); + +#endif diff --git a/src/modules/jackrack/vst2_context.c b/src/modules/jackrack/vst2_context.c new file mode 100644 index 000000000..0bd7b1758 --- /dev/null +++ b/src/modules/jackrack/vst2_context.c @@ -0,0 +1,168 @@ +/* + * VST2 Context + * + * Original: + * Copyright (C) Robert Ham 2002, 2003 (node@users.sourceforge.net) + * + * Modification for MLT: + * Copyright (C) 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 + * 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 "framework/mlt_log.h" +#include "vst2_context.h" +#include "vst2_plugin_settings.h" + +#ifndef _ +#define _(x) x +#endif +#define _x (const xmlChar *) +#define _s (const char *) + +extern vst2_mgr_t *g_vst2_plugin_mgr; + +vst2_context_t *vst2_context_new(const char *client_name, unsigned long channels) +{ + vst2_context_t *rack; + + rack = g_malloc(sizeof(vst2_context_t)); + rack->saved_plugins = NULL; + rack->channels = channels; + rack->procinfo = vst2_process_info_new(client_name, channels, FALSE, FALSE); + if (!rack->procinfo) { + g_free(rack); + return NULL; + } + rack->plugin_mgr = g_vst2_plugin_mgr; + vst2_mgr_set_plugins(rack->plugin_mgr, channels); + return rack; +} + +#include +extern vst2_plugin_t *vst2_get_first_enabled_plugin(vst2_process_info_t *procinfo); +extern vst2_plugin_t *vst2_get_last_enabled_plugin(vst2_process_info_t *procinfo); + +void vst2_context_destroy(vst2_context_t *vst2_context) +{ + vst2_plugin_t *first_enabled = vst2_get_first_enabled_plugin(vst2_context->procinfo); + vst2_plugin_t *last_enabled = vst2_get_last_enabled_plugin(vst2_context->procinfo); + vst2_plugin_t *plugin = first_enabled; + do { + dlclose(plugin->dl_handle); + } while ((plugin != last_enabled) && (plugin = plugin->next)); + + vst2_process_quit(vst2_context->procinfo); + vst2_process_info_destroy(vst2_context->procinfo); + g_slist_free(vst2_context->saved_plugins); + g_free(vst2_context); +} + +vst2_plugin_t *vst2_context_instantiate_plugin(vst2_context_t *vst2_context, + vst2_plugin_desc_t *desc) +{ + vst2_plugin_t *plugin; + + /* check whether or not the plugin is RT capable 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 = vst2_plugin_new(desc, vst2_context); + + if (!plugin) { + mlt_log_error(NULL, + "Error loading file plugin '%s' from file '%s'\n", + desc->name, + desc->object_file); + } + + return plugin; +} + +void vst2_context_add_saved_plugin(vst2_context_t *vst2_context, vst2_saved_plugin_t *saved_plugin) +{ + vst2_plugin_t *plugin = vst2_context_instantiate_plugin(vst2_context, + saved_plugin->settings->desc); + if (!plugin) { + mlt_log_warning(NULL, + "%s: could not instantiate object file '%s'\n", + __FUNCTION__, + saved_plugin->settings->desc->object_file); + return; + } + vst2_context->saved_plugins = g_slist_append(vst2_context->saved_plugins, saved_plugin); + vst2_process_add_plugin(vst2_context->procinfo, plugin); + vst2_context_add_plugin(vst2_context, plugin); +} + +void vst2_context_add_plugin(vst2_context_t *vst2_context, vst2_plugin_t *plugin) +{ + vst2_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 = vst2_context->saved_plugins; list; list = g_slist_next(list)) { + saved_plugin = list->data; + + if (saved_plugin->settings->desc->id == plugin->desc->id) { + /* process the settings! */ + vst2_context->saved_plugins = g_slist_remove(vst2_context->saved_plugins, saved_plugin); + break; + } + saved_plugin = NULL; + } + + if (!saved_plugin) + return; + + /* initialize plugin parameters */ + plugin->enabled = vst2_settings_get_enabled(saved_plugin->settings); + plugin->wet_dry_enabled = vst2_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 = vst2_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 < vst2_context->channels; channel++) { + value = vst2_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); + } +} + +/* EOF */ diff --git a/src/modules/jackrack/vst2_context.h b/src/modules/jackrack/vst2_context.h new file mode 100644 index 000000000..5182c66c5 --- /dev/null +++ b/src/modules/jackrack/vst2_context.h @@ -0,0 +1,74 @@ +/* + * VST2 Context + * + * Based on the Jack Rack module + * + * Original: + * Copyright (C) Robert Ham 2002, 2003 (node@users.sourceforge.net) + * + * Modification for MLT: + * Copyright (C) 2004-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 + * 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 __JR_VST2_CONTEXT_H__ +#define __JR_VST2_CONTEXT_H__ + +#include +#include + +#include "plugin_mgr.h" +#include "vst2_plugin.h" +#include "vst2_plugin_settings.h" +#include "vst2_process.h" + +typedef struct _vst2_saved_plugin vst2_saved_plugin_t; + +struct _vst2_saved_plugin +{ + vst2_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 _vst2_context vst2_context_t; + +struct _vst2_context +{ + vst2_mgr_t *plugin_mgr; + vst2_process_info_t *procinfo; + unsigned long channels; + GSList *saved_plugins; +}; + +vst2_context_t *vst2_context_new(const char *client_name, unsigned long channels); +void vst2_context_destroy(vst2_context_t *vst2_context); + +void vst2_context_add_plugin(vst2_context_t *vst2_context, vst2_plugin_t *plugin); +void vst2_context_add_saved_plugin(vst2_context_t *vst2_context, + struct _vst2_saved_plugin *saved_plugin); + +vst2_plugin_t *vst2_context_instantiate_plugin(vst2_context_t *vst2_context, + vst2_plugin_desc_t *desc); + +#endif /* __JR_VST2_CONTEXT_H__ */ diff --git a/src/modules/jackrack/vst2_plugin.c b/src/modules/jackrack/vst2_plugin.c new file mode 100644 index 000000000..009d871ba --- /dev/null +++ b/src/modules/jackrack/vst2_plugin.c @@ -0,0 +1,601 @@ +/* + * JACK Rack + * + * Original: + * Copyright (C) Robert Ham 2002, 2003 (node@users.sourceforge.net) + * + * Modification for MLT: + * Copyright (C) 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 + * 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 "framework/mlt_log.h" +#include "modules/jackrack/lock_free_fifo.h" +#include "vst2_context.h" +#include "vst2_plugin.h" +#include "vst2_process.h" + +#define CONTROL_FIFO_SIZE 128 + +#ifdef WITH_JACK +/* swap over the jack ports in two plugins */ +static void vst2_plugin_swap_aux_ports(vst2_plugin_t *plugin, vst2_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 ladspa instance's input buffers to the previous + plugin's audio memory. make sure to check that plugin->prev + exists. */ +void vst2_plugin_connect_input_ports(vst2_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++) { + plugin->holders[copy] + .effect->setParameter(plugin->holders[copy].effect, + plugin->desc->audio_input_port_indicies[channel] + - (plugin->holders[copy].effect->numInputs + + plugin->holders[copy].effect->numOutputs), + *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 vst2_plugin_connect_output_ports(vst2_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++) { + /* WIP: It might be not used */ + plugin->holders[copy] + .effect->setParameter(plugin->holders[copy].effect, + plugin->desc->audio_input_port_indicies[channel] + - (plugin->holders[copy].effect->numInputs + + plugin->holders[copy].effect->numOutputs), + *plugin->audio_output_memory[rack_channel]); + rack_channel++; + } + } +} + +void vst2_process_add_plugin(vst2_process_info_t *procinfo, vst2_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 */ +vst2_plugin_t *vst2_process_remove_plugin(vst2_process_info_t *procinfo, vst2_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) { + vst2_plugin_t *other; + + for (other = plugin->next; other; other = other->next) + if (other->desc->id == plugin->desc->id) + vst2_plugin_swap_aux_ports(plugin, other); + } +#endif + + return plugin; +} + +/** enable/disable a plugin */ +void vst2_process_ablise_plugin(vst2_process_info_t *procinfo, + vst2_plugin_t *plugin, + gboolean enable) +{ + plugin->enabled = enable; +} + +/** enable/disable a plugin */ +void vst2_process_ablise_vst2_plugin_wet_dry(vst2_process_info_t *procinfo, + vst2_plugin_t *plugin, + gboolean enable) +{ + plugin->wet_dry_enabled = enable; +} + +/** move a plugin up or down one place in the chain */ +void vst2_process_move_plugin(vst2_process_info_t *procinfo, vst2_plugin_t *plugin, gint up) +{ + /* other plugins in the chain */ + vst2_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) { + vst2_plugin_t *other; + other = up ? plugin->next : plugin->prev; + + /* swap around the jack ports */ + if (other->desc->id == plugin->desc->id) + vst2_plugin_swap_aux_ports(plugin, other); + } +#endif +} + +/** exchange an existing plugin for a newly created one */ +vst2_plugin_t *vst2_process_change_plugin(vst2_process_info_t *procinfo, + vst2_plugin_t *plugin, + vst2_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) { + vst2_plugin_t *other; + + for (other = plugin->next; other; other = other->next) + if (other->desc->id == plugin->desc->id) + vst2_plugin_swap_aux_ports(plugin, other); + } +#endif + + return plugin; +} + +/****************************************** + ************* non RT stuff *************** + ******************************************/ + +static int vst2_plugin_open_plugin(vst2_plugin_desc_t *desc, + void **dl_handle_ptr, + const AEffect **effect_ptr) +{ + /* void * dl_handle; */ + /* const char * dlerr; */ + //LADSPA_Descriptor_Function get_descriptor; + + /* clear the error report */ + //dlerror (); + + /* open the object file */ + //dl_handle = dlopen (desc->object_file, RTLD_NOW); + /* dlerr = dlerror (); + if (!dl_handle || dlerr) + { + if (!dlerr) + dlerr = "unknown error"; + mlt_log_warning( NULL, "%s: error opening shared object file '%s': %s\n", + __FUNCTION__, desc->object_file, dlerr); + return 1; + } */ + + /* get the get_descriptor function */ + /* get_descriptor = (LADSPA_Descriptor_Function) + dlsym (dl_handle, "ladspa_descriptor"); + dlerr = dlerror(); + if (dlerr) + { + if (!dlerr) + dlerr = "unknown error"; + mlt_log_warning( NULL, "%s: error finding descriptor symbol in object file '%s': %s\n", + __FUNCTION__, desc->object_file, dlerr); + dlclose (dl_handle); + return 1; + } */ + + /* #ifdef __APPLE__ + if (!get_descriptor (desc->index)) { + void (*constructor)(void) = dlsym (dl_handle, "_init"); + if (constructor) constructor(); + } + #endif */ + + *effect_ptr = desc->effect; + if (!*effect_ptr) { + mlt_log_warning(NULL, + "%s: error finding index %lu in object file '%s'\n", + __FUNCTION__, + desc->index, + desc->object_file); + /* dlclose (dl_handle); */ + return 1; + } + /* *dl_handle_ptr = dl_handle; */ + + return 0; +} + +static int vst2_plugin_instantiate(AEffect *effect, + unsigned long vst2_plugin_index, + gint copies, + AEffect **effects) +{ + gint i; + + for (i = 0; i < copies; i++) { + effects[i] = effect; + effect->dispatcher(effect, effSetSampleRate, 0, 0, NULL, (float) vst2_sample_rate); + + /* if (!effects[i]) + { + unsigned long d; + + for (d = 0; d < i; d++) + descriptor->cleanup (effects[d]); + + return 1; + } */ + } + + return 0; +} + +#ifdef WITH_JACK + +static void vst2_plugin_create_aux_ports(vst2_plugin_t *plugin, + guint copy, + vst2_context_t *vst2_context) +{ + vst2_plugin_desc_t *desc; + // vst2_plugin_slot_t * slot; + unsigned long aux_channel = 1; + unsigned long vst2_plugin_index = 1; + unsigned long i; + char port_name[64]; + char *vst2_plugin_name; + char *ptr; + // GList * list; + vst2_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 = vst2_plugin_name = g_strndup(plugin->desc->name, 7); + while (*ptr != '\0') { + if (*ptr == ' ') + *ptr = '_'; + else + *ptr = tolower(*ptr); + + ptr++; + } + + /* + for (list = vst2_context->slots; list; list = g_list_next (list)) + { + slot = (vst2_plugin_slot_t *) list->data; + + if (slot->plugin->desc->id == plugin->desc->id) + vst2_plugin_index++; + } +*/ + + for (i = 0; i < desc->aux_channels; i++, aux_channel++) { + sprintf(port_name, + "%s_%ld-%d_%c%ld", + vst2_plugin_name, + vst2_plugin_index, + copy + 1, + desc->aux_are_input ? 'i' : 'o', + aux_channel); + + holder->aux_ports[i] = jack_port_register(vst2_context->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(vst2_plugin_name); +} + +#endif + +static void vst2_plugin_init_holder(vst2_plugin_t *plugin, + guint copy, + AEffect *effect, + vst2_context_t *vst2_context) +{ + unsigned long i; + vst2_plugin_desc_t *desc; + vst2_holder_t *holder; + + desc = plugin->desc; + holder = plugin->holders + copy; + + holder->effect = effect; + + 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)); + holder->control_memory[i] + = vst2_plugin_desc_get_default_control_value(desc, + desc->control_port_indicies[i], + vst2_sample_rate); + holder->effect->setParameter(holder->effect, + desc->control_port_indicies[i] + - (holder->effect->numInputs + holder->effect->numOutputs), + *(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; + } + + if (holder->control_memory) { + for (i = 0; i < desc->status_port_count; i++) { + holder->effect->setParameter(holder->effect, + desc->control_port_indicies[i] + - (holder->effect->numInputs + + holder->effect->numOutputs), + *(holder->control_memory + i)); + } + } + +#ifdef WITH_JACK + if (vst2_context->procinfo->jack_client && plugin->desc->aux_channels > 0) + vst2_plugin_create_aux_ports(plugin, copy, vst2_context); +#endif + + /* if (plugin->descriptor->activate) + plugin->descriptor->activate (effects); */ +} + +vst2_plugin_t *vst2_plugin_new(vst2_plugin_desc_t *desc, vst2_context_t *vst2_context) +{ + void *dl_handle; + //const LADSPA_Descriptor * descriptor; + const AEffect *effect; + AEffect **effects; + gint copies; + unsigned long i; + int err; + vst2_plugin_t *plugin; + + /* open the plugin */ + err = vst2_plugin_open_plugin(desc, &dl_handle, &effect); + if (err) + return NULL; + + /* create the effects */ + copies = vst2_plugin_desc_get_copies(desc, vst2_context->channels); + effects = g_malloc(sizeof(AEffect) * copies); + + err = vst2_plugin_instantiate(desc->effect, desc->index, copies, effects); + if (err) { + g_free(effects); + dlclose(dl_handle); + return NULL; + } + + plugin = g_malloc(sizeof(vst2_plugin_t)); + + plugin->dl_handle = dl_handle; + plugin->desc = desc; + plugin->copies = copies; + plugin->enabled = FALSE; + plugin->next = NULL; + plugin->prev = NULL; + plugin->wet_dry_enabled = FALSE; + plugin->vst2_context = vst2_context; + + /* create audio memory and wet/dry stuff */ + plugin->audio_output_memory = g_malloc(sizeof(LADSPA_Data *) * vst2_context->channels); + plugin->wet_dry_fifos = g_malloc(sizeof(lff_t) * vst2_context->channels); + plugin->wet_dry_values = g_malloc(sizeof(LADSPA_Data) * vst2_context->channels); + + for (i = 0; i < vst2_context->channels; i++) { + plugin->audio_output_memory[i] = g_malloc(sizeof(LADSPA_Data) * vst2_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(vst2_holder_t) * copies); + for (i = 0; i < copies; i++) + vst2_plugin_init_holder(plugin, i, effects[i], vst2_context); + + return plugin; +} + +void vst2_plugin_destroy(vst2_plugin_t *plugin) +{ + unsigned long i, j; + int err; + + /* destroy holders */ + for (i = 0; i < plugin->copies; i++) { + 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->vst2_context->procinfo->jack_client && plugin->desc->aux_channels > 0) { + for (j = 0; j < plugin->desc->aux_channels; j++) { + err = jack_port_unregister(plugin->vst2_context->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->vst2_context->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); + + err = dlclose(plugin->dl_handle); + if (err) { + mlt_log_warning(NULL, + "%s: error closing shared object '%s': %s\n", + __FUNCTION__, + plugin->desc->object_file, + dlerror()); + } + + g_free(plugin); +} + +/* EOF */ diff --git a/src/modules/jackrack/vst2_plugin.h b/src/modules/jackrack/vst2_plugin.h new file mode 100644 index 000000000..0f87e33c9 --- /dev/null +++ b/src/modules/jackrack/vst2_plugin.h @@ -0,0 +1,99 @@ +/* + * JACK Rack + * + * Original: + * Copyright (C) Robert Ham 2002, 2003 (node@users.sourceforge.net) + * + * Modification for MLT: + * Copyright (C) 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 + * 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 __JR_VST2_PLUGIN_H__ +#define __JR_VST2_PLUGIN_H__ + +#include +#include +#include +#ifdef WITH_JACK +#include +#endif + +#include "lock_free_fifo.h" +#include "plugin_desc.h" +#include "vst2_process.h" + +typedef struct _vst2_holder vst2_holder_t; +typedef struct _vst2_plugin vst2_plugin_t; + +struct _vst2_holder +{ + //LADSPA_Handle instance; + AEffect *effect; + lff_t *ui_control_fifos; + LADSPA_Data *control_memory; + LADSPA_Data *status_memory; + +#ifdef WITH_JACK + jack_port_t **aux_ports; +#endif +}; + +struct _vst2_plugin +{ + vst2_plugin_desc_t *desc; + gint enabled; + + gint copies; + vst2_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; + + vst2_plugin_t *next; + vst2_plugin_t *prev; + + //const LADSPA_Descriptor * descriptor; + //const AEffect * effect; + void *dl_handle; + struct _vst2_context *vst2_context; +}; + +void vst2_process_add_plugin(vst2_process_info_t *, vst2_plugin_t *plugin); +vst2_plugin_t *vst2_process_remove_plugin(vst2_process_info_t *, vst2_plugin_t *plugin); +void vst2_process_ablise_plugin(vst2_process_info_t *, vst2_plugin_t *plugin, gboolean able); +void vst2_process_ablise_plugin_wet_dry(vst2_process_info_t *, + vst2_plugin_t *plugin, + gboolean enable); +void vst2_process_move_plugin(vst2_process_info_t *, vst2_plugin_t *plugin, gint up); +vst2_plugin_t *vst2_process_change_plugin(vst2_process_info_t *, + vst2_plugin_t *plugin, + vst2_plugin_t *new_plugin); + +struct _vst2_context; +struct _ui; + +vst2_plugin_t *vst2_plugin_new(vst2_plugin_desc_t *plugin_desc, struct _vst2_context *vst2_context); +void vst2_plugin_destroy(vst2_plugin_t *plugin); + +void vst2_plugin_connect_input_ports(vst2_plugin_t *plugin, LADSPA_Data **inputs); +void vst2_plugin_connect_output_ports(vst2_plugin_t *plugin); + +#endif /* __JR_VST2_PLUGIN_H__ */ diff --git a/src/modules/jackrack/vst2_plugin_settings.c b/src/modules/jackrack/vst2_plugin_settings.c new file mode 100644 index 000000000..eb4c7d26c --- /dev/null +++ b/src/modules/jackrack/vst2_plugin_settings.c @@ -0,0 +1,366 @@ +/* + * JACK Rack + * + * Original: + * Copyright (C) Robert Ham 2002, 2003 (node@users.sourceforge.net) + * + * Modification for MLT: + * Copyright (C) 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 + * 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 "vst2_plugin_settings.h" + +static void vst2_settings_set_to_default(vst2_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 = vst2_plugin_desc_get_default_control_value(settings->desc, control, sample_rate); + + for (copy = 0; copy < settings->copies; copy++) { + settings->control_values[copy][control] = value; + } + + settings->locks[control] = TRUE; + } +} + +vst2_settings_t *vst2_settings_new(vst2_plugin_desc_t *desc, + unsigned long channels, + guint32 sample_rate) +{ + vst2_settings_t *settings; + unsigned long channel; + guint copies; + + settings = g_malloc(sizeof(vst2_settings_t)); + copies = vst2_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); + } + + vst2_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; +} + +vst2_settings_t *vst2_settings_dup(vst2_settings_t *other) +{ + vst2_settings_t *settings; + vst2_plugin_desc_t *desc; + unsigned long channel; + + settings = g_malloc(sizeof(vst2_settings_t)); + + settings->sample_rate = other->sample_rate; + settings->desc = other->desc; + settings->copies = vst2_settings_get_copies(other); + settings->channels = vst2_settings_get_channels(other); + settings->wet_dry_enabled = vst2_settings_get_wet_dry_enabled(other); + settings->wet_dry_locked = vst2_settings_get_wet_dry_locked(other); + settings->lock_all = vst2_settings_get_lock_all(other); + settings->enabled = vst2_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++) + vst2_settings_set_lock(settings, control, vst2_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] = vst2_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] = vst2_settings_get_wet_dry_value(other, channel); + + return settings; +} + +void vst2_settings_destroy(vst2_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 vst2_settings_set_copies(vst2_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 vst2_settings_set_channels(vst2_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 vst2_settings_set_sample_rate(vst2_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 vst2_settings_set_control_value(vst2_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) + vst2_settings_set_copies(settings, copy + 1); + + settings->control_values[copy][control_index] = value; +} + +void vst2_settings_set_lock(vst2_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 vst2_settings_set_lock_all(vst2_settings_t *settings, gboolean lock_all) +{ + g_return_if_fail(settings != NULL); + + settings->lock_all = lock_all; +} + +void vst2_settings_set_enabled(vst2_settings_t *settings, gboolean enabled) +{ + g_return_if_fail(settings != NULL); + + settings->enabled = enabled; +} + +void vst2_settings_set_wet_dry_enabled(vst2_settings_t *settings, gboolean enabled) +{ + g_return_if_fail(settings != NULL); + + settings->wet_dry_enabled = enabled; +} + +void vst2_settings_set_wet_dry_locked(vst2_settings_t *settings, gboolean locked) +{ + g_return_if_fail(settings != NULL); + + settings->wet_dry_locked = locked; +} + +void vst2_settings_set_wet_dry_value(vst2_settings_t *settings, + unsigned long channel, + LADSPA_Data value) +{ + g_return_if_fail(settings != NULL); + + if (channel >= settings->channels) + vst2_settings_set_channels(settings, channel + 1); + + settings->wet_dry_values[channel] = value; +} + +LADSPA_Data vst2_settings_get_control_value(vst2_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) + vst2_settings_set_copies(settings, copy - 1); + + return settings->control_values[copy][control_index]; +} + +gboolean vst2_settings_get_lock(const vst2_settings_t *settings, unsigned long control_index) +{ + g_return_val_if_fail(settings != NULL, FALSE); + + return settings->locks[control_index]; +} + +gboolean vst2_settings_get_lock_all(const vst2_settings_t *settings) +{ + g_return_val_if_fail(settings != NULL, FALSE); + + return settings->lock_all; +} + +gboolean vst2_settings_get_enabled(const vst2_settings_t *settings) +{ + g_return_val_if_fail(settings != NULL, FALSE); + + return settings->enabled; +} + +guint vst2_settings_get_copies(const vst2_settings_t *settings) +{ + g_return_val_if_fail(settings != NULL, 0); + + return settings->copies; +} + +unsigned long vst2_settings_get_channels(const vst2_settings_t *settings) +{ + g_return_val_if_fail(settings != NULL, 0); + + return settings->channels; +} + +gboolean vst2_settings_get_wet_dry_enabled(const vst2_settings_t *settings) +{ + g_return_val_if_fail(settings != NULL, FALSE); + + return settings->wet_dry_enabled; +} + +gboolean vst2_settings_get_wet_dry_locked(const vst2_settings_t *settings) +{ + g_return_val_if_fail(settings != NULL, FALSE); + + return settings->wet_dry_locked; +} + +LADSPA_Data vst2_settings_get_wet_dry_value(vst2_settings_t *settings, unsigned long channel) +{ + g_return_val_if_fail(settings != NULL, NAN); + + if (channel >= settings->channels) + vst2_settings_set_channels(settings, channel + 1); + + return settings->wet_dry_values[channel]; +} + +/* EOF */ diff --git a/src/modules/jackrack/vst2_plugin_settings.h b/src/modules/jackrack/vst2_plugin_settings.h new file mode 100644 index 000000000..3da33208f --- /dev/null +++ b/src/modules/jackrack/vst2_plugin_settings.h @@ -0,0 +1,83 @@ +/* + * JACK Rack + * + * Original: + * Copyright (C) Robert Ham 2002, 2003 (node@users.sourceforge.net) + * + * Modification for MLT: + * Copyright (C) 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 + * 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 __JR_VST2_PLUGIN_SETTINGS_H__ +#define __JR_VST2_PLUGIN_SETTINGS_H__ + +#include +#include + +#include "plugin_desc.h" + +typedef struct _vst2_settings vst2_settings_t; + +struct _vst2_settings +{ + guint32 sample_rate; + vst2_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; +}; + +vst2_settings_t *vst2_settings_new(vst2_plugin_desc_t *desc, + unsigned long channels, + guint32 sample_rate); +vst2_settings_t *vst2_settings_dup(vst2_settings_t *settings); +void vst2_settings_destroy(vst2_settings_t *settings); + +void vst2_settings_set_control_value(vst2_settings_t *settings, + guint copy, + unsigned long control_index, + LADSPA_Data value); +void vst2_settings_set_lock(vst2_settings_t *settings, unsigned long control_index, gboolean locked); +void vst2_settings_set_lock_all(vst2_settings_t *settings, gboolean lock_all); +void vst2_settings_set_enabled(vst2_settings_t *settings, gboolean enabled); +void vst2_settings_set_wet_dry_enabled(vst2_settings_t *settings, gboolean enabled); +void vst2_settings_set_wet_dry_locked(vst2_settings_t *settings, gboolean locked); +void vst2_settings_set_wet_dry_value(vst2_settings_t *settings, + unsigned long channel, + LADSPA_Data value); + +LADSPA_Data vst2_settings_get_control_value(vst2_settings_t *settings, + guint copy, + unsigned long control_index); +gboolean vst2_settings_get_lock(const vst2_settings_t *settings, unsigned long control_index); +gboolean vst2_settings_get_lock_all(const vst2_settings_t *settings); +gboolean vst2_settings_get_enabled(const vst2_settings_t *settings); +guint vst2_settings_get_copies(const vst2_settings_t *settings); +unsigned long vst2_settings_get_channels(const vst2_settings_t *settings); +gboolean vst2_settings_get_wet_dry_enabled(const vst2_settings_t *settings); +gboolean vst2_settings_get_wet_dry_locked(const vst2_settings_t *settings); +LADSPA_Data vst2_settings_get_wet_dry_value(vst2_settings_t *settings, unsigned long channel); + +void vst2_settings_set_sample_rate(vst2_settings_t *settings, guint32 sample_rate); + +#endif /* __JR_VST2_PLUGIN_SETTINGS_H__ */ diff --git a/src/modules/jackrack/vst2_process.c b/src/modules/jackrack/vst2_process.c new file mode 100644 index 000000000..b3d47979e --- /dev/null +++ b/src/modules/jackrack/vst2_process.c @@ -0,0 +1,639 @@ +/* + * JACK Rack + * + * Original: + * Copyright (C) Robert Ham 2002, 2003 (node@users.sourceforge.net) + * + * Modification for MLT: + * Copyright (C) 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 + * 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 "framework/mlt_log.h" +#include "lock_free_fifo.h" +#include "vst2_plugin.h" +#include "vst2_process.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 vst2_sample_rate; +jack_nframes_t vst2_buffer_size; + +/* #ifdef WITH_JACK + + static void + jack_shutdown_cb (void * data) + { + vst2_process_info_t * procinfo = data; + + procinfo->quit = TRUE; + } + + #endif */ + +/** process messages for plugins' control ports */ +void vst2_process_control_port_messages(vst2_process_info_t *procinfo) +{ + vst2_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(vst2_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 + +vst2_plugin_t *vst2_get_first_enabled_plugin(vst2_process_info_t *procinfo) +{ + vst2_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; +} + +vst2_plugin_t *vst2_get_last_enabled_plugin(vst2_process_info_t *procinfo) +{ + vst2_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 vst2_connect_chain(vst2_process_info_t *procinfo, jack_nframes_t frames) +{ + vst2_plugin_t *first_enabled, *last_enabled, *plugin; + gint copy; + unsigned long channel; + if (!procinfo->chain) + return; + + first_enabled = vst2_get_first_enabled_plugin(procinfo); + if (!first_enabled) + return; + + last_enabled = vst2_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++) { + plugin->holders[copy].effect->setParameter( + plugin->holders[copy].effect, + plugin->desc->audio_aux_port_indicies[channel] + - (plugin->holders[copy].effect->numInputs + + plugin->holders[copy].effect->numOutputs), + *((LADSPA_Data *) + 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++) { + plugin->holders[copy] + .effect->setParameter(plugin->holders[copy].effect, + plugin->desc->audio_aux_port_indicies[channel] + - (plugin->holders[copy].effect->numInputs + + plugin->holders[copy].effect->numOutputs), + *(procinfo->silent_buffer)); + } + } + } + } while ((plugin != last_enabled) && (plugin = plugin->next)); + + /* ensure that all the of the enabled plugins are connected to their memory */ + vst2_plugin_connect_output_ports(first_enabled); + if (first_enabled != last_enabled) { + vst2_plugin_connect_input_ports(last_enabled, last_enabled->prev->audio_output_memory); + for (plugin = first_enabled->next; plugin; plugin = plugin->next) { + if (plugin->enabled) { + vst2_plugin_connect_input_ports(plugin, plugin->prev->audio_output_memory); + vst2_plugin_connect_output_ports(plugin); + } + } + } + + /* input buffers for first plugin */ + if (plugin && plugin->desc->has_input) + vst2_plugin_connect_input_ports(first_enabled, procinfo->jack_input_buffers); +} + +void vst2_process_chain(vst2_process_info_t *procinfo, jack_nframes_t frames) +{ + vst2_plugin_t *first_enabled; + vst2_plugin_t *last_enabled = NULL; + vst2_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 = vst2_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 = vst2_get_last_enabled_plugin(procinfo); + + for (plugin = first_enabled; plugin; plugin = plugin->next) { + if (plugin->enabled) { + for (i = 0; i < plugin->copies; i++) { + if (plugin->holders[i].effect->processReplacing != NULL) { + plugin->holders[i].effect->processReplacing(plugin->holders[i].effect, + plugin->audio_input_memory, + plugin->audio_output_memory, + frames); + } else { + plugin->holders[i] + .effect->process(plugin->holders[i].effect, + plugin->audio_input_memory, + plugin->audio_output_memory, + frames); // deprecated only few plugins support it + } + } + + 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_vst2(vst2_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 vst2_process_info from jack!\n", __FUNCTION__); + return 1; + } + + if (procinfo->quit == TRUE) + return 1; + + vst2_process_control_port_messages(procinfo); + + for (channel = 0; channel < procinfo->channels; channel++) { + if (vst2_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; + } + } + + vst2_connect_chain(procinfo, frames); + + vst2_process_chain(procinfo, frames); + + return 0; +} + +#ifdef WITH_JACK + +int vst2_process_jack(jack_nframes_t frames, void *data) +{ + int err; + vst2_process_info_t *procinfo; + + procinfo = (vst2_process_info_t *) data; + + if (!procinfo) { + mlt_log_error(NULL, "%s: no vst2_process_info from jack!\n", __FUNCTION__); + return 1; + } + + if (procinfo->port_count == 0) + return 0; + + if (procinfo->quit == TRUE) + return 1; + + vst2_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; + } + + vst2_connect_chain(procinfo, frames); + + vst2_process_chain(procinfo, frames); + + return 0; +} + +/******************************************* + ************** non RT stuff *************** + *******************************************/ + +/* static int + vst2_process_info_connect_jack (vst2_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_vst2_process_callback (procinfo->jack_client, vst2_process_jack, procinfo); + jack_on_shutdown (procinfo->jack_client, jack_shutdown_cb, procinfo); + + return 0; + } */ + +static void vst2_process_info_connect_port(vst2_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 vst2_process_info_set_port_count(vst2_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)) + vst2_process_info_connect_port(procinfo, in, i, port_name); + + g_free(port_name); + } + } + + procinfo->port_count = port_count; + + return 0; +} + +#endif + +void vst2_process_info_set_channels(vst2_process_info_t *procinfo, + unsigned long channels, + gboolean connect_inputs, + gboolean connect_outputs) +{ +#ifdef WITH_JACK + vst2_process_info_set_port_count(procinfo, channels, connect_inputs, connect_outputs); +#endif + procinfo->channels = channels; +} + +vst2_process_info_t *vst2_process_info_new(const char *client_name, + unsigned long rack_channels, + gboolean connect_inputs, + gboolean connect_outputs) +{ + vst2_process_info_t *procinfo; + char *jack_client_name; + int err; + + procinfo = g_malloc(sizeof(vst2_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) { + vst2_sample_rate = 48000; // should be set externally before calling process_vst2 + vst2_buffer_size = MAX_BUFFER_SIZE; + procinfo->silent_buffer = g_malloc(sizeof(LADSPA_Data) * vst2_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 = vst2_process_info_connect_jack (procinfo); + if (err) + { + /\* g_free (procinfo); *\/ + return NULL; + /\* abort (); *\/ + } + + vst2_sample_rate = jack_get_sample_rate (procinfo->jack_client); + vst2_buffer_size = jack_get_sample_rate (procinfo->jack_client); + + jack_set_vst2_process_callback (procinfo->jack_client, vst2_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 = vst2_process_info_set_port_count (procinfo, rack_channels, connect_inputs, connect_outputs); + if (err) + return NULL; + #endif */ + + return procinfo; +} + +void vst2_process_info_destroy(vst2_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 vst2_process_quit(vst2_process_info_t *procinfo) +{ + procinfo->quit = TRUE; +} diff --git a/src/modules/jackrack/vst2_process.h b/src/modules/jackrack/vst2_process.h new file mode 100644 index 000000000..d4dcb7414 --- /dev/null +++ b/src/modules/jackrack/vst2_process.h @@ -0,0 +1,88 @@ +/* + * JACK Rack + * + * Original: + * Copyright (C) Robert Ham 2002, 2003 (node@users.sourceforge.net) + * + * Modification for MLT: + * Copyright (C) 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 + * 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_VST2_PROCESS_H__ +#define __JLH_VST2_PROCESS_H__ + +#include +#ifdef WITH_JACK +#include +#endif +#include + +typedef struct _vst2_process_info vst2_process_info_t; + +/** this is what gets passed to the process() callback and contains all + the data the process callback will need */ +struct _vst2_process_info +{ + /** the plugin instance chain */ + struct _vst2_plugin *chain; + struct _vst2_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 vst2_sample_rate; +extern jack_nframes_t vst2_buffer_size; + +vst2_process_info_t *vst2_process_info_new(const char *client_name, + unsigned long rack_channels, + gboolean connect_inputs, + gboolean connect_outputs); +void vst2_process_info_destroy(vst2_process_info_t *procinfo); + +void vst2_process_info_set_channels(vst2_process_info_t *procinfo, + unsigned long channels, + gboolean connect_inputs, + gboolean connect_outputs); + +int process_vst2(vst2_process_info_t *procinfo, + jack_nframes_t frames, + LADSPA_Data **inputs, + LADSPA_Data **outputs); + +#ifdef WITH_JACK +int vst2_process_jack(jack_nframes_t frames, void *data); +#endif + +void vst2_process_quit(vst2_process_info_t *procinfo); + +#endif /* __JLH_VST2_PROCESS_H__ */