/*
**	SongClass.c
**
**	Copyright (C) 1994,95,96,97 Bernardo Innocenti
**
**	Song class dispatcher and handling functions.
*/

/****** songclass/--background-- *******************************************
*
*	NAME
*		songclass -- XModule 'boopsi'-oriented song implementation.
*
*	DESCRIPTION
*		The song class is an object oriented way to handle a song.  The song
*		class handles all data storing mechanisms for you and adds a layer
*		of abstraction between the song internal data structures and the
*		application.  The advantage is that the internal structures can be
*		changed while keeping compatibility with existing software.
*
*		Another great advantage of being a 'boopsi' class is that the song
*		can notify other boopsi objects whenever its attributes change.
*		This simplifies the task of keeping the user interface updated each
*		time the user (or an ARexx macro, or whatever) changes something.
*
*		For speed reasons, the song class does also allow 'white box'
*		istance access.  This means that you can also directly access
*		the internal data structures of the song, without using standard
*		boopsi methods.  You are ONLY allowed to READ public fields, but not
*		to write any of them.  The main reason to forbid direct writing is
*		that the song class must send notifications to its targets, but it
*		does also allow the song implementation to change in future version
*		without breaking existing applications.
*
*		When you create a new istance of the song class, the object handle
*		you get is actually a SongInfo structure.  This is only possible
*		because the song class is a subclass of the rootclass, whose istance
*		is placed at a negative offset in the object handle.
*		Future song class implementations could require to be subclasses
*		of other classes, such as the gadget class or even the datatypes
*		class.  This problem will be probably got around by keeping the
*		root class as the real superclass of the song and creating an
*		istance of the other superclass which will be passed all the
*		methods which are not recognized by the song its-self.  Call this
*		boopsi polymorphism, if you like to :-)
*
*	QUOTATION
*		Don't be a tuna head.
*
****************************************************************************
*/


#include <exec/memory.h>
#include <intuition/classes.h>
#include <intuition/classusr.h>
#include <libraries/xmodule.h>

#include <clib/alib_protos.h>
#include <proto/exec.h>
#include <proto/dos.h>
#include <proto/intuition.h>
#include <proto/utility.h>
#include <proto/icon.h>
#include <proto/xmodule.h>

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



/* Local function prototypes */

static ULONG HOOKCALL SongDispatcher (
	REG(a0, Class *cl),
	REG(a2, struct SongInfo *si),
	REG(a1, Msg msg));

INLINE ULONG			 SongNewMethod		(Class *cl, struct SongInfo *si, struct opSet *opset);
INLINE ULONG			 SongDisposeMethod	(Class *cl, struct SongInfo *si, Msg msg);
INLINE ULONG			 SongSetMethod		(Class *cl, struct SongInfo *si, struct opSet *opset);

static BOOL					 GuessAuthor		(struct SongInfo *si);
static UWORD				*SetSongLen			(struct SongInfo *si, ULONG len);
static struct Pattern		*AddPattern			(struct SongInfo *si, struct TagItem *tags);
static LONG					 SetPattern			(struct SongInfo *si, ULONG patnum, struct TagItem *tags);
static void					 RemPattern			(struct SongInfo *si, ULONG patnum, ULONG newpatt);
static struct Instrument	*AddInstrument		(struct SongInfo *si, ULONG num, struct TagItem *tags);
static LONG					 SetInstrument		(struct SongInfo *si, ULONG num, struct TagItem *tags);
static void					 RemInstrument		(struct SongInfo *si, ULONG num);
static void					 SwapInstruments	(struct SongInfo *si, LONG n, LONG m);




GLOBALCALL Class *InitSongClass (void)
{
	Class *class;

	if (class = MakeClass (NULL, ROOTCLASS, NULL, sizeof (struct SongInfo), 0))
		class->cl_Dispatcher.h_Entry = (ULONG (*)()) SongDispatcher;

	return class;
}



GLOBALCALL void FreeSongClass (Class *cl)
{
	while (!(FreeClass (cl)))
		ShowRequest (MSG_CLOSE_ALL_SONGS, MSG_CONTINUE, NULL);
}



static ULONG HOOKCALL SongDispatcher (
	REG(a0, Class *cl),
	REG(a2, struct SongInfo *si),
	REG(a1, Msg msg))
{
	ULONG result = 0;

	/* Warning: the song class can only be a subclass of the rootclass because
	 * its istance must be at offset 0 in the object handle.
	 */

	switch (msg->MethodID)
	{
		case OM_NEW:
			result = SongNewMethod (cl, si, (struct opSet *)msg);
			break;

		case OM_DISPOSE:
			result = SongDisposeMethod (cl, si, msg);
			break;

		case OM_SET:
		case OM_UPDATE:
			result = SongSetMethod (cl, si, (struct opSet *)msg);
			break;

		case OM_GET:
			break;

		case OM_NOTIFY:
			break;

		case SNGM_ADDPATTERN:
			result = (ULONG) AddPattern (si, ((struct opSet *)msg)->ops_AttrList);
			break;

		case SNGM_SETPATTERN:
			result = (ULONG) SetPattern (si, ((struct spSetPattern *)msg)->spsp_PattNum,
				((struct spSetPattern *)msg)->spsp_AttrList);

		case SNGM_REMPATTERN:
			RemPattern (si,
				((struct spRemPattern *)msg)->sprp_PattNum,
				((struct spRemPattern *)msg)->sprp_NewPatt);
			break;

		case SNGM_ADDINSTRUMENT:
			result = (ULONG) AddInstrument (si,
				((struct spAddInstrument *)msg)->spsi_InstrNum,
				((struct spAddInstrument *)msg)->spsi_AttrList);
			break;

		case SNGM_SETINSTRUMENT:
			result = (ULONG) SetInstrument (si,
				((struct spSetInstrument *)msg)->spsi_InstrNum,
				((struct spSetInstrument *)msg)->spsi_AttrList);
			break;

		case SNGM_REMINSTRUMENT:
			RemInstrument (si,
				((struct spRemInstrument *)msg)->spri_Num);
			break;

		case SNGM_SWAPINSTRUMENTS:
			SwapInstruments (si,
				((struct spSwapInstruments *)msg)->spsi_Num1,
				((struct spSwapInstruments *)msg)->spsi_Num2);
			break;

		default:

			/* Unsupported method: let our superclass take a look at it. */
			result = DoSuperMethodA (cl, (Object *)si, msg);
			break;
	}

	return result;
}



INLINE ULONG SongNewMethod (Class *cl, struct SongInfo *si, struct opSet *opset)

/* OM_NEW */
{
	if (si = (struct SongInfo *)DoSuperMethodA (cl, (Object *)si, (Msg)opset))
	{
		ULONG dummy;

		/* Clear object instance */
		memset (si, 0, sizeof (struct SongInfo));

		/* Initialize song instance to defaults values */
		si->Pool			= XModuleBase->xm_Pool;
		si->DefNumTracks	= DEF_NUMTRACKS;
		si->DefPattLen		= DEF_PATTLEN;
		si->GlobalSpeed		= DEF_SONGSPEED;
		si->GlobalTempo		= DEF_SONGTEMPO;
		si->Instr			= si->InstrumentsTable;
		DupStringPooled (si->Pool, STR(MSG_AUTHOR_UNKNOWN), &si->Author);
		DupStringPooled (si->Pool, STR(MSG_SONG_UNTITLED), &si->Title);
		si->Link.ln_Name	= si->Title;
		CurrentTime			(&si->CreationDate, &dummy);
		InitSemaphore		(&si->Lock);


		if (opset->ops_AttrList)
		{
			/* Set generic attributes */

			SongSetMethod (cl, si, opset);
			si->Changes = 0;


			/* Set initialization-only attributes */

			if (GetTagData (SNGA_ReadyToUse, FALSE, opset->ops_AttrList))
			{
				if (!DoMethod ((Object *)si, SNGM_ADDPATTERN, NULL))
				{
					DisposeObject (si);
					return NULL;
				}

				/* Add one position to the new song */
				SetAttrs (si, SNGA_Length, 1, TAG_DONE);

				if (!si->Sequence)
				{
					DisposeObject (si);
					return NULL;
				}
			}
		}
	}

	return (ULONG)si;
}



INLINE ULONG SongDisposeMethod (Class *cl, struct SongInfo *si, Msg msg)

/* OM_DISPOSE */
{
	LONG i;

	/* All this stuff could be removed as all allocations are made
	 * inside a memory pool.
	 */

	/* Remove song sequence */
	SetAttrs (si, SNGA_Length, 0, TAG_DONE);

	/* Free patterns */
	for (i = si->NumPatterns - 1; i >= 0  ; i--)
		DoMethod ((Object *)si, SNGM_REMPATTERN, i, 0);

	/* Free instruments */
	for (i = 1 ; i <= si->LastInstrument ; i++)
		DoMethod ((Object *)si, SNGM_REMINSTRUMENT, i);

	FreeVecPooled (si->Pool, si->Title);
	FreeVecPooled (si->Pool, si->Author);
	FreeVecPooled (si->Pool, si->Description);
	FreeVecPooled (si->Pool, si->Path);

	/* And let our superclass free the istance */
	return DoSuperMethodA (cl, (Object *)si, msg);
}



INLINE ULONG SongSetMethod (Class *cl, struct SongInfo *si, struct opSet *opset)

/* OM_SET */
{
	struct TagItem	*ti, *tstate = opset->ops_AttrList;
	BOOL	do_supermethod	= FALSE;
	ULONG	oldchanges		= si->Changes;


	while (ti = NextTagItem(&tstate))
	{
		switch (ti->ti_Tag)
		{
			case SNGA_CurrentPatt:
				si->CurrentPatt = ti->ti_Data;
				break;

			case SNGA_CurrentPos:
				si->CurrentPos = ti->ti_Data;
				break;

			case SNGA_CurrentInst:
				si->CurrentInst = ti->ti_Data;
				break;

			case SNGA_CurrentLine:
				si->CurrentLine = ti->ti_Data;
				break;

			case SNGA_CurrentTrack:
				si->CurrentTrack = ti->ti_Data;
				break;

			case SNGA_Length:
				SetSongLen (si, ti->ti_Data);
				break;

			case SNGA_Title:
				if (DupStringPooled (si->Pool, (STRPTR)ti->ti_Data, &si->Title))
				{
					if (si->Title)
						si->Link.ln_Name = si->Title;
					else
						si->Link.ln_Name = STR(MSG_UNNAMED);
					si->Changes++;
				}
				break;

			case SNGA_Author:
				if (ti->ti_Data == -1)
				{
					if (GuessAuthor (si))
						si->Changes++;
				}
				else if (DupStringPooled (si->Pool, (STRPTR)ti->ti_Data, &si->Author))
					si->Changes++;
				break;

			case SNGA_Description:
				if (DupStringPooled (si->Pool, (STRPTR)ti->ti_Data, &si->Description))
					si->Changes++;
				break;

			case SNGA_Path:
				if (DupStringPooled (si->Pool, (STRPTR)ti->ti_Data, &si->Path))
					si->Changes++;
				break;

			case SNGA_Changes:
				if (ti->ti_Data == -1)
					si->Changes++;
				else
					si->Changes = ti->ti_Data;
				break;

			case SNGA_TotalChanges:
				si->TotalChanges = ti->ti_Data;
				break;

			case SNGA_CreationDate:
				si->CreationDate = ti->ti_Data;
				break;

			case SNGA_LastChanged:
				si->LastChanged = ti->ti_Data;
				break;

			case SNGA_DefaultTracks:
				si->DefNumTracks = ti->ti_Data;
				break;

			case SNGA_DefaultPattLen:
				si->DefPattLen = ti->ti_Data;
				break;

			case SNGA_GlobalSpeed:
			{
				LONG speed = ti->ti_Data;

				if (speed < 1) speed = 1;
				if (speed > 31) speed = 31;

				if (si->GlobalSpeed != speed)
				{
					si->GlobalSpeed = speed;
					si->Changes++;
				}
				break;
			}

			case SNGA_GlobalTempo:
			{
				LONG tempo = ti->ti_Data;

				if (tempo > 255) tempo = 255;
				if (tempo < 32) tempo = 32;

				if (si->GlobalTempo != tempo)
				{
					si->GlobalTempo = tempo;
					si->Changes++;
				}
				break;
			}

			case SNGA_RestartPos:
			{
				LONG restart = ti->ti_Data;

				if (restart < 0) restart = 0;
				if (restart >= si->Length) restart = si->Length - 1;

				if (si->RestartPos != restart)
				{
					si->RestartPos = restart;
					si->Changes++;
				}
				break;
			}

			default:
				do_supermethod = TRUE;
				break;
		}
	}	/* End while (NextTagItem()) */

	if (do_supermethod)
		return (DoSuperMethodA (cl, (Object *)si, (Msg)opset));
	else
		/* Returns the number of attributes which have changed */
		return si->Changes - oldchanges;
}



static UWORD *SetSongLen (struct SongInfo *si, ULONG len)
{
	ULONG len_quantized;

	if (len == si->Length)
		return si->Sequence;

	si->Changes++;

	if (len == 0)
	{
		/* Deallocate sequence */

		FreeVecPooled (si->Pool, si->Sequence);
		si->Sequence = NULL;
		si->Length = 0;
		return NULL;
	}

	/* Check for too many song positions */

	if (len > MAXPOSITIONS)
		return NULL;


	len_quantized = (len + SEQUENCE_QUANTUM - 1) & ~(SEQUENCE_QUANTUM - 1);


	if (!si->Sequence)
	{
		/* Create a new sequence table */

		if (si->Sequence = AllocVecPooled (si->Pool,
			len_quantized * sizeof (UWORD)))
		{
			si->Length = len;
			memset (si->Sequence, 0, len * sizeof (UWORD));	/* Clear sequence table */
		}
	}
	else if (si->Length > len_quantized)
	{
		UWORD *newseq;

		/* Shrink sequence table */

		si->Length = len;

		if (newseq = AllocVecPooled (si->Pool, len_quantized * sizeof (UWORD)))
		{
			CopyMem (si->Sequence, newseq, len * sizeof (UWORD));
			FreeVecPooled (si->Pool, si->Sequence);
			si->Sequence = newseq;
		}
		/* If the previous allocation failed we ignore it and continue
		 * without shrinking the sequence table.
		 */
	}
	else if (si->Length <= len_quantized - SEQUENCE_QUANTUM)
	{
		UWORD *newseq;

		/* Expand the sequence table */

		if (!(newseq = AllocVecPooled (si->Pool, len_quantized * sizeof (UWORD))))
			return NULL;

		/* Now replace the the old sequence with the new and delete the old one */

		CopyMem (si->Sequence, newseq, si->Length * sizeof (UWORD));
		FreeVecPooled (si->Pool, si->Sequence);

		/* Clear the new sequence table entries */
		memset (newseq + si->Length, 0, (len - si->Length) * sizeof (UWORD));
		si->Length = len;
		si->Sequence = newseq;
	}
	else
	{
		/* No reallocation */

		if (si->Length > len)
			/* Clear the new sequence table entries */
			memset (si->Sequence + si->Length, 0, (si->Length - len) * sizeof (UWORD));

		si->Length = len;
	}

	if (si->CurrentPos >= si->Length)
		si->CurrentPos = si->Length - 1;

	return si->Sequence;
}



static struct Pattern *AddPattern (struct SongInfo *si, struct TagItem *tags)

/* SNGM_ADDPATTERN
 *
 * Allocates a pattern structure and adds it to the the passed song.
 * The tracks are also allocated and attached to the pattern structure.
 *
 * PATTA_Num - When this attribute is passed, the pattern will be inserted
 * before the existing pattern <patnum>.  Patterns >= <patnum> will be moved
 * ahead one slot.  The position table is updated inserting references to
 * the new pattern immediately before each occurence of patnum, so that the
 * two patterns are allways played together.
 * If patnum is -1, the pattern will be allocated but NOT inserted in the song.
 *
 * PATTA_Replace - If TRUE, the existing pattern <patnum> will be replaced by
 *	the new one.  The other pattern will be freed after the new one is
 *	allocated.
 *
 * PATTA_Pattern - specifies an already allocated pattern structure to be
 *	used instead of allocating a new one.  Adding patterns allocated
 *	by another istance of the song class is illegal.
 *
 * PATTA_Lines - Number of lines in each track.  If lines is 0, track data
 *	is not allocated.
 *
 * RETURNS
 *    Pointer to the newly allocated pattern structure, NULL for failure.
 */
{
	struct Pattern *patt;
	ULONG numpatt_quantized;
	ULONG i;
	ULONG tracks, lines, patnum, replace;

	tracks	= GetTagData (PATTA_Tracks,		si->DefNumTracks, tags);
	lines	= GetTagData (PATTA_Lines,		si->DefPattLen, tags);
	patnum	= GetTagData (PATTA_Num,		si->NumPatterns, tags);
	replace	= GetTagData (PATTA_Replace,	FALSE, tags);
	patt	= (struct Pattern *)GetTagData (PATTA_Pattern,	NULL, tags);

	if (!tracks || (si->NumPatterns == MAXPATTERNS))
		return NULL;


	/* Round up to an even number of quantums */
	numpatt_quantized = (si->NumPatterns + PATTERNS_QUANTUM - 1) & ~(PATTERNS_QUANTUM - 1);

	if (!si->Patt)
	{
		/* Create a new pattern table */

		si->Patt = AllocVecPooled (si->Pool,
			PATTERNS_QUANTUM * sizeof (struct Pattern *));
	}
	else if (si->NumPatterns + 1 >= numpatt_quantized)
	{
		struct Pattern **newpatt;

		/* Expand patterns table */

		if (newpatt = AllocVecPooled (si->Pool,
			(numpatt_quantized + PATTERNS_QUANTUM) * sizeof (struct Pattern *)))
		{
			CopyMem (si->Patt, newpatt, si->NumPatterns * sizeof (struct Pattern *));
			FreeVecPooled (si->Pool, si->Patt);
			si->Patt = newpatt;
		}
		else return NULL;
	}


	/* Pattern allocation */

	if (!patt)
	{
		if (patt = CAllocPooled (si->Pool, sizeof (struct Pattern) +
			tracks * sizeof (struct Note *)))
		{
			patt->Tracks	= tracks;
			patt->Lines		= lines;

			if (lines)
				for (i = 0 ; i < tracks ; i++)
				{
					if (!(patt->Notes[i] = (struct Note *)
						CAllocPooled (si->Pool, sizeof (struct Note) * lines)))
					{
						ULONG j;

						for (j = 0 ; j < tracks ; j++)
							FreePooled (si->Pool, patt->Notes[i], lines * sizeof (struct Note));

						FreePooled (si->Pool, patt,  sizeof (struct Pattern) +
							tracks * sizeof (struct Note *));
						return NULL;
					}
				}

			DupStringPooled (si->Pool, (STRPTR)GetTagData (PATTA_Name, NULL, tags), &patt->Name);
		}
	}

	if (patt)
	{
		if (patnum != -1)
		{
			if (replace)	/* Free old pattern */
			{
				ULONG j;

				for (j = 0 ; j < si->Patt[patnum]->Tracks ; j++)
					FreePooled (si->Pool, si->Patt[patnum]->Notes[j],
						si->Patt[patnum]->Lines * sizeof (struct Note));

				FreeVecPooled (si->Pool, si->Patt[patnum]->Name);
				FreePooled (si->Pool, si->Patt[patnum],  sizeof (struct Pattern) +
					patt->Tracks * sizeof (struct Note *));
			}

			si->Patt[patnum] = patt;

			if (!replace)
			{
				if (patnum < si->NumPatterns)
				{
					/* Shift subsequent patterns one position ahead */
					for (i = si->NumPatterns ; i > patnum ; i--)
						si->Patt[i] = si->Patt[i-1];
				}

				si->NumPatterns++;
			}

			si->Changes++;
		}

		if (tracks > si->MaxTracks)
			si->MaxTracks = tracks;
	}

	return patt;
}



static LONG SetPattern (struct SongInfo *si, ULONG num, struct TagItem *tags)

/* SNGM_SETPATTERN */
{
	struct TagItem *ti;
	struct Pattern *patt;

	if (patt = si->Patt[num])
		if (ti = FindTagItem (PATTA_Name, tags))
		{
			DupStringPooled (si->Pool, (STRPTR)ti->ti_Data, &si->Patt[num]->Name);
			si->Changes++;
			return 1;
		}

	return 0;
}



static void RemPattern (struct SongInfo *si, ULONG patnum, ULONG newpatt)

/* SNGM_REMPATTERN
 *
 * Remove a pattern from a song.  All patterns >= <patnum> will be moved
 * back one slot.  The position table is updated replacing each
 * occurence of <patnum> with pattern <newpatt>.  If <patnum> is -1,
 * newpatt will be used as a pointer to a pattern structure which has been
 * created by the song, but has not been added to the pattern table.  This
 * is useful for freeing patterns created with num = -1 in the
 * SNGM_ADDPATTERN method.
 */
{
	struct Pattern *patt;
	ULONG i;

	if (patnum == -1)
	{
		patt = (struct Pattern *)newpatt;
		newpatt = - 1;
	}
	else
		patt = si->Patt[patnum];

	if (patt)
	{
		ULONG j;

		if (patnum < si->NumPatterns)
			si->Patt[patnum] = NULL;

		for (j = 0 ; j < patt->Tracks ; j++)
			FreePooled (si->Pool, patt->Notes[j], patt->Lines * sizeof (struct Note));

		FreeVecPooled (si->Pool, patt->Name);
		FreePooled (si->Pool, patt,  sizeof (struct Pattern) +
			patt->Tracks * sizeof (struct Note *));
	}

	if (patnum < si->NumPatterns)
	{
		/* Scroll subsequent patterns */
		for (i = patnum; i < si->NumPatterns; i++)
			si->Patt[i] = si->Patt[i+1];
	}

	if (si->Sequence)
	{
		/* Adjust position table */

		for (i = 0 ; i < si->Length ; i++)
		{
			/* Substitute references to the old pattern in the position table */
			if (si->Sequence[i] == patnum) si->Sequence[i] = newpatt;

			/* Fix pattern numbers */
			if (si->Sequence[i] > patnum) si->Sequence[i]--;
		}
	}

	if (patnum != -1)
	{
		si->Changes++;
		si->NumPatterns--;

		if (si->NumPatterns)
		{
			if (si->CurrentPatt >= si->NumPatterns)
				si->CurrentPatt = si->NumPatterns - 1;
		}
		else
		{
			FreeVecPooled (si->Pool, si->Patt);
			si->Patt = NULL;
		}
	}
}



static struct Instrument *AddInstrument (struct SongInfo *si, ULONG num, struct TagItem *tags)

/* SNGM_ADDINSTRUMENT
 *
 * Allocates an instrument structure and adds it to the the passed song.
 * It will also free the old instrument if there is one already.
 * If <num> is -1, it will load the instrument in the slot right after
 * the last instrument.  If <num> is 0, it will load the instrument in
 * the first free slot.
 *
 * RETURNS
 *    Pointer to the newly allocated instrument structure, NULL for failure.
 */
{
	struct Instrument	*instr;

	if (num == -1)
		num = si->LastInstrument + 1;

	if (num == 0)
	{
		for (num = 1; num <= si->LastInstrument; num++)
			if (!si->Instr[num]) break;

		if (num == MAXINSTRUMENTS) return NULL;
	}


	if (num > MAXINSTRUMENTS)
		return NULL;

	if (si->Instr[num])
		RemInstrument (si, num);

	if (!(instr = CAllocPooled (si->Pool, sizeof (struct Instrument))))
		return NULL;

	si->Instr[num] = instr;
	if (num > si->LastInstrument)
		si->LastInstrument = num;

	SetInstrument (si, num, tags);

	return instr;
}



static void SwapInstruments (struct SongInfo *si, LONG n, LONG m)

/* SNGM_SWAPINSTRUMENTS
 *
 * Swaps two instruments and updates the LastInstrument field if required.
 *
 * RETURNS
 *    Pointer to the newly allocated instrument structure, NULL for failure.
 */
{
	struct Instrument *tmp;

	if (n == m) return;

	tmp = si->Instr[n];
	si->Instr[n] = si->Instr[m];
	si->Instr[m] = tmp;

	n = max (n,m);

	if (n >= si->LastInstrument)
	{
		for ( ; n >= 0; n--)
			if (si->Instr[n]) break;

		DB (kprintf ("n = %ld\n", n));

		si->LastInstrument = n;
	}
}



static LONG SetInstrument (struct SongInfo *si, ULONG num, struct TagItem *tags)

/* SNGM_SETINSTRUMENT */
{
	struct Instrument	*instr;
	struct TagItem		*ti;

	if ((num <= si->LastInstrument) && (instr = si->Instr[num]))
	{
		while (ti = NextTagItem (&tags))
			switch (ti->ti_Tag)
			{
				case INSTRA_Type:
					instr->Type		= ti->ti_Data;
					break;

				case INSTRA_Name:
					DupStringPooled (si->Pool, (STRPTR)ti->ti_Data, &instr->Name);
					break;

				case INSTRA_Volume:
					instr->Volume	= ti->ti_Data;
					break;

				case INSTRA_Sample:
					instr->Sample	= (BYTE *) ti->ti_Data;
					break;

				case INSTRA_Length:
					instr->Length	= ti->ti_Data;
					break;

				case INSTRA_Flags:
					instr->Flags	= ti->ti_Data;
					break;

				case INSTRA_LoopStart:
					instr->Repeat	= ti->ti_Data;
					break;

				case INSTRA_LoopLen:
					instr->Replen	= ti->ti_Data;
					break;

				case INSTRA_LoopEnd:
					instr->Replen	= ti->ti_Data - instr->Repeat - 2;
					break;

				case INSTRA_FineTune:
					instr->FineTune	= ti->ti_Data;
					/* Note: I'm falling through here! */

				default:
					break;
			}
		/* End while (NextTagItem()) */

		si->Changes++;
		return 1;
	}

	return 0;
}


static void RemInstrument (struct SongInfo *si, ULONG num)

/* SNGM_REMINSTRUMENT */
{
	if ((num <= si->LastInstrument) && (si->Instr[num]))
	{
		FreeVec			(si->Instr[num]->Sample);
		FreeVecPooled	(si->Pool, si->Instr[num]->Name);
		FreePooled		(si->Pool, si->Instr[num], sizeof (struct Instrument));
		si->Instr[num] = NULL;
		si->Changes++;
	}

	if (num == si->LastInstrument)
	{
		ULONG i;

		for (i = num - 1; i > 0 ; i--)
			if (si->Instr[i])
				break;

		si->LastInstrument = i;
	}
}



#if 0 /* --- This part has been removed --- */

static WORD ModType (BPTR fh)

/* Guess source module type */
{
	UBYTE __aligned str[0x30];
	WORD trackertype;

	if (Read (fh, str, 0x30) != 0x30)
		return -1;

	/* Check XModule */
	if (!(strncmp (str, "FORM", 4) || strncmp (str+8, "XMOD", 4)))
		return FMT_XMODULE;

	/* Check MED */
	if (!(strncmp (str, "MMD", 3)))
		return FMT_MED;

	/* Check Oktalyzer */
	if (!(strncmp (str, "OKTASONG", 8)))
		return FMT_OKTALYZER;

	if (!(strncmp (&str[0x2C], "SCRM", 4)))
		return FMT_SCREAMTRACKER;

	if (!(strncmp (str, "Extended module: ", 17)))
		return FMT_FASTTRACKER2;

	/* Check #?Tracker */
	if ((trackertype = IsTracker (fh)) != FMT_UNKNOWN)
		return trackertype;

	switch (ShowRequestArgs (MSG_UNKNOWN_MOD_FORMAT, MSG_SOUND_PRO_CANCEL, NULL))
	{
		case 1:
			return FMT_STRACKER;
			break;

		case 2:
			return FMT_PTRACKER;
			break;

		default:
			break;
	}

	return FMT_UNKNOWN;
}
#endif /* --- This part has been removed --- */



#define tolower(c) ((c) | (1<<5))

static BOOL GuessAuthor (struct SongInfo *si)

/* Tries to find the author of the song by looking up the instrument
 * names.
 */
{
	UBYTE *name;
	ULONG i, j;

	if (!si->Instr) return FALSE;

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

		name = si->Instr[i]->Name;

		/* Check for IntuiTracker-style embedded author name */
		if (name[0] == '#')
		{
			for (j = 1; name[j] == ' '; j++); /* Skip extra blanks */

			/* Skip "by " */
			if ((tolower(name[j]) == 'b') && (tolower(name[j+1]) == 'y') && name[j+2] == ' ')
				j += 3;

			for (; name[j] == ' '; j++); /* Skip extra blanks */

			if (name[j])
			{
				SetAttrs (si, SNGA_Author, &(name[j]), TAG_DONE);
				return TRUE; /* Stop looking for author */
			}
		}

		/* Now look for the occurence of "by ", "by:", "by\0" or "(c)".  Ignore case. */
		for (j = 0; name[j]; j++)
		{
			if (( (tolower(name[j]) == 'b') && (tolower(name[j+1]) == 'y') &&
				((name[j+2] == ' ') || (name[j+2] == ':') || (name[j+2] == '\0')) ) ||
				((name[j] == '(') && (tolower(name[j+1]) == 'c') && (name[j+2] == ')') ))
			{
				j+=3;	/* Skip 'by ' */

				/* Skip extra blanks/punctuations */
				while (name[j] == ' ' || name[j] == ':' || name[j] == '.') j++;

				if (name[j]) /* Check if the end is reached */
					/* The name of the author comes (hopefully) right after 'by ' */
					SetAttrs (si, SNGA_Author, &(name[j]), TAG_DONE);
				else
					/* The name of the author is stored in the next instrument */
					if (i < si->LastInstrument - 1)
					SetAttrs (si, SNGA_Author, si->Instr[i+1]->Name, TAG_DONE);

				return TRUE; /* Stop loop */
			}
		}
	}

	return FALSE;
}



GLOBALCALL ULONG CalcInstSize (struct SongInfo *si)

/* Calculate total size of all instruments in a song. */
{
	ULONG i;
	ULONG size = 0;

	for (i = 1; i < si->LastInstrument; i++)
	{
		if (!si->Instr[i]) continue;
			size += si->Instr[i]->Length + sizeof (struct Instrument);
	}

	return size;
}



GLOBALCALL ULONG CalcSongSize (struct SongInfo *si)

/* Calculate total size of a song */
{
	ULONG i;
	struct Pattern *patt;
	ULONG size = sizeof (struct SongInfo) + CalcInstSize (si);

	/* Calculate total patterns size */

	for ( i = 0; i < si->NumPatterns; i++)
	{
		if (patt = si->Patt[i])
			size += patt->Lines * patt->Tracks * sizeof (struct Note) + sizeof (struct Pattern);
	}

	return size;
}



GLOBALCALL ULONG CalcSongTime (struct SongInfo *si)

/* Calculate song length in seconds
 *
 * One note lasts speed/50 seconds at 125bpm.
 */
{
	ULONG i, j, k;
	struct Pattern *patt;
	struct Note *note;
	ULONG speed = si->GlobalSpeed,
//		tempo = si->GlobalTempo,
		ticks = speed * 20,
		millisecs = 0;


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

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

				switch (note->EffNum)
				{
					case EFF_POSJUMP:
						if (note->EffVal > i)
						{
							i = note->EffVal;
							j = patt->Lines;
							k = patt->Tracks;
						}
						else return millisecs;
						break;

					case EFF_SETSPEED:
						/* At speed 1, one line lasts one VBlank,
						 * that is 20 milliseconds.
						 */
						ticks = note->EffVal * 20;
						break;

					case EFF_PATTERNBREAK:
						j = patt->Lines;
						k = patt->Tracks;
						break;

					/* case EFF_MISC: Loop */
				}

				millisecs += ticks;
			}
	}

	return millisecs;
}

