diff options
-rw-r--r-- | plugins/eg-sampler.lv2/sampler.c | 97 | ||||
-rw-r--r-- | plugins/eg-sampler.lv2/sampler_ui.c | 4 | ||||
-rw-r--r-- | wscript | 3 |
3 files changed, 61 insertions, 43 deletions
diff --git a/plugins/eg-sampler.lv2/sampler.c b/plugins/eg-sampler.lv2/sampler.c index 154e717..45d94af 100644 --- a/plugins/eg-sampler.lv2/sampler.c +++ b/plugins/eg-sampler.lv2/sampler.c @@ -20,18 +20,18 @@ /** @file sampler.c Sampler Plugin - A simple example of an LV2 sampler that dynamically loads samples (based on - incoming events) and also triggers their playback (based on incoming MIDI - note events). The sample must be monophonic. - - So that the run() method stays real-time safe, the plugin creates a worker - thread (worker_thread_main) that listens for file loading events. It loads - everything in plugin->pending_samp and then signals the run() that it's time - to install it. run() just has to swap pointers... so the change happens - very fast and atomically. + A simple example of an LV2 sampler that dynamically loads a single sample + (based on incoming events) and triggers their playback (based on incoming + MIDI note events). + + This plugin illustrates: + - UI <=> Plugin communication via events + - Use of the worker extension for non-realtime tasks (sample loading) + - Use of the log extension to print log messages via the host + - Saving plugin state via the state extension + - Dynamic plugin control via the same properties saved to state */ -#include <assert.h> #include <math.h> #include <stdbool.h> #include <stdlib.h> @@ -81,6 +81,8 @@ typedef struct { float* output_port; LV2_Atom_Sequence* control_port; LV2_Atom_Sequence* notify_port; + + /* Forge frame for notify port (for writing worker replies). */ LV2_Atom_Forge_Frame notify_frame; /* URIs */ @@ -113,22 +115,31 @@ print(Sampler* self, LV2_URID type, const char* fmt, ...) /** An atom-like message used internally to apply/free samples. - - This is only used internally to communicate with the worker, it is not an - Atom because it is not POD. + + This is only used internally to communicate with the worker, it is never + sent to the outside world via a port since it is not POD. It is convenient + to use an Atom header so actual atoms can be easily sent through the same + ringbuffer. */ typedef struct { LV2_Atom atom; Sample* sample; } SampleMessage; +/** + Load a new sample and return it. + + Since this is of course not a real-time safe action, this is called in the + worker thread only. The sample is loaded and returned only, plugin state is + not modified. +*/ static Sample* load_sample(Sampler* self, const char* path) { const size_t path_len = strlen(path); print(self, self->uris.log_Trace, - "Loading sample %s\n", path); + "Loading sample %s\n", path); Sample* const sample = (Sample*)malloc(sizeof(Sample)); SF_INFO* const info = &sample->info; @@ -136,7 +147,7 @@ load_sample(Sampler* self, const char* path) if (!sndfile || !info->frames || (info->channels != 1)) { print(self, self->uris.log_Error, - "Failed to open sample '%s'.\n", path); + "Failed to open sample '%s'.\n", path); free(sample); return NULL; } @@ -145,7 +156,7 @@ load_sample(Sampler* self, const char* path) float* const data = malloc(sizeof(float) * info->frames); if (!data) { print(self, self->uris.log_Error, - "Failed to allocate memory for sample.\n"); + "Failed to allocate memory for sample.\n"); return NULL; } sf_seek(sndfile, 0ul, SEEK_SET); @@ -172,7 +183,13 @@ free_sample(Sampler* self, Sample* sample) } } -/** Handle work (load or free a sample) in a non-realtime thread. */ +/** + Do work in a non-realtime thread. + + This is called for every piece of work scheduled in the audio thread using + self->schedule->schedule_work(). A reply can be sent back to the audio + thread using the provided respond function. +*/ static LV2_Worker_Status work(LV2_Handle instance, LV2_Worker_Respond_Function respond, @@ -207,7 +224,13 @@ work(LV2_Handle instance, return LV2_WORKER_SUCCESS; } -/** Handle a response from work() in the audio thread. */ +/** + Handle a response from work() in the audio thread. + + When running normally, this will be called by the host after run(). When + freewheeling, this will be called immediately at the point the work was + scheduled. +*/ static LV2_Worker_Status work_response(LV2_Handle instance, uint32_t size, @@ -220,7 +243,7 @@ work_response(LV2_Handle instance, /* Send a message to the worker to free the current sample */ self->schedule->schedule_work(self->schedule->handle, sizeof(msg), &msg); - + /* Install the new sample */ self->sample = *(Sample**)data; @@ -239,7 +262,6 @@ connect_port(LV2_Handle instance, void* data) { Sampler* self = (Sampler*)instance; - switch (port) { case SAMPLER_CONTROL: self->control_port = (LV2_Atom_Sequence*)data; @@ -261,20 +283,14 @@ instantiate(const LV2_Descriptor* descriptor, const char* path, const LV2_Feature* const* features) { + /* Allocate and initialise instance structure. */ Sampler* self = (Sampler*)malloc(sizeof(Sampler)); if (!self) { return NULL; } - memset(self, 0, sizeof(Sampler)); - self->sample = (Sample*)malloc(sizeof(Sample)); - if (!self->sample) { - return NULL; - } - memset(self->sample, 0, sizeof(Sample)); - - /* Scan and store host features */ + /* Get host features */ for (int i = 0; features[i]; ++i) { if (!strcmp(features[i]->URI, LV2_URID__map)) { self->map = (LV2_URID_Map*)features[i]->data; @@ -292,7 +308,7 @@ instantiate(const LV2_Descriptor* descriptor, goto fail; } - /* Map URIS and initialise forge */ + /* Map URIs and initialise forge */ map_sampler_uris(self->map, &self->uris); lv2_atom_forge_init(&self->forge, self->map); @@ -303,6 +319,7 @@ instantiate(const LV2_Descriptor* descriptor, 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; @@ -323,7 +340,7 @@ static void run(LV2_Handle instance, uint32_t sample_count) { - Sampler* self = (Sampler*)instance; + Sampler* self = (Sampler*)instance; SamplerURIs* uris = &self->uris; sf_count_t start_frame = 0; sf_count_t pos = 0; @@ -345,7 +362,7 @@ run(LV2_Handle instance, if (ev->body.type == uris->midi_Event) { uint8_t* const data = (uint8_t* const)(ev + 1); if ((data[0] & 0xF0) == 0x90) { - start_frame = ev->time.frames; + start_frame = ev->time.frames; self->frame = 0; self->play = true; } @@ -355,15 +372,15 @@ run(LV2_Handle instance, /* Received a set message, send it to the worker. */ print(self, self->uris.log_Trace, "Queueing set message\n"); self->schedule->schedule_work(self->schedule->handle, - lv2_atom_total_size(&ev->body), - &ev->body); + lv2_atom_total_size(&ev->body), + &ev->body); } else { print(self, self->uris.log_Trace, - "Unknown object type %d\n", obj->body.otype); + "Unknown object type %d\n", obj->body.otype); } } else { print(self, self->uris.log_Trace, - "Unknown event type %d\n", ev->body.type); + "Unknown event type %d\n", ev->body.type); } } @@ -407,9 +424,9 @@ save(LV2_Handle instance, } } - Sampler* self = (Sampler*)instance; - char* apath = map_path->abstract_path(map_path->handle, - self->sample->path); + Sampler* self = (Sampler*)instance; + char* apath = map_path->abstract_path(map_path->handle, + self->sample->path); store(handle, self->uris.eg_file, @@ -468,9 +485,9 @@ static const LV2_Descriptor descriptor = { EG_SAMPLER_URI, instantiate, connect_port, - NULL, // activate, + NULL, // activate, run, - NULL, // deactivate, + NULL, // deactivate, cleanup, extension_data }; diff --git a/plugins/eg-sampler.lv2/sampler_ui.c b/plugins/eg-sampler.lv2/sampler_ui.c index c2380eb..06f5a82 100644 --- a/plugins/eg-sampler.lv2/sampler_ui.c +++ b/plugins/eg-sampler.lv2/sampler_ui.c @@ -127,8 +127,8 @@ instantiate(const LV2UI_Descriptor* descriptor, 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); + 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); @@ -361,5 +361,6 @@ def dist(ctx): def lint(ctx): for i in (['lv2/lv2plug.in/ns/lv2core/lv2.h'] + glob.glob('lv2/lv2plug.in/ns/ext/*/*.h') - + glob.glob('lv2/lv2plug.in/ns/extensions/*/*.h')): + + glob.glob('lv2/lv2plug.in/ns/extensions/*/*.h') + + glob.glob('plugins/*/*.c') + glob.glob('plugins/*.*.h')): subprocess.call('cpplint.py --filter=+whitespace/comments,-whitespace/tab,-whitespace/braces,-whitespace/labels,-whitespace/blank_line,-build/header_guard,-readability/casting,-readability/todo,-build/include ' + i, shell=True) |