aboutsummaryrefslogtreecommitdiff
path: root/monitors_midi_pianoroll.c
diff options
context:
space:
mode:
authorHanspeter Portner <dev@open-music-kontrollers.ch>2018-04-18 19:25:50 +0200
committerHanspeter Portner <dev@open-music-kontrollers.ch>2018-04-18 19:25:50 +0200
commit209b1c28169f9a07b13d9e95801a857bb89e90c8 (patch)
tree7df1c1c714b675321dbb974c377ca5a1e590dad5 /monitors_midi_pianoroll.c
parent23eb22affecca25fa621d34a355dcba95b766c03 (diff)
downloadmonitors.lv2-209b1c28169f9a07b13d9e95801a857bb89e90c8.tar.xz
prototype monitors:midi_pianoroll.
Diffstat (limited to 'monitors_midi_pianoroll.c')
-rw-r--r--monitors_midi_pianoroll.c538
1 files changed, 538 insertions, 0 deletions
diff --git a/monitors_midi_pianoroll.c b/monitors_midi_pianoroll.c
new file mode 100644
index 0000000..06257bd
--- /dev/null
+++ b/monitors_midi_pianoroll.c
@@ -0,0 +1,538 @@
+/*
+ * 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>
+
+#define MAX_GRAPH 0x20000 //FIXME actually measure this
+#define MAX_NPROPS 3
+#define MAX_KEYS 0x80
+#define MAX_NOTES 0x80
+
+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 aspect_ratio;
+ uint8_t graph [MAX_GRAPH];
+};
+
+struct _plughandle_t {
+ LV2_URID_Map *map;
+
+ const LV2_Atom_Sequence *control;
+ craft_t notify;
+
+ LV2_URID midi_MidiEvent;
+
+ LV2_Canvas_URID urid;
+ double sample_rate;
+ float update_rate;
+ uint32_t graph_size;
+
+ uint32_t spf;
+ uint32_t thresh;
+ int64_t cnt;
+
+ plugstate_t state;
+ plugstate_t stash;
+
+ active_note_t actives [MAX_KEYS];
+ passive_note_t passives [MAX_KEYS][MAX_NOTES];
+
+ uint32_t palette [0x80];
+
+ PROPS_T(props, MAX_NPROPS);
+};
+
+static const uint32_t palette_colors [] = {
+ 0x00ffffff,
+ 0x00ff00ff,
+ 0xffff00ff,
+ 0xff0000ff
+};
+
+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 dx = 1.f / 0x7f;
+
+ lv2_atom_forge_set_buffer(forge, handle->state.graph, MAX_GRAPH);
+
+ 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_lineWidth(forge, urid, dx) )
+ {
+ fprintf(stderr, "out-of-memory\n"); //FIXME
+ }
+
+ const int64_t range = 1e-3 * handle->sample_rate * handle->state.window;
+ const float range_1 = 1.f / range;
+ const int64_t head = handle->cnt + frames;
+ const int64_t tail = head - range;
+
+ if(head < range)
+ {
+ return; //FIXME solve this differently
+ }
+
+ float x = dx * 0.5f;
+ for(unsigned i = 0; i < MAX_KEYS; i++, x+=dx)
+ {
+ active_note_t *active = &handle->actives[i];
+ passive_note_t *passives = handle->passives[i];
+
+ if(active->start != 0)
+ {
+ const float y0 = fmaxf(0.f, range_1 * (active->start - tail));
+ const float y1 = 1.f;
+ const float line [] = {
+ x, y0,
+ x, y1
+ };
+
+ if( !lv2_canvas_forge_polyLine(forge, urid, 4, line)
+ || !lv2_canvas_forge_style(forge, urid, active->style)
+ || !lv2_canvas_forge_stroke(forge, urid) )
+ {
+ fprintf(stderr, "out-of-memory\n"); //FIXME
+ }
+ }
+
+ for(unsigned j = 0; j < MAX_NOTES; j++)
+ {
+ passive_note_t *passive = &passives[j];
+
+ if(passive->start == 0)
+ {
+ continue; // skip this key
+ }
+
+ const bool start_in_window = (passive->start > tail) && (passive->start < head);
+ const bool end_in_window = (passive->end > tail) && (passive->end < head);
+
+ if(start_in_window || end_in_window)
+ {
+ const float y0 = start_in_window
+ ? range_1 * (passive->start - tail)
+ : 0.f;
+ const float y1 = end_in_window
+ ? range_1 * (passive->end - tail)
+ : 1.f;
+ const float line [] = {
+ x, y0,
+ x, y1
+ };
+
+ if( !lv2_canvas_forge_polyLine(forge, urid, 4, line)
+ || !lv2_canvas_forge_style(forge, urid, passive->style)
+ || !lv2_canvas_forge_stroke(forge, urid) )
+ {
+ fprintf(stderr, "out-of-memory\n"); //FIXME
+ }
+ }
+ else
+ {
+ passive->start = 0; // invalidate
+ }
+ }
+ }
+
+ {
+ lv2_atom_forge_pop(forge, &frame);
+
+ props_impl_t *impl = _props_bsearch(&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_window(void *data, int64_t frames, props_impl_t *impl)
+{
+ plughandle_t *handle = data;
+
+ // clear note states
+ memset(handle->actives, 0x0, sizeof(handle->actives));
+ memset(handle->passives, 0x0, sizeof(handle->passives));
+
+ _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
+ },
+ {
+ .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
+ }
+};
+
+static void
+_fill_palette(uint32_t *palette)
+{
+ const unsigned n = sizeof(palette_colors) / sizeof(uint32_t);
+ unsigned I = 0;
+ unsigned J = ceilf((float)0x80 / (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 < 0x80)
+ {
+ 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, 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);
+
+ 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;
+ }
+
+ _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];
+ passive_note_t *term = handle->passives[key] + MAX_NOTES;
+ passive_note_t *passive;
+
+ // shift notes right
+ for(passive = term - 1; passive > handle->passives[key]; passive--)
+ {
+ passive[0] = passive[-1];
+ }
+
+ // prepend
+ 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, 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 == 0) //FIXME
+ {
+ _note_on(handle, frames, msg[1], msg[2]);
+ }
+
+ } break;
+ case LV2_MIDI_MSG_NOTE_OFF:
+ {
+ const uint8_t cha = msg[0] & 0x0f;
+
+ if(cha == 0) //FIXME
+ {
+ _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);
+}
+
+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 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
+};