aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDavid Robillard <d@drobilla.net>2016-07-31 15:18:56 -0400
committerDavid Robillard <d@drobilla.net>2016-07-31 15:19:35 -0400
commitbc5600d2957d45a6745911f0562c365beb70841e (patch)
tree1a7dc654b9b094611e3b3d2820d43ba3a7b8e78e
parentcb0fd14636a32785963474a8d9400ba9bd2ff1e2 (diff)
downloadlv2-bc5600d2957d45a6745911f0562c365beb70841e.tar.xz
eg-sampler: Support thread-safe state restoration
-rw-r--r--lv2/lv2plug.in/ns/meta/meta.ttl2
-rw-r--r--plugins/eg-sampler.lv2/sampler.c146
-rw-r--r--plugins/eg-sampler.lv2/sampler.ttl9
-rw-r--r--plugins/eg-sampler.lv2/sampler_ui.c22
-rw-r--r--plugins/eg-sampler.lv2/uris.h19
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 */