aboutsummaryrefslogtreecommitdiff
path: root/midi_inspector_eo.c
diff options
context:
space:
mode:
Diffstat (limited to 'midi_inspector_eo.c')
-rw-r--r--midi_inspector_eo.c867
1 files changed, 867 insertions, 0 deletions
diff --git a/midi_inspector_eo.c b/midi_inspector_eo.c
new file mode 100644
index 0000000..0234d68
--- /dev/null
+++ b/midi_inspector_eo.c
@@ -0,0 +1,867 @@
+/*
+ * Copyright (c) 2015 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 <sherlock.h>
+
+#include <Elementary.h>
+
+#define COUNT_MAX 2048 // maximal amount of events shown
+#define STRING_BUF_SIZE 2048
+#define STRING_MAX 256
+#define STRING_OFF (STRING_MAX - 4)
+
+typedef struct _UI UI;
+
+struct _UI {
+ LV2UI_Write_Function write_function;
+ LV2UI_Controller controller;
+
+ LV2_URID_Map *map;
+ LV2_Atom_Forge forge;
+ LV2_URID event_transfer;
+
+ Evas_Object *widget;
+ Evas_Object *table;
+ Evas_Object *list;
+ Evas_Object *clear;
+ Evas_Object *autoclear;
+ Evas_Object *autoblock;
+ Evas_Object *popup;
+
+ Elm_Genlist_Item_Class *itc_midi;
+ Elm_Genlist_Item_Class *itc_group;
+
+ char string_buf [STRING_BUF_SIZE];
+ char *logo_path;
+};
+
+typedef struct _midi_msg_t midi_msg_t;
+
+struct _midi_msg_t {
+ uint8_t type;
+ const char *key;
+};
+
+#define COMMANDS_NUM 18
+static const midi_msg_t commands [COMMANDS_NUM] = {
+ { LV2_MIDI_MSG_NOTE_OFF , "NoteOff" },
+ { LV2_MIDI_MSG_NOTE_ON , "NoteOn" },
+ { LV2_MIDI_MSG_NOTE_PRESSURE , "NotePressure" },
+ { LV2_MIDI_MSG_CONTROLLER , "Controller" },
+ { LV2_MIDI_MSG_PGM_CHANGE , "ProgramChange" },
+ { LV2_MIDI_MSG_CHANNEL_PRESSURE , "ChannelPressure" },
+ { LV2_MIDI_MSG_BENDER , "Bender" },
+ { LV2_MIDI_MSG_SYSTEM_EXCLUSIVE , "SystemExclusive" },
+ { LV2_MIDI_MSG_MTC_QUARTER , "QuarterFrame" },
+ { LV2_MIDI_MSG_SONG_POS , "SongPosition" },
+ { LV2_MIDI_MSG_SONG_SELECT , "SongSelect" },
+ { LV2_MIDI_MSG_TUNE_REQUEST , "TuneRequest" },
+ { LV2_MIDI_MSG_CLOCK , "Clock" },
+ { LV2_MIDI_MSG_START , "Start" },
+ { LV2_MIDI_MSG_CONTINUE , "Continue" },
+ { LV2_MIDI_MSG_STOP , "Stop" },
+ { LV2_MIDI_MSG_ACTIVE_SENSE , "ActiveSense" },
+ { LV2_MIDI_MSG_RESET , "Reset" },
+};
+
+#define CONTROLLERS_NUM 72
+static const midi_msg_t controllers [CONTROLLERS_NUM] = {
+ { LV2_MIDI_CTL_MSB_BANK , "BankSelection_MSB" },
+ { LV2_MIDI_CTL_MSB_MODWHEEL , "Modulation_MSB" },
+ { LV2_MIDI_CTL_MSB_BREATH , "Breath_MSB" },
+ { LV2_MIDI_CTL_MSB_FOOT , "Foot_MSB" },
+ { LV2_MIDI_CTL_MSB_PORTAMENTO_TIME , "PortamentoTime_MSB" },
+ { LV2_MIDI_CTL_MSB_DATA_ENTRY , "DataEntry_MSB" },
+ { LV2_MIDI_CTL_MSB_MAIN_VOLUME , "MainVolume_MSB" },
+ { LV2_MIDI_CTL_MSB_BALANCE , "Balance_MSB" },
+ { LV2_MIDI_CTL_MSB_PAN , "Panpot_MSB" },
+ { LV2_MIDI_CTL_MSB_EXPRESSION , "Expression_MSB" },
+ { LV2_MIDI_CTL_MSB_EFFECT1 , "Effect1_MSB" },
+ { LV2_MIDI_CTL_MSB_EFFECT2 , "Effect2_MSB" },
+ { LV2_MIDI_CTL_MSB_GENERAL_PURPOSE1 , "GeneralPurpose1_MSB" },
+ { LV2_MIDI_CTL_MSB_GENERAL_PURPOSE2 , "GeneralPurpose2_MSB" },
+ { LV2_MIDI_CTL_MSB_GENERAL_PURPOSE3 , "GeneralPurpose3_MSB" },
+ { LV2_MIDI_CTL_MSB_GENERAL_PURPOSE4 , "GeneralPurpose4_MSB" },
+
+ { LV2_MIDI_CTL_LSB_BANK , "BankSelection_LSB" },
+ { LV2_MIDI_CTL_LSB_MODWHEEL , "Modulation_LSB" },
+ { LV2_MIDI_CTL_LSB_BREATH , "Breath_LSB" },
+ { LV2_MIDI_CTL_LSB_FOOT , "Foot_LSB" },
+ { LV2_MIDI_CTL_LSB_PORTAMENTO_TIME , "PortamentoTime_LSB" },
+ { LV2_MIDI_CTL_LSB_DATA_ENTRY , "DataEntry_LSB" },
+ { LV2_MIDI_CTL_LSB_MAIN_VOLUME , "MainVolume_LSB" },
+ { LV2_MIDI_CTL_LSB_BALANCE , "Balance_LSB" },
+ { LV2_MIDI_CTL_LSB_PAN , "Panpot_LSB" },
+ { LV2_MIDI_CTL_LSB_EXPRESSION , "Expression_LSB" },
+ { LV2_MIDI_CTL_LSB_EFFECT1 , "Effect1_LSB" },
+ { LV2_MIDI_CTL_LSB_EFFECT2 , "Effect2_LSB" },
+ { LV2_MIDI_CTL_LSB_GENERAL_PURPOSE1 , "GeneralPurpose1_LSB" },
+ { LV2_MIDI_CTL_LSB_GENERAL_PURPOSE2 , "GeneralPurpose2_LSB" },
+ { LV2_MIDI_CTL_LSB_GENERAL_PURPOSE3 , "GeneralPurpose3_LSB" },
+ { LV2_MIDI_CTL_LSB_GENERAL_PURPOSE4 , "GeneralPurpose4_LSB" },
+
+ { LV2_MIDI_CTL_SUSTAIN , "SustainPedal" },
+ { LV2_MIDI_CTL_PORTAMENTO , "Portamento" },
+ { LV2_MIDI_CTL_SOSTENUTO , "Sostenuto" },
+ { LV2_MIDI_CTL_SOFT_PEDAL , "SoftPedal" },
+ { LV2_MIDI_CTL_LEGATO_FOOTSWITCH , "LegatoFootSwitch" },
+ { LV2_MIDI_CTL_HOLD2 , "Hold2" },
+
+ { LV2_MIDI_CTL_SC1_SOUND_VARIATION , "SC1_SoundVariation" },
+ { LV2_MIDI_CTL_SC2_TIMBRE , "SC2_Timbre" },
+ { LV2_MIDI_CTL_SC3_RELEASE_TIME , "SC3_ReleaseTime" },
+ { LV2_MIDI_CTL_SC4_ATTACK_TIME , "SC4_AttackTime" },
+ { LV2_MIDI_CTL_SC5_BRIGHTNESS , "SC5_Brightness" },
+ { LV2_MIDI_CTL_SC6 , "SC6" },
+ { LV2_MIDI_CTL_SC7 , "SC7" },
+ { LV2_MIDI_CTL_SC8 , "SC8" },
+ { LV2_MIDI_CTL_SC9 , "SC9" },
+ { LV2_MIDI_CTL_SC10 , "SC10" },
+
+ { LV2_MIDI_CTL_GENERAL_PURPOSE5 , "GeneralPurpose5" },
+ { LV2_MIDI_CTL_GENERAL_PURPOSE6 , "GeneralPurpose6" },
+ { LV2_MIDI_CTL_GENERAL_PURPOSE7 , "GeneralPurpose7" },
+ { LV2_MIDI_CTL_GENERAL_PURPOSE8 , "GeneralPurpose8" },
+ { LV2_MIDI_CTL_PORTAMENTO_CONTROL , "PortamentoControl" },
+
+ { LV2_MIDI_CTL_E1_REVERB_DEPTH , "E1_ReverbDepth" },
+ { LV2_MIDI_CTL_E2_TREMOLO_DEPTH , "E2_TremoloDepth" },
+ { LV2_MIDI_CTL_E3_CHORUS_DEPTH , "E3_ChorusDepth" },
+ { LV2_MIDI_CTL_E4_DETUNE_DEPTH , "E4_DetuneDepth" },
+ { LV2_MIDI_CTL_E5_PHASER_DEPTH , "E5_PhaserDepth" },
+
+ { LV2_MIDI_CTL_DATA_INCREMENT , "DataIncrement" },
+ { LV2_MIDI_CTL_DATA_DECREMENT , "DataDecrement" },
+
+ { LV2_MIDI_CTL_NRPN_LSB , "NRPN_LSB" },
+ { LV2_MIDI_CTL_NRPN_MSB , "NRPN_MSB" },
+
+ { LV2_MIDI_CTL_RPN_LSB , "RPN_LSB" },
+ { LV2_MIDI_CTL_RPN_MSB , "RPN_MSB" },
+
+ { LV2_MIDI_CTL_ALL_SOUNDS_OFF , "AllSoundsOff" },
+ { LV2_MIDI_CTL_RESET_CONTROLLERS , "ResetControllers" },
+ { LV2_MIDI_CTL_LOCAL_CONTROL_SWITCH , "LocalControlSwitch" },
+ { LV2_MIDI_CTL_ALL_NOTES_OFF , "AllNotesOff" },
+ { LV2_MIDI_CTL_OMNI_OFF , "OmniOff" },
+ { LV2_MIDI_CTL_OMNI_ON , "OmniOn" },
+ { LV2_MIDI_CTL_MONO1 , "Mono1" },
+ { LV2_MIDI_CTL_MONO2 , "Mono2" },
+};
+
+static int
+_cmp_search(const void *itm1, const void *itm2)
+{
+ const midi_msg_t *msg1 = itm1;
+ const midi_msg_t *msg2 = itm2;
+
+ if(msg1->type < msg2->type)
+ return -1;
+ else if(msg1->type > msg2->type)
+ return 1;
+
+ return 0;
+}
+
+static inline const midi_msg_t *
+_search_command(uint8_t type)
+{
+ return bsearch(&type, commands, COMMANDS_NUM, sizeof(midi_msg_t), _cmp_search);
+}
+
+static inline const midi_msg_t *
+_search_controller(uint8_t type)
+{
+ return bsearch(&type, controllers, CONTROLLERS_NUM, sizeof(midi_msg_t), _cmp_search);
+}
+
+#define CODE_PRE "<font=Mono style=shadow,bottom>"
+#define CODE_POST "</font>"
+
+#define RAW_PRE "<color=#b0b><b>"
+#define RAW_POST "</b></color>"
+
+#define SYSTEM(TYP) "<color=#888><b>"TYP"</b></color>"
+#define CHANNEL(TYP, VAL) "<color=#888><b>"TYP"</b></color><color=#fff>"VAL"</color>"
+#define COMMAND(VAL) "<color=#0b0>"VAL"</color>"
+#define CONTROLLER(VAL) "<color=#b00>"VAL"</color>"
+#define PUNKT(VAL) "<color=#00b>"VAL"</color>"
+
+static const char *keys [12] = {
+ "C", "#C",
+ "D", "#D",
+ "E",
+ "F", "#F",
+ "G", "#G",
+ "A", "#A",
+ "H"
+};
+
+static inline const char *
+_note(uint8_t val, uint8_t *octave)
+{
+ *octave = val / 12;
+
+ return keys[val % 12];
+}
+
+static inline char *
+_atom_stringify(UI *ui, char *ptr, char *end, const LV2_Atom *atom)
+{
+ //FIXME check for buffer overflows!!!
+
+ const uint8_t *midi = LV2_ATOM_BODY_CONST(atom);
+
+ sprintf(ptr, CODE_PRE RAW_PRE);
+ ptr += strlen(ptr);
+
+ char *barrier = ptr + STRING_OFF;
+ unsigned i;
+ for(i=0; (i<atom->size) && (ptr<barrier); i++, ptr += 3)
+ sprintf(ptr, " %02"PRIX8, midi[i]);
+
+ for( ; (i<3) && (ptr<barrier); i++, ptr += 3)
+ sprintf(ptr, " ");
+
+ sprintf(ptr, RAW_POST);
+ ptr += strlen(ptr);
+
+ if( (midi[0] & 0xf0) == 0xf0) // system messages
+ {
+ const midi_msg_t *command_msg = _search_command(midi[0]);
+ const char *command_str = command_msg
+ ? command_msg->key
+ : "Unknown";
+
+ if(midi[0] == LV2_MIDI_MSG_SYSTEM_EXCLUSIVE) // sysex message
+ {
+ for( ; (i<atom->size) && ptr<barrier; i++, ptr += 3)
+ sprintf(ptr, " %02"PRIX8, midi[i]);
+ }
+ else if(midi[0] == LV2_MIDI_MSG_SONG_POS)
+ {
+ uint16_t pos = (((uint16_t)midi[2] << 7) | midi[1]);
+ sprintf(ptr,
+ PUNKT(" [") SYSTEM("Syst.")
+ PUNKT(", ") COMMAND("%s")
+ PUNKT(", ") "%"PRIu16
+ PUNKT("]"),
+ command_str, pos);
+ }
+ else // other system message
+ {
+ if(atom->size == 2)
+ {
+ sprintf(ptr,
+ PUNKT(" [") SYSTEM("Syst.")
+ PUNKT(", ") COMMAND("%s")
+ PUNKT(", ") "%"PRIi8
+ PUNKT("]"),
+ command_str, midi[1]);
+ }
+ else if(atom->size == 3)
+ {
+ sprintf(ptr,
+ PUNKT(" [") SYSTEM("Syst.")
+ PUNKT(", ") COMMAND("%s")
+ PUNKT(", ") "%"PRIi8
+ PUNKT(", ") "%"PRIi8
+ PUNKT("]"),
+ command_str, midi[1], midi[2]);
+ }
+ else // assume atom->size == 1, aka no data
+ {
+ sprintf(ptr,
+ PUNKT(" [") SYSTEM("Syst.")
+ PUNKT(", ") COMMAND("%s")
+ PUNKT("]"),
+ command_str);
+ }
+ }
+ }
+ else // channel messages
+ {
+ const uint8_t command = midi[0] & 0xf0;
+ const uint8_t channel = midi[0] & 0x0f;
+
+ const midi_msg_t *command_msg = _search_command(command);
+ const char *command_str = command_msg
+ ? command_msg->key
+ : "Unknown";
+
+ if( (command == LV2_MIDI_MSG_NOTE_ON)
+ || (command == LV2_MIDI_MSG_NOTE_OFF)
+ || (command == LV2_MIDI_MSG_NOTE_PRESSURE))
+ {
+ uint8_t octave;
+ const char *note = _note(midi[1], &octave);
+ sprintf(ptr,
+ PUNKT(" [") CHANNEL("Ch ", "%02"PRIX8)
+ PUNKT(", ") COMMAND("%s")
+ PUNKT(", ") "%s-%"PRIu8
+ PUNKT(", ") "%"PRIi8
+ PUNKT("]"),
+ channel, command_str, note, octave, midi[2]);
+ }
+ else if(command == LV2_MIDI_MSG_CONTROLLER)
+ {
+ const midi_msg_t *controller_msg = _search_controller(midi[1]);
+ const char *controller_str = controller_msg
+ ? controller_msg->key
+ : "Unknown";
+
+ if(atom->size == 3)
+ {
+ sprintf(ptr,
+ PUNKT(" [") CHANNEL("Ch ", "%02"PRIX8)
+ PUNKT(", ") COMMAND("%s")
+ PUNKT(", ") CONTROLLER("%s")
+ PUNKT(", ") "%"PRIi8
+ PUNKT("]"),
+ channel, command_str, controller_str, midi[2]);
+ }
+ else if(atom->size == 2)
+ {
+ sprintf(ptr,
+ PUNKT(" [") CHANNEL("Ch ", "%02"PRIX8)
+ PUNKT(", ") COMMAND("%s")
+ PUNKT(", ") CONTROLLER("%s")
+ PUNKT("]"),
+ channel, command_str, controller_str);
+ }
+ }
+ else if(command == LV2_MIDI_MSG_BENDER)
+ {
+ int16_t bender = (((int16_t)midi[2] << 7) | midi[1]) - 0x2000;
+ sprintf(ptr,
+ PUNKT(" [") CHANNEL("Ch ", "%02"PRIX8)
+ PUNKT(", ") COMMAND("%s")
+ PUNKT(", ") "%"PRIi16
+ PUNKT("]"),
+ channel, command_str, bender);
+ }
+ else if(atom->size == 3)
+ {
+ sprintf(ptr,
+ PUNKT(" [") CHANNEL("Ch ", "%02"PRIX8)
+ PUNKT(", ") COMMAND("%s")
+ PUNKT(", ") "%"PRIi8
+ PUNKT(", ") "%"PRIi8
+ PUNKT("]"),
+ channel, command_str, midi[1], midi[2]);
+ }
+ else if(atom->size == 2)
+ {
+ sprintf(ptr,
+ PUNKT(" [") CHANNEL("Ch ", "%02"PRIX8)
+ PUNKT(", ") COMMAND("%s")
+ PUNKT(", ") "%"PRIi8
+ PUNKT("]"),
+ channel, command_str, midi[1]);
+ }
+ else // fall-back
+ {
+ sprintf(ptr,
+ PUNKT(" [") CHANNEL("Ch ", "%02"PRIX8)
+ PUNKT(", ") COMMAND("%s")
+ PUNKT("]"),
+ channel, command_str);
+ }
+ }
+ ptr += strlen(ptr);
+
+ if(ptr >= barrier) // there would be more to print
+ {
+ ptr = barrier;
+ sprintf(ptr, "...");
+ ptr += 4;
+ }
+
+ sprintf(ptr, CODE_POST);
+
+ return ptr + strlen(ptr);
+}
+
+static char *
+_midi_label_get(void *data, Evas_Object *obj, const char *part)
+{
+ UI *ui = evas_object_data_get(obj, "ui");
+ const LV2_Atom_Event *ev = data;
+
+ if(!ui)
+ return NULL;
+
+ if(!strcmp(part, "elm.text"))
+ {
+ char *buf = ui->string_buf;
+ char *ptr = buf;
+ char *end = buf + STRING_BUF_SIZE;
+
+ ptr = _atom_stringify(ui, ptr, end, &ev->body);
+
+ return ptr
+ ? strdup(buf)
+ : NULL;
+ }
+
+ return NULL;
+}
+
+static Evas_Object *
+_midi_content_get(void *data, Evas_Object *obj, const char *part)
+{
+ UI *ui = evas_object_data_get(obj, "ui");
+ const LV2_Atom_Event *ev = data;
+
+ if(!ui)
+ return NULL;
+
+ if(!strcmp(part, "elm.swallow.icon"))
+ {
+ char *buf = ui->string_buf;
+
+ sprintf(buf, "<color=#bb0 font=Mono>%04ld</color>", ev->time.frames);
+
+ Evas_Object *label = elm_label_add(obj);
+ if(label)
+ {
+ elm_object_part_text_set(label, "default", buf);
+ evas_object_show(label);
+ }
+
+ return label;
+ }
+ else if(!strcmp(part, "elm.swallow.end"))
+ {
+ char *buf = ui->string_buf;
+
+ sprintf(buf, "<color=#0bb font=Mono>%4u</color>", ev->body.size);
+
+ Evas_Object *label = elm_label_add(obj);
+ if(label)
+ {
+ elm_object_part_text_set(label, "default", buf);
+ evas_object_show(label);
+ }
+
+ return label;
+ }
+
+ return NULL;
+}
+
+static Evas_Object *
+_group_item_content_get(void *data, Evas_Object *obj, const char *part)
+{
+ UI *ui = evas_object_data_get(obj, "ui");
+ const position_t *pos = data;
+
+ if(!ui)
+ return NULL;
+
+ if(!strcmp(part, "elm.swallow.icon"))
+ {
+ char *buf = ui->string_buf;
+
+ sprintf(buf, "<color=#000 font=Mono>0x%"PRIx64"</color>", pos->offset);
+
+ Evas_Object *label = elm_label_add(obj);
+ if(label)
+ {
+ elm_object_part_text_set(label, "default", buf);
+ evas_object_show(label);
+ }
+
+ return label;
+ }
+ else if(!strcmp(part, "elm.swallow.end"))
+ {
+ char *buf = ui->string_buf;
+
+ sprintf(buf, "<color=#0bb font=Mono>%"PRIu32"</color>", pos->nsamples);
+
+ Evas_Object *label = elm_label_add(obj);
+ if(label)
+ {
+ elm_object_part_text_set(label, "default", buf);
+ evas_object_show(label);
+ }
+
+ return label;
+ }
+
+ return NULL;
+}
+
+static void
+_del(void *data, Evas_Object *obj)
+{
+ free(data);
+}
+
+static void
+_clear_update(UI *ui, int count)
+{
+ if(!ui->clear)
+ return;
+
+ char *buf = ui->string_buf;
+ sprintf(buf, "Clear (%"PRIi32" of %"PRIi32")", count, COUNT_MAX);
+ elm_object_text_set(ui->clear, buf);
+}
+
+static void
+_clear_clicked(void *data, Evas_Object *obj, void *event_info)
+{
+ UI *ui = data;
+
+ if(ui->list)
+ elm_genlist_clear(ui->list);
+
+ _clear_update(ui, 0);
+}
+
+static void
+_info_clicked(void *data, Evas_Object *obj, void *event_info)
+{
+ UI *ui = data;
+
+ // toggle popup
+ if(ui->popup)
+ {
+ if(evas_object_visible_get(ui->popup))
+ evas_object_hide(ui->popup);
+ else
+ evas_object_show(ui->popup);
+ }
+}
+
+static void
+_content_free(void *data, Evas *e, Evas_Object *obj, void *event_info)
+{
+ UI *ui = data;
+
+ ui->widget = NULL;
+}
+
+static void
+_content_del(void *data, Evas *e, Evas_Object *obj, void *event_info)
+{
+ UI *ui = data;
+
+ evas_object_del(ui->widget);
+}
+
+static Evas_Object *
+_content_get(UI *ui, Evas_Object *parent)
+{
+ ui->table = elm_table_add(parent);
+ if(ui->table)
+ {
+ elm_table_homogeneous_set(ui->table, EINA_FALSE);
+ elm_table_padding_set(ui->table, 0, 0);
+ evas_object_size_hint_min_set(ui->table, 600, 800);
+ evas_object_event_callback_add(ui->table, EVAS_CALLBACK_FREE, _content_free, ui);
+ evas_object_event_callback_add(ui->table, EVAS_CALLBACK_DEL, _content_del, ui);
+
+ ui->list = elm_genlist_add(ui->table);
+ if(ui->list)
+ {
+ elm_genlist_homogeneous_set(ui->list, EINA_TRUE); // needef for lazy-loading
+ elm_genlist_mode_set(ui->list, ELM_LIST_LIMIT);
+ elm_genlist_block_count_set(ui->list, 64); // needef for lazy-loading
+ elm_genlist_reorder_mode_set(ui->list, EINA_FALSE);
+ elm_genlist_select_mode_set(ui->list, ELM_OBJECT_SELECT_MODE_DEFAULT);
+ evas_object_data_set(ui->list, "ui", ui);
+ evas_object_size_hint_weight_set(ui->list, EVAS_HINT_EXPAND, EVAS_HINT_EXPAND);
+ evas_object_size_hint_align_set(ui->list, EVAS_HINT_FILL, EVAS_HINT_FILL);
+ evas_object_show(ui->list);
+ elm_table_pack(ui->table, ui->list, 0, 0, 4, 1);
+ }
+
+ ui->clear = elm_button_add(ui->table);
+ if(ui->clear)
+ {
+ _clear_update(ui, 0);
+ evas_object_smart_callback_add(ui->clear, "clicked", _clear_clicked, ui);
+ evas_object_size_hint_weight_set(ui->clear, EVAS_HINT_EXPAND, 0.f);
+ evas_object_size_hint_align_set(ui->clear, EVAS_HINT_FILL, EVAS_HINT_FILL);
+ evas_object_show(ui->clear);
+ elm_table_pack(ui->table, ui->clear, 0, 1, 1, 1);
+ }
+
+ ui->autoclear = elm_check_add(ui->table);
+ if(ui->autoclear)
+ {
+ elm_object_text_set(ui->autoclear, "overwrite");
+ evas_object_size_hint_weight_set(ui->autoclear, 0.f, 0.f);
+ evas_object_size_hint_align_set(ui->autoclear, EVAS_HINT_FILL, EVAS_HINT_FILL);
+ evas_object_show(ui->autoclear);
+ elm_table_pack(ui->table, ui->autoclear, 1, 1, 1, 1);
+ }
+
+ ui->autoblock = elm_check_add(ui->table);
+ if(ui->autoblock)
+ {
+ elm_object_text_set(ui->autoblock, "block");
+ evas_object_size_hint_weight_set(ui->autoblock, 0.f, 0.f);
+ evas_object_size_hint_align_set(ui->autoblock, EVAS_HINT_FILL, EVAS_HINT_FILL);
+ evas_object_show(ui->autoblock);
+ elm_table_pack(ui->table, ui->autoblock, 2, 1, 1, 1);
+ }
+
+ Evas_Object *info = elm_button_add(ui->table);
+ if(info)
+ {
+ evas_object_smart_callback_add(info, "clicked", _info_clicked, ui);
+ evas_object_size_hint_weight_set(info, 0.f, 0.f);
+ evas_object_size_hint_align_set(info, 1.f, EVAS_HINT_FILL);
+ evas_object_show(info);
+ elm_table_pack(ui->table, info, 3, 1, 1, 1);
+
+ Evas_Object *icon = elm_icon_add(info);
+ if(icon)
+ {
+ elm_image_file_set(icon, ui->logo_path, NULL);
+ evas_object_size_hint_min_set(icon, 20, 20);
+ evas_object_size_hint_max_set(icon, 32, 32);
+ //evas_object_size_hint_aspect_set(icon, EVAS_ASPECT_CONTROL_BOTH, 1, 1);
+ evas_object_show(icon);
+ elm_object_part_content_set(info, "icon", icon);
+ }
+ }
+
+ ui->popup = elm_popup_add(ui->table);
+ if(ui->popup)
+ {
+ elm_popup_allow_events_set(ui->popup, EINA_TRUE);
+
+ Evas_Object *hbox = elm_box_add(ui->popup);
+ if(hbox)
+ {
+ elm_box_horizontal_set(hbox, EINA_TRUE);
+ elm_box_homogeneous_set(hbox, EINA_FALSE);
+ elm_box_padding_set(hbox, 10, 0);
+ evas_object_show(hbox);
+ elm_object_content_set(ui->popup, hbox);
+
+ Evas_Object *icon = elm_icon_add(hbox);
+ if(icon)
+ {
+ elm_image_file_set(icon, ui->logo_path, NULL);
+ evas_object_size_hint_min_set(icon, 128, 128);
+ evas_object_size_hint_max_set(icon, 256, 256);
+ evas_object_size_hint_aspect_set(icon, EVAS_ASPECT_CONTROL_BOTH, 1, 1);
+ evas_object_show(icon);
+ elm_box_pack_end(hbox, icon);
+ }
+
+ Evas_Object *label = elm_label_add(hbox);
+ if(label)
+ {
+ elm_object_text_set(label,
+ "<color=#b00 shadow_color=#fff font_size=20>"
+ "Sherlock - MIDI Inspector"
+ "</color></br><align=left>"
+ "Version "SHERLOCK_VERSION"</br></br>"
+ "Copyright (c) 2015 Hanspeter Portner</br></br>"
+ "This is free and libre software</br>"
+ "Released under Artistic License 2.0</br>"
+ "By Open Music Kontrollers</br></br>"
+ "<color=#bbb>"
+ "http://open-music-kontrollers.ch/lv2/sherlock</br>"
+ "dev@open-music-kontrollers.ch"
+ "</color></align>");
+
+ evas_object_show(label);
+ elm_box_pack_end(hbox, label);
+ }
+ }
+ }
+ }
+
+ return ui->table;
+}
+
+static LV2UI_Handle
+instantiate(const LV2UI_Descriptor *descriptor, const char *plugin_uri,
+ const char *bundle_path, LV2UI_Write_Function write_function,
+ LV2UI_Controller controller, LV2UI_Widget *widget,
+ const LV2_Feature *const *features)
+{
+ if(strcmp(plugin_uri, SHERLOCK_MIDI_INSPECTOR_URI))
+ return NULL;
+
+ UI *ui = calloc(1, sizeof(UI));
+ if(!ui)
+ return NULL;
+
+ ui->write_function = write_function;
+ ui->controller = controller;
+
+ Evas_Object *parent = NULL;
+ for(int i=0; features[i]; i++)
+ {
+ if(!strcmp(features[i]->URI, LV2_URID__map))
+ ui->map = features[i]->data;
+ else if(!strcmp(features[i]->URI, LV2_UI__parent))
+ parent = features[i]->data;
+ }
+
+ if(!ui->map)
+ {
+ fprintf(stderr, "LV2 URID extension not supported\n");
+ free(ui);
+ return NULL;
+ }
+ if(!parent)
+ {
+ free(ui);
+ return NULL;
+ }
+
+ ui->event_transfer = ui->map->map(ui->map->handle, LV2_ATOM__eventTransfer);
+ lv2_atom_forge_init(&ui->forge, ui->map);
+
+ ui->itc_midi = elm_genlist_item_class_new();
+ if(ui->itc_midi)
+ {
+ ui->itc_midi->item_style = "default_style";
+ ui->itc_midi->func.text_get = _midi_label_get;
+ ui->itc_midi->func.content_get = _midi_content_get;
+ ui->itc_midi->func.state_get = NULL;
+ ui->itc_midi->func.del = _del;
+ }
+
+ ui->itc_group = elm_genlist_item_class_new();
+ if(ui->itc_group)
+ {
+ ui->itc_group->item_style = "default_style";
+ ui->itc_group->func.text_get = NULL;
+ ui->itc_group->func.content_get = _group_item_content_get;
+ ui->itc_group->func.state_get = NULL;
+ ui->itc_group->func.del = _del;
+ }
+
+ sprintf(ui->string_buf, "%s/omk_logo_256x256.png", bundle_path);
+ ui->logo_path = strdup(ui->string_buf);
+
+ ui->widget = _content_get(ui, parent);
+ if(!ui->widget)
+ {
+ free(ui);
+ return NULL;
+ }
+ *(Evas_Object **)widget = ui->widget;
+
+ return ui;
+}
+
+static void
+cleanup(LV2UI_Handle handle)
+{
+ UI *ui = handle;
+
+ if(ui->widget)
+ evas_object_del(ui->widget);
+
+ if(ui->logo_path)
+ free(ui->logo_path);
+
+ if(ui->itc_midi)
+ elm_genlist_item_class_free(ui->itc_midi);
+
+ free(ui);
+}
+
+static void
+port_event(LV2UI_Handle handle, uint32_t i, uint32_t size, uint32_t urid,
+ const void *buf)
+{
+ UI *ui = handle;
+
+ if( (i == 2) && (urid == ui->event_transfer) && ui->list)
+ {
+ const LV2_Atom_Tuple *tup = buf;
+ const LV2_Atom_Long *offset = (const LV2_Atom_Long *)lv2_atom_tuple_begin(tup);
+ const LV2_Atom_Int *nsamples = (const LV2_Atom_Int *)lv2_atom_tuple_next(&offset->atom);
+ const LV2_Atom_Sequence *seq = (const LV2_Atom_Sequence *)lv2_atom_tuple_next(&nsamples->atom);
+ int n = elm_genlist_items_count(ui->list);
+
+ Elm_Object_Item *itm = NULL;
+ if(seq->atom.size > sizeof(LV2_Atom_Sequence_Body)) // there are events
+ {
+ position_t *pos = malloc(sizeof(position_t));
+ if(!pos)
+ return;
+
+ pos->offset = offset->body;
+ pos->nsamples = nsamples->body;
+
+ // check item count
+ if(n + 1 > COUNT_MAX)
+ {
+ if(elm_check_state_get(ui->autoclear))
+ {
+ elm_genlist_clear(ui->list);
+ n = 0;
+ }
+ else
+ {
+ return;
+ }
+ }
+ else if(elm_check_state_get(ui->autoblock))
+ {
+ return;
+ }
+
+ itm = elm_genlist_item_append(ui->list, ui->itc_group,
+ pos, NULL, ELM_GENLIST_ITEM_GROUP, NULL, NULL);
+ elm_genlist_item_select_mode_set(itm, ELM_OBJECT_SELECT_MODE_NONE);
+
+ LV2_ATOM_SEQUENCE_FOREACH(seq, elmnt)
+ {
+ size_t len = sizeof(LV2_Atom_Event) + elmnt->body.size;
+ LV2_Atom_Event *ev = malloc(len);
+ if(!ev)
+ continue;
+
+ memcpy(ev, elmnt, len);
+
+ Elm_Object_Item *itm2 = elm_genlist_item_append(ui->list, ui->itc_midi,
+ ev, itm, ELM_GENLIST_ITEM_NONE, NULL, NULL);
+ elm_genlist_item_select_mode_set(itm2, ELM_OBJECT_SELECT_MODE_DEFAULT);
+ n++;
+
+ // scroll to last item
+ //elm_genlist_item_show(itm, ELM_GENLIST_ITEM_SCROLLTO_MIDDLE);
+ }
+ }
+
+ if(seq->atom.size > sizeof(LV2_Atom_Sequence_Body))
+ _clear_update(ui, n); // only update if there where any events
+ }
+}
+
+const LV2UI_Descriptor midi_inspector_eo = {
+ .URI = SHERLOCK_MIDI_INSPECTOR_EO_URI,
+ .instantiate = instantiate,
+ .cleanup = cleanup,
+ .port_event = port_event,
+ .extension_data = NULL
+};