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.
 
 
 

453 lines
15 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/>.
*/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <stdio.h>
#include <microx.h>
#include <sysex.h>
int
kmx_microx_identify(midi_io_t *midi,
struct kmx_microx_version *version)
{
int n;
midi_device_id_t devid;
if ( (n = sysex_identify(midi, &devid)) < 0 )
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;
}
return 0;
}
int
kmx_microx_query_status(midi_io_t *midi,
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 */ };
if ( (n = sysex_query(midi, query, sizeof(query),
reply, sizeof(reply))) < 0 )
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 0;
}
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;
}
struct dump {
unsigned char query_code;
unsigned char reply_code;
size_t data_size;
};
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 }
};
static int
get_dump_parameters(enum kmx_microx_data_type type,
const char *what,
unsigned char *buffer,
int load)
{
unsigned char program, bank;
switch ( 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;
}
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;
break;
case MULTI_DATA:
if ( sscanf(what, "%02hhd", &program) != 1 || program > 127 )
return KMX_INVALID_QUERY;
buffer[0] = 1;
buffer[1] = program;
break;
case DRUMKIT_DATA:
if ( sscanf(what, "%03hhd", &program) != 1 || program > 39 )
return KMX_INVALID_QUERY;
buffer[0] = 1;
buffer[1] = 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;
break;
case EXTSET_DATA:
if ( sscanf(what, "%02hhd", &program) != 1 || program > 63 )
return KMX_INVALID_QUERY;
buffer[0] = 1;
buffer[1] = program;
break;
}
return 0;
}
int
kmx_microx_read_dump(midi_io_t *midi,
unsigned char reply_code,
unsigned char *data,
size_t len)
{
size_t n;
unsigned char i, j, k ,m;
unsigned char reply[1024];
j = k = m = 0;
do {
if ( (n = sysex_read(midi, reply, sizeof(reply))) < 0 )
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. */
}
int
kmx_microx_dump(midi_io_t *midi,
enum kmx_microx_data_type type,
const char *what,
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 ( 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;
return kmx_microx_read_dump(midi, dumps[type].reply_code, data, len);
}
int
kmx_microx_load(midi_io_t *midi,
enum kmx_microx_data_type type,
const char *what,
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];
if ( len < dumps[type].data_size )
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 ) {
/* 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) < 0 )
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;
}