Skip to content

Commit

Permalink
Initial Implementation of LV2 module (#983)
Browse files Browse the repository at this point in the history
* Adding initial LV2 support for jackrack module

* renaming lv2_rack to lv2_context, updating copyright year, clang-format lv2 feature in jackrack module

* Removing the wrong reference to JACK Rack XML file resource parameters in filter_lv2.yml

* Adding more static features to lv2 instance run

* Adding support to enumeration parameter type in lv2 module

* Removing unused lv2 code, refactor CMake files, replace : with ^ instead of <

---------

Co-authored-by: mr.fantastic <mrfantastic@firemail.cc>
  • Loading branch information
joinlaw and mr.fantastic authored Jul 1, 2024
1 parent 17c6829 commit 8c744a6
Show file tree
Hide file tree
Showing 21 changed files with 4,273 additions and 689 deletions.
9 changes: 9 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ option(MOD_GDK "Enable GDK module" ON)
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(MOD_KDENLIVE "Enable Kdenlive module" ON)
option(MOD_NDI "Enable NDI module" OFF)
option(MOD_NORMALIZE "Enable Normalize module (GPL)" ON)
Expand Down Expand Up @@ -250,6 +251,13 @@ if(MOD_JACKRACK)
list(APPEND MLT_SUPPORTED_COMPONENTS jackrack)
endif()

if(USE_LV2)
pkg_check_modules(lilv IMPORTED_TARGET lilv-0)
if(NOT lilv_FOUND)
set(USE_LV2 OFF)
endif()
endif()

if(MOD_KDENLIVE)
list(APPEND MLT_SUPPORTED_COMPONENTS kdenlive)
endif()
Expand Down Expand Up @@ -632,5 +640,6 @@ add_feature_info("SWIG: PHP" SWIG_PHP "")
add_feature_info("SWIG: Python" SWIG_PYTHON "")
add_feature_info("SWIG: Ruby" SWIG_RUBY "")
add_feature_info("SWIG: Tcl" SWIG_TCL "")
add_feature_info("lv2: LV2 Plugins support" USE_LV2 "")

feature_summary(WHAT ALL INCLUDE_QUIET_PACKAGES FATAL_ON_MISSING_REQUIRED_PACKAGES)
18 changes: 18 additions & 0 deletions src/modules/jackrack/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,11 @@ if(TARGET JACK::JACK)
install(FILES consumer_jack.yml DESTINATION ${MLT_INSTALL_DATA_DIR}/jackrack)
endif()

if(USE_LV2)
target_compile_definitions(mltjackrack PRIVATE WITH_LV2)
install(FILES filter_lv2.yml producer_lv2.yml DESTINATION ${MLT_INSTALL_DATA_DIR}/jackrack)
endif()

if(GPL AND TARGET PkgConfig::xml AND TARGET PkgConfig::glib AND ladspa_h_FOUND)
target_sources(mltjackrack PRIVATE
jack_rack.c jack_rack.h
Expand All @@ -40,6 +45,19 @@ if(GPL AND TARGET PkgConfig::xml AND TARGET PkgConfig::glib AND ladspa_h_FOUND)
target_sources(mltjackrack PRIVATE filter_jackrack.c)
install(FILES filter_jackrack.yml DESTINATION ${MLT_INSTALL_DATA_DIR}/jackrack)
endif()

if(USE_LV2 AND TARGET PkgConfig::lilv)
target_link_libraries(mltjackrack PRIVATE PkgConfig::lilv)
target_sources(mltjackrack PRIVATE
filter_lv2.c producer_lv2.c
lv2_context.c lv2_context.h
lv2_plugin.c lv2_plugin.h
lv2_process.c lv2_process.h
lv2_plugin_settings.c lv2_plugin_settings.h
lv2_urid_helper.h)
install(FILES filter_lv2.yml producer_lv2.yml DESTINATION ${MLT_INSTALL_DATA_DIR}/jackrack)
endif()

endif()

set_target_properties(mltjackrack PROPERTIES LIBRARY_OUTPUT_DIRECTORY "${MLT_MODULE_OUTPUT_DIRECTORY}")
Expand Down
265 changes: 264 additions & 1 deletion src/modules/jackrack/factory.c
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/*
* factory.c -- the factory method interfaces
* Copyright (C) 2003-2022 Meltytech, LLC
* Copyright (C) 2003-2024 Meltytech, LLC
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
Expand Down Expand Up @@ -49,8 +49,38 @@ extern mlt_producer producer_ladspa_init(mlt_profile profile,
const char *id,
char *arg);

#ifdef WITH_LV2

#include <lv2.h>

/* lv2 extenstions */
#include <lv2/atom/atom.h>
#include <lv2/midi/midi.h>
#include <lv2/port-groups/port-groups.h>
#include <lv2/port-props/port-props.h>
#include <lv2/presets/presets.h>
#include <lv2/resize-port/resize-port.h>
#include <lv2/ui/ui.h>
#include <lv2/worker/worker.h>

#include <lilv/lilv.h>

extern mlt_filter filter_lv2_init(mlt_profile profile,
mlt_service_type type,
const char *id,
char *arg);
extern mlt_producer producer_lv2_init(mlt_profile profile,
mlt_service_type type,
const char *id,
char *arg);
#endif

plugin_mgr_t *g_jackrack_plugin_mgr = NULL;

#ifdef WITH_LV2
lv2_mgr_t *g_lv2_plugin_mgr = NULL;
#endif

static void add_port_to_metadata(mlt_properties p, plugin_desc_t *desc, int j)
{
LADSPA_Data sample_rate = 48000;
Expand Down Expand Up @@ -211,6 +241,205 @@ static mlt_properties metadata(mlt_service_type type, const char *id, char *data
return result;
}

#ifdef WITH_LV2

static void lv2_add_port_to_metadata(mlt_properties p, lv2_plugin_desc_t *desc, int j)
{
LADSPA_PortRangeHintDescriptor hint_descriptor = desc->port_range_hints[j].HintDescriptor;

mlt_properties_set(p, "title", desc->port_names[j]);
if (LADSPA_IS_HINT_INTEGER(hint_descriptor)) {
mlt_properties_set(p, "type", "integer");
mlt_properties_set_int(p, "default", (int) desc->def_values[j]);
mlt_properties_set_double(p, "minimum", (int) desc->min_values[j]);
mlt_properties_set_double(p, "maximum", (int) desc->max_values[j]);
} else if (LADSPA_IS_HINT_TOGGLED(hint_descriptor)) {
mlt_properties_set(p, "type", "boolean");
mlt_properties_set_int(p, "default", desc->def_values[j]);
} else {
mlt_properties_set(p, "type", "float");
mlt_properties_set_double(p, "default", desc->def_values[j]);
mlt_properties_set_double(p, "minimum", desc->min_values[j]);
mlt_properties_set_double(p, "maximum", desc->max_values[j]);
}

if (LADSPA_IS_HINT_ENUMERATION(hint_descriptor)) {
mlt_properties_set(p, "type", "string");

char *str_ptr = strchr(desc->uri, '^');
while (str_ptr != NULL) {
*str_ptr++ = ':';
str_ptr = strchr(str_ptr, '^');
}

LilvNode* puri_temp = lilv_new_uri(g_lv2_plugin_mgr->lv2_world, desc->uri);

str_ptr = strchr(desc->uri, ':');
while (str_ptr != NULL) {
*str_ptr++ = '^';
str_ptr = strchr(str_ptr, ':');
}

const LilvPlugin* p_temp = lilv_plugins_get_by_uri(g_lv2_plugin_mgr->plugin_list, puri_temp);
const LilvPort *port_temp = lilv_plugin_get_port_by_index(p_temp, j);

lilv_node_free(puri_temp);

mlt_properties values_temp = mlt_properties_new();
mlt_properties_set_data(p,
"values",
values_temp,
0,
(mlt_destructor) mlt_properties_close,
NULL);

// Fill scalePoints Map
LilvScalePoints* sp = lilv_port_get_scale_points(p_temp, port_temp);
if (sp) {
LILV_FOREACH (scale_points, s, sp) {
const LilvScalePoint* p = lilv_scale_points_get(sp, s);
const LilvNode* val = lilv_scale_point_get_value(p);
if (!lilv_node_is_float(val) && !lilv_node_is_int(val)) {
continue;
}

const float f = lilv_node_as_float(val);


char key_temp[20];

if (lilv_node_is_float(val)) {
snprintf(key_temp, 20, "%f", f);
} else if (lilv_node_is_int(val)) {
snprintf(key_temp, 20, "%d", (int) f);
}

mlt_properties_set(values_temp, key_temp, lilv_node_as_string(lilv_scale_point_get_label(p)));

}

lilv_scale_points_free(sp);
}
}

if (LADSPA_IS_HINT_LOGARITHMIC(hint_descriptor))
mlt_properties_set(p, "scale", "log");
mlt_properties_set(p, "mutable", "yes");
mlt_properties_set(p, "animation", "yes");
}

static mlt_properties lv2_metadata(mlt_service_type type, const char *id, char *data)
{
char file[PATH_MAX];
if (type == mlt_service_filter_type) {
snprintf(file,
PATH_MAX,
"%s/lv2/%s",
mlt_environment("MLT_DATA"),
strncmp(id, "lv2.", 4) ? data : "filter_lv2.yml");
} else {
snprintf(file,
PATH_MAX,
"%s/lv2/%s",
mlt_environment("MLT_DATA"),
strncmp(id, "lv2.", 4) ? data : "producer_lv2.yml");
}
mlt_properties result = mlt_properties_parse_yaml(file);

if (!strncmp(id, "lv2.", 4)) {
// Annotate the yaml properties with lv2 control port info.
lv2_plugin_desc_t *desc = lv2_mgr_get_any_desc(g_lv2_plugin_mgr, (char *) &id[4]);

if (desc) {
mlt_properties params = mlt_properties_new();
mlt_properties p;
char key[20];
int i;

mlt_properties_set(result, "identifier", id);
mlt_properties_set(result, "title", desc->name);
mlt_properties_set(result, "creator", desc->maker ? desc->maker : "unknown");
mlt_properties_set(result, "description", "LV2 plugin");
mlt_properties_set_data(result,
"parameters",
params,
0,
(mlt_destructor) mlt_properties_close,
NULL);
for (i = 0; i < desc->control_port_count; i++) {
int j = desc->control_port_indicies[i];
p = mlt_properties_new();
snprintf(key, sizeof(key), "%d", mlt_properties_count(params));
mlt_properties_set_data(params,
key,
p,
0,
(mlt_destructor) mlt_properties_close,
NULL);
snprintf(key, sizeof(key), "%d", j);
mlt_properties_set(p, "identifier", key);
lv2_add_port_to_metadata(p, desc, j);
mlt_properties_set(p, "mutable", "yes");
}
for (i = 0; i < desc->status_port_count; i++) {
int j = desc->status_port_indicies[i];
p = mlt_properties_new();
snprintf(key, sizeof(key), "%d", mlt_properties_count(params));
mlt_properties_set_data(params,
key,
p,
0,
(mlt_destructor) mlt_properties_close,
NULL);
snprintf(key, sizeof(key), "%d[*]", j);
mlt_properties_set(p, "identifier", key);
lv2_add_port_to_metadata(p, desc, j);
mlt_properties_set(p, "readonly", "yes");
}

p = mlt_properties_new();
snprintf(key, sizeof(key), "%d", mlt_properties_count(params));
mlt_properties_set_data(params, key, p, 0, (mlt_destructor) mlt_properties_close, NULL);
mlt_properties_set(p, "identifier", "instances");
mlt_properties_set(p, "title", "Instances");
mlt_properties_set(p,
"description",
"The number of instances of the plugin that are in use.\n"
"MLT will create the number of plugins that are required "
"to support the number of audio channels.\n"
"Status parameters (readonly) are provided for each instance "
"and are accessed by specifying the instance number after the "
"identifier (starting at zero).\n"
"e.g. 9[0] provides the value of status 9 for the first instance.");
mlt_properties_set(p, "type", "integer");
mlt_properties_set(p, "readonly", "yes");

if (type == mlt_service_filter_type) {
p = mlt_properties_new();
snprintf(key, sizeof(key), "%d", mlt_properties_count(params));
mlt_properties_set_data(params,
key,
p,
0,
(mlt_destructor) mlt_properties_close,
NULL);
mlt_properties_set(p, "identifier", "wetness");
mlt_properties_set(p, "title", "Wet/Dry");
mlt_properties_set(p, "type", "float");
mlt_properties_set_double(p, "default", 1);
mlt_properties_set_double(p, "minimum", 0);
mlt_properties_set_double(p, "maximum", 1);
mlt_properties_set(p, "mutable", "yes");
mlt_properties_set(p, "animation", "yes");
}
}
}

return result;
}

#endif

MLT_REPOSITORY
{
#ifdef GPL
Expand All @@ -235,6 +464,40 @@ MLT_REPOSITORY
}
mlt_factory_register_for_clean_up(g_jackrack_plugin_mgr, (mlt_destructor) plugin_mgr_destroy);

#ifdef WITH_LV2
g_lv2_plugin_mgr = lv2_mgr_new();

char global_lv2_world[20];
snprintf (global_lv2_world, 20, "%p", g_lv2_plugin_mgr->lv2_world);
mlt_environment_set ("global_lv2_world", global_lv2_world);

for (list = g_lv2_plugin_mgr->all_plugins; list; list = g_slist_next(list)) {
lv2_plugin_desc_t *desc = (lv2_plugin_desc_t *) list->data;
char *s = NULL;
s = calloc(1, strlen("lv2.") + strlen(desc->uri) + 1);

sprintf(s, "lv2.%s", desc->uri);

char *str_ptr = strchr(s, ':');
while (str_ptr != NULL) {
*str_ptr++ = '^';
str_ptr = strchr(str_ptr, ':');
}

if (desc->has_input) {
MLT_REGISTER(mlt_service_filter_type, s, filter_lv2_init);
MLT_REGISTER_METADATA(mlt_service_filter_type, s, lv2_metadata, NULL);
} else {
MLT_REGISTER(mlt_service_producer_type, s, producer_lv2_init);
MLT_REGISTER_METADATA(mlt_service_producer_type, s, lv2_metadata, NULL);
}

if (s) {
free(s);
}
}
#endif

#ifdef WITH_JACK
MLT_REGISTER(mlt_service_filter_type, "jack", filter_jackrack_init);
MLT_REGISTER_METADATA(mlt_service_filter_type, "jack", metadata, "filter_jack.yml");
Expand Down
Loading

0 comments on commit 8c744a6

Please sign in to comment.