Add sick midi input visualizations
This commit is contained in:
135
src/main.cpp
135
src/main.cpp
@@ -267,31 +267,147 @@ static void build_midi_panel(B32 show, MidiEngine *midi) {
|
|||||||
.layout = {
|
.layout = {
|
||||||
.sizing = { .width = CLAY_SIZING_GROW(), .height = CLAY_SIZING_GROW() },
|
.sizing = { .width = CLAY_SIZING_GROW(), .height = CLAY_SIZING_GROW() },
|
||||||
.padding = { 8, 8, 6, 6 },
|
.padding = { 8, 8, 6, 6 },
|
||||||
.childGap = 4,
|
.childGap = 6,
|
||||||
.layoutDirection = CLAY_TOP_TO_BOTTOM,
|
.layoutDirection = CLAY_TOP_TO_BOTTOM,
|
||||||
}
|
}
|
||||||
) {
|
) {
|
||||||
// Refresh button
|
// Refresh button
|
||||||
CLAY(CLAY_ID("MidiRefreshBtn"),
|
Clay_ElementId refresh_eid = CLAY_ID("MidiRefreshBtn");
|
||||||
|
B32 refresh_hovered = Clay_PointerOver(refresh_eid);
|
||||||
|
CLAY(refresh_eid,
|
||||||
.layout = {
|
.layout = {
|
||||||
.sizing = { .width = CLAY_SIZING_FIT(), .height = CLAY_SIZING_FIXED(28) },
|
.sizing = { .width = CLAY_SIZING_FIT(), .height = CLAY_SIZING_FIXED(28) },
|
||||||
.padding = { 12, 12, 0, 0 },
|
.padding = { 12, 12, 0, 0 },
|
||||||
.childAlignment = { .y = CLAY_ALIGN_Y_CENTER },
|
.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)
|
.cornerRadius = CLAY_CORNER_RADIUS(3)
|
||||||
) {
|
) {
|
||||||
CLAY_TEXT(CLAY_STRING("Refresh"), &g_text_config_normal);
|
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];
|
static char device_bufs[64][128];
|
||||||
int32_t device_count = midi_get_device_count(midi);
|
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++) {
|
for (int32_t i = 0; i < device_count && i < 64; i++) {
|
||||||
MidiDeviceInfo *dev = midi_get_device(midi, 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_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 = {
|
.layout = {
|
||||||
.sizing = { .width = CLAY_SIZING_GROW(), .height = CLAY_SIZING_FIT() },
|
.sizing = { .width = CLAY_SIZING_GROW(), .height = CLAY_SIZING_FIT() },
|
||||||
.padding = { 4, 4, 2, 2 },
|
.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);
|
CLAY_TEXT(device_str, &g_text_config_normal);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (device_count == 0) {
|
if (!has_outputs) {
|
||||||
CLAY_TEXT(CLAY_STRING("No MIDI devices found"), &g_text_config_dim);
|
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))
|
if (!renderer_begin_frame(app->renderer))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
// Update MIDI activity timers
|
||||||
|
midi_update(app->midi, dt);
|
||||||
|
|
||||||
// Gather input
|
// Gather input
|
||||||
PlatformInput input = platform_get_input(app->window);
|
PlatformInput input = platform_get_input(app->window);
|
||||||
ui_widgets_begin_frame(input);
|
ui_widgets_begin_frame(input);
|
||||||
|
|||||||
@@ -9,6 +9,10 @@ struct MidiDeviceInfo {
|
|||||||
char name[64];
|
char name[64];
|
||||||
int32_t id;
|
int32_t id;
|
||||||
bool is_input;
|
bool is_input;
|
||||||
|
bool active; // true when note(s) currently held
|
||||||
|
bool releasing; // true during release flash
|
||||||
|
int32_t velocity; // last note-on velocity (0-127)
|
||||||
|
int32_t note; // last MIDI note number (0-127)
|
||||||
};
|
};
|
||||||
|
|
||||||
MidiEngine *midi_create();
|
MidiEngine *midi_create();
|
||||||
@@ -16,3 +20,8 @@ void midi_destroy(MidiEngine *engine);
|
|||||||
void midi_refresh_devices(MidiEngine *engine);
|
void midi_refresh_devices(MidiEngine *engine);
|
||||||
int32_t midi_get_device_count(MidiEngine *engine);
|
int32_t midi_get_device_count(MidiEngine *engine);
|
||||||
MidiDeviceInfo *midi_get_device(MidiEngine *engine, int32_t index);
|
MidiDeviceInfo *midi_get_device(MidiEngine *engine, int32_t index);
|
||||||
|
|
||||||
|
void midi_open_all_inputs(MidiEngine *engine);
|
||||||
|
void midi_close_all_inputs(MidiEngine *engine);
|
||||||
|
void midi_update(MidiEngine *engine, float dt);
|
||||||
|
bool midi_is_input_active(MidiEngine *engine, int32_t device_index);
|
||||||
|
|||||||
@@ -4,24 +4,164 @@
|
|||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
|
||||||
#define MIDI_MAX_DEVICES 64
|
#define MIDI_MAX_DEVICES 64
|
||||||
|
#define MIDI_RELEASE_FLASH_DURATION 0.15f
|
||||||
|
|
||||||
struct MidiEngine {
|
struct MidiEngine {
|
||||||
MidiDeviceInfo devices[MIDI_MAX_DEVICES];
|
MidiDeviceInfo devices[MIDI_MAX_DEVICES];
|
||||||
int32_t device_count;
|
int32_t device_count;
|
||||||
|
|
||||||
|
HMIDIIN input_handles[MIDI_MAX_DEVICES];
|
||||||
|
|
||||||
|
// Set atomically from callback thread
|
||||||
|
volatile LONG pending_note_on_vel[MIDI_MAX_DEVICES]; // last note-on velocity (0 = consumed)
|
||||||
|
volatile LONG pending_note_num[MIDI_MAX_DEVICES]; // last note-on note number + 1 (0 = consumed)
|
||||||
|
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
|
||||||
|
|
||||||
|
// Main thread only
|
||||||
|
int32_t display_velocity[MIDI_MAX_DEVICES];
|
||||||
|
int32_t display_note[MIDI_MAX_DEVICES];
|
||||||
|
float release_timers[MIDI_MAX_DEVICES];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
////////////////////////////////
|
||||||
|
// MIDI input callback — called from Win32 MIDI driver thread
|
||||||
|
|
||||||
|
static MidiEngine *g_midi_engine = nullptr;
|
||||||
|
|
||||||
|
static void CALLBACK midi_in_callback(HMIDIIN hMidiIn, UINT wMsg, DWORD_PTR dwInstance,
|
||||||
|
DWORD_PTR dwParam1, DWORD_PTR dwParam2) {
|
||||||
|
(void)hMidiIn;
|
||||||
|
(void)dwParam2;
|
||||||
|
|
||||||
|
if (wMsg != MIM_DATA) return;
|
||||||
|
if (!g_midi_engine) return;
|
||||||
|
|
||||||
|
int32_t idx = (int32_t)dwInstance;
|
||||||
|
if (idx < 0 || idx >= MIDI_MAX_DEVICES) return;
|
||||||
|
|
||||||
|
BYTE status = (BYTE)(dwParam1 & 0xFF);
|
||||||
|
BYTE note = (BYTE)((dwParam1 >> 8) & 0xFF);
|
||||||
|
BYTE velocity = (BYTE)((dwParam1 >> 16) & 0xFF);
|
||||||
|
BYTE kind = status & 0xF0;
|
||||||
|
|
||||||
|
// Note-on with velocity > 0
|
||||||
|
if (kind == 0x90 && velocity > 0) {
|
||||||
|
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"
|
||||||
|
InterlockedIncrement(&g_midi_engine->held_note_count[idx]);
|
||||||
|
}
|
||||||
|
// Note-off (0x80) or note-on with velocity 0 (running status note-off)
|
||||||
|
else if (kind == 0x80 || (kind == 0x90 && velocity == 0)) {
|
||||||
|
InterlockedExchange(&g_midi_engine->pending_note_off[idx], 1);
|
||||||
|
LONG count = InterlockedDecrement(&g_midi_engine->held_note_count[idx]);
|
||||||
|
if (count < 0) InterlockedExchange(&g_midi_engine->held_note_count[idx], 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
MidiEngine *midi_create() {
|
MidiEngine *midi_create() {
|
||||||
MidiEngine *engine = new MidiEngine();
|
MidiEngine *engine = new MidiEngine();
|
||||||
engine->device_count = 0;
|
memset(engine, 0, sizeof(*engine));
|
||||||
|
g_midi_engine = engine;
|
||||||
midi_refresh_devices(engine);
|
midi_refresh_devices(engine);
|
||||||
return engine;
|
return engine;
|
||||||
}
|
}
|
||||||
|
|
||||||
void midi_destroy(MidiEngine *engine) {
|
void midi_destroy(MidiEngine *engine) {
|
||||||
|
midi_close_all_inputs(engine);
|
||||||
|
if (g_midi_engine == engine) g_midi_engine = nullptr;
|
||||||
delete engine;
|
delete engine;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void midi_open_all_inputs(MidiEngine *engine) {
|
||||||
|
for (int32_t i = 0; i < engine->device_count; i++) {
|
||||||
|
MidiDeviceInfo *dev = &engine->devices[i];
|
||||||
|
if (!dev->is_input) continue;
|
||||||
|
if (engine->input_handles[i]) continue; // already open
|
||||||
|
|
||||||
|
HMIDIIN handle = nullptr;
|
||||||
|
MMRESULT res = midiInOpen(&handle, (UINT)dev->id,
|
||||||
|
(DWORD_PTR)midi_in_callback,
|
||||||
|
(DWORD_PTR)i, CALLBACK_FUNCTION);
|
||||||
|
if (res == MMSYSERR_NOERROR) {
|
||||||
|
engine->input_handles[i] = handle;
|
||||||
|
midiInStart(handle);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void midi_close_all_inputs(MidiEngine *engine) {
|
||||||
|
for (int32_t i = 0; i < MIDI_MAX_DEVICES; i++) {
|
||||||
|
if (engine->input_handles[i]) {
|
||||||
|
midiInStop(engine->input_handles[i]);
|
||||||
|
midiInClose(engine->input_handles[i]);
|
||||||
|
engine->input_handles[i] = nullptr;
|
||||||
|
}
|
||||||
|
engine->pending_note_on_vel[i] = 0;
|
||||||
|
engine->pending_note_num[i] = 0;
|
||||||
|
engine->pending_note_off[i] = 0;
|
||||||
|
engine->held_note_count[i] = 0;
|
||||||
|
engine->display_velocity[i] = 0;
|
||||||
|
engine->display_note[i] = 0;
|
||||||
|
engine->release_timers[i] = 0.0f;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void midi_update(MidiEngine *engine, float dt) {
|
||||||
|
for (int32_t i = 0; i < engine->device_count; i++) {
|
||||||
|
if (!engine->devices[i].is_input) continue;
|
||||||
|
|
||||||
|
// Consume pending note-on velocity and note number
|
||||||
|
LONG vel = InterlockedExchange(&engine->pending_note_on_vel[i], 0);
|
||||||
|
LONG note_p1 = InterlockedExchange(&engine->pending_note_num[i], 0);
|
||||||
|
if (vel > 0) {
|
||||||
|
engine->display_velocity[i] = (int32_t)vel;
|
||||||
|
}
|
||||||
|
if (note_p1 > 0) {
|
||||||
|
engine->display_note[i] = (int32_t)(note_p1 - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Consume pending note-off
|
||||||
|
LONG off = InterlockedExchange(&engine->pending_note_off[i], 0);
|
||||||
|
|
||||||
|
// Read held note count
|
||||||
|
LONG held = engine->held_note_count[i];
|
||||||
|
|
||||||
|
if (held > 0) {
|
||||||
|
engine->devices[i].active = true;
|
||||||
|
engine->devices[i].releasing = false;
|
||||||
|
engine->release_timers[i] = 0.0f;
|
||||||
|
} else if (off || (engine->devices[i].active && held <= 0)) {
|
||||||
|
// All notes just released — start release flash
|
||||||
|
engine->devices[i].active = false;
|
||||||
|
engine->devices[i].releasing = true;
|
||||||
|
engine->release_timers[i] = MIDI_RELEASE_FLASH_DURATION;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decay release flash timer
|
||||||
|
if (engine->release_timers[i] > 0.0f) {
|
||||||
|
engine->release_timers[i] -= dt;
|
||||||
|
if (engine->release_timers[i] <= 0.0f) {
|
||||||
|
engine->release_timers[i] = 0.0f;
|
||||||
|
engine->devices[i].releasing = false;
|
||||||
|
engine->display_velocity[i] = 0;
|
||||||
|
engine->display_note[i] = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
engine->devices[i].velocity = engine->display_velocity[i];
|
||||||
|
engine->devices[i].note = engine->display_note[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool midi_is_input_active(MidiEngine *engine, int32_t device_index) {
|
||||||
|
if (device_index < 0 || device_index >= engine->device_count)
|
||||||
|
return false;
|
||||||
|
return engine->devices[device_index].active;
|
||||||
|
}
|
||||||
|
|
||||||
void midi_refresh_devices(MidiEngine *engine) {
|
void midi_refresh_devices(MidiEngine *engine) {
|
||||||
|
midi_close_all_inputs(engine);
|
||||||
engine->device_count = 0;
|
engine->device_count = 0;
|
||||||
|
|
||||||
UINT num_in = midiInGetNumDevs();
|
UINT num_in = midiInGetNumDevs();
|
||||||
@@ -32,6 +172,7 @@ void midi_refresh_devices(MidiEngine *engine) {
|
|||||||
strncpy_s(dev->name, sizeof(dev->name), caps.szPname, _TRUNCATE);
|
strncpy_s(dev->name, sizeof(dev->name), caps.szPname, _TRUNCATE);
|
||||||
dev->id = (int32_t)i;
|
dev->id = (int32_t)i;
|
||||||
dev->is_input = true;
|
dev->is_input = true;
|
||||||
|
dev->active = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -43,8 +184,11 @@ void midi_refresh_devices(MidiEngine *engine) {
|
|||||||
strncpy_s(dev->name, sizeof(dev->name), caps.szPname, _TRUNCATE);
|
strncpy_s(dev->name, sizeof(dev->name), caps.szPname, _TRUNCATE);
|
||||||
dev->id = (int32_t)i;
|
dev->id = (int32_t)i;
|
||||||
dev->is_input = false;
|
dev->is_input = false;
|
||||||
|
dev->active = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
midi_open_all_inputs(engine);
|
||||||
}
|
}
|
||||||
|
|
||||||
int32_t midi_get_device_count(MidiEngine *engine) {
|
int32_t midi_get_device_count(MidiEngine *engine) {
|
||||||
|
|||||||
Reference in New Issue
Block a user