#include "pbd/compose.h"
#include "pbd/error.h"
#include "pbd/pathscanner.h"
+#include "pbd/stl_delete.h"
#include "pbd/xml++.h"
+#include "libardour-config.h"
+
#include "ardour/ardour.h"
#include "ardour/audio_buffer.h"
#include "ardour/audioengine.h"
#include "ardour/lv2_plugin.h"
#include "ardour/session.h"
-#include "pbd/stl_delete.h"
-
#include "i18n.h"
#include <locale.h>
+#include "lv2ext/lv2_files.h"
#include "lv2ext/lv2_persist.h"
-#include "lv2_pfile.h"
+#include "rdff.h"
+#ifdef HAVE_SUIL
+#include <suil/suil.h>
+#endif
#define NS_DC "http://dublincore.org/documents/dcmi-namespace/"
#define NS_LV2 "http://lv2plug.in/ns/lv2core#"
using namespace ARDOUR;
using namespace PBD;
-URIMap LV2Plugin:: _uri_map;
+URIMap LV2Plugin::_uri_map;
uint32_t LV2Plugin::_midi_event_type = _uri_map.uri_to_id(
"http://lv2plug.in/ns/ext/event",
"http://lv2plug.in/ns/ext/midi#MidiEvent");
: Plugin(engine, session)
, _world(world)
, _features(NULL)
+ , _insert_id("0")
{
init(world, plugin, rate);
}
: Plugin(other)
, _world(other._world)
, _features(NULL)
+ , _insert_id(other._insert_id)
{
init(other._world, other._plugin, other._sample_rate);
void
LV2Plugin::init(LV2World& world, SLV2Plugin plugin, framecnt_t rate)
{
- DEBUG_TRACE(DEBUG::LV2, "LV2 plugin init\n");
+ DEBUG_TRACE(DEBUG::LV2, "init\n");
_world = world;
_plugin = plugin;
_ui = NULL;
+ _ui_type = NULL;
_control_data = 0;
_shadow_data = 0;
_latency_control_port = 0;
_was_activated = false;
- _instance_access_feature.URI = "http://lv2plug.in/ns/ext/instance-access";
- _data_access_feature.URI = "http://lv2plug.in/ns/ext/data-access";
- _persist_feature.URI = "http://lv2plug.in/ns/ext/persist";
- _persist_feature.data = NULL;
+ _instance_access_feature.URI = "http://lv2plug.in/ns/ext/instance-access";
+ _data_access_feature.URI = "http://lv2plug.in/ns/ext/data-access";
+ _path_support_feature.URI = LV2_FILES_PATH_SUPPORT_URI;
+ _new_file_support_feature.URI = LV2_FILES_NEW_FILE_SUPPORT_URI;
+ _persist_feature.URI = "http://lv2plug.in/ns/ext/persist";
+ _persist_feature.data = NULL;
SLV2Value persist_uri = slv2_value_new_uri(_world.world, _persist_feature.URI);
_supports_persist = slv2_plugin_has_feature(plugin, persist_uri);
slv2_value_free(persist_uri);
- _features = (LV2_Feature**)malloc(sizeof(LV2_Feature*) * 5);
+ _features = (LV2_Feature**)malloc(sizeof(LV2_Feature*) * 7);
_features[0] = &_instance_access_feature;
_features[1] = &_data_access_feature;
- _features[2] = &_persist_feature;
- _features[3] = _uri_map.feature();
- _features[4] = NULL;
+ _features[2] = &_path_support_feature;
+ _features[3] = &_new_file_support_feature;
+ _features[4] = &_persist_feature;
+ _features[5] = _uri_map.feature();
+ _features[6] = NULL;
+
+ LV2_Files_Path_Support* path_support = (LV2_Files_Path_Support*)malloc(
+ sizeof(LV2_Files_Path_Support));
+ path_support->host_data = this;
+ path_support->abstract_path = &lv2_files_abstract_path;
+ path_support->absolute_path = &lv2_files_absolute_path;
+ _path_support_feature.data = path_support;
+
+ LV2_Files_New_File_Support* new_file_support = (LV2_Files_New_File_Support*)malloc(
+ sizeof(LV2_Files_New_File_Support));
+ new_file_support->host_data = this;
+ new_file_support->new_file_path = &lv2_files_new_file_path;
+ _new_file_support_feature.data = new_file_support;
_instance = slv2_plugin_instantiate(plugin, rate, _features);
_name = slv2_plugin_get_name(plugin);
SLV2UIs uis = slv2_plugin_get_uis(_plugin);
if (slv2_uis_size(uis) > 0) {
+#if defined(HAVE_NEW_SLV2) and defined(HAVE_SUIL)
+ // Look for embeddable UI
+ SLV2Value ui_type = NULL;
+ SLV2_FOREACH(u, uis) {
+ SLV2UI this_ui = slv2_uis_get(uis, u);
+ if (slv2_ui_is_supported(this_ui,
+ suil_ui_supported,
+ _world.gtk_gui,
+ &_ui_type)) {
+ // TODO: Multiple UI support
+ _ui = this_ui;
+ break;
+ }
+ }
+#else
// Look for Gtk native UI
for (unsigned i = 0; i < slv2_uis_size(uis); ++i) {
SLV2UI ui = slv2_uis_get_at(uis, i);
break;
}
}
+#endif
// If Gtk UI is not available, try to find external UI
if (!_ui) {
slv2_instance_free(_instance);
slv2_value_free(_name);
slv2_value_free(_author);
+ slv2_value_free(_ui_type);
delete [] _control_data;
delete [] _shadow_data;
LV2Plugin::set_parameter(uint32_t which, float val)
{
DEBUG_TRACE(DEBUG::LV2, string_compose(
- "%1 set parameter %2 to %3\n", name(), which, val));
+ "%1 set parameter %2 to %3\n", name(), which, val));
if (which < slv2_plugin_get_num_ports(_plugin)) {
_shadow_data[which] = val;
return 0;
}
-void
-LV2Plugin::lv2_persist_store_callback(void* callback_data,
- const char* key,
+struct PersistValue {
+ inline PersistValue(uint32_t k, const void* v, size_t s, uint32_t t, uint32_t f)
+ : key(k), value(v), size(s), type(t), flags(f)
+ {}
+
+ const uint32_t key;
+ const void* value;
+ const size_t size;
+ const uint32_t type;
+ const bool flags;
+};
+
+struct PersistState {
+ PersistState(URIMap& map) : uri_map(map) {}
+
+ typedef std::map<uint32_t, std::string> URIs;
+ typedef std::map<uint32_t, PersistValue> Values;
+
+ uint32_t file_id_to_runtime_id(uint32_t file_id) const {
+ URIs::const_iterator i = uris.find(file_id);
+ if (i == uris.end()) {
+ error << "LV2 state refers to undefined URI ID" << endmsg;
+ return 0;
+ }
+ return uri_map.uri_to_id(NULL, i->second.c_str());
+ }
+
+ int add_uri(uint32_t file_id, const char* str) {
+ // TODO: check for clashes (invalid file)
+ uris.insert(make_pair(file_id, str));
+ return 0;
+ }
+
+ int add_value(uint32_t file_key,
+ const void* value,
+ size_t size,
+ uint32_t file_type,
+ uint32_t flags) {
+ const uint32_t key = file_id_to_runtime_id(file_key);
+ const uint32_t type = file_id_to_runtime_id(file_type);
+ if (!key || !type) {
+ return 1;
+ }
+
+ Values::const_iterator i = values.find(key);
+ if (i != values.end()) {
+ error << "LV2 state contains duplicate keys" << endmsg;
+ return 1;
+ } else {
+ void* value_copy = malloc(size);
+ memcpy(value_copy, value, size); // FIXME: leak
+ values.insert(
+ make_pair(key,
+ PersistValue(key, value_copy, size, type, flags)));
+ return 0;
+ }
+ }
+
+ URIMap& uri_map;
+ URIs uris;
+ Values values;
+};
+
+int
+LV2Plugin::lv2_persist_store_callback(void* host_data,
+ uint32_t key,
const void* value,
size_t size,
- uint32_t type)
+ uint32_t type,
+ uint32_t flags)
{
- LV2PFile file = (LV2PFile)callback_data;
-
- // FIXME: assumes URIs are mapped in the default context (or not event, at least)
- const char* type_uri = LV2Plugin::_uri_map.id_to_uri(NULL, type);
- cout << "LV2 PERSIST STORE " << key << " = " << value << " :: " << type_uri << endl;
- lv2_pfile_write(file, key, value, size, type_uri);
+ DEBUG_TRACE(DEBUG::LV2, string_compose(
+ "persist store %1 (size: %2, type: %3)\n",
+ _uri_map.id_to_uri(NULL, key),
+ size,
+ _uri_map.id_to_uri(NULL, type)));
+
+ PersistState* state = (PersistState*)host_data;
+ state->add_uri(key, _uri_map.id_to_uri(NULL, key));
+ state->add_uri(type, _uri_map.id_to_uri(NULL, type));
+ return state->add_value(key, value, size, type, flags);
}
const void*
-LV2Plugin::lv2_persist_retrieve_callback(void* callback_data,
- const char* key,
- size_t* size,
- uint32_t* type)
+LV2Plugin::lv2_persist_retrieve_callback(void* host_data,
+ uint32_t key,
+ size_t* size,
+ uint32_t* type,
+ uint32_t* flags)
{
- //LV2PFile file = (LV2PFile)callback_data;
- cout << "LV2 PERSIST RETRIEVE " << key << endl;
- return NULL;
+ PersistState* state = (PersistState*)host_data;
+ PersistState::Values::const_iterator i = state->values.find(key);
+ if (i == state->values.end()) {
+ warning << "LV2 plugin attempted to retrieve nonexistent key: "
+ << _uri_map.id_to_uri(NULL, key) << endmsg;
+ return NULL;
+ }
+ *size = i->second.size;
+ *type = i->second.type;
+ *flags = LV2_PERSIST_IS_POD | LV2_PERSIST_IS_PORTABLE; // FIXME
+ DEBUG_TRACE(DEBUG::LV2, string_compose(
+ "persist retrieve %1 = %2 (size: %3, type: %4)\n",
+ _uri_map.id_to_uri(NULL, key),
+ i->second.value, *size, *type));
+ return i->second.value;
+}
+
+char*
+LV2Plugin::lv2_files_abstract_path(LV2_Files_Host_Data host_data,
+ const char* absolute_path)
+{
+ LV2Plugin* me = (LV2Plugin*)host_data;
+ if (me->_insert_id == PBD::ID("0")) {
+ return g_strdup(absolute_path);
+ }
+
+ const std::string state_dir = Glib::build_filename(me->_session.plugins_dir(),
+ me->_insert_id.to_s());
+
+ char* ret = NULL;
+ if (strncmp(absolute_path, state_dir.c_str(), state_dir.length())) {
+ ret = g_strdup(absolute_path);
+ } else {
+ const std::string path(absolute_path + state_dir.length() + 1);
+ ret = g_strndup(path.c_str(), path.length());
+ }
+
+ DEBUG_TRACE(DEBUG::LV2, string_compose("abstract path %1 => %2\n",
+ absolute_path, ret));
+
+ return ret;
+}
+
+char*
+LV2Plugin::lv2_files_absolute_path(LV2_Files_Host_Data host_data,
+ const char* abstract_path)
+{
+ LV2Plugin* me = (LV2Plugin*)host_data;
+ if (me->_insert_id == PBD::ID("0")) {
+ return g_strdup(abstract_path);
+ }
+
+ char* ret = NULL;
+ if (g_path_is_absolute(abstract_path)) {
+ ret = g_strdup(abstract_path);
+ } else {
+ const std::string apath(abstract_path);
+ const std::string state_dir = Glib::build_filename(me->_session.plugins_dir(),
+ me->_insert_id.to_s());
+ const std::string path = Glib::build_filename(state_dir,
+ apath);
+ ret = g_strndup(path.c_str(), path.length());
+ }
+
+ DEBUG_TRACE(DEBUG::LV2, string_compose("absolute path %1 => %2\n",
+ abstract_path, ret));
+
+ return ret;
+}
+
+char*
+LV2Plugin::lv2_files_new_file_path(LV2_Files_Host_Data host_data,
+ const char* relative_path)
+{
+ LV2Plugin* me = (LV2Plugin*)host_data;
+ if (me->_insert_id == PBD::ID("0")) {
+ return g_strdup(relative_path);
+ }
+
+ const std::string state_dir = Glib::build_filename(me->_session.plugins_dir(),
+ me->_insert_id.to_s());
+ const std::string path = Glib::build_filename(state_dir,
+ relative_path);
+
+ char* dirname = g_path_get_dirname(path.c_str());
+ g_mkdir_with_parents(dirname, 0744);
+ free(dirname);
+
+ DEBUG_TRACE(DEBUG::LV2, string_compose("new file path %1 => %2\n",
+ relative_path, path));
+
+ return g_strndup(path.c_str(), path.length());
}
void
LV2Plugin::add_state(XMLNode* root) const
{
+ assert(_insert_id != PBD::ID("0"));
+
XMLNode* child;
char buf[16];
LocaleGuard lg(X_("POSIX"));
if (_supports_persist) {
// Create state directory for this plugin instance
- const std::string state_filename = _id.to_s() + ".lv2pfile";
+ const std::string state_filename = _insert_id.to_s() + ".rdff";
const std::string state_path = Glib::build_filename(
_session.plugins_dir(), state_filename);
return;
}
- LV2PFile file = lv2_pfile_open(state_path.c_str(), true);
- persist->save(_instance->lv2_handle, &LV2Plugin::lv2_persist_store_callback, file);
- lv2_pfile_close(file);
+ // Save plugin state to state object
+ PersistState state(_uri_map);
+ persist->save(_instance->lv2_handle,
+ &LV2Plugin::lv2_persist_store_callback,
+ &state);
+
+ // Open state file
+ RDFF file = rdff_open(state_path.c_str(), true);
+
+ // Write all referenced URIs to state file
+ for (PersistState::URIs::const_iterator i = state.uris.begin();
+ i != state.uris.end(); ++i) {
+ rdff_write_uri(file,
+ i->first,
+ i->second.length(),
+ i->second.c_str());
+ }
+
+ // Write all values to state file
+ for (PersistState::Values::const_iterator i = state.values.begin();
+ i != state.values.end(); ++i) {
+ const uint32_t key = i->first;
+ const PersistValue& val = i->second;
+ rdff_write_triple(file,
+ 0,
+ key,
+ val.type,
+ val.size,
+ val.value);
+ }
+
+ // Close state file
+ rdff_close(file);
root->add_property("state-file", state_filename);
}
return _ui != NULL;
}
+void
+LV2Plugin::set_insert_info(const PluginInsert* insert)
+{
+ _insert_id = insert->id();
+}
+
int
LV2Plugin::set_state(const XMLNode& node, int version)
{
std::string state_path = Glib::build_filename(_session.plugins_dir(),
prop->value());
+ cout << "LV2 state path " << state_path << endl;
+
// Get LV2 Persist extension data from plugin instance
LV2_Persist* persist = (LV2_Persist*)slv2_instance_get_extension_data(
_instance, "http://lv2plug.in/ns/ext/persist");
if (persist) {
cout << "Loading LV2 state from " << state_path << endl;
- LV2PFile file = lv2_pfile_open(state_path.c_str(), false);
+ RDFF file = rdff_open(state_path.c_str(), false);
+
+ PersistState state(_uri_map);
+
+ // Load file into state object
+ RDFFChunk* chunk = (RDFFChunk*)malloc(sizeof(RDFFChunk));
+ chunk->size = 0;
+ while (!rdff_read_chunk(file, &chunk)) {
+ if (rdff_chunk_is_uri(chunk)) {
+ RDFFURIChunk* body = (RDFFURIChunk*)chunk->data;
+ state.add_uri(body->id, body->uri);
+ } else if (rdff_chunk_is_triple(chunk)) {
+ RDFFTripleChunk* body = (RDFFTripleChunk*)chunk->data;
+ state.add_value(body->predicate,
+ body->object,
+ body->object_size,
+ body->object_type,
+ LV2_PERSIST_IS_POD | LV2_PERSIST_IS_PORTABLE);
+ }
+ }
+ free(chunk);
+
persist->restore(_instance->lv2_handle,
&LV2Plugin::lv2_persist_retrieve_callback,
- file);
- lv2_pfile_close(file);
+ &state);
+ rdff_close(file);
} else {
warning << string_compose(
_("Plugin \"%1\% failed to return LV2 persist data"),