Browse Source

Merge branch 'develop'

master v0.2.0
Damien Goutte-Gattat 8 years ago
parent
commit
b2bae9984f
  1. 1
      .gitignore
  2. 1794
      Doxyfile
  3. 4
      Makefile.am
  4. 6
      NEWS
  5. 12
      README
  6. 78
      configure.ac
  7. 38
      man/kmxtool.1.in
  8. 169
      src/kmxtool.c
  9. 567
      src/microx.c
  10. 52
      src/microx.h
  11. 677
      src/midi.c
  12. 13
      src/midi.h
  13. 84
      src/sysex.c
  14. 15
      src/sysex.h

1
.gitignore

@ -9,6 +9,7 @@ config.status
config
configure
stamp-*
doc
# Autoconf macros files (ignored by default)
*.m4

1794
Doxyfile
File diff suppressed because it is too large
View File

4
Makefile.am

@ -2,6 +2,6 @@ SUBDIRS = lib src man
ACLOCAL_AMFLAGS = -I m4 --install
dist_doc_DATA = AUTHORS COPYING README
dist_doc_DATA = AUTHORS COPYING NEWS README
AM_DISTCHECK_CONFIGURE_FLAGS = --with-alsa
EXTRA_DIST = Doxyfile

6
NEWS

@ -0,0 +1,6 @@
Changes in kmxtools 0.2.0
* List programs on the microX.
* Support dumping of multiple slots.
* Auto-detect a connected microX device.
* Enable runtime selection of MIDI backend.

12
README

@ -40,8 +40,10 @@ against the ALSA emulation layer for OSS; it *does* work against the
emulated OSS, so I expect it should also work against a real OSS, but I
cannot guarantee that.
The MIDI backend must be selected at configure time with the
`--with-alsa' or (exclusive) `--with-oss' options. There is no default,
so one of these options *must* be used when calling configure. The
selected backend cannot be changed later without recompiling the program
(runtime selection of the MIDI backend may be implemented someday).
MIDI backends available on the target system are enabled by default; if
support for a given backend is not wanted, use the corresponding
`--disable-{alsa,oss}' option at configure time.
When the `-p, --port` option is used, the name of the MIDI port
determines which backend is used. For example, `/dev/midi00` selects the
OSS backend, while `hw:1,0,0' selects the ALSA backend.

78
configure.ac

@ -1,6 +1,6 @@
dnl Configure template for the kmxtool package
AC_INIT([KORG microX utility], [0.1.0],
AC_INIT([KORG microX utility], [0.2.0],
[dgouttegattat@incenp.org], [kmxtool])
AC_CONFIG_SRCDIR([configure.ac])
AC_CONFIG_MACRO_DIR([m4])
@ -18,30 +18,58 @@ AC_PROG_INSTALL
dnl Check for some non-ubiquitous functions
ICP_CHECK_NOTCH_FUNCS
dnl Choice of MIDI backend
midi_backend=
midi_default_port=
AC_ARG_WITH([alsa],
[AS_HELP_STRING([--with-alsa],
[use ALSA MIDI backend])],
[AS_IF([test "x$with_alsa" = xyes], [midi_backend=alsa])])
AC_ARG_WITH([oss],
[AS_HELP_STRING([--with-oss],
[use OSS MIDI backend])],
[AS_IF([test "x$with_oss" = xyes], [midi_backend=oss])])
dnl Check for MIDI backends
AM_PATH_ALSA([1.0.0], [alsa_available=yes], [alsa_available=no])
oss_available=yes
dnl Select MIDI backends to enable
AC_ARG_ENABLE([alsa],
[AS_HELP_STRING([--enable-alsa],
[Enable ALSA MIDI backend (default=auto)])],
[], [])
AC_ARG_ENABLE([oss],
[AS_HELP_STRING([--enable-oss],
[Enable OSS MIDI backend (default=yes)])])
dnl Now decide which backends to use
midi_backends=
AS_IF([test "x$alsa_available" = xyes],
[AS_IF([test "x$enable_alsa" != xno],
[use_alsa=yes],
[use_alsa=no
CFLAGS="$alsa_save_CFLAGS"
LDFLAGS="$alsa_save_LDFLAGS"
LIBS="$alsa_save_LIBS"])],
[use_alsa=no
AS_IF([test "x$enable_alsa" = xyes],
[AC_MSG_ERROR([ALSA backend requested but not available.])])])
AS_IF([test "x$oss_available" = xyes],
[AS_IF([test "x$enable_oss" != xno],
[use_oss=yes],
[use_oss=no])],
[use_oss=no
AS_IF([test "x$enable_oss" = xyes],
[AC_MSG_ERROR([OSS backend requested but not available.])])])
dnl Backend(s) selected, now act accordingly
AS_IF([test "x$use_alsa" = xyes],
[AS_IF([test -z "$midi_backends"],
[midi_backends="alsa"], [midi_backends="$midi_backends alsa"])
AC_DEFINE([HAVE_ALSA], [1],
[Whether ALSA MIDI backend is available.])
midi_default_port=hw:1,0,0])
AS_IF([test "x$use_oss" = xyes],
[AS_IF([test -z "$midi_backends"],
[midi_backends="oss"], [midi_backends="$midi_backends oss"])
AC_DEFINE([HAVE_OSS], [1],
[Whether OSS MIDI backend is available.])
midi_default_port=/dev/midi00])
dnl At least one backend must be available and enabled
AS_IF([test -z "$midi_backends"],
[AC_MSG_ERROR([No MIDI backend enabled.])])
dnl Check for selected backend
AS_CASE(["x$midi_backend"],
[xalsa],
[AM_PATH_ALSA([1.0.0],
[AC_DEFINE([USE_ALSA_MIDI_API], [1],
[Define wether to use the ALSA MIDI API.])
midi_default_port="hw:1,0,0"])],
[xoss],
[AC_DEFINE([USE_OSS_MIDI_API], [1],
[Define whether to use the OSS MIDI API.])
midi_default_port="/dev/midi00"],
[AC_MSG_ERROR([No MIDI backend selected.])])
AC_DEFINE_UNQUOTED([DEFAULT_MIDI_PORT], ["$midi_default_port"],
[Default MIDI port to use.])
AC_SUBST([DEFAULT_MIDI_PORT], [$midi_default_port])
@ -59,6 +87,6 @@ Configuration complete
Prefix: '${prefix}'
Compiler: '${CC} ${CFLAGS} ${CPPFLAGS}'
MIDI backend: '${midi_backend}'
MIDI backends: '${midi_backends}'
"

38
man/kmxtool.1.in

@ -14,8 +14,6 @@ kmxtool - KORG microX Utility
.IR id ]
.RB [ \-l | --load-data
.IR id ]
.RB [ \-t | --data-type
.IR type ]
.YS
.SH DESCRIPTION
@ -36,8 +34,14 @@ Display the help message.
Display the version message.
.TP
.BR -p ", " --port " " \fIport\fR
Connect to the specified MIDI \fIport\fR.
If not specified, default is \(lq@DEFAULT_MIDI_PORT@\(rq.
Connect to the specified MIDI \fIport\fR (any port name
that can be recognized by any of the available MIDI
backends, e.g. \fI/dev/midi00\fR for the OSS backend or
\fIhw:1,0,0\fR for the ALSA backend).
If not specified, a \fIDevice Inquiry\fR message is sent
to all available MIDI ports to detect a microX synthesizer.
.TP
.BR -s ", " --status
Print the current status of the synthesizer. This is
@ -49,11 +53,27 @@ Dump the specified memory slot to standard output.
.BR -l ", " --load-data " " \fIid\fR
Load data read from standard input to the specified
memory slot.
.TP
.BR -t ", " --data-type " " \fItype\fR
Specify the type of memory slot to dump data from or to
load data into. \fItype\fR must be one of
.IR program ", " combination ", " multi ", " drumkit ", " arpeggio ", " extset .
.SH MEMORY SLOT NAMES
.PP
A memory slot \fIid\fR consists of one letter indicating the
type of memory (\(aqP\(aq for programs, \(aqC\(aq for
combinations, \(aqM\(aq for multis, \(aqD\(aq for drumkits,
\(aqA\(aq for arpeggio patterns, and \(aqE\(aq for external
controllers sets), one letter indicating the bank (only for
programs and combinations), and up to three decimal digits
indicating the slot number (counting from one, not zero).
.PP
Replacing the slot number by an asterisk (\(aq*\(aq) instructs
.B kmxtool
to dump all slots in the given bank (for programs and
combinations) or all slots of the given type (for multis,
drumkits, arpeggio patterns and external controllers sets).
For programs and combinations, replacing the bank letter by
an asterisk instructs
.B kmxtool
to dump all programs or combinations in all banks.
.SH INPUT FORMAT
.PP

169
src/kmxtool.c

@ -34,6 +34,7 @@
#define MODE_STATUS 0x0
#define MODE_DATA_DUMP 0x1
#define MODE_DATA_LOAD 0x2
#define MODE_PROGRAM_LIST 0x4
/* Globals. */
@ -55,17 +56,18 @@ Send/receive data to/from a connected microX synthesizer.\n");
");
puts("\
-p, --port PORT Specify MIDI port to use.\n\
-p, --port PORT Specify MIDI port to use. If\n\
unspecified, all available MIDI\n\
ports will be scanned to detect\n\
a microX synthesizer.\n\
");
puts("\
-s, --status Print device status (default).\n\
-L, --list-programs List all programs in all banks.\n\
-d, --dump-data ID Dump the specified slot.\n\
-l, --load-data ID Load program read from standard\n\
-l, --load-data ID Load data read from standard\n\
input into the specified slot.\n\
-t, --data-type {program|combination|multi|drumkit|arpeggio|extset}\n\
Specify the type of data to dump\n\
or load. Default is `program'.\n\
");
printf("Report bugs to <%s>.\n", PACKAGE_BUGREPORT);
@ -150,23 +152,63 @@ print_microx_status(struct kmx_microx_status *s)
printf(" Current Controller: %d\n", s->ext_ctrl.current);
}
struct kmx_data_type
static void
do_list_programs(midi_io_t *midi)
{
enum kmx_microx_data_type code;
size_t size;
const char *name;
};
size_t len;
int n;
unsigned char *reply, *cursor, bank, program;
struct kmx_microx_dump dump;
dump.type = PROGRAM_DATA;
dump.load = 0;
dump.bank = KMX_MICROX_DUMP_ALL;
len = kmx_microx_get_dump_size(&dump);
reply = xmalloc(len);
if ( (n = kmx_microx_dump(midi, &dump, reply, len)) < 0 )
errx(EXIT_FAILURE, "cannot dump data: %s", kmx_microx_error(midi, n));
for ( cursor = reply, bank = 'A'; bank < 'F'; bank++ ) {
for ( program = 1; program <= MICROX_BANK_SIZE; program++ ) {
printf("%c%03d %.16s\n", bank, program, cursor);
cursor += MICROX_PROGRAM_SIZE;
}
}
/* Also list non-modifiable programs of the GM bank */
for ( program = 0; program < 128; program++ )
printf("G%03d %s\n", program + 1, midi_gm_patches[program]);
free(reply);
}
struct kmx_data_type kmx_data_types[] =
static int
find_microx_on_port(const char *port,
struct kmx_microx_version *version,
int quiet)
{
{ PROGRAM_DATA, MICROX_PROGRAM_SIZE, "program" },
{ COMBINATION_DATA, MICROX_COMBINATION_SIZE, "combination" },
{ MULTI_DATA, MICROX_MULTI_SIZE, "multi" },
{ DRUMKIT_DATA, MICROX_DRUMKIT_SIZE, "drumkit" },
{ ARPEGGIO_DATA, MICROX_ARPEGGIO_SIZE, "arpeggio" },
{ EXTSET_DATA, MICROX_EXTSET_SIZE, "extset" },
{ 0, 0, NULL }
};
int ret, e;
ret = -1;
if ( (midi = midi_open(port)) != NULL ) {
if ( (e = kmx_microx_identify(midi, version)) == 0 )
ret = 0;
else {
midi_close(midi);
midi = NULL;
if ( ! quiet )
errx(EXIT_FAILURE, "cannot identify microX device: %s",
kmx_microx_error(midi, e));
}
}
else if ( ! quiet )
errx(EXIT_FAILURE, "cannot open MIDI port %s", port);
return ret;
}
/* Main function. */
@ -178,28 +220,27 @@ main(int argc, char **argv)
int e, mode;
struct kmx_microx_version version;
struct kmx_microx_status status;
struct kmx_data_type *type;
struct kmx_microx_dump dump;
struct option options[] = {
{ "help", 0, NULL, 'h' },
{ "version", 0, NULL, 'v' },
{ "port", 1, NULL, 'p' },
{ "status", 0, NULL, 's' },
{ "dump-data", 1, NULL, 'd' },
{ "load-data", 1, NULL, 'l' },
{ "data-type", 1, NULL, 't' },
{ NULL, 0, NULL, 0 }
{ "help", 0, NULL, 'h' },
{ "version", 0, NULL, 'v' },
{ "port", 1, NULL, 'p' },
{ "status", 0, NULL, 's' },
{ "dump-data", 1, NULL, 'd' },
{ "load-data", 1, NULL, 'l' },
{ "list-programs", 0, NULL, 'L' },
{ NULL, 0, NULL, 0 }
};
setprogname(argv[0]);
setlocale(LC_ALL, "");
atexit(cleanup);
port = DEFAULT_MIDI_PORT;
port = NULL;
mode = MODE_STATUS;
type = kmx_data_types;
while ( (c = getopt_long(argc, argv, "hvp:sd:l:t:",
while ( (c = getopt_long(argc, argv, "hvp:sd:l:L",
options, NULL)) != -1 ) {
switch ( c ) {
case 'h':
@ -232,29 +273,34 @@ main(int argc, char **argv)
param = optarg;
break;
case 't':
while ( type->name ) {
if ( strcmp(type->name, optarg) == 0 )
break;
type += 1;
}
if ( type->name == NULL )
errx(EXIT_FAILURE, "invalid data type: %s", optarg);
case 'L':
mode = MODE_PROGRAM_LIST;
break;
}
}
if ( ! (midi = midi_open(port)) )
errx(EXIT_FAILURE, "cannot open MIDI port %s", port);
if ( port )
find_microx_on_port(port, &version, 0);
else {
char **ports, **p;
int found;
ports = midi_get_ports();
if ( (e = kmx_microx_identify(midi, &version)) < 0 )
errx(EXIT_FAILURE, "cannot identify microX device: %s",
kmx_microx_error(midi, e));
for ( p = ports, found = 0; *p != NULL; p++ ) {
if ( ! found )
found = find_microx_on_port(*p, &version, 1) != -1;
free(*p);
}
free(ports);
if ( ! found )
errx(EXIT_FAILURE, "no microX device found");
}
if ( mode == MODE_STATUS ) {
printf("%s: KORG microX synthesizer, version %02d.%02d.%02d.%02d\n",
port, version.major, version.minor, version.release, version.build);
printf("KORG microX synthesizer, version %02d.%02d.%02d.%02d\n",
version.major, version.minor, version.release, version.build);
if ( (e = kmx_microx_query_status(midi, &status)) < 0 )
errx(EXIT_FAILURE, "cannot query microX status: %s",
@ -263,31 +309,42 @@ main(int argc, char **argv)
print_microx_status(&status);
}
else if ( mode == MODE_DATA_DUMP ) {
unsigned char *program;
unsigned char *data;
int n;
program = xmalloc(type->size);
dump.load = 0;
if ( kmx_microx_parse_dump_request(param, &dump) == KMX_INVALID_QUERY )
errx(EXIT_FAILURE, "invalid dump request: %s", param);
if ( (n = kmx_microx_dump(midi, type->code, param, program,
type->size)) < 0 )
n = kmx_microx_get_dump_size(&dump);
data = xmalloc(n);
if ( (n = kmx_microx_dump(midi, &dump, data, n)) < 0 )
errx(EXIT_FAILURE, "cannot dump data: %s",
kmx_microx_error(midi, n));
fprinthd(stdout, program, n, 0);
fprinthd(stdout, data, n, 0);
}
else if ( mode == MODE_DATA_LOAD ) {
unsigned char *program;
unsigned char *data;
ssize_t n;
program = NULL;
n = freadhda(stdin, &program, NULL);
dump.load = 1;
if ( kmx_microx_parse_dump_request(param, &dump) == KMX_INVALID_QUERY )
errx(EXIT_FAILURE, "invalid load request: %s", param);
data = NULL;
n = freadhda(stdin, &data, NULL);
if ( n < 1 )
err(EXIT_FAILURE, "cannot read data");
if ( (n = kmx_microx_load(midi, type->code, param, program, n)) < 0 )
if ( (n = kmx_microx_load(midi, &dump, data, n)) < 0 )
errx(EXIT_FAILURE, "cannot load data: %s",
kmx_microx_error(midi, n));
}
else if ( mode == MODE_PROGRAM_LIST ) {
do_list_programs(midi);
}
return EXIT_SUCCESS;
}

567
src/microx.c

@ -16,6 +16,13 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
/** @file microx.c
* MicroX-specific functions.
*
* This module provides helper functions to deal with a Korg microX
* synthesizer.
*/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
@ -25,6 +32,26 @@
#include <microx.h>
#include <sysex.h>
/**
* Identify a connected microX device.
* This function check whether the device connected to the MIDI
* port is a Korg microX synthesizer.
*
* @param[in] midi The MIDI port.
* @param[out] version A pointer to a kmx_microx_version structure
* to be filled in with informations from the
* device; may be @c NULL.
*
* @return
* - @e KMX_OK if the port is connected to a device which
* successfully identified itself as a microX device;
* - @e KMX_IO_ERROR if an I/O error occured;
* - @e KMX_INVALID_REPLY if the device replied incorrectly:
* - @e KMX_NOT_KORG is the device did not identify itself as
* a Korg device;
* - @e KMX_NOT_MICROX is the device did not identify itself
* as a microX device.
*/
int
kmx_microx_identify(midi_io_t *midi,
struct kmx_microx_version *version)
@ -32,7 +59,7 @@ kmx_microx_identify(midi_io_t *midi,
int n;
midi_device_id_t devid;
if ( (n = sysex_identify(midi, &devid)) < 0 )
if ( (n = sysex_identify(midi, &devid)) == -1 )
return KMX_IO_ERROR;
else if ( n == 0 )
return KMX_INVALID_REPLY;
@ -50,9 +77,25 @@ kmx_microx_identify(midi_io_t *midi,
version->build = (devid.version >> 24) & 0xFF;
}
return 0;
return KMX_OK;
}
/**
* Get the microX current status.
* This function gets the current status of the connected microX
* synthesizer.
*
* @param[in] midi The MIDI port.
* @param[out] status A pointer to a kmx_microx_status structure
* to be filled in with the status data
* received from the microX; may be @c NULL.
*
* @return
* - @e KMX_OK if the query was successful;
* - @e KMX_IO_ERROR if an I/O error occured;
* - @e KMX_NOT_KORG if the reply did not come from a Korg device;
* - @e KMX_INVALID_REPLY if the device replied incorrectly.
*/
int
kmx_microx_query_status(midi_io_t *midi,
struct kmx_microx_status *s)
@ -67,7 +110,7 @@ kmx_microx_query_status(midi_io_t *midi,
0xF7 /* SysEx message end */ };
if ( (n = sysex_query(midi, query, sizeof(query),
reply, sizeof(reply))) < 0 )
reply, sizeof(reply))) == -1 )
return KMX_IO_ERROR;
/*
@ -160,9 +203,24 @@ kmx_microx_query_status(midi_io_t *midi,
s->ext_ctrl.current = reply[24];
}
return 0;
return KMX_OK;
}
/**
* Get an error message.
* This function translates an error code returned by other
* functions of this module to an error message.
*
* If the error happens to be a MIDI I/O error, this function
* calls the midi_error() function to get a meaningful
* message from the underlying MIDI API.
*
* @param[in] midi The MIDI port used to talk to the microX.
* @param[in] e The error code to lookup.
*
* @return
* A static string containing the user-readable error message.
*/
const char *
kmx_microx_error(midi_io_t *midi, int e)
{
@ -221,130 +279,375 @@ kmx_microx_error(midi_io_t *midi, int e)
return msg;
}
struct dump {
unsigned char query_code;
unsigned char reply_code;
size_t data_size;
static struct {
unsigned char query;
unsigned char reply;
} dump_codes[] = {
{ 0x1C, 0x4C },
{ 0x1D, 0x4D },
{ 0x18, 0x48 },
{ 0x0D, 0x52 },
{ 0x34, 0x69 },
{ 0x1E, 0x6E }
};
static struct dump dumps[] = {
{ 0x1C, 0x4C, MICROX_PROGRAM_SIZE },
{ 0x1D, 0x4D, MICROX_COMBINATION_SIZE },
{ 0x18, 0x48, MICROX_MULTI_SIZE },
{ 0x0D, 0x52, MICROX_DRUMKIT_SIZE },
{ 0x34, 0x69, MICROX_ARPEGGIO_SIZE },
{ 0x1E, 0x6E, MICROX_EXTSET_SIZE }
};
/**
* Parse a string as a positive integer.
* This function parses the given string as a positive integer
* value.
*
* @param[in] s The string to parse.
*
* @return
* - The positive integer value contained in @a s;
* - -1 if @a s could not be parsed as a positive integer value.
*/
static int
parse_int(const char *s)
{
int val;
val = 0;
while ( *s && val >= 0 ) {
if ( *s >= '0' && *s <= '9' )
val = val * 10 + *s - '0';
else
val = -1;
s += 1;
}
return val;
}
/**
* Parse a string as a dump/load request to the microX.
* This function translates a string to a formal representation
* of a dump or load parameter request to a microX device.
*
* If the request string is valid and successfully parsed, the
* function fills in a kmx_microx_dump structure which may then
* be used in a call to kmx_microx_dump() or kmx_microx_load().
*
* The request string should start with a single letter
* indicating the type of memory to dump or load: 'P' for programs,
* 'C' for combinations, 'M' for multis, 'D' for drumkits,
* 'A' for arpeggio patterns, or 'E' for external controllers sets.
* It must be followed by a single uppercase letter indicating the
* bank (only for programs and combinations), and up to three
* decimal digits indicating the slot number (counting from one).
*
* An asterisk may be used instead of the program number, in that
* case the microX will be instructed to dump all slots in the
* bank. For programs and combinations, an asterisk may also be
* used instead of the bank letter @e and the program number, in
* that case the microX will be instructed to dump all slots in
* all program or combination banks.
*
* For example:
*
* - @c "PA123" means "dump program #123 of bank A";
* - @c "M17" means "dump multi #17";
* - @c "CB*" means "dump all combinations in bank B";
* - @c "C*" means "dump all combinations in all banks";
* - @c "D*" means "dump all drumkits".
*
* @param[in] what The request string to parse.
* @param[out] The kmx_microx_dump structure to fill in.
*
* @return
* - @e KMX_OK if the dump request was successfully parsed.
* - @e KMX_INVALID_QUERY if the dump request is invalid.
*/
int
kmx_microx_parse_dump_request(const char *what, struct kmx_microx_dump *dump)
{
int val, ret;
char bank;
ret = KMX_OK;
switch ( *what++ ) {
case 'P':
dump->type = PROGRAM_DATA;
bank = *what++;
if ( bank == '*' )
dump->bank = KMX_MICROX_DUMP_ALL;
else if ( bank >= 'A' && bank <= 'A' + MICROX_N_PROG_BANK ) {
dump->bank = bank - 'A';
if ( *what == '*' )
dump->program = KMX_MICROX_DUMP_ALL;
else if ( (val = parse_int(what)) >= 1 && val <= MICROX_BANK_SIZE )
dump->program = val - 1;
else
ret = KMX_INVALID_QUERY;
}
else
ret = KMX_INVALID_QUERY;
break;
case 'C':
dump->type = COMBINATION_DATA;
bank = *what++;
if ( bank == '*' )
dump->bank = KMX_MICROX_DUMP_ALL;
else if ( bank >= 'A' && bank <= 'A' + MICROX_N_COMB_BANK ) {
dump->bank = bank - 'A';
if ( *what == '*' )
dump->program = KMX_MICROX_DUMP_ALL;
else if ( (val = parse_int(what)) >= 1 && val <= MICROX_BANK_SIZE )
dump->program = val - 1;
else
ret = KMX_INVALID_QUERY;
}
else
ret = KMX_INVALID_QUERY;
break;
case 'M':
dump->type = MULTI_DATA;
if ( *what == '*' )
dump->program = KMX_MICROX_DUMP_ALL;
else if ( (val = parse_int(what)) >= 1 && val <= MICROX_N_MULTI )
dump->program = val - 1;
else
ret = KMX_INVALID_QUERY;
break;
case 'D':
dump->type = DRUMKIT_DATA;
if ( *what == '*' )
dump->program = KMX_MICROX_DUMP_ALL;
else if ( (val = parse_int(what)) >= 1 && val <= MICROX_N_DRUMKIT )
dump->program = val - 1;
else
ret = KMX_INVALID_QUERY;
break;
case 'A':
dump->type = ARPEGGIO_DATA;
if ( *what == '*' )
dump->program = KMX_MICROX_DUMP_ALL;
else if ( (val = parse_int(what)) >= 1 && val <= MICROX_N_ARPEGGIO )
dump->program = val - 1;
else
ret = KMX_INVALID_QUERY;
break;
case 'E':
dump->type = EXTSET_DATA;
if ( *what == '*' )
dump->program = KMX_MICROX_DUMP_ALL;
else if ( (val = parse_int(what)) >= 1 && val <= MICROX_N_EXTSET )
dump->program = val - 1;
else
ret = KMX_INVALID_QUERY;
break;
default:
ret = KMX_INVALID_QUERY;
}
return ret;
}
/**
* Get the size of a microX data dump.
* Given a formal dump specification as returned by the
* kmx_microx_parse_dump_request() function, this function
* returns the expected size of the corresponding dump.
*
* @param[in] dump A kmx_microx_dump structure representing
* a dump request.
*
* @return
* The expected size of the specified data dump.
*/
size_t
kmx_microx_get_dump_size(struct kmx_microx_dump *dump)
{
size_t s;
switch ( dump->type ) {
case PROGRAM_DATA:
s = MICROX_PROGRAM_SIZE;
if ( dump->bank == KMX_MICROX_DUMP_ALL )
s *= MICROX_N_PROGRAM;
else if ( dump->program == KMX_MICROX_DUMP_ALL )
s *= MICROX_BANK_SIZE;
break;
case COMBINATION_DATA:
s = MICROX_COMBINATION_SIZE;
if ( dump->bank == KMX_MICROX_DUMP_ALL )
s *= MICROX_N_COMBINATION;
else if ( dump->program == KMX_MICROX_DUMP_ALL )
s *= MICROX_BANK_SIZE;
break;
case MULTI_DATA:
s = MICROX_MULTI_SIZE;
if ( dump->program == KMX_MICROX_DUMP_ALL )
s *= MICROX_N_MULTI;
break;
case DRUMKIT_DATA:
s = MICROX_DRUMKIT_SIZE;
if ( dump->program == KMX_MICROX_DUMP_ALL )
s *= MICROX_N_DRUMKIT;
break;
case ARPEGGIO_DATA:
s = MICROX_ARPEGGIO_SIZE;
if ( dump->program == KMX_MICROX_DUMP_ALL )
s *= MICROX_N_ARPEGGIO;
break;
case EXTSET_DATA:
s = MICROX_EXTSET_SIZE;
if ( dump->program == KMX_MICROX_DUMP_ALL )
s *= MICROX_N_EXTSET;
break;
}
return s;
}
/**
* Translate a dump request into actual MIDI bytes.
* Given a formal dump specification as returned by the
* kmx_microx_parse_dump_request() function, this function
* translates the request into the appropriate MIDI bytes
* that can be received by a microX synthesizer.
*
* @param[in] dump A kmx_microx_dump structure
* representing a dump request.
* @param[out] buffer A buffer to be filled in with the
* appropriate MIDI bytes; this buffer
* should be at least 4 four bytes long.
*
* @return
* - @e KMX_OK if the operation was successful;
* - @e KMX_INVALID_QUERY if an error occured.
*
* The current implementation is always successful.
*/
static int
get_dump_parameters(enum kmx_microx_data_type type,
const char *what,
unsigned char *buffer,
int load)
get_dump_parameters(struct kmx_microx_dump *dump,
unsigned char *buffer)
{
unsigned char program, bank;
buffer[0] = dump->load ? dump_codes[dump->type].reply
: dump_codes[dump->type].query;
switch ( type ) {
switch ( dump->type ) {
case PROGRAM_DATA:
if ( sscanf(what, "%c%03hhd", &bank, &program) != 2 )
return KMX_INVALID_QUERY;
if ( (bank < 'A' || bank > 'E') || program > 127 )
return KMX_INVALID_QUERY;
if ( load ) {
buffer[0] = 1;
buffer[1] = 0x20 + (bank - 'A');
buffer[2] = program;
}
else {
buffer[0] = 0x20 + (bank - 'A');
buffer[1] = program;
if ( dump->bank != KMX_MICROX_DUMP_ALL ) {
if ( dump->program == KMX_MICROX_DUMP_ALL )
buffer[1] = 0x10 + dump->bank;
else {
if ( dump->load ) {
buffer[1] = 1;
buffer[2] = 0x20 + dump->bank;
buffer[3] = dump->program;
}
else {
buffer[1] = 0x20 + dump->bank;
buffer[2] = dump->program;
}
}
}
break;
case COMBINATION_DATA:
if ( sscanf(what, "%c%03hhd", &bank, &program) != 2 )
return KMX_INVALID_QUERY;
if ( (bank < 'A' || bank > 'C') || program > 127 )
return KMX_INVALID_QUERY;
buffer[0] = 0x20 + (bank - 'A');
buffer[1] = program;
if ( dump->bank != KMX_MICROX_DUMP_ALL ) {
if ( dump->program == KMX_MICROX_DUMP_ALL )
buffer[1] = 0x10 + dump->bank;
else {
if ( dump->load ) {
buffer[2] = 0x20 + dump->bank;
buffer[3] = dump->program;
}
else {
buffer[1] = 0x20 + dump->bank;
buffer[2] = dump->program;
}
}
}
break;
case MULTI_DATA:
if ( sscanf(what, "%02hhd", &program) != 1 || program > 127 )
return KMX_INVALID_QUERY;
buffer[0] = 1;
buffer[1] = program;
if ( dump->program != KMX_MICROX_DUMP_ALL ) {
buffer[1] = 1;
buffer[2] = dump->program;
}
break;
case DRUMKIT_DATA:
if ( sscanf(what, "%03hhd", &program) != 1 || program > 39 )
return KMX_INVALID_QUERY;
buffer[0] = 1;
buffer[1] = program;
if ( dump->program != KMX_MICROX_DUMP_ALL ) {
buffer[1] = 1;
buffer[2] = dump->program;
}
break;
case ARPEGGIO_DATA:
if ( sscanf(what, "%03hhd", &program) != 1 || program > 250 )
return KMX_INVALID_QUERY;
buffer[0] = 1;
buffer[1] = program >> 7;
buffer[2] = program & 0x7F;
if ( dump->program != KMX_MICROX_DUMP_ALL ) {
buffer[1] = 0x40;
buffer[2] = dump->program >> 7;
buffer[3] = dump->program & 0x7F;
}
break;
case EXTSET_DATA:
if ( sscanf(what, "%02hhd", &program) != 1 || program > 63 )
return KMX_INVALID_QUERY;
buffer[0] = 1;
buffer[1] = program;
if ( dump->program != KMX_MICROX_DUMP_ALL ) {
buffer[1] = 1;
buffer[2] = dump->program;
}
break;
}
return 0;
return KMX_OK;
}
/**
* Read a microX data dump.
* This function reads data from a MIDI port and, if it corresponds
* to a SysEx message announcing a Korg microX data dump, translates
* it from the MIDI encoding used to transmit it.
*
* @param[in] midi The MIDI port to read from.
* @param[in] reply_code The expected function code of the SysEx
* message to read.
* @param[out] data The buffer where to store the dump.
* @param[in] len The size of the @a data buffer.
*
* @return
* - The count of data bytes read and stored in the @a data buffer;
* - @e KMX_IO_ERROR if an I/O error occured;
* - @e KMX_NOT_KORG if the reply did not come from a Korg device;
* - @e KMX_NOT_MICROX if the reply did not come from a microX device;
* - @e KMX_INVALID_REPLY if the device replied incorrectly.
*/
int
kmx_microx_dump(midi_io_t *midi,
enum kmx_microx_data_type type,
const char *what,
unsigned char *data,
size_t len)
kmx_microx_read_dump(midi_io_t *midi,
unsigned char reply_code,
unsigned char *data,
size_t len)
{
size_t n;
int i, j, k, m;
unsigned char reply[1024];
unsigned char query[] = { 0xF0, /* SysEx message begin */
0x42, /* KORG manufacturer ID */
0x30, /* Global channel */
0x7A, /* End of SysEx header */
0x00, /* Dump type */
0x00, /* Dump parameter 1 */
0x00, /* Dump parameter 2 */
0x00, /* Dump parameter 3 */
0xF7 /* SysEx message end */ };
if ( len < dumps[type].data_size )
return KMX_BUFFER_TOO_SMALL;
query[4] = dumps[type].query_code;
if ( get_dump_parameters(type, what, &(query[5]), 0) < 0 )
return KMX_INVALID_QUERY;
if ( (n = midi_write(midi, query, sizeof(query))) < 0 )
return KMX_IO_ERROR;
i = j = k = 0;
j = k = m = 0;
do {
if ( (n = sysex_read(midi, reply, sizeof(reply))) < 0 )
if ( (n = sysex_read(midi, reply, sizeof(reply))) == -1 )
return KMX_IO_ERROR;
i = 0;
if ( j == 0 ) {
/* Check reply header. */
if ( reply[0] != 0xF0 || reply[4] != dumps[type].reply_code )
if ( reply[0] != 0xF0 || reply[4] != reply_code )
return KMX_INVALID_REPLY;
if ( reply[1] != KORG_ID )
return KMX_NOT_KORG;
@ -384,10 +687,87 @@ kmx_microx_dump(midi_io_t *midi,
return j - 1; /* Remove SysEx terminating byte. */
}
/**
* Dump memory slot(s) from a microX device.
* This function sends a parameter dump request to the
* connected microX synthesizer and gets the dump data
* in reply.
*
* @param[in] midi The MIDI port.
* @param[in] dump A kmx_microx_dump structure representing
* the dump request.
* @param[out] data The buffer where to store the dump.
* @param[in] len The size of the @a data buffer.
*
* @return
* - The number of bytes read and stored in the @a data buffer;
* - @e KMX_IO_ERROR if an I/O error occured;
* - @e KMX_INVALID_QUERY if the dump query was invalid;
* - @e KMX_BUFFER_TOO_SMALL if the provided buffer was not large
* enough to hold the expected data dump;
* - @e KMX_NOT_KORG if the reply did not come from a Korg device;
* - @e KMX_NOT_MICROX if the reply did not come from a microX device;
* - @e KMX_INVALID_REPLY if the device replied incorrectly.
*/
int
kmx_microx_dump(midi_io_t *midi,
struct kmx_microx_dump *dump,
unsigned char *data,
size_t len)
{
size_t n;
unsigned char query[] = { 0xF0, /* SysEx message begin */
0x42, /* KORG manufacturer ID */
0x30, /* Global channel */
0x7A, /* End of SysEx header */
0x00, /* Dump type */
0x00, /* Dump parameter 1 */
0x00, /* Dump parameter 2 */
0x00, /* Dump parameter 3 */
0xF7 /* SysEx message end */ };
if ( get_dump_parameters(dump, &(query[4])) == KMX_INVALID_QUERY )
return KMX_INVALID_QUERY;
if ( len < kmx_microx_get_dump_size(dump) )
return KMX_BUFFER_TOO_SMALL;
if ( (n = midi_write(midi, query, sizeof(query))) == -1 )
return KMX_IO_ERROR;
return kmx_microx_read_dump(midi, dump_codes[dump->type].reply, data, len);
}
/**
* Load a memory slot to a microX device.
* This function loads the provided data to a memory slot of
* the microX synthesizer.
*
* @param[in] midi The MIDI port.
* @param[in] dump A kmx_microx_dump structure indicating the
* memory slot to load the data into.
* @param[in] data The data to load.
* @param[in] len The number of bytes from the @a data buffer
* to send to the device.
*
* @return
* - @e KMX_OK if the load operation completed successfully;
* - @e KMX_IO_ERROR if an I/O error occured;
* - @e KMX_INVALID_QUERY if the load query was invalid;
* - @e KMX_BUFFER_TOO_SMALL if the size of the @a data buffer did
* not match the expected size for the specified memory slot;
* - @e KMX_INVALID_REPLY if the device replied unexpectedly;
* - @e KMX_DEST_MEM_PROTECTED if the specified memory slot was
* write-protected;
* - @e KMX_DEST_PRG_MISSING if the specified memory slot did not
* exist;
* - @e KMX_MEM_OVERFLOW if a memory error occured on the device;
* - @e KMX_OTHER_ERROR if an unspecified error occured on the
* device.
*/
int
kmx_microx_load(midi_io_t *midi,
enum kmx_microx_data_type type,
const char *what,
struct kmx_microx_dump *dump,
unsigned char *data,
size_t len)
{
@ -403,14 +783,13 @@ kmx_microx_load(midi_io_t *midi,
0x00, /* Dump parameter 3 */ };
unsigned char reply[7];
if ( len < dumps[type].data_size )
if ( get_dump_parameters(dump, &(buffer[4])) == KMX_INVALID_QUERY )
return KMX_INVALID_QUERY;
if ( len < kmx_microx_get_dump_size(dump) )
return KMX_BUFFER_TOO_SMALL;
buffer[4] = dumps[type].reply_code;
if ( get_dump_parameters(type, what, &(buffer[5]), 1) < 0 )
return KMX_INVALID_QUERY;
n = 8;
m = status = 0;
while ( m < len && status == 0 ) {
@ -422,7 +801,7 @@ kmx_microx_load(midi_io_t *midi,
buffer[n++] = data[m++] & 0x7F;
if ( n == sizeof(buffer) ) {
if ( midi_write(midi, buffer, n) < 0 )
if ( midi_write(midi, buffer, n) == -1 )
status = KMX_IO_ERROR;
n = 0;
}

52
src/microx.h

@ -42,8 +42,11 @@
#define KMX_OTHER_ERROR KMX_ERROR_CODE - 40
/* Numbers of memories. */
#define MICROX_N_PROGRAM 640
#define MICROX_N_COMBINATION 384
#define MICROX_N_PROG_BANK 5
#define MICROX_N_COMB_BANK 3
#define MICROX_BANK_SIZE 128
#define MICROX_N_PROGRAM MICROX_N_PROG_BANK * MICROX_BANK_SIZE
#define MICROX_N_COMBINATION MICROX_N_COMB_BANK * MICROX_BANK_SIZE
#define MICROX_N_MULTI 128
#define MICROX_N_DRUMKIT 40
#define MICROX_N_ARPEGGIO 251
@ -151,6 +154,32 @@ struct kmx_microx_status
} ext_ctrl;
};
#define KMX_MICROX_DUMP_ALL -1
/** Represents a parameter dump/load request. */
struct kmx_microx_dump
{
/** The type of memory slot. */
enum kmx_microx_data_type type;
/** If non-zero, represents a load request. */
char load;
/** The bank letter.
* Represents the bank letter of a dump request
* (only for programs and combinations); if set to
* KMX_MICROX_DUMP_ALL, indicates to dump all
* programs or combinations in all banks. */
char bank;
/** The program number.
* Represents the program number (counting from
* zero); if set to KMX_MICROX_DUMP_ALL, indicates
* to dump all slots of the given type or of the
* given bank (for programs and combinations). */
short program;
};
#ifdef __cpluscplus
extern "C" {
#endif
@ -164,17 +193,28 @@ kmx_microx_query_status(midi_io_t *, struct kmx_microx_status *);
const char *
kmx_microx_error(midi_io_t *, int);
int
kmx_microx_parse_dump_request(const char *,
struct kmx_microx_dump *);
size_t
kmx_microx_get_dump_size(struct kmx_microx_dump *);
int
kmx_microx_read_dump(midi_io_t *,
unsigned char,
unsigned char *,
size_t);
int
kmx_microx_dump(midi_io_t *,
enum kmx_microx_data_type,
const char *,
struct kmx_microx_dump *,
unsigned char *,
size_t);
int
kmx_microx_load(midi_io_t *,
enum kmx_microx_data_type,
const char *,
struct kmx_microx_dump *,
unsigned char *,
size_t );

677
src/midi.c

@ -1,6 +1,6 @@
/*
* asysex - A SysEx Utility
* Copyright (C) 2012 Damien Goutte-Gattat
* 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
@ -16,51 +16,180 @@
* 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>
#include <xmem.h>
#if USE_ALSA_MIDI_API
/* Linux ALSA API */
#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
{
snd_rawmidi_t *in;
snd_rawmidi_t *out;
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
};
};
midi_io_t *
midi_open(const char *name)
/* Backend-specific functions. */
#ifdef HAVE_ALSA
static int
alsa_midi_close(midi_io_t *midi)
{
midi_io_t *midi;
snd_rawmidi_close(midi->alsa.in);
snd_rawmidi_close(midi->alsa.out);
assert(name != NULL);
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)) != 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;
midi = xmalloc(sizeof(*midi));
midi->in = midi->out = NULL;
midi->alsa.in = midi->alsa.out = NULL;
midi->error = midi->pos = midi->len = midi->status = 0;
if ( snd_rawmidi_open(&(midi->in), &(midi->out), name,
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->out, 0) < 0 ) {
else if ( snd_rawmidi_nonblock(midi->alsa.out, 0) < 0 ) {
midi_close(midi);
midi = NULL;
}
@ -68,166 +197,311 @@ midi_open(const char *name)
return midi;
}
int
midi_close(midi_io_t *midi)
static void
alsa_midi_get_ports(char ***ports, size_t *n, size_t *max)
{
assert(midi != NULL);
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 >= *max ) {
*max += 10;
*ports = xrealloc(*ports, *max);
}
(*ports)[(*n)++] = xstrdup(name);
}
snd_ctl_close(ctl);
}
}
}
snd_rawmidi_close(midi->in);
snd_rawmidi_close(midi->out);
#endif /* HAVE_ALSA */
#ifdef HAVE_OSS
static int
oss_midi_close(midi_io_t *midi)
{
close(midi->oss.fd);
free(midi);
return 0;
}
ssize_t
midi_write(midi_io_t *midi, unsigned char *buffer, size_t len)
static ssize_t
oss_midi_write(midi_io_t *midi, unsigned char *buffer, size_t len)
{
assert(midi != NULL);
assert(buffer != NULL);
ssize_t n;
/* Flush reading buffer. */
midi->pos = midi->len = 0;
midi->error = snd_rawmidi_write(midi->out, buffer, len);
return midi->error;
if ( (n = write(midi->oss.fd, buffer, len)) == -1 )
midi->error = errno;
else
midi->error = 0;
return n;
}
ssize_t
midi_read(midi_io_t *midi, unsigned char *buffer, size_t len)
static ssize_t
oss_midi_read(midi_io_t *midi, unsigned char *buffer, size_t len)
{
assert(midi != NULL);
assert(buffer != NULL);
ssize_t n;
if ( (n = read(midi->oss.fd, buffer, len)) == -1 )
midi->error = errno;
else
midi->error = 0;