|
|
|
@ -40,6 +40,7 @@
@@ -40,6 +40,7 @@
|
|
|
|
|
#include <sndfile.h> |
|
|
|
|
|
|
|
|
|
#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 {
@@ -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 {
@@ -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)
@@ -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 <file://hal/home/me/foo.wav> ;
|
|
|
|
|
* ] ; |
|
|
|
|
* ] |
|
|
|
|
*/ |
|
|
|
|
|
|
|
|
|
/* 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,
@@ -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)
@@ -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,
@@ -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,
@@ -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)
@@ -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,
@@ -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,
@@ -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,
@@ -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,
@@ -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); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|