Files
mt32-pi-control/common_src/mt32-pi.c
T

642 lines
18 KiB
C

/*
* mt32-pi control program for DOS PCs
*
* Copyright (C) 2021 Andreas Zdziarstek
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include "midi_dev.h"
#include "getopt.h"
#include "delay.h"
#include "miniwift.h"
#ifndef PROGRAM_NAME
#define PROGRAM_NAME "MT32-PI.EXE"
#endif
typedef enum {
MODE_UNCHANGED,
MODE_MUNT,
MODE_FLUID
} mt32pi_mode_t;
typedef enum {
ROM_UNCHANGED,
ROM_OLD,
ROM_NEW,
ROM_CM32L
} mt32pi_romset_t;
static void str_to_sysex_disp_mt32(unsigned char *sysexbuf, const char *str);
static int str_to_sysex_disp_sc55(unsigned char *sysexbuf, const char *str);
static void bmp_to_sysex_disp_sc55(unsigned char *se_array, const char *fname, int negative);
static void strpic_to_sysex_disp_sc55(unsigned char *sysexbuf, const char *str, int negative);
static unsigned char roland_checksum(const unsigned char *buf, unsigned short len);
static void print_usage(void);
static unsigned char cmd_sysex[5] = {0xF0, 0x7D, 0x00, 0x00, 0xF7};
static unsigned char reboot_sysex[4] = {0xF0, 0x7D, 0x00, 0xF7};
static unsigned char gm_reset[6] = {0xF0, 0x7E, 0x7F, 0x09, 0x01, 0xF7};
static unsigned char gs_reset[11] = {0xF0, 0x41, 0x10, 0x42, 0x12, 0x40, 0x00, 0x7F, 0x00, 0x41, 0xF7};
static unsigned char mt32_reset[8] = {0xF0, 0x41, 0x10, 0x16, 0x12, 0x7F, 0x01, 0xF7};
static unsigned char custom_midi_buf[64];
static int custom_midi_len = 0;
static char sc55_bmp_fname[64] = {'\0'};
static char syx_fname[64] = {'\0'};
static unsigned char sysexbuf[74];
static char mt32_text[21] = {'\0'};
static char sc55_text[33] = {'\0'};
static char sc55_pictxt[9] = {'\0'};
int main(int argc, char *argv[]) {
// static linkage because of C89 and struct
// initialization for long_options[]
static int reboot_flag = 0,
mt32_rst_flag = 0,
gs_rst_flag = 0,
gm_rst_flag = 0,
pic_negative_flag = 0,
verbose = 0,
soundfont = -1;
static mt32pi_mode_t mode = MODE_UNCHANGED;
static mt32pi_romset_t romset = ROM_UNCHANGED;
#ifdef ARGV_NO_BASENAME
argv[0] = PROGRAM_NAME;
#endif
int c;
static struct option long_options[] =
{
{"verbose", no_argument, &verbose, 1},
{"reboot", no_argument, &reboot_flag, 1},
{"help", no_argument, 0, 'h'},
{"mt32", no_argument, 0, 'm'},
{"fluidsynth", no_argument, 0, 'g'},
{"soundfont", required_argument, 0, 's'},
{"mt32-reset", no_argument, &mt32_rst_flag, 1},
{"gm-reset", no_argument, &gm_rst_flag, 1},
{"gs-reset", no_argument, &gs_rst_flag, 1},
{"mt32-txt", required_argument, 0, 't'},
{"sc55-txt", required_argument, 0, 'T'},
{"sc55-bmp", required_argument, 0, 'P'},
{"sc55-btxt", required_argument, 0, 'X'},
{"negative", required_argument, 0, 'N'},
{"romset", required_argument, 0, 'b'},
{"midi", required_argument, 0, 'M'},
{"syx", required_argument, 0, 'Y'},
{0, 0, 0, 0}
};
char optstr[32] = "hs:t:T:b:mgrvM:P:NY:X:";
// The MIDI backend may add some more short options
// Be sure that strlen(optstr) remains <32
// Could also generate new string of arbitrary
// but don't really wanna mess with malloc/free
// for this bit
mididev_add_optstr(optstr);
opterr = 1;
if(argc == 1) {
print_usage();
#ifndef NO_ERROR_ON_HELP
return EXIT_FAILURE;
#else
return EXIT_SUCCESS;
#endif
}
while(1) {
c = getopt_long(argc, argv, optstr, long_options, NULL);
// End of options
if(c == -1)
break;
switch(c) {
case 0:
break;
case 's':
soundfont = (int)strtol(optarg, NULL, 10);
break;
case 't':
strncpy(mt32_text, optarg, 20);
break;
case 'T':
strncpy(sc55_text, optarg, 32);
break;
case 'b':
if(strcmp(optarg, "old")==0) {
romset = ROM_OLD;
} else if(strcmp(optarg, "new")==0) {
romset = ROM_NEW;
} else if(strcmp(optarg, "cm32l")==0) {
romset = ROM_CM32L;
} else {
fprintf(stderr, "%s is not a recognized romset.\n", optarg);
return EXIT_FAILURE;
}
break;
case 'm':
mode = MODE_MUNT;
break;
case 'g':
mode = MODE_FLUID;
break;
case 'r':
reboot_flag = 1;
break;
case 'M':
{
unsigned char *mptr = custom_midi_buf;
char *end;
while(1) {
errno = 0;
*mptr = (unsigned char)strtoul(optarg, &end, 16);
if(errno || end==optarg)
break;
custom_midi_len++;
mptr++;
if(mptr == custom_midi_buf+64 || *end == '\0')
break;
optarg = end;
}
}
break;
case 'P':
if(strlen(optarg) > 63) {
fprintf(stderr, "Sorry, the filename is too long!\n");
return EXIT_FAILURE;
}
strncpy(sc55_bmp_fname, optarg, 64);
break;
case 'X':
strncpy(sc55_pictxt, optarg, 8);
break;
case 'N':
pic_negative_flag = 1;
break;
case 'Y':
if(strlen(optarg) > 63) {
fprintf(stderr, "Sorry, the filename is too long!\n");
return EXIT_FAILURE;
}
strncpy(syx_fname, optarg, 64);
break;
case 'v':
verbose = 1;
break;
case ':':
case '?':
case 'h':
print_usage();
#ifndef NO_ERROR_ON_HELP
return EXIT_FAILURE;
#else
return EXIT_SUCCESS;
#endif
default:
if(mididev_parse_arg(c, optarg)<0) {
fprintf(stderr, "Error parsing argument \"%s\"\n", optarg);
return EXIT_FAILURE;
}
}
}
// Reset/init the MIDI interface
if(mididev_init() == -1) {
fprintf(stderr, "Error initializing midi interface.\n");
return EXIT_FAILURE;
}
// -r/--reboot
if(reboot_flag) {
if(verbose)
fprintf(stderr, "Rebooting MT32-PI and waiting 5 seconds.\n");
mididev_send_bytes(reboot_sysex, 4);
delay_ms(5000);
}
// -m/--mt32 and -g/--fluidsynth
if(mode==MODE_MUNT) {
if(verbose)
fprintf(stderr, "Switching to MT-32 mode.\n");
cmd_sysex[2] = 0x03;
cmd_sysex[3] = 0x00;
mididev_send_bytes(cmd_sysex, 5);
} else if(mode==MODE_FLUID) {
if(verbose)
fprintf(stderr, "Switching to FluidSynth mode.\n");
cmd_sysex[2] = 0x03;
cmd_sysex[3] = 0x01;
mididev_send_bytes(cmd_sysex, 5);
}
// -b/--romset
if(romset==ROM_OLD) {
if(verbose)
fprintf(stderr, "Switching to old MT-32 romset.\n");
cmd_sysex[2] = 0x01;
cmd_sysex[3] = 0x00;
mididev_send_bytes(cmd_sysex, 5);
} else if(romset==ROM_NEW) {
if(verbose)
fprintf(stderr, "Switching to new MT-32 romset.\n");
cmd_sysex[2] = 0x01;
cmd_sysex[3] = 0x01;
mididev_send_bytes(cmd_sysex, 5);
} else if(romset==ROM_CM32L) {
if(verbose)
fprintf(stderr, "Switching to CM-32L romset.\n");
cmd_sysex[2] = 0x01;
cmd_sysex[3] = 0x02;
mididev_send_bytes(cmd_sysex, 5);
}
// -s/--soundfont
if(soundfont > -1) {
if(verbose)
fprintf(stderr, "Switching SoundFont to #%d.\n", soundfont);
cmd_sysex[2] = 0x02;
cmd_sysex[3] = soundfont;
mididev_send_bytes(cmd_sysex, 5);
}
// --mt32-reset
if(mt32_rst_flag) {
if(verbose)
fprintf(stderr, "Sending MT-32 reset and waiting 100ms.\n");
mididev_send_bytes(mt32_reset, 8);
delay_ms(100);
}
// --gm-reset
if(gm_rst_flag) {
if(verbose)
fprintf(stderr, "Sending GM reset and waiting 100ms.\n");
mididev_send_bytes(gm_reset, 6);
delay_ms(100);
}
// --gs-reset
if(gs_rst_flag) {
if(verbose)
fprintf(stderr, "Sending GS reset and waiting 100ms.\n");
mididev_send_bytes(gs_reset, 11);
delay_ms(100);
}
// -t/--mt32-txt
if(mt32_text[0] != '\0') {
if(verbose)
fprintf(stderr, "Displaying \"%s\" (MT-32 mode).\n", mt32_text);
str_to_sysex_disp_mt32(sysexbuf, mt32_text);
mididev_send_bytes(sysexbuf, 30);
}
// -T/--sc55-txt
if(sc55_text[0] != '\0') {
int se_len;
if(verbose)
fprintf(stderr, "Displaying \"%s\" (SC-55 mode).\n", sc55_text);
se_len = str_to_sysex_disp_sc55(sysexbuf, sc55_text);
mididev_send_bytes(sysexbuf, se_len);
}
// -M/--midi "C0 01 C0 DE"
if(custom_midi_len > 0) {
if(verbose)
fprintf(stderr, "Sending %d custom MIDI bytes.\n", custom_midi_len);
mididev_send_bytes(custom_midi_buf, custom_midi_len);
}
// -P/--sc55-bmp FILE.BMP and -N
if(sc55_bmp_fname[0] != '\0') {
if(verbose)
fprintf(stderr, "Displaying %s.\n", sc55_bmp_fname);
bmp_to_sysex_disp_sc55(sysexbuf, sc55_bmp_fname, pic_negative_flag);
mididev_send_bytes(sysexbuf, 74);
}
// -X/--sc55-btxt
if(sc55_pictxt[0] != '\0') {
if(verbose)
fprintf(stderr, "Displaying \"%s\" using bitmap mode.\n", sc55_pictxt);
strpic_to_sysex_disp_sc55(sysexbuf, sc55_pictxt, pic_negative_flag);
mididev_send_bytes(sysexbuf, 74);
}
// -Y/--syx FILE.SYX
if(syx_fname[0] != '\0') {
unsigned char fbuf[512];
FILE *fh;
if(verbose)
fprintf(stderr, "Sending %s.\n", syx_fname);
fh = fopen(syx_fname, "rb");
if(fh == NULL) {
fprintf(stderr, "ERROR: Can't open %s.\n", syx_fname);
mididev_deinit();
return EXIT_FAILURE;
}
// go through file
while(1) {
int i, found_start, found_end, start_index, end_index;
int n;
long int read_start = ftell(fh);
n = fread(fbuf, 1, 512, fh);
found_start = 0;
found_end = 0;
// search for next sysex in buffer
start_index = 0;
end_index = 0;
for(i=0; i<n; i++) {
if(found_start && found_end) break;
if(fbuf[i] == 0xF0) {
if(found_start) {
fprintf(stderr, "ERROR: Malformed SysEx in %s.\n", syx_fname);
mididev_deinit();
return EXIT_FAILURE;
}
found_start = 1;
start_index = i;
}
if(found_start && fbuf[i] == 0xF7) {
if(found_end) {
fprintf(stderr, "ERROR: Malformed SysEx in %s.\n", syx_fname);
mididev_deinit();
return EXIT_FAILURE;
}
found_end = 1;
end_index = i;
}
}
if(!found_start || !found_end) {
fprintf(stderr, "ERROR: Malformed or too long SysEx in %s.\n", syx_fname);
mididev_deinit();
return EXIT_FAILURE;
}
mididev_send_bytes(fbuf+start_index, end_index - start_index+1);
// Wait grace period
delay_ms(100);
if(read_start + end_index+1 == ftell(fh))
break;
fseek(fh, read_start + end_index+1, SEEK_SET);
}
fclose(fh);
}
mididev_deinit();
return EXIT_SUCCESS;
}
static void str_to_sysex_disp_mt32(unsigned char *sysexbuf, const char *str) {
const unsigned char prefix[6] = { 0xF0, 0x41, 0x10, 0x16, 0x12, 0x20 };
unsigned char *ptr;
memset(sysexbuf, '\0', 30);
/* Copy SysEx start and display cmd into msg */
memcpy(sysexbuf, prefix, 6);
/* Copy string into msg */
ptr = sysexbuf+8;
while(*str != '\0') {
*ptr++ = *(unsigned char*)str++;
}
/* Generate Roland checksum */
sysexbuf[28] = roland_checksum(sysexbuf+5, 23);
/* SysEx end */
sysexbuf[29] = 0xF7;
}
static int str_to_sysex_disp_sc55(unsigned char *sysexbuf, const char *str) {
const unsigned char prefix[6] = { 0xF0, 0x41, 0x10, 0x45, 0x12, 0x10 };
unsigned char *ptr;
unsigned int len;
memset(sysexbuf, '\0', 42);
/* Copy SysEx start and display cmd into msg */
memcpy(sysexbuf, prefix, 6);
/* Copy string into msg */
ptr = sysexbuf+8;
len = 0;
while(*str != '\0') {
*ptr++ = *(unsigned char*)str++;
len++;
}
/* Generate Roland checksum */
sysexbuf[8+len] = roland_checksum(sysexbuf+5, len+3);
/* SysEx end */
sysexbuf[9+len] = 0xF7;
return(len+10);
}
static void bmp_to_sysex_disp_sc55(unsigned char *sysexbuf, const char *fname, int negative) {
const unsigned char prefix[7] = { 0xF0, 0x41, 0x10, 0x45, 0x12, 0x10, 0x01 };
int i;
unsigned char fbuf[64];
FILE *fh;
int flen;
unsigned char pix_offset;
memset(sysexbuf, '\0', 74);
/* Copy SysEx start and display cmd into msg */
memcpy(sysexbuf, prefix, 7);
fh = fopen(fname, "rb");
if(fh == NULL) {
fprintf(stderr, "ERROR: Can't open %s.\n", fname);
abort();
}
// check if this is a BMP
if(!fread(fbuf, 1, 2, fh)) {
fprintf(stderr, "Error reading %s.\n", sc55_bmp_fname);
fclose(fh);
abort();
}
if(!(fbuf[0]=='B' && fbuf[1]=='M')) {
fprintf(stderr, "ERROR: %s is not a valid BMP file.\n", sc55_bmp_fname);
fclose(fh);
abort();
}
// check offset of pixel array
fseek(fh, 0xA, SEEK_SET);
if(!fread(&pix_offset, 1, 1, fh)) {
fprintf(stderr, "Error reading %s.\n", sc55_bmp_fname);
fclose(fh);
abort();
}
fseek(fh, 0, SEEK_END);
flen = ftell(fh);
if(flen - pix_offset < 64) {
fprintf(stderr, "ERROR: %s seems to be too small in size.\n", sc55_bmp_fname);
fclose(fh);
abort();
}
// read pixel array
fseek(fh, pix_offset, SEEK_SET);
if(!fread(fbuf, 1, 64, fh)) {
fprintf(stderr, "Error reading %s.\n", sc55_bmp_fname);
fclose(fh);
abort();
}
fclose(fh);
// Perform bit-manipulation magic to
// build the SysEx
for(i=0; i<16; i++) {
unsigned char *bmprow, *sysexrow;
bmprow = &fbuf[i*4];
sysexrow = &sysexbuf[(15-i)+8];
sysexrow[0] = (bmprow[0]>>3) & 0x1F;
sysexrow[16] = ((bmprow[0]&0x07)<<2) | ((bmprow[1]>>6)&0x03);
sysexrow[32] = (bmprow[1]>>1)&0x1F;
sysexrow[48] = (bmprow[1] & 0x01)<<4;
if(negative) {
sysexrow[0] = (~sysexrow[0])&0x1F;
sysexrow[16] = (~sysexrow[16])&0x1F;
sysexrow[32] = (~sysexrow[32])&0x1F;
sysexrow[48] = (~sysexrow[48])&0x10;
}
}
sysexbuf[72] = roland_checksum(sysexbuf+5, 67);
sysexbuf[73] = 0xF7;
}
static void strpic_to_sysex_disp_sc55(unsigned char *sysexbuf, const char *str, int negative) {
const unsigned char prefix[7] = { 0xF0, 0x41, 0x10, 0x45, 0x12, 0x10, 0x01 };
int i, slen;
unsigned char picbuf[32];
memset(sysexbuf, '\0', 74);
memset(picbuf, 0, sizeof(picbuf));
/* Copy SysEx start and display cmd into msg */
memcpy(sysexbuf, prefix, 7);
slen = strlen(str);
if(slen > 8) {
slen = 8;
}
for(i=0; i<slen; i++) {
unsigned char dest_lshift, src_rshift;
unsigned char c, *destptr, j;
if(str[i] < 32 || str[i] > 127) {
continue;
}
c = str[i] - 32;
src_rshift = (c & 1) ? 0 : 4;
dest_lshift = (i & 1) ? 0 : 4;
destptr = (i<4) ? &picbuf[i/2] : &picbuf[(i-4)/2 + 16];
for(j=0; j<8; j++) {
destptr[j*2] |= ( (miniwi_data[c/2][j]>>src_rshift ) & 0x0F ) <<dest_lshift;
}
}
// Perform bit-manipulation magic to
// build the SysEx
for(i=0; i<16; i++) {
unsigned char *bmprow, *sysexrow;
bmprow = &picbuf[i*2];
sysexrow = &sysexbuf[i+8];
sysexrow[0] = (bmprow[0]>>3) & 0x1F;
sysexrow[16] = ((bmprow[0]&0x07)<<2) | ((bmprow[1]>>6)&0x03);
sysexrow[32] = (bmprow[1]>>1)&0x1F;
sysexrow[48] = (bmprow[1] & 0x01)<<4;
if(negative) {
sysexrow[0] = (~sysexrow[0])&0x1F;
sysexrow[16] = (~sysexrow[16])&0x1F;
sysexrow[32] = (~sysexrow[32])&0x1F;
sysexrow[48] = (~sysexrow[48])&0x10;
}
}
sysexbuf[72] = roland_checksum(sysexbuf+5, 67);
sysexbuf[73] = 0xF7;
}
static unsigned char roland_checksum(const unsigned char *buf, unsigned short len) {
const unsigned char *ptr;
unsigned short ck_acc = 0;
for(ptr=buf; ptr<buf+len; ptr++) {
ck_acc = (ck_acc + (unsigned short)*ptr) & 0x7F;
}
return(0x80 - ck_acc);
}
static void print_usage(void) {
printf("USAGE: " PROGRAM_NAME " [OPTIONS]\n");
printf("OPTIONS:\n");
printf( "--help: Print this info.\n"
"-v/--verbose: Be verbose about what is going on.\n"
"-r/--reboot: Reboot the Pi. Will block for a few secs to give it time.\n");
mididev_print_usage();
#ifdef ATARI_ST_QUOTING
printf("-m/--mt32: Switch mt32-pi to MT-32 mode.\n"
"-g/--fluidsynth: Switch mt32-pi to FluidSynth mode.\n"
"-b/--romset [old, new, cm32l]: Switch MT-32 romset.\n"
"-s/--soundfont [NUMBER]: Set FluidSynth SoundFont.\n"
"--mt32-reset: Send an MT-32 reset SysEx message.\n"
"--gm-reset: Send a GM reset SysEx message.\n"
"--gs-reset: Send a GS reset SysEx message.\n"
"\'-tSome text\': Send an MT-32 text display SysEx.\n"
"\'-TSome text\': Send an SC-55 text display SysEx.\n"
"-P/--sc55-bmp FILE.BMP: Display a 16x16 1bpp BMP on the screen. (SC-55 SysEx)\n"
"\'-X/--sc55-btxt SomeText\': Display a string on the screen as a Bitmap. (SC-55)\n"
"-N/--negative: Reverse color. Use with \'-P/--sc55-bmp\' or \'-X/--sc55-btxt\'.\n"
"\'-M C0 01 C0 DE\': Send a list of custom MIDI bytes.\n"
"NOTE: If you use quotes in arguments, put those before unquoted ones.\n" );
#else
printf("-m/--mt32: Switch mt32-pi to MT-32 mode.\n"
"-g/--fluidsynth: Switch mt32-pi to FluidSynth mode.\n"
"-b/--romset [old, new, cm32l]: Switch MT-32 romset.\n"
"-s/--soundfont [NUMBER]: Set FluidSynth SoundFont.\n"
"--mt32-reset: Send an MT-32 reset SysEx message.\n"
"--gm-reset: Send a GM reset SysEx message.\n"
"--gs-reset: Send a GS reset SysEx message.\n"
"-t/--mt32-txt \"Some text\": Send an MT-32 text display SysEx.\n"
"-T/--sc55-txt \"Some text\": Send an SC-55 text display SysEx.\n"
"-P/--sc55-bmp FILE.BMP: Display a 16x16 1bpp BMP on the screen. (SC-55 SysEx)\n"
"-X/--sc55-btxt \"SomeText\": Display a string on the screen as a Bitmap. (SC-55)\n"
"-N/--negative: Reverse color. Use with \'-P/--sc55-bmp\' or \'-X/--sc55-btxt\'.\n"
"-M/--midi \"C0 01 C0 DE\": Send a list of custom MIDI bytes.\n" );
#endif
}