From c6a68dde29f61d6f67439635a33413a8c63f3091 Mon Sep 17 00:00:00 2001 From: David Robillard Date: Sat, 18 Feb 2012 01:15:07 +0000 Subject: Send notifications to the UI and display loaded sample path. --- plugins/eg-sampler.lv2/sampler.c | 239 ++++++++++++++++++------------------ plugins/eg-sampler.lv2/sampler_ui.c | 40 ++++-- plugins/eg-sampler.lv2/uris.h | 70 +++++++++-- 3 files changed, 209 insertions(+), 140 deletions(-) diff --git a/plugins/eg-sampler.lv2/sampler.c b/plugins/eg-sampler.lv2/sampler.c index 82a374c..bf288d7 100644 --- a/plugins/eg-sampler.lv2/sampler.c +++ b/plugins/eg-sampler.lv2/sampler.c @@ -40,6 +40,7 @@ #include #include "lv2/lv2plug.in/ns/ext/atom/atom-helpers.h" +#include "lv2/lv2plug.in/ns/ext/atom/forge.h" #include "lv2/lv2plug.in/ns/ext/message/message.h" #include "lv2/lv2plug.in/ns/ext/state/state.h" #include "lv2/lv2plug.in/ns/ext/urid/urid.h" @@ -62,15 +63,20 @@ enum { static const char* default_sample_file = "monosample.wav"; typedef struct { - SF_INFO info; - float* data; - char* path; + SF_INFO info; /**< Info about sample from sndfile */ + float* data; /**< Sample data in float */ + char* uri; /**< URI of file */ + const char* path; /**< Path of file (pointer into uri) */ + size_t uri_len; /**< Length of uri. */ } Sample; typedef struct { /* Features */ LV2_URID_Map* map; + /* Forge for creating atoms */ + LV2_Atom_Forge forge; + /* Worker thread, communication, and sync */ ZixThread worker_thread; ZixSem signal; @@ -99,20 +105,52 @@ typedef struct { * This is only used internally via ringbuffers, since it is not POD and * therefore not strictly an Atom. */ -typedef struct -{ +typedef struct { LV2_Atom atom; Sample* sample; } SampleMessage; +static bool +parse_file_uri(const char* uri, + const char** host, size_t* host_len, + const char** path, size_t* path_len) +{ + if (strncmp(uri, "file://", strlen("file://"))) { + return false; + } + + *host = uri + strlen("file://"); + const char* host_end = *host; + for (; *host_end && *host_end != '/'; ++host_end) {} + + *host_len = host_end - *host; + *path = host_end; + *path_len = (uri + strlen(uri)) - host_end; + + return true; +} + static Sample* -load_sample(Sampler* plugin, const char* path, uint32_t path_len) +load_sample(Sampler* plugin, const char* uri) { + const size_t uri_len = strlen(uri); + const char* host = NULL; + const char* path = NULL; + size_t host_len = 0; + size_t path_len = 0; + if (!parse_file_uri(uri, &host, &host_len, &path, &path_len)) { + fprintf(stderr, "Request to load bad file URI %s\n", uri); + return NULL; + } + + /* Probably should check if the host is local here, but we'll just + blissfully attempt to load the path on this machine... */ + printf("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); - + if (!sndfile || !info->frames || (info->channels != 1)) { fprintf(stderr, "failed to open sample '%s'.\n", path); free(sample); @@ -130,94 +168,27 @@ load_sample(Sampler* plugin, const char* path, uint32_t path_len) sf_close(sndfile); /* Fill sample struct and return it. */ - sample->data = data; - sample->path = (char*)malloc(path_len + 1); - memcpy(sample->path, path, path_len + 1); + sample->data = data; + sample->uri = (char*)malloc(uri_len + 1); + sample->path = sample->uri + (path - uri); + sample->uri_len = uri_len; + memcpy(sample->uri, uri, uri_len + 1); return sample; } -static bool -is_object_type(Sampler* plugin, LV2_URID type) -{ - return type == plugin->uris.atom_Resource - || type == plugin->uris.atom_Blank; -} - -static bool -parse_file_uri(const char* uri, - const char** host, size_t* host_len, - const char** path, size_t* path_len) -{ - if (strncmp(uri, "file://", strlen("file://"))) { - return false; - } - - *host = uri + strlen("file://"); - const char* host_end = *host; - for (; *host_end && *host_end != '/'; ++host_end) {} - - *host_len = host_end - *host; - *path = host_end; - *path_len = (uri + strlen(uri)) - host_end; - - return true; -} - static bool handle_set_message(Sampler* plugin, const LV2_Atom_Object* obj) { - if (obj->type != plugin->uris.msg_Set) { - fprintf(stderr, "Ignoring unknown message type %d\n", obj->type); - return false; - } - - /* Message should look like this: - * [ - * a msg:Set ; - * msg:body [ - * eg-sampler:file ; - * ] ; - * ] - */ - - /* Get body of message. */ - const LV2_Atom_Object* body = NULL; - lv2_object_getv(obj, plugin->uris.msg_body, &body, 0); - if (!body) { - fprintf(stderr, "Malformed set message has no body.\n"); - return false; - } - if (!is_object_type(plugin, body->atom.type)) { - fprintf(stderr, "Malformed set message has non-object body.\n"); - return false; - } - - /* Get file URI from body. */ - const LV2_Atom* file_uri = NULL; - lv2_object_getv(body, plugin->uris.eg_file, &file_uri, 0); + /* Get file URI from message */ + const LV2_Atom* file_uri = get_msg_file_uri(&plugin->uris, obj); if (!file_uri) { - fprintf(stderr, "Ignored set message with no file URI.\n"); return false; } /* Load sample. */ - const char* uri = (const char*)LV2_ATOM_BODY(file_uri); - const char* host = NULL; - const char* path = NULL; - size_t host_len = 0; - size_t path_len = 0; - if (!parse_file_uri(uri, &host, &host_len, &path, &path_len)) { - fprintf(stderr, "Request to load bad file URI %s\n", uri); - return false; - } - - /* Probably should check if the host is local here, but we'll just - blissfully attempt to load the path on this machine... */ - - Sample* sample = load_sample(plugin, path, path_len); - + Sample* sample = load_sample(plugin, LV2_ATOM_BODY(file_uri)); if (sample) { /* Loaded sample, send it to run() to be applied. */ const SampleMessage msg = { @@ -231,7 +202,16 @@ handle_set_message(Sampler* plugin, return true; } - + +void +free_sample(Sample* sample) +{ + fprintf(stderr, "Freeing %s\n", sample->uri); + free(sample->uri); + free(sample->data); + free(sample); +} + void* worker_thread_main(void* arg) { @@ -251,10 +231,7 @@ worker_thread_main(void* arg) if (obj->type == plugin->uris.eg_freeSample) { /* Free old sample */ SampleMessage* msg = (SampleMessage*)obj; - fprintf(stderr, "Freeing %s\n", msg->sample->path); - free(msg->sample->path); - free(msg->sample->data); - free(msg->sample); + free_sample(msg->sample); } else { /* Handle set message (load sample). */ handle_set_message(plugin, (LV2_Atom_Object*)obj); @@ -305,6 +282,23 @@ instantiate(const LV2_Descriptor* descriptor, memset(plugin->sample, 0, sizeof(Sample)); memset(&plugin->uris, 0, sizeof(plugin->uris)); + /* Scan host features for URID map */ + LV2_URID_Map* map = NULL; + for (int i = 0; features[i]; ++i) { + if (!strcmp(features[i]->URI, LV2_URID_URI "#map")) { + map = (LV2_URID_Map*)features[i]->data; + } + } + if (!map) { + fprintf(stderr, "Host does not support urid:map.\n"); + goto fail; + } + + /* Map URIS and initialise forge */ + plugin->map = map; + map_sampler_uris(plugin->map, &plugin->uris); + lv2_atom_forge_init(&plugin->forge, plugin->map); + /* Create signal for waking up worker thread */ if (zix_sem_init(&plugin->signal, 0)) { fprintf(stderr, "Could not initialize semaphore.\n"); @@ -326,30 +320,13 @@ instantiate(const LV2_Descriptor* descriptor, zix_ring_mlock(plugin->to_worker); zix_ring_mlock(plugin->from_worker); - /* Scan host features for URID map */ - LV2_URID_Map* map = NULL; - for (int i = 0; features[i]; ++i) { - if (!strcmp(features[i]->URI, LV2_URID_URI "#map")) { - map = (LV2_URID_Map*)features[i]->data; - } - } - - if (!map) { - fprintf(stderr, "Host does not support urid:map.\n"); - goto fail; - } - - plugin->map = map; - map_sampler_uris(plugin->map, &plugin->uris); - /* Load the default sample file */ - const size_t path_len = strlen(path); - const size_t sample_file_len = strlen(default_sample_file); - const size_t len = path_len + sample_file_len; - char* sample_path = (char*)malloc(len + 1); - memcpy(sample_path, path, path_len); - memcpy(sample_path + path_len, default_sample_file, sample_file_len + 1); - plugin->sample = load_sample(plugin, sample_path, len); + const size_t path_len = strlen(path); + const size_t file_len = strlen(default_sample_file); + const size_t len = strlen("file://") + path_len + file_len; + char* sample_uri = (char*)malloc(len + 1); + snprintf(sample_uri, len + 1, "file://%s%s", path, default_sample_file); + plugin->sample = load_sample(plugin, sample_uri); return (LV2_Handle)plugin; @@ -369,11 +346,8 @@ cleanup(LV2_Handle instance) zix_sem_destroy(&plugin->signal); zix_ring_free(plugin->to_worker); zix_ring_free(plugin->from_worker); - - free(plugin->sample->data); - free(plugin->sample->path); - free(plugin->sample); - free(instance); + free_sample(plugin->sample); + free(plugin); } static void @@ -395,7 +369,7 @@ run(LV2_Handle instance, plugin->frame = 0; plugin->play = true; } - } else if (is_object_type(plugin, ev->body.type)) { + } else if (is_object_type(&plugin->uris, ev->body.type)) { const LV2_Atom_Object* obj = (LV2_Atom_Object*)&ev->body; if (obj->type == plugin->uris.msg_Set) { /* Received a set message, send it to the worker thread. */ @@ -438,12 +412,19 @@ run(LV2_Handle instance, output[pos] = 0.0f; } + /* Set up forge to write directly to notify output port buffer */ + LV2_Atom* seq = plugin->notify_port->data; + lv2_atom_forge_set_buffer( + &plugin->forge, + LV2_ATOM_CONTENTS(LV2_Atom_Sequence, seq), + plugin->notify_port->capacity); + /* Read messages from worker thread */ SampleMessage m; const uint32_t msize = lv2_atom_pad_size(sizeof(m)); while (zix_ring_read(plugin->from_worker, &m, msize) == msize) { if (m.atom.type == plugin->uris.eg_applySample) { - /** Send a message to the worker to free the current sample */ + /* Send a message to the worker to free the current sample */ SampleMessage free_msg = { { plugin->uris.eg_freeSample, sizeof(plugin->sample) }, plugin->sample @@ -453,8 +434,26 @@ run(LV2_Handle instance, lv2_atom_pad_size(sizeof(free_msg))); zix_sem_post(&plugin->signal); - /** Install the new sample */ + /* Install the new sample */ plugin->sample = m.sample; + + /* Send a notification that we're using a new sample. */ + + lv2_atom_forge_audio_time(&plugin->forge, seq, 0, 0); + + LV2_Atom* set = (LV2_Atom*)lv2_atom_forge_blank( + &plugin->forge, NULL, 0, plugin->uris.msg_Set); + + lv2_atom_forge_property_head(&plugin->forge, set, plugin->uris.msg_body, 0); + LV2_Atom* body = (LV2_Atom*)lv2_atom_forge_blank(&plugin->forge, set, 0, 0); + + lv2_atom_forge_property_head(&plugin->forge, body, plugin->uris.eg_file, 0); + lv2_atom_forge_uri(&plugin->forge, set, + (const uint8_t*)plugin->sample->uri, + plugin->sample->uri_len); + + set->size += body->size; + seq->size += lv2_atom_total_size(set); } else { fprintf(stderr, "Unknown message from worker\n"); } @@ -510,8 +509,8 @@ restore(LV2_Handle instance, if (value) { const char* path = (const char*)value; printf("Restoring file %s\n", path); - // FIXME: leak? - plugin->sample = load_sample(plugin, path, size - 1); + free_sample(plugin->sample); + plugin->sample = load_sample(plugin, path); } } diff --git a/plugins/eg-sampler.lv2/sampler_ui.c b/plugins/eg-sampler.lv2/sampler_ui.c index ad28a0f..179c588 100644 --- a/plugins/eg-sampler.lv2/sampler_ui.c +++ b/plugins/eg-sampler.lv2/sampler_ui.c @@ -43,15 +43,11 @@ typedef struct { LV2UI_Write_Function write; LV2UI_Controller controller; - GtkWidget* button; + GtkWidget* box; + GtkWidget* button; + GtkWidget* label; } SamplerUI; -static LV2_URID -uri_to_id(SamplerUI* ui, const char* uri) -{ - return ui->map->map(ui->map->handle, uri); -} - static void on_load_clicked(GtkWidget* widget, void* handle) @@ -108,11 +104,10 @@ on_load_clicked(GtkWidget* widget, lv2_atom_forge_property_head(&ui->forge, body, ui->uris.eg_file, 0); lv2_atom_forge_uri(&ui->forge, set, (const uint8_t*)file_uri, file_uri_len); - lv2_atom_forge_property_head(&ui->forge, body, ui->uris.msg_body, 0); set->size += body->size; ui->write(ui->controller, 0, sizeof(LV2_Atom) + set->size, - uri_to_id(ui, NS_ATOM "atomTransfer"), + ui->uris.atom_eventTransfer, set); g_free(filename); @@ -132,7 +127,9 @@ instantiate(const LV2UI_Descriptor* descriptor, ui->map = NULL; ui->write = write_function; ui->controller = controller; + ui->box = NULL; ui->button = NULL; + ui->label = NULL; *widget = NULL; @@ -152,12 +149,16 @@ instantiate(const LV2UI_Descriptor* descriptor, lv2_atom_forge_init(&ui->forge, ui->map); + ui->box = gtk_hbox_new(FALSE, 4); + ui->label = gtk_label_new("?"); ui->button = gtk_button_new_with_label("Load Sample"); + gtk_box_pack_start(GTK_BOX(ui->box), ui->label, TRUE, TRUE, 4); + gtk_box_pack_start(GTK_BOX(ui->box), ui->button, FALSE, TRUE, 4); g_signal_connect(ui->button, "clicked", G_CALLBACK(on_load_clicked), ui); - *widget = ui->button; + *widget = ui->box; return ui; } @@ -177,6 +178,25 @@ port_event(LV2UI_Handle handle, uint32_t format, const void* buffer) { + SamplerUI* ui = (SamplerUI*)handle; + if (format == ui->uris.atom_eventTransfer) { + LV2_Atom* atom = (LV2_Atom*)buffer; + if (atom->type == ui->uris.atom_Blank) { + LV2_Atom_Object* obj = (LV2_Atom_Object*)atom; + const LV2_Atom* file_uri = get_msg_file_uri(&ui->uris, obj); + if (!file_uri) { + fprintf(stderr, "Unknown message sent to UI.\n"); + return; + } + + const char* uri = (const char*)LV2_ATOM_BODY(file_uri); + gtk_label_set_text(GTK_LABEL(ui->label), uri); + } else { + fprintf(stderr, "Unknown message type.\n"); + } + } else { + fprintf(stderr, "Unknown format.\n"); + } } const void* diff --git a/plugins/eg-sampler.lv2/uris.h b/plugins/eg-sampler.lv2/uris.h index 8c8801a..6584c13 100644 --- a/plugins/eg-sampler.lv2/uris.h +++ b/plugins/eg-sampler.lv2/uris.h @@ -32,9 +32,10 @@ typedef struct { LV2_URID atom_Blank; LV2_URID atom_Resource; + LV2_URID atom_eventTransfer; LV2_URID eg_applySample; - LV2_URID eg_freeSample; LV2_URID eg_file; + LV2_URID eg_freeSample; LV2_URID midi_Event; LV2_URID msg_Set; LV2_URID msg_body; @@ -44,15 +45,64 @@ typedef struct { static inline void map_sampler_uris(LV2_URID_Map* map, SamplerURIs* uris) { - uris->atom_Blank = map->map(map->handle, NS_ATOM "Blank"); - uris->atom_Resource = map->map(map->handle, NS_ATOM "Resource"); - uris->eg_applySample = map->map(map->handle, APPLY_SAMPLE_URI); - uris->eg_file = map->map(map->handle, FILE_URI); - uris->eg_freeSample = map->map(map->handle, FREE_SAMPLE_URI); - uris->midi_Event = map->map(map->handle, MIDI_EVENT_URI); - uris->msg_Set = map->map(map->handle, LV2_MESSAGE_Set); - uris->msg_body = map->map(map->handle, LV2_MESSAGE_body); - uris->state_Path = map->map(map->handle, LV2_STATE_PATH_URI); + uris->atom_Blank = map->map(map->handle, NS_ATOM "Blank"); + uris->atom_Resource = map->map(map->handle, NS_ATOM "Resource"); + uris->atom_eventTransfer = map->map(map->handle, NS_ATOM "eventTransfer"); + uris->eg_applySample = map->map(map->handle, APPLY_SAMPLE_URI); + uris->eg_file = map->map(map->handle, FILE_URI); + uris->eg_freeSample = map->map(map->handle, FREE_SAMPLE_URI); + uris->midi_Event = map->map(map->handle, MIDI_EVENT_URI); + uris->msg_Set = map->map(map->handle, LV2_MESSAGE_Set); + uris->msg_body = map->map(map->handle, LV2_MESSAGE_body); + uris->state_Path = map->map(map->handle, LV2_STATE_PATH_URI); +} + +static inline bool +is_object_type(const SamplerURIs* uris, LV2_URID type) +{ + return type == uris->atom_Resource + || type == uris->atom_Blank; +} + +static inline const LV2_Atom* +get_msg_file_uri(const SamplerURIs* uris, + const LV2_Atom_Object* obj) +{ + /* Message should look like this: + * [ + * a msg:Set ; + * msg:body [ + * eg-sampler:file ; + * ] ; + * ] + */ + + if (obj->type != uris->msg_Set) { + fprintf(stderr, "Ignoring unknown message type %d\n", obj->type); + return NULL; + } + + /* Get body of message. */ + const LV2_Atom_Object* body = NULL; + lv2_object_getv(obj, uris->msg_body, &body, 0); + if (!body) { + fprintf(stderr, "Malformed set message has no body.\n"); + return NULL; + } + if (!is_object_type(uris, body->atom.type)) { + fprintf(stderr, "Malformed set message has non-object body.\n"); + return NULL; + } + + /* Get file URI from body. */ + const LV2_Atom* file_uri = NULL; + lv2_object_getv(body, uris->eg_file, &file_uri, 0); + if (!file_uri) { + fprintf(stderr, "Ignored set message with no file URI.\n"); + return NULL; + } + + return file_uri; } #endif /* SAMPLER_URIS_H */ -- cgit v1.2.1