v0.9.5 beta — full feature build ready for distribution
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>
This commit is contained in:
157
source/midi_linux.c
Normal file
157
source/midi_linux.c
Normal file
@@ -0,0 +1,157 @@
|
||||
#include "midi.h"
|
||||
#include <stdio.h>
|
||||
#include <poll.h>
|
||||
#include <stdatomic.h>
|
||||
#include <alloca.h>
|
||||
|
||||
extern atomic_int gRunning;
|
||||
|
||||
int midiInit(MidiState *m, Synth *synth)
|
||||
{
|
||||
if (snd_seq_open(&m->seq, "default", SND_SEQ_OPEN_INPUT | SND_SEQ_NONBLOCK, 0) < 0) {
|
||||
fprintf(stderr, "Failed to open ALSA sequencer\n");
|
||||
return 0;
|
||||
}
|
||||
snd_seq_nonblock(m->seq, 1);
|
||||
snd_seq_set_client_name(m->seq, "soundThing");
|
||||
m->client = snd_seq_client_id(m->seq);
|
||||
m->port = snd_seq_create_simple_port(m->seq, "MIDI In",
|
||||
SND_SEQ_PORT_CAP_WRITE | SND_SEQ_PORT_CAP_SUBS_WRITE,
|
||||
SND_SEQ_PORT_TYPE_APPLICATION);
|
||||
m->synth = synth;
|
||||
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;
|
||||
snd_seq_client_info_t *cinfo;
|
||||
snd_seq_port_info_t *pinfo;
|
||||
snd_seq_client_info_alloca(&cinfo);
|
||||
snd_seq_port_info_alloca(&pinfo);
|
||||
|
||||
snd_seq_client_info_set_client(cinfo, -1);
|
||||
while (snd_seq_query_next_client(m->seq, cinfo) >= 0) {
|
||||
int client = snd_seq_client_info_get_client(cinfo);
|
||||
if (client == m->client) continue;
|
||||
|
||||
snd_seq_port_info_set_client(pinfo, client);
|
||||
snd_seq_port_info_set_port(pinfo, -1);
|
||||
while (snd_seq_query_next_port(m->seq, pinfo) >= 0) {
|
||||
if (m->inputCount >= MIDI_MAX_INPUTS) break;
|
||||
unsigned int caps = snd_seq_port_info_get_capability(pinfo);
|
||||
if ((caps & SND_SEQ_PORT_CAP_READ) && (caps & SND_SEQ_PORT_CAP_SUBS_READ)) {
|
||||
MidiInputInfo *info = &m->inputs[m->inputCount++];
|
||||
info->client = client;
|
||||
info->port = snd_seq_port_info_get_port(pinfo);
|
||||
snprintf(info->clientName, sizeof(info->clientName), "%s",
|
||||
snd_seq_client_info_get_name(cinfo));
|
||||
snprintf(info->portName, sizeof(info->portName), "%s",
|
||||
snd_seq_port_info_get_name(pinfo));
|
||||
}
|
||||
}
|
||||
if (m->inputCount >= MIDI_MAX_INPUTS) break;
|
||||
}
|
||||
}
|
||||
|
||||
void midiDevDisconnect(MidiState *m)
|
||||
{
|
||||
if (m->connectedClient < 0) return;
|
||||
|
||||
snd_seq_port_subscribe_t *sub;
|
||||
snd_seq_addr_t sender, dest;
|
||||
|
||||
sender.client = m->connectedClient;
|
||||
sender.port = m->connectedPort;
|
||||
dest.client = m->client;
|
||||
dest.port = m->port;
|
||||
|
||||
snd_seq_port_subscribe_alloca(&sub);
|
||||
snd_seq_port_subscribe_set_sender(sub, &sender);
|
||||
snd_seq_port_subscribe_set_dest(sub, &dest);
|
||||
snd_seq_unsubscribe_port(m->seq, sub);
|
||||
|
||||
m->connectedClient = -1;
|
||||
m->connectedPort = -1;
|
||||
}
|
||||
|
||||
void midiDevConnect(MidiState *m, int srcClient, int srcPort)
|
||||
{
|
||||
midiDevDisconnect(m);
|
||||
|
||||
snd_seq_port_subscribe_t *sub;
|
||||
snd_seq_addr_t sender, dest;
|
||||
|
||||
sender.client = srcClient;
|
||||
sender.port = srcPort;
|
||||
dest.client = m->client;
|
||||
dest.port = m->port;
|
||||
|
||||
snd_seq_port_subscribe_alloca(&sub);
|
||||
snd_seq_port_subscribe_set_sender(sub, &sender);
|
||||
snd_seq_port_subscribe_set_dest(sub, &dest);
|
||||
snd_seq_subscribe_port(m->seq, sub);
|
||||
|
||||
m->connectedClient = srcClient;
|
||||
m->connectedPort = srcPort;
|
||||
}
|
||||
|
||||
void midiClose(MidiState *m)
|
||||
{
|
||||
midiDevDisconnect(m);
|
||||
snd_seq_close(m->seq);
|
||||
}
|
||||
|
||||
void *midiThread(void *arg)
|
||||
{
|
||||
MidiState *m = (MidiState *)arg;
|
||||
struct pollfd pfd;
|
||||
snd_seq_poll_descriptors(m->seq, &pfd, 1, POLLIN);
|
||||
|
||||
while (gRunning) {
|
||||
int ready = poll(&pfd, 1, 50);
|
||||
if (ready <= 0) continue;
|
||||
|
||||
snd_seq_event_t *ev = NULL;
|
||||
while (snd_seq_event_input(m->seq, &ev) >= 0 && ev) {
|
||||
switch (ev->type) {
|
||||
case SND_SEQ_EVENT_NOTEON:
|
||||
if (ev->data.note.velocity == 0)
|
||||
synthNoteOff(m->synth, ev->data.note.note);
|
||||
else
|
||||
synthNoteOn(m->synth, ev->data.note.note);
|
||||
break;
|
||||
case SND_SEQ_EVENT_NOTEOFF:
|
||||
synthNoteOff(m->synth, ev->data.note.note);
|
||||
break;
|
||||
case SND_SEQ_EVENT_CONTROLLER: {
|
||||
int cc = ev->data.control.param;
|
||||
int val = ev->data.control.value;
|
||||
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);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case SND_SEQ_EVENT_PITCHBEND:
|
||||
m->synth->pitchBend = ev->data.control.value / 8191.0f;
|
||||
break;
|
||||
}
|
||||
snd_seq_free_event(ev);
|
||||
ev = NULL;
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
Reference in New Issue
Block a user