/*
**	TrackerHook.c
**
**	Copyright (C) 1993,94,95,96,97 Bernardo Innocenti
**
**
**	Load a Sound/Noise/ProTracker module with 15 or 31 instruments, decode
**	it and store into internal structures.
**
**	Save internal structures to a SoundTracker module with 15 instruments
**	or to a 31 instruments Noise/ProTracker unpacked module.
*/

#include <exec/memory.h>
#include <dos/stdio.h>

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

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



/* This structure holds a tracker row */

struct StRow
{
	UWORD Note;
	UBYTE InstEff;
	UBYTE EffVal;
};



/*	Definitions for various xxxTracker IDs. */

#define ID_SOUNDTRACKER		'ST15'		/* "ST15" */
#define ID_TAKETRACKER		'32CH'		/* "32CH" */
#define ID_FASTTRACKER1		'8CHN'		/* "8CHN" */
#define ID_NOISETRACKER		0x4D264B21	/* "M&K!" */
#define ID_PROTRACKER		0x4D2E4B2E	/* "M.K." */
#define ID_PROTRACKER100	0x4D214B21	/* "M!K!" */
#define ID_STARTREKKER4		0x464C5434	/* "FLT4" */
#define ID_STARTREKKER8		0x464C5438	/* "FLT8" */
#define ID_UNICTRACKER		0x454D5733	/* "EMW3" */
#define ID_POWERMUSIC		0x21504D21	/* "!PM!" */



/* Local function prototypes */
static HOOKCALL struct XMHook *IdentifyTracker (
	REG(d0, BPTR fh),
	REG(a0, struct XMHook *loader),
	REG(a1, ULONG *tags));
static HOOKCALL struct XMHook *IdentifySoundTracker (
	REG(d0, BPTR fh),
	REG(a0, struct XMHook *loader),
	REG(a1, ULONG *tags));
static HOOKCALL LONG SaveTracker (
	REG(d0, BPTR fh),
	REG(a0, struct SongInfo *si),
	REG(a1, struct XMHook *saver),
	REG(a2, ULONG *tags));
static HOOKCALL LONG LoadTracker (
	REG(d0, BPTR fh),
	REG(a0, struct SongInfo *si),
	REG(a1, struct XMHook *loader),
	REG(a2, ULONG *tags));

static void SetGlobalSpeed (struct SongInfo *si, UWORD tracks);
INLINE UWORD DecodeNote (UWORD Note, UWORD Patt, UWORD Line, UWORD Track);
INLINE UBYTE DecodeEff (UBYTE eff, UBYTE effval);


static const ULONG TakeTrackerIDs[32] =
{
	'TDZ1', 'TDZ2', 'TDZ3', 'M.K.', '5CHN', '6CHN', '7CHN', '8CHN',
	'9CHN', '10CH', '11CH', '12CH', '13CH', '14CH', '15CH', '16CH',
	'17CH', '18CH', '19CH', '20CH', '21CH', '22CH', '23CH', '24CH',
	'25CH', '26CH', '27CH', '28CH', '29CH', '30CH', '31CH', '32CH'
};


/* Note conversion table for Sound/Noise/ProTracker */
static const UWORD TrackerNotes[] =
{
	0x000,										/* Null note */

	0x6B0, 0x650, 0x5F5, 0x5A0, 0x54D, 0x501,	/* Octave 0 */
	0x4B9, 0x475, 0x435, 0x3F9, 0x3C1, 0x38B,

	0x358, 0x328, 0x2FA, 0x2D0, 0x2A6, 0x280,	/* Octave 1 */
	0x25C, 0x23A, 0x21A, 0x1FC, 0x1E0, 0x1C5,

	0x1AC, 0x194, 0x17D, 0x168, 0x153, 0x140,	/* Octave 2 */
	0x12E, 0x11d, 0x10D, 0x0FE, 0x0F0, 0x0E2,

	0x0D6, 0x0CA, 0x0BE, 0x0B4, 0x0AA, 0x0A0,	/* Octave 3 */
	0x097, 0x08F, 0x087, 0x07F, 0x078, 0x071,

	0x06B, 0x065, 0x05F, 0x05A, 0x055, 0x050,	/* Octave 4 */
	0x04C, 0x047, 0x043, 0x040, 0x03C, 0x039,

	0x035, 0x032, 0x030, 0x02D, 0x02A, 0x028,	/* Octave 5 */
	0x026, 0x024, 0x022, 0x020, 0x01E, 0x01C
};



static const UBYTE Effects[MAXTABLEEFFECTS] =
{
/*    STRK  	XModule			Val */

	0x00,	/*	Null effect		$00	*/

	0x01,	/*	Portamento Up	$01	*/
	0x02,	/*	Portamento Down	$02	*/
	0x03,	/*	Tone Portamento	$03	*/
	0x04,	/*	Vibrato			$04	*/
	0x05,	/*	ToneP + VolSl	$05	*/
	0x06,	/*	Vibra + VolSl	$06	*/
	0x07,	/*	Tremolo			$07	*/
	0x08,	/*	Set Hold/Decay	$08	*/
	0x09,	/*	Sample Offset	$09	*/
	0x0A,	/*	Volume Slide	$0A	*/
	0x0B,	/*	Position Jump  	$0B	*/
	0x0C,	/*	Set Volume		$0C	*/
	0x0D,	/*	Pattern break	$0D	*/
	0x0E,	/*	Misc			$0E	*/
	0x0F,	/*	Set Speed		$0F	*/
	0x0F,	/*	Set Tempo		$10	*/
	0x00,	/*	Arpeggio		$11	*/

	0x03, 	/*	Oktalyzer H			*/
	0x03, 	/*	Oktalyzer L			*/
};



static struct XMHook *ProTrackerLoader = NULL;



static HOOKCALL struct XMHook *IdentifyTracker (
	REG(d0, BPTR fh),
	REG(a0, struct XMHook *loader),
	REG(a1, ULONG *tags))

/* Determine if the given file is an XyzTracker module.
 * Note: the file position will be changed on exit.
 */
{
	LONG id;
	ULONG i;

	Seek (fh, 1080, OFFSET_BEGINNING);
	if (FRead (fh, &id, 4, 1) != 1)
		return NULL;

	/* Check Noise/ProTracker */
	if ((id == ID_PROTRACKER) || (id == ID_PROTRACKER100) || (id == ID_NOISETRACKER) ||
		(id == ID_UNICTRACKER) || (id == ID_STARTREKKER4))
		return loader;

	/* Check for Multi/Fast Tracker */
	for (i = 0; i < 32; i++)
		if (id == TakeTrackerIDs[i])
			return loader;

	return NULL;
}



static HOOKCALL struct XMHook *IdentifySoundTracker (
	REG(d0, BPTR fh),
	REG(a0, struct XMHook *loader),
	REG(a1, ULONG *tags))

/* Last chance loader: SoundTracker15 or weird ProTracker module */
{
	switch (ShowRequestArgs (MSG_UNKNOWN_MOD_FORMAT, MSG_SOUND_PRO_CANCEL, NULL))
	{
		case 1:
			return loader;

		case 2:
			return ProTrackerLoader;

		default:
			return NULL;
	}
}



static HOOKCALL LONG SaveTracker (
	REG(d0, BPTR fh),
	REG(a0, struct SongInfo *si),
	REG(a1, struct XMHook *saver),
	REG(a2, ULONG *tags))
{
	struct Instrument *instr;
	ULONG	pattcount = 0, maxnumpatt = 64, numtracks = 4, songlength;
	ULONG	l;
	ULONG	i, j, k;	/* Loop counters */
	UBYTE c;
	struct { UWORD Note; UBYTE InstEff; UBYTE EffVal; }
		strow; /* Temporary storage for a SoundTracker row */



	/* Calculate pattcount */

	songlength = min (si->Length, 128);

	for (i = 0 ; i < songlength ; i++)
		if (si->Sequence[i] > pattcount)
			pattcount = si->Sequence[i];

	pattcount++;	/* Pattern numbering starts from 0 */


	/* Write Song name */
	{
		UBYTE name[20] = {0};

		/* Tracker modules do not require the NULL terminator */
		if (si->Title) strncpy (name, si->Title, 20);

		if (FWrite (fh, name, 20, 1) != 1) return ERROR_IOERR;
	}


	/* Write instruments name, length, volume, repeat, replen */

	xmDisplayMessageA (XMDMF_USECATALOG | XMDMF_ACTION,
		(APTR)MSG_WRITING_INSTINFO, NULL);


	for ( j = 1 ; j < (((ULONG)saver->xmh_ID == ID_SOUNDTRACKER) ? 16 : 32) ; j++ )
	{
		struct
		{
			UBYTE namebuf[22];
			UWORD length;
			UWORD volume;
			UWORD repeat;
			UWORD replen;
		} modinstr;


		memset (&modinstr, 0, sizeof (modinstr));

		if (instr = si->Instr[j])
		{
			/* Some Trackers require the instrument buffer to be
			 * padded with 0's.
			 */
			strncpy (modinstr.namebuf, instr->Name, 22);

			/* SoundTracker cannot handle instruments longer than 64K */
			if (instr->Length > 0xFFFE)
			{
				modinstr.length = 0x7FFF;
				xmDisplayMessage (XMDMF_USECATALOG | XMDMF_WARNING,
					(APTR)MSG_INSTR_TOO_LONG, j);
	 		}
			else modinstr.length = instr->Length >> 1;

			if (!instr->Sample) modinstr.length = 0;


			if (saver->xmh_ID == ID_SOUNDTRACKER)
				modinstr.volume = instr->Volume;
			else
				modinstr.volume = instr->Volume | ((instr->FineTune & 0x0F) << 8);

			modinstr.repeat = instr->Repeat >> 1;
			modinstr.replen = instr->Replen >> 1;
			if (!modinstr.replen) modinstr.replen = 1;
		}

		if (FWrite (fh, &modinstr, sizeof (modinstr), 1) != 1)
			return ERROR_IOERR;
	}


	/******************************************/
	/* Put number of positions in song (BYTE) */
	/******************************************/

	if (FPutC (fh, songlength) == ENDSTREAMCH)
		return ERROR_IOERR;


	/*****************************************/
	/* Put number of patterns in song (BYTE) */
	/*****************************************/

	switch (saver->xmh_ID)
	{
		case ID_SOUNDTRACKER:
			/* SoundTracker stores the number of patterns here */
			c = pattcount;
			break;

		case ID_STARTREKKER4:
			/* StarTrekker modules store restart value here */
			c = si->RestartPos;
			break;

		default:
			/* Noise/ProTracker stores $7F as the number of patterns.
			 * The correct number of patterns is calculated by looking
			 * for the highest pattern referenced in the position table.
			 * Therefore, unused patterns MUST be removed, or the Tracker
			 * will get confused loading the module.
			 */
			c = 0x7F;
			break;
	}

	if (FPutC (fh, c) == ENDSTREAMCH)
		return ERROR_IOERR;


	/********************/
	/* Choose module ID */
	/********************/

	l = saver->xmh_ID;

	switch (l)
	{
		case ID_PROTRACKER:
			if (pattcount > 64)
			{
				l = ID_PROTRACKER100;
				maxnumpatt = 100;
				xmDisplayMessageA (XMDMF_WARNING | XMDMF_USECATALOG,
					(APTR)MSG_EXCEEDS_64_PATTS, NULL);
			}
			break;

		case ID_PROTRACKER100:
			maxnumpatt = 100;
			if (pattcount <= 64)
				l = ID_PROTRACKER;
			break;

		case ID_TAKETRACKER:
			numtracks = si->MaxTracks;
			l = TakeTrackerIDs[si->MaxTracks - 1];
			maxnumpatt = 100;
			break;

		default:
			break;
	}

	if (pattcount >= maxnumpatt)
	{
		pattcount = maxnumpatt;
		xmDisplayMessage (XMDMF_WARNING | XMDMF_USECATALOG,
			(APTR)MSG_EXCEEDS_MAXPAATTS, maxnumpatt);
	}


	/************************/
	/* Write position table */
	/************************/
	{
		UBYTE postable[128];

		memset (postable, 0, 128);

		/* All this is needed because ProTracker has serious
		 * problems dealing with modules whose postable has
		 * references to inexistant patterns.
		 */
		for (i = 0; i < songlength; i++)
		{
			postable[i] = si->Sequence[i];

			if (postable[i] >= pattcount)
				postable[i] = 0;
		}

		if (FWrite (fh, postable, 1, 128) != 128) return ERROR_IOERR;
	}


	/*******************/
	/* Write module ID */
	/*******************/

	if (l != ID_SOUNDTRACKER)
		if (FWrite (fh, &l, 4, 1) != 1) return ERROR_IOERR;



	/**********************/
	/* Write pattern data */
	/**********************/

	SetGlobalSpeed (si, numtracks);

	xmDisplayMessageA (XMDMF_USECATALOG | XMDMF_ACTION,
		(APTR)MSG_WRITING_PATTS, NULL);

	for (i = 0 ; i < pattcount ; i++)
	{
		struct Note **tracks = si->Patt[i]->Notes;

		if (xmDisplayProgress (i, pattcount))
			return ERROR_BREAK;

		for (j = 0 ; j < 0x40 ; j++)
		{
			for (k = 0 ; k < numtracks ; k++)
			{
				struct Note *note = &tracks[k][j];

				/* Translate Note */
				strow.Note = TrackerNotes[note->Note];

				/* Translate instr # (high nibble) & effect (low nibble) */
				c = note->Inst;

				if ((c > 0x0F) && (saver->xmh_ID != ID_SOUNDTRACKER))
				{
					/* Noise/ProTracker stores the high bit of the
					 * instrument number in bit 15 of the note value.
					 */
					strow.Note |= 0x1000;
				}

				strow.InstEff = (c << 4) | Effects[note->EffNum];

				/* Copy effect value */
				strow.EffVal = note->EffVal;

				/* Write the Row */
				if (FWrite (fh, &strow, 4, 1) != 1) return ERROR_IOERR;
			}
		}
	}

	/* Instruments are written using non buffered I/O because it's
	 * generally more efficient when writing big buffers.
	 */
	Flush (fh);

	/********************/
	/* Save Instruments */
	/********************/

	xmDisplayMessageA (XMDMF_USECATALOG | XMDMF_ACTION,
		(APTR)MSG_WRITING_INSTDATA, NULL);

	for (i = 1 ; i < (saver->xmh_ID == ID_SOUNDTRACKER ? 16 : 32) ; i++)
	{
		ULONG len;

		if (!(instr = si->Instr[i])) continue;

		len = instr->Length & (~1);

		/* Adapt instrument to SoundTracker 64K limit */
		if (len > 0xFFFE) len = 0xFFFE;

		if (xmDisplayProgress (i, (saver->xmh_ID == ID_SOUNDTRACKER) ? 16 : 32))
			return ERROR_BREAK;

		if (len)
			if (Write (fh, instr->Sample, len) != len)
				return ERROR_IOERR;
	}

	return (0);
}



static HOOKCALL LONG LoadTracker (
	REG(d0, BPTR fh),
	REG(a0, struct SongInfo *si),
	REG(a1, struct XMHook *loader),
	REG(a2, ULONG *tags))
{
	struct Note *note;
	struct Instrument	*instr;
	struct Pattern		*patt;
	struct StRow		*stpatt,	/* Temporary storage for a Tracker pattern */
						*strow;

	ULONG i, j, k;		/* Loop counters */
	ULONG pattsize;
	UWORD numtracks = 4, numpatterns, songlen;
	UBYTE c;			/* Read buffers */

	ULONG soundtracker = (loader->xmh_ID == ID_SOUNDTRACKER);


	/* Get the score name */
	{
		UBYTE name[21];

		if (FRead (fh, name, 20, 1) != 1) return ERROR_IOERR;
		name[20] = '\0'; /* Ensure the string is Null-terminated */

		SetAttrs (si,
			SNGA_Title, name,
			TAG_DONE);
	}

	/* Get the Instruments name, length, cycle, effects */

	xmDisplayMessageA (XMDMF_USECATALOG | XMDMF_ACTION,
		(APTR)MSG_READING_INSTS_INFO, NULL);

	for ( j = 1 ; j < (soundtracker ? 16 : 32); j++ )
	{
		struct
		{
			UBYTE	namebuf[22];
			UWORD	length;
			BYTE	finetune;
			UBYTE	volume;
			UWORD	repeat;
			UWORD	replen;
		} modinstr;


		memset (&modinstr, 0, sizeof (modinstr));

		if (FRead (fh, &modinstr, sizeof (modinstr), 1) != 1)
			return ERROR_IOERR;

		if (modinstr.namebuf[0] || modinstr.length)
		{
			modinstr.namebuf[21] = '\0'; /* Ensure the string is Null-terminated */

			if (!xmAddInstrument (si, j,
				INSTRA_Name,		modinstr.namebuf,
				INSTRA_Length,		modinstr.length << 1,
				INSTRA_FineTune,	(modinstr.finetune & 0x08) ?	/* Fix sign */
					(modinstr.finetune | 0xF0) : modinstr.finetune,
				INSTRA_Volume,		modinstr.volume,
				INSTRA_Repeat,		modinstr.repeat << 1,
				INSTRA_Replen,		(modinstr.replen == 1) ? 0 : (modinstr.replen << 1),
				TAG_DONE))
				return ERROR_NO_FREE_STORE;
		}
	}


	/* Read song length */

	if ((songlen = FGetC (fh)) == ENDSTREAMCH) return ERROR_IOERR;

	/* Read & Ignore number of song patterns.
	 *
	 * Noise/ProTracker stores $7F as the number of patterns.
	 * The correct number of patterns is calculated by looking
	 * for the highest pattern referenced in the position table.
	 * The OctaMED saver wrongly puts the number of patterns
	 * when saving as a ProTracker module.
	 * SoundTracker should save the correct number of patterns,
	 * but some 15 instrument modules I found had incorrect
	 * values here.  So we always ignore this field.
	 */
	if (FGetC (fh) == ENDSTREAMCH) return ERROR_IOERR;


	/* Read the song sequence */
	{
		UBYTE postable[128];

		if (FRead (fh, postable, 1, 128) != 128) return ERROR_IOERR;

		if (!(xmSetSongLen (si, songlen)))
			return ERROR_NO_FREE_STORE;

		for (i = 0; i < si->Length; i++)
			si->Sequence[i] = postable[i];

		/* Search for the highest pattern referenced in the position
		 * table to find out the actual number of patterns.
		 *
		 * Important note: Some modules do have position table
		 * entries beyond the song length.  These references *must*
		 * be taken into account to calculate the last pattern, so
		 * looping only <songlen> times here is *wrong*.  The
		 * correct behaviour is to always look all of the 128
		 * position table entries.
		 */

		numpatterns = 0;

		for (i = 0 ; i < 128 ; i++)
			if (postable[i] > numpatterns)
				numpatterns = postable[i];

		numpatterns++;	/* Pattern numbering starts from 0 */
	}



	/* Check module ID */

	if (!soundtracker)
	{
		ULONG __aligned id = 0;

		/* The module ID could have been stripped away in modules
		 * coming from games and intros to make the module harder
		 * to rip, therefore its absence is acceptable.
		 */
		if (FRead (fh, &id, 4, 1) != 1) return ERROR_IOERR;


		/* Check for FastTracker/TakeTracker IDs */

		for (i = 0; i < 32; i++)
		{
			if (id == TakeTrackerIDs[i])
			{
				numtracks = i + 1;
				break;
			}
		}

		if (id)
		{
			UBYTE buf[5];

			xmDisplayMessage (XMDMF_USECATALOG | XMDMF_INFORMATION,
				(APTR)MSG_MODULE_ID, IDtoStr (id, buf));
		}
	}


	xmDisplayMessage (XMDMF_USECATALOG | XMDMF_INFORMATION,
		(APTR)MSG_MODULE_HAS_N_CHN, numtracks);


	/* Allocate memory for a full SoundTracker pattern */

	si->MaxTracks = numtracks;
	pattsize = (sizeof (struct StRow) * 0x40) * numtracks;

	if (!(stpatt = AllocMem (pattsize, 0)))
		return ERROR_NO_FREE_STORE;


	/* Read pattern data */

	xmDisplayMessageA (XMDMF_USECATALOG | XMDMF_ACTION,
		(APTR)MSG_READING_PATTS, NULL);

	for (i = 0; i < numpatterns; i++)
	{
		if (xmDisplayProgress (i, numpatterns))
		{
			FreeMem (stpatt, pattsize);
			return ERROR_BREAK;
		}

		/* Read a whole Tracker row */
		if (FRead (fh, stpatt, pattsize, 1) != 1)
		{
			FreeMem (stpatt, pattsize);
			return ERROR_IOERR;
		}

		/* Reset note counter */
		strow = stpatt;

		/* Allocate memory for pattern */
		if (!(patt = xmAddPattern (si,
			PATTA_Tracks,	numtracks,
			PATTA_Lines,	64,
			TAG_DONE)))
		{
			FreeMem (stpatt, pattsize);
			return ERROR_NO_FREE_STORE;
		}

		for ( j = 0 ; j < 0x40 ; j++ )
		{
			for ( k = 0 ; k < numtracks ; k++, strow++ )
			{
				/* Get address of the current pattern row */
				note = &patt->Notes[k][j];

				/* Decode note (highest nibble is cleared) */
				note->Note = DecodeNote (strow->Note & 0xFFF, i, j, k);

				/* Decode instrument number (high nibble) */
				c = strow->InstEff >> 4; /* Get instrument nr. */
				if (!soundtracker && (strow->Note & 0x1000))
					c |= 0x10;	/* High bit of Noise/ProTracker instrument */
				note->Inst = c;

				/* Decode effect (low nibble) */
				note->EffNum = DecodeEff (strow->InstEff & 0x0F, strow->EffVal);

				/* Copy effect value */
				note->EffVal = strow->EffVal;
			}
		}
	}

	FreeMem (stpatt, pattsize);


	/* Look for a SetSpeed command ($F) in the first row and
	 * set si->GlobalSpeed.  If Speed is higher than 31,
	 * si->GlobalTempo should be initialized instead. (TODO)
	 */

	SetAttrs (si,
		SNGA_Author, -1,
		SNGA_GlobalSpeed,	6,
		SNGA_GlobalTempo,	125,
		TAG_DONE);


	/* Load Instruments */

	xmDisplayMessageA (XMDMF_USECATALOG | XMDMF_ACTION,
		(APTR)MSG_READING_INSTS, NULL);


	for (j = 1 ; j < (soundtracker ? 16 : 32) ; j++)
	{
		BYTE *sample;

		/* Check if instrument exists */

		if (!(instr = si->Instr[j])) continue;
		if (instr->Length == 0) continue;

		if (xmDisplayProgress (j, soundtracker ? 16 : 32))
			return ERROR_BREAK;

		if (!(sample = (BYTE *) AllocVec (instr->Length, MEMF_SAMPLE)))
			return ERROR_NO_FREE_STORE;

		if (FRead (fh, sample, instr->Length, 1) != 1)
		{
			FreeVec (sample);

			/* Clear instrument lengths */
			for (i = j; i < 31; i++)
				if (si->Instr[i]) si->Instr[i]->Length = 0;

			if (j == 0)
			{
				xmDisplayMessageA (XMDMF_WARNING | XMDMF_USECATALOG,
					(APTR)MSG_SONG_HAS_NO_INSTS, NULL);

				return RETURN_WARN;	/* Tell 'em this is a damn song */
			}

			return ERROR_IOERR;
		}

		xmSetInstrument (si, j,
			INSTRA_Sample, sample,
			TAG_DONE);
	}

	/* Check for extra data following the module */
	if (FGetC (fh) != ENDSTREAMCH)
		xmDisplayMessageA (XMDMF_USECATALOG | XMDMF_NOTE,
			(APTR)MSG_EXTRA_DATA_AFTER_MOD, NULL);

	return RETURN_OK;
}



INLINE UWORD DecodeNote (UWORD Note, UWORD Patt, UWORD Line, UWORD Track)
{
	if (!Note) return 0;

	{
		ULONG n, mid, low = 1, high = MAXNOTES-1;

		/* The nice binary search learnt at school ;-) */
		do
		{
			mid = (low + high) / 2;
			if ((n = TrackerNotes[mid]) > Note) low = mid + 1;
			else if (n < Note) high = mid - 1;
			else return (UWORD)mid;
		} while (low <= high);
	}
	xmDisplayMessage (XMDMF_USECATALOG | XMDMF_NOTE,
		(APTR)MSG_INVALID_NOTE, Note, Patt, Track, Line);
	return 0;
}



INLINE UBYTE DecodeEff (UBYTE eff, UBYTE effval)
{
	UBYTE i;

	if (eff == 0 && effval)
		return (EFF_ARPEGGIO);

	if (eff == 0x0F) /* Speed */
	{
		if (effval < 0x20)
			return EFF_SETSPEED;
		else
			return EFF_SETTEMPO;
	}

	for ( i = 0 ; i < MAXTABLEEFFECTS ; i++ )
		if (eff == Effects[i])
			return i;

	return 0;
}



static void SetGlobalSpeed (struct SongInfo *si, UWORD tracks)

/* Put a speed command ($F) at the first line played in the song */
{
	struct Pattern *patt = si->Patt[si->Sequence[0]];
	struct Note **pn = patt->Notes;
	ULONG i;

	tracks = min (tracks, patt->Tracks);

	/* Do it only if required */
	if (si->GlobalSpeed != DEF_SONGSPEED)
	{
		/* Ensure a SetSpeed command does not exist yet in the first row... */
		for (i = 0 ; i < tracks ; i++)
			if (pn[i][0].EffNum == EFF_SETSPEED)
				goto settempo;	/* Speed is already set, now for the Tempo... */

		/* Try to find a free effect slot in the row... */
		for (i = 0 ; i < tracks ; i++)
			if (pn[i][0].EffNum == EFF_NULL && pn[i][0].EffVal == 0)
					break;

		pn[i][0].EffNum = EFF_SETSPEED;
		pn[i][0].EffVal = si->GlobalSpeed;
	}

settempo:
	if (si->GlobalTempo != DEF_SONGTEMPO)
	{
		/* Ensure a SetTempo command does not exist yet in the first row... */
		for (i = 0 ; i < tracks ; i++)
			if (pn[i][0].EffNum == EFF_SETTEMPO)
				return;	/* Tempo is already set, nothing else to do... */

		/* Try to find a free effect slot in the row... */
		for (i = 0 ; i < tracks ; i++)
			if (pn[i][0].EffNum == 0 && pn[i][0].EffVal == 0)
				break;

		pn[i][0].EffNum = EFF_SETTEMPO;
		pn[i][0].EffVal = si->GlobalTempo;
	}
}



GLOBALCALL void AddTrackerHooks (void)

/* Adds Tracker loaders and savers */
{
	ProTrackerLoader = xmAddHook (
		XMHOOK_Type,			NT_XMLOADER,
		XMHOOK_Name,			(LONG)"Tracker",
		XMHOOK_Priority,		10,
		XMHOOK_Descr,			(LONG)"Sound/Noise/Star/Pro/Fast/TakeTracker",
		XMHOOK_Author,			(LONG)"Bernardo Innocenti",
		XMHOOK_ID,				ID_PROTRACKER,
		XMHOOK_Flags,			XMHF_INTERNAL,
		XMHOOK_LoadModFunc,		LoadTracker,
		XMHOOK_IdentifyModFunc,	IdentifyTracker,
		XMHOOK_MaxTracks,		32,
		XMHOOK_MaxPatterns,		255,
		XMHOOK_MaxInstruments,	31,
		XMHOOK_MaxLength,		128,
		XMHOOK_MaxSampleLen,	65534,
		XMHOOK_MaxPattLen,		64,
		TAG_DONE);

	xmAddHook (
		XMHOOK_Type,			NT_XMLOADER,
		XMHOOK_Name,			(LONG)"SoundTracker",
		XMHOOK_Priority,		-100,
		XMHOOK_Descr,			(LONG)"SoundTracker 15 instruments",
		XMHOOK_Author,			(LONG)"Bernardo Innocenti",
		XMHOOK_ID,				ID_SOUNDTRACKER,
		XMHOOK_Flags,			XMHF_INTERNAL | XMHF_FIXED_PATT_LEN,
		XMHOOK_LoadModFunc,		LoadTracker,
		XMHOOK_IdentifyModFunc,	IdentifySoundTracker,
		XMHOOK_MaxTracks,		4,
		XMHOOK_MaxPatterns,		64,
		XMHOOK_MaxInstruments,	15,
		XMHOOK_MaxLength,		128,
		XMHOOK_MaxSampleLen,	65534,
		XMHOOK_MaxPattLen,		64,
		TAG_DONE);

	xmAddHook (
		XMHOOK_Type,			NT_XMSAVER,
		XMHOOK_Name,			(LONG)"TakeTracker",
		XMHOOK_Priority,		20,
		XMHOOK_Descr,			(LONG)"TakeTracker",
		XMHOOK_Author,			(LONG)"Bernardo Innocenti",
		XMHOOK_ID,				ID_TAKETRACKER,
		XMHOOK_Flags,			XMHF_INTERNAL | XMHF_EXCLUDE_INSTRUMENTS |
								XMHF_EXCLUDE_NAMES | XMHF_FIXED_PATT_LEN,
		XMHOOK_SaveModFunc,		SaveTracker,
		XMHOOK_MaxTracks,		32,
		XMHOOK_MaxPatterns,		100,
		XMHOOK_MaxInstruments,	31,
		XMHOOK_MaxLength,		128,
		XMHOOK_MaxSampleLen,	65534,
		XMHOOK_MaxPattLen,		64,
		TAG_DONE);

	xmAddHook (
		XMHOOK_Type,			NT_XMSAVER,
		XMHOOK_Name,			(LONG)"FastTracker1",
		XMHOOK_Priority,		20,
		XMHOOK_Descr,			(LONG)"FastTracker 1.0",
		XMHOOK_Author,			(LONG)"Bernardo Innocenti",
		XMHOOK_ID,				ID_FASTTRACKER1,
		XMHOOK_Flags,			XMHF_INTERNAL | XMHF_EXCLUDE_INSTRUMENTS |
								XMHF_EXCLUDE_NAMES | XMHF_FIXED_PATT_LEN,
		XMHOOK_SaveModFunc,		SaveTracker,
		XMHOOK_MaxTracks,		8,
		XMHOOK_MaxPatterns,		128,
		XMHOOK_MaxInstruments,	31,
		XMHOOK_MaxLength,		128,
		XMHOOK_MaxSampleLen,	65534,
		XMHOOK_MaxPattLen,		64,
		TAG_DONE);

	xmAddHook (
		XMHOOK_Type,			NT_XMSAVER,
		XMHOOK_Name,			(LONG)"ProTracker",
		XMHOOK_Priority,		20,
		XMHOOK_Descr,			(LONG)"ProTracker 2.x - 3.x",
		XMHOOK_Author,			(LONG)"Bernardo Innocenti",
		XMHOOK_ID,				ID_PROTRACKER,
		XMHOOK_Flags,			XMHF_INTERNAL | XMHF_EXCLUDE_INSTRUMENTS |
								XMHF_EXCLUDE_NAMES | XMHF_FIXED_PATT_LEN,
		XMHOOK_SaveModFunc,		SaveTracker,
		XMHOOK_MaxTracks,		4,
		XMHOOK_MaxPatterns,		64,
		XMHOOK_MaxInstruments,	31,
		XMHOOK_MaxLength,		128,
		XMHOOK_MaxSampleLen,	65534,
		XMHOOK_MaxPattLen,		64,
		TAG_DONE);

	xmAddHook (
		XMHOOK_Type,			NT_XMSAVER,
		XMHOOK_Name,			(LONG)"ProTracker100",
		XMHOOK_Priority,		15,
		XMHOOK_Descr,			(LONG)"ProTracker 2.3 (100 patterns)",
		XMHOOK_Author,			(LONG)"Bernardo Innocenti",
		XMHOOK_ID,				ID_PROTRACKER100,
		XMHOOK_Flags,			XMHF_INTERNAL | XMHF_EXCLUDE_INSTRUMENTS |
								XMHF_EXCLUDE_NAMES | XMHF_FIXED_PATT_LEN,
		XMHOOK_SaveModFunc,		SaveTracker,
		XMHOOK_MaxTracks,		4,
		XMHOOK_MaxPatterns,		100,
		XMHOOK_MaxInstruments,	31,
		XMHOOK_MaxLength,		128,
		XMHOOK_MaxSampleLen,	65534,
		XMHOOK_MaxPattLen,		64,
		TAG_DONE);

	xmAddHook (
		XMHOOK_Type,			NT_XMSAVER,
		XMHOOK_Name,			(LONG)"NoiseTracker",
		XMHOOK_Priority,		15,
		XMHOOK_Descr,			(LONG)"NoiseTracker 31 instruments",
		XMHOOK_Author,			(LONG)"Bernardo Innocenti",
		XMHOOK_ID,				ID_NOISETRACKER,
		XMHOOK_Flags,			XMHF_INTERNAL | XMHF_EXCLUDE_INSTRUMENTS |
								XMHF_EXCLUDE_NAMES | XMHF_FIXED_PATT_LEN,
		XMHOOK_SaveModFunc,		SaveTracker,
		XMHOOK_MaxTracks,		4,
		XMHOOK_MaxPatterns,		64,
		XMHOOK_MaxInstruments,	31,
		XMHOOK_MaxLength,		128,
		XMHOOK_MaxSampleLen,	65534,
		XMHOOK_MaxPattLen,		64,
		TAG_DONE);

	xmAddHook (
		XMHOOK_Type,			NT_XMSAVER,
		XMHOOK_Name,			(LONG)"StarTrekker4",
		XMHOOK_Priority,		10,
		XMHOOK_Descr,			(LONG)"StarTrakker 4 channels",
		XMHOOK_Author,			(LONG)"Bernardo Innocenti",
		XMHOOK_ID,				ID_STARTREKKER4,
		XMHOOK_Flags,			XMHF_INTERNAL | XMHF_EXCLUDE_INSTRUMENTS |
								XMHF_EXCLUDE_NAMES | XMHF_FIXED_PATT_LEN,
		XMHOOK_SaveModFunc,		SaveTracker,
		XMHOOK_MaxTracks,		4,
		XMHOOK_MaxPatterns,		64,
		XMHOOK_MaxInstruments,	31,
		XMHOOK_MaxLength,		128,
		XMHOOK_MaxSampleLen,	65534,
		XMHOOK_MaxPattLen,		64,
		TAG_DONE);

	xmAddHook (
		XMHOOK_Type,			NT_XMSAVER,
		XMHOOK_Name,			(LONG)"SoundTracker",
		XMHOOK_Descr,			(LONG)"Old SoundTracker 15 instruments",
		XMHOOK_Author,			(LONG)"Bernardo Innocenti",
		XMHOOK_ID,				ID_SOUNDTRACKER,
		XMHOOK_Flags,			XMHF_INTERNAL | XMHF_EXCLUDE_INSTRUMENTS |
								XMHF_EXCLUDE_NAMES | XMHF_FIXED_PATT_LEN,
		XMHOOK_SaveModFunc,		SaveTracker,
		XMHOOK_UserData,		ID_SOUNDTRACKER,
		XMHOOK_MaxTracks,		4,
		XMHOOK_MaxPatterns,		64,
		XMHOOK_MaxInstruments,	15,
		XMHOOK_MaxLength,		128,
		XMHOOK_MaxSampleLen,	65534,
		XMHOOK_MaxPattLen,		64,
		TAG_DONE);
}

