Add keyboard / sample edit view with piano widget
This commit is contained in:
188
src/main.cpp
188
src/main.cpp
@@ -80,6 +80,10 @@ struct AppState {
|
|||||||
|
|
||||||
// Tab state
|
// Tab state
|
||||||
S32 right_panel_tab; // 0 = Properties, 1 = MIDI Devices
|
S32 right_panel_tab; // 0 = Properties, 1 = MIDI Devices
|
||||||
|
S32 bottom_panel_tab; // 0 = Item Editor, 1 = Sample Mapper
|
||||||
|
|
||||||
|
// Piano state
|
||||||
|
S32 piano_mouse_note; // MIDI note held by mouse click (-1 = none)
|
||||||
|
|
||||||
// Demo widget state
|
// Demo widget state
|
||||||
B32 demo_checkbox_a;
|
B32 demo_checkbox_a;
|
||||||
@@ -138,6 +142,68 @@ struct AppState {
|
|||||||
F32 panel_drag_start_size; // panel size when drag started
|
F32 panel_drag_start_size; // panel size when drag started
|
||||||
};
|
};
|
||||||
|
|
||||||
|
////////////////////////////////
|
||||||
|
// Piano helpers
|
||||||
|
|
||||||
|
#define PIANO_FIRST_NOTE 21 // A0
|
||||||
|
#define PIANO_LAST_NOTE 108 // C8
|
||||||
|
#define PIANO_BLACK_W 11.0f
|
||||||
|
#define PIANO_BLACK_H_PCT 0.6f
|
||||||
|
|
||||||
|
static bool piano_is_black_key(int note) {
|
||||||
|
int 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)
|
||||||
|
static Clay_Color velocity_color(int32_t velocity) {
|
||||||
|
float t = (float)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);
|
||||||
|
}
|
||||||
|
return Clay_Color{r, g, b, 255};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Piano input: handle mouse clicks on piano keys
|
||||||
|
static void update_piano_input(AppState *app) {
|
||||||
|
PlatformInput input = g_wstate.input;
|
||||||
|
|
||||||
|
if (!input.mouse_down) {
|
||||||
|
app->piano_mouse_note = -1;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find hovered piano key — check black keys first (they're on top)
|
||||||
|
S32 hovered_note = -1;
|
||||||
|
for (int 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 (int 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) {
|
||||||
|
app->piano_mouse_note = hovered_note;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
////////////////////////////////
|
////////////////////////////////
|
||||||
// Panel builders
|
// Panel builders
|
||||||
|
|
||||||
@@ -574,20 +640,7 @@ static void build_right_panel(AppState *app) {
|
|||||||
// Idle = dark gray
|
// Idle = dark gray
|
||||||
Clay_Color box_color;
|
Clay_Color box_color;
|
||||||
if (dev->active) {
|
if (dev->active) {
|
||||||
float t = (float)dev->velocity / 127.0f;
|
box_color = velocity_color(dev->velocity);
|
||||||
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) {
|
} else if (dev->releasing) {
|
||||||
box_color = Clay_Color{255, 255, 255, 255};
|
box_color = Clay_Color{255, 255, 255, 255};
|
||||||
} else {
|
} else {
|
||||||
@@ -696,13 +749,12 @@ static void build_log_panel(AppState *app) {
|
|||||||
.backgroundColor = lp_top,
|
.backgroundColor = lp_top,
|
||||||
.custom = { .customData = lp_grad },
|
.custom = { .customData = lp_grad },
|
||||||
) {
|
) {
|
||||||
{
|
static const char *bottom_tabs[] = { "Item Editor", "Sample Mapper" };
|
||||||
S32 sel = 0;
|
ui_tab_bar("BottomTabRow", bottom_tabs, 2, &app->bottom_panel_tab);
|
||||||
static const char *log_tabs[] = { "Log" };
|
|
||||||
ui_tab_bar("LogTabRow", log_tabs, 1, &sel);
|
|
||||||
}
|
|
||||||
|
|
||||||
CLAY(CLAY_ID("LogContent"),
|
if (app->bottom_panel_tab == 0) {
|
||||||
|
// Item Editor tab
|
||||||
|
CLAY(CLAY_ID("ItemEditorContent"),
|
||||||
.layout = {
|
.layout = {
|
||||||
.sizing = { .width = CLAY_SIZING_GROW(), .height = CLAY_SIZING_GROW() },
|
.sizing = { .width = CLAY_SIZING_GROW(), .height = CLAY_SIZING_GROW() },
|
||||||
.padding = { uip(8), uip(8), uip(6), uip(6) },
|
.padding = { uip(8), uip(8), uip(6), uip(6) },
|
||||||
@@ -710,12 +762,99 @@ static void build_log_panel(AppState *app) {
|
|||||||
.layoutDirection = CLAY_TOP_TO_BOTTOM,
|
.layoutDirection = CLAY_TOP_TO_BOTTOM,
|
||||||
}
|
}
|
||||||
) {
|
) {
|
||||||
// Top highlight (beveled edge)
|
CLAY(CLAY_ID("ItemEditorHighlight"),
|
||||||
CLAY(CLAY_ID("LogHighlight"),
|
|
||||||
.layout = { .sizing = { .width = CLAY_SIZING_GROW(), .height = CLAY_SIZING_FIXED(1) } },
|
.layout = { .sizing = { .width = CLAY_SIZING_GROW(), .height = CLAY_SIZING_FIXED(1) } },
|
||||||
.backgroundColor = g_theme.bg_lighter
|
.backgroundColor = g_theme.bg_lighter
|
||||||
) {}
|
) {}
|
||||||
CLAY_TEXT(CLAY_STRING("Output / Log"), &g_text_config_normal);
|
CLAY_TEXT(CLAY_STRING("Item Editor"), &g_text_config_normal);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Sample Mapper tab — 88-key piano
|
||||||
|
CLAY(CLAY_ID("SampleMapperContent"),
|
||||||
|
.layout = {
|
||||||
|
.sizing = { .width = CLAY_SIZING_GROW(), .height = CLAY_SIZING_GROW() },
|
||||||
|
.padding = { uip(4), uip(4), uip(4), uip(4) },
|
||||||
|
.layoutDirection = CLAY_TOP_TO_BOTTOM,
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
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 piano_avail_h = uis(app->log_height) - TAB_HEIGHT - uip(8);
|
||||||
|
F32 black_key_h = piano_avail_h * PIANO_BLACK_H_PCT;
|
||||||
|
if (black_key_h < uis(20)) black_key_h = uis(20);
|
||||||
|
|
||||||
|
F32 white_key_w = ((F32)app->last_w - uip(8)) / 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 (int note = PIANO_FIRST_NOTE; note <= PIANO_LAST_NOTE; note++) {
|
||||||
|
if (piano_is_black_key(note)) continue;
|
||||||
|
|
||||||
|
B32 midi_held = midi_is_note_held(app->midi, note);
|
||||||
|
B32 mouse_held = app->piano_mouse_note == note;
|
||||||
|
Clay_Color bg;
|
||||||
|
if (midi_held) {
|
||||||
|
bg = velocity_color(midi_get_note_velocity(app->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 (int 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(app->midi, note);
|
||||||
|
B32 mouse_held = app->piano_mouse_note == note;
|
||||||
|
Clay_Color bg;
|
||||||
|
if (midi_held) {
|
||||||
|
bg = velocity_color(midi_get_note_velocity(app->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,
|
||||||
|
},
|
||||||
|
) {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1068,6 +1207,7 @@ static void do_frame(AppState *app) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ui_widgets_begin_frame(input);
|
ui_widgets_begin_frame(input);
|
||||||
|
update_piano_input(app);
|
||||||
update_panel_splitters(app);
|
update_panel_splitters(app);
|
||||||
|
|
||||||
// Build UI with Clay
|
// Build UI with Clay
|
||||||
@@ -1147,6 +1287,8 @@ int main(int argc, char **argv) {
|
|||||||
app.show_props = 1;
|
app.show_props = 1;
|
||||||
app.show_log = 1;
|
app.show_log = 1;
|
||||||
app.show_midi_devices = 1;
|
app.show_midi_devices = 1;
|
||||||
|
app.bottom_panel_tab = 0;
|
||||||
|
app.piano_mouse_note = -1;
|
||||||
app.demo_knob_unsigned = 75.0f;
|
app.demo_knob_unsigned = 75.0f;
|
||||||
app.demo_knob_signed = 0.0f;
|
app.demo_knob_signed = 0.0f;
|
||||||
app.demo_slider_h = 50.0f;
|
app.demo_slider_h = 50.0f;
|
||||||
|
|||||||
@@ -25,3 +25,8 @@ void midi_open_all_inputs(MidiEngine *engine);
|
|||||||
void midi_close_all_inputs(MidiEngine *engine);
|
void midi_close_all_inputs(MidiEngine *engine);
|
||||||
void midi_update(MidiEngine *engine, float dt);
|
void midi_update(MidiEngine *engine, float dt);
|
||||||
bool midi_is_input_active(MidiEngine *engine, int32_t device_index);
|
bool midi_is_input_active(MidiEngine *engine, int32_t device_index);
|
||||||
|
|
||||||
|
// Per-note state: returns true if note (0-127) is currently held on any input device
|
||||||
|
bool midi_is_note_held(MidiEngine *engine, int32_t note);
|
||||||
|
// Returns the last note-on velocity (0-127) for a given note, or 0 if not held
|
||||||
|
int32_t midi_get_note_velocity(MidiEngine *engine, int32_t note);
|
||||||
|
|||||||
@@ -23,6 +23,10 @@ struct MidiEngine {
|
|||||||
_Atomic int32_t pending_note_off[MIDI_MAX_DEVICES];
|
_Atomic int32_t pending_note_off[MIDI_MAX_DEVICES];
|
||||||
_Atomic int32_t held_note_count[MIDI_MAX_DEVICES];
|
_Atomic int32_t held_note_count[MIDI_MAX_DEVICES];
|
||||||
|
|
||||||
|
// Per-note state (across all devices), set atomically from callback
|
||||||
|
_Atomic int32_t note_states[128]; // held count
|
||||||
|
_Atomic int32_t note_velocities[128]; // last note-on velocity
|
||||||
|
|
||||||
// Main thread only
|
// Main thread only
|
||||||
int32_t display_velocity[MIDI_MAX_DEVICES];
|
int32_t display_velocity[MIDI_MAX_DEVICES];
|
||||||
int32_t display_note[MIDI_MAX_DEVICES];
|
int32_t display_note[MIDI_MAX_DEVICES];
|
||||||
@@ -59,17 +63,30 @@ static void midi_read_callback(const MIDIPacketList *pktlist, void *readProcRefC
|
|||||||
atomic_store(&engine->pending_note_on_vel[device_idx], (int32_t)velocity);
|
atomic_store(&engine->pending_note_on_vel[device_idx], (int32_t)velocity);
|
||||||
atomic_store(&engine->pending_note_num[device_idx], (int32_t)(note + 1));
|
atomic_store(&engine->pending_note_num[device_idx], (int32_t)(note + 1));
|
||||||
atomic_fetch_add(&engine->held_note_count[device_idx], 1);
|
atomic_fetch_add(&engine->held_note_count[device_idx], 1);
|
||||||
|
if (note < 128) {
|
||||||
|
atomic_fetch_add(&engine->note_states[note], 1);
|
||||||
|
atomic_store(&engine->note_velocities[note], (int32_t)velocity);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
// Note-on with velocity 0 = note-off
|
// Note-on with velocity 0 = note-off
|
||||||
atomic_store(&engine->pending_note_off[device_idx], 1);
|
atomic_store(&engine->pending_note_off[device_idx], 1);
|
||||||
int32_t count = atomic_fetch_sub(&engine->held_note_count[device_idx], 1);
|
int32_t count = atomic_fetch_sub(&engine->held_note_count[device_idx], 1);
|
||||||
if (count <= 1) atomic_store(&engine->held_note_count[device_idx], 0);
|
if (count <= 1) atomic_store(&engine->held_note_count[device_idx], 0);
|
||||||
|
if (note < 128) {
|
||||||
|
int32_t c = atomic_fetch_sub(&engine->note_states[note], 1);
|
||||||
|
if (c <= 1) atomic_store(&engine->note_states[note], 0);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else if (kind == 0x80 && j + 2 < packet->length) {
|
} else if (kind == 0x80 && j + 2 < packet->length) {
|
||||||
|
uint8_t note_off = packet->data[j + 1];
|
||||||
j += 3;
|
j += 3;
|
||||||
atomic_store(&engine->pending_note_off[device_idx], 1);
|
atomic_store(&engine->pending_note_off[device_idx], 1);
|
||||||
int32_t count = atomic_fetch_sub(&engine->held_note_count[device_idx], 1);
|
int32_t count = atomic_fetch_sub(&engine->held_note_count[device_idx], 1);
|
||||||
if (count <= 1) atomic_store(&engine->held_note_count[device_idx], 0);
|
if (count <= 1) atomic_store(&engine->held_note_count[device_idx], 0);
|
||||||
|
if (note_off < 128) {
|
||||||
|
int32_t c = atomic_fetch_sub(&engine->note_states[note_off], 1);
|
||||||
|
if (c <= 1) atomic_store(&engine->note_states[note_off], 0);
|
||||||
|
}
|
||||||
} else if (kind == 0xC0 || kind == 0xD0) {
|
} else if (kind == 0xC0 || kind == 0xD0) {
|
||||||
j += 2; // Program Change, Channel Pressure (2 bytes)
|
j += 2; // Program Change, Channel Pressure (2 bytes)
|
||||||
} else if (kind == 0xF0) {
|
} else if (kind == 0xF0) {
|
||||||
@@ -189,6 +206,10 @@ void midi_close_all_inputs(MidiEngine *engine) {
|
|||||||
engine->display_note[i] = 0;
|
engine->display_note[i] = 0;
|
||||||
engine->release_timers[i] = 0.0f;
|
engine->release_timers[i] = 0.0f;
|
||||||
}
|
}
|
||||||
|
for (int32_t i = 0; i < 128; i++) {
|
||||||
|
atomic_store(&engine->note_states[i], 0);
|
||||||
|
atomic_store(&engine->note_velocities[i], 0);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void midi_update(MidiEngine *engine, float dt) {
|
void midi_update(MidiEngine *engine, float dt) {
|
||||||
@@ -233,6 +254,17 @@ bool midi_is_input_active(MidiEngine *engine, int32_t device_index) {
|
|||||||
return engine->devices[device_index].active;
|
return engine->devices[device_index].active;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool midi_is_note_held(MidiEngine *engine, int32_t note) {
|
||||||
|
if (note < 0 || note > 127) return false;
|
||||||
|
return atomic_load(&engine->note_states[note]) > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int32_t midi_get_note_velocity(MidiEngine *engine, int32_t note) {
|
||||||
|
if (note < 0 || note > 127) return 0;
|
||||||
|
if (atomic_load(&engine->note_states[note]) <= 0) return 0;
|
||||||
|
return atomic_load(&engine->note_velocities[note]);
|
||||||
|
}
|
||||||
|
|
||||||
void midi_refresh_devices(MidiEngine *engine) {
|
void midi_refresh_devices(MidiEngine *engine) {
|
||||||
midi_close_all_inputs(engine);
|
midi_close_all_inputs(engine);
|
||||||
enumerate_midi_devices(engine);
|
enumerate_midi_devices(engine);
|
||||||
|
|||||||
@@ -18,6 +18,10 @@ struct MidiEngine {
|
|||||||
volatile LONG pending_note_off[MIDI_MAX_DEVICES]; // note-off received flag
|
volatile LONG pending_note_off[MIDI_MAX_DEVICES]; // note-off received flag
|
||||||
volatile LONG held_note_count[MIDI_MAX_DEVICES]; // number of notes currently held
|
volatile LONG held_note_count[MIDI_MAX_DEVICES]; // number of notes currently held
|
||||||
|
|
||||||
|
// Per-note state (across all devices), set atomically from callback
|
||||||
|
volatile LONG note_states[128]; // held count
|
||||||
|
volatile LONG note_velocities[128]; // last note-on velocity
|
||||||
|
|
||||||
// Main thread only
|
// Main thread only
|
||||||
int32_t display_velocity[MIDI_MAX_DEVICES];
|
int32_t display_velocity[MIDI_MAX_DEVICES];
|
||||||
int32_t display_note[MIDI_MAX_DEVICES];
|
int32_t display_note[MIDI_MAX_DEVICES];
|
||||||
@@ -50,12 +54,20 @@ static void CALLBACK midi_in_callback(HMIDIIN hMidiIn, UINT wMsg, DWORD_PTR dwIn
|
|||||||
InterlockedExchange(&g_midi_engine->pending_note_on_vel[idx], (LONG)velocity);
|
InterlockedExchange(&g_midi_engine->pending_note_on_vel[idx], (LONG)velocity);
|
||||||
InterlockedExchange(&g_midi_engine->pending_note_num[idx], (LONG)(note + 1)); // +1 so 0 means "no pending"
|
InterlockedExchange(&g_midi_engine->pending_note_num[idx], (LONG)(note + 1)); // +1 so 0 means "no pending"
|
||||||
InterlockedIncrement(&g_midi_engine->held_note_count[idx]);
|
InterlockedIncrement(&g_midi_engine->held_note_count[idx]);
|
||||||
|
if (note < 128) {
|
||||||
|
InterlockedIncrement(&g_midi_engine->note_states[note]);
|
||||||
|
InterlockedExchange(&g_midi_engine->note_velocities[note], (LONG)velocity);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// Note-off (0x80) or note-on with velocity 0 (running status note-off)
|
// Note-off (0x80) or note-on with velocity 0 (running status note-off)
|
||||||
else if (kind == 0x80 || (kind == 0x90 && velocity == 0)) {
|
else if (kind == 0x80 || (kind == 0x90 && velocity == 0)) {
|
||||||
InterlockedExchange(&g_midi_engine->pending_note_off[idx], 1);
|
InterlockedExchange(&g_midi_engine->pending_note_off[idx], 1);
|
||||||
LONG count = InterlockedDecrement(&g_midi_engine->held_note_count[idx]);
|
LONG count = InterlockedDecrement(&g_midi_engine->held_note_count[idx]);
|
||||||
if (count < 0) InterlockedExchange(&g_midi_engine->held_note_count[idx], 0);
|
if (count < 0) InterlockedExchange(&g_midi_engine->held_note_count[idx], 0);
|
||||||
|
if (note < 128) {
|
||||||
|
LONG c = InterlockedDecrement(&g_midi_engine->note_states[note]);
|
||||||
|
if (c < 0) InterlockedExchange(&g_midi_engine->note_states[note], 0);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -105,6 +117,10 @@ void midi_close_all_inputs(MidiEngine *engine) {
|
|||||||
engine->display_note[i] = 0;
|
engine->display_note[i] = 0;
|
||||||
engine->release_timers[i] = 0.0f;
|
engine->release_timers[i] = 0.0f;
|
||||||
}
|
}
|
||||||
|
for (int32_t i = 0; i < 128; i++) {
|
||||||
|
engine->note_states[i] = 0;
|
||||||
|
engine->note_velocities[i] = 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void midi_update(MidiEngine *engine, float dt) {
|
void midi_update(MidiEngine *engine, float dt) {
|
||||||
@@ -160,6 +176,17 @@ bool midi_is_input_active(MidiEngine *engine, int32_t device_index) {
|
|||||||
return engine->devices[device_index].active;
|
return engine->devices[device_index].active;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool midi_is_note_held(MidiEngine *engine, int32_t note) {
|
||||||
|
if (note < 0 || note > 127) return false;
|
||||||
|
return engine->note_states[note] > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int32_t midi_get_note_velocity(MidiEngine *engine, int32_t note) {
|
||||||
|
if (note < 0 || note > 127) return 0;
|
||||||
|
if (engine->note_states[note] <= 0) return 0;
|
||||||
|
return (int32_t)engine->note_velocities[note];
|
||||||
|
}
|
||||||
|
|
||||||
void midi_refresh_devices(MidiEngine *engine) {
|
void midi_refresh_devices(MidiEngine *engine) {
|
||||||
midi_close_all_inputs(engine);
|
midi_close_all_inputs(engine);
|
||||||
engine->device_count = 0;
|
engine->device_count = 0;
|
||||||
|
|||||||
Reference in New Issue
Block a user