/*	
	Sound.c
	Veneer for islsfx2
*/

#include "global.h"
#include "islxa.h"
#include "sound.h"
#include "map_draw.h"
#include "main.h"
#include "options.h"
#include "maths.h"

// tbd - define & use in options.c/h rather than the hard-coded "128" that's there at the moment
#ifndef OPTIONS_MAX_VOLUME
	#define OPTIONS_MAX_VOLUME	128
#endif

unsigned long sbb_names[MAX_SOUND_BANKS] = {0,0,0,0};
unsigned long spu_addrs[MAX_SOUND_BANKS] = {0,0,0,0};
SfxBankType * sbh_addrs[MAX_SOUND_BANKS] = {0,0,0,0};
XAFileType * music, * ref_music;
int musicPaused=0;
extern XADataType XAData;

struct
{
// CPW: 	int		playing;
	int		volume;
	VECTOR *pos;
}dynamicPanChan[24];


// make sure this is always correct or funny things will happen
#define	NUMBEROF8TRACKXA	3

// The best way to do this I think is to have all the 8 track xa's first and put the 32 track ones after

// Languages moved to subdirectory to get round PSX's max-findable-files-per-directory
char * xa_file_names[] =
{
	"tune05.adx",			// TUNE_5_COOPS_D				 0
	"tune06.adx",			// TUNE_6_COOPS_N				 1
	"tune09.adx",			// TUNE_9_COG					 2
	"tune10.adx",			// TUNE_10_FLYMACHINE			 3
	"tune11.adx",			// TUNE_11_MRST					 4
	"tune07.adx",			// TUNE_7_WORKING				 5
	"tune08.adx",			// TUNE_8_GOLF					 6
	"",
	"tune01.adx",			// TUNE_1_INTRO					 7
	"tune04.adx",			// TUNE_4_FAILED				 8
	"tune02.adx",			// TUNE_2_BRIEFING				 9
	"tune03.adx",			// TUNE_3_COMPLETE				10
	"tune12.adx",			// TUNE_12_DIES					11
	"tune13.adx"			// TUNE_13_GAMEOVER				12
	"",
	"",
	"",
	"",
	"test.adx"				// TUNE_AT1						16
	"test.adx"				// TUNE_AT2						17
	"test.adx"				// TUNE_AT3						18
	"test.adx"				// TUNE_AT4						19
	"test.adx"				// TUNE_AT5						20
	"test.adx"				// TUNE_AT6						21
	"test.adx"				// TUNE_AT7						22
	"test.adx"				// TUNE_AT8						23
};

#define N_XA_TRACKS (sizeof(xa_file_names) / 4)
//XAFileType ref_music[N_XA_TRACKS];

int music_playing = -1;
int music_to_resume = -1;
int music_queued = -1;
int music_fader = 256;

char music_fade_current = 0;
char music_fade_time = 0;
char music_fade_dirn = 0;


// =========== Internal Utils ===============

int soundFindFreeBank()
{
	int i;
	for(i = 0; i < MAX_SOUND_BANKS; i++)
	{
		if(!sbb_names[i])
			return i;
	}
	return -1;
}
int soundFindCRCBank(unsigned long crc)
{
	int i;
	for(i = 0; i < MAX_SOUND_BANKS; i++)
	{
		if(sbb_names[i]==crc)
			return i;
	}
	return -1;
}

int soundFindShortestAngle(int ang1, int ang2)
{
	int temp = ang1 - ang2;

	if ((temp > -2048) && (temp <= 2048))
		return temp;
	else if (temp > 2048)
		return ang1 - ang2 - 4096;
	else
		return ang1 + 4096 - ang2;
}

// Stereo calculations, ripped from GloverPSX
// Vols[0],Vols[1] are returned as 0...256 (erk, not 255)
int soundCalc3DPanFade(VECTOR *position, int *vols, int zerodist)
{
	int cameraAngle, soundAngle, offsetAngle;
	int	distanceX, distanceY, distanceZ, distance;
	int tmpLeftVol, tmpRightVol;
	ULONG calc;
	VECTOR *campos;
	VECTOR *camdirn;

	// CPW: DB("pos = (%d, %d, %d)\n", position->vx, position->vy, position->vz);

	campos = (VECTOR *)&mapCamData.gameCam.vpx;
	camdirn = (VECTOR *)&mapCamData.realCam.vrx;	// which, conveniently, is a zero-relative direction already

	distanceX = (position->vx) - campos->vx;	// Get dist from cam
	distanceY = (position->vy) - campos->vy;
	distanceZ = (position->vz) - campos->vz;

	calc=distanceX * distanceX + distanceY * distanceY + distanceZ * distanceZ;

	if (calc==0)
	{
		distance=0;
	}
	else
	{
		distance = utilSqrt(calc)>>16;
	}

	if(distance > zerodist)	// if sound is too far to be heard, don't bother with positioning
	{
//		DB("Dist %d is beyond %d\n",distance,zerodist);
		return 0;
	}
	

// find the angle of the sound with respect to the camera's position
	cameraAngle = (utilCalcAngle(camdirn->vx,camdirn->vz)) & 4095;
	soundAngle  = (utilCalcAngle((position->vx>>12) - campos->vx, (position->vz>>12) - campos->vz)) & 4095;
	offsetAngle = soundFindShortestAngle(soundAngle & 4095, cameraAngle & 4095);


	if(abs(offsetAngle) > 1024)					// sound is behind us
	{
		if(offsetAngle > 0)
		{
			offsetAngle = 2048 - offsetAngle;	// 1024...2048
		}
		else
		{
			offsetAngle = -2048 - offsetAngle;
		}
	}

//  offsetAngle = -1024...+1024

	tmpRightVol = (offsetAngle + 1024) / 8;	// standard stereo panning

	tmpLeftVol  = 255 - tmpRightVol;

// Distance scaling of the volume
	vols[0] = ((-tmpLeftVol  * distance) / zerodist) + tmpLeftVol;
	vols[1] = ((-tmpRightVol * distance) / zerodist) + tmpRightVol;

	
// CPW: 	DB("leftvol = %d, rightvol = %d, dist = %d\n",vols[0],vols[1], distance);

	return 1;
}




// Do a stereo sfx at full volume (eg - level bosses)
// Vols[0],Vols[1] are returned as 0...256 (erk, not 255)
int soundCalc3DPanOnly(VECTOR *position, int *vols)
{
	int cameraAngle, soundAngle, offsetAngle;
	int	distanceX, distanceZ, distance;
	int tmpLeftVol, tmpRightVol;
	ULONG calc;

	VECTOR *campos;
	VECTOR *camdirn;

	campos = (VECTOR *)&mapCamData.gameCam.vpx;
	camdirn = (VECTOR *)&mapCamData.realCam.vrx;	// which, conveniently, is a zero-relative direction already


	
	distanceX = (position->vx>>12) - campos->vx;	// get the sound's distance from the camera
	distanceZ = (position->vz>>12) - campos->vz;

	calc=distanceX * distanceX + distanceZ * distanceZ;

// find the angle of the sound with respect to the camera's position
	cameraAngle = (utilCalcAngle(camdirn->vx,camdirn->vz)) & 4095;
	soundAngle  = (utilCalcAngle((position->vx>>12) - campos->vx, (position->vz>>12) - campos->vz)) & 4095;
	offsetAngle = soundFindShortestAngle(soundAngle & 4095, cameraAngle & 4095);

	if(abs(offsetAngle) > 1024)					// sound is behind us
	{
		if(offsetAngle > 0)
		{
			offsetAngle = 2048 - offsetAngle;
		}
		else
		{
			offsetAngle = -2048 - offsetAngle;
		}
	}

//  offsetAngle = -1024...+1024
	tmpRightVol = (offsetAngle + 1024) / 8;	// standard stereo panning

	tmpLeftVol  = 255 - tmpRightVol;

	vols[0] = tmpLeftVol;
	vols[1] = tmpRightVol;

//	DB("leftvol = %d, rightvol = %d\n",vols[0],vols[1]);

	return 1;
}


// =========== Loading & Freeing Banks ===============


// Leafnames, in upper case, please
int soundLoadJustBody(char *leafname)
{
	char name[100];
	int i;

	i = soundFindFreeBank();
	if(i!=-1)
	{
		sbb_names[i] = utilStr2CRC(leafname);

		//sprintf(name,"%s\\%s",leafname, leafname);
		gdFsChangeDir("\\");
		gdFsChangeDir("AUDIO");
		// CPW: spu_addrs[i] = sfxLoadSampleBankBody("SOUND\\TEST");
		spu_addrs[i] = sfxLoadSampleBankBody(leafname);
		if(!spu_addrs[i])
		{
			return -1;
		}
	}
	return i;
}

// This won't be needed once we've put the sbh's into bff files, but for now...
SfxBankType *soundLoadBodyAndHeader(char *leafname)
{
	unsigned long crc;
	char name[100];
	int i;
	SfxBankType *sbh = NULL;

	i = soundLoadJustBody(leafname);
	if(i != -1)
	{
		sprintf(name,"%s\\%s.SBH",leafname, leafname);
		sbh = (SfxBankType *)fileLoad(name,NULL);
//		ASSERT(sbh);	// to match the release version "fileLoad"
		sfxFixupSampleBankHeader(sbh, spu_addrs[i]);
		sbh_addrs[i] = sbh;
	}

	return sbh;
}

// A bff sound segment is just an SBH, which is assumed to link into a previously loaded & spu-transferred SBB
int soundLinkBFF(SOUND_BFF *sb)
{
	SfxBankType *sbh;
	int i;

	i = soundFindCRCBank(sb->name_crc);
	if(i != -1)
	{
		sbh = sfxFixupSampleBankHeader(&sb->sbh, spu_addrs[i]);
		sbh_addrs[i] = sbh;
	}
	return i;
}


void soundFreeBank(int i, int free)
{
	if(i == -1)
		return;

	if(spu_addrs[i])
	{
		sfxUnloadSampleBank(sbh_addrs[i]);
		spu_addrs[i] = 0;
	}
	if(sbh_addrs[i])
	{
		// Unhook it from the list of samples to test against
		sfxRemoveSampleBank(sbh_addrs[i], free);
		sbh_addrs[i] = 0;
	}
	sbb_names[i] = 0;
}

// Unhook a BFF'd SBH from the list of samples checked for with "findinallbanks"
void soundUnlinkBFF(SOUND_BFF *sb)
{
	int i;
	for(i = 0; i < MAX_SOUND_BANKS; i++)
	{
		if(sbh_addrs[i]==&sb->sbh)
		{
			soundFreeBank(i,0);
		}
	}
}





// =========== Top-End stuff ===============

int sound_GenericBank = -1;
void soundSetup()
{
	int ch;
	unsigned long spu_addr;
	SfxBankType *sbh;
	sfxInitialise(SFX_REVERB_MODE_OFF);
	// CPW: sfxInitialise(SFX_REVERB_MODE_ECHO);

	// CPW: sfxSetReverb(5, 100);

// Testing....
//	sound_GenericBank = soundLoadBodyAndHeader("TEST");
	
	sfxOn();
	sfxStartSound();

/*	for(ch = 0; ch<24; ch++)
	{
		dynamicPanChan[ch].playing = 0;
	}
*/
}

void soundShutdown()
{
// Testing...
//	soundFreeBank(sound_GenericBank);
//	sound_GenericBank = -1;


}


void soundCycle()
{
//	DB("xa %d %d\n",XAData.currSector,XAData.currSectorReal);
// Just a test for now, but we might need to do some sound maintainance
/*
// Testing...
	if(!(loop_ticker & 31))
	{
		SfxSampleType *samp;
		VECTOR posn = {0,0,0};
		samp = sfxFindSampleInAllBanks("smetbrk1");	// ok, lower case finds it... We really should do this once & cache the result, but WTH - this is test code
//				printf("%8x\n",samp);

//		sfxPlaySample(samp, 255, 255, 0);
//		soundPlaySample(samp, 0xffff, 0);
//		soundPlaySamplePanOnly(samp, 0xff, 0, &posn);
		soundPlaySamplePanFade(samp, 0xff, 0, &posn);


	}
*/

// Deal with a single queued XA track...
/*	if(XAData.currXA == NULL && XAData.currXA->status == 0 && music_queued != -1)

	{
		sound_XA_Start(music_queued);
		music_queued = -1;
	} */

	if (music)
	{
		if (ADXT_GetStat(music->adxt) == ADXT_STAT_PLAYEND)
		{
			if (music->loop)
				ADXT_StartFname(music->adxt, music->fileInfo);
			else
				sound_XA_Stop();
			// end-if-else

		} // end-if

	} // end-if
}



// =========== Playing samples ===============

// These started off as Mermaid routines, but got horribly savaged to link into the Glover stereo code
// Volume = 0xllrr
int soundPlaySample(SfxSampleType *sample, short volume, int pitch)
{
	int vl,vr;
	if(!sample)
		return -1;

	VERIFY(sample);

	vl = (volume>>8)&0xff;
	vr = volume&0xff;	
	return sfxPlaySample( sample, vl,vr, pitch*10);
}

//#define ZERO_DIST 8192	// RFW - upped to 32768: 8192 was prototyped on the (rather close-up) fredtest level
#define ZERO_DIST 12000

int soundPlaySamplePanFade(SfxSampleType *sample, int volume, int pitch,VECTOR *at)
{
	int d;
	int vols[2];
	int ch;

	VERIFY(sample);

	if(!soundCalc3DPanFade(at, &vols[0], ZERO_DIST))
		return -1;


/*
	if(!soundCalc3DPanOnly(at, &vols[0]))
		return -1;
*/
	if(volume > 255)	// if "volume" is 0...255, and vols[0],vols[1] are 0...256, then wer're ok
		volume = 255;

	vols[0] = (vols[0] * volume) >>8;
	vols[1] = (vols[1] * volume) >>8;

	volume = (vols[0] << 8) | vols[1];
// CPW: 	DB("Playing sample %p, %04X, %d\n", sample, volume, sample->inSPURam);
	ch = soundPlaySample(sample,volume,pitch);

	if(ch>=0)
	{
		dynamicPanChan[ch].pos = at;
		dynamicPanChan[ch].volume = volume;
	}

	return ch;
}

/*
int soundPlaySamplePanOnly(SfxSampleType *sample, int volume, int pitch,VECTOR *at)
{
	int d;
	int vols[2];

	soundCalc3DPanOnly(at, &vols[0]);

	if(volume > 255)	// if "volume" is 0...255, and vols[0],vols[1] are 0...256, then wer're ok
		volume = 255;

	vols[0] = (vols[0] * volume) >>8;
	vols[1] = (vols[1] * volume) >>8;

	volume = (vols[0] << 8) | vols[1];
	return soundPlaySample(sample,volume,pitch);
}
*/

void soundMaintainPan(void)
{
	char s[32];
	int ch;

	for(ch=0; ch<24; ch++)
	{
			if(sfxGetChannelStatus(ch))
			{	// CPW: channel is playing a sample
				int volume;
				int vols[2];

				volume = dynamicPanChan[ch].volume;

				soundCalc3DPanFade(dynamicPanChan[ch].pos, &vols[0], ZERO_DIST);
				// CPW: soundCalc3DPanOnly(dynamicPanChan[ch].pos, &vols[0]);

				if(volume > 255)	// if "volume" is 0...255, and vols[0],vols[1] are 0...256, then wer're ok
					volume = 255;

				vols[0] = (vols[0] * volume) >>8;
				vols[1] = (vols[1] * volume) >>8;

				DB("Vol = %d, %d\n", vols[0], vols[1]);

				sfxSetChannelVolume(ch, vols[0], vols[1]);

				if((vols[0] + vols[1]) == 0)
				{
					sfxStopChannel(ch);
				}

				s[ch] = '1';
			}
			else
			{
				s[ch] = '0';
			}

	}

	s[ch] = 0;

	// CPW: DB("Chan status = %s\n", s);
}





// =========== X.A.-Playing Veneer ==========


void sound_XA_VolRefresh()
{
	int vol;
	vol = optionData.sound.musicvol * 100 / OPTIONS_MAX_VOLUME;
#ifndef _WINDOWS
  //	SpuSetCommonCDVolume((vol*0x7fff)/100,(vol*0x7fff)/100);
#endif
}

void sound_XA_StartFadeOut(int n_frames)
{
	music_fade_current = n_frames;
	music_fade_time = n_frames;
	music_fade_dirn = -1;
}
void sound_XA_StartFadeIn(int n_frames)
{
	music_fade_current = 0;
	music_fade_time = n_frames;
	music_fade_dirn = 1;
	if(XAData.prevXA)
		XAData.prevXA->vol = 0;
	if(XAData.currXA)
		XAData.currXA->vol = 0;

}
void sound_XAInit()
{
}

void sound_XA_Start(int tune)
{
	int file_no;
	int channel;
	int loop;

	if(tune < 0) tune = -1;	// RFW - I'm getting -ve inputs during the fma

	if(tune & SOUND_XA_NOLOOP)
	{
		loop = 0;
	}
	else
	{
		loop = 1;
	}
	tune &= 127;

	if(music_playing >= 0)
	{
		sound_XA_Stop();
	}

	if(tune == -1)	// This will happen if we try to "resume" from nothing (which can happen)
	{
		music_playing = tune;
		music_to_resume = tune;
		return;
	}

	file_no = tune >> 3;	//xa_tracks_list[menu_choice].file_no;
	channel = tune & 7;		//xa_tracks_list[menu_choice].channel;

//	XAsetStatus(1);	// On golds, this is set according to the return value of cdinit

	
	music = XAgetFileInfo(xa_file_names[file_no]);	// the ";1" is added internally
	if(!music)
	{
		tune = -1;
		music_playing = tune;
		music_to_resume = tune;
		XAsetStatus(0);
	}


	if(!ref_music[file_no].startPos)
	{
		music_playing = -1;
		music_to_resume = -1;
		return;
	}

	music_fader = 256;


	XAstart(1);	// 1 = double speed


	XAplayChannel(music, channel, loop, optionData.sound.musicvol * 100 / OPTIONS_MAX_VOLUME);	// "1" = loop-enable
	music_playing = tune;
	music_to_resume = tune;
	utilPrintf("XA Starting tune %d\n",tune);
}

void sound_XA_Stop()
{
	if(music_playing >= 0)
	{
		XAstop();
		ADXT_Destroy(music->adxt);		// Free the file handle
		FREE(music);		
		music_playing = -1;
		music = NULL;
	}
	music_to_resume = -1;
	music_queued = -1;
}

// As we're mixing 8 and 32 track XA's what Im going to do is put the 8 track ones first in the list so I can work out
// the track number for the 32 track one's
void sound_XA_StartFma(int tune)
{
	int file_no;
	int channel;
	int loop;

	if(tune < 0) tune = -1;	// RFW - I'm getting -ve inputs during the fma

	file_no=NUMBEROF8TRACKXA;
	
	// NOTE fma XA's should never loop
/*	if(tune & SOUND_XA_NOLOOP)
	{
		loop = 0;
	}
	else
	{
		loop = 1;
	}
	tune &= 127;
*/
	loop=0;
	if(music_playing >= 0)
	{
		sound_XA_Stop();
	}

	if(tune == -1)	// This will happen if we try to "resume" from nothing (which can happen)
	{
		music_playing = tune;
		music_to_resume = tune;
		return;
	}

	DB("tune %d\n",tune);
	file_no += tune >> 5;	//xa_tracks_list[menu_choice].file_no;
	channel = tune & 31;		//xa_tracks_list[menu_choice].channel;

	if(!ref_music[file_no].startPos)
	{
		DB("Not playing non-present fma track\n");
		music_playing = -1;
		music_to_resume = -1;
		return;
	}
	DB("XA start Fma was asked to play file num %d channel num %d\n",file_no,channel);

//	XAsetStatus(1);	// On golds, this is set according to the return value of cdinit

	music = XAgetFileInfo(xa_file_names[file_no]);	// the ";1" is added internally
	if(!music)
	{
		tune = -1;
		music_playing = tune;
		music_to_resume = tune;
		XAsetStatus(0);
	}

	music_fader = 256;

	XAstart(1);	// 1 = double speed
	XAplayChannel(music, channel, loop, optionData.sound.musicvol * 100 / OPTIONS_MAX_VOLUME);	// "1" = loop-enable

	music_playing = tune;
	music_to_resume = tune;
	utilPrintf("XA Starting tune %d\n",tune);
}



// After calling here we may ask to resume, or we might switch to another tune
// Thing is, we need to keep track of where the cd was when it stopped....

// Note - doesn't overwrite the "queued" track
void sound_XA_PauseForLoading()
{
	XAstorePrevious();
	XAstop();
	ADXT_Pause(music->adxt, 1);
	//musicPaused=1;
}

void sound_XA_EnsurePaused()
{
	if(music_playing != -1)
	{
		sound_XA_PauseForLoading();
	}
}

void sound_XA_Resume(int tune)
{
// If "tune" is the same as the tune-to-resume, then carry on where we left off
	if(tune == music_to_resume || tune == -1)
	{
		int file_no;
		int channel;

		if(tune==-1)
		{
			tune = music_to_resume;
		}
		if(tune == -1)	// This will happen if we try to "resume" from nothing (which can happen)
		{
			music_playing = tune;
			music_to_resume = tune;
			return;
		}

		if(music_playing >= 0)
		{
			sound_XA_Stop();
		}

		file_no = tune;
		channel = tune & 7;		//xa_tracks_list[menu_choice].channel;

		XAsetStatus(1);
		if (musicPaused)
		{
			ADXT_Pause(music->adxt, 0);
			musicPaused = 0;
		} else {

			music = XAgetFileInfo(xa_file_names[file_no]);	// the ";1" is added internally
			XAstart(1);										// 1 = double speed
			XAplayChannel(music, channel, 1, 90);			// 1 = loop enable, 90 = volume (which should be got from options, really)
		}

		music_playing = tune;
		music_to_resume = tune;

		utilPrintf("XA Resuming tune %d\n",tune);

		XArestartPrevious();
	}
	else
	{
		// else start at the very beginning
		sound_XA_Start(tune);
		music_queued = -1;
	} 
}

void sound_XA_Queue(int tune)
{
	if(XAData.currXA)
	{
		XAData.currXA->loop = 0;
	}
	music_queued = tune;
}



