/*
**
** A program to extract samples from a GUS patch.
** (c) by Peter Kunath
**
** last change: 25-Jul-96
**
*/

/* Note: TAB SIZE = 3 */


#define PATCH_VERSION 0
#define PATCH_REVISION 6
#define ASCII_VERSION "0.6"

#define VERSION_STRING {"\0$VER: RipGUS " ASCII_VERSION " "__AMIGADATE__"\r\n"}


/* Includes */

#include <exec/exec.h>
#include <dos/dos.h>
#include <iff/iff.h>
#include <iff/8svx.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>

#include <proto/exec.h>
#include <proto/dos.h>

#include "functions.h"
#include "guspatch.h"


/* Disable the nasty ctrl-c handling */

void __regargs __chkabort(void);
void __regargs __chkabort(void)
{
}


/* Externals */

BOOL PatchList ( char *name, struct patchheader *pheader );
BOOL PatchExtract ( char *name, struct patchheader *pheader );
struct patchheader *LoadPatch ( char *name );
void FreePatch ( struct patchheader *pheader );
BOOL SaveSample ( struct patchheader *pheader, struct waveheader *wheader );
ULONG NoteToNumber ( int num );
ULONG NumberToNote ( int freq );

/* Definitionen */

/* Commandline Template */

#define TEMPLATE		"LIST=L/S,EXTRACT=X/S,PATCH,DESTDIR"
#define OPT_LIST		0
#define OPT_EXTRACT	1
#define OPT_PATCH		2
#define OPT_DESTDIR	3
#define OPT_COUNT		4


/* Note: Patch manager is wrong in the octave assignments by 1 octave. */

ULONG scale_table[108] =
{
  /* Octave 0 */  16351, 17323, 18354, 19445, 20601, 21826, 23124, 24499, 25956, 27500, 29135, 30867,
  /* Octave 1 */  32703, 34647, 36708, 38890, 41203, 43653, 46249, 48999, 51913, 54999, 58270, 61735,
  /* Octave 2 */  65406, 69295, 73416, 77781, 82406, 87306, 92498, 97998, 103826, 109999, 116540, 123470,
  /* Octave 3 */  130812, 138591, 146832, 155563, 164813, 174614, 184997, 195997, 207652, 219999, 233081, 246941,
  /* Octave 4 */  261625, 277182, 293664, 311126, 329627, 349228, 369994, 391995, 415304, 440000, 466163, 493883,
  /* Octave 5 */  523251, 554365, 587329, 622254, 659255, 698456, 739989, 783991, 830609, 880000, 932328, 987767,
  /* Octave 6 */  1046503, 1108731, 1174660, 1244509, 1318511, 1396914, 1479979, 1567983, 1661220, 1760002, 1864657, 1975536,
  /* Octave 7 */  2093007, 2217464, 2349321, 2489019, 2637024, 2793830, 2959960, 3135968, 3322443, 3520006, 3729316, 3951073,
  /* Octave 8 */  4186073, 4434930, 4698645, 4978041, 5274051, 5587663, 5919922, 6271939, 6644889, 7040015, 7458636, 7902150,
};


struct patchheader
{
	struct List list;				/* list header for the instruments */
	char *file;						/* filename of the patch */
	UBYTE patch[GPH_sizeof];	/* patch header */
};

struct instrheader
{
	struct Node node;				/* for the instrument list */
	struct List list;				/* list header for the layers */
	UBYTE instr[GIH_sizeof];	/* instrument header */
};

struct layerheader
{
	struct Node node;				/* for the layer list */
	struct List list;				/* list header for the waves */
	UBYTE layer[GLH_sizeof];	/* layer header */
};

struct waveheader
{
	struct Node node;				/* for the wave list */
	char *sample;					/* the actual sample data */
	UBYTE wave[GWH_sizeof];		/* wave header */
};


/****************************************************************************/

/* Variables */

static char VerString[] = VERSION_STRING;

LONG result[OPT_COUNT];		/* C guarantees this will be all 0's! */
char filename[256];

char *notenames[12] = { "c","cs","d","ds","e","f","fs","g","gs","a","as","h" };


/*****************************************************************************
 *
 * Main Loop
 *
 */

void main(void)
{
	struct RDArgs *rdargs;
   struct patchheader *patch;

	/* Open the dos library */
	if (DOSBase = (struct DosLibrary *) OpenLibrary ("dos.library", 0))
	{
		if ( DOSBase->dl_lib.lib_Version >= 37)
		{
			if ((rdargs = ReadArgs(TEMPLATE, result, NULL)) != NULL)
			{
				if ( result[OPT_LIST] ^ result[OPT_EXTRACT] )
				{
					if ( result[OPT_PATCH] )
					{
						if ( patch = LoadPatch( (char *)result[OPT_PATCH]) )
						{
							if ( result[OPT_LIST] )
							{
								PatchList( (char *)result[OPT_PATCH], patch );
							}
							else if ( result[OPT_EXTRACT])
							{
								PatchExtract( (char *)result[OPT_DESTDIR], patch );
							}

							FreePatch( patch );
						}
						else printf( "error loading patch\n");
					}
					else printf( "no patchfile given\n");
				}
				else printf( "wrong command\n");

				FreeArgs( rdargs);
			}
			else printf( "wrong arguments\n");
		}
		else
			printf( "this program requires at least kick 2.04\n");

		/* Close the dos library */
		CloseLibrary ((struct DosLibrary *) DOSBase);
	}
}


/*****************************************************************************
 *
 * Loads a patch into memory
 *
 */

struct patchheader *LoadPatch( char *name)
{
	BPTR handle;
	struct patchheader *pheader = NULL;
	struct instrheader *iheader;
	struct layerheader *lheader;
	struct waveheader *wheader;
	int instrs;
	int layers;
	int waves;
	char *sample;
	ULONG size;

	if ( name )
	{
		if( handle = Open( name, MODE_OLDFILE))
		{

#ifdef DEBUG_VERSION
	printf("loading patch: '%s'\n", name);
#endif

			if( pheader = AllocVec( sizeof( struct patchheader), MEMF_ANY|MEMF_CLEAR))
			{
				/* init instrument list */
				NewList( &pheader->list);

				if ( pheader->file = AllocVec( strlen(name)+1, MEMF_ANY|MEMF_CLEAR))
				{
					/* copy filename */
					strcpy( pheader->file, name);

					/* read patch header */
					if( GPH_sizeof == Read( handle, pheader->patch, GPH_sizeof))
					{
						if ( strncmp( pheader->patch+GPH_header, GF1_HEADER_TEXT, 8) == 0 )
						{
							if ( strcmp( pheader->patch+GPH_gravis_id, GRAVIS_ID_TEXT) == 0 )
							{
								/* get number of instruments */
								instrs = GetLEByte( pheader->patch+GPH_instruments);

								while( (instrs-- > 0) && pheader)
								{

#ifdef DEBUG_VERSION
	printf("instr: %ld\n", instrs+1);
#endif

									if( iheader = AllocVec( sizeof( struct instrheader), MEMF_ANY|MEMF_CLEAR))
									{
										/* append node to instrument list */
										AddTail( &pheader->list, &iheader->node);

										/* init layer list */
										NewList( &iheader->list);

										/* load instrument header */
										if( GIH_sizeof == Read( handle, iheader->instr, GIH_sizeof))
										{
											/* get number of layers */
											layers = GetLEByte( iheader->instr+GIH_layers);

											while( (layers-- > 0) && pheader)
											{

#ifdef DEBUG_VERSION
	printf("layer: %ld\n", layers+1);
#endif

												if( lheader = AllocVec( sizeof( struct layerheader), MEMF_ANY|MEMF_CLEAR))
												{
													/* append node to layer list */
													AddTail( &iheader->list, &lheader->node);

													/* init wave list */
													NewList( &lheader->list);

													/* load layer header */
													if( GLH_sizeof == Read( handle, lheader->layer, GLH_sizeof))
													{
														/* get number of waves */
														waves = GetLEByte( lheader->layer+GLH_samples);

														while( (waves-- > 0) && pheader)
														{

#ifdef DEBUG_VERSION
	printf("wave: %ld\n", waves+1);
#endif

															if( wheader = AllocVec( sizeof( struct waveheader), MEMF_ANY|MEMF_CLEAR))
															{
																/* append node to wave list */
																AddTail( &lheader->list, &wheader->node);

																/* load wave header */
																if( GWH_sizeof == Read( handle, wheader->wave, GWH_sizeof))
																{
																	/* duplicate layer? */
																	if ( GetLEByte( lheader->layer+GLH_layer_duplicate) == 0)
																	{
																		if ( size = GetLELong( wheader->wave+GWH_wave_size))
																		{
																			if ( sample = AllocVec( size, MEMF_ANY|MEMF_CLEAR))
																			{
																				wheader->sample = sample;

																				/* load sample data */
																				if ( size == Read( handle, sample, size))
																				{
																					continue;
																				}

																				FreeVec( sample);
																				wheader->sample = NULL;
																			}
																		}
																	}
																	else
																	{
																		continue;
																	}
																}
															}

															FreePatch( pheader);
															pheader = NULL;
															break;
														}

														continue;
													}
												}

												FreePatch( pheader);
												pheader = NULL;
												break;
											}

											continue;
										}
									}

									FreePatch( pheader);
									pheader = NULL;
									break;
								}
							}
							else
							{
								FreePatch( pheader);
								pheader = NULL;
							}
						}
						else
						{
							FreePatch( pheader);
							pheader = NULL;
						}
					}
					else
					{
						FreePatch( pheader);
						pheader = NULL;
					}
				}
			}

			Close( handle);
		}
	}

	return( pheader );
}


/*****************************************************************************
 *
 * Frees a patch
 *
 */

void FreePatch( struct patchheader *pheader)
{
	struct instrheader *inode, *inode2;
	struct layerheader *lnode, *lnode2;
	struct waveheader *wnode, *wnode2;

	if ( pheader )
	{
		if ( pheader->file)
		{
			FreeVec( pheader->file);
		}

		inode = (struct instrheader *)pheader->list.lh_Head;

		while( inode2 = (struct instrheader *)inode->node.ln_Succ)
		{
			lnode = (struct layerheader *)inode->list.lh_Head;

			while( lnode2 = (struct layerheader *)lnode->node.ln_Succ)
			{
				wnode = (struct waveheader *)lnode->list.lh_Head;

				while( wnode2 = (struct waveheader *)wnode->node.ln_Succ)
				{
					if ( wnode->sample)
					{
						FreeVec( wnode->sample);
					}

					FreeVec( wnode);

					wnode = wnode2;
				}

				FreeVec( lnode);

				lnode = lnode2;
			}

			FreeVec( inode);

			inode = inode2;
		}

		FreeVec( pheader);
	}
}


/*****************************************************************************
 *
 * List a patch
 *
 */

BOOL PatchList( char *name, struct patchheader *pheader )
{
	BOOL success = FALSE;
	struct instrheader *inode, *inode2;
	struct layerheader *lnode, *lnode2;
	struct waveheader *wnode, *wnode2;
	UBYTE typ;
	ULONG notenum, octave, note;

	if ( pheader )
	{
		success = TRUE;

		printf("filename of patch: '%s'\n", pheader->file);
		printf("patch description: '%s'\n", pheader->patch+GPH_description);
		printf("number of instruments in patch: %ld\n", (ULONG)GetLEByte( pheader->patch+GPH_instruments));
		printf("number of waveforms in patch: %ld\n", (ULONG)GetLEWord( pheader->patch+GPH_wave_forms));
		printf("master volume (0-127): %ld\n", (ULONG)GetLEByte( pheader->patch+GPH_master_volume));

		inode = (struct instrheader *)pheader->list.lh_Head;

		while( inode2 = (struct instrheader *)inode->node.ln_Succ)
		{
			printf(" instrument ID: %ld\n", (ULONG)GetLEWord(inode->instr+GIH_instrument));
			printf(" instrument name: '%s'\n", inode->instr+GIH_instrument_name);
			printf(" number of layers in instr (1-4): %ld\n", (ULONG)GetLEByte( inode->instr+GIH_layers));

			lnode = (struct layerheader *)inode->list.lh_Head;

			while( lnode2 = (struct layerheader *)lnode->node.ln_Succ)
			{
				printf("  layer duplicate: %ld\n", (ULONG)GetLEByte( lnode->layer+GLH_layer_duplicate));
				printf("  number of samples in layer: %ld\n", (ULONG)GetLEByte( lnode->layer+GLH_samples));

				wnode = (struct waveheader *)lnode->list.lh_Head;

				while( wnode2 = (struct waveheader *)wnode->node.ln_Succ)
				{
					typ = GetLEByte( wnode->wave+GWH_modes);

					printf("   wave name: '%s'\n", wnode->wave+GWH_wave_name);
					printf("   wave size in bytes: %ld\n", (ULONG)GetLELong( wnode->wave+GWH_wave_size));

					if ( notenum = NumberToNote( GetLELong( wnode->wave+GWH_root_frequency)))
					{
						octave = notenum / 12;
						note = notenum % 12;

						printf( "   note: %c%ld (%ld Hz)\n", *notenames[note], octave, (ULONG)GetLELong( wnode->wave+GWH_root_frequency)/1000);
					}

					printf("   sample frequency (Hz): %ld\n", (ULONG)GetLEWord( wnode->wave+GWH_sample_rate));

					printf("   sample type:");

					if ( typ & WAVE_16_BITS)
						printf(" 16bit");
					else
						printf(" 8bit");

					if ( typ & WAVE_UNSIGNED)
						printf(" unsigned");
					else
						printf(" signed");

					printf("\n");

					wnode = wnode2;
				}

				lnode = lnode2;
			}

			inode = inode2;
		}
	}

	return( success);
}


/*****************************************************************************
 *
 * Extract the samples of a patch
 *
 */

BOOL PatchExtract( char *name, struct patchheader *pheader )
{
	BOOL success = FALSE;
	BPTR olddir, newdir = NULL;
	struct instrheader *inode, *inode2;
	struct layerheader *lnode, *lnode2;
	struct waveheader *wnode, *wnode2;
	

	if ( pheader )
	{
		success = TRUE;

		if ( name )
		{
			if ( newdir = Lock( name, SHARED_LOCK))
			{
				olddir = CurrentDir( newdir);
			}
			else
			{
				printf("wrong destination directory\n");
				success = FALSE;
			}
		}

		if ( success)
		{
			inode = (struct instrheader *)pheader->list.lh_Head;

			while( (inode2 = (struct instrheader *)inode->node.ln_Succ) && success)
			{
				lnode = (struct layerheader *)inode->list.lh_Head;

				while( (lnode2 = (struct layerheader *)lnode->node.ln_Succ) && success)
				{
					wnode = (struct waveheader *)lnode->list.lh_Head;

					while( (wnode2 = (struct waveheader *)wnode->node.ln_Succ) && success)
					{
						if ( wnode->sample)
						{
							success = SaveSample( pheader, wnode);
						}

						wnode = wnode2;
					}

					lnode = lnode2;
				}

				inode = inode2;
			}

			if ( newdir)
			{
				CurrentDir( olddir);

				UnLock( newdir);
			}
		}
	}

	return( success);
}


/*****************************************************************************
 *
 * Converts a GUS waveform into an IFF-8SVX sample
 *
 */

BOOL SaveSample( struct patchheader *pheader, struct waveheader *wheader)
{
	BOOL success = FALSE;
	BPTR handle;
	UBYTE typ;
	ULONG len, len2, size;
	char *source, *dest;
	ULONG onelen, replen;
	ULONG sampfreq, rootfreq;
	ULONG notenum, octave, note;
	ULONG i, j;

	char annotext[] = "Converted from a GUS patch by RipGUS " ASCII_VERSION "\0";

	struct iffsample
	{
		ChunkHeader head;
		LONG type;
		ChunkHeader vhdr;
		Voice8Header vhdrch;
		ChunkHeader anno;
		char annoch[ sizeof( annotext)];
		ChunkHeader body;
		char bodych[0];
	};

	struct iffsample *sample;


	if ( pheader && wheader )
	{
		typ = GetLEByte( wheader->wave+GWH_modes);

		if ( typ & WAVE_LOOPING)
		{
			onelen = GetLELong( wheader->wave+GWH_start_loop);
			replen = GetLELong( wheader->wave+GWH_end_loop) - GetLELong( wheader->wave+GWH_start_loop);
		}
		else
		{
			onelen = GetLELong( wheader->wave+GWH_wave_size);
			replen = 0;
		}

		/* this cuts off unused samples at the end of a looped sample */
		len = onelen+replen;
//		len = GetLELong( wheader->wave+GWH_wave_size);

		if ( typ & WAVE_16_BITS)
		{
			len2 = ((len >> 1)+1)&~1;
			onelen >>= 1;
			replen >>= 1;
		}
		else
		{
			len2 = (len+1)&~1;
		}

		size = sizeof( struct iffsample) + len2;

		sampfreq = GetLEWord( wheader->wave+GWH_sample_rate);
		rootfreq = GetLELong( wheader->wave+GWH_root_frequency);

		if ( sample = AllocVec( size, MEMF_ANY|MEMF_CLEAR))
		{
			sample->head.ckID = FORM;
			sample->head.ckSize = size-8;

			sample->type = ID_8SVX;

			sample->vhdr.ckID = ID_VHDR;
			sample->vhdr.ckSize = sizeof( Voice8Header);
			sample->vhdrch.oneShotHiSamples = onelen;		/* # samples in the high octave 1-shot part */
			sample->vhdrch.repeatHiSamples = replen;		/* # samples in the high octave repeat part */
			sample->vhdrch.samplesPerHiCycle = 0;			/* # samples/cycle in high octave, else 0 */
			sample->vhdrch.samplesPerSec = sampfreq;		/* data sampling rate */
			sample->vhdrch.ctOctave = 1;						/* # of octaves of waveforms */
			sample->vhdrch.sCompression = 0;					/* data compression technique used */
			sample->vhdrch.volume = 0x10000;					/* playback nominal volume from 0 to Unity */

			sample->anno.ckID = ID_ANNO;
			sample->anno.ckSize = sizeof( annotext);
			strcpy( sample->annoch, annotext);

			sample->body.ckID = ID_BODY;
			sample->body.ckSize = len2;

			source = wheader->sample;
			dest = sample->bodych;

			if ( typ & WAVE_16_BITS )
			{
				if ( typ & WAVE_UNSIGNED )
				{
					/* 16 bit unsigned */

					for(i=0,j=1;j<len;i++,j+=2)
					{
						*(dest+i) = *(source+j) - 0x80;
					}
				}
				else
				{
					/* 16 bit signed */

					for(i=0,j=1;j<len;i++,j+=2)
					{
						*(dest+i) = *(source+j);
					}
				}
			}
			else
			{
				if ( typ & WAVE_UNSIGNED )
				{
					/* 8 bit unsigned */

					for(i=0;i<len;i++)
					{
						*(dest+i) = *(source+i) - 0x80;
					}
				}
				else
				{
					/* 8 bit signed */

					for(i=0;i<len;i++)
					{
						*(dest+i) = *(source+i);
					}
				}
			}
 
			if ( notenum = NumberToNote( rootfreq))
			{
				octave = notenum / 12;
				note = notenum % 12;

				sprintf( filename, "%s.%c%ld", FilePart( pheader->file), *notenames[note], octave);

				printf( "extracting sample '%s'\n", filename);

				if( handle = Open( filename, MODE_NEWFILE))
				{
					if ( size == Write( handle, sample, size))
					{
						success = TRUE;
					}

					Close( handle);

					if ( success == FALSE)
					{
						DeleteFile( filename);
	
						printf("failed to extract sample\n");
					}
				}
			}

			FreeVec( sample);
		}
	}

	return( success);
}


/*****************************************************************************
 *
 * Converts a notenumber into frequency
 *
 */

ULONG NoteToNumber( int num)
{
	if( num < (sizeof( scale_table)/sizeof( ULONG)) )
	{
		return( scale_table[num] );
	}
	else
	{
		return( 16351L );
	}
}


/*****************************************************************************
 *
 * Converts a frequency into a notenumber
 *
 */

ULONG NumberToNote( int freq)
{
	ULONG i;

	for(i=0;i<(sizeof( scale_table)/sizeof( ULONG));i++)
	{
		if ( freq <= scale_table[i])
			return( i );
	}
	return( 0 );
}


/****************************************************************************/

