#include "ui/ui_piano.h" #define PIANO_BLACK_W 11.0f #define PIANO_BLACK_H_PCT 0.6f B32 piano_is_black_key(S32 note) { S32 n = note % 12; return n == 1 || n == 3 || n == 6 || n == 8 || n == 10; } // Velocity-based color: blue (vel 0) → green (mid) → red (vel 127) Clay_Color piano_velocity_color(S32 velocity) { F32 t = (F32)velocity / 127.0f; F32 r, g, b; if (t < 0.5f) { F32 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 { F32 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); } return Clay_Color{r, g, b, 255}; } void ui_piano(UI_PianoState *state, MidiEngine *midi, F32 avail_w, F32 avail_h) { Clay_ElementId piano_id = CLAY_ID("PianoContainer"); CLAY(piano_id, .layout = { .sizing = { .width = CLAY_SIZING_GROW(), .height = CLAY_SIZING_GROW() }, .layoutDirection = CLAY_LEFT_TO_RIGHT, } ) { // Compute black key size proportional to white keys F32 black_key_h = avail_h * PIANO_BLACK_H_PCT; if (black_key_h < uis(20)) black_key_h = uis(20); F32 white_key_w = avail_w / 52.0f; F32 black_key_w = white_key_w * 0.6f; if (black_key_w < uis(8)) black_key_w = uis(8); // White keys (grow to fill width and height) for (S32 note = PIANO_FIRST_NOTE; note <= PIANO_LAST_NOTE; note++) { if (piano_is_black_key(note)) continue; B32 midi_held = midi_is_note_held(midi, note); B32 mouse_held = state->mouse_note == note; Clay_Color bg; if (midi_held) { bg = piano_velocity_color(midi_get_note_velocity(midi, note)); } else if (mouse_held) { bg = g_theme.accent; } else { bg = Clay_Color{240, 240, 240, 255}; } CLAY(CLAY_IDI("PKey", note), .layout = { .sizing = { .width = CLAY_SIZING_GROW(), .height = CLAY_SIZING_GROW() }, }, .backgroundColor = bg, .border = { .color = {190, 190, 190, 255}, .width = { .right = 1 } }, ) {} } // Black keys (floating, attached to left white key) for (S32 note = PIANO_FIRST_NOTE; note <= PIANO_LAST_NOTE; note++) { if (!piano_is_black_key(note)) continue; Clay_ElementId parent_wkey = CLAY_IDI("PKey", note - 1); B32 midi_held = midi_is_note_held(midi, note); B32 mouse_held = state->mouse_note == note; Clay_Color bg; if (midi_held) { bg = piano_velocity_color(midi_get_note_velocity(midi, note)); } else if (mouse_held) { bg = g_theme.accent; } else { bg = Clay_Color{25, 25, 30, 255}; } CLAY(CLAY_IDI("PKey", note), .layout = { .sizing = { .width = CLAY_SIZING_FIXED(black_key_w), .height = CLAY_SIZING_FIXED(black_key_h), }, }, .backgroundColor = bg, .cornerRadius = { .topLeft = 0, .topRight = 0, .bottomLeft = uis(2), .bottomRight = uis(2) }, .floating = { .parentId = parent_wkey.id, .zIndex = 100, .attachPoints = { .element = CLAY_ATTACH_POINT_CENTER_TOP, .parent = CLAY_ATTACH_POINT_RIGHT_TOP, }, .attachTo = CLAY_ATTACH_TO_ELEMENT_WITH_ID, }, ) {} } } } void ui_piano_update_input(UI_PianoState *state) { PlatformInput input = g_wstate.input; if (!input.mouse_down) { state->mouse_note = -1; return; } // Find hovered piano key — check black keys first (they're on top) S32 hovered_note = -1; for (S32 note = PIANO_FIRST_NOTE; note <= PIANO_LAST_NOTE; note++) { if (piano_is_black_key(note) && Clay_PointerOver(CLAY_IDI("PKey", note))) { hovered_note = note; break; } } if (hovered_note == -1) { for (S32 note = PIANO_FIRST_NOTE; note <= PIANO_LAST_NOTE; note++) { if (!piano_is_black_key(note) && Clay_PointerOver(CLAY_IDI("PKey", note))) { hovered_note = note; break; } } } if (hovered_note != -1) { state->mouse_note = hovered_note; } }