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
This commit is contained in:
David Robillard 2011-03-28 23:54:08 +00:00
parent 8d86a71f0f
commit 2c0cd4d430
5 changed files with 570 additions and 299 deletions

View file

@ -149,16 +149,18 @@ class LV2Plugin : public ARDOUR::Plugin
static URIMap _uri_map; static URIMap _uri_map;
static uint32_t _midi_event_type; static uint32_t _midi_event_type;
static void lv2_persist_store_callback (void* callback_data, static int lv2_persist_store_callback (void* callback_data,
const char* key, uint32_t key,
const void* value, const void* value,
size_t size, size_t size,
uint32_t type); uint32_t type,
bool pod);
static const void* lv2_persist_retrieve_callback (void* callback_data, static const void* lv2_persist_retrieve_callback (void* callback_data,
const char* key, uint32_t key,
size_t* size, size_t* size,
uint32_t* type); uint32_t* type,
bool* pod);
void init (LV2World& world, SLV2Plugin plugin, framecnt_t rate); void init (LV2World& world, SLV2Plugin plugin, framecnt_t rate);
void run (pframes_t nsamples); void run (pframes_t nsamples);

View file

@ -1,20 +1,23 @@
/* Portable file-based implementation of LV2 Persist. /*
* See <http://lv2plug.in/ns/ext/persist> for details. Portable file-based LV2 Persist implementation.
* Copyright (C) 2010 David Robillard <http://drobilla.net> See <http://lv2plug.in/ns/ext/persist> for details.
*
* This file is free software; you can redistribute it and/or modify it Copyright 2011 David Robillard <http://drobilla.net>
* 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) This is free software; you can redistribute it and/or modify it
* any later version. under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* This file is distributed in the hope that it will be useful, but WITHOUT (at your option) any later version.
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License This software is distributed in the hope that it will be useful,
* for more details. but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* You should have received a copy of the GNU General Public License along See the GNU General Public License for more details.
* with this file; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 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 <assert.h> #include <assert.h>
@ -25,8 +28,16 @@
#include "lv2_pfile.h" #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 { struct _LV2PFile {
FILE* fd; FILE* fd;
uint32_t size;
bool write;
}; };
LV2PFile LV2PFile
@ -38,88 +49,125 @@ lv2_pfile_open(const char* path, bool write)
return NULL; return NULL;
} }
static const char* const magic = "LV2PFILE"; uint32_t size = 0;
static const size_t magic_len = 8;
if (write) { 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 { } else {
char file_magic[magic_len]; char magic[CHUNK_ID_LEN];
if (fread(file_magic, magic_len, 1, fd) != 1 if (fread(magic, CHUNK_ID_LEN, 1, fd) != 1
|| strncmp(file_magic, magic, magic_len)) { || strncmp(magic, "RIFF", CHUNK_ID_LEN)) {
fclose(fd); 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; return NULL;
} }
} }
LV2PFile ret = (LV2PFile)malloc(sizeof(LV2PFile)); LV2PFile ret = (LV2PFile)malloc(sizeof(struct _LV2PFile));
ret->fd = fd; ret->fd = fd;
ret->size = size;
ret->write = write;
return ret; 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) \ #define WRITE(ptr, size, nmemb, stream) \
if (fwrite(ptr, size, nmemb, stream) != nmemb) { \ if (fwrite(ptr, size, nmemb, stream) != nmemb) { \
return LV2_PFILE_UNKNOWN_ERROR; \ return LV2_PFILE_UNKNOWN_ERROR; \
} }
const uint32_t key_len = strlen(key); LV2PFileStatus
WRITE(&key_len, sizeof(key_len), 1, file->fd); lv2_pfile_write_uri(LV2PFile file,
WRITE(key, key_len + 1, 1, file->fd); uint32_t id,
const char* uri,
const uint32_t type_len = strlen(type); uint32_t len)
WRITE(&type_len, sizeof(type_len), 1, file->fd); {
WRITE(type, type_len + 1, 1, file->fd); const uint32_t chunk_size = sizeof(id) + len + 1;
WRITE(CHUNK_URID, CHUNK_ID_LEN, 1, file->fd);
WRITE(&size, sizeof(size), 1, file->fd); WRITE(&chunk_size, sizeof(chunk_size), 1, file->fd);
WRITE(value, 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; return LV2_PFILE_OK;
} }
LV2PFileStatus LV2PFileStatus
lv2_pfile_read(LV2PFile file, lv2_pfile_write_value(LV2PFile file,
char** key, uint32_t key,
uint32_t* key_len, const void* value,
char** type, uint32_t size,
uint32_t* type_len, uint32_t type)
void** value, {
uint64_t* size) 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_chunk(LV2PFile file,
LV2PFileChunkHeader** buf)
{ {
if (feof(file->fd)) if (feof(file->fd))
return LV2_PFILE_EOF; return LV2_PFILE_EOF;
#define READ(ptr, size, nmemb, stream) \ #define READ(ptr, size, nmemb, stream) \
if (fread(ptr, size, nmemb, stream) != nmemb) { \ if (fread(ptr, size, nmemb, stream) != nmemb) { \
assert(false); \
return LV2_PFILE_CORRUPT; \ return LV2_PFILE_CORRUPT; \
} }
READ(key_len, sizeof(*key_len), 1, file->fd); const uint32_t alloc_size = (*buf)->size;
*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);
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; return LV2_PFILE_OK;
} }
void void
lv2_pfile_close(LV2PFile file) 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); fclose(file->fd);
}
free(file); free(file);
} }
@ -140,14 +188,23 @@ main(int argc, char** argv)
if (!file) if (!file)
goto fail; goto fail;
char wkey[6]; static const int N_URIS = 16;
char wval[6]; static const int N_RECORDS = 16;
const char* wtype = "http://example.org/type";
#define NUM_RECORDS 32 char uri[64];
for (int i = 0; i < NUM_RECORDS; ++i) { for (int i = 0; i < N_URIS; ++i) {
snprintf(wkey, sizeof(wkey), "KEY%02d", i); snprintf(uri, sizeof(uri), "http://example.org/uri%02d", i + 1);
snprintf(wval, sizeof(wval), "VAL%02d", i); lv2_pfile_write_uri(file, i + 1, uri, strlen(uri) + 1);
lv2_pfile_write(file, wkey, wval, strlen(wval) + 1, wtype); }
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); lv2_pfile_close(file);
@ -156,23 +213,31 @@ main(int argc, char** argv)
if (!file) if (!file)
goto fail; goto fail;
char* rkey; LV2PFileChunkHeader* chunk = malloc(sizeof(LV2PFileChunkHeader));
uint32_t rkey_len; chunk->size = 0;
char* rtype; for (int i = 0; i < N_URIS; ++i) {
uint32_t rtype_len; if (lv2_pfile_read_chunk(file, &chunk)
uint64_t rsize; || strncmp(chunk->type, "URID", 4)) {
void* rval; fprintf(stderr, "error: expected URID chunk\n");
for (int i = 0; i < NUM_RECORDS; ++i) {
if (lv2_pfile_read(file, &rkey, &rkey_len, &rtype, &rtype_len, &rval, &rsize))
goto fail; goto fail;
}
printf("%s = %s :: %s\n", rkey, (char*)rval, rtype); LV2PFileURIChunk* body = (LV2PFileURIChunk*)chunk->data;
free(rkey); printf("URI: %s\n", body->uri);
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); lv2_pfile_close(file);
return 0; return 0;
fail: fail:

View file

@ -1,24 +1,37 @@
/* Portable file-based implementation of LV2 Persist. /*
* See <http://lv2plug.in/ns/ext/persist> for details. Portable file-based LV2 Persist implementation.
* See <http://lv2plug.in/ns/ext/persist> 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 Copyright 2011 David Robillard <http://drobilla.net>
* Software Foundation; either version 2 of the License, or (at your option)
* any later version. This is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by
* This file is distributed in the hope that it will be useful, but WITHOUT the Free Software Foundation; either version 2 of the License, or
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or (at your option) any later version.
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* for more details. This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* You should have received a copy of the GNU General Public License along MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* with this file; if not, write to the Free Software Foundation, Inc., General Public License for more details.
* 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
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 <stdbool.h> #include <stdbool.h>
#include <stdint.h> #include <stdint.h>
#ifdef __GNUC__
# define PACKED __attribute__((__packed__))
#else
# define PACKED
#endif
#ifdef __cplusplus #ifdef __cplusplus
extern "C" { extern "C" {
#endif #endif
@ -33,22 +46,58 @@ typedef enum {
LV2_PFILE_CORRUPT = 3 LV2_PFILE_CORRUPT = 3
} LV2PFileStatus; } 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 LV2PFile
lv2_pfile_open(const char* path, bool write); lv2_pfile_open(const char* path, bool write);
/** Write a record to a persist file. */ /**
LV2PFileStatus Write a URI ID to @a file.
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.
*/ */
LV2PFileStatus LV2PFileStatus
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, lv2_pfile_read(LV2PFile file,
char** key, char** key,
uint32_t* key_len, uint32_t* key_len,
@ -56,9 +105,11 @@ lv2_pfile_read(LV2PFile file,
uint32_t* type_len, uint32_t* type_len,
void** value, void** value,
uint64_t* size); 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 void
lv2_pfile_close(LV2PFile file); lv2_pfile_close(LV2PFile file);
@ -66,3 +117,5 @@ lv2_pfile_close(LV2PFile file);
#ifdef __cplusplus #ifdef __cplusplus
} /* extern "C" */ } /* extern "C" */
#endif #endif
#endif /* LV2PFILE_H */

View file

@ -296,31 +296,109 @@ LV2Plugin::nth_parameter(uint32_t n, bool& ok) const
return 0; return 0;
} }
void struct PersistValue {
LV2Plugin::lv2_persist_store_callback(void* callback_data, inline PersistValue(uint32_t k, const void* v, size_t s, uint32_t t, bool p)
const char* key, : 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<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, const void* value,
size_t size, size_t size,
uint32_t type) uint32_t file_type,
{ bool pod) {
LV2PFile file = (LV2PFile)callback_data; 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;
}
// FIXME: assumes URIs are mapped in the default context (or not event, at least) Values::const_iterator i = values.find(key);
const char* type_uri = LV2Plugin::_uri_map.id_to_uri(NULL, type); if (i != values.end()) {
cout << "LV2 PERSIST STORE " << key << " = " << value << " :: " << type_uri << endl; error << "LV2 state contains duplicate keys" << endmsg;
lv2_pfile_write(file, key, value, size, type_uri); 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,
uint32_t key,
const void* value,
size_t size,
uint32_t type,
bool pod)
{
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* const void*
LV2Plugin::lv2_persist_retrieve_callback(void* callback_data, LV2Plugin::lv2_persist_retrieve_callback(void* callback_data,
const char* key, uint32_t key,
size_t* size, size_t* size,
uint32_t* type) uint32_t* type,
bool* pod)
{ {
//LV2PFile file = (LV2PFile)callback_data; cout << "LV2 PERSIST RETRIEVE " << _uri_map.id_to_uri(NULL, key) << endl;
cout << "LV2 PERSIST RETRIEVE " << 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; return NULL;
} }
*size = i->second.size;
*type = i->second.type;
*pod = true; // FIXME
return i->second.value;
}
void void
LV2Plugin::add_state(XMLNode* root) const LV2Plugin::add_state(XMLNode* root) const
@ -341,7 +419,7 @@ LV2Plugin::add_state(XMLNode* root) const
if (_supports_persist) { if (_supports_persist) {
// Create state directory for this plugin instance // 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( const std::string state_path = Glib::build_filename(
_session.plugins_dir(), state_filename); _session.plugins_dir(), state_filename);
@ -357,8 +435,35 @@ LV2Plugin::add_state(XMLNode* root) const
return; 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); 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); lv2_pfile_close(file);
root->add_property("state-file", state_filename); root->add_property("state-file", state_filename);
@ -527,9 +632,34 @@ LV2Plugin::set_state(const XMLNode& node, int version)
if (persist) { if (persist) {
cout << "Loading LV2 state from " << state_path << endl; cout << "Loading LV2 state from " << state_path << endl;
LV2PFile file = lv2_pfile_open(state_path.c_str(), false); 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, persist->restore(_instance->lv2_handle,
&LV2Plugin::lv2_persist_retrieve_callback, &LV2Plugin::lv2_persist_retrieve_callback,
file); &state);
lv2_pfile_close(file); lv2_pfile_close(file);
} else { } else {
warning << string_compose( warning << string_compose(

View file

@ -1,176 +1,197 @@
/* lv2_persist.h - C header file for the LV2 Persist extension. /*
* Copyright (C) 2010 Leonard Ritter <paniq@paniq.org> Copyright (C) 2010-2011 David Robillard <http://drobilla.net>
* Copyright (C) 2010 Leonard Ritter <paniq@paniq.org>
* 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 This header is free software; you can redistribute it and/or modify it
* by the Free Software Foundation; either version 2 of the License, or under the terms of the GNU Lesser General Public License as published
* (at your option) any later version. 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 This header is distributed in the hope that it will be useful, but WITHOUT
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* License for more details. 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, You should have received a copy of the GNU Lesser General Public License
* Inc., 59 Temple Place, Suite 330, Boston, MA 01222-1307 USA 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 <http://lv2plug.in/ns/ext/persist>. @file
C API for the LV2 Persist extension <http://lv2plug.in/ns/ext/persist>.
*/ */
#ifndef LV2_PERSIST_H #ifndef LV2_PERSIST_H
#define LV2_PERSIST_H #define LV2_PERSIST_H
#include <stdbool.h>
#include <stdint.h>
#ifdef __cplusplus #ifdef __cplusplus
extern "C" { extern "C" {
#endif #endif
#define LV2_PERSIST_URI "http://lv2plug.in/ns/ext/persist" #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(). A host-provided function to store a value under a given key.
* @param key The URI key (predicate) under which the value is to be stored. @param callback_data Must be the callback_data passed to LV2_Persist.save().
* @param value Pointer to the value (object) to be stored. @param key The key (predicate) to store @a value under (URI mapped integer).
* @param size The size of the data at @a value in bytes. @param value Pointer to the value (object) to be stored.
* @param type The type of @a value, as a URI mapped to an integer. @param size The size of the data at @a value in bytes.
* @param type The type of @a value (URI mapped integer).
* The host passes a callback of this type to LV2_Persist.save(). This @param pod True iff @a value is POD.
* callback is called repeatedly by the plugin within LV2_Persist.save() to @return 0 on success, otherwise a non-zero error code.
* store all the key/value records that describe its current state.
* The host passes a callback of this type to LV2_Persist.save().
* Unless @a type is 0, @a value is guaranteed to be POD (i.e. a region This callback is called repeatedly by the plugin within
* of memory that does not contain pointers and can safely be copied LV2_Persist.save() to store all the key/value records that describe
* and persisted indefinitely with a simple memcpy). If @a type is 0, its current state.
* then @a value is a reference, as defined by the LV2 Atom extension
* <http://lv2plug.in/ns/ext/atom/>. Hosts are not required to support If @a pod is true, @a value is guaranteed to be architecture-independent POD
* references: a plugin MUST NOT expect a host to persist references unless (i.e. a region of memory that does not contain pointers or references to
* the host supports the feature <http://lv2plug.in/ns/ext/atom#blobSupport>. non-persistent resources and can safely be copied and stored with a simple
* Plugins SHOULD express their state entirely with POD values. memcpy). Note that this definition of POD is more strict than exclusively
* in-memory definitions since the value MUST be architecture independent;
* Note that @a size MUST be > 0, and @a value MUST point to a valid region of e.g. endianness must be considered (so basic numeric types are typically NOT
* memory @a size bytes long (this is required to make restore unambiguous). POD). Hosts MAY fail to store the value, particularly if it is
* non-POD. Plugins MUST gracefully handle this situation, even though state
* The plugin MUST NOT attempt to use this function outside of the may not be fully restored. Hosts SHOULD support any POD value, even if the
* LV2_Persist.restore() context. 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 <http://lv2plug.in/ns/ext/atom> 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 void (*LV2_Persist_Store_Function)( typedef int (*LV2_Persist_Store_Function)(
void* callback_data, void* callback_data,
const char* key, const uint32_t key,
const void* value, const void* value,
size_t size, size_t size,
uint32_t type); 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(). A host-provided function to retrieve a value under a given key.
* @param key The URI key (predicate) under which a value has been stored. @param callback_data Must be the callback_data passed to LV2_Persist.restore().
* @param size (Output) If non-NULL, set to the size of the restored value. @param key The key (predicate) of the value to retrieve (URI mapped integer).
* @param type (Output) If non-NULL, set to the type of the restored value. @param size (Output) If non-NULL, set to the size of the restored value.
* @return A pointer to the restored value (object), or NULL if no value @param type (Output) If non-NULL, set to the type of the restored value.
* has been stored under @a key. @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
* A callback of this type is passed by the host to LV2_Persist.restore(). This has been stored under @a key.
* callback is called repeatedly by the plugin within LV2_Persist.restore() to
* retrieve the values of any keys it requires to restore its state. 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
* The returned value MUST remain valid until LV2_Persist.restore() returns. retrieve the values of any keys it requires to restore its state.
*
* The plugin MUST NOT attempt to use this function, or any value returned from The returned value MUST remain valid until LV2_Persist.restore() returns.
* it, outside of the LV2_Persist.restore() context. Returned values MAY be
* copied for later use if necessary. 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)( typedef const void* (*LV2_Persist_Retrieve_Function)(
void* callback_data, void* callback_data,
const char* key, uint32_t key,
size_t* size, size_t* size,
uint32_t* type); uint32_t* type,
bool* pod);
/** 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 Persist Extension Data.
* the lifetime of the plugin.
* When the plugin's extension_data is called with argument LV2_PERSIST_URI,
* The host can use the contained function pointers to save and restore the the plugin MUST return an LV2_Persist structure, which remains valid for
* state of a plugin instance at any time (provided the threading restrictions the lifetime of the plugin.
* for the given function are met).
* The host can use the contained function pointers to save and restore the
* The typical use case is to save the plugin's state when a project is state of a plugin instance at any time (provided the threading restrictions
* saved, and to restore the state when a project has been loaded. Other for the given function are met).
* uses are possible (e.g. cloning plugin instances or taking a snapshot
* of plugin state). 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
* Stored data is only guaranteed to be compatible between instances of plugins uses are possible (e.g. cloning plugin instances or taking a snapshot
* with the same URI (i.e. if a change to a plugin would cause a fatal error of plugin state).
* 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 Stored data is only guaranteed to be compatible between instances of plugins
* authors should consider this possibility, and always store sensible data with the same URI (i.e. if a change to a plugin would cause a fatal error
* with meaningful types to avoid such compatibility issues in the future. 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 { typedef struct _LV2_Persist {
/** Save plugin state using a host-provided @a store callback. /**
* 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 instance The instance handle of the plugin.
* @param callback_data An opaque pointer to host data, e.g. the map or @param store The host-provided store callback.
* file where the values are to be stored. If @a store is called, @param callback_data An opaque pointer to host data, e.g. the map or
* this MUST be passed as its callback_data parameter. 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 The plugin is expected to store everything necessary to completely
* process, on a completely different machine, etc.) 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(). 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 This function has its own special threading class: it may not be
* may be called concurrently with functions in any other class, called concurrently with any "Instantiation" function, but it
* unless the definition of that class prohibits it (e.g. it may may be called concurrently with functions in any other class,
* not be called concurrently with a "Discovery" function, but it unless the definition of that class prohibits it (e.g. it may
* may be called concurrently with an "Audio" function. The plugin not be called concurrently with a "Discovery" function, but it
* is responsible for any locking or lock-free techniques necessary may be called concurrently with an "Audio" function. The plugin
* to make this possible. 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 Note that in the simple case where state is only modified by
* never called concurrently with restore() (though run() may read restore(), there are no synchronization issues since save() is
* it during a save). 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 Plugins that dynamically modify state while running, however,
* save() will save a consistent representation of plugin state for a must take care to do so in such a way that a concurrent call to
* single instant in time. The simplest way to do this is to modify a save() will save a consistent representation of plugin state for a
* copy of the state map and atomically swap a pointer to the entire single instant in time.
* 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).
*/ */
void (*save)(LV2_Handle instance, void (*save)(LV2_Handle instance,
LV2_Persist_Store_Function store, LV2_Persist_Store_Function store,
void* callback_data); void* callback_data);
/** Restore plugin state using a host-provided @a retrieve callback. /**
* 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 instance The instance handle of the plugin.
* @param callback_data An opaque pointer to host data, e.g. the map or @param retrieve The host-provided retrieve callback.
* file from which the values are to be restored. If @a retrieve is @param callback_data An opaque pointer to host data, e.g. the map or
* called, this MUST be passed as its callback_data parameter. 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 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 The plugin MUST gracefully fall back to a default value when a
* plugin state with an empty map. 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(). 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 This function is in the "Instantiation" threading class as defined
* function on the same plugin instance. by LV2. This means it MUST NOT be called concurrently with any other
function on the same plugin instance.
*/ */
void (*restore)(LV2_Handle instance, void (*restore)(LV2_Handle instance,
LV2_Persist_Retrieve_Function retrieve, LV2_Persist_Retrieve_Function retrieve,