KORG microX utility.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

784 lines
18 KiB

/*
* asysex - A SysEx Utility
* Copyright (C) 2012,2013,2016 Damien Goutte-Gattat
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
/** @file midi.c
* MIDI I/O API.
*
* This module implements an abstraction layer above the system MIDI
* I/O API(s).
*
* Currently the supported APIs are ALSA and OSS.
*/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <midi.h>
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#ifdef HAVE_ALSA
#include <alsa/asoundlib.h>
#endif
#ifdef HAVE_OSS
#include <fcntl.h>
#include <string.h>
#include <dirent.h>
#endif
/** Pointer to a backend-specific implementation of midi_close(). */
typedef int (*m_close) (midi_io_t *);
/** Pointer to a backend-specific implementation of midi_write(). */
typedef ssize_t (*m_write) (midi_io_t *, unsigned char *, size_t);
/** Pointer to a backend-specific implementation of midi_read(). */
typedef ssize_t (*m_read) (midi_io_t *, unsigned char *, size_t);
/** Pointer to a backend-specific implementation of midi_error(). */
typedef const char * (*m_error) (midi_io_t *);
/** The size of the internal read buffer. */
#define MIDI_IO_BUFFER_SIZE 256
/** An abstracted MIDI port.
* This structure represents an opened MIDI port.
*/
struct midi_io
{
struct {
/** Pointer to a midi_close() implementation. */
m_close close;
/** Pointer to a midi_write() implementation. */
m_write write;
/** Pointer to a midi_read() implementation. */
m_read read;
/** Pointer to a midi_error() implementation. */
m_error error;
} backend;
/** The error code of the last read or write operation. */
int error;
/** The input buffer. */
unsigned char buffer[MIDI_IO_BUFFER_SIZE];
/** The current position in the input buffer. */
size_t pos;
/** The number of available bytes in the input buffer. */
size_t len;
/** The current MIDI Status Byte. */
unsigned char status;
/** Backend-dependent data. */
union {
#ifdef HAVE_ALSA
struct {
snd_rawmidi_t *in;
snd_rawmidi_t *out;
} alsa;
#endif
#ifdef HAVE_OSS
struct {
int fd;
} oss;
#endif
};
};
/* Backend-specific functions. */
#ifdef HAVE_ALSA
static int snd_config_updated = 0;
#define SND_CONFIG_UPDATE() \
do { \
if ( ! snd_config_updated ) { \
snd_config_update(); \
snd_config_updated = 1; \
} \
} while ( 0 )
static int
alsa_midi_close(midi_io_t *midi)
{
snd_rawmidi_close(midi->alsa.in);
snd_rawmidi_close(midi->alsa.out);
free(midi);
return 0;
}
static ssize_t
alsa_midi_write(midi_io_t *midi, unsigned char *buffer, size_t len)
{
ssize_t n;
/* Flush reading buffer. */
midi->pos = midi->len = 0;
if ( (n = snd_rawmidi_write(midi->alsa.out, buffer, len)) < 0 ||
(unsigned)n != len ) {
midi->error = n;
n = -1;
}
else
midi->error = 0;
return n;
}
static ssize_t
alsa_midi_read(midi_io_t *midi, unsigned char *buffer, size_t len)
{
ssize_t n;
if ( (n = snd_rawmidi_read(midi->alsa.in, buffer, len)) < 0 ) {
midi->error = n;
n = -1;
}
else
midi->error = 0;
return n;
}
static const char *
alsa_midi_error(midi_io_t *midi)
{
if ( midi->error == 0 )
return NULL;
else
return snd_strerror(midi->error);
}
static midi_io_t *
alsa_midi_open(const char *name)
{
midi_io_t *midi;
if ( ! (midi = malloc(sizeof(*midi))) )
return NULL;
SND_CONFIG_UPDATE();
midi->alsa.in = midi->alsa.out = NULL;
midi->error = midi->pos = midi->len = midi->status = 0;
midi->backend.close = alsa_midi_close;
midi->backend.write = alsa_midi_write;
midi->backend.read = alsa_midi_read;
midi->backend.error = alsa_midi_error;
if ( snd_rawmidi_open_lconf(&(midi->alsa.in), &(midi->alsa.out), name,
SND_RAWMIDI_NONBLOCK, snd_config) < 0 ) {
free(midi);
midi = NULL;
}
else if ( snd_rawmidi_nonblock(midi->alsa.out, 0) < 0 ) {
midi_close(midi);
midi = NULL;
}
return midi;
}
static void
alsa_midi_get_ports(char ***ports, size_t *n, size_t *max)
{
char name[32];
int card;
SND_CONFIG_UPDATE();
card = -1;
while ( snd_card_next(&card) >= 0 && card != -1 ) {
snd_ctl_t *ctl;
snprintf(name, sizeof(name), "hw:%d", card);
if ( snd_ctl_open_lconf(&ctl, name, 0, snd_config) >= 0 ) {
int device;
device = -1;
while ( snd_ctl_rawmidi_next_device(ctl, &device) >= 0 && device >= 0 ) {
snprintf(name, sizeof(name), "hw:%d,%d", card, device);
if ( *n + 1 >= *max ) {
char **tmp;
if ( ! (tmp = realloc(*ports, *max + 10)) )
goto err;
*ports = tmp;
*max += 10;
}
if ( ((*ports)[*n] = strdup(name)) )
*n += 1;
}
err:
snd_ctl_close(ctl);
}
}
}
#endif /* HAVE_ALSA */
#ifdef HAVE_OSS
static int
oss_midi_close(midi_io_t *midi)
{
close(midi->oss.fd);
free(midi);
return 0;
}
static ssize_t
oss_midi_write(midi_io_t *midi, unsigned char *buffer, size_t len)
{
ssize_t n;
/* Flush reading buffer. */
midi->pos = midi->len = 0;
if ( (n = write(midi->oss.fd, buffer, len)) == -1 )
midi->error = errno;
else
midi->error = 0;
return n;
}
static ssize_t
oss_midi_read(midi_io_t *midi, unsigned char *buffer, size_t len)
{
ssize_t n;
if ( (n = read(midi->oss.fd, buffer, len)) == -1 )
midi->error = errno;
else
midi->error = 0;
return n;
}
static const char *
oss_midi_error(midi_io_t *midi)
{
if ( midi->error == 0 )
return NULL;
else
return strerror(midi->error);
}
static midi_io_t *
oss_midi_open(const char *name)
{
midi_io_t *midi;
assert(name != NULL);
if ( ! (midi = malloc(sizeof(*midi))) )
return NULL;
midi->oss.fd = -1;
midi->error = midi->pos = midi->len = midi->status = 0;
midi->backend.close = oss_midi_close;
midi->backend.write = oss_midi_write;
midi->backend.read = oss_midi_read;
midi->backend.error = oss_midi_error;
if ( (midi->oss.fd = open(name, O_RDWR, 0)) == -1 ) {
free(midi);
midi = NULL;
}
return midi;
}
static int
oss_midi_device(const struct dirent *entry)
{
return strncmp(entry->d_name, "midi", 4) == 0;
}
static void
oss_midi_get_ports(char ***ports, size_t *n, size_t *max)
{
char name[32];
int k;
struct dirent **namelist;
if ( (k = scandir("/dev", &namelist, oss_midi_device, alphasort)) != -1 ) {
while ( k-- ) {
snprintf(name, sizeof(name), "/dev/%s", namelist[k]->d_name);
free(namelist[k]);
if ( *n + 1 >= *max ) {
char **tmp;
if ( ! (tmp = realloc(*ports, *max + 10)) )
goto err;
*max += 10;
*ports = tmp;
}
if ( ((*ports)[*n] = strdup(name)) )
*n += 1;
err:
;
}
}
}
#endif /* HAVE_OSS */
/* Backend-independent functions. */
/**
* Open a MIDI port.
* This function opens the MIDI port identified by the specified
* name. It allocates a midi_io structure which should be used
* for all consecutive operations on that port.
*
* When no longer needed, the port should be closed by calling
* the midi_close() function.
*
* @note The port name determines which MIDI backend will be
* used; all available backends are tried successively until
* one of them recognizes the name as a valid port name.
*
* @param[in] name The name of the MIDI port to open.
*
* @return
* - A pointer to a newly allocated midi_io object;
* - @c NULL if an error occured.
*/
midi_io_t *
midi_open(const char *name)
{
midi_io_t *midi;
midi = NULL;
#ifdef HAVE_ALSA
if ( midi == NULL && strncmp("hw:", name, 3) == 0 )
midi = alsa_midi_open(name);
#endif
#ifdef HAVE_OSS
if ( midi == NULL && strncmp("/dev/midi", name, 9) == 0 )
midi = oss_midi_open(name);
#endif
return midi;
}
/**
* Close a MIDI port.
* This function closes the MIDI port and frees the associated
* resources.
*
* @return
* - 0 if the port was successfully closed;
* - -1 if an error occured.
*/
int
midi_close(midi_io_t *midi)
{
assert(midi != NULL);
return midi->backend.close(midi);
}
/**
* Write to a MIDI port.
* This function writes arbitrary data to the MIDI port. It
* does not perform any kind of check on the data before
* sending them to the MIDI port; it is the caller's
* responsibility to provide this function with valid and
* meaningful MIDI data.
*
* @param[in] midi The MIDI port to write to.
* @param[in] buffer The MIDI data to write.
* @param[in] len The number of bytes from @a buffer to write.
*
* @return
* - The number of bytes written to the port;
* - -1 if an error occured.
*/
ssize_t
midi_write(midi_io_t *midi, unsigned char *buffer, size_t len)
{
assert(midi != NULL);
assert(buffer != NULL);
return midi->backend.write(midi, buffer, len);
}
/**
* Read from a MIDI port.
* This function reads data from the MIDI port and stores it
* in the provided buffer.
*
* Note that MIDI messages may be split between two calls of
* this function.
*
* @param[in] midi The MIDI port to read from.
* @param[out] buffer The buffer where to store read data.
* @param[in] len The size of the @a buffer store.
*
* @return
* - The number of bytes read and stored into @a buffer;
* - -1 if an error occured.
*/
ssize_t
midi_read(midi_io_t *midi, unsigned char *buffer, size_t len)
{
assert(midi != NULL);
assert(buffer != NULL);
return midi->backend.read(midi, buffer, len);
}
/**
* Get a description for the last error.
* This function gets an error message for the last error
* that occured during a write or read operation.
*
* @param[in] midi The MIDI port.
*
* @return
* A static string containing the user-readable error message,
* or @c NULL if the last I/O operation was successful.
*/
const char *
midi_error(midi_io_t *midi)
{
assert(midi != NULL);
return midi->backend.error(midi);
}
/**
* List available MIDI ports.
* This function returns a list of all available MIDI ports
* for all the supported backends.
*
* @return
* A newly allocated null-terminated array of strings, each
* string being the name of a MIDI port. Freeing each string
* and the array itself is the caller responsibility.
*/
char **
midi_get_ports(void)
{
char **ports;
size_t n, max;
ports = NULL;
n = max = 0;
#ifdef HAVE_ALSA
alsa_midi_get_ports(&ports, &n, &max);
#endif
#ifdef HAVE_OSS
oss_midi_get_ports(&ports, &n, &max);
#endif
if ( ports ) {
assert (n < max);
ports[n] = NULL;
}
return ports;
}
/**
* Read a MIDI byte.
* This function reads a single MIDI byte from the MIDI port.
*
* @param[in] midi The MIDI port to read from.
*
* @return
* - A single MIDI byte;
* - -1 if an error occured.
*/
int
midi_next(midi_io_t *midi)
{
unsigned char b;
assert(midi != NULL);
if ( midi->pos >= midi->len ) {
ssize_t n;
if ( (n = midi_read(midi, midi->buffer, MIDI_IO_BUFFER_SIZE)) == -1 )
return -1;
midi->len = n;
midi->pos = 0;
}
b = (unsigned char) midi->buffer[midi->pos++];
if ( b >= 0x80 && b < 0xF8 ) /* Status byte? */
midi->status = b;
return b;
}
/**
* Get the current Status Byte.
* This function returns the current (Running) Status Byte in
* the input MIDI stream.
*
* @param[in] midi The MIDI port.
*
* @return
* The last MIDI Status byte encountered.
*/
inline unsigned char
midi_status(midi_io_t *midi)
{
return midi->status;
}
/**
* Emit a Program Change message.
* This function emits Bank Select and Program Change messages
* to the specified MIDI channel.
*
* @param[in] midi The MIDI port to write to.
* @param[in] channel The MIDI channel to write to.
* @param[in] bank The bank number to select.
* @param[in] program The program number to change to.
*
* @return
* - 0 if the messages were successfully sent;
* - -1 if an error occured.
*/
int
midi_change_program(midi_io_t *midi,
unsigned char channel,
unsigned short bank,
unsigned char program)
{
unsigned char msg[] = { 0xB0, /* Control Change */
0x00, /* CC Bank Select MSB */
0x00, /* Bank number MSB */
0xB0, /* Control Change */
0x20, /* CC Bank Select LSB */
0x00, /* Bank number LSB */
0xC0, /* Program Change */
0x00 /* Program number */ };
assert(midi != NULL);
msg[0] |= (channel & 0x0F);
msg[3] |= (channel & 0x0F);
msg[6] |= (channel & 0x0F);
msg[2] |= ((bank >> 7) & 0x7F);
msg[5] |= (bank & 0x7F);
msg[7] |= (program & 0x7F);
return midi_write(midi, msg, sizeof(msg)) == sizeof(msg) ? 0 : -1;
}
/**
* Set a MIDI controller to a specified value.
* This function emits a Control Change message to set a MIDI
* controller to a specified value.
*
* @param[in] midi The MIDI port to write to.
* @param[in] channel The MIDI channel to write to.
* @param[in] controller The controller number to assign.
* @param[in] value The value to assign to the controller.
*
* @return
* - 0 if the message was successfully sent;
* - -1 if an error occured.
*/
int
midi_set_controller(midi_io_t *midi,
unsigned char channel,
unsigned char controller,
unsigned char value)
{
unsigned char msg[] = { 0xB0, /* Control change */
0x00, /* Controller */
0x00, /* Value */ };
assert(midi != NULL);
msg[0] |= (channel & 0x0F);
msg[1] |= (controller & 0x7F);
msg[2] |= (value & 0x7F);
return midi_write(midi, msg, sizeof(msg)) == sizeof(msg) ? 0 : -1;
}
/**
* General MIDI patches.
* This array contains the names of the standard GM patches.
*/
const char *midi_gm_patches[] = {
"Acoustic Grand Piano",
"Bright Acoustic Piano",
"Electric Grand Piano",
"Honky-tonk Piano",
"Electric Piano 1",
"Electric Piano 2",
"Harpsichord",
"Clavi",
"Celesta",
"Glockenspiel",
"Music Box",
"Vibraphone",
"Marimba",
"Xylophone",
"Tubular Bells",
"Dulcimer",
"Drawbar Organ",
"Percussive Organ",
"Rock Organ",
"Church Organ",
"Reed Organ",
"Accordion",
"Harmonica",
"Tango Accordion",
"Guitar (nylon)",
"Acoustic Guitar (steel)",
"Electric Guitar (jazz)",
"Electric Guitar (clean)",
"Electric Guitar (muted)",
"Overdriven Guitar",
"Distortion Guitar",
"Guitar harmonics",
"Acoustic Bass",
"Electric Bass (finger)",
"Electric Bass (pick)",
"Fretless Bass",
"Slap Bass 1",
"Slap Bass 2",
"Synth Bass 1",
"Synth Bass 2",
"Violin",
"Viola",
"Cello",
"Contrabass",
"Tremolo Strings",
"Pizzicato Strings",
"Orchestral Harp",
"Timpani",
"String Ensemble 1",
"String Ensemble 2",
"SynthStrings 1",
"SynthStrings 2",
"Choir Aahs",
"Voice Oohs",
"Synth Voice",
"Orchestra Hit",
"Trumpet",
"Trombone",
"Tuba",
"Muted Trumpet",
"French Horn",
"Brass Section",
"SynthBrass 1",
"SynthBrass 2",
"Soprano Sax",
"Alto Sax",
"Tenor Sax",
"Baritone Sax",
"Oboe",
"English Horn",
"Bassoon",
"Clarinet",
"Piccolo",
"Flute",
"Recorder",
"Pan Flute",
"Blown Bottle",
"Shakuhachi",
"Whistle",
"Ocarina",
"Lead 1 (square)",
"Lead 2 (sawtooth)",
"Lead 3 (calliope)",
"Lead 4 (chiff)",
"Lead 5 (charang)",
"Lead 6 (voice)",
"Lead 7 (fifths)",
"Lead 8 (bass+lead)",
"Pad 1 (new age)",
"Pad 2 (warm)",
"Pad 3 (polysynth)",
"Pad 4 (choir)",
"Pad 5 (bowed)",
"Pad 6 (metallic)",
"Pad 7 (halo)",
"Pad 8 (sweep)",
"FX 1 (rain)",
"FX 2 (soundtrack)",
"FX 3 (crystal)",
"FX 4 (atmosphere)",
"FX 5 (brightness)",
"FX 6 (goblins)",
"FX 7 (echoes)",
"FX 8 (sci-fi)",
"Sitar",
"Banjo",
"Shamisen",
"Koto",
"Kalimba",
"Bag pipe",
"Fiddle",
"Shanai",
"Tinkle Bell",
"Agogo",
"Steel Drums",
"Woodblock",
"Taiko drum",
"Melodic Tom",
"Synth Drum",
"Reverse Cymbal",
"Guitar Fret Noise",
"Breath Noise",
"Seashore",
"Bird Tweet",
"Telephone Ring",
"Helicopter",
"Applause",
"Gunshot"
};