blob: 9dd67f1d7dbe3fc5307ba3ecd321b5f1e407053b [file] [log] [blame]
/*
* BRLTTY - A background process providing access to the console screen (when in
* text mode) for a blind person using a refreshable braille display.
*
* Copyright (C) 1995-2023 by The BRLTTY Developers.
*
* BRLTTY comes with ABSOLUTELY NO WARRANTY.
*
* This is free software, placed under the terms of the
* GNU Lesser General Public License, as published by the Free Software
* Foundation; either version 2.1 of the License, or (at your option) any
* later version. Please see the file LICENSE-LGPL for details.
*
* Web Page: http://brltty.app/
*
* This software is maintained by Dave Mielke <dave@mielke.cc>.
*/
#include "prologue.h"
#include <alsa/asoundlib.h>
#include "log.h"
#include "parse.h"
#include "timing.h"
#include "midi.h"
struct MidiDeviceStruct {
snd_seq_t *sequencer;
int port;
int queue;
snd_seq_queue_status_t *status;
snd_seq_real_time_t time;
unsigned char note;
};
#define snd_seq_control_queue snd_seq_control_queue
static int
findMidiDevice (MidiDevice *midi, int errorLevel, int *client, int *port) {
snd_seq_client_info_t *clientInformation = malloc(snd_seq_client_info_sizeof());
if (clientInformation) {
memset(clientInformation, 0, snd_seq_client_info_sizeof());
snd_seq_client_info_set_client(clientInformation, -1);
while (snd_seq_query_next_client(midi->sequencer, clientInformation) >= 0) {
int clientIdentifier = snd_seq_client_info_get_client(clientInformation);
snd_seq_port_info_t *portInformation = malloc(snd_seq_port_info_sizeof());
if (portInformation) {
memset(portInformation, 0, snd_seq_port_info_sizeof());
snd_seq_port_info_set_client(portInformation, clientIdentifier);
snd_seq_port_info_set_port(portInformation, -1);
while (snd_seq_query_next_port(midi->sequencer, portInformation) >= 0) {
int portIdentifier = snd_seq_port_info_get_port(portInformation);
int actualCapabilities = snd_seq_port_info_get_capability(portInformation);
const int neededCapabilties = SND_SEQ_PORT_CAP_WRITE | SND_SEQ_PORT_CAP_SUBS_WRITE;
if (((actualCapabilities & neededCapabilties) == neededCapabilties) &&
!(actualCapabilities & SND_SEQ_PORT_CAP_NO_EXPORT)) {
*client = clientIdentifier;
*port = portIdentifier;
logMessage(LOG_DEBUG, "Using ALSA MIDI device: %d[%s] %d[%s]",
clientIdentifier, snd_seq_client_info_get_name(clientInformation),
portIdentifier, snd_seq_port_info_get_name(portInformation));
free(portInformation);
free(clientInformation);
return 1;
}
}
free(portInformation);
} else {
logMallocError();
}
}
free(clientInformation);
} else {
logMallocError();
}
logMessage(errorLevel, "No MDII devices.");
return 0;
}
static int
parseMidiDevice (MidiDevice *midi, int errorLevel, const char *device, int *client, int *port) {
char **components = splitString(device, ':', NULL);
if (components) {
char **component = components;
if (*component && **component) {
const char *clientSpecifier = *component++;
if (*component && **component) {
const char *portSpecifier = *component++;
if (!*component) {
int clientIdentifier;
int clientOk = 0;
if (isInteger(&clientIdentifier, clientSpecifier)) {
if ((clientIdentifier >= 0) && (clientIdentifier <= 0XFFFF)) clientOk = 1;
} else {
snd_seq_client_info_t *info = malloc(snd_seq_client_info_sizeof());
if (info) {
memset(info, 0, snd_seq_client_info_sizeof());
snd_seq_client_info_set_client(info, -1);
while (snd_seq_query_next_client(midi->sequencer, info) >= 0) {
const char *name = snd_seq_client_info_get_name(info);
if (strstr(name, clientSpecifier)) {
clientIdentifier = snd_seq_client_info_get_client(info);
clientOk = 1;
logMessage(LOG_INFO, "Using ALSA MIDI client: %d[%s]",
clientIdentifier, name);
break;
}
}
free(info);
} else {
logMallocError();
}
}
if (clientOk) {
int portIdentifier;
int portOk = 0;
if (isInteger(&portIdentifier, portSpecifier)) {
if ((portIdentifier >= 0) && (portIdentifier <= 0XFFFF)) portOk = 1;
} else {
snd_seq_port_info_t *info = malloc(snd_seq_port_info_sizeof());
if (info) {
memset(info, 0, snd_seq_port_info_sizeof());
snd_seq_port_info_set_client(info, clientIdentifier);
snd_seq_port_info_set_port(info, -1);
while (snd_seq_query_next_port(midi->sequencer, info) >= 0) {
const char *name = snd_seq_port_info_get_name(info);
if (strstr(name, portSpecifier)) {
portIdentifier = snd_seq_port_info_get_port(info);
portOk = 1;
logMessage(LOG_INFO, "Using ALSA MIDI port: %d[%s]",
portIdentifier, name);
break;
}
}
free(info);
} else {
logMallocError();
}
}
if (portOk) {
*client = clientIdentifier;
*port = portIdentifier;
deallocateStrings(components);
return 1;
} else {
logMessage(errorLevel, "Invalid ALSA MIDI port: %s", device);
}
} else {
logMessage(errorLevel, "Invalid ALSA MIDI client: %s", device);
}
} else {
logMessage(errorLevel, "Too many ALSA MIDI device components: %s", device);
}
} else {
logMessage(errorLevel, "Missing ALSA MIDI port specifier: %s", device);
}
} else {
logMessage(errorLevel, "Missing ALSA MIDI client specifier: %s", device);
}
deallocateStrings(components);
} else {
logMallocError();
}
return 0;
}
static void
updateMidiStatus (MidiDevice *midi) {
snd_seq_get_queue_status(midi->sequencer, midi->queue, midi->status);
}
static void
startMidiTimer (MidiDevice *midi) {
if (!midi->time.tv_sec && !midi->time.tv_nsec) {
updateMidiStatus(midi);
midi->time = *snd_seq_queue_status_get_real_time(midi->status);
}
}
static void
stopMidiTimer (MidiDevice *midi) {
midi->time.tv_sec = 0;
midi->time.tv_nsec = 0;
}
MidiDevice *
openMidiDevice (int errorLevel, const char *device) {
MidiDevice *midi;
if ((midi = malloc(sizeof(*midi)))) {
const char *sequencerName = "default";
int result;
if ((result = snd_seq_open(&midi->sequencer, sequencerName, SND_SEQ_OPEN_OUTPUT, 0)) >= 0) {
snd_seq_set_client_name(midi->sequencer, PACKAGE_NAME);
if ((midi->port = snd_seq_create_simple_port(midi->sequencer, "out0",
SND_SEQ_PORT_CAP_READ|SND_SEQ_PORT_CAP_SUBS_READ,
SND_SEQ_PORT_TYPE_APPLICATION)) >= 0) {
if ((midi->queue = snd_seq_alloc_queue(midi->sequencer)) >= 0) {
if ((result = snd_seq_queue_status_malloc(&midi->status)) >= 0) {
int client = 0;
int port = 0;
int deviceOk;
if (*device) {
deviceOk = parseMidiDevice(midi, errorLevel, device, &client, &port);
} else {
deviceOk = findMidiDevice(midi, errorLevel, &client, &port);
}
if (deviceOk) {
logMessage(LOG_DEBUG, "Connecting to ALSA MIDI device: %d:%d", client, port);
if ((result = snd_seq_connect_to(midi->sequencer, midi->port, client, port)) >= 0) {
if ((result = snd_seq_start_queue(midi->sequencer, midi->queue, NULL)) >= 0) {
stopMidiTimer(midi);
return midi;
} else {
logMessage(errorLevel, "Cannot start ALSA MIDI queue: %d:%d: %s",
client, port, snd_strerror(result));
}
} else {
logMessage(errorLevel, "Cannot connect to ALSA MIDI device: %d:%d: %s",
client, port, snd_strerror(result));
}
}
snd_seq_queue_status_free(midi->status);
} else {
logMessage(errorLevel, "Cannot allocate ALSA MIDI queue status container: %s",
snd_strerror(result));
}
} else {
logMessage(errorLevel, "Cannot allocate ALSA MIDI queue: %s",
snd_strerror(result));
}
} else {
logMessage(errorLevel, "Cannot create ALSA MIDI output port: %s",
snd_strerror(midi->port));
}
snd_seq_close(midi->sequencer);
} else {
logMessage(errorLevel, "Cannot open ALSA sequencer: %s: %s",
sequencerName, snd_strerror(result));
}
free(midi);
} else {
logSystemError("MIDI device allocation");
}
return NULL;
}
void
closeMidiDevice (MidiDevice *midi) {
snd_seq_queue_status_free(midi->status);
snd_seq_close(midi->sequencer);
free(midi);
}
int
flushMidiDevice (MidiDevice *midi) {
while (1) {
int duration;
updateMidiStatus(midi);
{
const snd_seq_real_time_t *time = snd_seq_queue_status_get_real_time(midi->status);
int seconds = midi->time.tv_sec - time->tv_sec;
int nanoseconds = midi->time.tv_nsec - time->tv_nsec;
duration = (seconds * 1000) + (nanoseconds / 1000000);
}
if (duration <= 0) break;
approximateDelay(duration);
}
stopMidiTimer(midi);
return 1;
}
static void
prepareMidiEvent (MidiDevice *midi, snd_seq_event_t *event) {
snd_seq_ev_clear(event);
snd_seq_ev_set_source(event, midi->port);
snd_seq_ev_set_subs(event);
}
static void
scheduleMidiEvent (MidiDevice *midi, snd_seq_event_t *event) {
snd_seq_ev_schedule_real(event, midi->queue, 1, &midi->time);
}
static int
sendMidiEvent (MidiDevice *midi, snd_seq_event_t *event) {
int result;
if ((result = snd_seq_event_output(midi->sequencer, event)) >= 0) {
snd_seq_drain_output(midi->sequencer);
return 1;
} else {
logMessage(LOG_ERR, "ALSA MIDI write error: %s", snd_strerror(result));
}
return 0;
}
int
setMidiInstrument (MidiDevice *midi, unsigned char channel, unsigned char instrument) {
snd_seq_event_t event;
prepareMidiEvent(midi, &event);
snd_seq_ev_set_pgmchange(&event, channel, instrument);
return sendMidiEvent(midi, &event);
}
int
beginMidiBlock (MidiDevice *midi) {
startMidiTimer(midi);
return 1;
}
int
endMidiBlock (MidiDevice *midi) {
return 1;
}
int
startMidiNote (MidiDevice *midi, unsigned char channel, unsigned char note, unsigned char volume) {
snd_seq_event_t event;
prepareMidiEvent(midi, &event);
snd_seq_ev_set_noteon(&event, channel, note, volume);
midi->note = note;
scheduleMidiEvent(midi, &event);
return sendMidiEvent(midi, &event);
}
int
stopMidiNote (MidiDevice *midi, unsigned char channel) {
snd_seq_event_t event;
prepareMidiEvent(midi, &event);
snd_seq_ev_set_noteoff(&event, channel, midi->note, 0);
midi->note = 0;
scheduleMidiEvent(midi, &event);
return sendMidiEvent(midi, &event);
}
int
insertMidiWait (MidiDevice *midi, int duration) {
midi->time.tv_sec += duration / 1000;
midi->time.tv_nsec += (duration % 1000) * 1000000;
while (midi->time.tv_nsec >= 1000000000) {
midi->time.tv_nsec -= 1000000000;
midi->time.tv_sec++;
}
return 1;
}