From 2c0cd4d430f8766e224d04e1ebcc07a91b9eef92 Mon Sep 17 00:00:00 2001 From: David Robillard Date: Mon, 28 Mar 2011 23:54:08 +0000 Subject: Implement most recent LV2 persist extension. Plugin state data is saved to a simple RIFF-based binary file. Cross-endianness and non-POD data not yet implemented. git-svn-id: svn://localhost/ardour2/branches/3.0@9220 d708f5d6-7413-0410-9779-e7cbd77b26cf --- libs/ardour/ardour/lv2_plugin.h | 22 +-- libs/ardour/lv2_pfile.c | 233 +++++++++++++++++---------- libs/ardour/lv2_pfile.h | 115 +++++++++---- libs/ardour/lv2_plugin.cc | 170 +++++++++++++++++--- libs/ardour/lv2ext/lv2_persist.h | 337 +++++++++++++++++++++------------------ 5 files changed, 574 insertions(+), 303 deletions(-) (limited to 'libs') diff --git a/libs/ardour/ardour/lv2_plugin.h b/libs/ardour/ardour/lv2_plugin.h index a1791661bf..71d9bfa624 100644 --- a/libs/ardour/ardour/lv2_plugin.h +++ b/libs/ardour/ardour/lv2_plugin.h @@ -149,16 +149,18 @@ class LV2Plugin : public ARDOUR::Plugin static URIMap _uri_map; static uint32_t _midi_event_type; - static void lv2_persist_store_callback (void* callback_data, - const char* key, - const void* value, - size_t size, - uint32_t type); - - static const void* lv2_persist_retrieve_callback (void* callback_data, - const char* key, - size_t* size, - uint32_t* type); + static int lv2_persist_store_callback (void* callback_data, + uint32_t key, + const void* value, + size_t size, + uint32_t type, + bool pod); + + static const void* lv2_persist_retrieve_callback (void* callback_data, + uint32_t key, + size_t* size, + uint32_t* type, + bool* pod); void init (LV2World& world, SLV2Plugin plugin, framecnt_t rate); void run (pframes_t nsamples); diff --git a/libs/ardour/lv2_pfile.c b/libs/ardour/lv2_pfile.c index f5c1c02a78..cdf14b2ed2 100644 --- a/libs/ardour/lv2_pfile.c +++ b/libs/ardour/lv2_pfile.c @@ -1,21 +1,24 @@ -/* Portable file-based implementation of LV2 Persist. - * See for details. - * Copyright (C) 2010 David Robillard - * - * This file 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 file 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 file; if not, write to the Free Software Foundation, Inc., - * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. - */ +/* + Portable file-based LV2 Persist implementation. + See for details. + + Copyright 2011 David Robillard + + This 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 software 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 sofware; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + 02110-1301 USA. +*/ #include #include @@ -25,8 +28,16 @@ #include "lv2_pfile.h" +#define CHUNK_ID_LEN 4 + +static const char FILE_TYPE[CHUNK_ID_LEN] = "LV2F"; /* LV2 RIFF File */ +static const char CHUNK_KVAL[CHUNK_ID_LEN] = "KVAL"; /* Key/Value Chunk */ +static const char CHUNK_URID[CHUNK_ID_LEN] = "URID"; /* URI ID Chunk */ + struct _LV2PFile { - FILE* fd; + FILE* fd; + uint32_t size; + bool write; }; LV2PFile @@ -38,88 +49,125 @@ lv2_pfile_open(const char* path, bool write) return NULL; } - static const char* const magic = "LV2PFILE"; - static const size_t magic_len = 8; + uint32_t size = 0; + if (write) { - fwrite(magic, magic_len, 1, fd); + fwrite("RIFF", CHUNK_ID_LEN, 1, fd); /* RIFF chunk ID */ + fwrite(&size, sizeof(size), 1, fd); /* RIFF chunk size */ + fwrite(FILE_TYPE, CHUNK_ID_LEN, 1, fd); /* LV2 RIFF file type */ } else { - char file_magic[magic_len]; - if (fread(file_magic, magic_len, 1, fd) != 1 - || strncmp(file_magic, magic, magic_len)) { + char magic[CHUNK_ID_LEN]; + if (fread(magic, CHUNK_ID_LEN, 1, fd) != 1 + || strncmp(magic, "RIFF", CHUNK_ID_LEN)) { + fclose(fd); + fprintf(stderr, "%s: error: not a RIFF file\n", path); + return NULL; + } + + if (fread(&size, sizeof(size), 1, fd) != 1) { + fclose(fd); + fprintf(stderr, "%s: error: missing RIFF chunk size\n", path); + return NULL; + } + + if (fread(magic, CHUNK_ID_LEN, 1, fd) != 1 + || strncmp(magic, FILE_TYPE, CHUNK_ID_LEN)) { fclose(fd); + fprintf(stderr, "%s: error: not an LV2 RIFF file\n", path); return NULL; } } - LV2PFile ret = (LV2PFile)malloc(sizeof(LV2PFile)); - ret->fd = fd; + LV2PFile ret = (LV2PFile)malloc(sizeof(struct _LV2PFile)); + ret->fd = fd; + ret->size = size; + ret->write = write; return ret; } -LV2PFileStatus -lv2_pfile_write(LV2PFile file, - const char* key, - const void* value, - uint64_t size, - const char* type) -{ #define WRITE(ptr, size, nmemb, stream) \ if (fwrite(ptr, size, nmemb, stream) != nmemb) { \ return LV2_PFILE_UNKNOWN_ERROR; \ } - const uint32_t key_len = strlen(key); - WRITE(&key_len, sizeof(key_len), 1, file->fd); - WRITE(key, key_len + 1, 1, file->fd); - - const uint32_t type_len = strlen(type); - WRITE(&type_len, sizeof(type_len), 1, file->fd); - WRITE(type, type_len + 1, 1, file->fd); - - WRITE(&size, sizeof(size), 1, file->fd); - WRITE(value, size, 1, file->fd); +LV2PFileStatus +lv2_pfile_write_uri(LV2PFile file, + uint32_t id, + const char* uri, + uint32_t len) +{ + const uint32_t chunk_size = sizeof(id) + len + 1; + WRITE(CHUNK_URID, CHUNK_ID_LEN, 1, file->fd); + WRITE(&chunk_size, sizeof(chunk_size), 1, file->fd); + WRITE(&id, sizeof(id), 1, file->fd); + WRITE(uri, len + 1, 1, file->fd); + if ((chunk_size % 2)) { + WRITE("", 1, 1, file->fd); /* pad */ + } + file->size += 8 + chunk_size; + return LV2_PFILE_OK; +} +LV2PFileStatus +lv2_pfile_write_value(LV2PFile file, + uint32_t key, + const void* value, + uint32_t size, + uint32_t type) +{ + const uint32_t chunk_size = sizeof(key) + sizeof(type) + sizeof(size) + size; + WRITE(CHUNK_KVAL, CHUNK_ID_LEN, 1, file->fd); + WRITE(&chunk_size, sizeof(chunk_size), 1, file->fd); + WRITE(&key, sizeof(key), 1, file->fd); + WRITE(&type, sizeof(type), 1, file->fd); + WRITE(&size, sizeof(size), 1, file->fd); + WRITE(value, size, 1, file->fd); + if ((size % 2)) { + WRITE("", 1, 1, file->fd); /* write pad */ + } + file->size += 8 + chunk_size; return LV2_PFILE_OK; } LV2PFileStatus -lv2_pfile_read(LV2PFile file, - char** key, - uint32_t* key_len, - char** type, - uint32_t* type_len, - void** value, - uint64_t* size) +lv2_pfile_read_chunk(LV2PFile file, + LV2PFileChunkHeader** buf) { if (feof(file->fd)) return LV2_PFILE_EOF; #define READ(ptr, size, nmemb, stream) \ if (fread(ptr, size, nmemb, stream) != nmemb) { \ - assert(false); \ return LV2_PFILE_CORRUPT; \ } - READ(key_len, sizeof(*key_len), 1, file->fd); - *key = (char*)malloc(*key_len + 1); - READ(*key, *key_len + 1, 1, file->fd); - - READ(type_len, sizeof(*type_len), 1, file->fd); - *type = (char*)malloc(*type_len + 1); - READ(*type, *type_len + 1, 1, file->fd); - - READ(size, sizeof(*size), 1, file->fd); - *value = malloc(*size); - READ(*value, *size, 1, file->fd); + const uint32_t alloc_size = (*buf)->size; + READ((*buf)->type, sizeof((*buf)->type), 1, file->fd); + READ(&(*buf)->size, sizeof((*buf)->size), 1, file->fd); + if ((*buf)->size > alloc_size) { + *buf = realloc(*buf, sizeof(LV2PFileChunkHeader) + (*buf)->size); + } + READ((*buf)->data, (*buf)->size, 1, file->fd); + if (((*buf)->size % 2)) { + char pad; + READ(&pad, 1, 1, file->fd); /* skip pad */ + } return LV2_PFILE_OK; } void lv2_pfile_close(LV2PFile file) { - if (file) + if (file) { + if (file->write) { + fseek(file->fd, 4, SEEK_SET); + if (fwrite(&file->size, sizeof(file->size), 1, file->fd) != 1) { + fprintf(stderr, "failed to write RIFF header size\n"); + } + } fclose(file->fd); + } free(file); } @@ -140,14 +188,23 @@ main(int argc, char** argv) if (!file) goto fail; - char wkey[6]; - char wval[6]; - const char* wtype = "http://example.org/type"; -#define NUM_RECORDS 32 - for (int i = 0; i < NUM_RECORDS; ++i) { - snprintf(wkey, sizeof(wkey), "KEY%02d", i); - snprintf(wval, sizeof(wval), "VAL%02d", i); - lv2_pfile_write(file, wkey, wval, strlen(wval) + 1, wtype); + static const int N_URIS = 16; + static const int N_RECORDS = 16; + + char uri[64]; + for (int i = 0; i < N_URIS; ++i) { + snprintf(uri, sizeof(uri), "http://example.org/uri%02d", i + 1); + lv2_pfile_write_uri(file, i + 1, uri, strlen(uri) + 1); + } + + char val[6]; + for (int i = 0; i < N_RECORDS; ++i) { + snprintf(val, sizeof(val), "VAL%02d", i); + lv2_pfile_write_value(file, + rand() % N_URIS, + val, + sizeof(val), + 0); } lv2_pfile_close(file); @@ -156,23 +213,31 @@ main(int argc, char** argv) if (!file) goto fail; - char* rkey; - uint32_t rkey_len; - char* rtype; - uint32_t rtype_len; - uint64_t rsize; - void* rval; - for (int i = 0; i < NUM_RECORDS; ++i) { - if (lv2_pfile_read(file, &rkey, &rkey_len, &rtype, &rtype_len, &rval, &rsize)) + LV2PFileChunkHeader* chunk = malloc(sizeof(LV2PFileChunkHeader)); + chunk->size = 0; + for (int i = 0; i < N_URIS; ++i) { + if (lv2_pfile_read_chunk(file, &chunk) + || strncmp(chunk->type, "URID", 4)) { + fprintf(stderr, "error: expected URID chunk\n"); goto fail; + } + LV2PFileURIChunk* body = (LV2PFileURIChunk*)chunk->data; + printf("URI: %s\n", body->uri); + } - printf("%s = %s :: %s\n", rkey, (char*)rval, rtype); - free(rkey); - free(rtype); - free(rval); + for (int i = 0; i < N_RECORDS; ++i) { + if (lv2_pfile_read_chunk(file, &chunk) + || strncmp(chunk->type, "KVAL", 4)) { + fprintf(stderr, "error: expected KVAL chunk\n"); + goto fail; + } + LV2PFileValueChunk* body = (LV2PFileValueChunk*)chunk->data; + printf("KEY %d = %s\n", body->key, body->value); } + free(chunk); lv2_pfile_close(file); + return 0; fail: diff --git a/libs/ardour/lv2_pfile.h b/libs/ardour/lv2_pfile.h index be4611b87e..8cc97875db 100644 --- a/libs/ardour/lv2_pfile.h +++ b/libs/ardour/lv2_pfile.h @@ -1,24 +1,37 @@ -/* Portable file-based implementation of LV2 Persist. - * See for details. - * - * This file 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 file 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 file; if not, write to the Free Software Foundation, Inc., - * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. - */ +/* + Portable file-based LV2 Persist implementation. + See for details. + + Copyright 2011 David Robillard + + This 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 software 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 sofware; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + 02110-1301 USA. +*/ + +#ifndef LV2PFILE_H +#define LV2PFILE_H #include #include +#ifdef __GNUC__ +# define PACKED __attribute__((__packed__)) +#else +# define PACKED +#endif + #ifdef __cplusplus extern "C" { #endif @@ -33,21 +46,57 @@ typedef enum { LV2_PFILE_CORRUPT = 3 } LV2PFileStatus; -/** Open/Create a new persist file. */ +typedef struct { + char type[4]; + uint32_t size; + char data[]; +} PACKED LV2PFileChunkHeader; + +typedef struct { + uint32_t id; + char uri[]; +} PACKED LV2PFileURIChunk; + +typedef struct { + uint32_t key; + uint32_t type; + uint32_t size; + char value[]; +} PACKED LV2PFileValueChunk; + +/** + Open/Create a new persist file. +*/ LV2PFile lv2_pfile_open(const char* path, bool write); -/** Write a record to a persist file. */ +/** + Write a URI ID to @a file. +*/ LV2PFileStatus -lv2_pfile_write(LV2PFile file, - const char* key, - const void* value, - uint64_t size, - const char* type); - -/** Read a record from a persist file. - * @a key and @a value are allocated with malloc and must be freed by caller. - */ +lv2_pfile_write_uri(LV2PFile file, + uint32_t id, + const char* uri, + uint32_t size); + +/** + Write a key/value record to @a file. +*/ +LV2PFileStatus +lv2_pfile_write_value(LV2PFile file, + uint32_t key, + const void* value, + uint32_t size, + uint32_t type); +LV2PFileStatus +lv2_pfile_read_chunk(LV2PFile file, + LV2PFileChunkHeader** buf); + +/** + Read a record from a persist file. + @a key and @a value are allocated with malloc and must be freed by caller. +*/ +#if 0 LV2PFileStatus lv2_pfile_read(LV2PFile file, char** key, @@ -56,13 +105,17 @@ lv2_pfile_read(LV2PFile file, uint32_t* type_len, void** value, uint64_t* size); +#endif -/** Close a persist file. - * After this call, @a file is invalid. - */ +/** + Close @a file. + After this call, @a file is invalid. +*/ void lv2_pfile_close(LV2PFile file); #ifdef __cplusplus } /* extern "C" */ #endif + +#endif /* LV2PFILE_H */ diff --git a/libs/ardour/lv2_plugin.cc b/libs/ardour/lv2_plugin.cc index f3675bce12..f2016e6340 100644 --- a/libs/ardour/lv2_plugin.cc +++ b/libs/ardour/lv2_plugin.cc @@ -57,7 +57,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"); @@ -296,30 +296,108 @@ LV2Plugin::nth_parameter(uint32_t n, bool& ok) const return 0; } -void +struct PersistValue { + inline PersistValue(uint32_t k, const void* v, size_t s, uint32_t t, bool p) + : key(k), value(v), size(s), type(t), pod(p) + {} + + const uint32_t key; + const void* value; + const size_t size; + const uint32_t type; + const bool pod; +}; + +struct PersistState { + PersistState(URIMap& map) : uri_map(map) {} + + typedef std::map URIs; + typedef std::map 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, + bool pod) { + 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, pod))); + return 0; + } + } + + URIMap& uri_map; + URIs uris; + Values values; +}; + +int LV2Plugin::lv2_persist_store_callback(void* callback_data, - const char* key, + uint32_t key, const void* value, size_t size, - uint32_t type) + uint32_t type, + bool pod) { - 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); + cout << "LV2 PERSIST STORE " << key + << " = " << value + << " :: " << type + << " POD: " << pod << endl; + + PersistState* state = (PersistState*)callback_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, pod); } 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* callback_data, + uint32_t key, + size_t* size, + uint32_t* type, + bool* pod) { - //LV2PFile file = (LV2PFile)callback_data; - cout << "LV2 PERSIST RETRIEVE " << key << endl; - return NULL; + cout << "LV2 PERSIST RETRIEVE " << _uri_map.id_to_uri(NULL, key) << endl; + + PersistState* state = (PersistState*)callback_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; + *pod = true; // FIXME + return i->second.value; } void @@ -341,7 +419,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 = _id.to_s() + ".lv2f"; const std::string state_path = Glib::build_filename( _session.plugins_dir(), state_filename); @@ -357,8 +435,35 @@ LV2Plugin::add_state(XMLNode* root) const return; } + // Save plugin state to state object + PersistState state(_uri_map); + persist->save(_instance->lv2_handle, + &LV2Plugin::lv2_persist_store_callback, + &state); + + // Open state file LV2PFile file = lv2_pfile_open(state_path.c_str(), true); - persist->save(_instance->lv2_handle, &LV2Plugin::lv2_persist_store_callback, file); + + // Write all referenced URIs to state file + for (PersistState::URIs::const_iterator i = state.uris.begin(); + i != state.uris.end(); ++i) { + lv2_pfile_write_uri(file, i->first, + i->second.c_str(), i->second.length() + 1); + } + + // 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; + lv2_pfile_write_value(file, + key, + val.value, + val.size, + val.type); + } + + // Close state file lv2_pfile_close(file); root->add_property("state-file", state_filename); @@ -527,9 +632,34 @@ LV2Plugin::set_state(const XMLNode& node, int version) if (persist) { cout << "Loading LV2 state from " << state_path << endl; LV2PFile file = lv2_pfile_open(state_path.c_str(), false); + + PersistState state(_uri_map); + + // Load file into state object + LV2PFileChunkHeader* chunk = (LV2PFileChunkHeader*)malloc( + sizeof(LV2PFileChunkHeader)); + chunk->size = 0; + while (!lv2_pfile_read_chunk(file, &chunk)) { + if (!strncmp(chunk->type, "URID", 4)) { + LV2PFileURIChunk* body = (LV2PFileURIChunk*)chunk->data; + printf("READ URI %u: %s\n", body->id, body->uri); + state.add_uri(body->id, body->uri); + } else if (!strncmp(chunk->type, "KVAL", 4)) { + LV2PFileValueChunk* body = (LV2PFileValueChunk*)chunk->data; + printf("READ VAL %u = %s (size: %u type: %u)\n", + body->key, body->value, body->size, body->type); + state.add_value(body->key, + body->value, + body->size, + body->type, + true); + } + } + free(chunk); + persist->restore(_instance->lv2_handle, &LV2Plugin::lv2_persist_retrieve_callback, - file); + &state); lv2_pfile_close(file); } else { warning << string_compose( diff --git a/libs/ardour/lv2ext/lv2_persist.h b/libs/ardour/lv2ext/lv2_persist.h index 2ac4880154..5ed6c45030 100644 --- a/libs/ardour/lv2ext/lv2_persist.h +++ b/libs/ardour/lv2ext/lv2_persist.h @@ -1,177 +1,198 @@ -/* lv2_persist.h - C header file for the LV2 Persist extension. - * Copyright (C) 2010 Leonard Ritter - * - * This header is free software; you can redistribute it and/or modify it - * under the terms of the GNU Lesser General Public License as published - * by the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This header 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 Lesser General Public - * License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this header; if not, write to the Free Software Foundation, - * Inc., 59 Temple Place, Suite 330, Boston, MA 01222-1307 USA - */ - -/** @file - * C header for the LV2 Persist extension . - */ +/* + Copyright (C) 2010-2011 David Robillard + Copyright (C) 2010 Leonard Ritter + + This header is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published + by the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This header 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 Lesser General Public + License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with this header; if not, write to the Free Software Foundation, + Inc., 59 Temple Place, Suite 330, Boston, MA 01222-1307 USA +*/ + +/** + @file + C API for the LV2 Persist extension . +*/ #ifndef LV2_PERSIST_H #define LV2_PERSIST_H +#include +#include + #ifdef __cplusplus extern "C" { #endif #define LV2_PERSIST_URI "http://lv2plug.in/ns/ext/persist" -/** A host-provided function to store a value under a given key. - * @param callback_data Must be the callback_data passed to LV2_Persist.save(). - * @param key The URI key (predicate) under which the value is to be stored. - * @param value Pointer to the value (object) to be stored. - * @param size The size of the data at @a value in bytes. - * @param type The type of @a value, as a URI mapped to an integer. - * - * The host passes a callback of this type to LV2_Persist.save(). This - * callback is called repeatedly by the plugin within LV2_Persist.save() to - * store all the key/value records that describe its current state. - * - * Unless @a type is 0, @a value is guaranteed to be POD (i.e. a region - * of memory that does not contain pointers and can safely be copied - * and persisted indefinitely with a simple memcpy). If @a type is 0, - * then @a value is a reference, as defined by the LV2 Atom extension - * . Hosts are not required to support - * references: a plugin MUST NOT expect a host to persist references unless - * the host supports the feature . - * Plugins SHOULD express their state entirely with POD values. - * - * Note that @a size MUST be > 0, and @a value MUST point to a valid region of - * memory @a size bytes long (this is required to make restore unambiguous). - * - * The plugin MUST NOT attempt to use this function outside of the - * LV2_Persist.restore() context. - */ -typedef void (*LV2_Persist_Store_Function)( - void* callback_data, - const char* key, - const void* value, - size_t size, - uint32_t type); - -/** A host-provided function to retrieve a value under a given key. - * @param callback_data Must be the callback_data passed to LV2_Persist.restore(). - * @param key The URI key (predicate) under which a value has been stored. - * @param size (Output) If non-NULL, set to the size of the restored value. - * @param type (Output) If non-NULL, set to the type of the restored value. - * @return A pointer to the restored value (object), or NULL if no value - * has been stored under @a key. - * - * A callback of this type is passed by the host to LV2_Persist.restore(). This - * callback is called repeatedly by the plugin within LV2_Persist.restore() to - * retrieve the values of any keys it requires to restore its state. - * - * The returned value MUST remain valid until LV2_Persist.restore() returns. - * - * The plugin MUST NOT attempt to use this function, or any value returned from - * it, outside of the LV2_Persist.restore() context. Returned values MAY be - * copied for later use if necessary. - */ +/** + A host-provided function to store a value under a given key. + @param callback_data Must be the callback_data passed to LV2_Persist.save(). + @param key The key (predicate) to store @a value under (URI mapped integer). + @param value Pointer to the value (object) to be stored. + @param size The size of the data at @a value in bytes. + @param type The type of @a value (URI mapped integer). + @param pod True iff @a value is POD. + @return 0 on success, otherwise a non-zero error code. + + The host passes a callback of this type to LV2_Persist.save(). + This callback is called repeatedly by the plugin within + LV2_Persist.save() to store all the key/value records that describe + its current state. + + If @a pod is true, @a value is guaranteed to be architecture-independent POD + (i.e. a region of memory that does not contain pointers or references to + non-persistent resources and can safely be copied and stored with a simple + memcpy). Note that this definition of POD is more strict than exclusively + in-memory definitions since the value MUST be architecture independent; + e.g. endianness must be considered (so basic numeric types are typically NOT + POD). Hosts MAY fail to store the value, particularly if it is + non-POD. Plugins MUST gracefully handle this situation, even though state + may not be fully restored. Hosts SHOULD support any POD value, even if the + host does not know anything about its type. Plugins SHOULD express their + state entirely with POD values whenever possible, and use non-POD values + only where necessary. Plugins SHOULD use common RDF types and/or types from + the Atom extension whenever possible since + hosts are likely to already contain the necessary implementation. + + Note that @a size MUST be > 0, and @a value MUST point to a valid region of + memory @a size bytes long (this is required to make restore unambiguous). + + The plugin MUST NOT attempt to use this function outside of the + LV2_Persist.restore() context. +*/ +typedef int (*LV2_Persist_Store_Function)( + void* callback_data, + const uint32_t key, + const void* value, + size_t size, + uint32_t type, + bool pod); + +/** + A host-provided function to retrieve a value under a given key. + @param callback_data Must be the callback_data passed to LV2_Persist.restore(). + @param key The key (predicate) of the value to retrieve (URI mapped integer). + @param size (Output) If non-NULL, set to the size of the restored value. + @param type (Output) If non-NULL, set to the type of the restored value. + @param pod (Output) If non-NULL, set to true iff @a value is POD. + @return A pointer to the restored value (object), or NULL if no value + has been stored under @a key. + + A callback of this type is passed by the host to LV2_Persist.restore(). This + callback is called repeatedly by the plugin within LV2_Persist.restore() to + retrieve the values of any keys it requires to restore its state. + + The returned value MUST remain valid until LV2_Persist.restore() returns. + + The plugin MUST NOT attempt to use this function, or any value returned from + it, outside of the LV2_Persist.restore() context. Returned values MAY be + copied for later use if necessary, assuming the plugin knows how to + correctly do so (e.g. the value is POD, or the plugin understands the type). +*/ typedef const void* (*LV2_Persist_Retrieve_Function)( - void* callback_data, - const char* key, - size_t* size, - uint32_t* type); - -/** When the plugin's extension_data is called with argument LV2_PERSIST_URI, - * the plugin MUST return an LV2_Persist structure, which remains valid for - * the lifetime of the plugin. - * - * The host can use the contained function pointers to save and restore the - * state of a plugin instance at any time (provided the threading restrictions - * for the given function are met). - * - * The typical use case is to save the plugin's state when a project is - * saved, and to restore the state when a project has been loaded. Other - * uses are possible (e.g. cloning plugin instances or taking a snapshot - * of plugin state). - * - * Stored data is only guaranteed to be compatible between instances of plugins - * with the same URI (i.e. if a change to a plugin would cause a fatal error - * when restoring state saved by a previous version of that plugin, the plugin - * URI must change just as it must when a plugin's ports change). Plugin - * authors should consider this possibility, and always store sensible data - * with meaningful types to avoid such compatibility issues in the future. - */ + void* callback_data, + uint32_t key, + size_t* size, + uint32_t* type, + bool* pod); + +/** + Persist Extension Data. + + When the plugin's extension_data is called with argument LV2_PERSIST_URI, + the plugin MUST return an LV2_Persist structure, which remains valid for + the lifetime of the plugin. + + The host can use the contained function pointers to save and restore the + state of a plugin instance at any time (provided the threading restrictions + for the given function are met). + + The typical use case is to save the plugin's state when a project is + saved, and to restore the state when a project has been loaded. Other + uses are possible (e.g. cloning plugin instances or taking a snapshot + of plugin state). + + Stored data is only guaranteed to be compatible between instances of plugins + with the same URI (i.e. if a change to a plugin would cause a fatal error + when restoring state saved by a previous version of that plugin, the plugin + URI MUST change just as it must when ports change incompatibly). Plugin + authors should consider this possibility, and always store sensible data + with meaningful types to avoid such compatibility issues in the future. +*/ typedef struct _LV2_Persist { - - /** Save plugin state using a host-provided @a store callback. - * - * @param instance The instance handle of the plugin. - * @param store The host-provided store callback. - * @param callback_data An opaque pointer to host data, e.g. the map or - * file where the values are to be stored. If @a store is called, - * this MUST be passed as its callback_data parameter. - * - * The plugin is expected to store everything necessary to completely - * restore its state later (possibly much later, in a different - * process, on a completely different machine, etc.) - * - * The @a callback_data pointer and @a store function MUST NOT be - * used beyond the scope of save(). - * - * This function has its own special threading class: it may not be - * called concurrently with any "Instantiation" function, but it - * may be called concurrently with functions in any other class, - * unless the definition of that class prohibits it (e.g. it may - * not be called concurrently with a "Discovery" function, but it - * may be called concurrently with an "Audio" function. The plugin - * is responsible for any locking or lock-free techniques necessary - * to make this possible. - * - * Note that in the simple case where state is only modified by - * restore(), there are no synchronization issues since save() is - * never called concurrently with restore() (though run() may read - * it during a save). - * - * Plugins that dynamically modify state while running, however, - * must take care to do so in such a way that a concurrent call to - * save() will save a consistent representation of plugin state for a - * single instant in time. The simplest way to do this is to modify a - * copy of the state map and atomically swap a pointer to the entire - * map once the changes are complete (for very large state maps, - * a purely functional map data structure may be more appropriate - * since a complete copy is not necessary). - */ + + /** + Save plugin state using a host-provided @a store callback. + + @param instance The instance handle of the plugin. + @param store The host-provided store callback. + @param callback_data An opaque pointer to host data, e.g. the map or + file where the values are to be stored. If @a store is called, + this MUST be passed as its callback_data parameter. + + The plugin is expected to store everything necessary to completely + restore its state later (possibly much later, in a different + process, on a completely different machine, etc.) + + The @a callback_data pointer and @a store function MUST NOT be + used beyond the scope of save(). + + This function has its own special threading class: it may not be + called concurrently with any "Instantiation" function, but it + may be called concurrently with functions in any other class, + unless the definition of that class prohibits it (e.g. it may + not be called concurrently with a "Discovery" function, but it + may be called concurrently with an "Audio" function. The plugin + is responsible for any locking or lock-free techniques necessary + to make this possible. + + Note that in the simple case where state is only modified by + restore(), there are no synchronization issues since save() is + never called concurrently with restore() (though run() may read + it during a save). + + Plugins that dynamically modify state while running, however, + must take care to do so in such a way that a concurrent call to + save() will save a consistent representation of plugin state for a + single instant in time. + */ void (*save)(LV2_Handle instance, LV2_Persist_Store_Function store, void* callback_data); - /** Restore plugin state using a host-provided @a retrieve callback. - * - * @param instance The instance handle of the plugin. - * @param retrieve The host-provided retrieve callback. - * @param callback_data An opaque pointer to host data, e.g. the map or - * file from which the values are to be restored. If @a retrieve is - * called, this MUST be passed as its callback_data parameter. - * - * The plugin MAY assume a restored value was set by a previous call to - * LV2_Persist.save() by a plugin with the same URI. - * - * The plugin MUST gracefully fall back to a default value when a - * value can not be retrieved. This allows the host to reset the - * plugin state with an empty map. - * - * The @a callback_data pointer and @a store function MUST NOT be used - * beyond the scope of restore(). - * - * This function is in the "Instantiation" threading class as defined - * by LV2. This means it MUST NOT be called concurrently with any other - * function on the same plugin instance. - */ + /** + Restore plugin state using a host-provided @a retrieve callback. + + @param instance The instance handle of the plugin. + @param retrieve The host-provided retrieve callback. + @param callback_data An opaque pointer to host data, e.g. the map or + file from which the values are to be restored. If @a retrieve is + called, this MUST be passed as its callback_data parameter. + + The plugin MAY assume a restored value was set by a previous call to + LV2_Persist.save() by a plugin with the same URI. + + The plugin MUST gracefully fall back to a default value when a + value can not be retrieved. This allows the host to reset the + plugin state with an empty map. + + The @a callback_data pointer and @a store function MUST NOT be used + beyond the scope of restore(). + + This function is in the "Instantiation" threading class as defined + by LV2. This means it MUST NOT be called concurrently with any other + function on the same plugin instance. + */ void (*restore)(LV2_Handle instance, LV2_Persist_Retrieve_Function retrieve, void* callback_data); -- cgit v1.2.3