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:
parent
8d86a71f0f
commit
2c0cd4d430
|
@ -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 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,
|
||||
const char* key,
|
||||
size_t* size,
|
||||
uint32_t* type);
|
||||
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);
|
||||
|
|
|
@ -1,21 +1,24 @@
|
|||
/* Portable file-based implementation of LV2 Persist.
|
||||
* See <http://lv2plug.in/ns/ext/persist> for details.
|
||||
* Copyright (C) 2010 David Robillard <http://drobilla.net>
|
||||
*
|
||||
* 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 <http://lv2plug.in/ns/ext/persist> for details.
|
||||
|
||||
Copyright 2011 David Robillard <http://drobilla.net>
|
||||
|
||||
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 <assert.h>
|
||||
#include <errno.h>
|
||||
|
@ -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_read(LV2PFile file,
|
||||
char** key,
|
||||
uint32_t* key_len,
|
||||
char** type,
|
||||
uint32_t* type_len,
|
||||
void** value,
|
||||
uint64_t* size)
|
||||
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_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;
|
||||
|
||||
printf("%s = %s :: %s\n", rkey, (char*)rval, rtype);
|
||||
free(rkey);
|
||||
free(rtype);
|
||||
free(rval);
|
||||
}
|
||||
LV2PFileURIChunk* body = (LV2PFileURIChunk*)chunk->data;
|
||||
printf("URI: %s\n", body->uri);
|
||||
}
|
||||
|
||||
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:
|
||||
|
|
|
@ -1,24 +1,37 @@
|
|||
/* Portable file-based implementation of LV2 Persist.
|
||||
* 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
|
||||
* 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 <http://lv2plug.in/ns/ext/persist> for details.
|
||||
|
||||
Copyright 2011 David Robillard <http://drobilla.net>
|
||||
|
||||
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 <stdbool.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#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);
|
||||
lv2_pfile_write_uri(LV2PFile file,
|
||||
uint32_t id,
|
||||
const char* uri,
|
||||
uint32_t size);
|
||||
|
||||
/** Read a record from a persist file.
|
||||
* @a key and @a value are allocated with malloc and must be freed by caller.
|
||||
*/
|
||||
/**
|
||||
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 */
|
||||
|
|
|
@ -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<uint32_t, std::string> URIs;
|
||||
typedef std::map<uint32_t, PersistValue> Values;
|
||||
|
||||
uint32_t file_id_to_runtime_id(uint32_t file_id) const {
|
||||
URIs::const_iterator i = uris.find(file_id);
|
||||
if (i == uris.end()) {
|
||||
error << "LV2 state refers to undefined URI ID" << endmsg;
|
||||
return 0;
|
||||
}
|
||||
return uri_map.uri_to_id(NULL, i->second.c_str());
|
||||
}
|
||||
|
||||
int add_uri(uint32_t file_id, const char* str) {
|
||||
// TODO: check for clashes (invalid file)
|
||||
uris.insert(make_pair(file_id, str));
|
||||
return 0;
|
||||
}
|
||||
|
||||
int add_value(uint32_t file_key,
|
||||
const void* value,
|
||||
size_t size,
|
||||
uint32_t file_type,
|
||||
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;
|
||||
cout << "LV2 PERSIST STORE " << key
|
||||
<< " = " << value
|
||||
<< " :: " << type
|
||||
<< " POD: " << pod << endl;
|
||||
|
||||
// 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);
|
||||
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(
|
||||
|
|
|
@ -1,177 +1,198 @@
|
|||
/* lv2_persist.h - C header file for the LV2 Persist extension.
|
||||
* 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
|
||||
* 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
|
||||
*/
|
||||
/*
|
||||
Copyright (C) 2010-2011 David Robillard <http://drobilla.net>
|
||||
Copyright (C) 2010 Leonard Ritter <paniq@paniq.org>
|
||||
|
||||
/** @file
|
||||
* C header for the LV2 Persist extension <http://lv2plug.in/ns/ext/persist>.
|
||||
*/
|
||||
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 <http://lv2plug.in/ns/ext/persist>.
|
||||
*/
|
||||
|
||||
#ifndef LV2_PERSIST_H
|
||||
#define LV2_PERSIST_H
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#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
|
||||
* <http://lv2plug.in/ns/ext/atom/>. Hosts are not required to support
|
||||
* references: a plugin MUST NOT expect a host to persist references unless
|
||||
* the host supports the feature <http://lv2plug.in/ns/ext/atom#blobSupport>.
|
||||
* 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 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.
|
||||
|
||||
/** 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.
|
||||
*/
|
||||
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 <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 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);
|
||||
void* callback_data,
|
||||
uint32_t key,
|
||||
size_t* size,
|
||||
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
|
||||
* 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.
|
||||
*/
|
||||
/**
|
||||
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);
|
||||
|
|
Loading…
Reference in New Issue
Block a user