/*
**	Library.c
**
**	Copyright (C) 1995,96,97 Bernardo Innocenti
**
**	xmodule.library functions
*/

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

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

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



/****** xmodule/--background-- *********************************************
*
*	INTRODUCTION
*		The xmodule.library is an API that provides access to the XModule
*		song management engine, as well as other general pourpose
*		services.  Hooks and external applications can use this library
*		to create, load, save and modify songs.
*
*		The xmodule.library does not exist as a stand alone shared libray.
*		Instead, it's code is contained inside the main XModule executable
*		(altrough it's logically independant from the rest of the XModule
*		code).
*
*		XModule adds the xmodule.library to the exec libray list at
*		startup time, unless it finds one already in the list (this might
*		happen when multiple copies of XModule are running at the same
*		time).
*
*	PUBLIC SONGS
*		The xmodule.library maintains a list of public songs which can be
*		used by all applications which opened the library.  Each song in
*		the list is protected from multiple task access by a
*		SignalSemaphore, and whole list is also protected by another
*		semaphore.  The locking mechanism is very simple: before you can
*		access a song you must obtain its semaphore, and you must release
*		it as soon as you are done with it.  If you are locking a song to
*		just to read some information (i.e.: you don't want to modify
*		anything), you should obtain a shared lock instead of an exclusive
*		one.  The list must be locked whenever you want to add or remove
*		a song, or when you scan it in any way.
*
*		Since public songs could be modified or even deleted by other
*		tasks, do not make your songs public unless your code is smart
*		enough to handle all possible situations.
*
*
*	HOOKS
*		Actually, most modular programs call them `modules', but it would
*		have created a lot of confusion with a program like XModule :-).
*
*		Hooks are programs that add some kind of functionality
*		to XModule.  External hook files are standard shared libraries
*		which will open the xmodule.library when they are loaded and
*		call xmAddHookA() to add themselves to a list of hooks maintained
*		by the library.
*
*		Currently, XModule supports two kinds of hooks: loaders and
*		savers.  Loader hooks can also provide a function which
*		identifies a particular module format.
*
*		An external hook libray may also contain several hooks.
*		Putting a loader and a saver for one particolar format together
*		in one libray is generally a good idea, while making a hook
*		with hundereds of loaders and savers isn't a good move because
*		it makes the whole concept of external hooks quite useless.
*		Grouping different versions or variants of one format together
*		in one external hook is acceptable.
*
*	SEE ALSO
*		songclass/--background--, exec/ObtainSemaphore()
*
****************************************************************************
*/



/* Library function prototypes */

static LIBCALL struct SongInfo * XModuleCreateSong (
	REG(a0, struct TagItem *tags),
	REG(a6, struct XModuleBase *XModuleBase));
static LIBCALL void XModuleDeleteSong (
	REG(a0, struct SongInfo *si),
	REG(a6, struct XModuleBase *XModuleBase));
static LIBCALL ULONG XModuleAddSong (
	REG(a0, struct SongInfo *si),
	REG(a1, struct SongInfo *position),
	REG(a2, struct TagItem *tags),
	REG(a6, struct XModuleBase *XModuleBase));
static LIBCALL ULONG XModuleRemSong (
	REG(a0, struct SongInfo *si),
	REG(a6, struct XModuleBase *XModuleBase));
static LIBCALL ULONG XModuleActivateSong (
	REG(a0, struct SongInfo *si),
	REG(a6, struct XModuleBase *XModuleBase));
static LIBCALL struct SongInfo *XModuleLockActiveSong (
	REG(d0, UWORD mode),
	REG(a6, struct XModuleBase *XModuleBase));
static LIBCALL struct XMHook *XModuleAddHook (
	REG(a0, struct TagItem *tags),
	REG(a6, struct XModuleBase *XModuleBase));
static LIBCALL void XModuleRemHook (
	REG(a0, struct XMHook *hook),
	REG(a6, struct XModuleBase *XModuleBase));
static LIBCALL struct XMHook *XModuleIdentifyModule(
	REG(d0, BPTR fh),
	REG(a0, struct TagItem *tags),
	REG(a6, struct XModuleBase *XModuleBase));
static LIBCALL struct SongInfo * XModuleLoadModule (
	REG(a0, CONST_STRPTR filename),
	REG(a1, struct TagItem *tags),
	REG(a6, struct XModuleBase *XModuleBase));
static LIBCALL LONG XModuleSaveModule (
	REG(a0, struct SongInfo *si),
	REG(a1, CONST_STRPTR filename),
	REG(a2, struct XMHook *saver),
	REG(a3, struct TagItem *tags),
	REG(a6, struct XModuleBase *XModuleBase));
static LIBCALL UWORD *XModuleSetSongLen(
	REG(a0, struct SongInfo *si),
	REG(d0, UWORD length),
	REG(a6, struct XModuleBase *XModuleBase));
static LIBCALL void XModuleAddPattern (
	REG(a0, struct SongInfo *si),
	REG(a1, struct TagItem *tags),
	REG(a6, struct XModuleBase *XModuleBase));
static LIBCALL void XModuleSetPattern (
	REG(a0, struct SongInfo *si),
	REG(d0, ULONG pattNum),
	REG(a1, struct TagItem *tags),
	REG(a6, struct XModuleBase *XModuleBase));
static LIBCALL void XModuleRemPattern (
	REG(a0, struct SongInfo *si),
	REG(d0, ULONG pattNum),
	REG(d1, ULONG replaceWith));
static LIBCALL void XModuleAddInstrument (
	REG(a0, struct SongInfo *si),
	REG(d0, LONG num),
	REG(a1, struct TagItem *tags),
	REG(a6, struct XModuleBase *XModuleBase));
static LIBCALL void XModuleSetInstrument (
	REG(a0, struct SongInfo *si),
	REG(d0, LONG num),
	REG(a1, struct TagItem *tags),
	REG(a6, struct XModuleBase *XModuleBase));
static LIBCALL void XModuleRemInstrument (
	REG(a0, struct SongInfo *si),
	REG(d0, ULONG num),
	REG(a6, struct XModuleBase *XModuleBase));
static LIBCALL LONG XModuleProcessSong (
	REG(a0, struct SongInfo *si),
	REG(a1,	void *reserved),
	REG(a2,	struct TagItem *tags),
	REG(a6, struct XModuleBase *XModuleBase));
static LIBCALL void XModuleDisplayMessage (
	REG(d0, ULONG level),
	REG(a0,	CONST_STRPTR msg),
	REG(a1, LONG *args),
	REG(a6, struct XModuleBase *XModuleBase));
static LIBCALL void XModuleDisplayProgress (
	REG(d0,	ULONG actual),
	REG(d1, ULONG max),
	REG(a6, struct XModuleBase *XModuleBase));



/* Local function prototypes */

static LIBCALL void XModuleLibOpen		(void);
static LIBCALL void XModuleLibClose		(void);
static LIBCALL void XModuleLibExpunge	(void);
static LIBCALL void XModuleLibExtFunc	(void);
static LIBCALL LONG XModuleRexxServer	(void);



/* Library vector table */

static APTR XModuleVectors[] =
{
	XModuleLibOpen,
	XModuleLibClose,
	XModuleLibExpunge,
	XModuleLibExtFunc,
	XModuleRexxServer,
	XModuleRexxServer,
	XModuleRexxServer,
	XModuleRexxServer,

	XModuleCreateSong,
	XModuleDeleteSong,
	XModuleAddSong,
	XModuleRemSong,
	XModuleActivateSong,
	XModuleLockActiveSong,
	XModuleAddHook,
	XModuleRemHook,
	XModuleIdentifyModule,
	XModuleLoadModule,
	XModuleSaveModule,
	XModuleSetSongLen,
	XModuleAddPattern,
	XModuleSetPattern,
	XModuleRemPattern,
	XModuleAddInstrument,
	XModuleSetInstrument,
	XModuleRemInstrument,
	XModuleProcessSong,
	XModuleDisplayMessage,
	XModuleDisplayProgress,

	(APTR)-1
};



GLOBALCALL ULONG MakeXModuleLibrary (void)
{
	if (XModuleBase = (struct XModuleBase *)MakeLibrary (XModuleVectors,
		NULL, NULL, sizeof (struct XModuleBase), NULL))
	{
		/* Clear base variables following the library node
		 * (InitSemaphore reqires a clean memory block!)
		 *
		 * GOOD NEWS: Clearing the library base is not required because
		 * MakeLibrary() allocates a MEMF_CLEAR memory block.
		 *
		 * memset (((UBYTE *)XModuleBase) + sizeof (struct Library), 0,
		 *	sizeof (struct XModuleBase) - sizeof (struct Library));
		 */

		XModuleBase->xm_Library.lib_Node.ln_Name = (STRPTR)"xmodule.library";
		XModuleBase->xm_Library.lib_Version = VERSION;
		XModuleBase->xm_Library.lib_Revision = REVISION;
		XModuleBase->xm_Library.lib_IdString = (STRPTR)Version + 6;

		XModuleBase->xm_DOSBase = DOSBase;
		XModuleBase->xm_UtilityBase = UtilityBase;
		XModuleBase->xm_IFFParseBase = IFFParseBase;
		XModuleBase->xm_IntuitionBase = IntuitionBase;

		NEWLIST ((struct List *)&XModuleBase->xm_Songs);
		NEWLIST ((struct List *)&XModuleBase->xm_Loaders);
		NEWLIST ((struct List *)&XModuleBase->xm_Savers);
		InitSemaphore (&XModuleBase->xm_BaseLock);

		/* Create global memory pool */
		if (!(XModuleBase->xm_Pool = CreatePool (MEMF_ANY, 16*1024, 4*1024)))
			return ERROR_NO_FREE_STORE;

		if (!(XModuleBase->xm_SongClass = InitSongClass()))
			return ERROR_NO_FREE_STORE;

		return RETURN_OK;
	}

	return ERROR_NO_FREE_STORE;
}



GLOBALCALL void DisposeXModuleLibrary (void)
{
	if (XModuleBase)
	{
		ObtainSemaphore (&XModuleBase->xm_BaseLock);

		/* Free all songs in SongList */
		while (!IsListEmpty ((struct List *)&XModuleBase->xm_Songs))
			xmDeleteSong ((struct SongInfo *)XModuleBase->xm_Songs.mlh_Head);

		/* Remove all loaders */
		while (!IsListEmpty ((struct List *)&XModuleBase->xm_Loaders))
			xmRemHook ((struct XMHook *)XModuleBase->xm_Loaders.mlh_Head);

		/* Remove all savers */
		while (!IsListEmpty ((struct List *)&XModuleBase->xm_Savers))
			xmRemHook ((struct XMHook *)XModuleBase->xm_Savers.mlh_Head);

		/* Free the songclass */
		FreeSongClass (XModuleBase->xm_SongClass);

		/* Dispose the global memory pool */
		if (XModuleBase->xm_Pool) DeletePool (XModuleBase->xm_Pool);

		FreeMem (((UBYTE *)XModuleBase) - XModuleBase->xm_Library.lib_NegSize,
			XModuleBase->xm_Library.lib_NegSize + XModuleBase->xm_Library.lib_PosSize);
		XModuleBase = NULL;
	}
}



static LIBCALL void XModuleLibOpen (void)
{
}



static LIBCALL void XModuleLibClose (void)
{
}



static LIBCALL void XModuleLibExpunge (void)
{
}



static LIBCALL void XModuleLibExtFunc (void)
{
}



static LIBCALL LONG XModuleRexxServer (void)
{
	return 0;
}



/****** xmodule/xmAddHook **************************************************
*
*	NAME
*		xmAddHookA -- Creates a new XModule Hook
*		xmAddHook -- Varargs stub for xmAddHookA
*
*	SYNOPSIS
*		hook = xmAddHookA(tagList)
*		D0                A0
*
*		struct XMHook *xmAddHookA(struct TagItem *);
*
*
*		hook = xmAddHook(Tag1,...)
*
*		struct XMHook *xmAddHook(ULONG,...);
*
*	FUNCTION
*		Creates a new XMHook structure and fills it in with data supplied
*		with the TagList. Adds the newly created Hook to the appropriate
*		list.
*
*	INPUTS
*		tagList - pointer to a tag list specifying how to initialize the
*			XMHook structure.
*
*	TAGS
*		XMHOOK_Type - (ULONG) Defines the pourpose of this hook. Possible
*			values are currently NT_XMLOADER and NT_XMSAVER. (This
*			tag is REQUIRED).
*
*		XMHOOK_Name - (STRPTR) ti_Data contains a short name for the
*			hook (e.g: "SoundTracker").  This name will appear in the
*			Savers list if this hook is a saver and will be passed as an
*			argument for some ARexx commands, so please use a single
*			word name.  (This tag is REQUIRED).
*
*		XMHOOK_Priority - (BYTE) Priority to give to this hook.  Hooks
*			with higher priorities will be used before lower priority
*			ones and will come first in lists shown to the user. Valid
*			range is -128..+127, but please restrict to a -20..+20
*			interval for normal cases. (Defaults to 0).
*
*		XMHOOK_Descr - (STRPTR) Verbose description of the hook
*			(without newlines). (Defaults to NULL).
*
*		XMHOOK_Author - (STRPTR) Author's name.  Please, just put
*			your full name here; no greetings, copyright notices,
*			etc. (Defaults to NULL).
*
*		XMHOOK_ID - (ULONG) This is a unique, IFF-style identifier for
*			the format.  If the format is an IFF format, it must be the
*			same of the FORM ID. (Defaults to 0, this tag is required
*			for IFF loaders and savers).
*
*		XMHOOK_Flags - (ULONG) Sets miscellaneous flags for this hook.
*			See xmodule.h for possible flags.
*
*		XMHOOK_LibraryBase - (struct Library *) Pointer to the library
*			base for external hooks.  This pointer will be used to open
*			the library when the hook is created and to close it when
*			the hook is deleted.  If you do not pass this tag, you must
*			find some other way to keep your code in memory until one
*			or more of your hooks are active.  XModule will close your
*			library just after calling the SetupXMHook() function.
*			(Defaults to NULL).
*
*		XMHOOK_UserData - (APTR) ti_Data will be stored in the
*			xmh_UserData field of the XMHook structure. This field can
*			come andy to store private data. (Defaults to NULL).
*
*		XMHOOK_RemoveHookFunc - (APTR) Pointer to a function which will be
*			called upon removing the hook.  This function can be used to
*			free any private resources allocated by the hook when it was
*			created. The template for the function is:
*
*				void RemoveHookFunc (struct XMHook *hook);
*									 A0
*
*		XMHOOK_LoadModFunc - (APTR) Pointer to the hook function which
*			loads a module. The template for the function is:
*
*				LONG LoadModFunc (BPTR fileHandle, struct SongInfo *song,
*								  D0               A0
*					struct XMHook *loader);
*					A1
*
*			`fileHandle' is an open file to load the module from. The
*			caller will take care to close it for you. The loader should
*			return RETURN_OK for success, or any other AmigaDOS error
*			code to mean failure.  In particular, RETURN_WARN indicates
*			that something went wrong, but the song is ok.  The special
*			error code ERROR_IOERR can be used when some dos.library
*			call (e.g.: Read()) failed.  In the latter case, the
*			AmigaDOS IoErr value should be set to explain the specific
*			cause of the problem.
*			(This tag is required by all NT_XMLOADER type hooks).
*
*		XMHOOK_SaveModFunc - (APTR) Pointer to the hook function which
*			saves a module. The template for the function is:
*
*				LONG SaveModFunc (BPTR fileHandle, struct SongInfo *song,
*								  D0               A0
*					struct XMHook *saver);
*					A1
*
*			fileHandle is an open file to save the module to. The caller
*			will take care to close it for you. The saver should return
*			RETURN_OK for success, or any other AmigaDOS error code to
*			mean failure.  In particular, RETURN_WARN indicates that
*			something went wrong, but the song is ok.  The special
*			error code ERROR_IOERR can be used when some dos.library
*			call (e.g.: Read()) failed.  In the latter case, the
*			AmigaDOS IoErr value should be set to explain the specific
*			cause of the problem.
*			(This tag is required by all NT_XMSAVER type hooks).
*
*
*		XMHOOK_IdentifyModFunc - (APTR) Pointer to the hook function
*			which identifies a module format. The template for the
*			function is:
*
*				struct XMHook *IdentifyModFunc (BPTR fileHandle,
*									            D0
*					struct XMHook *hook,struct TagItem *tagList);
*					A0                  A1
*
*			fileHandle is an open file to try the identification on. The
*			caller will take care to close it. NOTE: Do not make assumptions
*			on the initial file position (e.g: seek yourself to position 0
*			if you need to). This funtion should return a pointer to a valid
*			loader (usually the one passed in) if the format is recognized,
*			NULL otherwhise.  (This tag is required by all NT_XMLOADER type
*			hooks).
*
*	RESULT
*		hook - Pointer to the newly allocated XMHook structure, or
*			NULL for failure.
*
*	SEE ALSO
*		xmRemHook(), xmIdentifyModule(), xmLoadSong(), xmSaveSong()
*
****************************************************************************
*/
static LIBCALL struct XMHook *XModuleAddHook (
	REG(a0, struct TagItem *tags),
	REG(a6, struct XModuleBase *XModuleBase))
{
	struct XMHook *hook;

	if (hook = AllocPooled (XModuleBase->xm_Pool, sizeof (struct XMHook)))
	{
		hook->xmh_Link.ln_Name	= (UBYTE *)	GetTagData (XMHOOK_Name,		NULL,	tags);
		hook->xmh_Link.ln_Type	= (UBYTE)	GetTagData (XMHOOK_Type,		0,		tags);
		hook->xmh_Link.ln_Pri	= (BYTE)	GetTagData (XMHOOK_Priority,	0,		tags);
		hook->xmh_Descr			= (STRPTR)	GetTagData (XMHOOK_Descr,		0,		tags);
		hook->xmh_Author		= (STRPTR)	GetTagData (XMHOOK_Author,		0,		tags);
		hook->xmh_ID			= (ULONG)	GetTagData (XMHOOK_ID,			0,		tags);
		hook->xmh_Flags			= (ULONG)	GetTagData (XMHOOK_Flags,		0,		tags);
		hook->xmh_UserData		= (APTR)	GetTagData (XMHOOK_UserData,	NULL,	tags);
		hook->xmh_LibraryBase	= (APTR)	GetTagData (XMHOOK_LibraryBase,	NULL,	tags);
		hook->xmh_RemoveHook	= (void (* ASMCALL)()) GetTagData (XMHOOK_RemoveFunc,	NULL, tags);
		hook->xmh_LoadMod		= (LONG (* ASMCALL)()) GetTagData (XMHOOK_LoadModFunc,	NULL, tags);
		hook->xmh_SaveMod		= (LONG (* ASMCALL)()) GetTagData (XMHOOK_SaveModFunc,	NULL, tags);
		hook->xmh_IdentifyMod	= (struct XMHook * (* ASMCALL)()) GetTagData (XMHOOK_IdentifyModFunc, NULL, tags);
		hook->xmh_MaxTracks		= GetTagData (XMHOOK_MaxTracks,			MAXTRACKS,		tags);
		hook->xmh_MaxPatterns	= GetTagData (XMHOOK_MaxPatterns,		MAXPATTERNS,	tags);
		hook->xmh_MaxInstruments= GetTagData (XMHOOK_MaxInstruments,	MAXINSTRUMENTS,	tags);
		hook->xmh_MaxLength		= GetTagData (XMHOOK_MaxLength,			MAXPOSITIONS,	tags);
		hook->xmh_MaxSampleLen	= GetTagData (XMHOOK_MaxSampleLen,		((2<<31)-1),	tags);
		hook->xmh_MaxPattLen	= GetTagData (XMHOOK_MaxPattLen,		MAXPATTLINES,	tags);
		hook->xmh_Version		= XMHOOK_VERSION;


		if (hook->xmh_LibraryBase)
			if (!(OpenLibrary (hook->xmh_LibraryBase->lib_Node.ln_Name, 0)))
				return NULL;

		ObtainSemaphore (&XModuleBase->xm_BaseLock);
		Enqueue ((struct List *)((hook->xmh_Link.ln_Type == NT_XMSAVER) ?
			&XModuleBase->xm_Savers : &XModuleBase->xm_Loaders),
			(struct Node *)hook);
		ReleaseSemaphore (&XModuleBase->xm_BaseLock);
	}

	return hook;
}



/****** xmodule/xmRemHook **************************************************
*
*	NAME
*		xmRemHook -- Removes an XModule Hook
*
*	SYNOPSIS
*		xmRemHookA(xmHook)
*		           A0
*
*		void xmRemHook(struct XMHook *);
*
*	FUNCTION
*		Removes an XModule Hook, calls its RemoveHookFunc() and then
*		frees its memory.
*
*	INPUTS
*		xmHook - pointer to the hook to be removed.
*
*	SEE ALSO
*		xmAddHook()
*
****************************************************************************
*/
static LIBCALL void XModuleRemHook (
	REG(a0, struct XMHook *hook),
	REG(a6, struct XModuleBase *XModuleBase))
{
	ObtainSemaphore (&XModuleBase->xm_BaseLock);
	REMOVE ((struct Node *)hook);
	ReleaseSemaphore (&XModuleBase->xm_BaseLock);

	if (hook->xmh_RemoveHook)
		hook->xmh_RemoveHook (hook);

	if (hook->xmh_LibraryBase) CloseLibrary (hook->xmh_LibraryBase);

	FreePooled (XModuleBase->xm_Pool, hook, sizeof (struct XMHook));
}




/****** xmodule/xmCreateSong ***********************************************
*
*	NAME
*		xmCreateSongA -- Create and initialize a new SongInfo structure
*		xmCreateSong -- Varargs stub for xmCreateSongA
*
*	SYNOPSIS
*		songInfo = xmCreateSongA(tagList);
*		D0                      A0
*
*		struct SongInfo *xmCreateSongA(struct TagItem *);
*
*
*		songInfo = xmCreateSong(Tag1,...);
*
*		struct SongInfo *xmCreateSong(ULONG,...);
*
*	FUNCTION
*		Allocates and initializes a new SongInfo structure.  The song
*		can then be added to the song list via xmAddSongA(), in which
*		case, it is no longer required to free it with xmDeleteSong().
*
*	INPUTS
*		tagList - pointer to an optional tag list specifying how to
*			initialize the SongInfo structure.
*
*	TAGS
*		SNGA_DefaultTracks - Sets the default number of pattern tracks for
*			the song. Defaults to 4.
*
*		SNGA_DefaultPattLen - Sets the default number of pattern lines for
*			the song. Defaults to 64.
*
*		SNGA_ReadyToUse - Adds one pattern and one position to the song.
*			Defaults to FALSE.
*
*		XMSNG_AddToList - (struct SongInfo *) Add the song to the song list.
*			When a song is being added to the list, a shared lock is
*			obtained on it to prevent other tasks from modifying (or even
*			remove) the song before you can do your job on it. IT IS YOUR
*			DUTY TO UNLOCK THE SONG as soon as you are done modifying it.
*			Passing NULL adds the song on the head of the list, while -1
*			(~0) will add it to the end of the SongList. Passing in a
*			pointer to another song which is already in the list, will
*			add the new song _after_ the latter.
*			(Default is not adding the song).
*
*		XMSNG_Active - (BOOL) Makes this song the active one. This tag is
*			considered only if the XMSNG_AddToList tag is also passed.
*
*	RESULT
*		songInfo - pointer to the newly allocated SongInfo structure, or
*			NULL for failure.
*
*	SEE ALSO
*		xmDeleteSong(), xmAddSongA()
*
****************************************************************************
*/
static LIBCALL struct SongInfo * XModuleCreateSong (
	REG(a0, struct TagItem *tags),
	REG(a6, struct XModuleBase *XModuleBase))
{
	struct SongInfo *si;
	struct TagItem *tag;

	if (si = NewObjectA (XModuleBase->xm_SongClass, NULL, tags))
	{
		if (tag = FindTagItem (XMSNG_AddToList, tags))
		{
			ObtainSemaphore (&si->Lock);
			xmAddSongA (si, (struct SongInfo *)tag->ti_Data, tags);
		}
	}

	return si;
}



/****** xmodule/xmDeleteSong ***********************************************
*
*	NAME
*		xmDeleteSong -- Deletes a song
*
*	SYNOPSIS
*		xmDeleteSong(songInfo)
*		             A0
*
*		void xmDeleteSong(struct SongInfo *);
*
*	FUNCTION
*		Deletes a song and all the items attached to it (patterns,
*		instruments, etc.).  This call will also remove the song from
*		the song list if it had been added to it.
*
*	INPUTS
*		songInfo - pointer to the SongInfo structure to be deleted.
*			Passing a NULL pointer is harmless.
*
*	NOTE
*		In order to delete a song which has been added to the public
*		song list, you must first obtain an exclusive lock on it to
*		prevent other tasks from walking on it while you are freeing
*		it's data structures.  The semaphore does NOT need to be
*		relinquished, because the SongInfo structure won't exist any
*		more when this call returns.
*
*	SEE ALSO
*		xmCreateSong(), xmRemSong()
*
****************************************************************************
*/
static LIBCALL void XModuleDeleteSong (
	REG(a0, struct SongInfo *si),
	REG(a6, struct XModuleBase *XModuleBase))
{
	xmRemSong (si);
	DisposeObject (si);
}



/****** xmodule/xmAddSongA *************************************************
*
*	NAME
*		xmAddSongA -- Add a song to the song list
*		xmAddSong -- Varargs stub for xmAddSongA
*
*	SYNOPSIS
*		success = xmAddSongA(songInfo,position,tagList);
*		D0                   A0       A1       A2
*
*		ULONG xmAddSongA(struct SongInfo *,struct SongInfo *,
*			struct TagItem *);
*
*
*		success = xmAddSong(songInfo,position,tag1,...);
*
*		ULONG xmAddSong(struct SongInfo *,struct SongInfo *,
*			LONG,...);
*
*	FUNCTION
*		Adds a song to the public song list. Trying to add a song
*		twice is harmless.
*
*	INPUTS
*		songInfo - song to be added.  Passing a NULL pointer is safe.
*		position - Position to add the song in.  Passing NULL adds the
*			song in the head of the list, while ~0 adds it in the tail.
*			passing a pointer to a SongInfo already in the list inserts
*			the first song _after_ the other one.
*		tagList - pointer to a TagList for more arguments.
*
*	TAGS
*		XMSNG_Active - (BOOL) Makes this song the active one. The
*			default is to just add the song without activating it.
*
*	RESULT
*		success - Will be 0 for failure, in which case the song will
*			not be added to the songs list. Currently, xmAddSongA()
*			will never fail.
*
*	NOTE
*		The song list is protected from multiple tasks access by a
*		SignalSemaphore in XModuleBase. This call will wait if the
*		song list has been locked by another task. Beware of deadlock
*		conditions!
*
*	SEE ALSO
*		xmRemSong()
*
****************************************************************************
*/
static LIBCALL ULONG XModuleAddSong (
	REG(a0, struct SongInfo *si),
	REG(a1, struct SongInfo *position),
	REG(a2, struct TagItem *tags),
	REG(a6, struct XModuleBase *XModuleBase))
{
	if (!si) return FALSE;

	if (!si->Link.ln_Type)	/* Is it already in the list? */
	{
		ObtainSemaphore (&XModuleBase->xm_BaseLock);
		DetatchSongInfoList();

		if ((ULONG)position == ~0)
			ADDTAIL ((struct List *)&XModuleBase->xm_Songs, (struct Node *)si);
		else
			Insert ((struct List *)&XModuleBase->xm_Songs, (struct Node *)si, (struct Node *)position);

		si->Link.ln_Type = NT_XMSONG;		/* Mark this song */

		if (GetTagData (XMSNG_Active, FALSE, tags))
			xmActivateSong (si);

		UpdateSongInfoList();

		ReleaseSemaphore (&XModuleBase->xm_BaseLock);
	}

	return TRUE;
}



/****** xmodule/xmRemSong **************************************************
*
*	NAME
*		xmRemSong -- Remove a song from the song list
*
*	SYNOPSIS
*		success = xmRemSong(songInfo);
*		D0                  A0
*
*		ULONG xmRemSong(struct SongInfo *);
*
*	FUNCTION
*		Removes a song from the public song list. It is safe to call this
*		function even if the song has not been added to the song list. If
*		the passed SongInfo is the active one, another song will be
*		selected automatically.
*
*	INPUTS
*		songInfo - song to be removed. If NULL, this function will take
*			no action.
*
*	RESULT
*		success - Will be 0 for failure, in which case the song will
*			not be removed from the song list. Currently,
*			xmRemSong() will never fail.
*
*	NOTE
*		In order to remove a song, you must first obtain an exclusive
*		lock on it.
*
*		The song list is also protected from multiple task access by
*		a SignalSemaphore. This call will wait if the song list has
*		been locked by another task. Beware of deadlock conditions!
*
*	SEE ALSO
*		xmAddSongA()
*
****************************************************************************
*/
static LIBCALL ULONG XModuleRemSong (
	REG(a0, struct SongInfo *si),
	REG(a6, struct XModuleBase *XModuleBase))
{
	if (!si) return FALSE;

	if (!si->Link.ln_Type) return TRUE;	/* Not in the list */


	/* Brutal but effective way to obtain two locks at the same time,
	 * avoiding the risk of deadlock conditions.  We can't just use
	 * ObtainSemaphoreList() here because the two semaphores can
	 * not be linked into a list.
	 */
/*	while (1)
	{
		ObtainSemaphore (&XModuleBase->xm_BaseLock);

		if (AttemptSemaphore (&si->Lock))
			break;

		ReleaseSemaphore (&XModuleBase->xm_SongsLock);
		ObtainSemaphore (&si->Lock);

		if (AttemptSemaphore (&XModuleBase->xm_SongsLock))
			break;

		ReleaseSemaphore (&si->Lock);
	}
*/

	ObtainSemaphore (&XModuleBase->xm_BaseLock);
	DetatchSongInfoList();

	REMOVE ((struct Node *)si);
	si->Link.ln_Type = 0;	/* Mark song outside list */

	if (XModuleBase->xm_CurrentSong == si)
	{
		if (IsListEmpty ((struct List *)&XModuleBase->xm_Songs))
			XModuleBase->xm_CurrentSong = NULL;
		else
			XModuleBase->xm_CurrentSong = (struct SongInfo *)
				XModuleBase->xm_Songs.mlh_TailPred;

		UpdateSongInfoList();
		UpdateSongInfo();
	}
	else UpdateSongInfoList();

	ReleaseSemaphore (&XModuleBase->xm_BaseLock);
	return TRUE;
}



/****** xmodule/xmActivateSong *********************************************
*
*	NAME
*		xmActivateSong -- Makes a song the active one
*
*	SYNOPSIS
*		success = xmActivateSong(songInfo);
*		D0                       A0
*
*		ULONG xmActivateSong(struct SongInfo *);
*
*	FUNCTION
*		Makes the passed song the currently active one.  It's pointer
*		will be stored in the public song list and most actions
*		will happen on this song by default.
*
*	INPUTS
*		songInfo - song to be activated. If NULL, this function will
*			take no action.
*
*	RESULT
*		success - Will be 0 for failure, in which case the song will
*			not be removed from the song list. Currently,
*			xmActivateSong() will never fail.
*
*	NOTE
*		In order to activate a song, you must own a shared lock
*		on it.  Please do not hog this lock for a long time when
*		xmActivateSong() returns, because most internal routines
*		try to lock the current song before taking any action.
*
*	SEE ALSO
*
****************************************************************************
*/
static LIBCALL ULONG XModuleActivateSong (
	REG(a0, struct SongInfo *si),
	REG(a6, struct XModuleBase *XModuleBase))
{
	if (!si) return FALSE;
	if (!si->Link.ln_Type) return FALSE;	/* Not in the list */

	ObtainSemaphore (&XModuleBase->xm_BaseLock);

	if (XModuleBase->xm_CurrentSong != si)
	{
		XModuleBase->xm_CurrentSong = si;
		UpdateSongInfo();	/**/
	}

	ReleaseSemaphore (&XModuleBase->xm_BaseLock);
	return TRUE;
}



/****** xmodule/xmLockActiveSong *******************************************
*
*	NAME
*		xmLockActiveSong -- Obtains an lock on the active song
*
*	SYNOPSIS
*		song = xmLockActiveSong(mode);
*		D0						D0:16
*
*		struct SongInfo *xmActivateSong(UWORD);
*
*	FUNCTION
*		Obtains an exclusive or shared lock on the active song,
*		waiting if needed.  This call is a shortcut to:
*
*			ObtainSemaphoreShared (&XModuleBase->xm_BaseLock);
*			ObtainSemaphore[Shared] (&XModuleBase->xm_CurrentSong.Lock);
*			ReleaseSemaphore (&XModuleBase->xm_BaseLock);
*
*		To unlock a song obtained in this way, you just need to
*		ReleaseSemaphore() it.
*
*		You MUST always lock a song before you even think to
*		read from -or write to- its data!
*
*	INPUTS
*		mode - one of SM_SHARED or SM_EXCLUSIVE.
*
*	RESULT
*		song - Pointer to the song which *was* active at the time
*			you called xmLockActiveSong().  The song will be
*			locked for you.  The result will be NULL if no song
*			is active.
*
*	NOTE
*		Please be careful if you call this function while holding
*		locks on other songs.  Doing so, you run the risk of causing
*		deadlock condition.
*		This function does only lock the song; it is NOT guaranteed
*		that it will remain the active one.
*
*	SEE ALSO
*
****************************************************************************
*/
static LIBCALL struct SongInfo *XModuleLockActiveSong (
	REG(d0, UWORD mode),
	REG(a6, struct XModuleBase *XModuleBase))
{
	struct SongInfo *si;


#ifdef OS30_ONLY
	ObtainSemaphoreShared (&XModuleBase->xm_BaseLock);
#else
	/* Workaround for Pre-V39 ObtainSemaphoreShared() bug (see autodoc) */

	/* Try to get the shared semaphore */
	if (!AttemptSemaphoreShared (&XModuleBase->xm_BaseLock))
		/* Check if we can get the exclusive version */
		if (!AttemptSemaphore (&XModuleBase->xm_BaseLock))
			/* Oh well, wait for the shared lock */
			ObtainSemaphoreShared (&XModuleBase->xm_BaseLock);
#endif /* OS30_ONLY */

	if (si = XModuleBase->xm_CurrentSong)
	{
		if (mode == SM_SHARED)
		{
#ifdef OS30_ONLY
			ObtainSemaphoreShared (&si->Lock);
#else
			/* Try to get the shared semaphore */
			if (!AttemptSemaphoreShared (&si->Lock))
				/* Check if we can get the exclusive version */
				if (!AttemptSemaphore (&si->Lock))
					/* Oh well, wait for the shared lock */
					ObtainSemaphoreShared (&si->Lock);
#endif /* OS30_ONLY */
		}
		else ObtainSemaphore (&si->Lock);
	}

	ReleaseSemaphore (&XModuleBase->xm_BaseLock);

	return si;
}



/****** xmodule/xmLoadModuleA **********************************************
*
*	NAME
*		xmLoadModuleA -- Loads a module and converts it in XModule format
*		xmLoadModule -- Varargs stub for xmLoadModuleA
*
*	SYNOPSIS
*		songInfo = xmLoadModuleA(fileName,tagList)
*		D0                       A0       A1
*
*		struct SongInfo *xmLoadModuleA(CONST_STRPTR,struct TagItem *);
*
*
*		songInfo = xmLoadModule(fileName,tag,...)
*
*		struct SongInfo *xmLoadModule(CONST_STRPTR,LONG,...);
*
*	FUNCTION
*		Loads fileName using the correct module loader.
*
*	INPUTS
*		fileName - File to read. Can be NULL if the XMSNG_FileHandle
*			tag is passed.
*		tagList - Additional parameters (see below).  Can be NULL.
*
*	TAGS
*		XMSNG_OldSong - ti_Data is the pointer to a SongInfo which will be
*			freed as soon as the module format has been determined.  This
*			is useful when the user wants to replace a song with another
*			one.  Passing NULL uses the currently active song.
*
*		XMSNG_AddToList - (struct SongInfo *) Add the song to the song list.
*			When a song is being added to the list, a shared lock is
*			obtained on it to prevent other tasks from modifying (or even
*			remove) the song before you can do your job on it. IT IS YOUR
*			DUTY TO UNLOCK THE SONG as soon as you are done modifying it.
*			Passing NULL adds the song on the head of the list, while -1
*			(~0) will add it to the end of the SongList. Passing in a
*			pointer to another song which is already in the list, will
*			add the new song _after_ the latter.
*			(Default is not adding the song).
*
*		XMSNG_Loader - (struct XMHook *) Disables automatic format
*			checking and forces loading the module with the given
*			loader. (Defaults to NULL).
*
*		XMSNG_FileHandle - (BPTR) pointer to an open AmigaDOS
*			FileHandle to read the module from.  The file must
*			already be positioned over the beginning of the module data.
*			NOTE: Even if this tag is passed, the fileName parameter is
*				still used to fill in the SongName field in case it is
*				missing inside the module AND the filesystem does not
*				support the ACTION_EXAMINE_FH packet.
*			NOTE: Some loaders may require a fully seekable file, so be
*				careful when using pipes.
*			NOTE: automatic data decompression is not possible when
*				XMSNG_FileHandle is passed.
*			(Defaults to NULL).
*
*		XMSNG_IFFHandle - (BPTR) pointer to an already initialized
*			IFFHandle to read the module from.  The IFF must
*			already be positioned over the beginning of the module FORM.
*			Even if this tag is passed, the fileName parameter is still
*			used to fill in the SongName field in case it is missing
*			inside the module.
*			NOTE: The iffparse.library can deal with non-seekable files,
*				but some loaders may require a fully seekable file, so be
*				careful when using pipes.
*			NOTE: automatic file decompression is not possible when
*				XMSNG_IFFHandle is passed.
*			(Defaults to NULL).
*
*		XMSNG_Active - (BOOL) Makes this song the active one. This tag is
*			considered only if the XMSNG_AddToList tag is also passed.
*
*	RESULT
*		songInfo - Pointer to the newly allocated SongInfo structure, or
*			NULL for failure.  This function will not fail if the module is
*			partially corrupted. Anyway, the returned SongInfo will always
*			be valid. You can check IoErr() to know if there were problems.
*
*	EXAMPLE
*		\* Load a song and add it to the SongList *\
*		si = xmLoadSongTags (file, NULL,
*			XMSNG_AddToList, (struct SongInfo *)~0,
*			TAG_DONE);
*
*		\* Check for errors even if si is not NULL *\
*		error = IoErr();
*
*		\* Release Semaphore got by xmLoadSong() *\
*		if (si) ReleaseSemaphore (&si->Lock);
*
*	SEE ALSO
*		xmAddSongA(), xmIdentifyModule()
*
****************************************************************************
*/
static LIBCALL struct SongInfo * XModuleLoadModule (
	REG(a0, CONST_STRPTR filename),
	REG(a1, struct TagItem *tags),
	REG(a6, struct XModuleBase *XModuleBase))
{
	struct XMHook	*loader;
	struct SongInfo *si = NULL;
	UBYTE			 fullpath[PATHNAME_MAX];
	ULONG			 err;
	BPTR			 fh, compressed = 0;
	UWORD			 type;


	loader = (struct XMHook *)GetTagData (XMSNG_Loader, NULL, tags);


	/* Get source file or open it */

	if (fh = (BPTR) GetTagData (XMSNG_FileHandle, NULL, tags))
	{
		/* Get the full pathname */

		if (!NameFromFH (fh, fullpath, PATHNAME_MAX))
			fullpath[0] = '\0';
	}
	else
	{
		if (!(fh = Open (filename, MODE_OLDFILE)))
		{
			UBYTE buf[FAULT_MAX];

			xmDisplayMessage (XMDMF_USECATALOG | XMDMF_DOSFAULT,
				(APTR)MSG_ERR_LOAD, filename, buf);
			return NULL;
		}

		/* Optimize file buffering for faster I/O.
		 *
		 * NOTE: SetVBuf() was introduced in V39, but V37 has a
		 * no-op vector for it, so it is safe to call SetVBuf()
		 * without checking the OS version.
		 * NOTE: Due to a bug in dos.library V40, SetVBuf() works
		 * only when called right after Open() (before doing any I/O).
		 */
		SetVBuf (fh, NULL, BUF_FULL, 16*1024);


		/* Get the full pathname */

		if (!NameFromFH (fh, fullpath, PATHNAME_MAX))
			fullpath[0] = '\0';


		/* Check wether the file is compressed */

		if (type = CruncherType (fh))
		{
			Close (fh);

			if (compressed = DecompressFile (filename, type))
			{
				if (!(fh = OpenFromLock (compressed)))
				{
					err = IoErr();
					UnLock (compressed);
					DecompressFileDone();
					SetIoErr (LastErr = err);
					return NULL;
				}
			}
			else return NULL;
		}
	}



	/* Lock loaders list */

#ifdef OS30_ONLY
	ObtainSemaphoreShared (&XModuleBase->xm_BaseLock);
#else
	/* Workaround for Pre-V39 ObtainSemaphoreShared() bug (see autodoc) */

	/* Try to get the shared semaphore */
	if (!AttemptSemaphoreShared (&XModuleBase->xm_BaseLock))
		/* Check if we can get the exclusive version */
		if (!AttemptSemaphore (&XModuleBase->xm_BaseLock))
			/* Oh well, wait for the shared lock */
			ObtainSemaphoreShared (&XModuleBase->xm_BaseLock);
#endif /* OS30_ONLY */


	/* Find out what the heck this file format is */

	if (!loader)
		loader = xmIdentifyModule (fh, tags);

	if (loader)
	{
		if (loader->xmh_LoadMod)
		{
			Seek (fh, 0, OFFSET_BEGINNING);		/* Reset file */


			/* Free old song */
			{
				struct TagItem *tag;

				if (tag = FindTagItem (XMSNG_OldSong, tags))
					xmDeleteSong (tag->ti_Data ?
						(struct SongInfo *)tag->ti_Data : XModuleBase->xm_CurrentSong);
			}


			/* Create a new song and set its title and path.
			 * File name will be replaced by the real song name if the
			 * load format supports embedded song names (e.g.: SoundTracker).
			 */
			if (si = xmCreateSong (
				SNGA_Path,	fullpath,
				SNGA_Title,	FilePart (filename),
				TAG_DONE))
			{
				OpenProgressWindow();

				xmDisplayMessage (XMDMF_USECATALOG | XMDMF_INFORMATION,
					(APTR)MSG_READING_TYPE_MODULE, loader->xmh_Link.ln_Name);


				/* Call loader hook */
				err = loader->xmh_LoadMod (fh, si, loader, tags);

				if (err == ERROR_IOERR)
				{
					if (err = IoErr())
						xmDisplayMessage (XMDMF_USECATALOG | XMDMF_DOSFAULT | err,
							(APTR)MSG_ERROR_READING, FilePart (filename));
					else
						xmDisplayMessage (XMDMF_USECATALOG | XMDMF_ERROR,
							(APTR)MSG_UNESPECTED_EOF);
				}

				FixSong (si);

				CloseProgressWindow();
			}
			else err = ERROR_NO_FREE_STORE;
		}
		else err = ERROR_ACTION_NOT_KNOWN;
	}
	else err = ERROR_OBJECT_WRONG_TYPE;

	ReleaseSemaphore (&XModuleBase->xm_BaseLock);

	Close (fh);

	if (compressed) DecompressFileDone ();

	if (!err)
		xmDisplayMessageA (XMDMF_INFORMATION | XMDMF_USECATALOG,
			(APTR)MSG_MODULE_LOADED_OK, NULL);
	else
		SetIoErr (LastErr = err);

	if (si)
	{
		struct TagItem *tag;

		if (tag = FindTagItem (XMSNG_AddToList, tags))
		{
			ObtainSemaphore (&si->Lock);
			xmAddSongA (si, (struct SongInfo *)tag->ti_Data, NULL);
			if (GetTagData (XMSNG_Active, FALSE, tags))
				xmActivateSong (si);
		}
	}

	return si;
}



/****** xmodule/xmSaveModuleA **********************************************
*
*	NAME
*		xmSaveModuleA -- Saves a module to the specified file format
*		xmSaveModule -- Varargs stub for xmSaveModuleA
*
*	SYNOPSIS
*		error = xmSaveModuleA(songInfo, fileName, saver, tagList)
*		D0                    A0        A1        A2     A3
*
*		LONG xmSaveModuleA(struct SongInfo *,CONST_STRPTR,struct XMHook *,
*			struct TagItem *);
*
*
*		error = xmSaveModule(songInfo, fileName, saver, tag1,...)
*
*		LONG xmSaveModule(struct SongInfo *,CONST_STRPTR,struct XMHook *,
*			LONG,...);
*
*	FUNCTION
*		Saves songInfo to fileName using the specified saver.
*
*	INPUTS
*		songInfo - Song to save.
*		fileName - AmigaDOS filename to write the song to.
*		saver - Pointer to the saver to use.  Pass NULL to use
*			the default saver.
*
*	TAGS
*		No tags are defined for this call.
*
*	RESULT
*		error - RETURN_OK for success, or any other AmigaDOS error code
*			to mean failure.  In particular, RETURN_WARN indicates that
*			something went wrong, but the song is still ok. The special
*			error code ERROR_IOERR means that some dos.library
*			call (e.g.: Read()) failed.  In the latter case, the
*			AmigaDOS IoErr() value will be set to explain the specific
*			cause of the problem.
*
*	SEE ALSO
*
****************************************************************************
*/
static LIBCALL LONG XModuleSaveModule (
	REG(a0, struct SongInfo *si),
	REG(a1, CONST_STRPTR filename),
	REG(a2, struct XMHook *saver),
	REG(a3, struct TagItem *tags),
	REG(a6, struct XModuleBase *XModuleBase))
{
	LONG err;
	BPTR fh;
	BOOL releasebase = FALSE;


	/* Use the default saver if not specified */
	if (!saver)
	{
#ifdef OS30_ONLY
		ObtainSemaphoreShared (&XModuleBase->xm_BaseLock);
#else
		/* Workaround for Pre-V39 ObtainSemaphoreShared() bug (see autodoc) */

		/* Try to get the shared semaphore */
		if (!AttemptSemaphoreShared (&XModuleBase->xm_BaseLock))
			/* Check if we can get the exclusive version */
			if (!AttemptSemaphore (&XModuleBase->xm_BaseLock))
				/* Oh well, wait for the shared lock */
				ObtainSemaphoreShared (&XModuleBase->xm_BaseLock);
#endif /* OS30_ONLY */

		if (!(saver = XModuleBase->xm_DefaultSaver))
		{
			ReleaseSemaphore (&XModuleBase->xm_BaseLock);
			return ERROR_ACTION_NOT_KNOWN;
		}

		releasebase = TRUE;
	}


	if (saver->xmh_SaveMod)
	{
		ULONG i;

		/* Make some checks before actually saving */


		if (si->LastInstrument > saver->xmh_MaxInstruments)
		{
			xmDisplayMessage (XMDMF_WARNING | XMDMF_USECATALOG,
				(APTR)MSG_SONG_TOO_MANY_INSTRS, si->LastInstrument, saver->xmh_MaxInstruments);

			if (ShowRequest (MSG_TRY_REMAPPING_INSTRUMENTS, MSG_PROCEED_OR_CANCEL))
				xmProcessSong (si, NULL,
					XMSNG_RemapInstruments, TRUE,
					TAG_DONE);
		}

		for (i = 1; i <= si->LastInstrument; i++)
			if (si->Instr[i] && (si->Instr[i]->Length > saver->xmh_MaxSampleLen))
			{
				xmDisplayMessage (XMDMF_WARNING | XMDMF_USECATALOG,
					(APTR)MSG_INSTR_TOO_LONG, i, saver->xmh_MaxSampleLen);
			}

		for (i = 0; i < si->NumPatterns; i++)
		{
			if (si->Patt[i])
				if ((si->Patt[i]->Lines > saver->xmh_MaxPattLen) ||
					( (saver->xmh_Flags & XMHF_FIXED_PATT_LEN) &&
					(si->Patt[i]->Lines != saver->xmh_MaxPattLen) ) )
			{
				xmDisplayMessage (XMDMF_WARNING | XMDMF_USECATALOG,
					(APTR)MSG_PATT_LENGTH_INVALID, i, si->Patt[i]->Lines);

				if (ShowRequest (MSG_WILL_MODIFY_SONG, MSG_PROCEED_OR_CANCEL))
				{
					xmProcessSong (si, NULL,
						XMSNG_LimitPatterns, saver->xmh_MaxPattLen |
						((saver->xmh_Flags & XMHF_FIXED_PATT_LEN) ? (saver->xmh_MaxPattLen << 16) : 0),
						TAG_DONE);
					break;
				}
				else
				{
					if (releasebase)
						ReleaseSemaphore (&XModuleBase->xm_BaseLock);

					return ERROR_BREAK;
				}
			}
		}

		if (si->MaxTracks > saver->xmh_MaxTracks)
			xmDisplayMessage (XMDMF_WARNING | XMDMF_USECATALOG,
				(APTR)MSG_SONG_TOO_MANY_TRACKS, saver->xmh_MaxTracks, si->MaxTracks);

		if (si->NumPatterns > saver->xmh_MaxPatterns)
			xmDisplayMessage (XMDMF_WARNING | XMDMF_USECATALOG,
				(APTR)MSG_SONG_TOO_MANY_PATTS, saver->xmh_MaxPatterns, si->NumPatterns);

		if (si->Length > saver->xmh_MaxLength)
			xmDisplayMessage (XMDMF_WARNING | XMDMF_USECATALOG,
				(APTR)MSG_SONG_TOO_MANY_POS, saver->xmh_MaxLength, si->Length);


		/* Now make a backup of the old file before overwriting this */

		if (GuiSwitches.DoBackups)
			BackupFile (filename, GuiSwitches.BackupTemplate, GuiSwitches.BackupVersions);


		if (fh = Open (filename, MODE_NEWFILE))
		{
			OpenProgressWindow();

			/* Optimize file buffering for faster I/O.
			 *
			 * NOTE: SetVBuf() was introduced in V39, but V37 has a
			 * no-op vector for it, so it is safe to call SetVBuf()
			 * without checking the OS version.
			 * NOTE: Due to a bug in dos.library V40, SetVBuf() works
			 * only when called right after Open() (before doing any I/O).
			 */
			SetVBuf (fh, NULL, BUF_FULL, 16*1024);


			xmDisplayMessage (XMDMF_INFORMATION | XMDMF_USECATALOG,
				(APTR)MSG_SAVING_MODULE, saver->xmh_Link.ln_Name,
				si->Title ? si->Title : STR(MSG_SONG_UNTITLED));


			err = saver->xmh_SaveMod (fh, si, saver, tags);

			Close (fh);
			SetComment (filename, VERS " by Bernardo Innocenti");

			if (err)
			{
				/* Delete incomplete file */
				DeleteFile (filename);

				if (err == ERROR_IOERR)
					xmDisplayMessage (XMDMF_USECATALOG | XMDMF_DOSFAULT,
						(APTR)MSG_ERROR_WRITING, filename);
			}
			else
			{
				if (SaveSwitches.SaveIcons)
					PutIcon ("def_Module", filename);
				xmDisplayMessageA (XMDMF_INFORMATION | XMDMF_USECATALOG,
					(APTR)MSG_MODULE_SAVED_OK, NULL);

			}

			CloseProgressWindow();
		}
		else	/* Open failed */
		{
			err = IoErr();
			xmDisplayMessage (XMDMF_USECATALOG | XMDMF_DOSFAULT,
				(APTR)MSG_CANT_OPEN, filename);
		}
	}
	else err = ERROR_ACTION_NOT_KNOWN;

	if (releasebase)
		ReleaseSemaphore (&XModuleBase->xm_BaseLock);

	return err;
}



/****** xmodule/xmIdentifyModule *******************************************
*
*	NAME
*		xmIdentifyModule -- Returns the type of a module
*
*	SYNOPSIS
*		loader = xmIdentifyModule(fh, tagList)
*		D0                        D0  A0
*
*		struct XMHook *xmIdentifyModule(BPTR,struct TagItem *);
*
*	FUNCTION
*		Finds out a loader which is able to read the given module.
*
*	INPUTS
*		fh - AmigaDOS FileHandle to examine.
*		tagList - Additional parameters. Leave it NULL for now.
*
*	RESULT
*		loader - Pointer to the first loader which knows how to load this
*			module, or NULL otherwise.
*
*	NOTE
*		Before you call this function, you must first obtain a lock on the
*		loaders list to protect yourself from other tasks which might remove
*		the returned loader before you can actually use it.
*
*	SEE ALSO
*
****************************************************************************
*/
static LIBCALL struct XMHook *XModuleIdentifyModule(
	REG(d0, BPTR fh),
	REG(a0, struct TagItem *tags),
	REG(a6, struct XModuleBase *XModuleBase))
{
	struct XMHook *loader, *ret = NULL;

	loader = (struct XMHook *) XModuleBase->xm_Loaders.mlh_Head;

	while (loader->xmh_Link.ln_Succ)
	{
		if (ret = loader->xmh_IdentifyMod (fh, loader, tags))
			break;

		loader = (struct XMHook *)loader->xmh_Link.ln_Succ;
	}

	Seek (fh, 0, OFFSET_BEGINNING);

	return ret;
}



/****** xmodule/xmSetSongLen ***********************************************
*
*	NAME
*		xmSetSongLen -- Set the number of song positions
*
*	SYNOPSIS
*		sequence = xmSetSongLen(songInfo, length)
*		D0                      A0        D0:16
*
*		UWORD *xmSetSongLen(struct SongInfo *, UWORD);
*
*	FUNCTION
*		Allocates space in the song for a sequence table of the given
*		length. If no sequence exists yet, a new one is allocated.
*		Increasing the song length may require the sequence table to be
*		expanded, in which case it will be re-allocated and the old sequence
*		entries will be copied. Decreasing the song length could also cause
*		a reallocation of the sequence table if the size drops down enough.
*		Setting the song length to 0 will free the sequence table and return
*		NULL.
*
*	INPUTS
*		songInfo - pointer to a SongInfo structure.
*		length - new length of song sequence table.
*
*	NOTE
*		This function will also adjust the CurrentPos field if it is found
*		beyond the song end
*
*	BUGS
*		This funtion is quite useless because all it does is setting the
*		SNGA_Length attribute in the song object.  Setting the SNGA_Length
*		attribute yourself is easier and faster if you are already setting
*		other attributes in the same song.
*
*   RESULT
*		Pointer to the newly allocated sequence table or NULL for failure,
*		in which case the previous sequence table is left untouched.
*
****************************************************************************
*/
static LIBCALL UWORD *XModuleSetSongLen (
	REG(a0, struct SongInfo *si),
	REG(d0, UWORD len),
	REG(a6, struct XModuleBase *XModuleBase))
{
	SetAttrs (si, SNGA_Length, len, TAG_DONE);
	return si->Sequence;
}



/****** xmodule/xmAddPatternA **********************************************
*
*	NAME
*		xmAddPatternA -- Adds a pattern to a song
*		xmAddPattern -- Varargs stub for xmAddPatternA
*
*	SYNOPSIS
*		pattern = xmAddPatternA(si, tagList)
*		D0                      A0  A1
*
*		struct Pattern *xmAddPatternA(struct SongInfo *,struct TagItem *);
*
*
*		pattern = xmAddPattern (si,tag1,...)
*
*		struct Pattern *xmAddPattern(struct SongInfo *,LONG Tag1,...);
*
*	FUNCTION
*		Adds a pattern to a song by calling the SNGM_ADDPATTERN method.
*
*	INPUTS
*		si - pointer to the song to which the pattern should be added.
*		tagList - optional TagList.  See SNGM_ADDPATTERN for possible tags.
*
*	RESULT
*		Pointer to the new pattern or NULL for failure.
*
*	NOTE
*		In order to add patterns, you should have exclusive access to
*		the song.  Always obtain a lock before you call this function on
*		public songs.
*
*	SEE ALSO
*		xmRemPattern(), songclass/SNGM_ADDPATTERN
*
****************************************************************************
*/
static LIBCALL void XModuleAddPattern (
	REG(a0, struct SongInfo *si),
	REG(a1, struct TagItem *tags),
	REG(a6, struct XModuleBase *XModuleBase))
{
	DoMethod ((Object *)si, SNGM_ADDPATTERN, tags);
}



/****** xmodule/xmSetPatternA **********************************************
*
*	NAME
*		xmSetPatternA -- Sets pattern attributes
*		xmSetPattern -- Varargs stub for xmSetPatternA
*
*	SYNOPSIS
*		success = xmSetPatternA(si, pattNum, tagList)
*		D0                      A0  D0       A1
*
*		ULONG xmSetPatternA(struct SongInfo *,ULONG,struct TagItem *);
*
*
*		success = xmSetPattern (si,pattNum,tag1,...)
*
*		ULONG xmSetPattern(struct SongInfo *,ULONG,LONG Tag1,...);
*
*	FUNCTION
*		Sets attributes of a pattern by calling the SNGM_SETPATTERN method.
*
*	INPUTS
*		si - pointer to the song which contains the pattern to be set.
*		tagList - list of attributes to set.  See SNGM_SETPATTERN for
*			possible tags.
*
*	RESULT
*		Non zero for success
*
*	NOTE
*		In order to set patterns attributes, you should have exclusive
*		access to the song.  Always obtain a lock before you call this
*		function on public songs.
*
*	SEE ALSO
*		xmAddPattern(), songclass/SNGM_SETPATTERN
*
****************************************************************************
*/
static LIBCALL void XModuleSetPattern (
	REG(a0, struct SongInfo *si),
	REG(d0, ULONG pattNum),
	REG(a1, struct TagItem *tags),
	REG(a6, struct XModuleBase *XModuleBase))
{
	DoMethod ((Object *)si, SNGM_SETPATTERN, pattNum, tags);
}



/****** xmodule/xmRemPattern ***********************************************
*
*	NAME
*		xmRemPattern -- Removes a pattern from a song
*
*	SYNOPSIS
*		xmRemPattern(si, pattNum, replaceWith)
*		             A0  D0,      D1
*
*		void xmRemPattern(struct SongInfo *,LONG,LONG);
*
*	FUNCTION
*		Removes a pattern from a song by calling the SNGM_REMPATTERN method.
*
*	INPUTS
*		si - pointer to the song to which the pattern should be removed.
*		mum - Number of the pattern to be removed.
*		replaceWith - What to put in the song sequence in place of the
*			deleted pattern.
*
*	NOTE
*		In order to remove patterns, you should have exclusive access to
*		the song.  Always obtain a lock before you call this function on
*		public songs.
*
*	SEE ALSO
*		xmAddPatternA(), songclass/SNGM_REMPATTERN
*
****************************************************************************
*/
static LIBCALL void XModuleRemPattern (
	REG(a0, struct SongInfo *si),
	REG(d0, ULONG pattNum),
	REG(d1, ULONG replaceWith))
{
	DoMethod ((Object *)si, SNGM_REMPATTERN, pattNum, replaceWith);
}



/****** xmodule/xmAddInstrumentA *******************************************
*
*	NAME
*		xmAddInstrumentA -- Adds a instrument to a song
*		xmAddInstrument -- Varargs stub for xmAddInstrumentA
*
*	SYNOPSIS
*		instrument = xmAddInstrumentA(si, instrNum, tagList)
*		D0                            A0  D0        A1
*
*		struct Instrument *xmAddInstrumentA(struct SongInfo *,LONG,
*			struct TagItem *);
*
*
*		instrument = xmAddInstrument(si,instrNum,tag1,...)
*
*		struct Instrument *xmAddInstrument(struct SongInfo *,LONG,LONG,...);
*
*	FUNCTION
*		Adds an instrument to a song by calling the SNGM_ADDINSTRUMENT method.
*
*	INPUTS
*		si - pointer to the song to which the instrument should be added.
*		tagList - optional TagList.  See SNGM_ADDINSTRUMENT for possible tags.
*
*	RESULT
*		Pointer to the new instrument or NULL for failure.
*
*	NOTE
*		In order to add instruments, you should have exclusive access to
*		the song.  Always obtain a lock before you call this function on
*		public songs.
*
*	SEE ALSO
*		xmRemInstrument(), songclass/SNGM_REMINSTRUMENT
*
****************************************************************************
*/
static LIBCALL void XModuleAddInstrument (
	REG(a0, struct SongInfo *si),
	REG(d0, LONG num),
	REG(a1, struct TagItem *tags),
	REG(a6, struct XModuleBase *XModuleBase))
{
	DoMethod ((Object *)si, SNGM_ADDINSTRUMENT, num, tags);
}



/****** xmodule/xmSetInstrumentA *******************************************
*
*	NAME
*		xmSetInstrumentA -- Sets an instrument's attributes
*		xmSetInstrument -- Varargs stub for xmSetInstrumentA
*
*	SYNOPSIS
*		success = xmSetInstrumentA(si, instrNum, tagList)
*		D0                         A0  D0        A1
*
*		ULONG xmSetInstrumentA(struct SongInfo *,LONG,
*			struct TagItem *);
*
*
*		success = xmSetInstrument(si,instrNum,tag1,...)
*
*		ULONG xmSetInstrument(struct SongInfo *,LONG,LONG,...);
*
*	FUNCTION
*		Sets an instrument's attributes by calling the SNGM_SETINSTRUMENT
*		method.
*
*	INPUTS
*		si - pointer to the song which contains the instrument to be set.
*		tagList - instrument attributes to set.  See SNGM_SETINSTRUMENT
*			for possible tags.
*
*	RESULT
*		non zero for success.
*
*	NOTE
*		In order to set instruments' attributes, you should have
*		exclusive access to the song.  Always obtain a lock before
*		you call this function on public songs.
*
*	SEE ALSO
*		xmAddInstrument(), songclass/SNGM_SETINSTRUMENT
*
****************************************************************************
*/
static LIBCALL void XModuleSetInstrument (
	REG(a0, struct SongInfo *si),
	REG(d0, LONG num),
	REG(a1, struct TagItem *tags),
	REG(a6, struct XModuleBase *XModuleBase))
{
	DoMethod ((Object *)si, SNGM_SETINSTRUMENT, num, tags);
}



/****** xmodule/xmRemInstrument ***********************************************
*
*	NAME
*		xmRemInstrument -- Removes an instrument from a song
*
*	SYNOPSIS
*		xmRemInstrument(si, instrNum)
*		                A0  D0
*
*		void xmRemInstrument(struct SongInfo *,LONG);
*
*	FUNCTION
*		Removes an instrument from a song by calling the SNGM_REMINSTRUMENT
*		method.
*
*	INPUTS
*		si - pointer to the song to which the instrument should be removed.
*		mum - Number of the instrument to be removed.
*
*	NOTE
*		In order to remove instruments, you should have exclusive access to
*		the song.  Always obtain a lock before you call this function on
*		public songs.
*
*	SEE ALSO
*		xmAddInstrumentA(), songclass/SNGM_REMINSTRUMENT
*
****************************************************************************
*/
static LIBCALL void XModuleRemInstrument (
	REG(a0, struct SongInfo *si),
	REG(d0, ULONG num),
	REG(a6, struct XModuleBase *XModuleBase))
{
	DoMethod ((Object *)si, SNGM_REMINSTRUMENT, num);
}



/****** xmodule/xmProcessSongA *********************************************
*
*	NAME
*		xmProcessSongA -- Performs complex processing on a song
*		xmProcessSong -- Varargs stub for xmProcessSongA()
*
*	SYNOPSIS
*		result = xmProcessSongA(si, reserved, tagList)
*		                        A0  A1,       A2
*
*		LONG xmProcessSongA(struct SongInfo *,void *,struct TagItem *);
*
*
*		result = xmProcessSong(si, reserved, tag1, ...)
*
*		LONG xmProcessSongA(struct SongInfo *,void *,LONG,...);
*
*	FUNCTION
*		Performs complex processing operations on a song.
*
*	INPUTS
*		si - pointer to the song to be processed.
*		reserved - Reserved for future use.
*		tagList - List of arguments.  See below for possible tags.
*
*	RESULT
*		The result depends on the kind of operation done.  For most
*		operations, it's value will be 0 to indicate success and
*		an AmigaDOS error code which indicates the cause for failure.
*
*	TAGS
*		XMSNG_Optimize - (LONG).  Tries to reduce the song size by removing
*			all unused data.  Possible optimizations are:
*				- XMOF_REM_UNUSED_PATTERNS
*				- XMOF_REM_DUP_PATTERNS
*				- XMOF_CUT_PATTERNS
*				- XMOF_REM_UNUSED_INSTRS
*				- XMOF_CUT_INSTR_LOOPS
*				- XMOF_CUT_INSTR_TAILS
*				- XMOF_DEFAULT
*			XMOF_DEFAULT will select all the optimizations choosen by
*			the user in addition to the ones specified with the other flags.
*
*		XMSNG_RemapInstruments - (BOOL) Performs instruments remapping.
*
*		XMSNG_LimitPatterns - (UWORD,UWORD) Limits the length all the
*			patterns inside a minimum and maximum value.  The upper 16bits
*			of the argument are the minimum value, the lower ones are
*			the maximum value.  Patterns outside this limits will be grown
*			or split as appropriate.
*
*		XMSNG_Join - (struct SongInfo *) - Joins the song with another one.
*
*		XMSNG_Merge - (struct SongInfo *) - Merges the song with another one.
*
*	NOTE
*		In order to process a song, you should have exclusive access to
*		it.  Always obtain a lock before you call this function on
*		public songs.
*
****************************************************************************
*/
static LIBCALL LONG XModuleProcessSong (
	REG(a0, struct SongInfo *si),
	REG(a1, void *reserved),
	REG(a2, struct TagItem *tags),
	REG(a6, struct XModuleBase *XModuleBase))
{
	struct TagItem	*ti, *tstate = tags;
	LONG result = 0;

	while (ti = NextTagItem(&tstate))
	{
		switch (ti->ti_Tag)
		{
			case XMSNG_Optimize:
			{
				ULONG flags = ti->ti_Data;

				if (flags & XMOF_DEFAULT)
				{
					if (OptSwitches.RemPatts)		flags |= XMOF_REM_UNUSED_PATTERNS;
					if (OptSwitches.RemDupPatts)	flags |= XMOF_REM_DUP_PATTERNS;
					if (OptSwitches.RemInstr)		flags |= XMOF_REM_UNUSED_INSTRS;
					if (OptSwitches.RemDupInstr)	flags |= XMOF_REM_DUP_INSTRS;
					if (OptSwitches.CutAfterLoop)	flags |= XMOF_CUT_INSTR_LOOPS;
					if (OptSwitches.CutZeroTail)	flags |= XMOF_CUT_INSTR_TAILS;
					if (OptSwitches.CutPatts)		flags |= XMOF_CUT_PATTERNS;
					if (OptSwitches.RemapInstr)		flags |= XMOF_REMAP_INSTRS;
				}

				if (flags & XMOF_REM_UNUSED_PATTERNS)		DiscardPatterns (si);
				if (flags & XMOF_REM_DUP_PATTERNS)			RemDupPatterns (si);
				if (flags & XMOF_CUT_PATTERNS)				CutPatterns (si);
				if (flags & XMOF_REM_UNUSED_INSTRS)			RemUnusedInstruments (si);
				if (flags & XMOF_REM_DUP_INSTRS)			RemDupInstruments (si);
				if (flags & XMOF_CUT_INSTR_LOOPS)			OptimizeInstruments (si);
				if (flags & XMOF_CUT_INSTR_TAILS)			OptimizeInstruments (si);
				if (flags & XMOF_REMAP_INSTRS)				RemapInstruments (si);

				break;
			}

			case XMSNG_RemapInstruments:
				RemapInstruments (si);
				result = 0;
				break;

			case XMSNG_LimitPatterns:
				result = ResizePatterns (si, ti->ti_Data >> 16, ti->ti_Data & 0xFFFF);
				break;

			case XMSNG_Join:
				result = NULL;
				break;

			case XMSNG_Merge:
				result = NULL;
				break;

			default:
				break;
		}
	}

	return result;
}



/****** xmodule/xmDisplayMessage *******************************************
*
*	NAME
*		xmDisplayMessageA -- Displays a message to the user
*		xmDisplayMessage -- Varargs stub for xmDisplayMessageA()
*
*	SYNOPSIS
*		xmDisplayMessageA(level, message, args)
*		                  DO     A0       A1
*
*		void xmDisplayMessageA(ULONG,APTR,LONG *);
*
*
*		xmDisplayMessage(level, message, ...)
*
*		void xmDisplayMessage(ULONG,APTR,...);
*
*	FUNCTION
*		Outputs a string in the XModule log window or in the 'action' field
*		of the progress indicator.  The string is printf-formatted before
*		being output.
*
*	INPUTS
*		level - a number from 0 to 7 which indicates the importance of the
*			message.  7 is used for very annoying messages (eg: debug output),
*			0 for very important things (eg: disaster).
*			If you set the XMDMF_USECATALOG bit in the level parameter, you
*			can pass a catalog string number instead of a pointer to a string.
*			Specifying XMDMF_ACTION, the message will be put in the 'action'
*			field of the progress indicator.
*			If the flag XMDMF_DOSFAULT is specified, a Fault() output is
*			formatted using the message as an header.  In this case, the
*			level parameter takes another meaing: The lower word can contain
*			an AmigaDOS error code. If it is 0, IoErr() will be used to get
*			the error code.
*		message - pointer to a string or catalog message number,
*			when the XMDMF_USECATALOG flag is set.
*		args - arguments for formatting.
*
*	EXAMPLES
*		xmDisplayMessage (XMDMF_ALERT,
*			"The application `%s' fucked up Windoze95 because %s.",
*			"(unknown name)", "an error occurred");
*
*		xmDisplayMessage (XMDMF_USE_CATALOG | XMDMF_COMMENT,
*			MSG_LIFE_UNIVERSE_AND_ALLTHAT, 42, "Fortytwo", "For tea too");
*
*		xmDisplayMessageA (XMDMF_ACTION | XMDMF_USECATALOG,
*			MSG_READING_COMICS, NULL);
*
*		xmDisplayMessage (XMDMF_DOSFAULT | XMDMF_USECATALOG,
*			MSG_CANT_LOAD_MODULE, ModuleName);
*
*	SEE ALSO
*		xmDisplayProgress()
*
****************************************************************************
*/
static LIBCALL void XModuleDisplayMessage (
	REG(d0, ULONG level),
	REG(a0,	CONST_STRPTR msg),
	REG(a1, LONG *args),
	REG(a6, struct XModuleBase *XModuleBase))
{
	if (level & XMDMF_USECATALOG)
		msg = STR((ULONG)msg);

	if (level & XMDMF_DOSFAULT)
	{
		UBYTE buf[FAULT_MAX + 100];
		UBYTE buf2[100];

		if (GuiSwitches.LogLevel >= XMDMF_ERROR)
		{
			if (args) VSPrintf (buf2, msg, args);

			if (Fault ((level & XMDMF_DOSERRORMASK) ? (level & XMDMF_DOSERRORMASK) : IoErr(),
				args ? buf2 : msg, buf, FAULT_MAX + 100))
			{
				if (level & XMDMF_USEREQUESTER) ShowRequestStr (buf, NULL, args);
				else ShowString (buf, args);
			}
		}
	}
	else if (level & XMDMF_ACTION)
	{
		DisplayActionStr (msg);
		if (GuiSwitches.LogLevel > XMDMF_INFORMATION)
			ShowString (msg, args);
	}
	else if (level & XMDMF_USEREQUESTER)
		ShowRequestStr (msg, NULL, args);
	else if (GuiSwitches.LogLevel > (level & XMDMF_LEVELMASK))
		ShowString (msg, args);
}



/****** xmodule/xmDisplayProgress ******************************************
*
*	NAME
*		xmDisplayProgress -- Uptdates the progress bar indicator
*
*	SYNOPSIS
*		abort = xmDisplayProgress(done, total)
*		                          D0    D1
*
*		LONG xmDisplayMessageA(ULONG,ULONG);
*
*	FUNCTION
*		Updates the position of the fuel gauge in the progress window to
*		show the progress of an operation.  Additionally, it checks for
*		user abort.
*
*	INPUTS
*		done - a number which indicates how much of the work has
*			been already completed
*		total - Total number of operations to do.
*
*	RESULT
*		0 for success, ERROR_BREAK if user abort was detected.  You should
*		always check this return code to abort what you were doing.
*
****************************************************************************
*/
static LIBCALL void XModuleDisplayProgress (
	REG(d0,	ULONG done),
	REG(d1, ULONG total),
	REG(a6, struct XModuleBase *XModuleBase))
{
	DisplayProgress (done, total);
}

