/*
**	termSound.c
**
**	Sound support routines
**
**	Copyright © 1990-1994 by Olaf `Olsen' Barthel
**		All Rights Reserved
*/

#include "termGlobal.h"

	/* 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

	/* Sample compression modes. */

#define sCmpNone	0
#define sCmpFibDelta	1

	/* The voice header. */

typedef struct
{
	ULONG	oneShotHiSamples,	/* # samples in the high octave 1-shot part */
		repeatHiSamples,	/* # samples in the high octave repeat part */
		samplesPerHiCycle;	/* # samples/cycle in high octave, else 0 */
	UWORD	samplesPerSec;		/* data sampling rate */
	UBYTE	ctOctave,		/* # of octaves of waveforms */
		sCompression;		/* data compression technique used */
	Fixed	volume;			/* playback nominal volume from 0 to Unity
					 * (full volume). Map this value into
					 * the output hardware's dynamic range.
					 */
} Voice8Header;

	/* Double-buffering information. */

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

	/* Sound replay information. */

struct SoundInfo
{
	UBYTE		 Name[MAX_FILENAME_LENGTH];

	ULONG		 Rate,
			 Length,
			 Volume;

	APTR		 SoundData;

	APTR		 LeftData,
			 RightData;

	Object		*SoundObject;
	struct timeval	 SoundTime;
};

	/* Local data. */

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

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

STATIC VOID __regargs
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 __regargs
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 * __regargs
LoadSound(STRPTR Name,BYTE Warn)
{
	struct SoundInfo	*SoundInfo = NULL;
	struct IFFHandle	*Handle;

		/* Allocate IFF handle for reading. */

	if(Handle = AllocIFF())
	{
			/* Open a standard DOS stream. */

		if(Handle -> iff_Stream = Open(Name,MODE_OLDFILE))
		{
				/* Say it's a DOS stream. */

			InitIFFasDOS(Handle);

				/* Open the file for reading. */

			if(!OpenIFF(Handle,IFFF_READ))
			{
				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;
					Voice8Header		 Header;
					UBYTE			 Compression;
					SampleType		 Channel	= SAMPLE_STEREO;
					BYTE			 SingleChannel	= TRUE;

						/* Clear the voice header. */

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

						/* 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(Voice8Header)),1) == 1)
								{
										/* Compression type supported? */

									if(Header . sCompression == sCmpNone || Header . sCompression == sCmpFibDelta)
									{
											/* 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 . samplesPerSec;
											SoundInfo -> Length	= Header . oneShotHiSamples ? Header . oneShotHiSamples : (Header . repeatHiSamples ? Header . repeatHiSamples : Header . samplesPerHiCycle);
											SoundInfo -> Volume	= (SoundConfig . Volume * ((Header . volume * MAX_VOLUME) / Unity)) / 100;

												/* Remember compression mode. */

											Compression		= Header . sCompression;
										}
									}
								}

								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)
								{
									BYTE Success = FALSE;

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

										/* Uncompressed raw data? */

									if(Compression == sCmpNone)
									{
										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;
						}
					}
				}

				CloseIFF(Handle);
			}

			Close(Handle -> iff_Stream);
		}

		FreeIFF(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,	64,
				SDTA_Cycles,	1,
			TAG_DONE))
			{
				ULONG			 SampleLength	= 0;
				struct VoiceHeader	*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)
			MyEasyRequest(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 __inline
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;
	WORD	 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(SoundControlRequest);
		WaitIO(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(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(SoundRequestRight);
		}

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

		SoundControlRequest -> ioa_Request . io_Command = CMD_START;

		SendIO(SoundControlRequest);
		WaitIO(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(SoundRequestLeft);

				if(Right)
					WaitIO(SoundRequestRight);

					/* Block both channels. */

				SoundControlRequest -> ioa_Request . io_Command = CMD_STOP;

				SendIO(SoundControlRequest);
				WaitIO(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(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(SoundRequestRight);
				}

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

				SoundControlRequest -> ioa_Request . io_Command = CMD_START;

				SendIO(SoundControlRequest);
				WaitIO(SoundControlRequest);
			}
		}

			/* Wait for sounds to terminate. */

		if(Left)
			WaitIO(SoundRequestLeft);

		if(Right)
			WaitIO(SoundRequestRight);
	}
	while(Size);
}

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

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

		if(TimePort = CreateMsgPort())
		{
			struct timerequest *TimeRequest;

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

					SetSignal(0,PORTMASK(TimePort));

					SendIO(TimeRequest);

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

					WaitIO(TimeRequest);

					CloseDevice(TimeRequest);
				}

				DeleteIORequest(TimeRequest);
			}

			DeleteMsgPort(TimePort);
		}
	}
	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	= 80;
							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,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(SoundControlRequest);
							}

							DeleteIORequest(SoundRequestRight);
						}

						DeleteIORequest(SoundRequestLeft);
					}

					DeleteIORequest(SoundControlRequest);
				}

				DeleteMsgPort(SoundPort);
			}

			FreeVec(BufferInfo);
		}
	}
}

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

STATIC BYTE __regargs
SoundLoad(WORD Sound,BYTE Warn)
{
	struct SoundInfo	**Info = &SoundSlot[Sound];
	STRPTR			Name;
	BYTE			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;
	}

		/* 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 __saveds
SoundServer(VOID)
{
	LONG Sound = (LONG)SysBase -> ThisTask -> tc_UserData;

		/* Get exclusive access to the sound slots. */

	ObtainSemaphore(SoundSemaphore);

		/* Replay the sound. */

	PlaySound(SoundSlot[Sound]);

	Forbid();

		/* Release access to the sound slots. */

	ReleaseSemaphore(SoundSemaphore);

		/* Remove ourselves. */

	RemTask(NULL);
}

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

VOID
SoundExit()
{
	WORD i;

		/* Access semaphore available? */

	if(SoundSemaphore)
	{
		ObtainSemaphore(SoundSemaphore);

		ReleaseSemaphore(SoundSemaphore);

		FreeVecPooled(SoundSemaphore);

		SoundSemaphore = NULL;
	}

		/* Free the slots. */

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

			SoundSlot[i] = NULL;
		}
	}
}

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

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

	if(SoundSemaphore)
	{
		ObtainSemaphore(SoundSemaphore);
		ReleaseSemaphore(SoundSemaphore);
	}
	else
	{
		if(SoundSemaphore = (struct SignalSemaphore *)AllocVecPooled(sizeof(struct SignalSemaphore),MEMF_ANY | MEMF_PUBLIC))
			InitSemaphore(SoundSemaphore);
	}

		/* Preload sounds if necessary. */

	if(SoundSemaphore && SoundConfig . Preload)
	{
		WORD i;

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

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

VOID __regargs
SoundPlay(WORD Sound)
{
		/* Sound access semaphore available? */

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

			if(!AttemptSemaphore(SoundSemaphore))
				return;
			else
				ReleaseSemaphore(SoundSemaphore);

				/* Release any other sound if necessary. */

			if(!SoundConfig . Preload)
			{
				WORD 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])
			{
				struct Task	*SoundTask;
				LONG		 Pri = ThisProcess -> pr_Task . tc_Node . ln_Pri + 10;

					/* Priority raised, check for limit. */

				if(Pri > 127)
					Pri = 127;

				Forbid();

					/* Fire off the sound server task. */

				if(SoundTask = CreateTask("term Sound Task",Pri,SoundServer,4096))
					SoundTask -> tc_UserData = (APTR)Sound;

				Permit();
			}
			else
				Beep();
		}
	}
	else
		Beep();
}
