/*
**	Sound.c
**
**	Sound support routines
**
**	Copyright © 1990-1996 by Olaf `Olsen' Barthel
**		All Rights Reserved
**
**	:ts=4
*/

#ifndef _GLOBAL_H
#include "Global.h"
#endif

	/* Double buffering chunk size. */

#define BUFFER_SIZE 32768

	/* Maximum replay volume. */

#define MAX_VOLUME 64

	/* Stereo/channel sample type. */

typedef LONG SampleType;

	/* Channel definitions. */

#define SAMPLE_LEFT		2
#define SAMPLE_RIGHT	4
#define SAMPLE_STEREO	6

	/* A fixed-point value, 16 bits to the left of
	 * the point and 16 to the right. A Fixed is a
	 * number of 2**16ths, i.e. 65536ths.
	 */

typedef LONG Fixed;

	/* Unity = Fixed 1.0 = maximum volume */

#define Unity 0x10000L

	/* Double-buffering information. */

struct BufferInfo
{
	LONG	Size;
	UBYTE	Buffer[BUFFER_SIZE];
};

	/* Local data. */

STATIC struct SignalSemaphore	 SoundSemaphore;
STATIC struct SoundInfo			*SoundSlot[SOUND_COUNT];
STATIC BOOL						 SoundInitialized;

	/* DeltaUnpack(UBYTE *Src,ULONG Size,BYTE *Dst):
	 *
	 *	Unpack Fibonacci-delta-encoded data.
	 */

STATIC VOID
DeltaUnpack(UBYTE *Src,ULONG Size,BYTE *Dst)
{
	STATIC BYTE CodeToDelta[16] = { -34,-21,-13,-8,-5,-3,-2,-1,0,1,2,3,5,8,13,21 };

	BYTE	Value = (BYTE)Src[1];
	UBYTE	Code;

		/* Skip the header information. */

	Src		+= 2;
	Size	-= 2;

		/* Run down the chunk... */

	while(Size--)
	{
		Code = *Src++;

			/* Add the top nibble delta. */

		Value += CodeToDelta[Code >> 4];

		*Dst++ = Value;

			/* Add the bottom nibble delta. */

		Value += CodeToDelta[Code & 0xF];

		*Dst++ = Value;
	}
}

	/* FreeSound(struct SoundInfo *SoundInfo):
	 *
	 *	Free sound handle and associated data.
	 */

VOID
FreeSound(struct SoundInfo *SoundInfo)
{
	if(SoundInfo->SoundObject)
		DisposeDTObject(SoundInfo->SoundObject);
	else
		FreeVecPooled(SoundInfo->SoundData);

	FreeVecPooled(SoundInfo);
}

	/* LoadSound(STRPTR Name):
	 *
	 *	Load a sound file from disk.
	 */

struct SoundInfo *
LoadSound(STRPTR Name,BOOL Warn)
{
	struct SoundInfo	*SoundInfo = NULL;
	struct IFFHandle	*Handle;

		/* Allocate IFF handle for reading. */

	if(Handle = OpenIFFStream(Name,MODE_OLDFILE))
	{
		LONG SoundStops[3 * 2] =
		{
			ID_8SVX,ID_VHDR,
			ID_8SVX,ID_CHAN,
			ID_8SVX,ID_BODY
		};

			/* Mark the chunks to stop at. */

		if(!StopChunks(Handle,SoundStops,3))
		{
			struct ContextNode *Chunk;
			struct VoiceHeader Header;
			SampleType Channel;
			BOOL SingleChannel;
			UBYTE Compression;

			Channel			= SAMPLE_STEREO;
			SingleChannel	= TRUE;

				/* Clear the voice header. */

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

				/* Scan for data... */

			while(!ParseIFF(Handle,IFFPARSE_SCAN))
			{
				Chunk = CurrentChunk(Handle);

					/* What did we find? */

				switch(Chunk->cn_ID)
				{
						/* Looks like the basic voice header. */

					case ID_VHDR:

							/* Read the header. */

						if(ReadChunkRecords(Handle,&Header,MIN(Chunk->cn_Size,sizeof(Header)),1) == 1)
						{
								/* Compression type supported? */

							if(Header.vh_Compression == CMP_NONE || Header.vh_Compression == CMP_FIBDELTA)
							{
									/* Allocate the sound handle. */

								if(SoundInfo = (struct SoundInfo *)AllocVecPooled(sizeof(struct SoundInfo),MEMF_ANY | MEMF_CLEAR))
								{
										/* Install the rate, volume and length. */

									SoundInfo->Rate		= SysBase->ex_EClockFrequency * 5 / Header.vh_SamplesPerSec;
									SoundInfo->Length	= Header.vh_OneShotHiSamples ? Header.vh_OneShotHiSamples : (Header.vh_RepeatHiSamples ? Header.vh_RepeatHiSamples : Header.vh_SamplesPerHiCycle);
									SoundInfo->Volume	= (SoundConfig.Volume * ((Header.vh_Volume * MAX_VOLUME) / Unity)) / 100;

										/* Remember compression mode. */

									Compression		= Header.vh_Compression;
								}
							}
						}

						break;

						/* Looks like sound channel information. */

					case ID_CHAN:

							/* Do we have a handle to manage it? */

						if(SoundInfo)
						{
								/* Read the channel information. */

							if(ReadChunkRecords(Handle,&Channel,MIN(Chunk->cn_Size,sizeof(SampleType)),1) == 1)
							{
									/* Stereo sound file? */

								if(Channel == SAMPLE_STEREO)
									SingleChannel = FALSE;
							}
							else
							{
								FreeVecPooled(SoundInfo);

								SoundInfo = NULL;
							}
						}

						break;

						/* Looks like the sound body data. */

					case ID_BODY:

							/* Do we have sound handle to manage it? */

						if(SoundInfo)
						{
							BOOL Success = FALSE;

							if(!SoundInfo->Length)
								SoundInfo->Length = Chunk->cn_Size;

								/* Uncompressed raw data? */

							if(Compression == CMP_NONE)
							{
								ULONG Wanted;

								if(Channel == SAMPLE_STEREO && !SingleChannel)
								{
									Wanted = SoundInfo->Length * 2;

									if(Wanted > Chunk->cn_Size)
									{
										SoundInfo->Length /= 2;

										Wanted = SoundInfo->Length * 2;
									}
								}
								else
									Wanted = SoundInfo->Length;

								if(Chunk->cn_Size >= Wanted)
								{
										/* Allocate a buffer. */

									if(SoundInfo->SoundData = AllocVecPooled(Wanted,MEMF_ANY))
									{
											/* Read the data. */

										if(ReadChunkRecords(Handle,SoundInfo->SoundData,Wanted,1) == 1)
											Success = TRUE;
										else
											FreeVecPooled(SoundInfo->SoundData);
									}
								}
							}
							else
							{
								ULONG Wanted;

								if(Channel == SAMPLE_STEREO && !SingleChannel)
								{
									Wanted = SoundInfo->Length + 4;

									if(Wanted > Chunk->cn_Size)
									{
										SoundInfo->Length /= 2;

										Wanted = SoundInfo->Length * 2 + 4;

										SingleChannel = TRUE;
									}
								}
								else
									Wanted = SoundInfo->Length / 2 + 2;

								if(Chunk->cn_Size >= Wanted)
								{
									UBYTE *TempBuffer;

										/* Allocate a temporary decompression buffer. */

									if(TempBuffer = (UBYTE *)AllocVecPooled(Chunk->cn_Size,MEMF_ANY))
									{
											/* Read the compressed data. */

										if(ReadChunkRecords(Handle,TempBuffer,Chunk->cn_Size,1) == 1)
										{
											ULONG Length = SoundInfo->Length;

												/* Allocate space for the uncompressed data. */

											if(SoundInfo->SoundData = AllocVecPooled(Length * 2,MEMF_ANY))
											{
													/* Stereo sound file? */

												if(!SingleChannel && Channel == SAMPLE_STEREO)
												{
													UBYTE *Data = SoundInfo->SoundData;

														/* Unpack the stereo sound. */

													DeltaUnpack(TempBuffer,                 Length / 2 + 2,Data);
													DeltaUnpack(TempBuffer + Length / 2 + 2,Length / 2 + 2,Data + Length);
												}
												else
												{
														/* Unpack the mono sound. */

													DeltaUnpack(TempBuffer,Length / 2 + 2,SoundInfo->SoundData);
												}

												Success = TRUE;
											}
										}

										FreeVecPooled(TempBuffer);
									}
								}
							}

							if(!Success)
							{
								FreeVecPooled(SoundInfo);

								SoundInfo = NULL;
							}
						}

						break;
				}
			}

				/* Did we get what we wanted? */

			if(SoundInfo)
			{
					/* Any sound data allocated? */

				if(SoundInfo->SoundData)
				{
					UBYTE *Data = SoundInfo->SoundData;

						/* Which kind of sound file did
						 * we read?
						 */

					switch(Channel)
					{
							/* Left channel only. */

						case SAMPLE_LEFT:

							SoundInfo->LeftData = Data;
							break;

							/* Right channel only. */

						case SAMPLE_RIGHT:

							SoundInfo->RightData = Data;
							break;

							/* Two stereo channels. */

						case SAMPLE_STEREO:

								/* One sound mapped to two voices. */

							if(SingleChannel)
								SoundInfo->LeftData = SoundInfo->RightData = Data;
							else
							{
									/* Split the voice data. */

								SoundInfo->LeftData		= Data;
								SoundInfo->RightData	= Data + SoundInfo->Length;
							}

							break;
					}
				}
				else
				{
					FreeVecPooled(SoundInfo);

					SoundInfo = NULL;
				}
			}
		}

		CloseIFFStream(Handle);
	}

		/* Successful? */

	if(SoundInfo)
		strcpy(SoundInfo->Name,Name);
	else
	{
		if(DataTypesBase)
		{
			Object *Sound;

			if(Sound = NewDTObject(Name,
				DTA_SourceType,	DTST_FILE,
				DTA_GroupID,	GID_SOUND,
				SDTA_Volume,	(SoundConfig.Volume * MAX_VOLUME) / 100,
				SDTA_Cycles,	1,
			TAG_DONE))
			{
				struct VoiceHeader *Header;
				ULONG SampleLength;

				SampleLength	= 0;
				Header			= NULL;

				if(GetDTAttrs(Sound,
					SDTA_SampleLength,	&SampleLength,
					SDTA_VoiceHeader,	&Header,
				TAG_DONE) == 2)
				{
					if(SampleLength && Header)
					{
						ULONG Secs,Micro,Period;

						Period	= Header->vh_SamplesPerSec;

						Secs	= SampleLength / Period;
						Micro	= (1000000 / Period) * (SampleLength % Period);

						if(SoundInfo = (struct SoundInfo *)AllocVecPooled(sizeof(struct SoundInfo),MEMF_ANY | MEMF_CLEAR))
						{
							strcpy(SoundInfo->Name,Name);

							SoundInfo->SoundObject			= Sound;
							SoundInfo->SoundTime.tv_secs	= Secs;
							SoundInfo->SoundTime.tv_micro	= Micro;

							return(SoundInfo);
						}
					}
				}

				DisposeDTObject(Sound);
			}
		}

		if(Warn)
			ShowRequest(Window,LocaleString(MSG_TERMSOUND_COULD_NOT_LOAD_SOUND_FILE_TXT),LocaleString(MSG_GLOBAL_CONTINUE_TXT),Name);
	}

	return(SoundInfo);
}

	/* ReplaySound():
	 *
	 *	Replay sound with given information (doubly-buffered).
	 */

STATIC VOID
ReplaySound(struct SoundInfo *SoundInfo,struct IOAudio *SoundControlRequest,struct IOAudio *SoundRequestLeft,struct IOAudio *SoundRequestRight,struct BufferInfo *BufferInfo)
{
	UBYTE	*Left	= SoundInfo->LeftData,
			*Right	= SoundInfo->RightData;
	ULONG	 Size	= SoundInfo->Length,Length;
	LONG	 Base	= 0;

	Length = MIN(BUFFER_SIZE,Size);

	Size -= Length;

		/* Left channel available? */

	if(!(((ULONG)SoundRequestLeft->ioa_Request.io_Unit) & (LEFT0F | LEFT1F)))
		Left = NULL;

		/* Right channel available? */

	if(!(((ULONG)SoundRequestRight->ioa_Request.io_Unit) & (RIGHT0F | RIGHT1F)))
		Right = NULL;

		/* Fill up left buffer. */

	if(Left)
	{
		CopyMem(Left,BufferInfo[Base].Buffer,Length);

		BufferInfo[Base].Size = Length;

		Left += Length;
	}

		/* Fill up right buffer. */

	if(Right)
	{
		CopyMem(Right,BufferInfo[Base + 2].Buffer,Length);

		BufferInfo[Base + 2].Size = Length;

		Right += Length;
	}

		/* Process sound data. */

	do
	{
			/* Block both channels. */

		SoundControlRequest->ioa_Request.io_Command = CMD_STOP;

		SendIO((struct IORequest *)SoundControlRequest);
		WaitIO((struct IORequest *)SoundControlRequest);

			/* Any data for the left channel? */

		if(Left)
		{
			SoundRequestLeft->ioa_Request.io_Command	= CMD_WRITE;
			SoundRequestLeft->ioa_Request.io_Flags		= ADIOF_PERVOL;
			SoundRequestLeft->ioa_Period				= SoundInfo->Rate;
			SoundRequestLeft->ioa_Volume				= SoundInfo->Volume;
			SoundRequestLeft->ioa_Cycles				= 1;
			SoundRequestLeft->ioa_Data					= BufferInfo[Base].Buffer;
			SoundRequestLeft->ioa_Length				= BufferInfo[Base].Size;

				/* Get the left channel going. */

			BeginIO((struct IORequest *)SoundRequestLeft);
		}

			/* Any data for the right channel? */

		if(Right)
		{
			SoundRequestRight->ioa_Request.io_Command	= CMD_WRITE;
			SoundRequestRight->ioa_Request.io_Flags		= ADIOF_PERVOL;
			SoundRequestRight->ioa_Period				= SoundInfo->Rate;
			SoundRequestRight->ioa_Volume				= SoundInfo->Volume;
			SoundRequestRight->ioa_Cycles				= 1;
			SoundRequestRight->ioa_Data					= BufferInfo[Base + 2].Buffer;
			SoundRequestRight->ioa_Length				= BufferInfo[Base + 2].Size;

				/* Get the right channel going. */

			BeginIO((struct IORequest *)SoundRequestRight);
		}

			/* Start up both channels synchronously... */

		SoundControlRequest->ioa_Request.io_Command = CMD_START;

		SendIO((struct IORequest *)SoundControlRequest);
		WaitIO((struct IORequest *)SoundControlRequest);

			/* Grab the other buffers. */

		Base ^= 1;

			/* Still any data left? */

		if(Size)
		{
				/* Cut off the next slice. */

			Length = MIN(BUFFER_SIZE,Size);

			Size -= Length;

				/* Left channel available? */

			if(Left)
			{
				CopyMem(Left,BufferInfo[Base].Buffer,Length);

				BufferInfo[Base].Size = Length;

				Left += Length;
			}

				/* Right channel available? */

			if(Right)
			{
				CopyMem(Right,BufferInfo[Base + 2].Buffer,Length);

				BufferInfo[Base + 2].Size = Length;

				Right += Length;
			}

				/* Last slice eaten? */

			if(!Size)
			{
					/* Wait for sounds to terminate. */

				if(Left)
					WaitIO((struct IORequest *)SoundRequestLeft);

				if(Right)
					WaitIO((struct IORequest *)SoundRequestRight);

					/* Block both channels. */

				SoundControlRequest->ioa_Request.io_Command = CMD_STOP;

				SendIO((struct IORequest *)SoundControlRequest);
				WaitIO((struct IORequest *)SoundControlRequest);

					/* Any data for the left channel? */

				if(Left)
				{
					SoundRequestLeft->ioa_Request.io_Command	= CMD_WRITE;
					SoundRequestLeft->ioa_Request.io_Flags		= ADIOF_PERVOL;
					SoundRequestLeft->ioa_Period				= SoundInfo->Rate;
					SoundRequestLeft->ioa_Volume				= SoundInfo->Volume;
					SoundRequestLeft->ioa_Cycles				= 1;
					SoundRequestLeft->ioa_Data					= BufferInfo[Base].Buffer;
					SoundRequestLeft->ioa_Length				= BufferInfo[Base].Size;

						/* Get the left channel going. */

					BeginIO((struct IORequest *)SoundRequestLeft);
				}

					/* Any data for the right channel? */

				if(Right)
				{
					SoundRequestRight->ioa_Request.io_Command	= CMD_WRITE;
					SoundRequestRight->ioa_Request.io_Flags		= ADIOF_PERVOL;
					SoundRequestRight->ioa_Period				= SoundInfo->Rate;
					SoundRequestRight->ioa_Volume				= SoundInfo->Volume;
					SoundRequestRight->ioa_Cycles				= 1;
					SoundRequestRight->ioa_Data					= BufferInfo[Base + 2].Buffer;
					SoundRequestRight->ioa_Length				= BufferInfo[Base + 2].Size;

						/* Get the right channel going. */

					BeginIO((struct IORequest *)SoundRequestRight);
				}

					/* Start up both channels synchronously... */

				SoundControlRequest->ioa_Request.io_Command = CMD_START;

				SendIO((struct IORequest *)SoundControlRequest);
				WaitIO((struct IORequest *)SoundControlRequest);
			}
		}

			/* Wait for sounds to terminate. */

		if(Left)
			WaitIO((struct IORequest *)SoundRequestLeft);

		if(Right)
			WaitIO((struct IORequest *)SoundRequestRight);
	}
	while(Size);
}

	/* PlaySound(struct SoundInfo *SoundInfo):
	 *
	 *	Replay a sound.
	 */

VOID
PlaySound(struct SoundInfo *SoundInfo)
{
	if(SoundInfo->SoundObject)
	{
		struct MsgPort *SoundTimePort;

		if(SoundTimePort = CreateMsgPort())
		{
			struct timerequest *SoundTimeRequest;

			if(SoundTimeRequest = (struct timerequest *)CreateIORequest(SoundTimePort,sizeof(struct timerequest)))
			{
				if(!OpenDevice(TIMERNAME,UNIT_VBLANK,(struct IORequest *)SoundTimeRequest,NULL))
				{
					SoundTimeRequest->tr_node.io_Command	= TR_ADDREQUEST;
					SoundTimeRequest->tr_time				= SoundInfo->SoundTime;

					SetSignal(0,PORTMASK(SoundTimePort));

					SendIO((struct IORequest *)SoundTimeRequest);

					DoMethod(SoundInfo->SoundObject,DTM_TRIGGER,NULL,STM_PLAY,NULL);

					WaitIO((struct IORequest *)SoundTimeRequest);

					CloseDevice((struct IORequest *)SoundTimeRequest);
				}

				DeleteIORequest(SoundTimeRequest);
			}

			DeleteMsgPort(SoundTimePort);
		}
	}
	else
	{
		struct BufferInfo *BufferInfo;

			/* Allocate sound buffers. */

		if(BufferInfo = (struct BufferInfo *)AllocVec(4 * sizeof(struct BufferInfo),MEMF_CHIP))
		{
			struct MsgPort *SoundPort;

				/* Allocate an io replyport. */

			if(SoundPort = CreateMsgPort())
			{
				struct IOAudio *SoundControlRequest;

					/* Allocate the big sound control request. */

				if(SoundControlRequest = (struct IOAudio *)CreateIORequest(SoundPort,sizeof(struct IOAudio)))
				{
					struct IOAudio *SoundRequestLeft;

						/* Allocate the left channel sound request. */

					if(SoundRequestLeft = (struct IOAudio *)CreateIORequest(SoundPort,sizeof(struct IOAudio)))
					{
						struct IOAudio *SoundRequestRight;

							/* Allocate the right channel sound request. */

						if(SoundRequestRight = (struct IOAudio *)CreateIORequest(SoundPort,sizeof(struct IOAudio)))
						{
							STATIC UBYTE TwoChannels[] =
							{
								LEFT0F | RIGHT0F,
								LEFT0F | RIGHT1F,
								LEFT1F | RIGHT0F,
								LEFT1F | RIGHT1F
							};

							STATIC UBYTE LeftChannel[] =
							{
								LEFT0F,
								LEFT1F
							};

							STATIC UBYTE RightChannel[] =
							{
								RIGHT0F,
								RIGHT1F
							};

							UBYTE *AllocationMap;
							LONG AllocationSize;

								/* Determine the correct channel
								 * allocation map.
								 */

							if(SoundInfo->LeftData)
							{
								if(SoundInfo->RightData)
								{
									AllocationMap	= TwoChannels;
									AllocationSize	= sizeof(TwoChannels);
								}
								else
								{
									AllocationMap	= LeftChannel;
									AllocationSize	= sizeof(LeftChannel);
								}
							}
							else
							{
								AllocationMap	= RightChannel;
								AllocationSize	= sizeof(RightChannel);
							}

								/* Set up for sound channel allocation. */

							SoundControlRequest->ioa_Request.io_Message.mn_Node.ln_Pri	= 127;
							SoundControlRequest->ioa_Request.io_Command					= ADCMD_ALLOCATE;
							SoundControlRequest->ioa_Request.io_Flags					= ADIOF_NOWAIT | ADIOF_PERVOL;
							SoundControlRequest->ioa_Data								= AllocationMap;
							SoundControlRequest->ioa_Length								= AllocationSize;

								/* Open audio.device, allocating the sound
								 * channels on the fly.
								 */

							if(!OpenDevice(AUDIONAME,NULL,(struct IORequest *)SoundControlRequest,NULL))
							{
									/* Clone the sound control request. */

								CopyMem(SoundControlRequest,SoundRequestLeft,	sizeof(struct IOAudio));
								CopyMem(SoundControlRequest,SoundRequestRight,	sizeof(struct IOAudio));

									/* Separate the channels. */

								SoundRequestLeft ->ioa_Request.io_Unit = (struct Unit *)((ULONG)SoundRequestLeft ->ioa_Request.io_Unit & (LEFT0F  | LEFT1F));
								SoundRequestRight->ioa_Request.io_Unit = (struct Unit *)((ULONG)SoundRequestRight->ioa_Request.io_Unit & (RIGHT0F | RIGHT1F));

									/* Replay the sound... */

								ReplaySound(SoundInfo,SoundControlRequest,SoundRequestLeft,SoundRequestRight,BufferInfo);

									/* Do the big cleanup. */

								CloseDevice((struct IORequest *)SoundControlRequest);
							}

							DeleteIORequest(SoundRequestRight);
						}

						DeleteIORequest(SoundRequestLeft);
					}

					DeleteIORequest(SoundControlRequest);
				}

				DeleteMsgPort(SoundPort);
			}

			FreeVec(BufferInfo);
		}
	}
}

	/* SoundLoad(LONG Sound,BOOL Warn):
	 *
	 *	Loads or frees a sound slot.
	 */

STATIC BOOL
SoundLoad(LONG Sound,BOOL Warn)
{
	struct SoundInfo **Info;
	BOOL Success;
	STRPTR Name;

	Info	= &SoundSlot[Sound];
	Success	= FALSE;

		/* Which sound file name are we to pick? */

	switch(Sound)
	{
		case SOUND_BELL:

			Name = SoundConfig.BellFile;
			break;

		case SOUND_CONNECT:

			Name = SoundConfig.ConnectFile;
			break;

		case SOUND_DISCONNECT:

			Name = SoundConfig.DisconnectFile;
			break;

		case SOUND_GOODTRANSFER:

			Name = SoundConfig.GoodTransferFile;
			break;

		case SOUND_BADTRANSFER:

			Name = SoundConfig.BadTransferFile;
			break;

		case SOUND_RING:

			Name = SoundConfig.RingFile;
			break;

		case SOUND_VOICE:

			Name = SoundConfig.VoiceFile;
			break;

		case SOUND_ERROR:

			Name = SoundConfig.ErrorNotifyFile;
			break;
	}

		/* Is a file name available? */

	if(Name[0])
	{
			/* Sound slot already initialized? */

		if(*Info)
		{
				/* Did the file name change? */

			if(Stricmp((*Info)->Name,Name))
			{
					/* Free the sound slot. */

				FreeSound(*Info);

					/* Try to load the sound slot. */

				if(*Info = LoadSound(Name,Warn))
				{
						/* Remember the file name. */

					strcpy((*Info)->Name,Name);

					Success = TRUE;
				}
			}
			else
				Success = TRUE;
		}
		else
		{
				/* Try to load the sound slot. */

			if(*Info = LoadSound(Name,Warn))
			{
					/* Remember the file name. */

				strcpy((*Info)->Name,Name);

				Success = TRUE;
			}
		}
	}
	else
	{
			/* Does the slot still contain any sound? */

		if(*Info)
		{
				/* Free the slot. */

			FreeSound(*Info);

			*Info = NULL;
		}

		Success = TRUE;
	}

	return(Success);
}

	/* SoundServer(VOID):
	 *
	 *	Replay a sound.
	 */

STATIC VOID SAVE_DS STACKARGS
SoundServer(LONG Index)
{
		/* Get access to the sound slots. */

	SafeObtainSemaphoreShared(&SoundSemaphore);

		/* Replay the sound. */

	PlaySound(SoundSlot[Index]);

	Forbid();

		/* Release access to the sound slots. */

	ReleaseSemaphore(&SoundSemaphore);
}

	/* SoundExit():
	 *
	 *	Free allocated sound resources.
	 */

VOID
SoundExit()
{
	LONG i;

	if(SoundInitialized)
		ObtainSemaphore(&SoundSemaphore);

		/* Free the slots. */

	for(i = 0 ; i < SOUND_COUNT ; i++)
	{
		if(SoundSlot[i])
		{
			FreeSound(SoundSlot[i]);

			SoundSlot[i] = NULL;
		}
	}

	if(SoundInitialized)
		ReleaseSemaphore(&SoundSemaphore);
}

	/* SoundInit():
	 *
	 *	Allocate resources required for sound replaying.
	 */

VOID
SoundInit()
{
		/* Sound access semaphore available? */

	if(!SoundInitialized)
	{
		InitSemaphore(&SoundSemaphore);
		SoundInitialized = TRUE;
	}

		/* Preload sounds if necessary. */

	if(SoundConfig.Preload)
	{
		LONG i;

		for(i = 0 ; i < SOUND_COUNT ; i++)
			SoundLoad(i,TRUE);
	}
}

	/* SoundPlay(LONG Sound):
	 *
	 *	Play a certain sound slot.
	 */

VOID
SoundPlay(LONG Sound)
{
	if(SoundConfig.Volume > 0)
	{
		BOOL DoBeep = FALSE;

			/* Sound access semaphore available? */

		if(SoundInitialized)
		{
				/* Check to see if we are currently playing a
				 * sound.
				 */

			if(AttemptSemaphore(&SoundSemaphore))
			{
				ReleaseSemaphore(&SoundSemaphore);

					/* Release any other sound if necessary. */

				if(!SoundConfig.Preload)
				{
					LONG i;

					for(i = 0 ; i < SOUND_COUNT ; i++)
					{
						if(i != Sound && SoundSlot[i])
						{
							FreeSound(SoundSlot[i]);

							SoundSlot[i] = NULL;
						}
					}
				}

					/* Load the sound slot. */

				SoundLoad(Sound,FALSE);

					/* Slot filled? */

				if(!SoundSlot[Sound])
					DoBeep = TRUE;
				else
				{
						/* Fire off the sound server task. */

					if(!LocalCreateTask("term Sound Task",ThisProcess->pr_Task.tc_Node.ln_Pri + 10,(TASKENTRY)SoundServer,4000,1,Sound))
						DoBeep = TRUE;
				}
			}
		}

			/* Check if the default sound should be played. */

		if(DoBeep)
			Beep();
	}
}
