aboutsummaryrefslogtreecommitdiffstats
path: root/src/state.c
diff options
context:
space:
mode:
authorDavid Robillard <d@drobilla.net>2011-12-21 19:34:52 +0000
committerDavid Robillard <d@drobilla.net>2011-12-21 19:34:52 +0000
commit2847c8be9cb7e8bb52357703db09b2f03f47af78 (patch)
tree5b127f5b81f51090b48d22b6a439361bef1fd862 /src/state.c
parenta6c98c802249b48b2e3cc5dbd89e1ab6595d89d9 (diff)
downloadjalv-2847c8be9cb7e8bb52357703db09b2f03f47af78.tar.gz
jalv-2847c8be9cb7e8bb52357703db09b2f03f47af78.tar.bz2
jalv-2847c8be9cb7e8bb52357703db09b2f03f47af78.zip
Begin merging state and prefix implementations.
git-svn-id: http://svn.drobilla.net/lad/trunk/jalv@3895 a436a847-0d15-0410-975c-d299462d15a1
Diffstat (limited to 'src/state.c')
-rw-r--r--src/state.c354
1 files changed, 334 insertions, 20 deletions
diff --git a/src/state.c b/src/state.c
index 5eb50a1..1f36dc1 100644
--- a/src/state.c
+++ b/src/state.c
@@ -14,25 +14,38 @@
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
+#define _POSIX_C_SOURCE 200112L /* for fileno */
+#define _BSD_SOURCE 1 /* for lockf */
+
#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
+#include <errno.h>
+#include <sys/stat.h>
+#include <sys/types.h>
#ifdef HAVE_LV2_STATE
# include "lv2/lv2plug.in/ns/ext/state/state.h"
#endif
+#include "lilv/lilv.h"
+
#include "jalv-config.h"
#include "jalv_internal.h"
-#define NS_ATOM (const uint8_t*)"http://lv2plug.in/ns/ext/atom#"
-#define NS_JALV (const uint8_t*)"http://drobilla.net/ns/jalv#"
-#define NS_LV2 (const uint8_t*)"http://lv2plug.in/ns/lv2core#"
-#define NS_PSET (const uint8_t*)"http://lv2plug.in/ns/ext/presets#"
-#define NS_STATE (const uint8_t*)"http://lv2plug.in/ns/ext/state#"
-#define NS_XSD (const uint8_t*)"http://www.w3.org/2001/XMLSchema#"
-#define NS_RDFS (const uint8_t*)"http://www.w3.org/2000/01/rdf-schema#"
+#ifdef HAVE_LOCKF
+#include <unistd.h>
+#endif
+
+#define NS_ATOM "http://lv2plug.in/ns/ext/atom#"
+#define NS_JALV "http://drobilla.net/ns/jalv#"
+#define NS_LV2 "http://lv2plug.in/ns/lv2core#"
+#define NS_PSET "http://lv2plug.in/ns/ext/presets#"
+#define NS_RDF "http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+#define NS_RDFS "http://www.w3.org/2000/01/rdf-schema#"
+#define NS_STATE "http://lv2plug.in/ns/ext/state#"
+#define NS_XSD "http://www.w3.org/2001/XMLSchema#"
#define USTR(s) ((const uint8_t*)s)
@@ -78,6 +91,20 @@ value_cmp(const void* a, const void* b)
return strcmp((const char*)pa->symbol.buf, (const char*)pb->symbol.buf);
}
+static char*
+strjoin(const char* a, const char* b)
+{
+ const size_t a_len = strlen(a);
+ const size_t b_len = strlen(b);
+ char* const out = malloc(a_len + b_len + 1);
+
+ memcpy(out, a, a_len);
+ memcpy(out + a_len, b, b_len);
+ out[a_len + b_len] = '\0';
+
+ return out;
+}
+
#ifdef HAVE_LV2_STATE
typedef struct {
LV2_URID_Unmap* unmap;
@@ -158,13 +185,8 @@ file_sink(const void* buf, size_t len, void* stream)
void
jalv_save(Jalv* jalv, const char* dir)
{
- const size_t dir_len = strlen(dir);
- const char* const filename = "state.ttl";
- const size_t path_len = dir_len + strlen(filename);
- char* const path = (char*)malloc(path_len + 1);
-
- snprintf(path, path_len + 1, "%s%s", dir, filename);
- FILE* out_fd = fopen(path, "w");
+ char* const path = strjoin(dir, "/state.ttl");
+ FILE* out_fd = fopen(path, "w");
SerdEnv* env = serd_env_new(NULL);
serd_env_set_prefix_from_strings(env, USTR("atom"), USTR(NS_ATOM));
@@ -174,7 +196,8 @@ jalv_save(Jalv* jalv, const char* dir)
serd_env_set_prefix_from_strings(env, USTR("rdfs"), USTR(NS_RDFS));
serd_env_set_prefix_from_strings(env, USTR("state"), USTR(NS_STATE));
- SerdNode lv2_appliesTo = serd_node_from_string(SERD_URI, NS_LV2 "appliesTo");
+ SerdNode lv2_appliesTo = serd_node_from_string(SERD_URI,
+ USTR(NS_LV2 "appliesTo"));
SerdNode plugin_uri = serd_node_from_string(SERD_URI, USTR(lilv_node_as_uri(
lilv_plugin_get_uri(jalv->plugin))));
@@ -206,7 +229,7 @@ jalv_save(Jalv* jalv, const char* dir)
if (state) {
SerdNode state_instanceState = serd_node_from_string(
- SERD_URI, (NS_STATE "instanceState"));
+ SERD_URI, USTR(NS_STATE "instanceState"));
// [] state:instanceState [
SerdNode state_node = serd_node_from_string(SERD_BLANK, USTR("state"));
@@ -295,15 +318,18 @@ load_state_from_file(LilvWorld* world,
PluginState*
jalv_load_state(Jalv* jalv, const char* dir)
{
- const size_t dir_len = strlen(dir);
- const size_t state_uri_len = strlen("file:///state.ttl") + dir_len + 1;
- char* state_uri = (char*)malloc(state_uri_len);
- snprintf(state_uri, state_uri_len, "file://%s/state.ttl", dir);
+ char* base_uri = strjoin("file://", dir);
+ char* state_uri = strjoin(base_uri, "/state.ttl");
+ fprintf(stderr, "DIR: %s\n", base_uri);
+ fprintf(stderr, "BASE: %s\n", base_uri);
+ fprintf(stderr, "LOAD: %s\n", state_uri);
+
PluginState* state = load_state_from_file(
jalv->world, &jalv->map, state_uri);
free(state_uri);
+ free(base_uri);
return state;
}
@@ -378,3 +404,291 @@ jalv_apply_state(Jalv* jalv, PluginState* state)
}
#endif // HAVE_LV2_STATE
}
+
+int
+jalv_load_presets(Jalv* jalv, PresetSink sink, void* data)
+{
+ LilvNodes* presets = lilv_plugin_get_related(jalv->plugin,
+ jalv->preset_class);
+ LILV_FOREACH(nodes, i, presets) {
+ const LilvNode* preset = lilv_nodes_get(presets, i);
+ lilv_world_load_resource(jalv->world, preset);
+ LilvNodes* labels = lilv_world_find_nodes(
+ jalv->world, preset, jalv->label_pred, NULL);
+ if (labels) {
+ const LilvNode* label = lilv_nodes_get_first(labels);
+ sink(jalv, preset, label, data);
+ lilv_nodes_free(labels);
+ } else {
+ fprintf(stderr, "Preset <%s> has no rdfs:label\n",
+ lilv_node_as_string(lilv_nodes_get(presets, i)));
+ }
+ }
+ lilv_nodes_free(presets);
+
+ return 0;
+}
+
+static inline const LilvNode*
+get_value(LilvWorld* world, const LilvNode* subject, const LilvNode* predicate)
+{
+ LilvNodes* vs = lilv_world_find_nodes(world, subject, predicate, NULL);
+ return vs ? lilv_nodes_get_first(vs) : NULL;
+}
+
+int
+jalv_apply_preset(Jalv* jalv, const LilvNode* preset)
+{
+ LilvNode* lv2_port = lilv_new_uri(jalv->world, NS_LV2 "port");
+ LilvNode* lv2_symbol = lilv_new_uri(jalv->world, NS_LV2 "symbol");
+ LilvNode* pset_value = lilv_new_uri(jalv->world, NS_PSET "value");
+
+ LilvNodes* ports = lilv_world_find_nodes(
+ jalv->world, preset, lv2_port, NULL);
+ LILV_FOREACH(nodes, i, ports) {
+ const LilvNode* port = lilv_nodes_get(ports, i);
+ const LilvNode* symbol = get_value(jalv->world, port, lv2_symbol);
+ const LilvNode* value = get_value(jalv->world, port, pset_value);
+ if (!symbol) {
+ fprintf(stderr, "error: Preset port missing symbol.\n");
+ } else if (!value) {
+ fprintf(stderr, "error: Preset port missing value.\n");
+ } else if (!lilv_node_is_float(value) && !lilv_node_is_int(value)) {
+ fprintf(stderr, "error: Preset port value is not a number.\n");
+ } else {
+ const char* sym = lilv_node_as_string(symbol);
+ struct Port* p = jalv_port_by_symbol(jalv, sym);
+ if (p) {
+ const float fvalue = lilv_node_as_float(value);
+ // Send value to plugin
+ jalv_ui_write(jalv, p->index, sizeof(float), 0, &fvalue);
+
+ // Update UI
+ char buf[sizeof(ControlChange) + sizeof(float)];
+ ControlChange* ev = (ControlChange*)buf;
+ ev->index = p->index;
+ ev->protocol = 0;
+ ev->size = sizeof(float);
+ *(float*)ev->body = fvalue;
+ jack_ringbuffer_write(jalv->plugin_events, buf, sizeof(buf));
+ } else {
+ fprintf(stderr, "error: Preset port `%s' is missing\n", sym);
+ }
+ }
+ }
+ lilv_nodes_free(ports);
+
+ lilv_node_free(pset_value);
+ lilv_node_free(lv2_symbol);
+ lilv_node_free(lv2_port);
+
+ return 0;
+}
+
+static char*
+pathify(const char* in, const char* ext)
+{
+ const size_t in_len = strlen(in);
+ const size_t ext_len = ext ? strlen(ext) : 0;
+
+ char* out = malloc(in_len + ext_len + 1);
+ for (size_t i = 0; i < in_len; ++i) {
+ char c = in[i];
+ if (!((c >= 'a' && c <= 'z')
+ || (c >= 'A' && c <= 'Z')
+ || (c >= '0' && c <= '9'))) {
+ c = '-';
+ }
+ out[i] = c;
+ }
+ if (ext) {
+ memcpy(out + in_len, ext, ext_len);
+ }
+ return out;
+}
+
+void
+jalv_save_port_values(Jalv* jalv,
+ SerdWriter* writer,
+ const SerdNode* subject)
+{
+ for (uint32_t i = 0; i < jalv->num_ports; ++i) {
+ struct Port* const port = &jalv->ports[i];
+ if (port->type != TYPE_CONTROL || port->flow != FLOW_INPUT) {
+ continue;
+ }
+
+ const LilvNode* sym = lilv_port_get_symbol(jalv->plugin, port->lilv_port);
+ LilvNode* val = lilv_new_float(jalv->world, port->control);
+
+ const SerdNode port_node = serd_node_from_string(
+ SERD_BLANK, USTR(lilv_node_as_string(sym)));
+
+ // <> lv2:port _:symbol
+ SerdNode p = serd_node_from_string(SERD_URI,
+ USTR(NS_LV2 "port"));
+ serd_writer_write_statement(writer, SERD_ANON_O_BEGIN,
+ NULL, subject, &p, &port_node, NULL, NULL);
+
+ // _:symbol lv2:symbol "symbol"
+ p = serd_node_from_string(SERD_URI, USTR(NS_LV2 "symbol"));
+ SerdNode o = serd_node_from_string(SERD_LITERAL,
+ USTR(lilv_node_as_string(sym)));
+ serd_writer_write_statement(writer, SERD_ANON_CONT,
+ NULL, &port_node, &p, &o, NULL, NULL);
+
+ // _:symbol pset:value value
+ p = serd_node_from_string(SERD_URI, USTR(NS_PSET "value"));
+ o = serd_node_from_string(SERD_LITERAL,
+ USTR(lilv_node_as_string(val)));
+ SerdNode t = serd_node_from_string(SERD_URI, USTR(NS_XSD "decimal"));
+ serd_writer_write_statement(writer, SERD_ANON_CONT,
+ NULL, &port_node, &p, &o, &t, NULL);
+
+ lilv_node_free(val);
+ serd_writer_end_anon(writer, &port_node);
+ }
+}
+
+int
+jalv_save_preset(Jalv* jalv, const char* label)
+{
+ const char* const home = getenv("HOME");
+ if (!home) {
+ fprintf(stderr, "error: $HOME is undefined\n");
+ return 1;
+ }
+
+ const char* const bundle_name = "presets.lv2/";
+
+ // Create ~/.lv2/ and ~/.lv2/presets.lv2/ if necessary
+ char* const lv2dir = strjoin(home, "/.lv2/");
+ char* const bundle = strjoin(lv2dir, bundle_name);
+ char* const filename = pathify(label, ".ttl");
+ char* const path = strjoin(bundle, filename);
+ char* const uri = strjoin("file://", path);
+ char* const manifest_path = strjoin(bundle, "manifest.ttl");
+ char* const manifest_uri = strjoin("file: //", manifest_path);
+
+ int ret = 0;
+ if ((mkdir(lv2dir, 0755) && errno != EEXIST)
+ || (mkdir(bundle, 0755) && errno != EEXIST)) {
+ fprintf(stderr, "error: Unable to create %s (%s)\n",
+ lv2dir, strerror(errno));
+ ret = 2;
+ goto done;
+ }
+
+ // Open preset file
+ FILE* fd = fopen((char*)path, "w");
+ if (!fd) {
+ fprintf(stderr, "error: Failed to open %s (%s)\n",
+ path, strerror(errno));
+ ret = 3;
+ goto done;
+ }
+
+ SerdURI base_uri;
+ SerdNode base = serd_node_new_uri_from_string((const uint8_t*)uri,
+ NULL, &base_uri);
+
+ SerdEnv* env = serd_env_new(&base);
+ serd_env_set_prefix_from_strings(env, USTR("lv2"), USTR(NS_LV2));
+ serd_env_set_prefix_from_strings(env, USTR("pset"), USTR(NS_PSET));
+ serd_env_set_prefix_from_strings(env, USTR("rdfs"), USTR(NS_RDFS));
+
+ // Write preset file
+
+ SerdWriter* writer = serd_writer_new(
+ SERD_TURTLE, SERD_STYLE_ABBREVIATED|SERD_STYLE_CURIED,
+ env, &base_uri,
+ file_sink,
+ fd);
+
+ serd_env_foreach(env, (SerdPrefixSink)serd_writer_set_prefix, writer);
+
+ // <> a pset:Preset
+ SerdNode s = serd_node_from_string(SERD_URI, USTR(""));
+ SerdNode p = serd_node_from_string(SERD_URI, USTR(NS_RDF "type"));
+ SerdNode o = serd_node_from_string(SERD_CURIE, USTR("pset:Preset"));
+ serd_writer_write_statement(writer, 0, NULL, &s, &p, &o, NULL, NULL);
+
+ // <> rdfs:label label
+ p = serd_node_from_string(SERD_URI, USTR(NS_RDFS "label"));
+ o = serd_node_from_string(SERD_LITERAL, USTR(label));
+ serd_writer_write_statement(writer, 0,
+ NULL, &s, &p, &o, NULL, NULL);
+
+ jalv_save_port_values(jalv, writer, &s);
+
+ serd_writer_free(writer);
+ serd_node_free(&base);
+
+ // Append record to manifest
+
+ fclose(fd);
+ fd = fopen((char*)manifest_path, "a");
+ if (!fd) {
+ fprintf(stderr, "error: Failed to open %s (%s)\n",
+ path, strerror(errno));
+ serd_env_free(env);
+ ret = 4;
+ goto done;
+ }
+
+#ifdef HAVE_LOCKF
+ lockf(fileno(fd), F_LOCK, 0);
+#endif
+
+ base = serd_node_new_uri_from_string((const uint8_t*)manifest_uri,
+ NULL, &base_uri);
+
+ writer = serd_writer_new(
+ SERD_TURTLE, SERD_STYLE_ABBREVIATED|SERD_STYLE_CURIED,
+ env, &base_uri,
+ file_sink,
+ fd);
+
+ fseek(fd, 0, SEEK_END);
+ if (ftell(fd) == 0) {
+ serd_env_foreach(env, (SerdPrefixSink)serd_writer_set_prefix, writer);
+ }
+
+ s = serd_node_from_string(SERD_URI, USTR(filename));
+
+ // <preset> a pset:Preset
+ p = serd_node_from_string(SERD_URI, USTR(NS_RDF "type"));
+ o = serd_node_from_string(SERD_CURIE, USTR("pset:Preset"));
+ serd_writer_write_statement(writer, 0, NULL, &s, &p, &o, NULL, NULL);
+
+ // <preset> rdfs:seeAlso <preset>
+ p = serd_node_from_string(SERD_URI, USTR(NS_RDFS "seeAlso"));
+ serd_writer_write_statement(writer, 0, NULL, &s, &p, &s, NULL, NULL);
+
+ // <preset> lv2:appliesTo <plugin>
+ p = serd_node_from_string(SERD_URI, USTR(NS_LV2 "appliesTo"));
+ o = serd_node_from_string(
+ SERD_URI, USTR(lilv_node_as_string(lilv_plugin_get_uri(jalv->plugin))));
+ serd_writer_write_statement(writer, 0, NULL, &s, &p, &o, NULL, NULL);
+
+ serd_writer_free(writer);
+ serd_env_free(env);
+ serd_node_free(&base);
+
+#ifdef HAVE_LOCKF
+ lockf(fileno(fd), F_ULOCK, 0);
+#endif
+
+ fclose(fd);
+
+done:
+ free(manifest_uri);
+ free(manifest_path);
+ free(uri);
+ free(path);
+ free(filename);
+ free(bundle);
+ free(lv2dir);
+
+ return ret;
+}