diff options
-rw-r--r-- | NEWS | 1 | ||||
-rw-r--r-- | src/jalv.c | 69 | ||||
-rw-r--r-- | src/jalv_internal.h | 6 | ||||
-rw-r--r-- | src/state.c | 31 | ||||
-rw-r--r-- | src/worker.c | 66 | ||||
-rw-r--r-- | src/worker.h | 7 | ||||
-rw-r--r-- | wscript | 2 |
7 files changed, 121 insertions, 61 deletions
@@ -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 @@ -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); @@ -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', |