aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorDavid Robillard <d@drobilla.net>2011-12-18 00:56:34 +0000
committerDavid Robillard <d@drobilla.net>2011-12-18 00:56:34 +0000
commite5a90b60c1cc62c782ac6b63025696dda95dae38 (patch)
treeacc64fc1894c57ff3df14759abebd831ea77cbbe /src
parent102ba23f77650bbeaceca98c3a6539f1e637b4f7 (diff)
downloadjalv-e5a90b60c1cc62c782ac6b63025696dda95dae38.tar.gz
jalv-e5a90b60c1cc62c782ac6b63025696dda95dae38.tar.bz2
jalv-e5a90b60c1cc62c782ac6b63025696dda95dae38.zip
Preset saving.
git-svn-id: http://svn.drobilla.net/lad/trunk/jalv@3880 a436a847-0d15-0410-975c-d299462d15a1
Diffstat (limited to 'src')
-rw-r--r--src/jalv.c4
-rw-r--r--src/jalv_gtk2.c40
-rw-r--r--src/jalv_internal.h6
-rw-r--r--src/presets.c235
4 files changed, 273 insertions, 12 deletions
diff --git a/src/jalv.c b/src/jalv.c
index de40077..743f1dc 100644
--- a/src/jalv.c
+++ b/src/jalv.c
@@ -62,7 +62,7 @@ typedef struct {
LV2_URID
map_uri(LV2_URID_Map_Handle handle,
- const char* uri)
+ const char* uri)
{
//return symap_map(((Jalv*)handle)->symap, uri);
const LV2_URID id = symap_map(((Jalv*)handle)->symap, uri);
@@ -72,7 +72,7 @@ map_uri(LV2_URID_Map_Handle handle,
const char*
unmap_uri(LV2_URID_Unmap_Handle handle,
- LV2_URID urid)
+ LV2_URID urid)
{
return symap_unmap(((Jalv*)handle)->symap, urid);
}
diff --git a/src/jalv_gtk2.c b/src/jalv_gtk2.c
index c56bab1..029376a 100644
--- a/src/jalv_gtk2.c
+++ b/src/jalv_gtk2.c
@@ -60,7 +60,7 @@ on_save_activate(GtkWidget* widget, void* ptr)
Jalv* jalv = (Jalv*)ptr;
GtkWidget* dialog = gtk_file_chooser_dialog_new(
"Save State",
- NULL, // FIXME: parent
+ jalv->window,
GTK_FILE_CHOOSER_ACTION_CREATE_FOLDER,
GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
GTK_STOCK_SAVE, GTK_RESPONSE_ACCEPT,
@@ -91,6 +91,36 @@ typedef struct {
} PresetRecord;
static void
+on_save_preset_activate(GtkWidget* widget, void* ptr)
+{
+ Jalv* jalv = (Jalv*)ptr;
+
+ GtkDialog* dialog = GTK_DIALOG(gtk_dialog_new_with_buttons(
+ "Save Preset", jalv->window,
+ GTK_DIALOG_MODAL|GTK_DIALOG_DESTROY_WITH_PARENT,
+ GTK_STOCK_CANCEL, GTK_RESPONSE_REJECT,
+ GTK_STOCK_SAVE, GTK_RESPONSE_ACCEPT,
+ NULL));
+
+ GtkWidget* content = gtk_dialog_get_content_area(dialog);
+ GtkWidget* hbox = gtk_hbox_new(FALSE, 0);
+ GtkWidget* entry = gtk_entry_new();
+ gtk_box_pack_start(GTK_BOX(hbox), gtk_label_new("Label:"), FALSE, TRUE, 6);
+ gtk_box_pack_start(GTK_BOX(hbox), entry, FALSE, TRUE, 6);
+ gtk_box_pack_start(GTK_BOX(content), hbox, TRUE, TRUE, 12);
+
+ gtk_widget_show_all(GTK_WIDGET(dialog));
+ gtk_entry_set_activates_default(GTK_ENTRY(entry), TRUE);
+ gtk_dialog_set_default_response(dialog, GTK_RESPONSE_ACCEPT);
+ if (gtk_dialog_run(dialog) == GTK_RESPONSE_ACCEPT) {
+ const char* label_str = gtk_entry_get_text(GTK_ENTRY(entry));
+ jalv_save_preset(jalv, label_str);
+ }
+
+ gtk_widget_destroy(GTK_WIDGET(dialog));
+}
+
+static void
on_preset_activate(GtkWidget* widget, gpointer data)
{
PresetRecord* record = (PresetRecord*)data;
@@ -145,6 +175,7 @@ jalv_open_ui(Jalv* jalv,
SuilInstance* instance)
{
GtkWidget* window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
+ jalv->window = window;
g_signal_connect(window, "destroy",
G_CALLBACK(on_window_destroy), NULL);
@@ -169,7 +200,11 @@ jalv_open_ui(Jalv* jalv,
GtkWidget* presets = gtk_menu_item_new_with_mnemonic("_Presets");
GtkWidget* presets_menu = gtk_menu_new();
+ GtkWidget* save_preset = gtk_menu_item_new_with_mnemonic("_Save Preset...");
gtk_menu_item_set_submenu(GTK_MENU_ITEM(presets), presets_menu);
+ gtk_menu_shell_append(GTK_MENU_SHELL(presets_menu), save_preset);
+ gtk_menu_shell_append(GTK_MENU_SHELL(presets_menu),
+ gtk_separator_menu_item_new());
gtk_menu_shell_append(GTK_MENU_SHELL(menu_bar), presets);
jalv_load_presets(jalv, add_preset_to_menu, presets_menu);
@@ -182,6 +217,9 @@ jalv_open_ui(Jalv* jalv,
g_signal_connect(G_OBJECT(save), "activate",
G_CALLBACK(on_save_activate), jalv);
+ g_signal_connect(G_OBJECT(save_preset), "activate",
+ G_CALLBACK(on_save_preset_activate), jalv);
+
GtkWidget* alignment = gtk_alignment_new(0.5, 0.5, 1.0, 1.0);
gtk_box_pack_start(GTK_BOX(vbox), alignment, TRUE, TRUE, 0);
diff --git a/src/jalv_internal.h b/src/jalv_internal.h
index d44b5c5..95bc659 100644
--- a/src/jalv_internal.h
+++ b/src/jalv_internal.h
@@ -92,6 +92,7 @@ typedef struct {
const LilvUI* ui; /**< Plugin UI (RDF data) */
LilvInstance* instance; /**< Plugin instance (shared library) */
SuilInstance* ui_instance; /**< Plugin UI instance (shared library) */
+ void* window; /**< Window (if applicable) */
struct Port* ports; /**< Port array of size num_ports */
size_t midi_buf_size; /**< Size of MIDI port buffers */
uint32_t num_ports; /**< Size of the two following arrays: */
@@ -153,7 +154,10 @@ int
jalv_load_presets(Jalv* jalv, PresetSink sink, void* data);
int
-jalv_apply_preset(Jalv* jalv, LilvNode* preset);
+jalv_apply_preset(Jalv* jalv, const LilvNode* preset);
+
+int
+jalv_save_preset(Jalv* jalv, const char* label);
void
jalv_save(Jalv* jalv, const char* dir);
diff --git a/src/presets.c b/src/presets.c
index bf7fd78..1963e87 100644
--- a/src/presets.c
+++ b/src/presets.c
@@ -14,11 +14,20 @@
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
+#include <errno.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+
+#include "lilv/lilv.h"
+
#include "jalv-config.h"
#include "jalv_internal.h"
#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_XSD "http://www.w3.org/2001/XMLSchema#"
#define USTR(s) ((const uint8_t*)s)
@@ -28,14 +37,14 @@ 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);
+ const LilvNode* preset = lilv_nodes_get(presets, i);
lilv_world_load_resource(jalv->world, preset);
- LilvNodes* titles = lilv_world_find_nodes(
+ LilvNodes* labels = lilv_world_find_nodes(
jalv->world, preset, jalv->label_pred, NULL);
- if (titles) {
- const LilvNode* title = lilv_nodes_get_first(titles);
- sink(jalv, preset, title, data);
- lilv_nodes_free(titles);
+ 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)));
@@ -54,7 +63,7 @@ get_value(LilvWorld* world, const LilvNode* subject, const LilvNode* predicate)
}
int
-jalv_apply_preset(Jalv* jalv, LilvNode* preset)
+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");
@@ -86,9 +95,219 @@ jalv_apply_preset(Jalv* jalv, LilvNode* preset)
lilv_nodes_free(ports);
lilv_node_free(pset_value);
- lilv_node_free(preset);
lilv_node_free(lv2_symbol);
lilv_node_free(lv2_port);
return 0;
}
+
+static size_t
+file_sink(const void* buf, size_t len, void* stream)
+{
+ FILE* file = (FILE*)stream;
+ return fwrite(buf, 1, len, file);
+}
+
+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;
+}
+
+static char*
+strjoin(const char* a, const char* b)
+{
+ const size_t a_len = strlen(a);
+ const size_t b_len = strlen(a);
+ 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;
+}
+
+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);
+
+ 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
+ p = serd_node_from_string(SERD_URI, USTR(NS_LV2 "port"));
+ serd_writer_write_statement(writer, SERD_ANON_O_BEGIN,
+ NULL, &s, &p, &port_node, NULL, NULL);
+
+ // _:symbol lv2:symbol "symbol"
+ p = serd_node_from_string(SERD_URI, USTR(NS_LV2 "symbol"));
+ 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);
+ }
+
+ 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;
+ }
+
+ 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);
+ fclose(fd);
+
+done:
+ free(manifest_uri);
+ free(manifest_path);
+ free(uri);
+ free(path);
+ free(filename);
+ free(bundle);
+ free(lv2dir);
+
+ return ret;
+}