diff options
author | David Robillard <d@drobilla.net> | 2016-07-31 15:18:56 -0400 |
---|---|---|
committer | David Robillard <d@drobilla.net> | 2016-07-31 15:19:35 -0400 |
commit | bc5600d2957d45a6745911f0562c365beb70841e (patch) | |
tree | 1a7dc654b9b094611e3b3d2820d43ba3a7b8e78e | |
parent | cb0fd14636a32785963474a8d9400ba9bd2ff1e2 (diff) | |
download | lv2-bc5600d2957d45a6745911f0562c365beb70841e.tar.xz |
eg-sampler: Support thread-safe state restoration
-rw-r--r-- | lv2/lv2plug.in/ns/meta/meta.ttl | 2 | ||||
-rw-r--r-- | plugins/eg-sampler.lv2/sampler.c | 146 | ||||
-rw-r--r-- | plugins/eg-sampler.lv2/sampler.ttl | 9 | ||||
-rw-r--r-- | plugins/eg-sampler.lv2/sampler_ui.c | 22 | ||||
-rw-r--r-- | plugins/eg-sampler.lv2/uris.h | 19 |
5 files changed, 109 insertions, 89 deletions
diff --git a/lv2/lv2plug.in/ns/meta/meta.ttl b/lv2/lv2plug.in/ns/meta/meta.ttl index 4725a50..888eab9 100644 --- a/lv2/lv2plug.in/ns/meta/meta.ttl +++ b/lv2/lv2plug.in/ns/meta/meta.ttl @@ -54,6 +54,8 @@ THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH R rdfs:label "eg-scope: Don't feed back UI state updates." ] , [ rdfs:label "eg-sampler: Fix handling of state file paths." + ] , [ + rdfs:label "eg-sampler: Support thread-safe state restoration." ] ] ] , [ diff --git a/plugins/eg-sampler.lv2/sampler.c b/plugins/eg-sampler.lv2/sampler.c index 1fd8c0e..5cc3c96 100644 --- a/plugins/eg-sampler.lv2/sampler.c +++ b/plugins/eg-sampler.lv2/sampler.c @@ -38,7 +38,8 @@ #include "lv2/lv2plug.in/ns/lv2core/lv2.h" #include "lv2/lv2plug.in/ns/lv2core/lv2_util.h" -#include "./uris.h" +#include "uris.h" +#include "atom_sink.h" enum { SAMPLER_CONTROL = 0, @@ -46,8 +47,6 @@ enum { SAMPLER_OUT = 2 }; -static const char* default_sample_file = "click.wav"; - typedef struct { SF_INFO info; // Info about sample from sndfile float* data; // Sample data in float @@ -64,10 +63,6 @@ typedef struct { // Forge for creating atoms LV2_Atom_Forge forge; - // Sample - Sample* sample; - bool sample_changed; - // Ports const LV2_Atom_Sequence* control_port; LV2_Atom_Sequence* notify_port; @@ -79,13 +74,14 @@ typedef struct { // URIs SamplerURIs uris; - // Current position in run() - uint32_t frame_offset; - // Playback state + Sample* sample; + uint32_t frame_offset; float gain; sf_count_t frame; bool play; + bool activated; + bool sample_changed; } Sampler; /** @@ -109,18 +105,17 @@ typedef struct { not modified. */ static Sample* -load_sample(Sampler* self, const char* path) +load_sample(LV2_Log_Logger* logger, const char* path) { - const size_t path_len = strlen(path); + lv2_log_trace(logger, "Loading %s\n", path); - lv2_log_trace(&self->logger, "Loading sample %s\n", path); - - Sample* const sample = (Sample*)malloc(sizeof(Sample)); - SF_INFO* const info = &sample->info; - SNDFILE* const sndfile = sf_open(path, SFM_READ, info); + const size_t path_len = strlen(path); + Sample* const sample = (Sample*)malloc(sizeof(Sample)); + SF_INFO* const info = &sample->info; + SNDFILE* const sndfile = sf_open(path, SFM_READ, info); if (!sndfile || !info->frames || (info->channels != 1)) { - lv2_log_error(&self->logger, "Failed to open sample '%s'\n", path); + lv2_log_error(logger, "Failed to open sample '%s'\n", path); free(sample); return NULL; } @@ -128,7 +123,7 @@ load_sample(Sampler* self, const char* path) // Read data float* const data = malloc(sizeof(float) * info->frames); if (!data) { - lv2_log_error(&self->logger, "Failed to allocate memory for sample\n"); + lv2_log_error(logger, "Failed to allocate memory for sample\n"); return NULL; } sf_seek(sndfile, 0ul, SEEK_SET); @@ -175,20 +170,19 @@ work(LV2_Handle instance, // Free old sample const SampleMessage* msg = (const SampleMessage*)data; free_sample(self, msg->sample); - } else { + } else if (atom->type == self->forge.Object) { // Handle set message (load sample). - const LV2_Atom_Object* obj = (const LV2_Atom_Object*)data; - - // Get file path from message - const LV2_Atom* file_path = read_set_file(&self->uris, obj); - if (!file_path) { + const LV2_Atom_Object* obj = (const LV2_Atom_Object*)data; + const char* path = read_set_file(&self->uris, obj); + if (!path) { + lv2_log_error(&self->logger, "Malformed set file request\n"); return LV2_WORKER_ERR_UNKNOWN; } // Load sample. - Sample* sample = load_sample(self, LV2_ATOM_BODY_CONST(file_path)); + Sample* sample = load_sample(&self->logger, path); if (sample) { - // Loaded sample, send it to run() to be applied. + // Send new sample to run() to be applied respond(handle, sizeof(sample), &sample); } } @@ -208,22 +202,25 @@ work_response(LV2_Handle instance, uint32_t size, const void* data) { - Sampler* self = (Sampler*)instance; - - SampleMessage msg = { { sizeof(Sample*), self->uris.eg_freeSample }, - self->sample }; - - // Send a message to the worker to free the current sample - self->schedule->schedule_work(self->schedule->handle, sizeof(msg), &msg); + Sampler* self = (Sampler*)instance; + Sample* old_sample = self->sample; + Sample* new_sample = *(Sample*const*)data; // Install the new sample self->sample = *(Sample*const*)data; - // Send a notification that we're using a new sample. - lv2_atom_forge_frame_time(&self->forge, self->frame_offset); - write_set_file(&self->forge, &self->uris, - self->sample->path, - self->sample->path_len); + // Schedule work to free the old sample + SampleMessage msg = { { sizeof(Sample*), self->uris.eg_freeSample }, + old_sample }; + self->schedule->schedule_work(self->schedule->handle, sizeof(msg), &msg); + + if (strcmp(old_sample->path, new_sample->path)) { + // Send a notification that we're using a new sample + lv2_atom_forge_frame_time(&self->forge, self->frame_offset); + write_set_file(&self->forge, &self->uris, + new_sample->path, + new_sample->path_len); + } return LV2_WORKER_SUCCESS; } @@ -279,15 +276,6 @@ instantiate(const LV2_Descriptor* descriptor, map_sampler_uris(self->map, &self->uris); lv2_atom_forge_init(&self->forge, self->map); - // Load the default sample file - const size_t path_len = strlen(path); - const size_t file_len = strlen(default_sample_file); - const size_t len = path_len + file_len; - char* sample_path = (char*)malloc(len + 1); - snprintf(sample_path, len + 1, "%s%s", path, default_sample_file); - self->sample = load_sample(self, sample_path); - free(sample_path); - return (LV2_Handle)self; } @@ -299,6 +287,18 @@ cleanup(LV2_Handle instance) free(self); } +static void +activate(LV2_Handle instance) +{ + ((Sampler*)instance)->activated = true; +} + +static void +deactivate(LV2_Handle instance) +{ + ((Sampler*)instance)->activated = false; +} + /** Define a macro for converting a gain in dB to a coefficient. */ #define DB_CO(g) ((g) > -90.0f ? powf(10.0f, (g) * 0.05f) : 0.0f) @@ -367,7 +367,7 @@ run(LV2_Handle instance, const uint32_t key = ((const LV2_Atom_URID*)property)->body; if (key == uris->eg_sample) { // Sample change, send it to the worker. - lv2_log_trace(&self->logger, "Queueing set message\n"); + lv2_log_trace(&self->logger, "Scheduling sample change\n"); self->schedule->schedule_work(self->schedule->handle, lv2_atom_total_size(&ev->body), &ev->body); @@ -379,7 +379,6 @@ run(LV2_Handle instance, } } else if (obj->body.otype == uris->patch_Get) { // Received a get message, emit our state (probably to UI) - lv2_log_trace(&self->logger, "Responding to get request\n"); lv2_atom_forge_frame_time(&self->forge, self->frame_offset); write_set_file(&self->forge, &self->uris, self->sample->path, @@ -462,6 +461,19 @@ restore(LV2_Handle instance, { Sampler* self = (Sampler*)instance; + // Get host features + LV2_Worker_Schedule* schedule = NULL; + LV2_State_Map_Path* paths = NULL; + const char* missing = lv2_features_query( + features, + LV2_STATE__mapPath, &paths, true, + LV2_WORKER__schedule, &schedule, false, + NULL); + if (missing) { + lv2_log_error(&self->logger, "Missing feature <%s>\n", missing); + return LV2_STATE_ERR_NO_FEATURE; + } + // Get eg:sample from state size_t size; uint32_t type; @@ -476,21 +488,29 @@ restore(LV2_Handle instance, return LV2_STATE_ERR_BAD_TYPE; } - LV2_State_Map_Path* map_path = (LV2_State_Map_Path*)lv2_features_data( - features, LV2_STATE__mapPath); - if (!map_path) { - return LV2_STATE_ERR_NO_FEATURE; - } - // Map abstract state path to absolute path const char* apath = (const char*)value; - char* path = map_path->absolute_path(map_path->handle, apath); + char* path = paths->absolute_path(paths->handle, apath); // Replace current sample with the new one - lv2_log_trace(&self->logger, "Restoring file %s\n", path); - free_sample(self, self->sample); - self->sample = load_sample(self, path); - self->sample_changed = true; + if (!self->activated || !schedule) { + // No scheduling available, load sample immediately + lv2_log_trace(&self->logger, "Synchronous restore\n"); + free_sample(self, self->sample); + self->sample = load_sample(&self->logger, path); + self->sample_changed = true; + } else { + // Schedule sample to be loaded by the provided worker + lv2_log_trace(&self->logger, "Scheduling restore\n"); + LV2_Atom_Forge forge; + LV2_Atom* buf = calloc(1, sizeof(LV2_Atom) * 32 + strlen(path)); + lv2_atom_forge_init(&forge, self->map); + lv2_atom_forge_set_sink(&forge, atom_sink, atom_sink_deref, buf); + write_set_file(&forge, &self->uris, path, strlen(path)); + + const uint32_t msg_size = lv2_atom_pad_size(buf->size); + schedule->schedule_work(self->schedule->handle, msg_size, buf + 1); + } return LV2_STATE_SUCCESS; } @@ -512,9 +532,9 @@ static const LV2_Descriptor descriptor = { EG_SAMPLER_URI, instantiate, connect_port, - NULL, // activate, + activate, run, - NULL, // deactivate, + deactivate, cleanup, extension_data }; diff --git a/plugins/eg-sampler.lv2/sampler.ttl b/plugins/eg-sampler.lv2/sampler.ttl index 197a735..f4a9c43 100644 --- a/plugins/eg-sampler.lv2/sampler.ttl +++ b/plugins/eg-sampler.lv2/sampler.ttl @@ -19,15 +19,16 @@ doap:name "Example Sampler" ; doap:license <http://opensource.org/licenses/isc> ; lv2:project <http://lv2plug.in/ns/lv2> ; - lv2:requiredFeature urid:map , + lv2:requiredFeature state:loadDefaultState , + urid:map , work:schedule ; lv2:optionalFeature lv2:hardRTCapable , - state:loadDefaultState ; + state:threadSafeRestore ; lv2:extensionData state:interface , work:interface ; ui:ui <http://lv2plug.in/plugins/eg-sampler#ui> ; - patch:writable <http://lv2plug.in/plugins/eg-sampler#sample> ; - patch:writable param:gain ; + patch:writable <http://lv2plug.in/plugins/eg-sampler#sample> , + param:gain ; lv2:port [ a lv2:InputPort , atom:AtomPort ; diff --git a/plugins/eg-sampler.lv2/sampler_ui.c b/plugins/eg-sampler.lv2/sampler_ui.c index 1a8ee2e..23204c5 100644 --- a/plugins/eg-sampler.lv2/sampler_ui.c +++ b/plugins/eg-sampler.lv2/sampler_ui.c @@ -78,8 +78,8 @@ on_load_clicked(GtkWidget* widget, uint8_t obj_buf[OBJ_BUF_SIZE]; lv2_atom_forge_set_buffer(&ui->forge, obj_buf, OBJ_BUF_SIZE); - LV2_Atom* msg = write_set_file(&ui->forge, &ui->uris, - filename, strlen(filename)); + LV2_Atom* msg = (LV2_Atom*)write_set_file(&ui->forge, &ui->uris, + filename, strlen(filename)); ui->write(ui->controller, 0, lv2_atom_total_size(msg), ui->uris.atom_eventTransfer, @@ -170,20 +170,18 @@ port_event(LV2UI_Handle handle, if (format == ui->uris.atom_eventTransfer) { const LV2_Atom* atom = (const LV2_Atom*)buffer; if (lv2_atom_forge_is_object_type(&ui->forge, atom->type)) { - const LV2_Atom_Object* obj = (const LV2_Atom_Object*)atom; - const LV2_Atom* file_uri = read_set_file(&ui->uris, obj); - if (!file_uri) { - fprintf(stderr, "Unknown message sent to UI.\n"); - return; + const LV2_Atom_Object* obj = (const LV2_Atom_Object*)atom; + const char* uri = read_set_file(&ui->uris, obj); + if (uri) { + gtk_label_set_text(GTK_LABEL(ui->label), uri); + } else { + lv2_log_warning(&ui->logger, "Malformed message\n"); } - - const char* uri = (const char*)LV2_ATOM_BODY_CONST(file_uri); - gtk_label_set_text(GTK_LABEL(ui->label), uri); } else { - fprintf(stderr, "Unknown message type.\n"); + lv2_log_error(&ui->logger, "Unknown message type\n"); } } else { - fprintf(stderr, "Unknown format.\n"); + lv2_log_warning(&ui->logger, "Unknown port event format\n"); } } diff --git a/plugins/eg-sampler.lv2/uris.h b/plugins/eg-sampler.lv2/uris.h index e7c4dcd..fe0497f 100644 --- a/plugins/eg-sampler.lv2/uris.h +++ b/plugins/eg-sampler.lv2/uris.h @@ -1,6 +1,6 @@ /* LV2 Sampler Example Plugin - Copyright 2011-2012 David Robillard <d@drobilla.net> + Copyright 2011-2016 David Robillard <d@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 @@ -73,14 +73,14 @@ map_sampler_uris(LV2_URID_Map* map, SamplerURIs* uris) * patch:property eg:sample ; * patch:value </home/me/foo.wav> . */ -static inline LV2_Atom* +static inline LV2_Atom_Forge_Ref write_set_file(LV2_Atom_Forge* forge, const SamplerURIs* uris, const char* filename, const uint32_t filename_len) { LV2_Atom_Forge_Frame frame; - LV2_Atom* set = (LV2_Atom*)lv2_atom_forge_object( + LV2_Atom_Forge_Ref set = lv2_atom_forge_object( forge, &frame, 0, uris->patch_Set); lv2_atom_forge_key(forge, uris->patch_property); @@ -89,7 +89,6 @@ write_set_file(LV2_Atom_Forge* forge, lv2_atom_forge_path(forge, filename, filename_len); lv2_atom_forge_pop(forge, &frame); - return set; } @@ -100,7 +99,7 @@ write_set_file(LV2_Atom_Forge* forge, * patch:property eg:sample ; * patch:value </home/me/foo.wav> . */ -static inline const LV2_Atom* +static inline const char* read_set_file(const SamplerURIs* uris, const LV2_Atom_Object* obj) { @@ -124,17 +123,17 @@ read_set_file(const SamplerURIs* uris, } /* Get value. */ - const LV2_Atom* file_path = NULL; - lv2_atom_object_get(obj, uris->patch_value, &file_path, 0); - if (!file_path) { + const LV2_Atom* value = NULL; + lv2_atom_object_get(obj, uris->patch_value, &value, 0); + if (!value) { fprintf(stderr, "Malformed set message has no value.\n"); return NULL; - } else if (file_path->type != uris->atom_Path) { + } else if (value->type != uris->atom_Path) { fprintf(stderr, "Set message value is not a Path.\n"); return NULL; } - return file_path; + return LV2_ATOM_BODY_CONST(value); } #endif /* SAMPLER_URIS_H */ |