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.
 
 
 

847 lines
27 KiB

/*
* kmxtool - KORG microX 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 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
#include <stdio.h>
#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.
* @param[out] global A pointer to an integer to receive the
* global MIDI channel used by the microX;
* 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,
unsigned int *global)
{
int n;
midi_device_id_t devid;
if ( (n = sysex_identify(midi, &devid)) == -1 )
return KMX_IO_ERROR;
else if ( n == 0 )
return KMX_INVALID_REPLY;
if ( devid.manufacturer != KORG_ID )
return KMX_NOT_KORG;
if ( devid.family != MICROX_ID || devid.model != MICROX_SUB_ID )
return KMX_NOT_MICROX;
if ( version ) {
version->major = devid.version & 0xFF;
version->minor = (devid.version >> 8) & 0xFF;
version->release = (devid.version >> 16) & 0xFF;
version->build = (devid.version >> 24) & 0xFF;
}
if ( global )
*global = devid.global_channel;
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[int] chan The global MIDI channel.
* @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,
unsigned int chan,
struct kmx_microx_status *s)
{
ssize_t n;
unsigned char reply[64];
unsigned char query[] = { 0xF0, /* SysEx message begin */
0x42, /* KORG manufacturer ID */
0x30, /* SysEx channel (1) */
0x7A, /* End of SysEx header */
0x12, /* Mode and state request */
0xF7 /* SysEx message end */ };
fprintf(stderr, "querying status on channel %d\n", chan);
query[2] |= chan & 0xF;
if ( (n = sysex_query(midi, query, sizeof(query),
reply, sizeof(reply))) == -1 )
return KMX_IO_ERROR;
/*
* Expected Mode and state Reply (27 bytes):
* Byte # Value Meaning
* 0 0xF0 SysEx message begin
* 1 0x42 KORG manufacturer ID
* 2 0x3n Global channel (n = 0x0 ~ 0xF)
* 3 0x7A End of SysEx header
* 4 0x42 Mode and state reply
* 5 0xnn Current mode (one of kmx_microx_mode)
* 6 0x06 Status of Global mode
* 7 0xnn Global mode setting data 1
* bit 0: Local Control Off/On
* bits 1,2: MIDI Clock (one of kmx_microx_clock)
* bit 3: (Reserved)
* bit 4: Bank MAP KORG/GM
* bit 5: Power On Mode Reset/Memorize
* bit 6: Receive Ext.Realtime Command Off/On
* 8 0xnn Global mode setting data 2
* bit 0: Enable Program Change Off/On
* bit 1: Enable Bank Change Off/On
* bit 2: Enable Combination Change Off/On
* bit 3: Enable Aftertouch Off/On
* bit 4: Enable Control Change Off/On
* bits 5,6: Note Filter All/Even/Odd
* 9 0xnn Global mode setting data 3 (Memory Protection)
* bit 0: Program
* bit 1: Combination
* bit 2: Multi
* bit 3: Drums
* bit 4: ArpeggioPattern
* bit 5: Ext.Control
* 10 0x02 Status of Program mode
* 11 0x0n Current Bank (n = 0x0 ~ 0x4, bank A ~ E)
* 12 0xnn Current Program (n = 0x0 ~ 0x7F)
* 13 0x00 (Reserved)
* 14 0x00 Status of Combination mode
* 15 0x0n Current Bank (n = 0x0 ~ 0x2, bank A ~ C)
* 16 0xnn Current Combination (n = 0x0 ~ 0x7F)
* 17 0x00 (Reserved)
* 18 0x04 Status of Multi mode
* 19 0x00 (Reserved)
* 20 0xnn Current Multi (n = 0x0 ~ 0x7F)
* 21 0x0n Control Track (n = 0x0 ~ 0xF, track 1 ~ 16)
* 22 0x21 Status of Ext.Controller
* 23 0x0n Ext.Controller Off/On
* 24 0xnn Current Controller (n = 0x0 ~ 0x3F)
* 25 0x00 (Reserved)
* 26 0xF7 SysEx message end
*/
if ( n != 27 || reply[4] != 0x42 )
return KMX_INVALID_REPLY;
if ( reply[1] != KORG_ID )
return KMX_NOT_KORG;
if ( s ) {
s->global_channel = reply[2] & 0xF;
s->active_mode = reply[5];
s->global.local_control = reply[7] & 0x1;
s->global.clock_source = reply[7] >> 1 & 0x3;
s->global.bank_map = reply[7] >> 4 & 0x1;
s->global.memorize_mode = reply[7] >> 5 & 0x1;
s->global.rec_ext_rt_cmd = reply[7] >> 6 & 0x1;
s->global.prgm_change = reply[8] & 0x1;
s->global.bank_change = reply[8] >> 1 & 0x1;
s->global.comb_change = reply[8] >> 2 & 0x1;
s->global.aftertouch = reply[8] >> 3 & 0x1;
s->global.ctrl_change = reply[8] >> 4 & 0x1;
s->global.note_filter = reply[8] >> 5 & 0x3;
s->global.protected_memory.program = reply[9] & 0x1;
s->global.protected_memory.bank = reply[9] >> 1 & 0x1;
s->global.protected_memory.multi = reply[9] >> 2 & 0x1;
s->global.protected_memory.drum = reply[9] >> 3 & 0x1;
s->global.protected_memory.arpeggio = reply[9] >> 4 & 0x1;
s->global.protected_memory.ext_ctrl = reply[9] >> 5 & 0x1;
s->program.current_bank = reply[11];
s->program.current_program = reply[12];
s->combination.current_bank = reply[15];
s->combination.current_combination = reply[16];
s->multi.current_multi = reply[20];
s->multi.control_track = reply[21];
s->ext_ctrl.enabled = reply[23];
s->ext_ctrl.current = reply[24];
}
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)
{
const char *msg;
switch ( e ) {
case KMX_IO_ERROR:
msg = midi_error(midi);
break;
case KMX_INVALID_REPLY:
msg = "invalid reply";
break;
case KMX_NOT_KORG:
msg = "not a KORG device";
break;
case KMX_NOT_MICROX:
msg = "not a microX device";
break;
case KMX_INVALID_QUERY:
msg = "invalid query";
break;
case KMX_BUFFER_TOO_SMALL:
msg = "output buffer too small";
break;
case KMX_DEST_MEM_PROTECTED:
msg = "destination memory is write-protected";
break;
case KMX_DEST_PRG_MISSING:
msg = "destination bank/program/parameter does not exist";
break;
case KMX_WRONG_MODE:
msg = "the mode is wrong";
break;
case KMX_MEM_OVERFLOW:
msg = "memory overflow";
break;
case KMX_OTHER_ERROR:
msg = "another error type";
break;
default: /* Should not happen. */
msg = "unknown error";
break;
}
return msg;
}
static struct {
unsigned char query;
unsigned char reply;
} dump_codes[] = {
{ 0x1C, 0x4C },
{ 0x1D, 0x4D },
{ 0x18, 0x48 },
{ 0x0D, 0x52 },
{ 0x34, 0x69 },
{ 0x1E, 0x6E }
};
/**
* 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;
default: /* Should never happen. */
s = 0;
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(struct kmx_microx_dump *dump,
unsigned char *buffer)
{
buffer[0] = dump->load ? dump_codes[dump->type].reply
: dump_codes[dump->type].query;
switch ( dump->type ) {
case PROGRAM_DATA:
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 ( 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 ( dump->program != KMX_MICROX_DUMP_ALL ) {
buffer[1] = 1;
buffer[2] = dump->program;
}
break;
case DRUMKIT_DATA:
if ( dump->program != KMX_MICROX_DUMP_ALL ) {
buffer[1] = 1;
buffer[2] = dump->program;
}
break;
case ARPEGGIO_DATA:
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 ( dump->program != KMX_MICROX_DUMP_ALL ) {
buffer[1] = 1;
buffer[2] = dump->program;
}
break;
}
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_read_dump(midi_io_t *midi,
unsigned char reply_code,
unsigned char *data,
size_t len)
{
ssize_t n;
unsigned i, j, k, m;
unsigned char reply[1024];
j = k = m = 0;
do {
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] != reply_code )
return KMX_INVALID_REPLY;
if ( reply[1] != KORG_ID )
return KMX_NOT_KORG;
if ( reply[3] != MICROX_ID )
return KMX_NOT_MICROX;
/* Skip reply header. */
i = 8;
}
/*
* Convert data back to the microX internal representation
* and copy it to the caller's buffer. When transmitting its
* data, the microX converts a group of seven 8-bits bytes
* into eight MIDI bytes, where the first byte is used to
* encode the value of the 8th bit of each following byte.
*
* For example, consider the following byte sequence in the
* transmitted MIDI data (all numbers hexadecimal):
*
* 20 00 00 00 06 0e 16 00
*
* The first byte has the 6th bit set. It means that in the
* microX internal representation, the sixth byte of that
* group has its 8th bit set; so the real value of that byte
* is not 16, as transmitted, but 96.
*/
for ( ; i < n; i++, k++ ) {
if ( k % 8 == 0 )
for ( m = 0; m < 7; m++ )
data[j+m] = (reply[i] >> m) << 7;
else
data[j++] += reply[i];
}
} while ( reply[n-1] != 0xF7 );
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] chan The global MIDI channel.
* @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,
unsigned int chan,
struct kmx_microx_dump *dump,
unsigned char *data,
size_t len)
{
ssize_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 */ };
query[2] |= chan & 0xF;
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] chan The global MIDI channel.
* @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,
unsigned int chan,
struct kmx_microx_dump *dump,
unsigned char *data,
size_t len)
{
size_t n, m;
int status;
unsigned char buffer[256] = { 0xF0, /* Begin SysEx message */
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 */ };
unsigned char reply[7];
buffer[2] |= chan & 0xF;
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;
n = 8;
m = status = 0;
while ( m < len && status == 0 ) {
/* Convert data from the microX internal representation
* to MIDI format. See comment in kmx_micro_dump above. */
if ( m % 7 == 0 )
buffer[n++] = 0;
buffer[n - (m % 7) - 1] += (data[m] & 0x80) >> (7 - m % 7);
buffer[n++] = data[m++] & 0x7F;
if ( n == sizeof(buffer) ) {
if ( midi_write(midi, buffer, n) == -1 )
status = KMX_IO_ERROR;
n = 0;
}
}
buffer[n++] = 0xF7; /* SysEx message end */
if ( (n = sysex_query(midi, buffer, n, reply, sizeof(reply))) < 6 )
status = KMX_IO_ERROR;
if ( reply[0] != 0xF0 || reply[1] != KORG_ID || reply[3] != 0x7A )
status = KMX_INVALID_REPLY;
else if ( reply[4] == 0x24 ) /* DATA LOAD ERROR (NAC) */
status = KMX_ERROR_CODE - reply[5];
else if ( reply[4] == 0x23 ) /* DATA LOAD COMPLETED (ACK) */
status = KMX_OK;
return status;
}