/* * Copyright (c) 2015-2020 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 #include #include #include 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" }, }; #define TIMECODES_NUM 8 static const midi_msg_t timecodes [TIMECODES_NUM] = { { 0 , "FrameNumber_LSB" }, { 1 , "FrameNumber_MSB" }, { 2 , "Second_LSB" }, { 3 , "Second_MSB" }, { 4 , "Minute_LSB" }, { 5 , "Minute_MSB" }, { 6 , "Hour_LSB" }, { 7 , "RateAndHour_MSB" }, }; 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); } static inline const midi_msg_t * _search_timecode(uint8_t type) { return bsearch(&type, timecodes, TIMECODES_NUM, sizeof(midi_msg_t), _cmp_search); } static const char *keys [12] = { "C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B" }; static inline const char * _note(uint8_t val, int8_t *octave) { *octave = val / 12 - 1; return keys[val % 12]; } static inline void _shadow(struct nk_context *ctx, bool *shadow) { if(*shadow) { struct nk_style *style = &ctx->style; const struct nk_vec2 group_padding = style->window.group_padding; struct nk_command_buffer *canvas = nk_window_get_canvas(ctx); struct nk_rect b = nk_widget_bounds(ctx); b.x -= group_padding.x; b.w *= 10; b.w += 5*group_padding.x; nk_fill_rect(canvas, b, 0.f, nk_rgb(0x28, 0x28, 0x28)); } *shadow = !*shadow; } void _midi_inspector_expose(struct nk_context *ctx, struct nk_rect wbounds, void *data) { plughandle_t *handle = data; handle->dy = 20.f * _get_scale(handle); const float widget_h = handle->dy; struct nk_style *style = &ctx->style; const struct nk_vec2 window_padding = style->window.padding; const struct nk_vec2 group_padding = style->window.group_padding; const char *window_name = "Sherlock"; if(nk_begin(ctx, window_name, wbounds, NK_WINDOW_NO_SCROLLBAR)) { struct nk_panel *panel = nk_window_get_panel(ctx); struct nk_command_buffer *canvas = nk_window_get_canvas(ctx); const float body_h = panel->bounds.h - 4*window_padding.y - 2*widget_h; nk_layout_row_dynamic(ctx, body_h, 1); nk_flags flags = NK_WINDOW_BORDER; if(handle->state.follow) { flags |= NK_WINDOW_NO_SCROLLBAR; } else { handle->shadow = false; } struct nk_list_view lview; if(nk_list_view_begin(ctx, &lview, "Events", flags, widget_h, NK_MIN(handle->n_item, MAX_LINES))) { if(handle->state.follow) { lview.end = NK_MAX(handle->n_item, 0); lview.begin = NK_MAX(lview.end - lview.count, 0); } for(int l = lview.begin; (l < lview.end) && (l < handle->n_item); l++) { item_t *itm = handle->items[l]; switch(itm->type) { case ITEM_TYPE_NONE: { // skip, was sysex payload } break; case ITEM_TYPE_FRAME: { nk_layout_row_dynamic(ctx, widget_h, 3); { struct nk_rect b = nk_widget_bounds(ctx); b.x -= group_padding.x; b.w *= 3; b.w += 4*group_padding.x; nk_fill_rect(canvas, b, 0.f, nk_rgb(0x18, 0x18, 0x18)); } nk_labelf_colored(ctx, NK_TEXT_LEFT, orange, "@%"PRIi64, itm->frame.offset); nk_labelf_colored(ctx, NK_TEXT_CENTERED, green, "-%"PRIu32"-", itm->frame.counter); nk_labelf_colored(ctx, NK_TEXT_RIGHT, violet, "%"PRIi32, itm->frame.nsamples); handle->shadow = false; } break; case ITEM_TYPE_EVENT: { LV2_Atom_Event *ev = &itm->event.ev; const LV2_Atom *body = &ev->body; const int64_t frames = ev->time.frames; const uint8_t *msg = LV2_ATOM_BODY_CONST(body); const uint8_t cmd = (msg[0] & 0xf0) == 0xf0 ? msg[0] : msg[0] & 0xf0; const midi_msg_t *command_msg = _search_command(cmd); const char *command_str = command_msg ? command_msg->key : "Unknown"; char tmp [16]; nk_layout_row_begin(ctx, NK_DYNAMIC, widget_h, 7); { nk_layout_row_push(ctx, 0.1); _shadow(ctx, &handle->shadow); nk_labelf_colored(ctx, NK_TEXT_LEFT, yellow, "+%04"PRIi64, frames); nk_layout_row_push(ctx, 0.2); const unsigned rem = body->size; const unsigned to = rem >= 4 ? 4 : rem % 4; for(unsigned i=0, ptr=0; ikey : "Unknown"; nk_layout_row_push(ctx, 0.2); nk_labelf_colored(ctx, NK_TEXT_RIGHT, cwhite, "%s=%3"PRIu8"", controller_str, msg[1]); nk_layout_row_push(ctx, 0.1); nk_labelf_colored(ctx, NK_TEXT_RIGHT, cwhite, "%"PRIu8, msg[2]); } break; case LV2_MIDI_MSG_PGM_CHANGE: // fall-through case LV2_MIDI_MSG_CHANNEL_PRESSURE: { nk_layout_row_push(ctx, 0.1); nk_labelf_colored(ctx, NK_TEXT_RIGHT, cwhite, "Ch:%2"PRIu8, (msg[0] & 0x0f) + 1); nk_layout_row_push(ctx, 0.2); nk_labelf_colored(ctx, NK_TEXT_RIGHT, cwhite, "%"PRIu8, msg[1]); nk_layout_row_push(ctx, 0.1); _empty(ctx); } break; case LV2_MIDI_MSG_BENDER: { const int16_t bender = (((int16_t)msg[2] << 7) | msg[1]) - 0x2000; nk_layout_row_push(ctx, 0.1); nk_labelf_colored(ctx, NK_TEXT_RIGHT, cwhite, "Ch:%2"PRIu8, (msg[0] & 0x0f) + 1); nk_layout_row_push(ctx, 0.2); nk_labelf_colored(ctx, NK_TEXT_RIGHT, cwhite, "%"PRIi16, bender); nk_layout_row_push(ctx, 0.1); _empty(ctx); } break; case LV2_MIDI_MSG_MTC_QUARTER: { const uint8_t msg_type = msg[1] >> 4; const uint8_t msg_val = msg[1] & 0xf; nk_layout_row_push(ctx, 0.1); _empty(ctx); const midi_msg_t *timecode_msg = _search_timecode(msg_type); const char *timecode_str = timecode_msg ? timecode_msg->key : "Unknown"; nk_layout_row_push(ctx, 0.2); nk_label_colored(ctx, timecode_str, NK_TEXT_RIGHT, cwhite); nk_layout_row_push(ctx, 0.1); nk_labelf_colored(ctx, NK_TEXT_RIGHT, cwhite, "%"PRIu8, msg_val); } break; case LV2_MIDI_MSG_SONG_POS: { const int16_t song_pos= (((int16_t)msg[2] << 7) | msg[1]); nk_layout_row_push(ctx, 0.1); _empty(ctx); nk_layout_row_push(ctx, 0.2); nk_labelf_colored(ctx, NK_TEXT_RIGHT, cwhite, "%"PRIu16, song_pos); nk_layout_row_push(ctx, 0.1); _empty(ctx); } break; case LV2_MIDI_MSG_SONG_SELECT: { nk_layout_row_push(ctx, 0.1); _empty(ctx); nk_layout_row_push(ctx, 0.2); nk_labelf_colored(ctx, NK_TEXT_RIGHT, cwhite, "%"PRIu8, msg[1]); nk_layout_row_push(ctx, 0.1); _empty(ctx); } break; case LV2_MIDI_MSG_SYSTEM_EXCLUSIVE: // fall-throuh case LV2_MIDI_MSG_TUNE_REQUEST: // fall-throuh case LV2_MIDI_MSG_CLOCK: // fall-throuh case LV2_MIDI_MSG_START: // fall-throuh case LV2_MIDI_MSG_CONTINUE: // fall-throuh case LV2_MIDI_MSG_STOP: // fall-throuh case LV2_MIDI_MSG_ACTIVE_SENSE: // fall-throuh case LV2_MIDI_MSG_RESET: { nk_layout_row_push(ctx, 0.1); _empty(ctx); nk_layout_row_push(ctx, 0.2); _empty(ctx); nk_layout_row_push(ctx, 0.1); _empty(ctx); } break; } nk_layout_row_push(ctx, 0.1); nk_labelf_colored(ctx, NK_TEXT_RIGHT, blue, "%"PRIu32, body->size); } nk_layout_row_end(ctx); for(unsigned j=4; jsize; j+=4) { nk_layout_row_begin(ctx, NK_DYNAMIC, widget_h, 7); { nk_layout_row_push(ctx, 0.1); _shadow(ctx, &handle->shadow); _empty(ctx); nk_layout_row_push(ctx, 0.2); const unsigned rem = body->size - j; const unsigned to = rem >= 4 ? 4 : rem % 4; for(unsigned i=0, ptr=0; istate.overwrite); if(state_overwrite != handle->state.overwrite) { handle->state.overwrite = state_overwrite; _set_bool(handle, handle->urid.overwrite, handle->state.overwrite); } nk_label(ctx, "overwrite", NK_TEXT_LEFT); const int32_t state_block = _check(ctx, handle->state.block); if(state_block != handle->state.block) { handle->state.block = state_block; _set_bool(handle, handle->urid.block, handle->state.block); } nk_label(ctx, "block", NK_TEXT_LEFT); const int32_t state_follow = _check(ctx, handle->state.follow); if(state_follow != handle->state.follow) { handle->state.follow = state_follow; _set_bool(handle, handle->urid.follow, handle->state.follow); } nk_label(ctx, "follow", NK_TEXT_LEFT); } const bool max_reached = handle->n_item >= MAX_LINES; nk_layout_row_dynamic(ctx, widget_h, 2); if(nk_button_symbol_label(ctx, max_reached ? NK_SYMBOL_TRIANGLE_RIGHT: NK_SYMBOL_NONE, "clear", NK_TEXT_LEFT)) { _clear(handle); } nk_label(ctx, "Sherlock.lv2: "SHERLOCK_VERSION, NK_TEXT_RIGHT); } nk_end(ctx); }