aboutsummaryrefslogtreecommitdiffstats
path: root/plugins/eg05-scope.lv2/examploscope.c
diff options
context:
space:
mode:
Diffstat (limited to 'plugins/eg05-scope.lv2/examploscope.c')
-rw-r--r--plugins/eg05-scope.lv2/examploscope.c404
1 files changed, 404 insertions, 0 deletions
diff --git a/plugins/eg05-scope.lv2/examploscope.c b/plugins/eg05-scope.lv2/examploscope.c
new file mode 100644
index 0000000..13281ab
--- /dev/null
+++ b/plugins/eg05-scope.lv2/examploscope.c
@@ -0,0 +1,404 @@
+/*
+ Copyright 2013 Robin Gareus <robin@gareus.org>
+
+ Permission to use, copy, modify, and/or distribute this software for any
+ purpose with or without fee is hereby granted, provided that the above
+ copyright notice and this permission notice appear in all copies.
+
+ THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+*/
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdint.h>
+
+#include "lv2/lv2plug.in/ns/ext/log/log.h"
+#include "lv2/lv2plug.in/ns/ext/log/logger.h"
+#include "lv2/lv2plug.in/ns/ext/state/state.h"
+#include "lv2/lv2plug.in/ns/lv2core/lv2.h"
+
+#include "./uris.h"
+
+/** Private plugin instance structure. */
+typedef struct {
+ // Port buffers
+ float* input[2];
+ float* output[2];
+ const LV2_Atom_Sequence* control;
+ LV2_Atom_Sequence* notify;
+
+ // Atom forge and URI mapping
+ LV2_URID_Map* map;
+ ScoLV2URIs uris;
+ LV2_Atom_Forge forge;
+ LV2_Atom_Forge_Frame frame;
+
+ // Log feature and convenience API
+ LV2_Log_Log* log;
+ LV2_Log_Logger logger;
+
+ // Instantiation settings
+ uint32_t n_channels;
+ double rate;
+
+ /* The state of the UI is stored here, so that the GUI can be displayed and
+ closed without losing the current settings. It is communicated to the
+ UI using atom messages.
+ */
+ bool ui_active;
+ bool send_settings_to_ui;
+ float ui_amp;
+ uint32_t ui_spp;
+} EgScope;
+
+/** Port indices. */
+typedef enum {
+ SCO_CONTROL = 0,
+ SCO_NOTIFY = 1,
+ SCO_INPUT0 = 2,
+ SCO_OUTPUT0 = 3,
+ SCO_INPUT1 = 4,
+ SCO_OUTPUT1 = 5,
+} PortIndex;
+
+/** Create plugin instance. */
+static LV2_Handle
+instantiate(const LV2_Descriptor* descriptor,
+ double rate,
+ const char* bundle_path,
+ const LV2_Feature* const* features)
+{
+ (void)descriptor; // Unused variable
+ (void)bundle_path; // Unused variable
+
+ // Allocate and initialise instance structure.
+ EgScope* self = (EgScope*)calloc(1, sizeof(EgScope));
+ if (!self) {
+ return NULL;
+ }
+
+ // 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;
+ } else if (!strcmp(features[i]->URI, LV2_LOG__log)) {
+ self->log = (LV2_Log_Log*)features[i]->data;
+ }
+ }
+
+ if (!self->map) {
+ fprintf(stderr, "EgScope.lv2 error: Host does not support urid:map\n");
+ free(self);
+ return NULL;
+ }
+
+ // Decide which variant to use depending on the plugin URI
+ if (!strcmp(descriptor->URI, SCO_URI "#Stereo")) {
+ self->n_channels = 2;
+ } else if (!strcmp(descriptor->URI, SCO_URI "#Mono")) {
+ self->n_channels = 1;
+ } else {
+ free(self);
+ return NULL;
+ }
+
+ // Initialise local variables
+ self->ui_active = false;
+ self->send_settings_to_ui = false;
+ self->rate = rate;
+
+ // Set default UI settings
+ self->ui_spp = 50;
+ self->ui_amp = 1.0;
+
+ // Map URIs and initialise forge/logger
+ map_sco_uris(self->map, &self->uris);
+ lv2_atom_forge_init(&self->forge, self->map);
+ lv2_log_logger_init(&self->logger, self->map, self->log);
+
+ return (LV2_Handle)self;
+}
+
+/** Connect a port to a buffer. */
+static void
+connect_port(LV2_Handle handle,
+ uint32_t port,
+ void* data)
+{
+ EgScope* self = (EgScope*)handle;
+
+ switch ((PortIndex)port) {
+ case SCO_CONTROL:
+ self->control = (const LV2_Atom_Sequence*)data;
+ break;
+ case SCO_NOTIFY:
+ self->notify = (LV2_Atom_Sequence*)data;
+ break;
+ case SCO_INPUT0:
+ self->input[0] = (float*)data;
+ break;
+ case SCO_OUTPUT0:
+ self->output[0] = (float*)data;
+ break;
+ case SCO_INPUT1:
+ self->input[1] = (float*)data;
+ break;
+ case SCO_OUTPUT1:
+ self->output[1] = (float*)data;
+ break;
+ }
+}
+
+/**
+ Forge vector of raw data.
+
+ @param forge Forge to use.
+ @param uris Mapped URI identifiers.
+ @param channel Channel ID to transmit.
+ @param n_samples Number of audio samples to transmit.
+ @param data Actual audio data.
+*/
+static void
+tx_rawaudio(LV2_Atom_Forge* forge,
+ ScoLV2URIs* uris,
+ const int32_t channel,
+ const size_t n_samples,
+ void* data)
+{
+ LV2_Atom_Forge_Frame frame;
+
+ // Forge container object of type 'rawaudio'
+ lv2_atom_forge_frame_time(forge, 0);
+ lv2_atom_forge_blank(forge, &frame, 1, uris->RawAudio);
+
+ // Add integer attribute 'channelid'
+ lv2_atom_forge_property_head(forge, uris->channelID, 0);
+ lv2_atom_forge_int(forge, channel);
+
+ // Add vector of floats raw 'audiodata'
+ lv2_atom_forge_property_head(forge, uris->audioData, 0);
+ lv2_atom_forge_vector(
+ forge, sizeof(float), uris->atom_Float, n_samples, data);
+
+ // Close off atom-object
+ lv2_atom_forge_pop(forge, &frame);
+}
+
+/** Process a block of audio */
+static void
+run(LV2_Handle handle, uint32_t n_samples)
+{
+ EgScope* self = (EgScope*)handle;
+
+ /* Ensure notify port buffer is large enough to hold all audio-samples and
+ configuration settings. A minimum size was requested in the .ttl file,
+ but check here just to be sure.
+
+ TODO: Explain these magic numbers.
+ */
+ const size_t size = (sizeof(float) * n_samples + 64) * self->n_channels;
+ const uint32_t space = self->notify->atom.size;
+ if (space < size + 128) {
+ /* Insufficient space, report error and do nothing. Note that a
+ real-time production plugin mustn't call log functions in run(), but
+ this can be useful for debugging and example purposes.
+ */
+ lv2_log_error(&self->logger, "Buffer size is insufficient\n");
+ return;
+ }
+
+ // Prepare forge buffer and initialize atom-sequence
+ lv2_atom_forge_set_buffer(&self->forge, (uint8_t*)self->notify, space);
+ lv2_atom_forge_sequence_head(&self->forge, &self->frame, 0);
+
+ /* Send settings to UI
+
+ The plugin can continue to run while the UI is closed and re-opened.
+ The state and settings of the UI are kept here and transmitted to the UI
+ every time it asks for them or if the user initializes a 'load preset'.
+ */
+ if (self->send_settings_to_ui && self->ui_active) {
+ self->send_settings_to_ui = false;
+ // Forge container object of type 'ui_state'
+ LV2_Atom_Forge_Frame frame;
+ lv2_atom_forge_frame_time(&self->forge, 0);
+ lv2_atom_forge_blank(&self->forge, &frame, 1, self->uris.ui_State);
+
+ // Add UI state as properties
+ lv2_atom_forge_property_head(&self->forge, self->uris.ui_spp, 0);
+ lv2_atom_forge_int(&self->forge, self->ui_spp);
+ lv2_atom_forge_property_head(&self->forge, self->uris.ui_amp, 0);
+ lv2_atom_forge_float(&self->forge, self->ui_amp);
+ lv2_atom_forge_property_head(&self->forge, self->uris.param_sampleRate, 0);
+ lv2_atom_forge_float(&self->forge, self->rate);
+ lv2_atom_forge_pop(&self->forge, &frame);
+ }
+
+ // Process incoming events from GUI
+ if (self->control) {
+ LV2_Atom_Event* ev = lv2_atom_sequence_begin(&(self->control)->body);
+ // For each incoming message...
+ while (!lv2_atom_sequence_is_end(
+ &self->control->body, self->control->atom.size, ev)) {
+ // If the event is an atom:Blank object
+ if (ev->body.type == self->uris.atom_Blank) {
+ const LV2_Atom_Object* obj = (LV2_Atom_Object*)&ev->body;
+ if (obj->body.otype == self->uris.ui_On) {
+ // If the object is a ui-on, the UI was activated
+ self->ui_active = true;
+ self->send_settings_to_ui = true;
+ } else if (obj->body.otype == self->uris.ui_Off) {
+ // If the object is a ui-off, the UI was closed
+ self->ui_active = false;
+ } else if (obj->body.otype == self->uris.ui_State) {
+ // If the object is a ui-state, it's the current UI settings
+ const LV2_Atom* spp = NULL;
+ const LV2_Atom* amp = NULL;
+ lv2_atom_object_get(obj, self->uris.ui_spp, &spp,
+ self->uris.ui_amp, &amp,
+ 0);
+ if (spp) {
+ self->ui_spp = ((LV2_Atom_Int*)spp)->body;
+ }
+ if (amp) {
+ self->ui_amp = ((LV2_Atom_Float*)amp)->body;
+ }
+ }
+ }
+ ev = lv2_atom_sequence_next(ev);
+ }
+ }
+
+ // Process audio data
+ for (uint32_t c = 0; c < self->n_channels; ++c) {
+ if (self->ui_active) {
+ // If UI is active, send raw audio data to UI
+ tx_rawaudio(&self->forge, &self->uris, c, n_samples, self->input[c]);
+ }
+ // If not processing audio in-place, forward audio
+ if (self->input[c] != self->output[c]) {
+ memcpy(self->output[c], self->input[c], sizeof(float) * n_samples);
+ }
+ }
+
+ // Close off sequence
+ lv2_atom_forge_pop(&self->forge, &self->frame);
+}
+
+static void
+cleanup(LV2_Handle handle)
+{
+ free(handle);
+}
+
+static LV2_State_Status
+state_save(LV2_Handle instance,
+ LV2_State_Store_Function store,
+ LV2_State_Handle handle,
+ uint32_t flags,
+ const LV2_Feature* const* features)
+{
+ EgScope* self = (EgScope*)instance;
+ if (!self) {
+ return LV2_STATE_SUCCESS;
+ }
+
+ /* Store state values. Note these values are POD, but not portable, since
+ different machines may have a different integer endianness or floating
+ point format. However, since standard Atom types are used, a good host
+ will be able to save them portably as text anyway. */
+
+ store(handle, self->uris.ui_spp,
+ (void*)&self->ui_spp, sizeof(uint32_t),
+ self->uris.atom_Int,
+ LV2_STATE_IS_POD);
+
+ store(handle, self->uris.ui_amp,
+ (void*)&self->ui_amp, sizeof(float),
+ self->uris.atom_Float,
+ LV2_STATE_IS_POD);
+
+ return LV2_STATE_SUCCESS;
+}
+
+static LV2_State_Status
+state_restore(LV2_Handle instance,
+ LV2_State_Retrieve_Function retrieve,
+ LV2_State_Handle handle,
+ uint32_t flags,
+ const LV2_Feature* const* features)
+{
+ EgScope* self = (EgScope*)instance;
+
+ size_t size;
+ uint32_t type;
+ uint32_t valflags;
+
+ const void* spp = retrieve(
+ handle, self->uris.ui_spp, &size, &type, &valflags);
+ if (spp && size == sizeof(uint32_t) && type == self->uris.atom_Int) {
+ self->ui_spp = *((uint32_t*)spp);
+ self->send_settings_to_ui = true;
+ }
+
+ const void* amp = retrieve(
+ handle, self->uris.ui_amp, &size, &type, &valflags);
+ if (amp && size == sizeof(float) && type == self->uris.atom_Float) {
+ self->ui_amp = *((float*)amp);
+ self->send_settings_to_ui = true;
+ }
+
+ return LV2_STATE_SUCCESS;
+}
+
+static const void*
+extension_data(const char* uri)
+{
+ static const LV2_State_Interface state = { state_save, state_restore };
+ if (!strcmp(uri, LV2_STATE__interface)) {
+ return &state;
+ }
+ return NULL;
+}
+
+static const LV2_Descriptor descriptor_mono = {
+ SCO_URI "#Mono",
+ instantiate,
+ connect_port,
+ NULL,
+ run,
+ NULL,
+ cleanup,
+ extension_data
+};
+
+static const LV2_Descriptor descriptor_stereo = {
+ SCO_URI "#Stereo",
+ instantiate,
+ connect_port,
+ NULL,
+ run,
+ NULL,
+ cleanup,
+ extension_data
+};
+
+LV2_SYMBOL_EXPORT
+const LV2_Descriptor*
+lv2_descriptor(uint32_t index)
+{
+ switch (index) {
+ case 0:
+ return &descriptor_mono;
+ case 1:
+ return &descriptor_stereo;
+ default:
+ return NULL;
+ }
+}