#include "midi.h" #include #include #include #include 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; }