/*
**	Instr.c
**
**	Copyright (C) 1994,95,96,97 Bernardo Innocenti
**
**	Instrument loading/saving/handling routines.
*/

#define IFFPARSE_V37_NAMES_ONLY

#include <exec/libraries.h>
#include <exec/memory.h>
#include <datatypes/soundclass.h>
#include <libraries/iffparse.h>
#include <libraries/maud.h>
#include <libraries/toccata.h>
#include <libraries/xmoduleclass.h>

#include <proto/exec.h>
#include <proto/dos.h>
#include <proto/iffparse.h>
#include <proto/datatypes.h>
#include <proto/toccata.h>
#include <proto/xmodule.h>

#include "XModulePriv.h"
#include "Gui.h"


#define UNITY 0x10000L


/* Local function prototypes */
static LONG DTLoadInstrument	(struct SongInfo *si, ULONG num, CONST_STRPTR filename);
static LONG LoadMAUDInstrument	(struct SongInfo *si, ULONG num, struct IFFHandle *iff, CONST_STRPTR filename);
static LONG RawLoadInstrument	(struct SongInfo *si, ULONG num, CONST_STRPTR filename, UWORD mode);
static BYTE D1Unpack			(UBYTE source[], LONG n, BYTE dest[], BYTE x);
static void DUnpack				(UBYTE source[], LONG n, BYTE dest[]);



struct Library *DataTypesBase	= NULL;
struct Library *ToccataBase	= NULL;



GLOBALCALL LONG LoadInstrument (struct SongInfo *si, ULONG num, CONST_STRPTR filename)

/* Load an instrument file to the instrument slot <num> in the passed song.
 * Will use DataTypes if available, otherwise it will use the built-in loaders.
 */
{
	LONG err = IFFERR_NOTIFF;

	/* Try loading with DataTypes */

	if (GuiSwitches.UseDataTypes)
		if (DataTypesBase = OpenLibrary ("datatypes.library", 39L))
		{
			err = DTLoadInstrument (si, num, filename);
			CloseLibrary (DataTypesBase);	DataTypesBase = NULL;
		}


	/* Try again using built-in loaders */

	if (err)
	{
		struct IFFHandle *iff;

		if (iff = AllocIFF())
		{
			if (iff->iff_Stream = (ULONG) Open (filename, MODE_OLDFILE))
			{
				InitIFFasDOS (iff);

				if (!(err = OpenIFF (iff, IFFF_READ)))
				{
					if (!(err = ParseIFF (iff, IFFPARSE_RAWSTEP)))
					{
						LONG type = CurrentChunk (iff)->cn_Type;
						switch (type)
						{
							case ID_8SVX:
									err = Load8SVXInstrument (si, num, iff, filename);
								break;

							case ID_MAUD:
									err = LoadMAUDInstrument (si, num, iff, filename);
								break;

							default:
							{
								UBYTE buf[5];

								IDtoStr (type, buf);
								ShowMessage (MSG_UNKNOWN_IFF, buf);
								err = ERROR_OBJECT_WRONG_TYPE;
								break;
							}
						}
					}
				}

				Close (iff->iff_Stream);
			}
			else err = IoErr();

			FreeIFF (iff);
		}
		else err = ERROR_NO_FREE_STORE;
	}

	if (err == IFFERR_MANGLED || err == IFFERR_SYNTAX)
		ShowMessage (MSG_ILLEGAL_IFF_STRUCTURE);

	if (err == IFFERR_NOTIFF)
	{
		LONG mode;

		if (mode = ShowRequestArgs (MSG_SELECT_RAW_MODE,
		MSG_RAW_MODES, NULL))
			err = RawLoadInstrument (si, num, filename, mode);
		else err = 0;
	}

	UpdateInstrList();

	if (err) LastErr = err;
	return err;
}



static LONG DTLoadInstrument (struct SongInfo *si, ULONG num, CONST_STRPTR filename)
{
	Object	*dto;
	UBYTE	*instname;
	LONG	 err = 0;

	if (dto = NewDTObject (filename,
		DTA_GroupID, GID_SOUND,
		TAG_DONE))
	{
		struct VoiceHeader *vhdr;
		LONG Len, Vol;
		UBYTE *Sample;
		UBYTE *errorstring;

		if (GetDTAttrs (dto,
			SDTA_VoiceHeader,	&vhdr,
			SDTA_SampleLength,	&Len,
			SDTA_Volume,		&Vol,
			SDTA_Sample,		&Sample,
			DTA_Title,			&instname,
			TAG_DONE) == 5)
		{
			/* Detach sample from DataType Object, so it won't
			 * be freed when we dispose the object.
			 */
			SetDTAttrs (dto, NULL, NULL,
				SDTA_Sample, NULL,
				TAG_DONE);

			/* Create new instrument */

			if (!xmAddInstrument (si, num,
				INSTRA_Name,	instname,
				INSTRA_Sample,	Sample,
				INSTRA_Length,	Len,
				INSTRA_Repeat,	vhdr->vh_OneShotHiSamples,
				INSTRA_Replen,	vhdr->vh_RepeatHiSamples,

				/* The sound.datatype _should_ return
				 * volumes in the normal Amiga range 0-64.
				 * However, the 8svx.datatype behaves
				 * differently: it returns the volume in the
				 * standard 8SVX format, where the maximum
				 * volume is $10000.  Here is a good
				 * workaround to this bug.
				 */
				INSTRA_Volume,	(vhdr->vh_Volume > 64) ?
					((vhdr->vh_Volume * 64) / UNITY) : vhdr->vh_Volume,
				TAG_DONE))
				err = ERROR_NO_FREE_STORE;

				DB(kprintf ("Vol: %ld InstVol: %ld\n", Vol, si->Instr[num]->Volume));
		}
		else err = RETURN_FAIL;


		if (GetDTAttrs (dto,
			DTA_ErrorString, &errorstring,
			TAG_DONE) == 1)
			ShowMessage (MSG_DATATYPES_ERROR, errorstring);

		DisposeDTObject (dto);
	}
	else err = IoErr();

	return err;
}



GLOBALCALL LONG Load8SVXInstrument (struct SongInfo *si, ULONG num, struct IFFHandle *iff, CONST_STRPTR filename)

/* Load an IFF 8SVX file to the instrument slot <num>.  If <num> is 0, it requires
 * the INST chunk and it uses the num stored there.
 * Can decode Fibonacci Delta encoded samples.
 */
{
	struct ContextNode		*cn;
	struct VoiceHeader		 vhdr;
	struct InstrumentHeader	 insthdr;
	LONG	err;
	UBYTE	name[64];
	BOOL	is_valid_8svx = FALSE,
			insthdr_loaded = FALSE;

	static LONG stopchunks[] =
	{
		ID_8SVX, ID_INST,
		ID_8SVX, ID_VHDR,
		ID_8SVX, ID_BODY,
		ID_8SVX, ID_NAME
	};

	/* Put the file name if the optional NAME propriety is missing */
	if (filename)
	{
		strncpy (name, FilePart (filename), 63);
		name[63] = '\0';
	}
	else name[0] = '\0';

	if (err = StopChunks (iff, stopchunks, 4))
		return err;

	if (err = StopOnExit (iff, ID_8SVX, ID_FORM))
		return err;

	while (1)
	{
		if (err = ParseIFF (iff, IFFPARSE_SCAN))
		{
			if (err == IFFERR_EOF || err == IFFERR_EOC) err = RETURN_OK;
			break; /* Free resources & exit */
		}

		if ((cn = CurrentChunk (iff)) && (cn->cn_Type == ID_8SVX))
		{
			switch (cn->cn_ID)
			{
				case ID_INST:
				{
					if ((err = ReadChunkBytes (iff, &insthdr, sizeof (insthdr))) !=
						sizeof (insthdr)) return err;

					if (!num) num = insthdr.Num;
				}

				case ID_VHDR:
				{
					if ((err = ReadChunkBytes (iff, &vhdr, sizeof (vhdr))) !=
						sizeof (vhdr)) return err;

					if (!is_valid_8svx)
					{
						if (!xmAddInstrumentA (si, num, NULL))
							return ERROR_NO_FREE_STORE;

						if (!num) num = si->LastInstrument;
					}

					xmSetInstrument (si, num,
						INSTRA_Repeat,		(vhdr.vh_RepeatHiSamples ? vhdr.vh_OneShotHiSamples : 0),
						INSTRA_Replen,		vhdr.vh_RepeatHiSamples,
						INSTRA_Volume,		(vhdr.vh_Volume * 64) / UNITY,
						INSTRA_FineTune,	insthdr_loaded ? insthdr.FineTune : 0,
						TAG_DONE);

					is_valid_8svx = TRUE;

					break;
				}

				case ID_BODY:
				{
					struct Instrument *instr;
					BYTE *sample;

					if (!is_valid_8svx)
					{
						xmAddInstrumentA (si, num, NULL);
						if (!num) num = si->LastInstrument;
					}

					if (!(instr = si->Instr[num]))
						return ERROR_NO_FREE_STORE;

					if (!(sample = AllocVec (cn->cn_Size, MEMF_SAMPLE)))
						return ERROR_NO_FREE_STORE;

					xmSetInstrument (si, num,
						INSTRA_Sample,	sample,
						INSTRA_Length,	cn->cn_Size,
						TAG_DONE);

					/* We only require that at least some data is
					 * read.  This way if, say, you have a corrupted
					 * 8SVX file, you can still load part of the
					 * instrument data.
					 */
					if ((err = ReadChunkBytes (iff, instr->Sample,
						cn->cn_Size)) < 0)
						return err;

					is_valid_8svx = TRUE;
					break;
				}

				case ID_NAME:
					ReadChunkBytes (iff, name, min(cn->cn_Size, 63));
					name[63] = '\0'; /* Ensure string termination */
					break;

				default:
					break;
			}
		}
	}

	if (is_valid_8svx)
	{
		xmSetInstrument (si, num,
			INSTRA_Name,	name,
			TAG_DONE);

		if (!err)
		{
			if (vhdr.vh_Compression == CMP_FIBDELTA)
			{
				BYTE *buf;
				struct Instrument *instr = si->Instr[num];

				if (buf = AllocVec (instr->Length * 2, MEMF_SAMPLE))
				{
					DUnpack (instr->Sample, instr->Length + 2, buf);
					FreeVec (instr->Sample);

					xmSetInstrument (si, num,
						INSTRA_Sample,	buf,
						INSTRA_Length,	instr->Length * 2,
						TAG_DONE);
				}
			}
			else if (vhdr.vh_Compression != CMP_NONE)
				ShowMessage (MSG_UNKNOWN_COMPRESSION);
		}
	}
	else err = IFFERR_MANGLED;

	return err;
}



/* Number of samples loaded & processed at one time */
#define MAUDBLOCKSIZE 32768

static LONG LoadMAUDInstrument (struct SongInfo *si, ULONG num, struct IFFHandle *iff, CONST_STRPTR filename)

/* Load an IFF MAUD file to the instrument slot <num> of the passed song.
 * MAUD is the standard file format for Macrosystem's audio boards
 * Toccata and Maestro.
 */
{
	struct ContextNode	*cn;
	struct MaudHeader mhdr;
	LONG err;
	BOOL	is_valid_maud = FALSE;
	UBYTE	name[64];

	static LONG stopchunks[] =
	{
		ID_MAUD, ID_MHDR,
		ID_MAUD, ID_MDAT,
		ID_MAUD, ID_NAME
	};


	/* Put the file name if the optional NAME propriety is missing */

	if (filename)
	{
		strncpy (name, FilePart (filename), 63);
		name[63] = '\0';
	}
	else name[0] = '\0';

	if (err = StopChunks (iff, stopchunks, 3))
		return err;

	if (err = StopOnExit (iff, ID_MAUD, ID_FORM))
		return err;


	while (1)
	{
		if (err = ParseIFF (iff, IFFPARSE_SCAN))
		{
			if (err == IFFERR_EOF || err == IFFERR_EOC) err = RETURN_OK;
			break; /* Free resources & exit */
		}

		if ((cn = CurrentChunk (iff)) && (cn->cn_Type == ID_MAUD))
		{
			switch (cn->cn_ID)
			{
				case ID_MHDR:
					if ((err = ReadChunkBytes (iff, &mhdr, sizeof (mhdr))) !=
						sizeof (mhdr)) return err;

					if ((mhdr.mhdr_SampleSizeU != 8) && (mhdr.mhdr_SampleSizeU != 16))
					{
						ShowMessage (MSG_SAMPLE_WRONG_SIZE, mhdr.mhdr_SampleSizeU);
						return IFFERR_SYNTAX;
					}

					if (mhdr.mhdr_ChannelInfo != MCI_MONO)
					{
						ShowMessage (MSG_SAMPLE_NOT_MONO, mhdr.mhdr_ChannelInfo);
						return IFFERR_SYNTAX;
					}

					if (mhdr.mhdr_Channels != 1)
					{
						ShowMessage (MSG_SAMPLE_WRONG_NUMBER_OF_CHANNELS, mhdr.mhdr_Channels);
						return IFFERR_SYNTAX;
					}

					is_valid_maud = TRUE;
					break;

				case ID_MDAT:
				{
					ULONG i;
					struct Instrument *instr;
					BYTE *sample;

					if (!is_valid_maud)
						return IFFERR_SYNTAX;

					if (!(sample = AllocVec ((mhdr.mhdr_Samples + 1) & (~1), MEMF_SAMPLE)))
						return ERROR_NO_FREE_STORE;

					if (!(instr = xmAddInstrument (si, num,
						INSTRA_Sample,	sample,
						INSTRA_Length,	(mhdr.mhdr_Samples + 1) & (~1),
						TAG_DONE)))
					{
						FreeVec (sample);
						return ERROR_NO_FREE_STORE;
					}

					if (mhdr.mhdr_SampleSizeU == 8)				/* 8 bit */
					{
						/* We only require that at least some data is
						 * read.  This way if, say, you have a corrupted
						 * MAUD file, you can still load part of the
						 * sample data.
						 */
						if ((err = ReadChunkBytes (iff, instr->Sample,
							instr->Length)) == 0) return err;

						SampChangeSign8 (instr->Sample, instr->Length);

					}
					else if (mhdr.mhdr_SampleSizeU == 16)		/* 16 bit */
					{
						WORD *tmp;
						ULONG actual, current = 0;

						if (!(tmp = AllocPooled (Pool, MAUDBLOCKSIZE * ((mhdr.mhdr_SampleSizeC + 7) / 8))))
							return ERROR_NO_FREE_STORE;

						if (mhdr.mhdr_Compression != MCOMP_NONE)
							if (!(ToccataBase = MyOpenLibrary ("toccata.library", 0L)))
							{
								FreePooled (Pool, tmp, MAUDBLOCKSIZE * sizeof (WORD));
								CantOpenLib ("toccata.library", 0L);
								return ERROR_INVALID_RESIDENT_LIBRARY;
							}

						for (;;)
						{
							actual = ReadChunkBytes (iff, tmp, MAUDBLOCKSIZE * ((mhdr.mhdr_SampleSizeC + 7) / 8));

							if (actual == 0) break;

							/* Filter (tmp, actual); */

							switch (mhdr.mhdr_Compression)
							{
								case MCOMP_ALAW:
									/* Convert 8bit A-Law data to 8bit signed data */
									T_Convert (tmp, instr->Sample + current, actual, TMODE_ALAW, TMODE_LINEAR_8);
									current += actual;
									break;

								case MCOMP_ULAW:
									/* Convert 8bit µ-Law data to 8bit signed data */
									T_Convert (tmp, instr->Sample + current, actual, TMODE_ULAW, TMODE_LINEAR_8);
									current += actual;
									break;

								default:
									/* Convert 16bit signed data to 8bit signed data */
									actual >>= 1;
									for (i = 0; (i < actual) && (current < mhdr.mhdr_Samples); i++, current++)
										instr->Sample[current] = tmp[i] >> 8;
									break;
							}
						}

						SampChangeSign8 (instr->Sample, instr->Length);
						FreePooled (Pool, tmp, MAUDBLOCKSIZE * sizeof (WORD));
						CloseLibrary (ToccataBase);
						ToccataBase = NULL;
					}

					is_valid_maud = TRUE;
					break;
				}

				case ID_NAME:
					ReadChunkBytes (iff, name, min(cn->cn_Size, 63));
					break;

				default:
					break;
			}
		}
	}

	if (is_valid_maud)
	{
		xmSetInstrument (si, num,
		INSTRA_Name,	name,
		TAG_DONE);

		if (!err)
		{
			if (mhdr.mhdr_Compression == CMP_FIBDELTA)
			{
				BYTE *buf;
				struct Instrument *instr = si->Instr[num];

				if (buf = AllocVec (instr->Length * 2, MEMF_SAMPLE))
				{
					DUnpack (instr->Sample, instr->Length + 2, buf);
					FreeVec (instr->Sample);

					xmSetInstrument (si, num,
						INSTRA_Sample,	buf,
						INSTRA_Length,	instr->Length * 2,
						TAG_DONE);
				}
			}
			else if (mhdr.mhdr_Compression != CMP_NONE)
				ShowMessage (MSG_UNKNOWN_COMPRESSION);
		}
	}
	else err = IFFERR_NOTIFF;

	return err;
}



static LONG RawLoadInstrument (struct SongInfo *si, ULONG num, CONST_STRPTR filename, UWORD mode)

/* Load a raw file to the instrument slot pointed by inst.
 * mode   1 - signed 8bit
 *        2 - unsigned 8bit
 */
{
	BPTR lock, fh;
	struct FileInfoBlock *fib;
	struct Instrument *instr;
	LONG err = 0;
	ULONG len;

	if (lock = Lock (filename, ACCESS_READ))
	{
		/* Get file size */
		if (fib = AllocDosObject (DOS_FIB, NULL))
		{
			if (Examine (lock, fib))
				len = fib->fib_Size;
			else
				err = IoErr();
			FreeDosObject (DOS_FIB, fib);
		}
		else err = ERROR_NO_FREE_STORE;

		if (!err)
		{
			if (fh = OpenFromLock (lock))
			{
				BYTE *sample;

				lock = NULL;	/* OpenFromLock() relinquished our lock! */

				if (sample = AllocVec (len, MEMF_SAMPLE))
				{
					if (instr = xmAddInstrument (si, num,
						INSTRA_Sample,	sample,
						INSTRA_Length,	len,
						INSTRA_Volume,	64,
						INSTRA_Name,	FilePart (filename),
						TAG_DONE))
					{
						/* We do not check for failure here to
						 * be more error tolerant.  This way you
						 * can load at least part of an instrument
						 * from a corrupted file ;-)
						 */
						Read (fh, sample, len);

						if (mode == 2)
							SampChangeSign8 (sample, instr->Length);
					}
					else
					{
						FreeVec (sample);
						err = ERROR_NO_FREE_STORE;
					}
				}
				else err = ERROR_NO_FREE_STORE;

				Close (fh);
			}
			else err = IoErr();
		}

		UnLock (lock);	/* Will be NULL if OpenFromLock() was successful */
	}
	else err = IoErr();

	return err;
}



GLOBALCALL LONG SaveInstrument (struct Instrument *inst, CONST_STRPTR filename)
{
	LONG err;
	struct IFFHandle *iff;


	if (iff = AllocIFF())
	{
		if (iff->iff_Stream = (ULONG) Open (filename, MODE_NEWFILE))
		{
			InitIFFasDOS (iff);

			if (!(err = OpenIFF (iff, IFFF_WRITE)))
			{
				err = Save8SVXInstrument (inst, 0, iff);
				CloseIFF (iff);
			}

			Close (iff->iff_Stream);
		}
		else err = IoErr();

		FreeIFF (iff);
	}
	else return ERROR_NO_FREE_STORE;

	if (!err)
	{
		if (GuiSwitches.InstrSaveIcons)
			/* Write icon */
			PutIcon ("def_Instrument", filename);
	}
	else
	{
		/* Remove incomplete file */
		LastErr = err;
		DeleteFile (filename);
	}

	return (err);
}



GLOBALCALL LONG Save8SVXInstrument (struct Instrument *instr, ULONG num, struct IFFHandle *iff)

/* Save the instrument pointed by inst to a standard IFF 8SVX file.
 */
{
	struct VoiceHeader vhdr;
	LONG err;


	/* Write 8SVX */

	if (err = PushChunk (iff, ID_8SVX, ID_FORM, IFFSIZE_UNKNOWN))
		return err;


	/* Write INST */
	if (num)
	{
		struct InstrumentHeader insthdr;

		insthdr.Num = num;
		insthdr.Type = ITYPE_SAMPLE8;
		insthdr.FineTune = instr->FineTune;

		if (err = PushChunk (iff, ID_8SVX, ID_INST, sizeof (insthdr)))
			return err;
		if ((err = WriteChunkBytes (iff, &insthdr, sizeof (insthdr))) !=
			sizeof (insthdr))
			return err;
		if (err = PopChunk (iff)) return err;	/* Pop INST */
	}

	/* Write VHDR */

	if (vhdr.vh_RepeatHiSamples = instr->Replen)
		/* Loop */
		vhdr.vh_OneShotHiSamples = instr->Repeat;
	else
		/* No Loop */
		vhdr.vh_OneShotHiSamples = instr->Length;

	vhdr.vh_SamplesPerHiCycle = 0;
	vhdr.vh_SamplesPerSec = 8363;
	vhdr.vh_Octaves = 1;
	vhdr.vh_Compression = CMP_NONE;
	vhdr.vh_Volume = (instr->Volume * UNITY) / 64;

	if (err = PushChunk (iff, ID_8SVX, ID_VHDR, sizeof (vhdr)))
		return err;
	if ((err = WriteChunkBytes (iff, &vhdr, sizeof (vhdr))) !=
		sizeof (vhdr))
		return err;
	if (err = PopChunk (iff)) return err;	/* Pop VHDR */


	/* Write NAME */
	{
		ULONG l = strlen (instr->Name) + 1;

		if (err = PushChunk (iff, ID_8SVX, ID_NAME, l))
			return err;
		if ((err = WriteChunkBytes (iff, instr->Name, l)) != l)
			return err;
		if (err = PopChunk (iff)) return err;	/* Pop NAME */
	}

	/* Write BODY */

	if (instr->Sample)
	{
		if (PushChunk (iff, ID_8SVX, ID_BODY, instr->Length))
			return err;
		if ((err = WriteChunkBytes (iff, instr->Sample, instr->Length)) !=
			instr->Length) return err;
		if (err = PopChunk (iff)) return err;	/* Pop BODY */
	}

	PopChunk (iff);	/* Pop 8SVX */

	return err;
}



GLOBALCALL void OptimizeInstruments (struct SongInfo *si)

/* Remove useless sample data (cut beyond loops and zero-tails) */
{
	UWORD	i;
	ULONG	newlen;
	struct Instrument *instr;

	for (i = 1 ; i <= si->LastInstrument ; i++)
	{
		if (!(instr = si->Instr[i])) continue;
		if (!instr->Sample) continue;

		newlen = instr->Length;

		if (instr->Replen)
		{
			if (instr->Length > instr->Repeat + instr->Replen)
				newlen = instr->Repeat + instr->Replen; /* Cut instrument after loop */
		}
		else
		{
			BYTE *tail;

			instr->Repeat = 0;	/* Kill null loops */

			/* Kill instrument zero-tail.
			 * In order to reduce the instrument even more,
			 * 1 & -1 are treated the same as zero.
			 */

			tail = instr->Sample + instr->Length - 1;
			while ((*tail < 1) && (*tail > -1) && (tail > instr->Sample))
				tail--;

			newlen = tail - instr->Sample;
			if (newlen & 1) newlen++;	/* Pad instrument size to words */

			/* leave 2 end zeroes to prevent an audible end-of-instrument click. */
			if (newlen) newlen += 2;
		}

		if (newlen == 0)
			xmRemInstrument (si, i);	/* This instrument is mute!  Free it... */
		else if (newlen < instr->Length)
		{
			/* Resize the instrument if necessary */
			BYTE *newinstr;

			/* Allocate memory for optimized instrument */
			if (!(newinstr = AllocVec (newlen, MEMF_SAMPLE)))
			{
				ShowMessage (MSG_NO_MEMORY_TO_OPTIMIZE_INSTR, i);
				continue;	/* Better luck with next instrument :) */
			}

			ShowMessage (MSG_INSTR_WILL_SHRINK, i, instr->Length, newlen);

			/* Copy first part of instrument */
			CopyMem (instr->Sample, newinstr, newlen);

			/* Free old instrument */
			FreeVec (instr->Sample);

			/* Replace with new instrument */
			xmSetInstrument (si, i,
				INSTRA_Sample,	newinstr,
				INSTRA_Length,	newlen,
				TAG_DONE);

		}
	}
}



GLOBALCALL void RemDupInstruments (struct SongInfo *si)
/* Find out identical patterns and cut them out */
{
	ULONG i, j, k, w, v;
	struct Instrument *insta, *instb;
	struct Pattern *patt;
	struct Note *note;


	for (i = 1; i < si->LastInstrument; i++)	/* '<' instead of '<=' is ok here... */
	{
		if (!(insta = si->Instr[i])) continue;
		if (!insta->Length) continue;

		for (j = i + 1; j <= si->LastInstrument ; j++)
		{
			if (!(instb = si->Instr[j])) continue;

			if (insta->Length == instb->Length &&
				insta->Repeat == instb->Repeat &&
				insta->Replen == instb->Replen &&
				insta->Volume == instb->Volume &&
				insta->Type == instb->Type &&
				insta->FineTune == instb->FineTune)
			{
				if (!memcmp (insta->Sample, instb->Sample, insta->Length))
				{
					xmRemInstrument (si, j);

					for (k = 0; k < si->NumPatterns; k++)
					{
						if (!(patt = si->Patt[k])) continue;

						for (w = 0; w < patt->Tracks; w++)
						{
							note = patt->Notes[w];
							for (v = 0; v < patt->Lines; v++, note++)
								if (note->Inst == j) note->Inst = i;
						}
					}

					ShowMessage (MSG_INSTR_DUPES_REMOVED, i, j);
				}
			}
		}
	}

}



GLOBALCALL void RemapInstruments (struct SongInfo *si)

/* Remove empty slots between instruments, to allow those module formats
 * that support less instruments to use even the last instruments.
 */
{
	UWORD i, j, k;
	UBYTE newpos[MAXINSTRUMENTS] = { 0 };
	struct Instrument *instr;
	struct Pattern *patt;
	struct Note *note;

	/* newpos[0] = 0; */

	DB (kprintf ("Before - LastInstrument = %ld", si->LastInstrument));

	/* Build instrument remap table &  compress instrument slots */
	for (i = 1, j = 0; i <= si->LastInstrument; i++)
	{
		if (!(instr = si->Instr[i])) continue;

		if (instr->Length)
		{
			j++;
			newpos[i] = j;

			if (j != i) DoMethod ((Object *)si, SNGM_SWAPINSTRUMENTS, i, j);
		}
		else newpos[i] = 0;
	}


	/* Update score */
	for (i = 0 ; i < si->NumPatterns ; i++)
	{
		patt = si->Patt[i];

		for (j = 0 ; j < patt->Tracks ; j++)
		{
			note = patt->Notes[j];

			for (k = 0; k < patt->Lines ; k++, note++)
				if (note->Note)
				{
					note->Inst = newpos[note->Inst];
					if (!note->Inst)
						note->Note = 0;
				}
		}
	}

	DB (kprintf ("After - LastInstrument = %ld", si->LastInstrument));
}



GLOBALCALL void RemUnusedInstruments (struct SongInfo *si)
{
	ULONG usecount[MAXINSTRUMENTS] = { 0 };
	struct Pattern *patt;
	struct Note *note;
	UWORD i, j, k;

	for (i = 0; i < si->NumPatterns; i++)
	{
		if (!(patt = si->Patt[i])) continue;

		for (j = 0; j < patt->Tracks; j++)
		{
			note = patt->Notes[j];

			for (k = 0; k < patt->Lines; k++, note++)
				usecount[note->Inst]++;
		}
	}

	for (i = 1; i <= si->LastInstrument; i++)
	{
		if ((usecount[i] == 0) && si->Instr[i])
		{
			ShowMessage (MSG_INSTR_UNUSED, i);
			xmRemInstrument (si, i);
		}
	}
}



/* DUnpack.c --- Fibonacci Delta decompression by Steve Hayes */

/* Fibonacci delta encoding for sound data */
static const BYTE codeToDelta[16] = {-34,-21,-13,-8,-5,-3,-2,-1,0,1,2,3,5,8,13,21};


static BYTE D1Unpack (UBYTE source[], LONG n, BYTE dest[], BYTE x)

/* Unpack Fibonacci-delta encoded data from n byte source
 * buffer into 2*n byte dest buffer, given initial data
 * value x.  It returns the last data value x so you can
 * call it several times to incrementally decompress the data.
 */
{
	UBYTE d;
	LONG i, lim;

	lim = n << 1;
	for (i = 0; i < lim; ++i)
	{
		/* Decode a data nibble, high nibble then low nibble */
		d = source[i >> 1];		/* get a pair of nibbles		*/
		if (i & 1)				/* select low or high nibble	*/
			d &= 0xf;			/* mask to get the low nibble	*/
		else
			d >>= 4;			/* shift to get the high nibble	*/
		x += codeToDelta[d];	/* add in the decoded delta		*/
		dest[i] = x;			/* store a 1 byte sample		*/
	}
	return x;
}


static void DUnpack (UBYTE source[], LONG n, BYTE dest[])

/* Unpack Fibonacci-delta encoded data from n byte
 * source buffer into 2*(n-2) byte dest buffer.
 * Source buffer has a pad byte, an 8-bit initial
 * value, followed by n-2 bytes comprising 2*(n-2)
 * 4-bit encoded samples.
 */
{
	D1Unpack (source+2, n-2, dest, (BYTE)source[1]);
}



GLOBALCALL void SampChangeSign8 (UBYTE *samp, ULONG len)

/* Performs a sign conversion on a 8bit sample.  The same function can be
 * used to convert a signed sample into an unsigned one and vice versa.
 *
 * TODO: optimize with a LONG oriented loop
 */
{
	while (len)
		samp[--len] ^= 0x80;
}

