From fd47b579b50dde747ae71ff94d34625dae3cb477 Mon Sep 17 00:00:00 2001 From: David Robillard Date: Wed, 23 Nov 2011 06:39:45 +0000 Subject: [PATCH] Implement latest LV2 state extension (0.4). Corresponding patch to LinuxSampler available here: http://drobilla.net/files/linuxsampler_lv2_state_0_4.diff git-svn-id: svn://localhost/ardour2/branches/3.0@10788 d708f5d6-7413-0410-9779-e7cbd77b26cf --- libs/ardour/ardour/lv2_plugin.h | 13 +- libs/ardour/ardour/lv2_state.h | 12 +- .../lv2/lv2plug.in/ns/ext/files/files.h | 140 ------------------ .../lv2/lv2plug.in/ns/ext/state/state.h | 96 ++++++++++++ libs/ardour/lv2_plugin_lilv.cc | 119 ++++++++------- 5 files changed, 180 insertions(+), 200 deletions(-) delete mode 100644 libs/ardour/lv2/lv2plug.in/ns/ext/files/files.h diff --git a/libs/ardour/ardour/lv2_plugin.h b/libs/ardour/ardour/lv2_plugin.h index 95f730a43e..adbb26b832 100644 --- a/libs/ardour/ardour/lv2_plugin.h +++ b/libs/ardour/ardour/lv2_plugin.h @@ -143,11 +143,12 @@ class LV2Plugin : public ARDOUR::Plugin LV2_DataAccess _data_access_extension_data; LV2_Feature _data_access_feature; LV2_Feature _instance_access_feature; - LV2_Feature _path_support_feature; - LV2_Feature _new_file_support_feature; + LV2_Feature _map_path_feature; + LV2_Feature _make_path_feature; static URIMap _uri_map; static uint32_t _midi_event_type; + static uint32_t _state_path_type; static int lv2_state_store_callback (void* handle, @@ -164,12 +165,12 @@ class LV2Plugin : public ARDOUR::Plugin uint32_t* type, uint32_t* flags); - static char* lv2_files_abstract_path (void* host_data, + static char* lv2_state_abstract_path (void* host_data, const char* absolute_path); - static char* lv2_files_absolute_path (void* host_data, + static char* lv2_state_absolute_path (void* host_data, const char* abstract_path); - static char* lv2_files_new_file_path (void* host_data, - const char* relative_path); + static char* lv2_state_make_path (void* host_data, + const char* path); void init (void* c_plugin, framecnt_t rate); void run (pframes_t nsamples); diff --git a/libs/ardour/ardour/lv2_state.h b/libs/ardour/ardour/lv2_state.h index 678c5482ed..6c37ebcd0b 100644 --- a/libs/ardour/ardour/lv2_state.h +++ b/libs/ardour/ardour/lv2_state.h @@ -34,8 +34,10 @@ namespace ARDOUR { +class LV2Plugin; + struct LV2State { - LV2State(URIMap& map) : uri_map(map) {} + LV2State(const LV2Plugin& plug, URIMap& map) : plugin(plug), uri_map(map) {} struct Value { inline Value(uint32_t k, const void* v, size_t s, uint32_t t, uint32_t f) @@ -75,6 +77,7 @@ struct LV2State { 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) { + PBD::error << "Invalid file key or type" << endmsg; return 1; } @@ -133,9 +136,10 @@ struct LV2State { } } - URIMap& uri_map; - URIs uris; - Values values; + const LV2Plugin& plugin; + URIMap& uri_map; + URIs uris; + Values values; }; } // namespace ARDOUR diff --git a/libs/ardour/lv2/lv2plug.in/ns/ext/files/files.h b/libs/ardour/lv2/lv2plug.in/ns/ext/files/files.h deleted file mode 100644 index f996e2ed16..0000000000 --- a/libs/ardour/lv2/lv2plug.in/ns/ext/files/files.h +++ /dev/null @@ -1,140 +0,0 @@ -/* - Copyright 2010-2011 David Robillard - - Permission is hereby granted, free of charge, to any person obtaining a - copy of this software and associated documentation files (the "Software"), - to deal in the Software without restriction, including without limitation - the rights to use, copy, modify, merge, publish, distribute, sublicense, - and/or sell copies of the Software, and to permit persons to whom the - Software is furnished to do so, subject to the following conditions: - - The above copyright notice and this permission notice shall be included - in all copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR - OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, - ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR - OTHER DEALINGS IN THE SOFTWARE. -*/ - -/** - @file files.h - C API for the LV2 Files extension . -*/ - -#ifndef LV2_FILES_H -#define LV2_FILES_H - -#ifdef __cplusplus -extern "C" { -#endif - -#define LV2_FILES_URI "http://lv2plug.in/ns/ext/files" -#define LV2_FILES_PATH_SUPPORT_URI LV2_FILES_URI "#pathSupport" -#define LV2_FILES_NEW_FILE_SUPPORT_URI LV2_FILES_URI "#newFileSupport" - -typedef void* LV2_Files_Host_Data; - -/** - files:pathSupport feature struct. - - To support this feature, the host MUST pass an LV2_Feature struct with @a - URI @ref LV2_FILES_PATH_SUPPORT_URI and @a data pointed to an instance of - this struct. -*/ -typedef struct { - - /** - Opaque host data. - */ - LV2_Files_Host_Data host_data; - - /** - Map an absolute path to an abstract path for use in plugin state. - @param host_data MUST be the @a host_data member of this struct. - @param absolute_path The absolute path of a file. - @return An abstract path suitable for use in plugin state. - - The plugin MUST use this function to map any paths that will be stored - in plugin state. The returned value is an abstract path which MAY not - be an actual file system path; @ref absolute_path MUST be used to map it - to an actual path in order to use the file. - - Hosts MAY map paths in any way (e.g. by creating symbolic links within - the plugin's state directory or storing a list of referenced files for - later export). Plugins MUST NOT make any assumptions about abstract - paths except that they can be mapped to an absolute path using @ref - absolute_path. Particularly when restoring from state, this absolute - path MAY not be the same as the original absolute path, but the host - MUST guarantee it refers to a file with contents equivalent to the - original. - - This function may only be called within the context of - LV2_Persist.save() or LV2_Persist.restore(). The caller is responsible - for freeing the returned value. - */ - char* (*abstract_path)(LV2_Files_Host_Data host_data, - const char* absolute_path); - - /** - Map an abstract path from plugin state to an absolute path. - @param host_data MUST be the @a host_data member of this struct. - @param abstract_path An abstract path (e.g. a path from plugin state). - @return An absolute file system path. - - Since abstract paths are not necessarily actual file paths (or at least - not necessarily absolute paths), this function MUST be used in order to - actually open or otherwise use the file referred to by an abstract path. - - This function may only be called within the context of - LV2_Persist.save() or LV2_Persist.restore(). The caller is responsible - for freeing the returned value. - */ - char* (*absolute_path)(LV2_Files_Host_Data host_data, - const char* abstract_path); - -} LV2_Files_Path_Support; - -/** - files:newFileSupport feature struct. - - To support this feature, the host MUST pass an LV2_Feature struct with @a - URI @ref LV2_FILES_NEW_FILE_SUPPORT_URI and @a data pointed to an instance - of this struct. -*/ -typedef struct { - - /** - Opaque host data. - */ - LV2_Files_Host_Data host_data; - - /** - Return an absolute path the plugin may use to create a new file. - @param host_data MUST be the @a host_data member of this struct. - @param relative_path The relative path of the file. - @return The absolute path to use for the new file. - - The plugin can assume @a relative_path is relative to a namespace - dedicated to that plugin instance; hosts MUST ensure this, e.g. by - giving each plugin instance its own state directory. The returned path - is absolute and thus suitable for creating and using a file, but NOT - suitable for storing in plugin state (it MUST be mapped to an abstract - path using @ref LV2_Files_Path_Support::abstract_path to do so). - - This function may be called from any non-realtime context. The caller - is responsible for freeing the returned value. - */ - char* (*new_file_path)(LV2_Files_Host_Data host_data, - const char* relative_path); - -} LV2_Files_New_File_Support; - -#ifdef __cplusplus -} /* extern "C" */ -#endif - -#endif /* LV2_FILES_H */ diff --git a/libs/ardour/lv2/lv2plug.in/ns/ext/state/state.h b/libs/ardour/lv2/lv2plug.in/ns/ext/state/state.h index 4e1c28a3b8..3d390120d7 100644 --- a/libs/ardour/lv2/lv2plug.in/ns/ext/state/state.h +++ b/libs/ardour/lv2/lv2plug.in/ns/ext/state/state.h @@ -36,8 +36,13 @@ extern "C" { #define LV2_STATE_URI "http://lv2plug.in/ns/ext/state" #define LV2_STATE_INTERFACE_URI LV2_STATE_URI "#Interface" +#define LV2_STATE_PATH_URI LV2_STATE_URI "#Path" +#define LV2_STATE_MAP_PATH_URI LV2_STATE_URI "#pathMap" +#define LV2_STATE_MAKE_PATH_URI LV2_STATE_URI "#newPath" typedef void* LV2_State_Handle; +typedef void* LV2_State_Map_Path_Handle; +typedef void* LV2_State_Make_Path_Handle; /** Flags describing value characteristics. @@ -251,6 +256,97 @@ typedef struct _LV2_State_Interface { } LV2_State_Interface; +/** + Feature data for state:pathMap (LV2_STATE_MAP_PATH_URI). +*/ +typedef struct { + + /** + Opaque host data. + */ + LV2_State_Map_Path_Handle handle; + + /** + Map an absolute path to an abstract path for use in plugin state. + @param handle MUST be the @a handle member of this struct. + @param absolute_path The absolute path of a file. + @return An abstract path suitable for use in plugin state. + + The plugin MUST use this function to map any paths that will be stored + in files in plugin state. The returned value is an abstract path which + MAY not be an actual file system path; @ref absolute_path MUST be used + to map it to an actual path in order to use the file. + + Hosts MAY map paths in any way (e.g. by creating symbolic links within + the plugin's state directory or storing a list of referenced files + elsewhere). Plugins MUST NOT make any assumptions about abstract paths + except that they can be mapped back to an absolute path using @ref + absolute_path. + + This function may only be called within the context of + LV2_State_Interface.save() or LV2_State_Interface.restore(). The caller + is responsible for freeing the returned value. + */ + char* (*abstract_path)(LV2_State_Map_Path_Handle handle, + const char* absolute_path); + + /** + Map an abstract path from plugin state to an absolute path. + @param handle MUST be the @a handle member of this struct. + @param abstract_path An abstract path (e.g. a path from plugin state). + @return An absolute file system path. + + Since abstract paths are not necessarily actual file paths (or at least + not necessarily absolute paths), this function MUST be used in order to + actually open or otherwise use the file referred to by an abstract path. + + This function may only be called within the context of + LV2_State_Interface.save() or LV2_State_Interface.restore(). The caller + is responsible for freeing the returned value. + */ + char* (*absolute_path)(LV2_State_Map_Path_Handle handle, + const char* abstract_path); + +} LV2_State_Map_Path; + +/** + Feature data for state:makePath (@ref LV2_STATE_MAKE_PATH_URI). +*/ +typedef struct { + + /** + Opaque host data. + */ + LV2_State_Make_Path_Handle handle; + + /** + Return a path the plugin may use to create a new file. + @param handle MUST be the @a handle member of this struct. + @param path The path of the new file relative to a namespace unique + to this plugin instance. + @return The absolute path to use for the new file. + + This function can be used by plugins to create files and directories, + either at state saving time (if this feature is passed to + LV2_State_Interface.save()) or any time (if this feature is passed to + LV2_Descriptor.instantiate()). + + The host must do whatever is necessary for the plugin to be able to + create a file at the returned path (e.g. using fopen), including + creating any leading directories. + + If this function is passed to LV2_Descriptor.instantiate(), it may be + called from any non-realtime context. If it is passed to + LV2_State_Interface.save(), it may only be called within the dynamic + scope of that function call. + + The caller is responsible for freeing the returned value with free(). + */ + char* (*path)(LV2_State_Make_Path_Handle handle, + const char* path); + +} LV2_State_Make_Path; + #ifdef __cplusplus } /* extern "C" */ #endif diff --git a/libs/ardour/lv2_plugin_lilv.cc b/libs/ardour/lv2_plugin_lilv.cc index bee0f3846f..271ab4618e 100644 --- a/libs/ardour/lv2_plugin_lilv.cc +++ b/libs/ardour/lv2_plugin_lilv.cc @@ -51,7 +51,6 @@ #include -#include "lv2/lv2plug.in/ns/ext/files/files.h" #include "lv2/lv2plug.in/ns/ext/state/state.h" #include "rdff.h" #ifdef HAVE_SUIL @@ -60,7 +59,6 @@ #define NS_DC "http://dublincore.org/documents/dcmi-namespace/" #define NS_LV2 "http://lv2plug.in/ns/lv2core#" -#define NS_STATE "http://lv2plug.in/ns/ext/state#" #define NS_PSET "http://lv2plug.in/ns/dev/presets#" #define NS_UI "http://lv2plug.in/ns/extensions/ui#" @@ -72,6 +70,8 @@ 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"); +uint32_t LV2Plugin::_state_path_type = _uri_map.uri_to_id( + NULL, LV2_STATE_PATH_URI); class LV2World : boost::noncopyable { public: @@ -137,18 +137,18 @@ LV2Plugin::init(void* c_plugin, framecnt_t rate) { DEBUG_TRACE(DEBUG::LV2, "init\n"); - _impl->plugin = (LilvPlugin*)c_plugin; - _impl->ui = NULL; - _impl->ui_type = NULL; + _impl->plugin = (LilvPlugin*)c_plugin; + _impl->ui = NULL; + _impl->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"; - _path_support_feature.URI = LV2_FILES_PATH_SUPPORT_URI; - _new_file_support_feature.URI = LV2_FILES_NEW_FILE_SUPPORT_URI; + _instance_access_feature.URI = "http://lv2plug.in/ns/ext/instance-access"; + _data_access_feature.URI = "http://lv2plug.in/ns/ext/data-access"; + _map_path_feature.URI = LV2_STATE_MAP_PATH_URI; + _make_path_feature.URI = LV2_STATE_MAKE_PATH_URI; LilvPlugin* plugin = _impl->plugin; @@ -170,25 +170,25 @@ LV2Plugin::init(void* c_plugin, framecnt_t rate) _features = (LV2_Feature**)malloc(sizeof(LV2_Feature*) * 8); _features[0] = &_instance_access_feature; _features[1] = &_data_access_feature; - _features[2] = &_path_support_feature; - _features[3] = &_new_file_support_feature; + _features[2] = &_map_path_feature; + _features[3] = &_make_path_feature; _features[4] = _uri_map.uri_map_feature(); _features[5] = _uri_map.urid_map_feature(); _features[6] = _uri_map.urid_unmap_feature(); _features[7] = 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_State_Map_Path* map_path = (LV2_State_Map_Path*)malloc( + sizeof(LV2_State_Map_Path)); + map_path->handle = this; + map_path->abstract_path = &lv2_state_abstract_path; + map_path->absolute_path = &lv2_state_absolute_path; + _map_path_feature.data = map_path; - 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; + LV2_State_Make_Path* make_path = (LV2_State_Make_Path*)malloc( + sizeof(LV2_State_Make_Path)); + make_path->handle = this; + make_path->path = &lv2_state_make_path; + _make_path_feature.data = make_path; _impl->instance = lilv_plugin_instantiate(plugin, rate, _features); _impl->name = lilv_plugin_get_name(plugin); @@ -463,15 +463,29 @@ LV2Plugin::lv2_state_store_callback(LV2_State_Handle handle, uint32_t flags) { DEBUG_TRACE(DEBUG::LV2, string_compose( - "state store %1 (size: %2, type: %3)\n", + "state store %1 (size: %2, type: %3, flags: %4)\n", _uri_map.id_to_uri(NULL, key), size, - _uri_map.id_to_uri(NULL, type))); + _uri_map.id_to_uri(NULL, type), + flags)); LV2State* state = (LV2State*)handle; 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); + + if (type == _state_path_type) { + const LV2Plugin& me = state->plugin; + LV2_State_Map_Path* mp = (LV2_State_Map_Path*)me._map_path_feature.data; + return state->add_value( + key, + lv2_state_abstract_path(mp->handle, (const char*)value), + size, type, flags); + } else if ((flags & LV2_STATE_IS_POD) && (flags & LV2_STATE_IS_PORTABLE)) { + return state->add_value(key, value, size, type, flags); + } else { + PBD::warning << "LV2 plugin attempted to store non-portable property." << endl; + return -1; + } } const void* @@ -488,21 +502,27 @@ LV2Plugin::lv2_state_retrieve_callback(LV2_State_Handle host_data, << _uri_map.id_to_uri(NULL, key) << endmsg; return NULL; } - *size = i->second.size; - *type = i->second.type; - *flags = LV2_STATE_IS_POD | LV2_STATE_IS_PORTABLE; // FIXME + *size = i->second.size; + *type = i->second.type; + *flags = LV2_STATE_IS_POD | LV2_STATE_IS_PORTABLE; DEBUG_TRACE(DEBUG::LV2, string_compose( "state retrieve %1 = %2 (size: %3, type: %4)\n", _uri_map.id_to_uri(NULL, key), i->second.value, *size, *type)); - return i->second.value; + if (*type == _state_path_type) { + const LV2Plugin& me = state->plugin; + LV2_State_Map_Path* mp = (LV2_State_Map_Path*)me._map_path_feature.data; + return lv2_state_absolute_path(mp->handle, (const char*)i->second.value); + } else { + return i->second.value; + } } char* -LV2Plugin::lv2_files_abstract_path(LV2_Files_Host_Data host_data, - const char* absolute_path) +LV2Plugin::lv2_state_abstract_path(LV2_State_Map_Path_Handle handle, + const char* absolute_path) { - LV2Plugin* me = (LV2Plugin*)host_data; + LV2Plugin* me = (LV2Plugin*)handle; if (me->_insert_id == PBD::ID("0")) { return g_strdup(absolute_path); } @@ -525,10 +545,10 @@ LV2Plugin::lv2_files_abstract_path(LV2_Files_Host_Data host_data, } char* -LV2Plugin::lv2_files_absolute_path(LV2_Files_Host_Data host_data, - const char* abstract_path) +LV2Plugin::lv2_state_absolute_path(LV2_State_Map_Path_Handle handle, + const char* abstract_path) { - LV2Plugin* me = (LV2Plugin*)host_data; + LV2Plugin* me = (LV2Plugin*)handle; if (me->_insert_id == PBD::ID("0")) { return g_strdup(abstract_path); } @@ -552,27 +572,26 @@ LV2Plugin::lv2_files_absolute_path(LV2_Files_Host_Data host_data, } char* -LV2Plugin::lv2_files_new_file_path(LV2_Files_Host_Data host_data, - const char* relative_path) +LV2Plugin::lv2_state_make_path(LV2_State_Make_Path_Handle handle, + const char* path) { - LV2Plugin* me = (LV2Plugin*)host_data; + LV2Plugin* me = (LV2Plugin*)handle; if (me->_insert_id == PBD::ID("0")) { - return g_strdup(relative_path); + return g_strdup(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); + const std::string abs_path = Glib::build_filename( + me->_session.plugins_dir(), + me->_insert_id.to_s(), + path); - char* dirname = g_path_get_dirname(path.c_str()); - g_mkdir_with_parents(dirname, 0744); - free(dirname); + const std::string dirname = Glib::path_get_dirname(abs_path); + g_mkdir_with_parents(dirname.c_str(), 0744); DEBUG_TRACE(DEBUG::LV2, string_compose("new file path %1 => %2\n", - relative_path, path)); + path, abs_path)); - return g_strndup(path.c_str(), path.length()); + return g_strndup(abs_path.c_str(), abs_path.length()); } void @@ -614,7 +633,7 @@ LV2Plugin::add_state(XMLNode* root) const } // Save plugin state to state object - LV2State state(_uri_map); + LV2State state(*this, _uri_map); state_iface->save(_impl->instance->lv2_handle, &LV2Plugin::lv2_state_store_callback, &state, @@ -781,7 +800,7 @@ LV2Plugin::set_state(const XMLNode& node, int version) if (state_iface) { cout << "Loading LV2 state from " << state_path << endl; RDFF file = rdff_open(state_path.c_str(), false); - LV2State state(_uri_map); + LV2State state(*this, _uri_map); state.read(file); state_iface->restore(_impl->instance->lv2_handle, &LV2Plugin::lv2_state_retrieve_callback,