/*	.MOD to MID file converter ©1993 Andrew Scott

		Amiga conversion © 1994 by Paul Huxham
*/

#include <ctype.h>
#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <exec/types.h>

#include <clib/dos_protos.h>

#include <pragmas/dos_pragmas.h>

#include "mod2midi.h"
#include "text.h"

/* CLI parameters */
#define OPT_TEMPLATE "MOD,MIDI,ABOUT/S,MAPSAMPLES/S,SAVEINFO/S,TRANSPOSE/S,DRUM/N,TEMPO/N,VOLUMESHIFT/S"
#define OPT_MODFILE 0
#define OPT_MIDIFILE 1
#define OPT_ABOUT 2
#define OPT_MAPSAMPLES 3
#define OPT_SAVEINFO 4
#define OPT_TRANSPOSE 5
#define OPT_DRUMCHANNEL 6
#define OPT_TEMPOTYPE 7
#define OPT_VOLUMESHIFT 8
#define OPT_NUMBEROF 9

/* Function Prototypes */
int Instrument( void );
void OutByte( bfile f, char b );
void FlushOut( bfile f );
void CloseOut( bfile f );
int OpenOut( bfile f, string fn );
unsigned long Beatle( bfile f );
unsigned char InByte( bfile f );
void CloseIn( bfile f );
int OpenIn( bfile f, string fn );
void Inskipp( bfile f, unsigned long n );
struct bpos FPos( bfile f );
void FGoto( bfile f, struct bpos x );
int WriteVLQ( bfile f, unsigned long i );
int MKTest( bfile f );
string SimplifyName( string s );
string GetLine( FILE *f );
int ReadModSpecs( bfile f, string n, samps s );
void ScanSamples( samps s );
int SetDefaults( samps s, string fn );
void SaveDefaults( samps s, string fn );
void NullArryFree( string *sp );
int Instrument();
string MIDIVal( samp *sam );
string TransVal( samp *sam );
string VolumeVal( samp *sam );
string NullVal( samp *sam );
int Sample( samps s, string (*MIDIVal)(samp *x), int w );
int ChooseChannels( samps s );
void MapSamples( samps s );
void SaveSamp( samp sam );
void SaveSamples( samps s );
void Transpositions( samps s );
void VolumeShifts( samps s );
unsigned char NoteValue( unsigned int n );
unsigned long NoteLength( unsigned char n, unsigned int l, unsigned int b );
void WriteHeader( bfile mf, unsigned char n );
unsigned int Trk0Info( bfile mf, string s );
void AddToLog( unsigned long a, unsigned long b );
void WriteLog( FILE *f );
unsigned char RestrictVol( int v );
void ConvertMOD( bfile f1, bfile f2, string tn, samps smp );
extern int ScrollChoice( string title, string *sp, int w );
extern string DialogBox( string *sp, string def );

char *version = "$VER: Mod2Midi 1.0 (12.6.94)";
long __stack = 20000; // Code allocates everything off the stack ;-)

bfile MidFile, ModFile;
char SongName[21];
samps Samples;
unsigned long PosLog[64];
int DrumChann = 9, TempoType = 1, PosI = 0;
string MidFN, ModFN;

char *TempoTypes[] = { "NORMAL", "EXTENDED" };

extern struct Library *DOSBase;

int main( int argc, char *argv[] )
{
	int r;
	UBYTE Mode = MODE_CONVERT;

	MidFile->f = ModFile->f = NULL;
	MidFN = ModFN = NULL;

	/* Only work if launched from shell */
	if ( argc != 0 )
	{
		struct RDArgs *RDArgs;
		LONG opts[OPT_NUMBEROF];

		opts[OPT_MODFILE] = 0;
		opts[OPT_MIDIFILE] = 0;
		opts[OPT_ABOUT] = 0;
		opts[OPT_MAPSAMPLES] = 0;
		opts[OPT_SAVEINFO] = 0;
		opts[OPT_TRANSPOSE] = 0;
		opts[OPT_DRUMCHANNEL] = 0;
		opts[OPT_TEMPOTYPE] = 0;
		opts[OPT_VOLUMESHIFT] = 0;

		RDArgs = ReadArgs( OPT_TEMPLATE, opts, NULL );
		if ( RDArgs != NULL )
		{
			/* Grab all set cli parameters */
			if ( opts[OPT_MODFILE] != 0 )
			{
				ModFN = malloc( strlen( (char *)opts[OPT_MODFILE] ) + 1 );
				if ( ModFN != NULL ) strcpy( ModFN, (char *)opts[OPT_MODFILE] );
			}

			if ( opts[OPT_MIDIFILE] != 0 )
			{
				MidFN = malloc( strlen( (char *)opts[OPT_MIDIFILE] ) + 1 );
				if ( MidFN != NULL ) strcpy( MidFN, (char *)opts[OPT_MIDIFILE] );
			}

			if ( opts[OPT_ABOUT] != 0 )	Mode |= MODE_ABOUT;

			if ( opts[OPT_MAPSAMPLES] != 0 ) Mode |= MODE_MAPSAMPLES;

			if ( opts[OPT_SAVEINFO] != 0 ) Mode |= MODE_SAVEINFO;

			if ( opts[OPT_TRANSPOSE] != 0 ) Mode |= MODE_TRANSPOSE;

			if ( opts[OPT_VOLUMESHIFT] != 0 ) Mode |= MODE_VOLUMESHIFT;

			if ( opts[OPT_DRUMCHANNEL] != 0 )
			{
				/* Drum channel is stored as value - 1 */
				DrumChann = *( (ULONG *)opts[OPT_DRUMCHANNEL] ) - 1;
				printf( "Drum channel set to: %d\n", DrumChann + 1 );
			}

			if ( opts[OPT_TEMPOTYPE] != 0 )
			{
				int value;

				value = *( (ULONG *)opts[OPT_TEMPOTYPE] );

				if ( value >= 0 && value <= 1 )
				{
					TempoType = value;
					printf( "Tempo type set to: %s\n", TempoTypes[TempoType] );
				}
				else printf( "Illegal value for TEMPO, using default of NORMAL\n" );
			}

			FreeArgs ( RDArgs );

			/* Open the MID file */
			if ( MidFN != NULL )
			{
				r = OpenOut( MidFile, MidFN );
			}

			/* Open the MOD file */
			if ( r != NULL && ModFN != NULL )
			{
				if ( ModFile->f != NULL ) SaveDefaults( Samples, ModFN ); // Save defaults

				r = OpenIn( ModFile, ModFN );
				if ( r != NULL )
				{
					if ( !ReadModSpecs( ModFile, SongName, Samples ) )
						printf( "%s is not a .mod file\n", ModFN );
					else
					if ( !SetDefaults( Samples, ModFN ) ) CloseIn( ModFile );
				}
			}

			if ( ( Mode & MODE_ABOUT ) == MODE_ABOUT )
			{
				int i = 0;
				while ( MSG_ABOUT[i] != NULL ) printf( "%s\n", MSG_ABOUT[ i++ ] );

				Mode = NULL; //Dont allow futher processing
			}

			if ( ( Mode & MODE_MAPSAMPLES ) == MODE_MAPSAMPLES )
			{
				if ( ModFile->f != NULL ) MapSamples( Samples );
				else
				printf( "%s", MSG_ERR_NO_MOD_FILENAME );
			}

			if ( ( Mode & MODE_SAVEINFO ) == MODE_SAVEINFO )
			{
				if ( ModFile->f != NULL ) SaveSamples( Samples );
				else
				printf( "%s", MSG_ERR_NO_MOD_FILENAME );
			}

			if ( ( Mode & MODE_TRANSPOSE ) == MODE_TRANSPOSE )
			{
				if ( ModFile->f != NULL ) Transpositions( Samples );
				else
				printf( "%s", MSG_ERR_NO_MOD_FILENAME );
			}

			if ( ( Mode & MODE_VOLUMESHIFT ) == MODE_VOLUMESHIFT )
			{
				if ( ModFile->f != NULL ) VolumeShifts( Samples );
				else
				printf( "%s", MSG_ERR_NO_MOD_FILENAME );
			}

			/* Do the conversion */
			if ( MidFile->f != NULL && ModFile->f != NULL && Mode != NULL )
			{
				if ( ChooseChannels( Samples ) <= 16 )
				{
					ConvertMOD( ModFile, MidFile, SongName, Samples );
				}
				else
				printf( "Too many instruments, there is a maximum of 16 allowed\n" );
			}
			else
			printf( "Both a source .MOD and a destination .MID file must be supplied\n" );

			/* If the file is open, close it */
			if ( ModFile->f != NULL )
			{
				CloseIn( ModFile );
				SaveDefaults( Samples, ModFN );
			}

			/* If a filename has been allocated, free it */
			if ( ModFN != NULL ) free( ModFN );

			/* If the file is open, close it */
			if ( MidFile->f != NULL ) CloseOut( MidFile );

			/* If a filename has been allocated, free it */
			if ( MidFN != NULL ) free( MidFN );
		}
		else printf( "Illegal parameters!\n" );
	}
}

/* Post: The byte b has been written to the buffer of the file f */
void OutByte( bfile f, char b )
{
	if ( f->o == BUFFSIZE )
	{
		fwrite( f->b, 1, BUFFSIZE, f->f );
		f->o = 0;
  }

	f->b[ f->o++ ] = b;
}

/* Pre: f was opened for writing */
/* Post: The file f has has its buffer flushed */
void FlushOut( bfile f )
{
	if ( f->o > 0 ) fwrite( f->b, 1, f->o, f->f );

	f->o = 0;
}

/* Pre: f was opened for writing */
/* Post: The file f has been flushed and is now closed */
void CloseOut( bfile f )
{
	FlushOut( f );

	fclose( f->f );
	f->f = NULL;
}

/* Returns: NZ if the file f has been opened for writing with the name fn */
int OpenOut( bfile f, string fn )
{
	if ( f->f != NULL ) CloseOut( f );

	f->f = fopen( fn, "wb" );
	f->o = 0;

	return f->f != NULL;
}

/* Returns: bfile-tell; the offset from the start */
unsigned long Beatle( bfile f )
{
	return ftell( f->f ) + f->o;
}

/* Pre: f was opened for reading */
/* Returns: The next byte from the file f */
unsigned char InByte( bfile f )
{
	if ( f->o == BUFFSIZE && !feof( f->f ) )
	{
		f->r = fread( f->b, 1, BUFFSIZE, f->f );
		if ( f->r < BUFFSIZE ) f->b[ f->r ] = 0;

		f->o = 0;
	}

	if ( f->o < f->r ) return f->b[ f->o++ ];

	return f->b[ f->o ];
}

/* Post: The file f is now closed */
void CloseIn( bfile f )
{
	fclose( f->f );
	f->f = NULL;
}

/* Returns: NZ if the file f has been opened for reading with the name fn */
int OpenIn( bfile f, string fn )
{
	if ( f->f != NULL ) CloseIn( f );

	f->f = fopen( fn, "rb" );

	f->o = f->r = BUFFSIZE;

	return f->f != NULL;
}

/* Pre: f was opened for reading */
/* Post: f's file pointer has skipped forward n bytes */
void Inskipp( bfile f, unsigned long n )
{
	n += f->o;

	while ( n >= BUFFSIZE && !feof( f->f ) )
	{
		f->r = fread( f->b, 1, BUFFSIZE, f->f );

		if ( f->r < BUFFSIZE ) f->b[ f->r ] = 0;

		n -= BUFFSIZE;
	}
	f->o = n; // May cause an error if was eof
}

/* Returns: All necessary information regarding file f's status */
struct bpos FPos( bfile f )
{
	struct bpos x;

	x.d = *f;
	x.p = ftell( f->f );

	return x;
}

/* Pre: x was the status of f previously */
/* Post: File f has had its status changed to x */
void FGoto( bfile f, struct bpos x )
{
	fseek( f->f, x.p, SEEK_SET );

	*f = x.d;
}

/*
 * Returns: # of bytes written after a variable-length-quantity equivalent
 *    of i has been written to the file f.
 */
int WriteVLQ( bfile f, unsigned long i )
{
	int x = 0;
	unsigned long buffer;

	buffer = i & 127;

	while ( (i >>= 7 ) > 0 )

	buffer = ( ( buffer << 8 ) | 128 ) + ( i & 127 );

	while ( 1 )
	{
		OutByte( f, buffer & 255 );
		x++;
		if ( buffer & 128 ) buffer >>= 8;
		else return x;
	}
}

/* Returns: The number of samples in the Module file f */
int MKTest( bfile f )
{
	unsigned long offset;
	int i = 15;
	char s[4];

	offset = ftell( f->f );

	if ( !fseek( f->f, 1080, SEEK_SET ) )
	{
		fread( s, 1, 4, f->f );

		if ( !memcmp( s, "M.K.", 4 ) || !memcmp( s, "M!K!", 4 ) ||
			!memcmp( s, "FLT4", 4 ) ) i = 31;
		else if ( !memcmp( s, "FLT8", 4 ) ) i = 0;
	}

	fseek( f->f, offset, SEEK_SET );

	return i;
}

/*
 * Returns: A string similar to s, but has had any nasty headers removed
 *    any leading spaces or trailing spaces, and all made lower-case with
 *    any intermediate spaces replaced by underbar-characters
 */
string SimplifyName( string s )
{
	string t, r, x;

	x = strchr( s, ':' );

	if ( x != NULL && x - s == 5 && tolower( s[0] ) == 's' &&
		tolower( s[1] ) == 't' && s[2] == '-' )
	{
		r = x = (string)malloc( 18 );
		t = s + 6;

		while( *(x++) = *(t++) ); // Remove soundtracker header
	} else strcpy( r = (string)malloc( 1 + strlen(s) ), s );

	for ( t = r; *t == ' '; t++ );

	x = r;

	while ( *(x++) = *(t++) ); // Remove leading spaces

	if ( *r )
	{
		x--;

		while ( *(--x) == ' ' ) // Remove trailing spaces

		*x = 0;
	}

	/* Fill intermediate spaces with '_' */
	for ( x = r; *x; x++ ) if ( *x == ' ' ) *x = '_';

	t = r = (string)realloc( r, strlen(r) + 1 );

	for ( ; *t; t++ ) *t = tolower( *t ); // Make the lot lower-case

	return r;
}

/* Returns: Next line from file f, NULL on error/eof */
string GetLine( FILE *f )
{
	string s, t;
	int c;

	if ( ( t = s = (string)malloc( MAXSTRING ) ) == NULL )
	{
		printf( "%s", MSG_ERR_OUT_OF_MEMORY );
		return NULL;
	}

	while ( ( c = fgetc( f ) ) != EOF && c != '\n' ) *(t++) = c;

	if ( s == t )
	{
		free( s );
		return NULL;
	}

	*t = 0;

	return (string)realloc( s, t - s + 1 );
}

/*
 * Returns: Z if f is not a supported Amiga Module, else n is set to
 * be the name of the Module and s is set to hold sample information
 */
int ReadModSpecs( bfile f, string n, samps s )
{
	unsigned char b, c;
	int i, okmodule;
	string t1;
	samp *t2;

	for ( i = 20, t1 = n; i--; *(t1++) = InByte( f ) );

	okmodule = !*n || isprint( *n );
	*t1 = 0;
	c = s->n = MKTest( f );
	okmodule = okmodule && c;

	for ( t2 = s->s; c--; t2++ )
	{
		for ( i = 22, t1 = t2->n; i--; *(t1++) = InByte( f ) );

		okmodule = okmodule && ( !*( t2->n ) || isprint( *(t2->n) ) );
		*t1 = 0;
		b = InByte( f );
		t2->l = 256 * b + InByte( f );

		/* If the sample is this small, not worth processing */
		if ( t2->l < 4 ) t2->l = 0;

		b = InByte( f );
		t2->v = InByte( f );
		InByte( f );
		InByte( f );
		b = InByte( f );

		/* Looping: plays 'forever' */
		if ( 256 * b + InByte( f ) > 1 && t2->l ) t2->l = -1;

		t2->m = 0;
	}

	return !feof( f->f ) && okmodule;
}

/*
 * Post: Sample data for ModFile has been scanned and samples in s have
 * been given "reasonable" values for volume shifting and transpositions
 * I hope :)
 */
void ScanSamples( samps s )
{
	samp *sam;
	unsigned int maxpat = 0, i, j, k;
	unsigned char x;
	signed char c[SCANSIZE];
	signed char max, min, *p;
	unsigned long offset, len, lenarry[31];
	long sum;
	double ratio, junk;

	offset = ftell( ModFile->f );
	fseek( ModFile->f, 42, SEEK_SET );

	for ( i = 0; i < s->n; i++ )
	{
		fread( &x, 1, 1, ModFile->f );
		lenarry[i] = 256 * x;

		fread( &x, 1, 1, ModFile->f );
		lenarry[i] += x;
		lenarry[i] *= 2;

		fseek( ModFile->f, 28, SEEK_CUR );
	}

	fseek( ModFile->f, 20 + 30 * s->n, SEEK_SET );
	fread( &x, 1, 1, ModFile->f );
	fread( c, 1, 129, ModFile->f );

	for ( ; x; x-- ) if ( c[x] > maxpat ) maxpat = c[x];

	len = 1024L * ( maxpat + 1 ) + 30 * s->n + ( ( s->n > 30 ) ? 4 : 0 ) + 150;

	fseek( ModFile->f, len, SEEK_SET );

	for ( i = 0, sam = s->s; i < s->n; i++, sam++ )
	{
		/* Set transposition values to 0 */
		sam->t[0] = 0;
		sam->t[1] = 0;
		sam->t[2] = 0;
		sam->a[0] = 0;

		/* No sample = no volume shifting */
		if ( !sam->l )
		{
			sam->a[1] = 1;
			sam->a[2] = 1;

			fseek( ModFile->f, lenarry[i], SEEK_CUR );
		}
		else
		{
			len = min( lenarry[i], SCANSIZE );

			if ( !( j = len = fread( c, 1, len, ModFile->f ) ) ) len = 1;

			fseek( ModFile->f, lenarry[i] - j, SEEK_CUR );

			min = 127;
			max = -128;
			sum = 0;

			for ( p = c; j--; p++ )
			{
				sum += *p;

				if ( *p > max ) max = *p;

				if ( *p < min ) min = *p;
			}

			/* Get average .. normally ~0 */
			ratio = 1.0 * sum / len;

			if ( fabs( max - ratio ) > fabs( min - ratio ) )
				ratio = fabs( min - ratio ) / 64.0;
			else
				ratio = fabs( max - ratio ) / 64.0;

			j = k = 1;

			/* Now find decent rational approx. */
			while ( k++ < 15 )
				if ( fabs( modf( ratio * k, &junk ) - .5 ) > fabs( modf( ratio * j, &junk ) - .5 ) )
					j = k;

			if ( !( sam->a[1] = ratio * j + 0.5 ) )
			{
				/* If really small scaling needed.. */
				sam->a[1] = 1;
				sam->a[2] = 16;
			}
			else sam->a[2] = j;
		}
	}

	fseek( ModFile->f, offset, SEEK_SET );
}

/*
 * Returns: NZ if the samples in s have been sucessfully allocated default
 *    values corresponding to definitions in the DEF_MAPFILE file, and from
 *    a .mm file corresponding to the filename fn
 */
int SetDefaults( samps s, string fn )
{
	FILE *f;
	char i, m[MAXSTRING];
	int d, e[6], v;
	samp *sam;
	string n, t;
	bfile mmf;

	t = strchr( strcpy( m, fn ), '.' );

	if ( t == NULL )
	{
		i = strlen( m );
		m[i] = '.';
	}
	else i = t - m;

	m[++i] = 'm';
	m[++i] = 'm';
	m[++i] = 0;
	mmf->f = NULL;

	if ( OpenIn( mmf, m ) )
	{
		for ( i = s->n, sam = s->s; i--; sam++ )
		{
			sam->m = InByte( mmf );
			sam->t[0] = (signed char)InByte( mmf );
			sam->t[1] = (signed char)InByte( mmf );
			sam->t[2] = (signed char)InByte( mmf );
			sam->a[0] = (signed char)InByte( mmf );
			sam->a[1] = (signed char)InByte( mmf );
			sam->a[2] = (signed char)InByte( mmf );
		}

		CloseIn( mmf );
	}
	else ScanSamples( s );

	if ( ( f = fopen( DEF_MAPFILE, "rt" ) ) == NULL )
	{
		printf( "%s: Not found\n", DEF_MAPFILE );
		return 0;
	}

	i = s->n;
	for ( sam = s->s; i--; sam++ )
	if ( sam->l )
	{
		n = SimplifyName( sam->n );
		t = GetLine( f );
		e[3] = 0; // These are the default volume constants
		e[4] = 1;
		e[5] = 1;
		sscanf( t, "%s %d %d %d %d %d %d %d", m, &d, &e[0], &e[1], &e[2], &e[3], &e[4], &e[5] );

		if ( ( v = strcmp( m, n ) ) > 0 )
		{
			rewind( f );
			free( t );
			t = GetLine( f );
			e[3] = 0;
			e[4] = 1;
			e[5] = 1;
			sscanf( t, "%s %d %d %d %d %d %d %d", m, &d, &e[0], &e[1], &e[2], &e[3], &e[4], &e[5] );
			v = strcmp( m, n );
		}

		free( t );

		while ( v < 0 && ( t = GetLine( f ) ) != NULL )
		{
			e[3] = 0;
			e[4] = 1;
			e[5] = 1;
			sscanf( t, "%s %d %d %d %d %d %d %d", m, &d, &e[0], &e[1], &e[2], &e[3], &e[4], &e[5] );
			free( t );
			v = strcmp( m, n );
		}

		if ( !v )
		{
			sam->m = d;
			sam->t[0] = e[0];
			sam->t[1] = e[1];
			sam->t[2] = e[2];
			sam->a[0] = e[3];
			sam->a[1] = e[4];
			sam->a[2] = e[5];
		}

		free( n );
	}

	fclose( f );
	return 1;
}

/*
 * Post: The samples attributes in s have been written to a .mm file
 *   corresponding to the filename fn
 */
void SaveDefaults( samps s, string fn )
{
	char m[MAXSTRING];
	string t;
	char i;
	bfile mmf;
	samp *sam;

	t = strchr( strcpy( m, fn ), '.' );

	if ( t == NULL )
	{
		i = strlen( m );
		m[i] = '.';
	}
	else i = t - m;

	m[++i] = 'm';
	m[++i] = 'm';
	m[++i] = 0;
	mmf->f = NULL;

	if ( !OpenOut( mmf, m ) ) return;

	for ( i = s->n, sam = s->s; i--; sam++ )
	{
		OutByte( mmf, sam->m );
		OutByte( mmf, sam->t[0] );
		OutByte( mmf, sam->t[1] );
		OutByte( mmf, sam->t[2] );
		OutByte( mmf, sam->a[0] );
		OutByte( mmf, sam->a[1] );
		OutByte( mmf, sam->a[2] );
	}

	CloseOut( mmf );
}

/* Post: The NULL-terminated array sp is gone */
void NullArryFree( string *sp )
{
	string *t;

	t = sp;

	while ( *t != NULL ) free( *(t++) );

	free( sp );
}

/* Returns: MIDI instrument selected from file DEF_INSFILE */
int Instrument( void )
{
	static int w = 0, doff = -1;
	static string *sp = NULL;
	int c;

	if ( sp == NULL )
	{
		FILE *f;
		string *t, s;
		int x, i = 1;

		if ( ( f = fopen( DEF_INSFILE, "rt" ) ) == NULL )
		{
			printf( "%s: Not found\n", DEF_INSFILE );
			return -1;
		}

		if ( ( t = sp = (string *)malloc( 257 * sizeof( string ) ) ) == NULL )
		{
			printf( "%s", MSG_ERR_OUT_OF_MEMORY );
			return -1;
		}

		while ( ( s = GetLine( f ) ) != NULL )

		if ( x = strlen( s ) ) // Ignore blank lines
		{
			i++;

			if ( x > w ) w = x;

			*(t++) = s;

			/* Take note of first drum position */
			if ( doff < 0 && *s == 'D' ) sscanf( s, "D%d ", &doff );
		}

		*t = NULL;
		sp = (string *)realloc( sp, i * sizeof( string ) ); // i <= 257

		fclose( f );
	}

	c = 0;
	c = ScrollChoice( "MIDI Instruments", sp, w );
	if ( c > 127 ) c += doff;

	return c;
}

/* Returns: a string representing the MIDI instrument *sam */
string MIDIVal( samp *sam )
{
	string s;

	s = (string)malloc( 5 );

	if ( sam->m < 128 ) sprintf( s, "%4d", sam->m );
	else sprintf( s, "D%3d", sam->m - 128 );

	return s;
}

/* Returns: a string representing the transpose amount of *sam */
string TransVal( samp *sam )
{
	string s;

	s = (string)malloc( 15 );

	if ( !sam->t[1] ) sprintf( s, "          %4d", sam->t[0] );
	else
	if ( !sam->t[2] ) sprintf( s, "     %4d,%4d", sam->t[0], sam->t[1] );
	else sprintf( s, "%4d,%4d,%4d", sam->t[0], sam->t[1], sam->t[2] );

	return s;
}

/* Returns: a string representing the volume shifting formula for *sam */
string VolumeVal( samp *sam )
{
	string s;

	s = (string)malloc( 17 );

	if ( sam->a[0] < 0 )
		sprintf( s, "(? -%3d)*%3d/%3d", abs(sam->a[0]), sam->a[1], sam->a[2] );
	else
		sprintf( s, "(? +%3d)*%3d/%3d", sam->a[0], sam->a[1], sam->a[2] );

	return s;
}

/* Returns: an empty string */
string NullVal( samp *sam )
{
	string s;

	*( s = (string)malloc( 1 ) ) = 0;

	return s;
}

/* Returns: Sample selected from list of samples, -1 on error */
int Sample( samps s, string (*MIDIVal)(samp *x), int w )
{
	string *sp, *t, p;
	samp *sam;
	int i;

	if ( ( t = sp = (string *)malloc( ( s->n + 1 ) * sizeof( string ) ) ) == NULL )
	{
		printf( "%s", MSG_ERR_OUT_OF_MEMORY );
		return -1;
	}

	for ( i = s->n, sam = s->s; i--; sam++ )
	{
		*t = (string)malloc( 25 + w );
		p = MIDIVal( sam );

		sprintf( *t, "%c%-22s %s", (sam->l) ? '*' : ' ', sam->n, p );
		free( p );
		t++;
	}

	*t = NULL;
	i = ScrollChoice( "Select Sample", sp, 24 + w );
	NullArryFree( sp );

	return i;
}

/*
 * Returns: The number of different channels needed to play instruments. If
 *    that number is not greater than 16, upto 16 channels will be allocated
 *    to the samples.
 */
int ChooseChannels( samps s )
{
	unsigned char c, d = 0, i[128], m, n, numchan;
	samp *sam1, *sam2;

	n = s->n;
	sam1 = s->s;
	memset( i, 0, 128 );

	for ( n = s->n, sam1 = s->s; n--; sam1++ )
	{
		sam1->c = -1;
		if ( sam1->l )
			if ( sam1->m > 127 )
			{
				d = 1;
				sam1->c = DrumChann;
			}
			else i[ sam1->m ] = 1;
		else sam1->m = 0;
	}

	for ( numchan = d, n = 128; n--; numchan += i[n] );

	if ( numchan > 16 ) return numchan;

	/* Ok.. now we must go through and set channels appropriately */
	m = s->n;
	sam1 = s->s;
	c = 0;
	while ( m-- )
	{
		if ( sam1->c < 0 )
		{
			sam1->c = c;
			n = m;
			sam2 = sam1 + 1;

			while ( n-- )
			{
				if ( sam2->c < 0 )
					if ( sam2->m == sam1->m || ! sam2->l ) sam2->c = c;
				sam2++;
			}

			if ( ++c == DrumChann && d )
			c++;
		}

		sam1++;
	}

	return numchan;
}

/* Post: The samples s have been allocated appropriate instruments, we hope */
void MapSamples( samps s )
{
	int i, j;

	do
		if ( ( i = Sample( s, MIDIVal, 4 ) ) >= 0 && ( j = Instrument() ) >= 0 )
			s->s[i].m = j;
	while ( i>=0 );
}

/* Post: The sample sam has been updated in the DEF_MAPFILE file */
void SaveSamp( samp sam )
{
	FILE *f1, *f2;
	string s, n;
	int v, d;
	char m[MAXSTRING];

	if ( !sam.l ) return;

	if ( ( f1 = fopen( DEF_MAPFILE, "rt" ) ) == NULL )
	{
		printf( "%s: Not found\n", DEF_MAPFILE );
		return;
	}

	f2 = fopen( "temp.$$$", "wt" );
	n = SimplifyName( sam.n );
	v = 1;

	while ( v > 0 && ( s = GetLine( f1 ) ) != NULL )
	{
		sscanf( s, "%s %d", m, &d );

		if ( ( v = strcmp( n, m ) ) <= 0 )
		{
			fprintf( f2, "%s %d %d %d %d %d %d %d\n", n, sam.m, sam.t[0], sam.t[1], sam.t[2], sam.a[0], sam.a[1], sam.a[2] );

			free( n );
			n = NULL;

			if ( v ) fprintf( f2, "%s\n", s );
		}
		else fprintf( f2, "%s\n", s );
		free( s );
	}

	while ( ( s = GetLine( f1 ) ) != NULL )
	{
		fprintf( f2, "%s\n", s );
		free( s );
	}

	if ( n != NULL )
	{
		fprintf( f2, "%s %d %d %d %d %d %d %d\n", n, sam.m, sam.t[0], sam.t[1], sam.t[2], sam.a[0], sam.a[1], sam.a[2] );
		free( n );
	}

	fclose( f2 );
	fclose( f1 );

	if ( unlink( DEF_MAPFILE ) < 0 || rename( "temp.$$$", DEF_MAPFILE ) < 0 )
		printf( "File %s cannot be updated. Instrument map changes cannot be made\n" );
}

/* Post: The desired sample attributes of s have been saved to disk */
void SaveSamples( samps s )
{
	int i;

	do
		if ( ( i = Sample( s, NullVal, 0 ) ) >= 0 ) SaveSamp( s->s[i] );
	while ( i >= 0 );
}

/* Post: All necessary transpositions have been applied to the samples s */
void Transpositions( samps s )
{
	int i;

	do
	if ( ( i = Sample( s, TransVal, 14 ) ) >= 0 )
	{
		char s1[41];
		string s2;
		samp *sam;

		sam = s->s + i;

		if ( !sam->t[1] ) sprintf( s1, "%d", sam->t[0] );
		else
		if ( !sam->t[2] ) sprintf( s1, "%d,%d", sam->t[0], sam->t[1] );
		else sprintf( s1, "%d,%d,%d", sam->t[0], sam->t[1], sam->t[2] );

		s2 = (string)malloc( 10 );
		strcpy( s2, "0,1,1" );
		s2 = DialogBox( MSG_ENTER_TRANSPOSITION, s1 );

		sam->t[1] = 0;
		sam->t[2] = 0;

		sscanf( s2, "%d,%d,%d", &sam->t[0], &sam->t[1], &sam->t[2] );
		free( s2 );

		if ( ( sam->t[0] || sam->t[1] ) && sam->m > 127 )
			printf( "Warning: transposition value for percussion instrument is changed\n" );

		if ( sam->t[0] <- 128 || sam->t[0] > 127 || sam->t[1] < -128 ||
		 sam->t[1] > 127 || sam->t[2] <- 128 || sam->t[2] > 127 )
		{
			printf( "Warning: Transposition value out of range, resetting to 0\n" );
			sam->t[0] = 0;
			sam->t[1] = 0;
			sam->t[2] = 0;
		}
	}
	while ( i >= 0 );
}

/* Post: All volume shifting formulae have been defined for the samples s */
void VolumeShifts( samps s )
{
	int i;

	do
	{
		i = Sample( s, VolumeVal, 16 );

		if ( i >= 0 )
		{
			char s1[41];
			string s2;
			samp *sam;

			sam = s->s + i;
			sprintf( s1, "%d,%d,%d", sam->a[0], sam->a[1], sam->a[2] );

			s2 = (string)malloc( 10 );
			s2 = DialogBox( MSG_ENTER_VOLUMESHIFT, s1 );

			if ( sscanf( s2, "%d,%d,%d", &sam->a[0], &sam->a[1], &sam->a[2]) != 3 ||
				sam->a[0] > 127 || sam->a[0] < -128 || sam->a[1] > 127 ||
				sam->a[1] < 0 || sam->a[2] > 127 || sam->a[2] < 1 )
			{
				printf( "Warning: volume-shift values out of range, resetting to 0, 1, 1\n" );
				sam->a[0] = 0;
				sam->a[1] = 1;
				sam->a[2] = 1;
			}

			free( s2 );
		}
	}
	while ( i >= 0 );
}

/* Returns: MIDI note equivalent of MOD period-lengths */
unsigned char NoteValue( unsigned int n )
{
	static unsigned int t[72] =
	{
		1712, 1616, 1525, 1440, 1357, 1281, 1209, 1141, 1077, 1017, 961, 907,
		856, 808, 762, 720, 678, 640, 604, 570, 538, 508, 480, 453,
		428, 404, 381, 360, 339, 320, 302, 285, 269, 254, 240, 226,
		214, 202, 190, 180, 170, 160, 151, 143, 135, 127, 120, 113,
		107, 101, 95, 90, 85, 80, 76, 71, 67, 64, 60, 57,
		53, 50, 48, 45, 42, 40, 38, 36, 34, 32, 30, 28
	};

	signed char a = 0, m = 35, b = 71;

	if ( !n ) return 0;

	/* Binary search */
	for ( ; b-a > 1; m = ( a + b ) / 2 )
	if ( t[m] < n ) b = m;
	else a = m;

	/* Choose closest value */
	if ( n - t[b] < t[a] - n ) return (unsigned char)( b + 36 );

	return (unsigned char )( a + 36 );
}

/* Returns: # of ticks to play note length l at pitch n, b beats/min */
unsigned long NoteLength( unsigned char n, unsigned int l, unsigned int b )
{
	static float t[84] =
	{
		3.200e-3, 3.020e-3, 2.851e-3, 2.691e-3, 2.540e-3, 2.397e-3,
		2.263e-3, 2.136e-3, 2.016e-3, 1.903e-3, 1.796e-3, 1.695e-3,
		1.600e-3, 1.510e-3, 1.425e-3, 1.345e-3, 1.270e-3, 1.197e-3,
		1.131e-3, 1.068e-3, 1.008e-3, 9.514e-4, 8.980e-4, 8.476e-4,
		8.000e-4, 7.551e-4, 7.127e-4, 6.727e-4, 6.350e-4, 5.993e-4,
		5.657e-4, 5.339e-4, 5.040e-4, 4.757e-4, 4.490e-4, 4.238e-4,
		4.000e-4, 3.775e-4, 3.564e-4, 3.364e-4, 3.175e-4, 2.997e-4,
		2.828e-4, 2.670e-4, 2.520e-4, 2.378e-4, 2.245e-4, 2.119e-4,
		2.000e-4, 1.888e-4, 1.782e-4, 1.682e-4, 1.587e-4, 1.498e-4,
		1.414e-4, 1.335e-4, 1.260e-4, 1.189e-4, 1.122e-4, 1.059e-4,
		1.000e-4, 9.439e-5, 8.909e-5, 8.409e-5, 7.937e-5, 7.492e-5,
		7.071e-5, 6.674e-5, 6.300e-5, 5.946e-5, 5.612e-5, 5.297e-5,
		5.000e-5, 4.719e-5, 4.454e-5, 4.204e-5, 3.969e-5, 3.746e-5,
		3.536e-5, 3.337e-5, 3.150e-5, 2.973e-5, 2.806e-5, 2.649e-5
	}; /* Multipliers for each pitch: 12th roots of 2 apart */

	/* Better not slide out of this range */
	return (unsigned long)( t[n - 36] * b * l );
}

/* Post: The MIDI header has been written to mf, with #tracks = n */
void WriteHeader( bfile mf, unsigned char n )
{
	static unsigned char MIDIH[14] =
	{
		77, 84, 104, 100, 0, 0, 0, 6, 0, 1, 0, (unsigned char)-1, 0, 192
	};

	int i = 0;

	MIDIH[11] = n + 1;

	while ( i < 14 )

	OutByte( mf, MIDIH[i++] );
}

/*
 * Returns: the number of bytes written as track 0 so far, given mf as the
 *    output MIDI file. s is the name of the tune.
 */
unsigned int Trk0Info( bfile mf, string s )
{
	static unsigned char TRK0I[63] =
	{
		0, 255, 2, 42, 70, 105, 108, 101, 32, 67, 111, 112, 121, 114, 105, 103,
		104, 116, 32, 40, 99, 41, 32, 49 ,57, 57, 51, 32, 65, 100, 114, 101, 110,
		97, 108, 105, 110, 32, 83, 111, 102, 116, 119, 97, 114, 101,
		0, 255, 88, 4, 3, 2, 24, 8,
		0, 255, 89, 2, 0, 0,
		0, 255, 3
	}; /* Standard header + copyright message */

	unsigned int i = 0;

	while ( i < 63 ) OutByte( mf, TRK0I[i++] );

	i = 64 + strlen( s );

	OutByte( mf, strlen( s ) );

	while ( *s ) OutByte( mf, *(s++) );

	return i;
}

/* Post: a (file position) and b (number) have been added to the log */
void AddToLog( unsigned long a, unsigned long b )
{
	PosLog[ PosI++ ] = b;
	PosLog[ PosI++ ] = a;
}

/* Post: PosLog has been written into file f */
void WriteLog( FILE *f )
{
	unsigned long x, y;
	unsigned char c[4];

	if ( !PosI ) return;

	x = ftell( f );

	while ( PosI )
	{
		fseek( f, PosLog[ --PosI ], SEEK_SET );

		y = PosLog[ --PosI ];

		c[3] = y & 255;
		y >>= 8;
		c[2] = y & 255;
		y >>= 8;
		c[1] = y & 255;
		y >>= 8;
		c[0] = y;
		fwrite( c, 1, 4, f );
	}

	fseek( f, x, SEEK_SET );
}

/* Returns: A volume in the range 0..127 */
unsigned char RestrictVol( int v )
{
	if ( v < 0 ) return 0;
	else return (unsigned char)( ( v > 127 ) ? 127 : v );
}

/*
 * Post: The Amiga MODfile f1 has been converted and written to MIDI file f2,
 *    using instrument mappings smp. Tune has a name tn.
 */
void ConvertMOD( bfile f1, bfile f2, string tn, samps smp )
{
	unsigned char b, length, pattern[128];
	string n;
	unsigned int i, j, k, tempdone = 0;
	samp *sam;
	struct bpos p1, p2;

	p1 = FPos( f1 );
	length = InByte( f1 );
	InByte( f1 );

	for ( i = 0; i < 128; pattern[i++] = InByte( f1 ) );

	Inskipp( f1, ( smp->n > 15 ) ? 4 : 0 );
	WriteHeader( f2, smp->n );
	p2 = FPos( f1 );

	for ( i = 0, sam = smp->s - 1; i <= smp->n; sam++, i++ )
	{
		unsigned long cnt, inst, timer, delay[4];
		unsigned char c;

		OutByte( f2, 77 );
		OutByte( f2, 84 );
		OutByte( f2, 114 );
		OutByte( f2, 107 );

		inst = Beatle( f2 ); /* Store this position so can set AFTERWARDS */

		OutByte( f2, 0 );
		OutByte( f2, 0 );
		OutByte( f2, 0 );
		OutByte( f2, 0 );

		if ( !i ) cnt = Trk0Info( f2, tn );
		else
		{
		OutByte( f2, 0 );
		OutByte( f2, (unsigned char)255 );
		OutByte( f2, 3 );

		b = strlen( n = sam->n );

		OutByte( f2, b );

		cnt = 7 + b;

		while ( b-- ) OutByte( f2, *(n++) );

		c = sam->c;

		OutByte( f2, 0 );
		OutByte( f2, 0xC0 + c ); // Set channel
		OutByte( f2, ( sam->m > 127 ) ? 126 : sam->m );
	}

	timer = 0;

	if ( sam->l || !i )
	{
		unsigned int bpm, ticks, l, effect, h;
		unsigned char sampnum, lastsam[4] = { 0 }, vol[4];
		signed char patbreak;
		unsigned long x, pause;
		int note, lastslide, slideto;
		int n[4][48][2]; // Note data for a song position
		int lastn[4]; // Last note on a particular channel

		printf( "Converting sample %d\n", i );

		memset( lastn, 0, 4 * sizeof( int ) );
		memset( vol, 0, 4 );
		memset( delay, 0, 4 * sizeof( long ) );

		bpm = 125;
		ticks = 6;
		lastslide = slideto = 0;
		patbreak = 0;

		for ( h = 48; h--; )
		for ( k = 4; k--; )
			n[k][h][0] = 0;
			for ( l = 0; l < length; l++ )
			{
				if ( pattern[l] <= pattern[ l - 1 ] || !l )
				{
					FGoto( f1, p2 );
					x = (unsigned)1024 * pattern[l];
				}
				else x = (unsigned)1024 * ( pattern[l] - pattern[ l - 1 ] - 1 );

				Inskipp( f1, x );
				j = 64;

				if ( patbreak > 0 ) patbreak = 1 - patbreak;

				while ( j-- )
				{
					pause = 0;

					if ( patbreak ) Inskipp( f1, 16 );
					else
					for ( k = 0; k < 4; k++ )
					{
						n[k][0][1] = sam->v;
						sampnum = InByte( f1 );
						note = ( ( sampnum & 15 ) << 8 ) + InByte( f1 );
						effect = InByte( f1 );
						sampnum = ( sampnum & ~15 ) + ( effect >> 4 );

						if ( !i ) note = 0;

						if ( ( note || sampnum ) && delay[k] )
						{
							/* Stop old note */
							cnt += 3 + WriteVLQ( f2, timer );
							timer = 0;

							OutByte( f2, 0x80 + c ); // Note off
							OutByte( f2, ENOTE( lastn[k], 0 ) );
							OutByte( f2, RestrictVol( EVOL( vol[k] ) ) );

							if ( sam->t[1] )
							{
								cnt += 4;

								OutByte( f2, 0 );
								OutByte( f2, 0x80 + c );
								OutByte( f2, ENOTE( lastn[k], 1 ) );
								OutByte( f2, RestrictVol( EVOL( vol[k] ) ) );

								if ( sam->t[2] )
								{
									cnt += 4;

									OutByte( f2, 0 );
									OutByte( f2, 0x80 + c );
									OutByte( f2, ENOTE( lastn[k], 2 ) );
									OutByte( f2, RestrictVol( EVOL( vol[k] ) ) );
								}
							}
							delay[k] = 0;
						}

						/* Check "defaults" */
						if ( !note && sampnum == i ) note = lastn[k];
						else
						if ( !sampnum )
							if ( lastsam[k] == i ) sampnum = i;
							else note = 0;
						else
						{
							if ( sampnum != i ) note = 0;
							lastsam[k] = sampnum;
						}

						n[k][0][0] = note;
						effect = ( ( effect & 15 ) << 8 ) + InByte( f1 );

						switch ( effect & 0xF00 )
						{
							/* Arpeggio */
							case 0x000:
								{
									int nv;

									if ( !i || !effect || sam->m > 127 ) break;
									if ( !note )
										if ( !delay[k] ) break;
										else
										{
											nv = NoteValue( lastn[k] );
											n[k][47][0] = lastn[k];
											n[k][47][1] = vol[k];

											if ( effect & 0x0F0 ) n[k][16][0] = -( nv + ( ( effect & 0x0F0 ) >> 4 ) );
											n[k][16][1] = vol[k];

											if ( effect & 0x00F ) n[k][32][0] = -( nv + ( effect & 0x00F ) );
											n[k][32][1] = vol[k];
										}
									else
									{
										nv = NoteValue( note );
										n[k][47][0] = note;
										n[k][47][1] = sam->v;

										if ( effect & 0x0F0 ) n[k][16][0] = -( nv + ( ( effect & 0x0F0 ) >> 4 ) );
										n[k][16][1] = sam->v;

										if ( effect & 0x00F ) n[k][32][0] = -( nv + ( effect & 0x00F ) );
										n[k][32][1] = sam->v;
									}
								break;
							}

						/* Slide to */
						case 0x300:
							if ( !note && !slideto || note == lastn[k] || sam->m > 127 ) break;

							if ( effect & 0x0FF ) lastslide = effect & 0x0FF;
							else lastslide = abs( lastslide );

							if ( note ) slideto = note;

							if ( slideto > lastn[k] )
							{
								n[k][0][0] = lastn[k] + lastslide*( ticks - 1 );
								if ( n[k][0][0] > 856 ) n[k][0][0] = 856;

								if ( n[k][0][0] > slideto ) n[k][0][0] = slideto;
							}
							else
							{
								n[k][0][0] = lastn[k] - lastslide * ( ticks - 1 );
								if ( n[k][0][0] < 113 ) n[k][0][0] = 113;

								if ( n[k][0][0] < slideto ) n[k][0][0] = slideto;
							}

							n[k][0][1] = vol[k];
						break;

						/* Slide up */
						case 0x100:
						/* Slide down */
						case 0x200:
							if ( ! ( effect & 0x0FF ) || sam->m > 127 ) break;

							if ( effect & 0x200 ) lastslide = effect & 0x0FF;
							else lastslide = -( effect & 0x0FF );

							if ( !note )
								if ( !delay[k] ) break;
								else
								{
									n[k][0][0] = lastn[k] + lastslide;
									n[k][0][1] = vol[k];
								}
							else n[k][0][0] += lastslide;

							if ( n[k][0][0] > 856 ) n[k][0][0] = 856;
							else
							if ( n[k][0][0] < 113 ) n[k][0][0] = 113;
						break;

						/* Vibrato */
						case 0x400:
						/* Tremolo */
						case 0x700:
							// Ignore these effects.. not convertable
						break;

						/* Slide to & volume slide */
						case 0x500:
							if ( ( note || slideto ) && note != lastn[k] && sam->m < 128 )
							{
								if ( note ) slideto = note;

								if ( slideto > lastn[k] )
								{
									n[k][0][0] = lastn[k] + lastslide * ( ticks - 1 );
									if ( n[k][0][0] > 856 ) n[k][0][0] = 856;

									if ( n[k][0][0] > slideto ) n[k][0][0] = slideto;
								}
								else
								{
									n[k][0][0] = lastn[k] - lastslide * ( ticks - 1 );
									if ( n[k][0][0] < 113 ) n[k][0][0] = 113;

									if ( n[k][0][0] < slideto ) n[k][0][0] = slideto;
								}
							}
							else n[k][0][0] = 0;

							note = 0;

						/* Vibrato & volume slide */
						case 0x600:
						/* Volume slide */
						case 0xA00:
							{
								int v;

								if ( !note ) v = vol[k];
								else v = sam->v;

								v += ( ticks - 1 ) * ( effect & 0x0F0 ); // Can't really slide
								v -= ( ticks - 1 ) * ( effect & 0x00F );

								if ( v > 127 ) v = 127;
								else if ( v < 0 ) v = 0;

								n[k][0][1] = v;
								break;
							}

						/* Set offset: pretend it's retrigger */
						case 0x900:
							if ( ( !n[k][0][0] || !sampnum ) && delay[k] )
							{
								n[k][0][0] = lastn[k];
								n[k][0][1] = vol[k];
							}
						break;

						/* Position jump: ignore, but break anyway */
						case 0xB00:
							patbreak = 1;
						break;

						/* Pattern break */
						case 0xD00:
							patbreak = 1 + 10 * ( effect & 0x0F0 ) + ( effect & 0x00F );
						break;

						/* Set volume */
						case 0xC00:
							n[k][0][1] = effect & 0x0FF;
						break;

						/* Set tempo */
						case 0xF00:
							{
								int temp;

								temp = effect & 0x0FF;
								if ( !temp ) temp = 1;

								if ( temp < 32 )
								{
									ticks = temp;

									/* Tempos act strangely */
									if ( TempoType )
									{
										bpm = 750 / temp;
										x = 80000 * temp;
									}
								}
								else
								{
									bpm = temp;
									x = 60000000 / temp;
								}

								if ( i ) break; // Only write tempo on track 0

								cnt += 6 + WriteVLQ( f2, timer );
								timer = 0;

								OutByte( f2, (unsigned char)255 ); // Meta-event
								OutByte( f2, 81 ); // Set tempo
								OutByte( f2, 3 );
								OutByte( f2, x >> 16 );
								OutByte( f2, ( x >> 8 ) & 0xFF );
								OutByte( f2, x & 0xFF );
								tempdone = 1;

								break;
							}

						/* Extended effects */
						case 0xE00:
							switch ( effect & 0x0F0 )
							{
								/* Fine slide up */
								case 0x010:
									if ( !( effect & 0x00F ) || sam->m > 127 ) break;

									if ( !note )
										if ( !delay[k] ) break;
										else
										{
											n[k][h][0] = lastn[k] + ( effect & 0x00F );
											n[k][h][1] = vol[k];
										}
									else n[k][h][0] += effect & 0x00F;
								break;

								/* Fine slide down */
								case 0x020:
									if ( !( effect & 0x00F ) || sam->m > 127 ) break;

									if ( !note )
										if ( !delay[k] ) break;
										else
										{
											n[k][h][0] = lastn[k] - ( effect & 0x00F );
											n[k][h][1] = vol[k];
										}
									else n[k][h][0] -= effect & 0x00F;
								break;

								case 0x000: /* Set filter on/off */
								case 0x030: /* glissando on/off */
								case 0x040: /* set vibrato wave */
								case 0x050: /* set finetune */
								case 0x060: /* pattern loop */
								case 0x070: /* set tremolo wave */
								case 0x080: /* un-used */
								case 0x0F0: /* invert loop */
									// Can't do these in MIDI.. ignore
								break;

								/* Fine volume slide up */
								case 0x0A0:
								/* Fine volume slide down */
								case 0x0B0:
									{
										int v;

										v = sam->v;
										if ( effect & 0x0A0 ) v += effect & 0x00F;
										else v -= effect & 0x00F;

										if ( v < 0 ) v = 0;
										else
										if ( v > 127 ) v = 127;

										n[k][0][1] = v;

										break;
									}

								/* Retrigger sample */
								case 0x090:
									{
										int a, b, c;

										if ( !note && !delay[k] || !( effect & 0x00F ) ) break;

										a = effect & 0x00F;
										if ( !( ticks / a ) ) break;

										if ( !note )
										{
											n[k][0][0] = lastn[k];
											n[k][0][1] = vol[k];
										}

										c = 0;
										b = 1;
										a *= 48;

										while ( c < 48 )
										{
											n[k][c][0] = note;
											n[k][c][1] = n[k][0][1];
											c = b * a / ticks;
											b++;
										}

										break;
									}

								/* Cut sample */
								case 0x0C0:
									{
										int a;

										if ( !note && !delay[k] ) break;

										a = 48 * ( effect & 0x00F ) / ticks;

										if ( a > 47 ) break;

										if ( note ) n[k][a][0] = note;
										else n[k][a][0] = lastn[k];

										n[k][a][1] = 0;

										break;
									}

								/* Delay sample */
								case 0x0D0:
									{
										int a;

										if ( !note || !( effect & 0x00F ) ) break;

										a = 48 * ( effect & 0x00F ) / ticks;
										n[k][0][0] = 0;

										if ( a > 47 ) break;

										n[k][a][0] = note;
										n[k][a][1] = n[k][a][0];

										break;
									}

								/* Pattern pause */
								case 0x0E0:
									pause = 48 * ( effect & 0x00F );
								break;
							}
						break;
						}
					}

					for ( h = 0; h < 48; h++ )
					{
						for ( k = 0; k < 4; k++ )
						if ( n[k][h][0] )
						{
							/* Turn off old note on same channel */
							if ( delay[k] )
							{
								cnt += 3 + WriteVLQ( f2, timer );
								timer = 0;

								OutByte( f2, 0x80 + c ); // Note off
								OutByte( f2, ENOTE( lastn[k], 0 ) );
								OutByte( f2, RestrictVol( EVOL( vol[k] ) ) );

								if ( sam->t[1] )
								{
									cnt += 4;
									OutByte( f2, 0 );
									OutByte( f2, 0x80 + c );
									OutByte( f2, ENOTE( lastn[k], 1 ) );
									OutByte( f2, RestrictVol( EVOL( vol[k] ) ) );

									if ( sam->t[2] )
									{
										cnt += 4;

										OutByte( f2, 0 );
										OutByte( f2, 0x80 + c );
										OutByte( f2, ENOTE( lastn[k], 2 ) );
										OutByte( f2, RestrictVol( EVOL( vol[k] ) ) );
									}
								}
								delay[k] = 0;
							}

							lastn[k] = n[k][h][0];
							n[k][h][0] = 0;
							vol[k] = n[k][h][1];
							cnt += 3 + WriteVLQ( f2, timer );
							timer = 0;

							OutByte( f2, 0x90 + c ); // Note on
							OutByte( f2, ENOTE( lastn[k], 0 ) );
							OutByte( f2, RestrictVol( EVOL( vol[k] ) ) );

							if ( sam->t[1] )
							{
								cnt += 4;

								OutByte( f2, 0 );
								OutByte( f2, 0x90 + c );
								OutByte( f2, ENOTE( lastn[k], 1 ) );
								OutByte( f2, RestrictVol( EVOL( vol[k] ) ) );

								if ( sam->t[2] )
								{
									cnt += 4;

									OutByte( f2, 0 );
									OutByte( f2, 0x90 + c );
									OutByte( f2, ENOTE( lastn[k], 2 ) );
									OutByte( f2, RestrictVol( EVOL( vol[k] ) ) );
								}
							}

							delay[k] = NoteLength( ANOTE( lastn[k] ), sam->l, bpm );
						}
						else
						if ( delay[k] == 1 )
						{
							delay[k] = 0;
							cnt += 3 + WriteVLQ( f2, timer );

							timer = 0;

							OutByte( f2, 0x80 + c ); // Note off
							OutByte( f2, ENOTE( lastn[k], 0 ) );
							OutByte( f2, RestrictVol( EVOL( vol[k] ) ) );

							if ( sam->t[1] )
							{
								cnt += 4;

								OutByte( f2, 0 );
								OutByte( f2, 0x80 + c );
								OutByte( f2, ENOTE( lastn[k], 1 ) );
								OutByte( f2, RestrictVol( EVOL( vol[k] ) ) );

								if ( sam->t[2] )
								{
									cnt += 4;

									OutByte( f2, 0 );
									OutByte( f2, 0x80 + c );
									OutByte( f2, ENOTE( lastn[k], 2 ) );
									OutByte( f2, RestrictVol( EVOL( vol[k] ) ) );
								}
							}
						}
						else
						if ( delay[k] > 0 ) delay[k]--;

							timer++;
					}

					timer += pause;

					if ( patbreak < 0 ) patbreak++;
					else
					if ( patbreak > 0 )
					{
						patbreak = 1 - patbreak;

						while ( j )
						{
							j--;
							Inskipp( f1, 16 ); // 16 bytes / song position
						}
					}
				}
			}

			for ( k = 0; k < 4; k++ )
			if ( delay[k] )
			{
				cnt += 3 + WriteVLQ( f2, timer );
				timer = 0;

				OutByte( f2, 0x80 + c ); // Note off
				OutByte( f2, ENOTE( lastn[k], 0 ) );
				OutByte( f2, RestrictVol( EVOL( vol[k] ) ) );

				if ( sam->t[1] )
				{
					cnt += 4;

					OutByte( f2, 0 );
					OutByte( f2, 0x80 + c );
					OutByte( f2, ENOTE( lastn[k], 1 ) );
					OutByte( f2, RestrictVol( EVOL( vol[k] ) ) );

					if ( sam->t[2] )
					{
						cnt += 4;

						OutByte( f2, 0 );
						OutByte( f2, 0x80 + c );
						OutByte( f2, ENOTE( lastn[k], 2 ) );
						OutByte( f2, RestrictVol( EVOL( vol[k] ) ) );
					}
				}
			}
		}

		if ( !i && !tempdone )
		{
			cnt += 7;

			OutByte( f2, 0 ); // Give the default 128 bpm if none done yet
			OutByte( f2, (unsigned char)255 );
			OutByte( f2, 81 );
			OutByte( f2, 3 );
			OutByte( f2, 7 );
			OutByte( f2, 39 );
			OutByte( f2, 14 );
		}

		cnt += 3 + WriteVLQ( f2, timer );

		OutByte( f2, (unsigned char)255 );
		OutByte( f2, 47 );
		OutByte( f2, 0 );

		AddToLog( inst, cnt ); // Process this later

		FGoto( f1, p2 );
	}

	FlushOut( f2 );
	WriteLog( f2->f );
	FGoto( f1, p1 );
}
