Missing file.
[ardour.git] / libs / ardour / lv2_plugin.cc
index f3675bce129bc8013f11168b000ed59b8245d61a..57242afab3f67414c03e7b318cc2a1d3d07c9da2 100644 (file)
 #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#"
@@ -57,7 +62,7 @@ using namespace std;
 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");
@@ -70,6 +75,7 @@ LV2Plugin::LV2Plugin (AudioEngine& engine,
        : Plugin(engine, session)
        , _world(world)
        , _features(NULL)
+       , _insert_id("0")
 {
        init(world, plugin, rate);
 }
@@ -78,6 +84,7 @@ LV2Plugin::LV2Plugin (const LV2Plugin& other)
        : Plugin(other)
        , _world(other._world)
        , _features(NULL)
+       , _insert_id(other._insert_id)
 {
        init(other._world, other._plugin, other._sample_rate);
 
@@ -90,31 +97,49 @@ LV2Plugin::LV2Plugin (const LV2Plugin& other)
 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);
@@ -182,6 +207,21 @@ LV2Plugin::init(LV2World& world, SLV2Plugin plugin, framecnt_t rate)
 
        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);
@@ -190,6 +230,7 @@ LV2Plugin::init(LV2World& world, SLV2Plugin plugin, framecnt_t rate)
                                break;
                        }
                }
+#endif
 
                // If Gtk UI is not available, try to find external UI
                if (!_ui) {
@@ -216,6 +257,7 @@ LV2Plugin::~LV2Plugin ()
        slv2_instance_free(_instance);
        slv2_value_free(_name);
        slv2_value_free(_author);
+       slv2_value_free(_ui_type);
 
        delete [] _control_data;
        delete [] _shadow_data;
@@ -255,7 +297,7 @@ void
 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;
@@ -296,35 +338,195 @@ LV2Plugin::nth_parameter(uint32_t n, bool& ok) const
        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"));
@@ -341,7 +543,7 @@ LV2Plugin::add_state(XMLNode* root) const
 
        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);
 
@@ -357,9 +559,39 @@ LV2Plugin::add_state(XMLNode* root) const
                        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);
        }
@@ -464,6 +696,12 @@ LV2Plugin::has_editor() const
        return _ui != NULL;
 }
 
+void
+LV2Plugin::set_insert_info(const PluginInsert* insert)
+{
+       _insert_id = insert->id();
+}
+
 int
 LV2Plugin::set_state(const XMLNode& node, int version)
 {
@@ -521,16 +759,39 @@ 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"),