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.
249 lines
7.6 KiB
249 lines
7.6 KiB
/*
|
|
* asysex - A SysEx Utility
|
|
* Copyright (C) 2012,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 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";
|
|
}
|