/*
**  Program:  JVINFO.C
**  Author:   Greg Youngdahl
**
**	This program will read Bulk dumps from JV-80 family synthesizers
**  (including JV-80, JV-880, JV-90, and JV-1000) in .SYX format and display
**  a summary of the messages it finds there.  Hopefully this will be useful
**  in determining your ability to make use of such a file, or perhaps in
**  cataloging the contents of various bulk dumps you may be saving on disk.
**
**	For example here is some sample information that will be produced by
**  the program.  Each patch found in the dump will be displayed like this
**  (data taken from the rndybrwn.syx file):
**
**  Internal Patch 5:
**  	Name: ARAMATHEA   
**  	Tone 1: ON  PCM Wave   2
**  	Tone 2: ON  INT Wave  10
**  	Tone 3: ON  INT Wave  10
**  	Tone 4: OFF INT Wave  37
**
**
**  Each performance will be displayed like this:
**
**  Internal Performance 10:
**  	Name: Jazz Combo  
**  	Part 1: patch A21  MIDI channel 1
**  	Part 2: patch A52  MIDI channel 2
**  	Part 3: patch B40  MIDI channel 3
**  	Part 4: patch B25  MIDI channel 4
**  	Part 5: patch B39  MIDI channel 5
**  	Part 6: patch A01  MIDI channel 6
**  	Part 7: patch A45  MIDI channel 7
**  	Part 8: patch A01  MIDI channel 10
**
**
**  The drum kit will be displayed as follows (excerpted from the 61 lines it
**  will actually display, or 76 lines from a '90 dump):
**
**  Internal Rhythm Setup:
**	Key 36  (C 2)  ON  INT Wave  90
**	Key 37  (C#2)  ON  INT Wave 102
**	Key 38  (D 2)  ON  INT Wave  85
**	Key 39  (D#2)  ON  INT Wave 103
**	Key 40  (E 2)  ON  INT Wave  87
**	[ ... ]
**
**
**	The utility will read any JV-80 family .SYX file and display whatever
**  it finds in there.  This includes the full dump files as well as files
**  that contain a 64 patch bank, or even the single patch files that we have
**  been exchanging over the list (several of which are on Wagner).  If the
**  file has data that came from a DATA CARD the "Internal" above will read
**  "Card", and if the data is from the Temporary areas of the JV (Temporary
**  Performance or Temporary Patch) it will be labeled appropriately as well.
**
**
**
**  To compile with Microsoft C/C++ 7.0 issue the following command:
**
**	CL /Za jvinfo.c
**
**  For other compilers be sure to compile for __STDC__ if possible (the
**  /Za option to CL enables this).  If you compiler isn't __STDC__ then
**  be sure that reading and writing of binary files requires only the "r"
**  and "w" mode strings to the fopen() call as opposed to "rb" and "wb".
**  Otherwise be sure and make the necessary adjustments to the code where
**  the fopen() call is made.
*/

#include <stdio.h>


/*  The following structure is used for each individual SYSEX message
**  found in the file.  It is allocated (with malloc()) and populated by the
**  get_syx_message() function when a Start Of SYSEX byte (0xF0) is found
**  and the get_syx_message() function returns a pointer to the allocated
**  memory.  The buffer pointed to by msg_bytes is also allocated using
**  malloc().  It is the responsability of the programmer to keep track of
**  and free() these areas as required.  The msg_bytes field will be NULL
**  if no message data was found, or if no memory was available.  If the
**  get_syx_message() function returns NULL then no SYSEX message was found
**  in the file, or no memory was available.
*/
struct syx_msg
{
	int size;
	int unit_num;
	char adr[ 4 ];
	char *msg_bytes;
	int errors;
};

/*  Bits for errors field of syx_msg
*/
#define E_NOT_ROLAND	0x01
#define E_BAD_DEVICE	0x02
#define E_WRONG_MODEL	0x04
#define E_BAD_COMMAND	0x08
#define E_BAD_CHECKSUM	0x10
#define E_EOF		0x20
#define E_SHORT_MSG	0x40
#define E_LONG_MSG	0x80


/*  Data structures reflecting the memory organization of
**  the JV-80, JV-880, JV-90, and JV-1000 synthesizers.
*/
struct system_common
{
	struct syx_msg *common;
};

struct performance
{
	struct syx_msg *common;
	struct syx_msg *part[ 8 ];
};
struct patch
{
	struct syx_msg *common;
	struct syx_msg *tone[ 4 ];
};

struct rhythm_setup
{
	struct syx_msg *key[ 61 ];
};

/*  This data exists on JV-90 and JV-1000 only.
*/
struct rhythm_setup2
{
	struct syx_msg *key[ 15 ];
};

struct temp_perf
{
	struct performance perf;
	struct patch pat[ 7 ];
	struct rhythm_setup rhythm;
	struct rhythm_setup2 rhythm2;	/*  JV-90/JV-1000 only  */
};


#ifndef __STDC__

/*  Non-ANSI "prototypes"
*/
int syx_init();
int syx_clear();

struct syx_msg **address_analysis();
struct syx_msg **perf_msg();
struct syx_msg **patch_msg();
struct syx_msg **rhythm_msg();
struct syx_msg **rhythm2_msg();		/*  JV-90/JV-1000 only  */
struct syx_msg *get_syx_message();

int display();
int disp_perf();
int disp_patch();
int disp_rhythm_setup();
int disp_key();
int disp_note();

struct syx_msg *get_syx_message();

int inform();

#else

/*  ANSI prototypes
*/
int syx_init( void );
int syx_clear( void );

struct syx_msg **address_analysis( struct syx_msg *mp );
struct syx_msg **perf_msg( struct syx_msg *mp, struct performance *pp );
struct syx_msg **patch_msg( struct syx_msg *mp, struct patch *pp );
struct syx_msg **rhythm_msg( struct syx_msg *mp, struct rhythm_setup *rp );
/*  JV-90/JV-1000 only...  */
struct syx_msg **rhythm2_msg( struct syx_msg *mp, struct rhythm_setup2 *r2p );

int display( void );
int disp_perf( struct performance *perf );
int disp_patch( struct patch *pat );
int disp_rhythm_setup(	struct rhythm_setup *rhy,
			struct rhythm_setup2 *rhy2,
			char *str );
int disp_key( struct syx_msg *mp );
int disp_note( int note );

struct syx_msg *get_syx_message( FILE *fp );

int inform( char *filename );

#endif


/*  Memory declarations for keeping track of messages
**  extracted from a JV .SYX file.
*/
struct system_common Sys_com;		/*  System Common data  */

/*  Temporary memory area
*/
struct temp_perf T_perform;
struct patch T_patch;

/*  Internal memory area
*/
struct performance I_performance[ 16 ];
struct rhythm_setup2 I_rhythm2;		/*  JV-90/JV-1000 only  */
struct patch I_patchbank[ 64 ];
struct rhythm_setup I_rhythm;

/*  Card memory area
*/
struct performance C_performance[ 16 ];
struct rhythm_setup2 C_rhythm2;		/*  JV-90/JV-1000 only  */
struct patch C_patchbank[ 64 ];
struct rhythm_setup C_rhythm;


/*  The syx_init() function should only be called once before any messages
**  are read.  It initializes everything to an empty state, and just NULLs
**  all the pointers.  If it is called after messages have been read then
**  the memory containing the data and the message information will not be
**  free()'d.  Use the syx_clear() function which will check the pointers
**  for non-NULL and free() any memory that has been allocated.
*/
#ifndef __STDC__
int
syx_init()
#else
int
syx_init( void )
#endif
{
	int i, j;
	struct performance *perf;
	struct patch *pat;
	struct rhythm_setup *rhy;
	struct rhythm_setup2 *rhy2;

	/*  Init system common references
	*/
	Sys_com.common = NULL;

	/*  Init temporary performance references
	*/
	perf = &T_perform.perf;
	perf->common = NULL;
	for( i = 0; i < 8; i++ )
	{
		perf->part[ i ] = NULL;
	}
	for( i = 0; i < 7; i++ )
	{
		pat = &T_perform.pat[ i ];
		for( j = 0; j < 4; j++ )
		{
			pat->tone[ j ] = NULL;
		}
	}
	rhy = &T_perform.rhythm;
	for( i = 0; i < 61; i++ )
	{
		rhy->key[ i ] = NULL;
	}
	rhy2 = &T_perform.rhythm2;
	for( i = 0; i < 15; i++ )
	{
		rhy2->key[ i ] = NULL;
	}

	/*  Init Internal memory references
	*/
	for( i = 0; i < 16; i++ )
	{
		perf = &I_performance[ i ];
		perf->common = NULL;
		for( j = 0; j < 8; j++ )
		{
			perf->part[ j ] = NULL;
		}
	}
	for( i = 0; i < 64; i++ )
	{
		pat = &I_patchbank[ i ];
		pat->common = NULL;
		for( j = 0; j < 4; j++ )
		{
			pat->tone[ j ] = NULL;
		}
	}
	rhy = &I_rhythm;
	for( i = 0; i < 61; i++ )
	{
		rhy->key[ i ] = NULL;
	}
	rhy2 = &I_rhythm2;
	for( i = 0; i < 15; i++ )
	{
		rhy2->key[ i ] = NULL;
	}

	/*  Init Card memory references
	*/
	for( i = 0; i < 16; i++ )
	{
		perf = &C_performance[ i ];
		perf->common = NULL;
		for( j = 0; j < 8; j++ )
		{
			perf->part[ j ] = NULL;
		}
	}
	for( i = 0; i < 64; i++ )
	{
		pat = &C_patchbank[ i ];
		pat->common = NULL;
		for( j = 0; j < 4; j++ )
		{
			pat->tone[ j ] = NULL;
		}
	}
	rhy = &C_rhythm;
	for( i = 0; i < 61; i++ )
	{
		rhy->key[ i ] = NULL;
	}
	rhy2 = &C_rhythm2;
	for( i = 0; i < 15; i++ )
	{
		rhy2->key[ i ] = NULL;
	}

	return 0;
}

#ifndef __STDC__
int
syx_clear()
#else
int
syx_clear( void )
#endif
{
	int i, j;
	struct performance *perf;
	struct patch *pat;
	struct rhythm_setup *rhy;
	struct rhythm_setup2 *rhy2;

	/*  Clear system common references
	*/
	syx_msg_free( Sys_com.common );
	Sys_com.common = NULL;

	/*  Clear temporary performance references
	*/
	perf = &T_perform.perf;
	syx_msg_free( perf->common );
	perf->common = NULL;
	for( i = 0; i < 8; i++ )
	{
		syx_msg_free( perf->part[ i ] );
		perf->part[ i ] = NULL;
	}
	for( i = 0; i < 7; i++ )
	{
		pat = &T_perform.pat[ i ];
		for( j = 0; j < 4; j++ )
		{
			syx_msg_free( pat->tone[ j ] );
			pat->tone[ j ] = NULL;
		}
	}
	rhy = &T_perform.rhythm;
	for( i = 0; i < 61; i++ )
	{
		syx_msg_free( rhy->key[ i ] );
		rhy->key[ i ] = NULL;
	}
	rhy2 = &T_perform.rhythm2;
	for( i = 0; i < 15; i++ )
	{
		syx_msg_free( rhy2->key[ i ] );
		rhy2->key[ i ] = NULL;
	}

	/*  Clear Internal memory references
	*/
	for( i = 0; i < 16; i++ )
	{
		perf = &I_performance[ i ];
		syx_msg_free( perf->common );
		perf->common = NULL;
		for( j = 0; j < 8; j++ )
		{
			syx_msg_free( perf->part[ j ] );
			perf->part[ j ] = NULL;
		}
	}
	for( i = 0; i < 64; i++ )
	{
		pat = &I_patchbank[ i ];
		syx_msg_free( pat->common );
		pat->common = NULL;
		for( j = 0; j < 4; j++ )
		{
			syx_msg_free( pat->tone[ j ] );
			pat->tone[ j ] = NULL;
		}
	}
	rhy = &I_rhythm;
	for( i = 0; i < 61; i++ )
	{
		syx_msg_free( rhy->key[ i ] );
		rhy->key[ i ] = NULL;
	}
	rhy2 = &I_rhythm2;
	for( i = 0; i < 15; i++ )
	{
		syx_msg_free( rhy2->key[ i ] );
		rhy2->key[ i ] = NULL;
	}

	/*  Clear Card memory references
	*/
	for( i = 0; i < 16; i++ )
	{
		perf = &C_performance[ i ];
		syx_msg_free( perf->common );
		perf->common = NULL;
		for( j = 0; j < 8; j++ )
		{
			syx_msg_free( perf->part[ j ] );
			perf->part[ j ] = NULL;
		}
	}
	for( i = 0; i < 64; i++ )
	{
		pat = &C_patchbank[ i ];
		syx_msg_free( pat->common );
		pat->common = NULL;
		for( j = 0; j < 4; j++ )
		{
			syx_msg_free( pat->tone[ j ] );
			pat->tone[ j ] = NULL;
		}
	}
	rhy = &C_rhythm;
	for( i = 0; i < 61; i++ )
	{
		syx_msg_free( rhy->key[ i ] );
		rhy->key[ i ] = NULL;
	}
	rhy2 = &C_rhythm2;
	for( i = 0; i < 15; i++ )
	{
		syx_msg_free( rhy2->key[ i ] );
		rhy2->key[ i ] = NULL;
	}

	return 0;
}

#ifndef __STDC__
int
syx_msg_free( mp )
struct syx_msg *mp;
#else
int
syx_msg_free( struct syx_msg *mp )
#endif
{
	if( mp )
	{
		if( mp->msg_bytes )
		{
			free( mp->msg_bytes );
		}
		free( mp );
	}

	return 0;
}


#ifndef __STDC__
struct syx_msg **
address_analysis( mp )
struct syx_msg *mp;
#else
struct syx_msg **
address_analysis( struct syx_msg *mp )
#endif
{
	struct syx_msg **dest = NULL;	/*  Initialize to invalid  */

	if( mp->adr[ 3 ] != 0 )
	{
		/*  All acceptable address have adr[ 3 ] == 0.
		*/
		return dest;
	}

	switch( mp->adr[ 0 ] )
	{
	    case 0x00 :
		/*  System common or Temporary buffers.
		*/
		if( mp->adr[ 1 ] == 0x00 )
		{
			if( mp->adr[ 2 ] == 0x00 )
			{
				/*  System Common data  */
				dest = &Sys_com.common;
			}
			else if( mp->adr[ 2 ] >= 0x10 && mp->adr[ 2 ] <= 0x1F )
			{
				/*  Temporary Performance data  */
				dest = perf_msg( mp, &T_perform.perf );
			}
			else if( mp->adr[ 2 ] >= 0x20 && mp->adr[ 2 ] <= 0x2B )
			{
				/*  Temporary Performance Patch 1 data  */
				dest = patch_msg( mp, &T_perform.pat[ 0 ] );
			}
		}
		else if( mp->adr[ 1 ] >= 0x01 && mp->adr[ 1 ] <= 0x06 )
		{
			/*  Temporary Performance Patch 2-7 data  */
			dest = patch_msg( mp, &T_perform.pat[ mp->adr[ 1 ] ] );
		}
		else if( mp->adr[ 1 ] == 0x07 )
		{
			dest = rhythm_msg( mp, &T_perform.rhythm );
		}
		else if( mp->adr[ 1 ] == 0x08 )
		{
			/*  Temporary Patch data  */
			dest = patch_msg( mp, &T_patch );
		}
		else if( mp->adr[ 1 ] == 0x20 )
		{
			dest = rhythm2_msg( mp, &T_perform.rhythm2 );
		}
		break;
	    case 0x01 :
		if( mp->adr[ 1 ] >= 0x00  &&  mp->adr[ 1 ] <= 0x0F )
		{
			/*  It is an Internal Performance message  */
			dest = perf_msg( mp, &I_performance[ mp->adr[ 1 ] ] );
		}
		else if( mp->adr[ 1 ] == 0x20 )
		{
			dest = rhythm2_msg( mp, &I_rhythm2 );
		}
		else if( mp->adr[ 1 ] >= 0x40  &&  mp->adr[ 1 ] <= 0x7E )
		{
			/*  It is an Internal Patch message  */
			dest = patch_msg( mp, &I_patchbank[ mp->adr[ 1 ] - 0x40 ] );
		}
		else if( mp->adr[ 1 ] == 0x7F )
		{
			if( mp->adr[ 2 ] >= 0x20 && mp->adr[ 2 ] <= 0x2B )
			{
				/*  It is an Internal Patch message  */
				dest = patch_msg( mp, &I_patchbank[ 0x3F ] );
			}
			else
			{
				/*  Its probably a rhythm setup message  */
				dest = rhythm_msg( mp, &I_rhythm );
			}
		}
		break;
	    case 0x02 :
		if( mp->adr[ 1 ] >= 0x00  &&  mp->adr[ 1 ] <= 0x0F )
		{
			/*  It is a Card Performance message  */
			dest = perf_msg( mp, &C_performance[ mp->adr[ 1 ] ] );
		}
		else if( mp->adr[ 1 ] == 0x20 )
		{
			dest = rhythm2_msg( mp, &C_rhythm2 );
		}
		else if( mp->adr[ 1 ] >= 0x40  &&  mp->adr[ 1 ] <= 0x7E )
		{
			/*  It is a Card Patch message  */
			dest = patch_msg( mp, &C_patchbank[ mp->adr[ 1 ] - 0x40 ] );
		}
		else if( mp->adr[ 1 ] == 0x7F )
		{
			if( mp->adr[ 2 ] >= 0x20 && mp->adr[ 2 ] <= 0x2B )
			{
				/*  It is a Card Patch message  */
				dest = patch_msg( mp, &C_patchbank[ 0x3F ] );
			}
			else
			{
				/*  Its probably a rhythm setup message  */
				dest = rhythm_msg( mp, &C_rhythm );
			}
		}
		break;
	    default :
		break;
	}

	return dest;
}

#ifndef __STDC__
struct syx_msg **
perf_msg( mp, pp )
struct syx_msg *mp;
struct performance *pp;
#else
struct syx_msg **
perf_msg( struct syx_msg *mp, struct performance *pp )
#endif
{
	struct syx_msg **ret = NULL;

	if( mp->adr[ 2 ] == 0x10 )
	{
		ret = &pp->common;
	}
	else if( mp->adr[ 2 ] >= 0x18 && mp->adr[ 2 ] <= 0x1F )
	{
		ret = &pp->part[ mp->adr[ 2 ] - 0x18 ];
	}

	return ret;
}

#ifndef __STDC__
struct syx_msg **
patch_msg( mp, pp )
struct syx_msg *mp;
struct patch *pp;
#else
struct syx_msg **
patch_msg( struct syx_msg *mp, struct patch *pp )
#endif
{
	struct syx_msg **ret = NULL;

	if( mp->adr[ 2 ] == 0x20 )
	{
		ret = &pp->common;
	}
	else if( mp->adr[ 2 ] >= 0x28 && mp->adr[ 2 ] <= 0x2B )
	{
		ret = &pp->tone[ mp->adr[ 2 ] - 0x28 ];
	}

	return ret;
}

#ifndef __STDC__
struct syx_msg **
rhythm_msg( mp, rp )
struct syx_msg *mp;
struct rhythm_setup *rp;
#else
struct syx_msg **
rhythm_msg( struct syx_msg *mp, struct rhythm_setup *rp )
#endif
{
	struct syx_msg **ret = NULL;

	if( mp->adr[ 2 ] >= 0x40 && mp->adr[ 2 ] <= 0x7C )
	{
		ret = &rp->key[ mp->adr[ 2 ] - 0x40 ];
	}

	return ret;
}

#ifndef __STDC__
struct syx_msg **
rhythm2_msg( mp, r2p )
struct syx_msg *mp;
struct rhythm_setup2 *r2p;
#else
struct syx_msg **
rhythm2_msg( struct syx_msg *mp, struct rhythm_setup2 *r2p )
#endif
{
	struct syx_msg **ret = NULL;

	if( mp->adr[ 2 ] >= 0x38 && mp->adr[ 2 ] <= 0x46 )
	{
		ret = &r2p->key[ mp->adr[ 2 ] - 0x38 ];
	}

	return ret;
}


#ifndef __STDC__
int
display()
#else
int
display( void )
#endif
{
	struct syx_msg *mp;
	int i;
	struct performance *perf;
	struct patch *pat;

	mp = Sys_com.common;
	if( mp )
	{
		printf( "Found System Common data, %d bytes\n", mp->size );
	}

	perf = &T_perform.perf;
	if( perf->common )
	{
		printf( "Temporary Performance:\n" );
		disp_perf( perf );
	}
	for( i = 0; i < 7; i++ )
	{
		pat = &T_perform.pat[ i ];
		if( pat->common )
		{
			printf( "Temporary Performance Patch %d:\n", i + 1 );
			disp_patch( pat );
		}
	}

	disp_rhythm_setup( &T_perform.rhythm, &T_perform.rhythm2, "Temporary" );

	if( T_patch.common )
	{
		printf( "Temporary Patch:\n" );
		disp_patch( &T_patch );
	}

	/*  Internal memory references
	*/
	for( i = 0; i < 16; i++ )
	{
		perf = &I_performance[ i ];
		if( perf->common )
		{
			printf( "Internal Performance %2d:\n", i + 1 );
			disp_perf( perf );
		}
	}
	for( i = 0; i < 64; i++ )
	{
		pat = &I_patchbank[ i ];
		if( pat->common )
		{
			printf( "Internal Patch %d:\n", i + 1 );
			disp_patch( pat );
		}
	}

	disp_rhythm_setup( &I_rhythm, &I_rhythm2, "Internal" );

	/*  Card memory references
	*/
	for( i = 0; i < 16; i++ )
	{
		perf = &C_performance[ i ];
		if( perf->common )
		{
			printf( "Card Performance %2d:\n", i + 1 );
			disp_perf( perf );
		}
	}
	for( i = 0; i < 64; i++ )
	{
		pat = &C_patchbank[ i ];
		if( pat->common )
		{
			printf( "Card Patch %d:\n", i + 1 );
			disp_patch( pat );
		}
	}

	disp_rhythm_setup( &C_rhythm, &C_rhythm2, "Card" );

	return 0;
}

#ifndef __STDC__
int
disp_perf( perf )
struct performance *perf;
#else
int
disp_perf( struct performance *perf )
#endif
{
	int i, n;
	char *cp;

	if( perf->common )
	{
		printf( "\tName: " );
		for( i = 0; i < 12; i++ )
		{
			printf( "%c", perf->common->msg_bytes[ i ] );
		}
		printf( "\n" );
	}
	for( i = 0; i < 8; i++ )
	{
		if( perf->part[ i ] )
		{
			printf( "\tPart %d: patch ", i + 1 );
			cp = &perf->part[ i ]->msg_bytes[ 0x17 ];
			n = (*cp++ & 0x0F) << 4;
			n |= *cp & 0x0F;
			if( n < 64 )
			{
				printf( "I" );
			}
			else if( n < 128 )
			{
				printf( "C" );
			}
			else if( n < 192 )
			{
				printf( "A" );
			}
			else
			{
				printf( "B" );
			}
			printf( "%02d  ", (n % 64) + 1 );
			printf( "MIDI channel %d\n",
				perf->part[ i ]->msg_bytes[ 0x16 ] + 1 );
		}
	}

	return 0;
}

#ifndef __STDC__
int
disp_patch( pat )
struct patch *pat;
#else
int
disp_patch( struct patch *pat )
#endif
{
	int i, n;
	char *cp;

	if( pat->common )
	{
		printf( "\tName: " );
		for( i = 0; i < 12; i++ )
		{
			printf( "%c", pat->common->msg_bytes[ i ] );
		}
		printf( "\n" );
	}
	for( i = 0; i < 4; i++ )
	{
		if( pat->tone[ i ] )
		{
			printf( "\tTone %d: ", i + 1 );
			n = pat->tone[ i ]->msg_bytes[ 3 ];
			printf( "%s", n ? "ON  " : "OFF " );
			n = pat->tone[ i ]->msg_bytes[ 0 ];
			if( n == 0 )
			{
				printf( "INT " );
			}
			else if( n == 1 )
			{
				printf( "EXP " );
			}
			else if( n == 2 )
			{
				printf( "PCM " );
			}
			else
			{
				printf( "UNK " );
			}
			cp = &pat->tone[ i ]->msg_bytes[ 1 ];
			n = (*cp++ & 0x0F) << 4;
			n |= *cp & 0x0F;
			printf( "Wave %3d\n", n + 1 );
		}
	}

	return 0;
}

#ifndef __STDC__
int
disp_rhythm_setup( rhy, rhy2, str )
struct rhythm_setup *rhy;
struct rhythm_setup2 *rhy2;
char *str;
#else
int
disp_rhythm_setup( struct rhythm_setup *rhy,
		   struct rhythm_setup2 *rhy2,
		   char *str )
#endif
{
	int i, j;

	j = 0;
	for( i = 0; i < 8; i++ )
	{
		if( rhy2->key[ i ] )
		{
			if( j == 0 )
			{
				printf( "%s Rhythm Setup:\n", str );
				j = 1;
			}
			printf( "\tKey %d  ", i + 28 );
			disp_note( i + 28 );
			disp_key( rhy2->key[ i ] );
		}
	}
	for( i = 0; i < 61; i++ )
	{
		if( rhy->key[ i ] )
		{
			if( j == 0 )
			{
				printf( "%s Rhythm Setup:\n", str );
				j = 1;
			}
			printf( "\tKey %d  ", i + 36 );
			disp_note( i + 36 );
			disp_key( rhy->key[ i ] );
		}
	}
	for( i = 8; i < 15; i++ )
	{
		if( rhy2->key[ i ] )
		{
			if( j == 0 )
			{
				printf( "%s Rhythm Setup:\n", str );
				j = 1;
			}
			printf( "\tKey %d  ", i + 89 );
			disp_note( i + 89 );
			disp_key( rhy2->key[ i ] );
		}
	}

	return 0;
}

#ifndef __STDC__
int
disp_key( mp )
struct syx_msg *mp;
#else
int
disp_key( struct syx_msg *mp )
#endif
{
	int n;
	char *cp;

	n = mp->msg_bytes[ 3 ];
	printf( "%s", n ? "ON  " : "OFF " );
	n = mp->msg_bytes[ 0 ];
	if( n == 0 )
	{
		printf( "INT " );
	}
	else if( n == 1 )
	{
		printf( "EXP " );
	}
	else if( n == 2 )
	{
		printf( "PCM " );
	}
	else
	{
		printf( "UNK " );
	}
	cp = &mp->msg_bytes[ 1 ];
	n = (*cp++ & 0x0F) << 4;
	n |= *cp & 0x0F;
	printf( "Wave %3d\n", n + 1 );

	return 0;
}

#ifndef __STDC__
int
disp_note( note )
int note;
#else
int
disp_note( int note )
#endif
{
	static char *notes[] =
	{
		"C ", "C#", "D ", "D#", "E ", "F ",
		"F#", "G ", "G#", "A ", "A#", "B "
	};

	printf( "(%s%d)  ", notes[ note % 12 ], (note/12)-1 );

	return 0;
}


#define MAX_BUFR 1024

#ifndef __STDC__
struct syx_msg *
get_syx_message( fp )
FILE *fp;
#else
struct syx_msg *
get_syx_message( FILE *fp )
#endif
{
	int nxt_byte, cksum, i;
	struct syx_msg *mp;
	static char bufr[ MAX_BUFR ];

	/*  Look for the start of the SYX message (0xF0 byte).
	**  In theory the first byte should be the SYSEX_START byte
	**  but we will allow a resync in case of trouble, or in case
	**  there are other bytes in the file.  Any non-0xF0 bytes
	**  are discarded at this point.
	*/
	while( (nxt_byte = getc( fp )) != 0xF0 )
	{
		if( nxt_byte == EOF )
		{
			/*  No more SYX messages in the file.
			*/
			return NULL;
		}

		/*  All non-0xF0 bytes are discarded here!  */
	}

	/*  A message is starting, allocate and initialize
	**  a syx_msg structure.
	*/
	mp = (struct syx_msg *) malloc( sizeof( struct syx_msg ) );
	if( mp == NULL )
	{
		/*  No memory for storing message, bail out!
		*/
		return NULL;
	}
	mp->size = -9;	/*  increments as bytes are analyzed  */
	mp->unit_num = 0;
	mp->adr[ 0 ] = 0;
	mp->adr[ 1 ] = 0;
	mp->adr[ 2 ] = 0;
	mp->adr[ 3 ] = 0;
	mp->msg_bytes = NULL;
	mp->errors = 0;

	/*  Begin collecting and analyzing the message.
	**  The first 8 bytes are "header" information.
	*/
	for( i = 0; i < 8; i++ )
	{
		if( (nxt_byte = getc( fp )) == EOF )
		{
			mp->errors |= E_EOF;
			return mp;
		}
		if( nxt_byte == 0xF7 )
		{
			mp->errors |= E_SHORT_MSG;
			return mp;
		}

		switch( mp->size++ )
		{
		    case -9:	/*  The Roland ID byte  */
			if( nxt_byte != 0x41 )
			{
				mp->errors |= E_NOT_ROLAND;
			}
			break;
		    case -8:	/*  The Device ID  */
			if( nxt_byte < 0x10  || nxt_byte > 0x1F )
			{
				mp->errors |= E_BAD_DEVICE;
			}
			/*  Convert to unit number  */
			mp->unit_num = nxt_byte + 1;
			break;
		    case -7:	/*  The Model ID  */
			if( nxt_byte != 0x46 )
			{
				mp->errors |= E_WRONG_MODEL;
			}
			break;
		    case -6:	/*  The Command byte  */
			if( nxt_byte != 0x12 )
			{
				mp->errors |= E_BAD_COMMAND;
			}
			break;
		    case -5:	/*  Address byte 1 (MSB)  */
			mp->adr[ 0 ] = nxt_byte;
			cksum = nxt_byte;
			break;
		    case -4:	/*  Address byte 2        */
			mp->adr[ 1 ] = nxt_byte;
			cksum += nxt_byte;
			break;
		    case -3:	/*  Address byte 3        */
			mp->adr[ 2 ] = nxt_byte;
			cksum += nxt_byte;
			break;
		    case -2:	/*  Address byte 4 (LSB)  */
			mp->adr[ 3 ] = nxt_byte;
			cksum += nxt_byte;
			break;
		    default :
			/*  How did we get here???  */
			printf( "Que passa (unexpected default 1)?\n" );
			break;
		}
	}

	/*  Now collect the actual data bytes into bufr.
	**  Stop at 0xF7 (hopefully), EOF, or mp->size greater than MAX_BUFR.
	**  The last byte before 0xF7 is the checksm byte, so some adjustment
	**  is necessary to mp->size, and the last byte should be compared to
	**  the calculated checksum and not copied into the buffer allocated
	**  for mp->msg_bytes.
	*/
	while( (nxt_byte = getc( fp )) != 0xF7 )
	{
		if( nxt_byte == EOF )
		{
			mp->errors |= E_EOF;
			break;
		}
		if( ++mp->size >= MAX_BUFR )
		{
			mp->errors |= E_LONG_MSG;
			--mp->size;
			break;
		}
		bufr[ mp->size ] = nxt_byte;
	}

	if( mp->size > 0 )
	{
		mp->msg_bytes = (char *) malloc( mp->size );

		for( i = 0; i < mp->size; i++ )
		{
			if( mp->msg_bytes != NULL )
			{
				mp->msg_bytes[ i ] = bufr[ i ];
			}
			cksum += bufr[ i ];
		}
	}

	if( mp->size >= 0 )
	{
		/*  Assume the last byte was the checksum (possibly invalid
		**  if nxt_byte == EOF or mp->size >= MAX_BUFR).  Verify this
		**  value with the calculated checksum.
		*/
		cksum &= 0x7F;
		if( cksum )
		{
			cksum = 0x80 - cksum;
		}
		if( bufr[ mp->size ] != cksum )
		{
			mp->errors |= E_BAD_CHECKSUM;
		}
	}

	return mp;
}


#ifndef __STDC__
int
inform( filename )
char *filename;
#else
int
inform( char *filename )
#endif
{
	FILE *fp;
	struct syx_msg *msg_p, **dest;
	int msg_cnt;

#ifndef __STDC__
	if( (fp = fopen( filename, "r" )) == NULL )
#else
	if( (fp = fopen( filename, "rb" )) == NULL )
#endif
	{
		fprintf( stderr, "Can't open %s for reading!\n", filename );
		return 1;
	}

	printf( "\nInformation from %s:\n", filename );

	syx_clear();
	msg_cnt = 0;
	while( (msg_p = get_syx_message( fp )) != NULL )
	{
		++msg_cnt;
		if( msg_p->errors != 0 )
		{
			printf( "Errors on message %3d -- 0x%02x (skipped)\n",
						msg_cnt, msg_p->errors );
			syx_msg_free( msg_p );
			continue;
		}

		dest = address_analysis( msg_p );
		if( dest == NULL )
		{
			printf( "Unrecognized address - " );
		}
		else if( *dest != NULL )
		{
			printf( "Duplicate message - " );
		}
		else
		{
			*dest = msg_p;
			continue;
		}

		printf( "Message %3d: ", msg_cnt );
		printf( "[%02x %02x %02x %02x]", msg_p->adr[ 0 ],
						 msg_p->adr[ 1 ],
						 msg_p->adr[ 2 ],
						 msg_p->adr[ 3 ]  );
		printf( ", message ignored.\n" );
		syx_msg_free( msg_p );
	}

	display();

	fclose( fp );
	return 0;
}


int
main( argc, argv )
int argc;
char *argv[];
{
	int i;

	if( argc < 2 )
	{
		fprintf( stderr, "USAGE: %s [.SYX file(s)]\n", argv[ 0 ] );
		exit( 1 );
	}

	syx_init();

	i = 1;
	while( i < argc )
	{
		inform( argv[ i ] );
		i++;
	}
}
