Add sick midi input visualizations

This commit is contained in:
2026-03-02 23:37:12 -05:00
parent 46f636a9ac
commit b469b8212f
3 changed files with 281 additions and 9 deletions

View File

@@ -267,31 +267,147 @@ static void build_midi_panel(B32 show, MidiEngine *midi) {
.layout = {
.sizing = { .width = CLAY_SIZING_GROW(), .height = CLAY_SIZING_GROW() },
.padding = { 8, 8, 6, 6 },
.childGap = 4,
.childGap = 6,
.layoutDirection = CLAY_TOP_TO_BOTTOM,
}
) {
// Refresh button
CLAY(CLAY_ID("MidiRefreshBtn"),
Clay_ElementId refresh_eid = CLAY_ID("MidiRefreshBtn");
B32 refresh_hovered = Clay_PointerOver(refresh_eid);
CLAY(refresh_eid,
.layout = {
.sizing = { .width = CLAY_SIZING_FIT(), .height = CLAY_SIZING_FIXED(28) },
.padding = { 12, 12, 0, 0 },
.childAlignment = { .y = CLAY_ALIGN_Y_CENTER },
},
.backgroundColor = Clay_Hovered() ? g_theme.accent_hover : g_theme.bg_lighter,
.backgroundColor = refresh_hovered ? g_theme.accent_hover : g_theme.bg_lighter,
.cornerRadius = CLAY_CORNER_RADIUS(3)
) {
CLAY_TEXT(CLAY_STRING("Refresh"), &g_text_config_normal);
}
if (refresh_hovered && g_wstate.mouse_clicked) {
midi_refresh_devices(midi);
}
// Device list - use static buffers so strings persist for Clay rendering
static char device_bufs[64][128];
int32_t device_count = midi_get_device_count(midi);
// --- Inputs section ---
CLAY_TEXT(CLAY_STRING("Inputs"), &g_text_config_dim);
static const char *note_names[] = {"C","Db","D","Eb","E","F","Gb","G","Ab","A","Bb","B"};
static char note_bufs[64][8];
static char vel_bufs[64][8];
static Clay_TextElementConfig box_text_config;
box_text_config = {};
box_text_config.textColor = Clay_Color{255, 255, 255, 255};
box_text_config.fontSize = 12;
box_text_config.wrapMode = CLAY_TEXT_WRAP_NONE;
B32 has_inputs = 0;
for (int32_t i = 0; i < device_count && i < 64; i++) {
MidiDeviceInfo *dev = midi_get_device(midi, i);
int len = snprintf(device_bufs[i], sizeof(device_bufs[i]), "[%s] %s", dev->is_input ? "IN" : "OUT", dev->name);
if (!dev->is_input) continue;
has_inputs = 1;
int len = snprintf(device_bufs[i], sizeof(device_bufs[i]), "%s", dev->name);
Clay_String device_str = { .isStaticallyAllocated = false, .length = len, .chars = device_bufs[i] };
CLAY(CLAY_IDI("MidiDevice", i),
// Velocity-based color: blue (vel 0) → green (mid) → red (vel 127)
// Idle = dark gray
Clay_Color box_color;
if (dev->active) {
float t = (float)dev->velocity / 127.0f;
float r, g, b;
if (t < 0.5f) {
float s = t * 2.0f;
r = 40.0f + s * (76.0f - 40.0f);
g = 120.0f + s * (175.0f - 120.0f);
b = 220.0f + s * (80.0f - 220.0f);
} else {
float s = (t - 0.5f) * 2.0f;
r = 76.0f + s * (220.0f - 76.0f);
g = 175.0f + s * (50.0f - 175.0f);
b = 80.0f + s * (40.0f - 80.0f);
}
box_color = Clay_Color{r, g, b, 255};
} else if (dev->releasing) {
box_color = Clay_Color{255, 255, 255, 255};
} else {
box_color = Clay_Color{60, 60, 60, 255};
}
// Box text: note name when held, "OFF" when releasing, "---" when idle
int nlen;
if (dev->active) {
int pitch = dev->note % 12;
int octave = (dev->note / 12) - 1;
nlen = snprintf(note_bufs[i], sizeof(note_bufs[i]), "%s%d", note_names[pitch], octave);
} else if (dev->releasing) {
nlen = snprintf(note_bufs[i], sizeof(note_bufs[i]), "OFF");
} else {
nlen = snprintf(note_bufs[i], sizeof(note_bufs[i]), "---");
}
Clay_String note_str = { .isStaticallyAllocated = false, .length = nlen, .chars = note_bufs[i] };
// Box text color: dark for white bg (releasing), white otherwise
Clay_TextElementConfig *box_txt = &box_text_config;
static Clay_TextElementConfig box_text_dark;
box_text_dark = box_text_config;
box_text_dark.textColor = Clay_Color{30, 30, 30, 255};
if (dev->releasing) box_txt = &box_text_dark;
// Velocity text
int vlen;
if (dev->active)
vlen = snprintf(vel_bufs[i], sizeof(vel_bufs[i]), "%d", dev->velocity);
else
vlen = snprintf(vel_bufs[i], sizeof(vel_bufs[i]), "");
Clay_String vel_str = { .isStaticallyAllocated = false, .length = vlen, .chars = vel_bufs[i] };
CLAY(CLAY_IDI("MidiIn", i),
.layout = {
.sizing = { .width = CLAY_SIZING_GROW(), .height = CLAY_SIZING_FIT() },
.padding = { 4, 4, 2, 2 },
.childGap = 6,
.childAlignment = { .y = CLAY_ALIGN_Y_CENTER },
.layoutDirection = CLAY_LEFT_TO_RIGHT,
}
) {
// Note name box (colored by velocity)
CLAY(CLAY_IDI("MidiInNote", i),
.layout = {
.sizing = { .width = CLAY_SIZING_FIXED(36), .height = CLAY_SIZING_FIXED(18) },
.childAlignment = { .x = CLAY_ALIGN_X_CENTER, .y = CLAY_ALIGN_Y_CENTER },
},
.backgroundColor = box_color,
.cornerRadius = CLAY_CORNER_RADIUS(3)
) {
CLAY_TEXT(note_str, box_txt);
}
// Velocity number
CLAY_TEXT(vel_str, &g_text_config_dim);
// Device name
CLAY_TEXT(device_str, &g_text_config_normal);
}
}
if (!has_inputs) {
CLAY_TEXT(CLAY_STRING(" No MIDI inputs"), &g_text_config_dim);
}
// --- Outputs section ---
CLAY_TEXT(CLAY_STRING("Outputs"), &g_text_config_dim);
B32 has_outputs = 0;
for (int32_t i = 0; i < device_count && i < 64; i++) {
MidiDeviceInfo *dev = midi_get_device(midi, i);
if (dev->is_input) continue;
has_outputs = 1;
int len = snprintf(device_bufs[i], sizeof(device_bufs[i]), "%s", dev->name);
Clay_String device_str = { .isStaticallyAllocated = false, .length = len, .chars = device_bufs[i] };
CLAY(CLAY_IDI("MidiOut", i),
.layout = {
.sizing = { .width = CLAY_SIZING_GROW(), .height = CLAY_SIZING_FIT() },
.padding = { 4, 4, 2, 2 },
@@ -300,8 +416,8 @@ static void build_midi_panel(B32 show, MidiEngine *midi) {
CLAY_TEXT(device_str, &g_text_config_normal);
}
}
if (device_count == 0) {
CLAY_TEXT(CLAY_STRING("No MIDI devices found"), &g_text_config_dim);
if (!has_outputs) {
CLAY_TEXT(CLAY_STRING(" No MIDI outputs"), &g_text_config_dim);
}
}
}
@@ -395,6 +511,9 @@ static void do_frame(AppState *app) {
if (!renderer_begin_frame(app->renderer))
return;
// Update MIDI activity timers
midi_update(app->midi, dt);
// Gather input
PlatformInput input = platform_get_input(app->window);
ui_widgets_begin_frame(input);