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.

250 lines
7.5 KiB

/*
* asysex - A SysEx Utility
* Copyright (C) 2012 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 sysex.c
* System Exclusive support module.
*
* This module provides helper functions to handle System Exclusive
* (SysEx) MIDI messages.
*/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <sysex.h>
/**
* Read a System Exclusive message.
* This function reads a single System Exclusive message.
* Realtime messages possibly interspersed inside the System
* Exclusive message are discarded. Incomplete SysEx messages,
* interrupted from the sending side before the terminating
* byte is received, are also discarded.
*
* @param[in] midi The MIDI port to read from.
* @param[out] data The buffer where to store the message.
* @param[in] len The size of the @a data buffer.
*
* @return
* - The number of bytes read and stored in the @a data buffer.
*
* If this value equals the size of the buffer, the caller should
* check whether the last byte in the buffer is a SysEx terminating
* byte (0xF7); if it is not, the buffer was not large enough to
* hold the entire message, and the caller may call the function
* again to get the rest of the message.
* - 0 if there was no SysEx message, or if it was abruptely
* interrupted.
* - -1 if an I/O error occured.
*/
ssize_t
sysex_read(midi_io_t *midi, unsigned char *data, size_t len)
{
int byte, loop;
unsigned n, p;
loop = 1;
n = p = 0;
while ( n < len && loop ) {
if ( (byte = midi_next(midi)) == -1 )
return byte;
/*
* We're not interested in System Realtime messages and should
* ignore them. However, when using a MIDI device which
* regularly emits such messages, this function can end up
* blocked until the next SysEx message. The workaround here is
* to count System Realtime messages: if we read more than 10
* of them in a row (arbitrary value), it's likely that there
* is no SysEx message in queue, and we can tell the caller
* that we read nothing.
*/
if ( byte >= 0xF8 ) {
if ( ++p >= 10 )
return 0;
continue;
}
else
p = 0;
if ( midi_status(midi) == 0xF0 ) {
if ( byte == 0xF0 && n > 0 ) {
/* Discard previous, incomplete SysEx message. */
n = 0;
}
data[n++] = byte;
}
else if ( n > 0 ) {
/* We were reading a SysEx message, what happened? */
if ( byte == 0xF7 ) {
/* Normal termination of SysEx message,
* stop reading and return data to caller. */
data[n++] = byte;
loop = 0;
}
else if ( byte >= 0x80 && byte < 0xF7 ) {
/* Abnormal termination of SysEx message,
* abort reading and report failure to caller. */
n = 0;
loop = 0;
}
}
}
return n;
}
/**
* Send a MIDI message and get a SysEx reply.
* This function writes the provided data to the MIDI port and
* read a single SysEx message in reply if any.
*
* @param[in] midi The MIDI port.
* @param[in] query The data to write.
* @param[in] query_len The number of bytes from the @a query
* buffer to write.
* @param[out] reply The buffer where to store the reply.
* @param[in] reply_len The size of the @a reply buffer.
*
* @return
* - The number of bytes read and stored in the @a reply buffer;
* - 0 if the reply message was abruptly terminated;
* - -1 if an I/O error occured.
*/
ssize_t
sysex_query(midi_io_t *midi,
unsigned char *query,
size_t query_len,
unsigned char *reply,
size_t reply_len)
{
ssize_t n;
if ( (n = midi_write(midi, query, query_len)) != -1 )
n = sysex_read(midi, reply, reply_len);
return n;
}
/**
* Identify a connected MIDI device.
* This function sends an universal Device Inquiry System
* Exclusive message to the MIDI port and read the reply,
* which is expected to contain informations about the
* connected MIDI device.
*
* @param[in] midi The MIDI port.
* @param[out] devid A midi_device_id structure which is
* to be filled in with the received
* informations.
*
* @return
* - 1 if the identify attempt was successful;
* - 0 if the reply was not a valid Device Inquiry Reply;
* - -1 if an I/O error occured.
*/
int
sysex_identify(midi_io_t *midi, midi_device_id_t *devid)
{
ssize_t n;
unsigned char reply[64], *p;
unsigned char query[] = { 0xF0, /* SysEx message begin */
0x7E, /* Non-realtime message */
0x7F, /* SysEx channel (Any) */
0x06, /* General information */
0x01, /* Identity request */
0xF7 /* SysEx message end */ };
if ( (n = sysex_query(midi, query, sizeof(query),
reply, sizeof(reply))) == -1 )
return n;
if ( n != 15 && n != 17 ) /* invalid reply length */
return 0;
if ( reply[1] != 0x7E || reply[3] != 0x06 || reply[4] != 0x02 )
/* invalid reply */
return 0;
devid->global_channel = reply[2] & 0xF;
p = &reply[5];
devid->manufacturer = *p++;
if ( devid->manufacturer == 0x00 ) { /* extended manufacturer ID */
devid->manufacturer = 1 << 16;
devid->manufacturer += *p++ << 8;
devid->manufacturer += *p++;
}
devid->family = *p++;
devid->family += *p++ << 8;
devid->model = *p++;
devid->model += *p++ << 8;
devid->version = *p++;
devid->version += *p++ << 8;
devid->version += *p++ << 16;
devid->version += *p++ << 24;
return 1;
}
struct manufacturer
{
unsigned int id;
const char *name;
};
/**
* Get the name of the MIDI manufacturer with the specified ID.
* This function lookups a internal list of MIDI manufacturers
* and return the name of the manufacturer with the specified
* identifier.
*
* @param[in] id A positive integer identifying a MIDI
* manufacturer.
*
* @return
* A static string containing the name of the MIDI manufacturer
* with the specified @a id.
*/
const char *
sysex_get_manufacturer(unsigned int id)
{
struct manufacturer *cursor;
static struct manufacturer manufacturers[] = {
#define MANUFACTURER(id, name) { id, name },
#define MANUFACTURER_EXTENDED(high_id, low_id, name) \
{ (1 << 16) + (high_id << 8) + low_id, name },
#include <manufacturers.h>
#undef MANUFACTURER
#undef MANUFACTURER_EXTENDED
{ 0x0, NULL }
};
cursor = manufacturers;
while ( cursor->name ) {
if ( cursor->id == id )
return cursor->name;
cursor += 1;
}
return "Unknown manufacturer";
}