aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--NEWS1
-rw-r--r--src/jalv.c69
-rw-r--r--src/jalv_internal.h6
-rw-r--r--src/state.c31
-rw-r--r--src/worker.c66
-rw-r--r--src/worker.h7
-rw-r--r--wscript2
7 files changed, 121 insertions, 61 deletions
diff --git a/NEWS b/NEWS
index 24d4d03..7567c2f 100644
--- a/NEWS
+++ b/NEWS
@@ -3,6 +3,7 @@ jalv (1.4.7) unstable;
* Support CV ports if Jack metadata is enabled (patch from Hanspeter Portner)
* Improve preset support
* Support numeric and string plugin properties (event-based control)
+ * Support thread-safe state restoration
* Update UI when internal plugin state is changed during preset load
* Add generic Qt control UI from Amadeus Folego
* Set Jack port order metadata
diff --git a/src/jalv.c b/src/jalv.c
index 5d43691..a78827f 100644
--- a/src/jalv.c
+++ b/src/jalv.c
@@ -141,14 +141,16 @@ static LV2_URI_Map_Feature uri_map = { NULL, &uri_to_id };
static LV2_Extension_Data_Feature ext_data = { NULL };
-static LV2_Feature uri_map_feature = { NS_EXT "uri-map", &uri_map };
-static LV2_Feature map_feature = { LV2_URID__map, NULL };
-static LV2_Feature unmap_feature = { LV2_URID__unmap, NULL };
-static LV2_Feature make_path_feature = { LV2_STATE__makePath, NULL };
-static LV2_Feature schedule_feature = { LV2_WORKER__schedule, NULL };
-static LV2_Feature log_feature = { LV2_LOG__log, NULL };
-static LV2_Feature options_feature = { LV2_OPTIONS__options, NULL };
-static LV2_Feature def_state_feature = { LV2_STATE__loadDefaultState, NULL };
+LV2_Feature uri_map_feature = { NS_EXT "uri-map", NULL };
+LV2_Feature map_feature = { LV2_URID__map, NULL };
+LV2_Feature unmap_feature = { LV2_URID__unmap, NULL };
+LV2_Feature make_path_feature = { LV2_STATE__makePath, NULL };
+LV2_Feature sched_feature = { LV2_WORKER__schedule, NULL };
+LV2_Feature state_sched_feature = { LV2_WORKER__schedule, NULL };
+LV2_Feature safe_restore_feature = { LV2_STATE__threadSafeRestore, NULL };
+LV2_Feature log_feature = { LV2_LOG__log, NULL };
+LV2_Feature options_feature = { LV2_OPTIONS__options, NULL };
+LV2_Feature def_state_feature = { LV2_STATE__loadDefaultState, NULL };
/** These features have no data */
static LV2_Feature buf_size_features[3] = {
@@ -156,13 +158,13 @@ static LV2_Feature buf_size_features[3] = {
{ LV2_BUF_SIZE__fixedBlockLength, NULL },
{ LV2_BUF_SIZE__boundedBlockLength, NULL } };
-const LV2_Feature* features[13] = {
+const LV2_Feature* features[12] = {
&uri_map_feature, &map_feature, &unmap_feature,
- &make_path_feature,
- &schedule_feature,
+ &sched_feature,
&log_feature,
&options_feature,
&def_state_feature,
+ &safe_restore_feature,
&buf_size_features[0],
&buf_size_features[1],
&buf_size_features[2],
@@ -386,7 +388,7 @@ activate_port(Jalv* jalv,
port->jack_port = jack_port_register(
jalv->jack_client, lilv_node_as_string(sym),
JACK_DEFAULT_AUDIO_TYPE, jack_flags, 0);
- if(port->jack_port) {
+ if (port->jack_port) {
jack_set_property(jalv->jack_client, jack_port_uuid(port->jack_port),
"http://jackaudio.org/metadata/signal-type", "CV",
"text/plain");
@@ -554,17 +556,14 @@ jack_process_cb(jack_nframes_t nframes, void* data)
lv2_pos->type, lv2_pos->size, LV2_ATOM_BODY(lv2_pos));
}
- if (jalv->state_changed) {
+ if (jalv->request_update) {
/* Plugin state has changed, request an update */
const LV2_Atom_Object get = {
{ sizeof(LV2_Atom_Object_Body), jalv->urids.atom_Object },
{ 0, jalv->urids.patch_Get } };
-
lv2_evbuf_write(
&iter, 0, 0,
get.atom.type, get.atom.size, LV2_ATOM_BODY(&get));
-
- jalv->state_changed = false;
}
if (port->jack_port) {
@@ -584,6 +583,7 @@ jack_process_cb(jack_nframes_t nframes, void* data)
lv2_evbuf_reset(port->evbuf, false);
}
}
+ jalv->request_update = false;
/* Read and apply control change events from UI */
if (jalv->has_ui) {
@@ -616,8 +616,9 @@ jack_process_cb(jack_nframes_t nframes, void* data)
/* Run plugin for this cycle */
lilv_instance_run(jalv->instance, nframes);
- /* Process any replies from the worker. */
- jalv_worker_emit_responses(jalv, &jalv->worker);
+ /* Process any worker replies. */
+ jalv_worker_emit_responses(&jalv->state_worker, jalv->instance);
+ jalv_worker_emit_responses(&jalv->worker, jalv->instance);
/* Notify the plugin the run() cycle is finished */
if (jalv->worker.iface && jalv->worker.iface->end_run) {
@@ -1000,12 +1001,16 @@ main(int argc, char** argv)
jalv.symap = symap_new();
zix_sem_init(&jalv.symap_lock, 1);
+ uri_map_feature.data = &uri_map;
uri_map.callback_data = &jalv;
jalv.map.handle = &jalv;
jalv.map.map = map_uri;
map_feature.data = &jalv.map;
+ jalv.worker.jalv = &jalv;
+ jalv.state_worker.jalv = &jalv;
+
jalv.unmap.handle = &jalv;
jalv.unmap.unmap = unmap_uri;
unmap_feature.data = &jalv.unmap;
@@ -1070,8 +1075,11 @@ main(int argc, char** argv)
LV2_State_Make_Path make_path = { &jalv, jalv_make_path };
make_path_feature.data = &make_path;
- LV2_Worker_Schedule schedule = { &jalv, jalv_worker_schedule };
- schedule_feature.data = &schedule;
+ LV2_Worker_Schedule sched = { &jalv.worker, jalv_worker_schedule };
+ sched_feature.data = &sched;
+
+ LV2_Worker_Schedule ssched = { &jalv.state_worker, jalv_worker_schedule };
+ state_sched_feature.data = &ssched;
LV2_Log_Log llog = { &jalv, jalv_printf, jalv_vprintf };
log_feature.data = &llog;
@@ -1192,6 +1200,14 @@ main(int argc, char** argv)
}
lilv_nodes_free(req_feats);
+ /* Check for thread-safe state restore() method. */
+ LilvNode* state_threadSafeRestore = lilv_new_uri(
+ jalv.world, LV2_STATE__threadSafeRestore);
+ if (lilv_plugin_has_feature(jalv.plugin, state_threadSafeRestore)) {
+ jalv.safe_restore = true;
+ }
+ lilv_node_free(state_threadSafeRestore);
+
if (!state) {
/* Not restoring state, load the plugin as a preset to get default */
state = lilv_state_new_from_world(
@@ -1345,13 +1361,16 @@ main(int argc, char** argv)
jalv_allocate_port_buffers(&jalv);
}
- /* Create thread and ringbuffers for worker if necessary */
+ /* Create workers if necessary */
if (lilv_plugin_has_feature(jalv.plugin, jalv.nodes.work_schedule)
&& lilv_plugin_has_extension_data(jalv.plugin, jalv.nodes.work_interface)) {
- jalv_worker_init(
- &jalv, &jalv.worker,
- (const LV2_Worker_Interface*)lilv_instance_get_extension_data(
- jalv.instance, LV2_WORKER__interface));
+ const LV2_Worker_Interface* iface = (const LV2_Worker_Interface*)
+ lilv_instance_get_extension_data(jalv.instance, LV2_WORKER__interface);
+
+ jalv_worker_init(&jalv, &jalv.worker, iface, true);
+ if (jalv.safe_restore) {
+ jalv_worker_init(&jalv, &jalv.state_worker, iface, false);
+ }
}
/* Apply loaded state to plugin instance if necessary */
diff --git a/src/jalv_internal.h b/src/jalv_internal.h
index 7aa9e9e..818f19c 100644
--- a/src/jalv_internal.h
+++ b/src/jalv_internal.h
@@ -237,12 +237,14 @@ typedef enum {
} JalvPlayState;
typedef struct {
+ Jalv* jalv; ///< Pointer back to Jalv
jack_ringbuffer_t* requests; ///< Requests to the worker
jack_ringbuffer_t* responses; ///< Responses from the worker
void* response; ///< Worker response buffer
ZixSem sem; ///< Worker semaphore
ZixThread thread; ///< Worker thread
const LV2_Worker_Interface* iface; ///< Plugin worker interface
+ bool threaded; ///< Run work in another thread
} JalvWorker;
struct Jalv {
@@ -264,6 +266,7 @@ struct Jalv {
jack_ringbuffer_t* plugin_events; ///< Port events from plugin
void* ui_event_buf; ///< Buffer for reading UI port events
JalvWorker worker; ///< Worker thread implementation
+ JalvWorker state_worker; ///< Synchronous worker for state restore
ZixSem* done; ///< Exit semaphore
ZixSem paused; ///< Paused signal from process thread
JalvPlayState play_state; ///< Current play state
@@ -296,7 +299,8 @@ struct Jalv {
bool buf_size_set; ///< True iff buffer size callback fired
bool exit; ///< True iff execution is finished
bool has_ui; ///< True iff a control UI is present
- bool state_changed; ///< Plugin state has changed
+ bool request_update; ///< True iff a plugin update is needed
+ bool safe_restore; ///< Plugin restore() is thread-safe
};
int
diff --git a/src/state.c b/src/state.c
index d50ef66..f59ea43 100644
--- a/src/state.c
+++ b/src/state.c
@@ -1,5 +1,5 @@
/*
- Copyright 2007-2015 David Robillard <http://drobilla.net>
+ Copyright 2007-2016 David Robillard <http://drobilla.net>
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
@@ -37,6 +37,27 @@
#define NS_RDFS "http://www.w3.org/2000/01/rdf-schema#"
#define NS_XSD "http://www.w3.org/2001/XMLSchema#"
+extern LV2_Feature uri_map_feature;
+extern LV2_Feature map_feature;
+extern LV2_Feature unmap_feature;
+extern LV2_Feature make_path_feature;
+extern LV2_Feature sched_feature;
+extern LV2_Feature state_sched_feature;
+extern LV2_Feature safe_restore_feature;
+extern LV2_Feature log_feature;
+extern LV2_Feature options_feature;
+extern LV2_Feature def_state_feature;
+
+const LV2_Feature* state_features[9] = {
+ &uri_map_feature, &map_feature, &unmap_feature,
+ &make_path_feature,
+ &state_sched_feature,
+ &safe_restore_feature,
+ &log_feature,
+ &options_feature,
+ NULL
+};
+
char*
jalv_make_path(LV2_State_Make_Path_Handle handle,
const char* path)
@@ -178,19 +199,19 @@ set_port_value(const char* port_symbol,
void
jalv_apply_state(Jalv* jalv, LilvState* state)
{
+ bool must_pause = !jalv->safe_restore && jalv->play_state == JALV_RUNNING;
if (state) {
- const bool must_pause = (jalv->play_state == JALV_RUNNING);
if (must_pause) {
jalv->play_state = JALV_PAUSE_REQUESTED;
zix_sem_wait(&jalv->paused);
}
lilv_state_restore(
- state, jalv->instance, set_port_value, jalv, 0, NULL);
+ state, jalv->instance, set_port_value, jalv, 0, state_features);
- jalv->state_changed = true;
if (must_pause) {
- jalv->play_state = JALV_RUNNING;
+ jalv->request_update = true;
+ jalv->play_state = JALV_RUNNING;
}
}
}
diff --git a/src/worker.c b/src/worker.c
index 074ecf2..79b220c 100644
--- a/src/worker.c
+++ b/src/worker.c
@@ -1,5 +1,5 @@
/*
- Copyright 2007-2013 David Robillard <http://drobilla.net>
+ Copyright 2007-2016 David Robillard <http://drobilla.net>
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
@@ -21,26 +21,26 @@ jalv_worker_respond(LV2_Worker_Respond_Handle handle,
uint32_t size,
const void* data)
{
- Jalv* jalv = (Jalv*)handle;
- jack_ringbuffer_write(jalv->worker.responses,
- (const char*)&size, sizeof(size));
- jack_ringbuffer_write(jalv->worker.responses, (const char*)data, size);
+ JalvWorker* worker = (JalvWorker*)handle;
+ jack_ringbuffer_write(worker->responses, (const char*)&size, sizeof(size));
+ jack_ringbuffer_write(worker->responses, (const char*)data, size);
return LV2_WORKER_SUCCESS;
}
static void*
worker_func(void* data)
{
- Jalv* jalv = (Jalv*)data;
- void* buf = NULL;
+ JalvWorker* worker = (JalvWorker*)data;
+ Jalv* jalv = worker->jalv;
+ void* buf = NULL;
while (true) {
- zix_sem_wait(&jalv->worker.sem);
+ zix_sem_wait(&worker->sem);
if (jalv->exit) {
break;
}
uint32_t size = 0;
- jack_ringbuffer_read(jalv->worker.requests, (char*)&size, sizeof(size));
+ jack_ringbuffer_read(worker->requests, (char*)&size, sizeof(size));
if (!(buf = realloc(buf, size))) {
fprintf(stderr, "error: realloc() failed\n");
@@ -48,10 +48,10 @@ worker_func(void* data)
return NULL;
}
- jack_ringbuffer_read(jalv->worker.requests, (char*)buf, size);
+ jack_ringbuffer_read(worker->requests, (char*)buf, size);
- jalv->worker.iface->work(
- jalv->instance->lv2_handle, jalv_worker_respond, jalv, size, buf);
+ worker->iface->work(
+ jalv->instance->lv2_handle, jalv_worker_respond, worker, size, buf);
}
free(buf);
@@ -61,14 +61,18 @@ worker_func(void* data)
void
jalv_worker_init(Jalv* jalv,
JalvWorker* worker,
- const LV2_Worker_Interface* iface)
+ const LV2_Worker_Interface* iface,
+ bool threaded)
{
worker->iface = iface;
- zix_thread_create(&worker->thread, 4096, worker_func, jalv);
- worker->requests = jack_ringbuffer_create(4096);
+ worker->threaded = threaded;
+ if (threaded) {
+ zix_thread_create(&worker->thread, 4096, worker_func, worker);
+ worker->requests = jack_ringbuffer_create(4096);
+ jack_ringbuffer_mlock(worker->requests);
+ }
worker->responses = jack_ringbuffer_create(4096);
worker->response = malloc(4096);
- jack_ringbuffer_mlock(worker->requests);
jack_ringbuffer_mlock(worker->responses);
}
@@ -76,9 +80,11 @@ void
jalv_worker_finish(JalvWorker* worker)
{
if (worker->requests) {
- zix_sem_post(&worker->sem);
- zix_thread_join(worker->thread, NULL);
- jack_ringbuffer_free(worker->requests);
+ if (worker->threaded) {
+ zix_sem_post(&worker->sem);
+ zix_thread_join(worker->thread, NULL);
+ jack_ringbuffer_free(worker->requests);
+ }
jack_ringbuffer_free(worker->responses);
free(worker->response);
}
@@ -89,16 +95,24 @@ jalv_worker_schedule(LV2_Worker_Schedule_Handle handle,
uint32_t size,
const void* data)
{
- Jalv* jalv = (Jalv*)handle;
- jack_ringbuffer_write(jalv->worker.requests,
- (const char*)&size, sizeof(size));
- jack_ringbuffer_write(jalv->worker.requests, (const char*)data, size);
- zix_sem_post(&jalv->worker.sem);
+ JalvWorker* worker = (JalvWorker*)handle;
+ Jalv* jalv = worker->jalv;
+ if (worker->threaded) {
+ // Schedule a request to be executed by the worker thread
+ jack_ringbuffer_write(worker->requests,
+ (const char*)&size, sizeof(size));
+ jack_ringbuffer_write(worker->requests, (const char*)data, size);
+ zix_sem_post(&worker->sem);
+ } else {
+ // Execute work immediately in this thread
+ worker->iface->work(
+ jalv->instance->lv2_handle, jalv_worker_respond, worker, size, data);
+ }
return LV2_WORKER_SUCCESS;
}
void
-jalv_worker_emit_responses(Jalv* jalv, JalvWorker* worker)
+jalv_worker_emit_responses(JalvWorker* worker, LilvInstance* instance)
{
if (worker->responses) {
uint32_t read_space = jack_ringbuffer_read_space(worker->responses);
@@ -110,7 +124,7 @@ jalv_worker_emit_responses(Jalv* jalv, JalvWorker* worker)
worker->responses, (char*)worker->response, size);
worker->iface->work_response(
- jalv->instance->lv2_handle, size, worker->response);
+ instance->lv2_handle, size, worker->response);
read_space -= sizeof(size) + size;
}
diff --git a/src/worker.h b/src/worker.h
index ff4029a..93f39e0 100644
--- a/src/worker.h
+++ b/src/worker.h
@@ -1,5 +1,5 @@
/*
- Copyright 2007-2013 David Robillard <http://drobilla.net>
+ Copyright 2007-2016 David Robillard <http://drobilla.net>
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
@@ -21,7 +21,8 @@
void
jalv_worker_init(Jalv* jalv,
JalvWorker* worker,
- const LV2_Worker_Interface* iface);
+ const LV2_Worker_Interface* iface,
+ bool threaded);
void
jalv_worker_finish(JalvWorker* worker);
@@ -32,4 +33,4 @@ jalv_worker_schedule(LV2_Worker_Schedule_Handle handle,
const void* data);
void
-jalv_worker_emit_responses(Jalv* jalv, JalvWorker* worker);
+jalv_worker_emit_responses(JalvWorker* worker, LilvInstance* instance);
diff --git a/wscript b/wscript
index 87d600f..2a88b8c 100644
--- a/wscript
+++ b/wscript
@@ -43,7 +43,7 @@ def configure(conf):
autowaf.set_c99_mode(conf)
autowaf.display_header('Jalv Configuration')
- autowaf.check_pkg(conf, 'lv2', atleast_version='1.11.1', uselib_store='LV2')
+ autowaf.check_pkg(conf, 'lv2', atleast_version='1.13.1', uselib_store='LV2')
autowaf.check_pkg(conf, 'lilv-0', uselib_store='LILV',
atleast_version='0.21.5', mandatory=True)
autowaf.check_pkg(conf, 'serd-0', uselib_store='SERD',