Core additions since initial commit: - Platform-split MIDI (midi_linux.c / midi_windows.c) - Patch system: save/load/scan, subdirectory navigation, favourites - Config system: last MIDI device and CC mappings persisted to disk - Custom embedded pixel font and sprite sheet (no loose asset files at runtime) - Window icon embedded at runtime (SetWindowIcon) and in Windows .exe (windres) - chdirToExeDir() in platform.c so double-click launch finds patches/ correctly - About screen accessible from Master panel UI polish: - TITLE_BAR_H constant (20 px) — sprite buttons now fit inside title bars - All title bar text, button overlay text, and close/active labels vertically centred - Full-screen dim overlay computed from camera transform (no more partial coverage) - Patch browser: folder navigation, breadcrumb title, 256-slot limit lifted Build: - Makefile auto-discovers sources; embeds sprites/font/icon via xxd rules - Windows cross-compile: make PLATFORM=windows (mingw-w64 + windres) - windows.h isolated in platform.c to avoid Rectangle/CloseWindow conflicts with raylib.h - WIN_RES uses lazy = assignment so OBJ_DIR expands correctly for windres output path Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
109 lines
3.2 KiB
C
109 lines
3.2 KiB
C
#include "midi.h"
|
|
#include <windows.h>
|
|
#include <mmsystem.h>
|
|
#include <stdatomic.h>
|
|
#include <stdio.h>
|
|
|
|
extern atomic_int gRunning;
|
|
|
|
static void CALLBACK midiCallbackWin32(HMIDIIN hIn, UINT msg, DWORD_PTR instance,
|
|
DWORD_PTR param1, DWORD_PTR param2)
|
|
{
|
|
(void)hIn; (void)param2;
|
|
if (msg != MIM_DATA) return;
|
|
|
|
MidiState *m = (MidiState *)instance;
|
|
BYTE status = (BYTE)( param1 & 0xFF);
|
|
BYTE data1 = (BYTE)((param1 >> 8) & 0xFF);
|
|
BYTE data2 = (BYTE)((param1 >> 16) & 0xFF);
|
|
BYTE type = status & 0xF0;
|
|
|
|
if (type == 0x90 && data2 > 0) {
|
|
synthNoteOn(m->synth, data1);
|
|
} else if (type == 0x80 || (type == 0x90 && data2 == 0)) {
|
|
synthNoteOff(m->synth, data1);
|
|
} else if (type == 0xB0) {
|
|
int cc = data1, val = data2;
|
|
if (cc < 128) {
|
|
if (atomic_load(&m->midiLearnMode))
|
|
atomic_store(&m->midiLearnCC, cc);
|
|
if (m->ccMappings[cc].active) {
|
|
float t = (float)val / 127.0f;
|
|
*m->ccMappings[cc].valuePtr =
|
|
m->ccMappings[cc].min + t * (m->ccMappings[cc].max - m->ccMappings[cc].min);
|
|
}
|
|
}
|
|
} else if (type == 0xE0) {
|
|
int pb = (data2 << 7) | data1; // 0..16383, centre = 8192
|
|
m->synth->pitchBend = (pb - 8192) / 8191.0f;
|
|
}
|
|
}
|
|
|
|
int midiInit(MidiState *m, Synth *synth)
|
|
{
|
|
m->synth = synth;
|
|
m->hMidiIn = NULL;
|
|
m->connectedClient = -1;
|
|
m->connectedPort = -1;
|
|
m->inputCount = 0;
|
|
atomic_store(&m->midiLearnMode, 0);
|
|
atomic_store(&m->midiLearnCC, -1);
|
|
for (int i = 0; i < 128; i++) m->ccMappings[i].active = 0;
|
|
return 1;
|
|
}
|
|
|
|
void midiScanInputs(MidiState *m)
|
|
{
|
|
m->inputCount = 0;
|
|
UINT n = midiInGetNumDevs();
|
|
for (UINT i = 0; i < n && m->inputCount < MIDI_MAX_INPUTS; i++) {
|
|
MIDIINCAPSA caps;
|
|
if (midiInGetDevCapsA(i, &caps, sizeof(caps)) == MMSYSERR_NOERROR) {
|
|
MidiInputInfo *info = &m->inputs[m->inputCount++];
|
|
info->client = (int)i;
|
|
info->port = 0;
|
|
snprintf(info->clientName, sizeof(info->clientName), "%s", caps.szPname);
|
|
snprintf(info->portName, sizeof(info->portName), "%s", caps.szPname);
|
|
}
|
|
}
|
|
}
|
|
|
|
void midiDevDisconnect(MidiState *m)
|
|
{
|
|
if (m->hMidiIn) {
|
|
midiInStop((HMIDIIN)m->hMidiIn);
|
|
midiInClose((HMIDIIN)m->hMidiIn);
|
|
m->hMidiIn = NULL;
|
|
}
|
|
m->connectedClient = -1;
|
|
m->connectedPort = -1;
|
|
}
|
|
|
|
void midiDevConnect(MidiState *m, int srcClient, int srcPort)
|
|
{
|
|
(void)srcPort;
|
|
midiDevDisconnect(m);
|
|
HMIDIIN hIn = NULL;
|
|
if (midiInOpen(&hIn, (UINT)srcClient, (DWORD_PTR)midiCallbackWin32,
|
|
(DWORD_PTR)m, CALLBACK_FUNCTION) == MMSYSERR_NOERROR) {
|
|
midiInStart(hIn);
|
|
m->hMidiIn = hIn;
|
|
m->connectedClient = srcClient;
|
|
m->connectedPort = 0;
|
|
}
|
|
}
|
|
|
|
void midiClose(MidiState *m)
|
|
{
|
|
midiDevDisconnect(m);
|
|
}
|
|
|
|
void *midiThread(void *arg)
|
|
{
|
|
// WinMM delivers MIDI events via midiCallbackWin32 on its own MM thread.
|
|
// This function exists only to satisfy the pthread_create call in soundThing.c.
|
|
(void)arg;
|
|
while (gRunning) Sleep(50);
|
|
return NULL;
|
|
}
|