aboutsummaryrefslogtreecommitdiff
path: root/monitors_midi_pianoroll.c
diff options
context:
space:
mode:
Diffstat (limited to 'monitors_midi_pianoroll.c')
-rw-r--r--monitors_midi_pianoroll.c724
1 files changed, 724 insertions, 0 deletions
diff --git a/monitors_midi_pianoroll.c b/monitors_midi_pianoroll.c
new file mode 100644
index 0000000..0df1f85
--- /dev/null
+++ b/monitors_midi_pianoroll.c
@@ -0,0 +1,724 @@
+/*
+ * 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 <monitors.h>
+
+#include <props.lv2/props.h>
+#include <canvas.lv2/canvas.h>
+#include <canvas.lv2/forge.h>
+
+#ifdef BUILD_IDISP
+# include <canvas.lv2/idisp.h>
+#endif
+
+#define MAX_GRAPH 0x20000 //FIXME actually measure this
+#define MAX_NPROPS 7
+#define MAX_KEYS 0x80
+#define MAX_NOTES 0x80
+#define MAX_VELOCITY 0x80
+#define MASK_KEYS (MAX_KEYS - 1)
+#define MASK_NOTES (MAX_NOTES - 1)
+
+typedef struct _plugstate_t plugstate_t;
+typedef struct _plughandle_t plughandle_t;
+typedef struct _active_note_t active_note_t;
+typedef struct _passive_note_t passive_note_t;
+
+struct _active_note_t {
+ int64_t start;
+ uint32_t style;
+};
+
+struct _passive_note_t {
+ int64_t start;
+ int64_t end;
+ uint32_t style;
+};
+
+struct _plugstate_t {
+ int32_t window;
+ float rotation;
+ int32_t hflip;
+ int32_t vflip;
+ float aspect_ratio;
+ uint8_t graph [MAX_GRAPH];
+ int32_t channel;
+};
+
+struct _plughandle_t {
+ LV2_URID_Map *map;
+
+ LV2_Log_Log *log;
+ LV2_Log_Logger logger;
+
+ const LV2_Atom_Sequence *control;
+ craft_t notify;
+
+ LV2_URID midi_MidiEvent;
+
+#ifdef BUILD_IDISP
+ LV2_Canvas_Idisp idisp;
+#endif
+ LV2_Canvas_URID urid;
+ double sample_rate;
+ float update_rate;
+ uint32_t graph_size;
+ unsigned range;
+ float range_1;
+
+ unsigned spf;
+ unsigned thresh;
+ int64_t cnt;
+
+ plugstate_t state;
+ plugstate_t stash;
+
+ active_note_t actives [MAX_KEYS];
+ passive_note_t passives [MAX_KEYS][MAX_NOTES];
+ unsigned offsets [MAX_KEYS];
+ uint32_t palette [MAX_VELOCITY];
+
+ PROPS_T(props, MAX_NPROPS);
+};
+
+static const uint32_t palette_colors [] = {
+ 0x00ffffff,
+ 0x00ff00ff,
+ 0xffff00ff,
+ 0xff0000ff
+};
+
+static inline void
+_out_of_memory(plughandle_t *handle)
+{
+ if(handle->log)
+ {
+ lv2_log_trace(&handle->logger, "out-of-memory\n");
+ }
+
+ _craft_clear(&handle->notify);
+}
+
+static inline void
+_clear_state(plughandle_t *handle)
+{
+ memset(handle->actives, 0x0, sizeof(handle->actives));
+ memset(handle->passives, 0x0, sizeof(handle->passives));
+}
+
+static void
+_render(plughandle_t *handle, int64_t frames)
+{
+ 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;
+
+ const float dy = 1.f / MASK_KEYS;
+ lv2_atom_forge_set_buffer(forge, handle->state.graph, MAX_GRAPH);
+
+ if( !lv2_atom_forge_tuple(forge, &frame)
+ || !lv2_canvas_forge_beginPath(forge, urid)
+ || !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_lineWidth(forge, urid, dy)
+ || !lv2_canvas_forge_save(forge, urid) )
+ {
+ _out_of_memory(handle);
+ return;
+ }
+
+ if(handle->state.hflip || handle->state.vflip)
+ {
+ const float xx = handle->state.hflip ? -1.f : 1.f;
+ const float xy = 0.f;
+ const float x0 = handle->state.hflip ? 1.f : 0.f;
+
+ const float yy = handle->state.vflip ? -1.f : 1.f;
+ const float yx = 0.f;
+ const float y0 = handle->state.vflip ? 1.f : 0.f;
+
+ if( !lv2_canvas_forge_transform(forge, urid, xx, xy, x0, yy, yx, y0) )
+ {
+ _out_of_memory(handle);
+ return;
+ }
+ }
+
+ if( (fmod(handle->state.rotation, 360.f) != 0.f) )
+ {
+ const float rot = handle->state.rotation / 180.f * M_PI;
+
+ if( !lv2_canvas_forge_translate(forge, urid, 0.5f, 0.5f)
+ || !lv2_canvas_forge_rotate(forge, urid, rot)
+ || !lv2_canvas_forge_translate(forge, urid, -0.5f, -0.5f) )
+ {
+ _out_of_memory(handle);
+ return;
+ }
+ }
+
+ const unsigned range = handle->range;
+ const float range_1 = handle->range_1;
+ const int64_t head = handle->cnt + frames;
+ const int64_t tail = head - range;
+
+ float y = 1.f - dy*0.5f;
+ for(unsigned i = 0; i < MAX_KEYS; i++, y -= dy)
+ {
+ active_note_t *active = &handle->actives[i];
+ passive_note_t *passives = handle->passives[i];
+
+ if(active->start != 0)
+ {
+ const bool start_in_window = (active->start >= tail) && (active->start <= head);
+ const float x0 = start_in_window
+ ? range_1 * (active->start - tail)
+ : 0.f;
+ const float x1 = 1.f;
+ const float line [] = {
+ x0, y,
+ x1, y
+ };
+
+ if( !lv2_canvas_forge_beginPath(forge, urid)
+ || !lv2_canvas_forge_polyLine(forge, urid, 4, line)
+ || !lv2_canvas_forge_style(forge, urid, active->style)
+ || !lv2_canvas_forge_stroke(forge, urid) )
+ {
+ _out_of_memory(handle);
+ return;
+ }
+ }
+
+ for(unsigned j = 0; j < MAX_NOTES; j++)
+ {
+ const unsigned idx = (handle->offsets[i] + j) & MASK_NOTES;
+ passive_note_t *passive = &passives[idx];
+
+ if(passive->start == 0)
+ {
+ break; // skip this key
+ }
+
+ const bool end_in_window = (passive->end >= tail) && (passive->end <= head);
+
+ if(end_in_window)
+ {
+ const bool start_in_window = (passive->start >= tail) && (passive->start <= head);
+ const float x0 = start_in_window
+ ? range_1 * (passive->start - tail)
+ : 0.f;
+ const float x1 = end_in_window
+ ? range_1 * (passive->end - tail)
+ : 1.f;
+ const float line [] = {
+ x0, y,
+ x1, y
+ };
+
+ if( !lv2_canvas_forge_beginPath(forge, urid)
+ || !lv2_canvas_forge_polyLine(forge, urid, 4, line)
+ || !lv2_canvas_forge_style(forge, urid, passive->style)
+ || !lv2_canvas_forge_stroke(forge, urid) )
+ {
+ _out_of_memory(handle);
+ return;
+ }
+ }
+ else
+ {
+ passive->start = 0; // invalidate
+ }
+ }
+ }
+
+ if( !lv2_canvas_forge_restore(forge, urid) )
+ {
+ _out_of_memory(handle);
+ return;
+ }
+
+ 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));
+ }
+
+#ifdef BUILD_IDISP
+ lv2_canvas_idisp_queue_draw(&handle->idisp);
+#endif
+}
+
+static void
+_intercept_window(void *data, int64_t frames,
+ props_impl_t *impl __attribute__((unused)))
+{
+ plughandle_t *handle = data;
+
+ handle->range = 1e-3 * handle->sample_rate * handle->state.window;
+ handle->range_1 = 1.f / handle->range;
+
+ _clear_state(handle);
+ _render(handle, frames);
+}
+
+static void
+_intercept_channel(void *data, int64_t frames,
+ props_impl_t *impl __attribute__((unused)))
+{
+ plughandle_t *handle = data;
+
+ _clear_state(handle);
+ _render(handle, frames);
+}
+
+static const props_def_t defs [MAX_NPROPS] = {
+ {
+ .property = MONITORS__window,
+ .offset = offsetof(plugstate_t, window),
+ .type = LV2_ATOM__Int,
+ .event_cb = _intercept_window
+ },
+ {
+ .property = MONITORS__rotation,
+ .offset = offsetof(plugstate_t, rotation),
+ .type = LV2_ATOM__Float
+ },
+ {
+ .property = MONITORS__horizontalFlip,
+ .offset = offsetof(plugstate_t, hflip),
+ .type = LV2_ATOM__Bool
+ },
+ {
+ .property = MONITORS__verticalFlip,
+ .offset = offsetof(plugstate_t, vflip),
+ .type = LV2_ATOM__Bool
+ },
+ {
+ .access = LV2_PATCH__readable,
+ .property = CANVAS__graph,
+ .offset = offsetof(plugstate_t, graph),
+ .type = LV2_ATOM__Tuple,
+ .max_size = MAX_GRAPH
+ },
+ {
+ .property = CANVAS__aspectRatio,
+ .offset = offsetof(plugstate_t, aspect_ratio),
+ .type = LV2_ATOM__Float
+ },
+ {
+ .property = MONITORS__channel,
+ .offset = offsetof(plugstate_t, channel),
+ .type = LV2_ATOM__Int,
+ .event_cb = _intercept_channel
+ }
+};
+
+static void
+_fill_palette(uint32_t *palette)
+{
+ const unsigned n = sizeof(palette_colors) / sizeof(uint32_t);
+ unsigned I = 0;
+ unsigned J = ceilf((float)MAX_VELOCITY / (n-1));
+
+ for(unsigned i = 0; i < (n-1); i++)
+ {
+ const uint32_t c1 = palette_colors[i];
+ const uint32_t c2 = palette_colors[i+1];
+
+ const uint8_t r1 = (c1 >> 24) & 0xff;
+ const uint8_t r2 = (c2 >> 24) & 0xff;
+ const uint8_t g1 = (c1 >> 16) & 0xff;
+ const uint8_t g2 = (c2 >> 16) & 0xff;
+ const uint8_t b1 = (c1 >> 8) & 0xff;
+ const uint8_t b2 = (c2 >> 8) & 0xff;
+ const uint8_t a1 = (c1 >> 0) & 0xff;
+ const uint8_t a2 = (c2 >> 0) & 0xff;
+
+ for(unsigned j = 0; j < J; j++)
+ {
+ const float p = (float)j / J;
+
+ const uint8_t r = r1 + floorf((r2 - r1)*p);
+ const uint8_t g = g1 + floorf((g2 - g1)*p);
+ const uint8_t b = b1 + floorf((b2 - b1)*p);
+ const uint8_t a = a1 + floorf((a2 - a1)*p);
+
+ if(I + j < MAX_VELOCITY)
+ {
+ palette[I + j] = (r << 24) | (g << 16) | (b << 8) | a;
+ }
+ }
+
+ I += J;
+ }
+}
+
+static LV2_Handle
+instantiate(const LV2_Descriptor* descriptor, double rate,
+ const char *bundle_path __attribute__((unused)),
+ 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;
+#ifdef BUILD_IDISP
+ LV2_Inline_Display *queue_draw = NULL;
+#endif
+ 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;
+ else if(!strcmp(features[i]->URI, LV2_LOG__log))
+ handle->log = features[i]->data;
+#ifdef BUILD_IDISP
+ else if(!strcmp(features[i]->URI, LV2_INLINEDISPLAY__queue_draw))
+ queue_draw = features[i]->data;
+#endif
+ }
+
+ 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;
+ }
+
+ if(handle->log)
+ {
+ lv2_log_logger_init(&handle->logger, handle->map, handle->log);
+ }
+
+ _craft_init(&handle->notify, handle->map);
+ lv2_canvas_urid_init(&handle->urid, handle->map);
+#ifdef BUILD_IDISP
+ lv2_canvas_idisp_init(&handle->idisp, queue_draw, handle->map);
+#endif
+
+ handle->midi_MidiEvent = handle->map->map(handle->map->handle, LV2_MIDI__MidiEvent);
+
+ 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->cnt = 1;
+
+ _render(handle, 0);
+ _fill_palette(handle->palette);
+
+ 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 inline void
+_note_off(plughandle_t *handle, int64_t frames, uint8_t key)
+{
+ active_note_t *active = &handle->actives[key];
+ unsigned *offset = &handle->offsets[key];
+
+ // shift offset left
+ *offset = (*offset - 1) & MASK_NOTES;
+
+ // prepend
+ passive_note_t *passive = &handle->passives[key][*offset];
+ passive->start = active->start;
+ passive->end = handle->cnt + frames;
+ passive->style = active->style;
+
+ active->start = 0; // invalidate this note
+}
+
+static inline void
+_note_on(plughandle_t *handle, int64_t frames, uint8_t key, uint8_t vel)
+{
+ active_note_t *active = &handle->actives[key];
+
+ if(active->start != 0) // found a missing note off
+ {
+ _note_off(handle, frames, key);
+ }
+
+ active->start = handle->cnt + frames;
+ active->style = handle->palette[vel];
+}
+
+static inline void
+_handle_midi(plughandle_t *handle, int64_t frames,
+ uint32_t size __attribute__((unused)), const uint8_t *msg)
+{
+ switch(lv2_midi_message_type(msg))
+ {
+ case LV2_MIDI_MSG_NOTE_ON:
+ {
+ const uint8_t cha = msg[0] & 0x0f;
+
+ if(cha == handle->state.channel)
+ {
+ _note_on(handle, frames, msg[1], msg[2]);
+ }
+
+ } break;
+ case LV2_MIDI_MSG_NOTE_OFF:
+ {
+ const uint8_t cha = msg[0] & 0x0f;
+
+ if(cha == handle->state.channel)
+ {
+ _note_off(handle, frames, msg[1]);
+ }
+
+ } break;
+ default:
+ {
+ // nothing
+ } 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->cnt++, handle->thresh++)
+ {
+ if(handle->thresh >= handle->spf)
+ {
+ handle->thresh = 0;
+
+ _render(handle, from + i);
+ props_set(&handle->props, &handle->notify.forge, from + i,
+ handle->urid.Canvas_graph, &handle->notify.ref);
+ }
+ }
+}
+
+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;
+
+ if(atom->type == handle->midi_MidiEvent)
+ {
+ const uint8_t *msg = LV2_ATOM_BODY_CONST(atom);
+ _handle_midi(handle, ev->time.frames, atom->size, msg);
+ }
+ else
+ {
+ const LV2_Atom_Object *obj = (const LV2_Atom_Object *)atom;
+
+ 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);
+}
+
+#ifdef BUILD_IDISP
+static LV2_Inline_Display_Image_Surface *
+_idisp_render(LV2_Handle instance, uint32_t w, uint32_t h)
+{
+ plughandle_t *handle = instance;
+
+ float aspect_ratio = 1.f;
+
+ {
+ props_impl_t *impl = _props_impl_get(&handle->props,
+ handle->urid.Canvas_aspectRatio);
+
+ if(impl)
+ {
+ _props_impl_spin_lock(impl, PROP_STATE_NONE, PROP_STATE_LOCK);
+
+ memcpy(&aspect_ratio, impl->stash.body, impl->stash.size);
+
+ _props_impl_unlock(impl, PROP_STATE_NONE);
+ }
+ }
+
+ LV2_Inline_Display_Image_Surface *surf =
+ lv2_canvas_idisp_surf_configure(&handle->idisp, w, h, aspect_ratio);
+
+ if(surf)
+ {
+ props_impl_t *impl = _props_impl_get(&handle->props,
+ handle->urid.Canvas_graph);
+
+ if(impl)
+ {
+ _props_impl_spin_lock(impl, PROP_STATE_NONE, PROP_STATE_LOCK);
+
+ lv2_canvas_idisp_render_body(&handle->idisp, impl->type, impl->stash.size,
+ impl->stash.body);
+
+ _props_impl_unlock(impl, PROP_STATE_NONE);
+ }
+ }
+
+ return surf;
+}
+
+static const LV2_Inline_Display_Interface idisp_iface = {
+ .render = _idisp_render
+};
+#endif
+
+static void
+cleanup(LV2_Handle instance)
+{
+ plughandle_t *handle = instance;
+
+ munlock(handle, sizeof(plughandle_t));
+#ifdef BUILD_IDISP
+ lv2_canvas_idisp_deinit(&handle->idisp);
+#endif
+ 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;
+#ifdef BUILD_IDISP
+ else if(!strcmp(uri, LV2_INLINEDISPLAY__interface))
+ return &idisp_iface;
+#endif
+
+ return NULL;
+}
+
+const LV2_Descriptor monitors_midi_pianoroll = {
+ .URI = MONITORS__midi_pianoroll,
+ .instantiate = instantiate,
+ .connect_port = connect_port,
+ .activate = NULL,
+ .run = run,
+ .deactivate = NULL,
+ .cleanup = cleanup,
+ .extension_data = extension_data
+};