/*	NEWUNIT.C  --  Converts files with SYSEX messages for Roland JV
**		       family synthesizers in them to have the specified
**		       unit number.
**
**  To compile with Microsoft C/C++ 7.0 issue the following command:
**
**	CL /Za newunit.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 two fopen() calls are made.
**
**  Written by:  Greg Youngdahl   greg@ihlpm.att.com
**  Date:        January 10, 1995
**
**  Released to the public domain for your use at your own risk.
*/

#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.
**
**  This structure is also used by put_syx_message() to write a message.
*/
struct syx_msg
{
	int unit_num;
	char address[ 4 ];
	int size;
	char *msg_bytes;
	int errors;
};

/*  Bits for errors field of syx_msg
*/
#define E_NOT_ROLAND	0x01	/*  Not a Roland SYSEX message		*/
#define E_BAD_DEVICE	0x02	/*  Device ID (Unit# -1) out of range	*/
#define E_WRONG_MODEL	0x04	/*  Not JV-80 family synth		*/
#define E_BAD_COMMAND	0x08	/*  Not a DT1 command byte		*/
#define E_BAD_CHECKSUM	0x10	/*  Improper Checksum value		*/
#define E_EOF		0x20	/*  EOF encountered before 0xF7 found	*/
#define E_SHORT_MSG	0x40	/*  0xF7 found without complete header	*/
#define E_LONG_MSG	0x80	/*  Message longer than internal buffer	*/


/*  Function prototypes
*/
#ifndef __STDC__

int usage();
int chgunit();
struct syx_msg *get_syx_message();
int put_syx_message();

#else

int usage( void );
int chgunit( int unit, FILE *ifp, FILE *ofp );
struct syx_msg *get_syx_message( FILE *fp );
int put_syx_message( FILE *ofp, struct syx_msg *mp );

#endif


#ifndef __STDC__
int
main( argc, argv )
int argc;
char *argv[];
#else
int
main( int argc, char *argv[] )
#endif
{
	int unit;
	FILE *ifp, *ofp;

	if( argc != 4 )
	{
		usage();
		return 1;
	}

	/*  safety check  */
	if( strcmp( argv[ 2 ], argv[ 3 ] ) == 0 )
	{
		/*  The same name was specified for input and output.  */
		fprintf( stderr, "%s: Please specify different ", argv[ 0 ] );
		fprintf( stderr, "names for input and output files!\n" );
		return 4;
	}

	if( sscanf( argv[ 1 ], "%d", &unit ) != 1 )
	{
		fprintf( stderr, "Invalid unit number!\n" );
		usage();
		return 2;
	}

	if( unit < 17  ||  unit > 32 )
	{
		fprintf( stderr, "Invalid unit number!\n" );
		usage();
		return 3;
	}

#ifndef __STDC__
	if( (ifp = fopen( argv[ 2 ], "r" )) == NULL )
#else
	if( (ifp = fopen( argv[ 2 ], "rb" )) == NULL )
#endif
	{
		fprintf( stderr, "%s: Can't open %s for reading!\n",
							argv[ 0 ], argv[ 2 ] );
		return 4;
	}

#ifndef __STDC__
	if( (ofp = fopen( argv[ 3 ], "w" )) == NULL )
#else
	if( (ofp = fopen( argv[ 3 ], "wb" )) == NULL )
#endif
	{
		fprintf( stderr, "%s: Can't open %s for writing!\n",
							argv[ 0 ], argv[ 3 ] );
		return 5;
	}

	chgunit( unit, ifp, ofp );

	fclose( ifp );
	fclose( ofp );

	return 0;
}

/*	int usage( void )
**
**  Issues the Usage message.
*/
#ifndef __STDC__
int
usage()
#else
int
usage( void )
#endif
{
	fprintf( stderr, "USAGE: newunit Unit# curfile newfile\n" );
	fprintf( stderr, "    Unit# may range from 17 to 32.\n" );
	return 0;
}

/*	int chgunit( int unit, FILE *ifp, FILE *ofp )
**
**  The control loop.  It extracts messages from the input file, checks
**  for error indications, and writes messages that don't indicate errors
**  to the output file with the modified unit number.  Probably not the
**  best behaviour in the face of errors, but it is simple.
*/
#ifndef __STDC__
int
chgunit( unit, ifp, ofp )
int unit;
FILE *ifp;
FILE *ofp;
#else
int
chgunit( int unit, FILE *ifp, FILE *ofp )
#endif
{
	struct syx_msg *msg_p;
	int msg_cnt;

	printf( "\nChanging unit number to %d.\n", unit );

	msg_cnt = 0;
	while( (msg_p = get_syx_message( ifp )) != NULL )
	{
		++msg_cnt;
		if( msg_p->errors != 0 )
		{
			printf( "Errors on message %d -- 0x%02x (skipped)\n",
						msg_cnt, msg_p->errors );
		}
		else
		{
			msg_p->unit_num = unit;
			put_syx_message( ofp, msg_p );
		}

		if( msg_p->msg_bytes != NULL )
		{
			free( msg_p->msg_bytes );
		}
		free( msg_p );
	}

	return 0;
}


/*	int put_syx_message( FILE *ofp, struct syx_msg *mp )
**
**  This function writes the SYSEX message pointed to by the mp into
**  the file pointed to by the ofp.  The necessary initial bytes 
**  necessary to conform to the format for Roland JV SYSEX messages
**  and the calculated checksum and final End_Of_SYSEX bytes are added
**  to make a complete message.
*/
#ifndef __STDC__
int
put_syx_message( ofp, mp )
FILE *ofp;
struct syx_msg *mp;
#else
int
put_syx_message( FILE *ofp, struct syx_msg *mp )
#endif
{
	int i, cksum, v;

	putc( 0xF0, ofp );		/*  Start of SYSEX	*/
	putc( 0x41, ofp );		/*  Roland ID byte	*/
	putc( mp->unit_num - 1, ofp );	/*  Device ID byte	*/
	putc( 0x46, ofp );		/*  JV model ID		*/
	putc( 0x12, ofp );		/*  Command byte	*/

	v = mp->address[ 0 ];		/*  Address MSB		*/
	cksum = v;
	putc( v, ofp );
	v = mp->address[ 1 ];		/*  Address		*/
	cksum += v;
	putc( v, ofp );
	v = mp->address[ 2 ];		/*  Address		*/
	cksum += v;
	putc( v, ofp );
	v = mp->address[ 3 ];		/*  Address LSB		*/
	cksum += v;
	putc( v, ofp );

	for( i = 0; i < mp->size; i++ )
	{
		v = mp->msg_bytes[ i ];
		cksum += v;
		putc( v, ofp );		/*  Data	*/
	}

	cksum &= 0x7F;
	if( cksum )
	{
		cksum = 0x80 - cksum;
	}

	putc( cksum, ofp );		/*  Message checksum	*/
	putc( 0xF7, ofp );		/*  End of SYSEX	*/

	return 0;
}


#define MAX_BUFR 1024

/*	struct syx_msg *get_syx_message( FILE *fp )
**
**  This function extracts one SYSEX message from the indicated file
**  and returns a pointer to an allocated syx_msg structure if a
**  message is found.  Various checks of the message are performed
**  and inconsistencies are reported in the errors field of the
**  syx_msg structure.  The pointer returned by this function (if non-NULL)
**  has been allocated byt malloc() as has the buffer pointed to by the
**  msg_bytes pointer (if non-NULL).  It is the responsability of the
**  programmer to keep track of and free() this memory appropriately.
*/
#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->address[ 0 ] = 0;
	mp->address[ 1 ] = 0;
	mp->address[ 2 ] = 0;
	mp->address[ 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->address[ 0 ] = nxt_byte;
			cksum = nxt_byte;
			break;
		    case -4:	/*  Address byte 2        */
			mp->address[ 1 ] = nxt_byte;
			cksum += nxt_byte;
			break;
		    case -3:	/*  Address byte 3        */
			mp->address[ 2 ] = nxt_byte;
			cksum += nxt_byte;
			break;
		    case -2:	/*  Address byte 4 (LSB)  */
			mp->address[ 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;
}
