aboutsummaryrefslogtreecommitdiff
path: root/widgets_xy_pad.c
diff options
context:
space:
mode:
Diffstat (limited to 'widgets_xy_pad.c')
-rw-r--r--widgets_xy_pad.c462
1 files changed, 462 insertions, 0 deletions
diff --git a/widgets_xy_pad.c b/widgets_xy_pad.c
new file mode 100644
index 0000000..4540265
--- /dev/null
+++ b/widgets_xy_pad.c
@@ -0,0 +1,462 @@
+/*
+ * Copyright (c) 2018 Hanspeter Portner (dev@open-music-kontrollers.ch)
+ *
+ * This is free software: you can redistribute it and/or modify
+ * it under the terms of the Artistic License 2.0 as published by
+ * The Perl Foundation.
+ *
+ * This source is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * Artistic License 2.0 for more details.
+ *
+ * You should have received a copy of the Artistic License 2.0
+ * along the source as a COPYING file. If not, obtain it from
+ * http://www.perlfoundation.org/artistic_license_2_0.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <math.h>
+
+#include <widgets.h>
+
+#include <props.lv2/props.h>
+#include <canvas.lv2/canvas.h>
+#include <canvas.lv2/forge.h>
+
+#define MAX_SAMPLES 512
+#define MAX_GRAPH 0x20000 //FIXME actually measure this
+#define MAX_NPROPS 8
+
+typedef struct _plugstate_t plugstate_t;
+typedef struct _plughandle_t plughandle_t;
+
+struct _plugstate_t {
+ // writable
+ float aspect_ratio;
+ float mouse_pos_x;
+ float mouse_pos_y;
+ int32_t mouse_but_left;
+ int32_t mouse_focus;
+ float x;
+ float y;
+ // readable
+ uint8_t graph [MAX_GRAPH];
+};
+
+struct _plughandle_t {
+ LV2_URID_Map *map;
+
+ const LV2_Atom_Sequence *control;
+ craft_t notify;
+
+ LV2_Canvas_URID urid;
+ double sample_rate;
+ float update_rate;
+
+ uint32_t spf;
+ uint32_t thresh;
+
+ struct {
+ float x;
+ float y;
+ } last;
+
+ plugstate_t state;
+ plugstate_t stash;
+
+ bool dirty;
+
+ PROPS_T(props, MAX_NPROPS);
+};
+
+#define A 0.05f
+#define B (1.f - 2*A)
+
+#define SCALE(v) (B*v)
+#define MAP(v) (A + SCALE(v))
+
+static void
+_render(plughandle_t *handle)
+{
+ LV2_Atom_Forge _forge = handle->notify.forge; // clone forge object
+ LV2_Atom_Forge *forge = &_forge;
+ LV2_Canvas_URID *urid = &handle->urid;
+ LV2_Atom_Forge_Frame frame;
+
+ lv2_atom_forge_set_buffer(forge, handle->state.graph, MAX_GRAPH);
+
+ const float x = MAP(handle->state.x);
+ const float y = MAP(handle->state.y);
+
+ const float hor [4] = {
+ 0.f, y,
+ 1.f, y
+ };
+
+ const float ver [4] = {
+ x, 0.f,
+ x, 1.f
+ };
+
+ if( lv2_atom_forge_tuple(forge, &frame)
+ && lv2_canvas_forge_rectangle(forge, urid, 0.f, 0.f, 1.f, 1.f)
+ && lv2_canvas_forge_style(forge, urid, 0x0000003f)
+ && lv2_canvas_forge_fill(forge, urid)
+
+ && lv2_canvas_forge_rectangle(forge, urid, MAP(0.f), MAP(0.f), SCALE(1.f), SCALE(1.f))
+ && lv2_canvas_forge_style(forge, urid, 0xffffff7f)
+ && lv2_canvas_forge_lineWidth(forge, urid, 0.01f)
+ && lv2_canvas_forge_stroke(forge, urid)
+
+ && lv2_canvas_forge_arc(forge, urid, x, y, 0.05f, 0.f, M_PI*2)
+ && lv2_canvas_forge_style(forge, urid, handle->state.mouse_but_left ? 0xff7f00ff : 0xff7f007f)
+ && lv2_canvas_forge_stroke(forge, urid)
+
+ && lv2_canvas_forge_arc(forge, urid, x, y, 0.025f, 0.f, M_PI*2)
+ && lv2_canvas_forge_style(forge, urid, 0xff7f00ff)
+ && lv2_canvas_forge_fill(forge, urid)
+
+ && lv2_canvas_forge_polyLine(forge, urid, 4, hor)
+ && lv2_canvas_forge_stroke(forge, urid)
+
+ && lv2_canvas_forge_polyLine(forge, urid, 4, ver)
+ && lv2_canvas_forge_stroke(forge, urid) )
+ {
+ lv2_atom_forge_pop(forge, &frame);
+
+ props_impl_t *impl = _props_impl_get(&handle->props, handle->urid.Canvas_graph);
+ if(impl)
+ {
+ const LV2_Atom *value= (const LV2_Atom *)handle->state.graph;
+
+ _props_impl_set(&handle->props, impl, value->type, value->size,
+ LV2_ATOM_BODY_CONST(value));
+ }
+ }
+}
+
+static void
+_intercept_mouse_pos_x(void *data, int64_t frames, props_impl_t *impl)
+{
+ plughandle_t *handle = data;
+
+ if(handle->state.mouse_but_left)
+ {
+ handle->state.x += handle->state.mouse_pos_x - handle->last.x;
+ if(handle->state.x < 0.f)
+ handle->state.x = 0.f;
+ else if(handle->state.x > 1.f)
+ handle->state.x = 1.f;
+
+ const LV2_URID widgets_x = props_map(&handle->props, WIDGETS__x);
+ props_set(&handle->props, &handle->notify.forge, frames,
+ widgets_x, &handle->notify.ref);
+
+ handle->dirty = true;
+ }
+
+ handle->last.x = handle->state.mouse_pos_x;
+}
+
+static void
+_intercept_mouse_pos_y(void *data, int64_t frames, props_impl_t *impl)
+{
+ plughandle_t *handle = data;
+
+ if(handle->state.mouse_but_left)
+ {
+ handle->state.y += handle->state.mouse_pos_y - handle->last.y;
+ if(handle->state.y < 0.f)
+ handle->state.y = 0.f;
+ else if(handle->state.y > 1.f)
+ handle->state.y = 1.f;
+
+ const LV2_URID widgets_y = props_map(&handle->props, WIDGETS__y);
+ props_set(&handle->props, &handle->notify.forge, frames,
+ widgets_y, &handle->notify.ref);
+
+ handle->dirty = true;
+ }
+
+ handle->last.y = handle->state.mouse_pos_y;
+}
+
+static void
+_intercept_mouse_focus(void *data, int64_t frames, props_impl_t *impl)
+{
+ plughandle_t *handle = data;
+
+ if(!handle->state.mouse_focus)
+ {
+ handle->state.mouse_but_left = 0;
+
+ props_set(&handle->props, &handle->notify.forge, frames,
+ handle->urid.Canvas_mouseButtonLeft, &handle->notify.ref);
+
+ handle->dirty = true;
+ }
+}
+
+static void
+_intercept_dirty(void *data, int64_t frames, props_impl_t *impl)
+{
+ plughandle_t *handle = data;
+
+ handle->dirty = true;
+}
+
+static const props_def_t defs [MAX_NPROPS] = {
+ // readable
+ {
+ .access = LV2_PATCH__readable,
+ .property = CANVAS__graph,
+ .offset = offsetof(plugstate_t, graph),
+ .type = LV2_ATOM__Tuple,
+ .max_size = MAX_GRAPH
+ },
+ // writable
+ {
+ .property = WIDGETS__x,
+ .offset = offsetof(plugstate_t, x),
+ .type = LV2_ATOM__Float,
+ .event_cb = _intercept_dirty
+ },
+ {
+ .property = WIDGETS__y,
+ .offset = offsetof(plugstate_t, y),
+ .type = LV2_ATOM__Float,
+ .event_cb = _intercept_dirty
+ },
+ {
+ .property = CANVAS__aspectRatio,
+ .offset = offsetof(plugstate_t, aspect_ratio),
+ .type = LV2_ATOM__Float
+ },
+ {
+ .property = CANVAS__mousePositionX,
+ .offset = offsetof(plugstate_t, mouse_pos_x),
+ .type = LV2_ATOM__Float,
+ .event_cb = _intercept_mouse_pos_x
+ },
+ {
+ .property = CANVAS__mousePositionY,
+ .offset = offsetof(plugstate_t, mouse_pos_y),
+ .type = LV2_ATOM__Float,
+ .event_cb = _intercept_mouse_pos_y
+ },
+ {
+ .property = CANVAS__mouseButtonLeft,
+ .offset = offsetof(plugstate_t, mouse_but_left),
+ .type = LV2_ATOM__Bool,
+ .event_cb = _intercept_dirty
+ },
+ {
+ .property = CANVAS__mouseFocus,
+ .offset = offsetof(plugstate_t, mouse_focus),
+ .type = LV2_ATOM__Bool,
+ .event_cb = _intercept_mouse_focus
+ }
+};
+
+static LV2_Handle
+instantiate(const LV2_Descriptor* descriptor, double rate,
+ const char *bundle_path, const LV2_Feature *const *features)
+{
+ plughandle_t *handle = calloc(1, sizeof(plughandle_t));
+ if(!handle)
+ return NULL;
+ mlock(handle, sizeof(plughandle_t));
+
+ LV2_Options_Option *opts = NULL;
+ for(unsigned i=0; features[i]; i++)
+ {
+ if(!strcmp(features[i]->URI, LV2_URID__map))
+ handle->map = features[i]->data;
+ else if(!strcmp(features[i]->URI, LV2_OPTIONS__options))
+ opts = features[i]->data;
+ }
+
+ if(!handle->map)
+ {
+ fprintf(stderr,
+ "%s: Host does not support urid:map\n", descriptor->URI);
+ free(handle);
+ return NULL;
+ }
+
+ if(!opts)
+ {
+ fprintf(stderr,
+ "%s: Host does not support opts:options\n", descriptor->URI);
+ free(handle);
+ return NULL;
+ }
+
+ _craft_init(&handle->notify, handle->map);
+ lv2_canvas_urid_init(&handle->urid, handle->map);
+
+ const LV2_URID ui_update_rate= handle->map->map(handle->map->handle,
+ LV2_UI__updateRate);
+
+ handle->update_rate = 60.f; // fall-back
+ handle->sample_rate = rate;
+
+ for(LV2_Options_Option *opt = opts;
+ opt && (opt->key != 0) && (opt->value != NULL);
+ opt++)
+ {
+ if( (opt->key == ui_update_rate) && (opt->type == handle->notify.forge.Float) )
+ handle->update_rate = *(float*)opt->value;
+ }
+
+ handle->spf = handle->sample_rate / handle->update_rate;
+
+ if(!props_init(&handle->props, descriptor->URI,
+ defs, MAX_NPROPS, &handle->state, &handle->stash,
+ handle->map, handle))
+ {
+ fprintf(stderr, "failed to initialize property structure\n");
+ free(handle);
+ return NULL;
+ }
+
+ handle->state.x = 0.5f;
+ handle->state.y = 0.5f;
+
+ _render(handle);
+
+ return handle;
+}
+
+static void
+connect_port(LV2_Handle instance, uint32_t port, void *data)
+{
+ plughandle_t *handle = (plughandle_t *)instance;
+
+ switch(port)
+ {
+ case 0:
+ handle->control = (const void *)data;
+ break;
+ case 1:
+ _craft_connect(&handle->notify, data);
+ break;
+
+ default:
+ break;
+ }
+}
+
+static void
+_run(plughandle_t *handle, uint32_t from, uint32_t to)
+{
+ const uint32_t num = (to - from);
+
+ for(uint32_t i = 0; i < num; i++, handle->thresh++)
+ {
+ if(handle->thresh >= handle->spf)
+ {
+ handle->thresh = 0;
+
+ if(handle->dirty)
+ {
+ _render(handle);
+ props_set(&handle->props, &handle->notify.forge, from + i,
+ handle->urid.Canvas_graph, &handle->notify.ref);
+
+ handle->dirty = false;
+ }
+ }
+ }
+}
+
+static void
+run(LV2_Handle instance, uint32_t nsamples)
+{
+ plughandle_t *handle = instance;
+
+ _craft_in(&handle->notify);
+
+ props_idle(&handle->props, &handle->notify.forge, 0, &handle->notify.ref);
+
+ int64_t from = 0;
+
+ LV2_ATOM_SEQUENCE_FOREACH(handle->control, ev)
+ {
+ const LV2_Atom *atom = &ev->body;
+ const LV2_Atom_Object *obj = (const LV2_Atom_Object *)atom;
+
+ if(props_advance(&handle->props, &handle->notify.forge, ev->time.frames, obj, &handle->notify.ref))
+ {
+ const int64_t to = ev->time.frames;
+
+ _run(handle, from, to);
+
+ from = to;
+ }
+ }
+
+ {
+ const int64_t to = nsamples;
+
+ _run(handle, from, to);
+ }
+
+ _craft_out(&handle->notify);
+}
+
+static void
+cleanup(LV2_Handle instance)
+{
+ plughandle_t *handle = instance;
+
+ munlock(handle, sizeof(plughandle_t));
+ free(handle);
+}
+
+static LV2_State_Status
+_state_save(LV2_Handle instance, LV2_State_Store_Function store,
+ LV2_State_Handle state, uint32_t flags,
+ const LV2_Feature *const *features)
+{
+ plughandle_t *handle = instance;
+
+ return props_save(&handle->props, store, state, flags, features);
+}
+
+static LV2_State_Status
+_state_restore(LV2_Handle instance, LV2_State_Retrieve_Function retrieve,
+ LV2_State_Handle state, uint32_t flags,
+ const LV2_Feature *const *features)
+{
+ plughandle_t *handle = instance;
+
+ return props_restore(&handle->props, retrieve, state, flags, features);
+}
+
+static const LV2_State_Interface state_iface = {
+ .save = _state_save,
+ .restore = _state_restore
+};
+
+static const void*
+extension_data(const char* uri)
+{
+ if(!strcmp(uri, LV2_STATE__interface))
+ return &state_iface;
+
+ return NULL;
+}
+
+const LV2_Descriptor widgets_xy_pad= {
+ .URI = WIDGETS__xy_pad,
+ .instantiate = instantiate,
+ .connect_port = connect_port,
+ .activate = NULL,
+ .run = run,
+ .deactivate = NULL,
+ .cleanup = cleanup,
+ .extension_data = extension_data
+};