/*
|
|
* asysex - A SysEx Utility
|
|
* Copyright (C) 2012,2013 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
|
|
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;
|
|
|
|
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(&(midi->alsa.in), &(midi->alsa.out), name,
|
|
SND_RAWMIDI_NONBLOCK) < 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;
|
|
|
|
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(&ctl, name, 0) >= 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;
|
|
}
|
|
|
|
/**
|
|
* 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"
|
|
};
|