#include "driver2.h"
#include "sound.h"
#include "camera.h"
#include "xmplay.h"
#include "mission.h"
#include "glaunch.h"
#include "gamesnd.h"
#include "xaplay.h"
#include "players.h"

#define SPU_CHANNEL_COUNT 24
#define LSB_BANK_COUNT 7

typedef struct __LSBDinfo
{
	int addr;
	int memtop;
	int count[LSB_BANK_COUNT];
	int bnktop[LSB_BANK_COUNT];
	int append;
} LSBDinfo;

typedef struct __pauseinfo
{
	u_short pitch[MAX_SFX_CHANNELS];
	u_short voll[MAX_SFX_CHANNELS];
	u_short volr[MAX_SFX_CHANNELS];
	u_short max, lev;
} pauseinfo;

struct SAMPLE_DATA
{
	u_int address, length, loop, samplerate;
};

static pauseinfo pause;
static LSBDinfo lsbTabs;

LONGVECTOR3 dummylong = { 0, 0, 0 };

int bankaddr[2] = { 0 };
int banksize[2] = { 88064, 412672 };

char banks[24] = { 0 };

SAMPLE_DATA samples[LSB_BANK_COUNT][35];
CHANNEL_DATA channels[MAX_SFX_CHANNELS]; // offset 0xDE480

int master_volume = 0;	// why need two?

int gMasterVolume = 0;
int gMusicVolume = -4000;

int Song_ID = -1;
int VABID = -1;

int gSoundMode = 1;		// mono or stereo

int stop_sound_handler = 0;
int sound_paused = 0;
int music_paused = 0;

SpuCommonAttr sound_attr;

int vblcounter = 0; // vblank counter

// [D] [T]
void SoundHandler(void)
{
	int ct;
	int off;

	off = 0;

	if (stop_sound_handler || sound_paused)
		return;

	for (ct = 0; ct < MAX_SFX_CHANNELS; ct++)
	{
		if (channels[ct].loop || channels[ct].time == 0)
			continue;

		if (--channels[ct].time == 0)
			off |= SPU_KEYCH(ct);
	}

	if (off)
		SpuSetKey(0, off);
}

// [D] [T]
void VsyncProc(void)
{
	vblcounter++;

	if (Song_ID != -1)
		XM_Update();

	SoundHandler();
}

// [D] [T]
void InitSound(void)
{
	int i;

	SpuInit();
	SpuInitMalloc(LSB_BANK_COUNT, banks);
	SpuSetMute(1);

	AllocateReverb(SPU_REV_MODE_ROOM | SPU_REV_MODE_STUDIO_A, 16384);

	for (i = 0; i < 2;  i++)
	{
		bankaddr[i] = SpuMalloc(banksize[i]);

		if (bankaddr[i] == -1)
		{
			printError("Failed to SpuMalloc! Exiting...\n");
#ifndef PSX
			exit(-1);
#endif
		}
	}

	ResetSound();

	XM_OnceOffInit(GetVideoMode());	// [A] PAL or NTSC here

	SetMasterVolume(gMasterVolume);
	VSyncCallback(VsyncProc);

	SpuSetMute(0);
}

// [D] [T]
void ClearChannelFields(int channel)
{
	CHANNEL_DATA* chan;

	chan = &channels[channel];
	
	chan->loop = 0;
	chan->locked = 0;
	chan->time = 0;
	chan->samplerate = 0;
	chan->srcvolume = -10000;
	chan->srcpitch = 4096;
	chan->volumeScale = 4096;
	chan->dopplerScale = 4096;
	chan->srcposition = NULL;
	chan->position.vx = 0;
	chan->position.vy = 0;
	chan->position.vz = 0;
	chan->srcvelocity = (LONGVECTOR3*)dummylong;
	chan->player = 0;
}

// [D] [T]
void ResetSound(void)
{
	CHANNEL_DATA* chan;
	int ct;

	stop_sound_handler = 1;
	sound_paused = 0;
	music_paused = 0;

	sound_attr.mvol.left = 16383;
	sound_attr.mvol.right = 16383;
	sound_attr.mask = SPU_COMMON_MVOLL | SPU_COMMON_MVOLR;

	SpuSetCommonAttr(&sound_attr);

	for (ct = 0; ct < MAX_SFX_CHANNELS; ct++) 
	{
		chan = &channels[ct];
		memset((u_char*)chan, 0, sizeof(CHANNEL_DATA));

		chan->attr.volmode.left = 0;
		chan->attr.volmode.right = 0;
		chan->attr.voice = SPU_KEYCH(ct);

		ClearChannelFields(ct);
	}

	for (ct = 0; ct < MAX_SFX_CHANNELS; ct++) 
	{
		SpuSetVoiceRR(ct, 6);
		SpuSetVoiceAR(ct, 35);
	}

	stop_sound_handler = 0;
}

// [D] [T]
void SetReverbState(int on)
{
	if (on == 0)
		SpuSetReverbVoice(0, SPU_ALLCH);
	else
		SpuSetReverbVoice(1, SPU_ALLCH);
}

// [D] [T]
void SetReverbInGameState(int on)
{
	int cl;

	cl = 0xffff;

	gSpeechQueue.reverb = on;

	if (on && gSpeechQueue.is_playing)
		cl &= ~SPU_KEYCH(gSpeechQueue.chan);

	SpuSetReverbVoice(on, cl);
}

// [D] [T]
int SetReverbChannelState(int ch, int on)
{
	int cl;
	int prev;

	cl = SPU_KEYCH(ch);

	prev = SpuGetReverbVoice();

	SpuSetReverbVoice(on, cl);

	return (prev & cl) != 0;
}

// [D] [T]
void SetMasterVolume(int vol)
{
	int vl = MAX(0, 10000 + vol);

	master_volume = vl + vl / 2 + vl / 8 + vl / 128;
	gMasterVolume = vol;
}

// [D] [T]
void SetXMVolume(int volume)
{
	int vol;

	vol = MIN(10000, MAX(0, 10000 + volume));

	gMusicVolume = volume;

	if (Song_ID == -1)
		return;

	XM_SetMasterVol(Song_ID, (vol >> 7) + (vol >> 8));
}

// [D] [T]
char SetPlayerOwnsChannel(int chan, char player)
{
	char oldPlayer;

	oldPlayer = channels[chan].player;
	channels[chan].player = player;

	return oldPlayer;
}

// [D] [T]
int CalculateVolume(int channel)
{
	int distance;
	int fade;
	int volume;

	VECTOR *srcPos;
	VECTOR *plPos;
	VECTOR ofse;
	CHANNEL_DATA* chan;

	chan = &channels[channel];

	volume = 10000 + chan->srcvolume;

	if (NumPlayers > 1 && NoPlayerControl == 0)
	{
		volume = (volume * 3 / 4);
	}

	srcPos = chan->srcposition;

	if (srcPos != NULL)
	{
		plPos = &player[chan->player].cameraPos;

		ofse.vx = srcPos->vx - plPos->vx;
		ofse.vy = srcPos->vy + plPos->vy;
		ofse.vz = srcPos->vz - plPos->vz;

		gte_ldlvl(&ofse);
		gte_sqr0();
		gte_stlvnl(&ofse);

		distance = SquareRoot0(ofse.vx + ofse.vy + ofse.vz);

		if (distance < 1024)
			distance = 1024;

		fade = 0xbd0000 / (distance + 2000);

		distance = MAX(0, MIN(4096, distance - 16000));

		if (distance != 0)
			fade *= (4096 - distance) / 4096;

		volume = (fade * volume) / 4096;
	}

	return volume - 10000;
}


int gSurround = 0;

// [D] [T]
void UpdateVolumeAttributesS(int channel, int proximity)
{
	int volume;
	int vol;
	int player_id;
	VECTOR *pos;
	VECTOR *cam_pos;
	int dist;
	int cam_ang, ang;
	int damp;
	CHANNEL_DATA* chan;

	chan = &channels[channel];
	player_id = chan->player;
	pos = chan->srcposition;
	cam_pos = &player[player_id].cameraPos;
	cam_ang = player[player_id].snd_cam_ang;

	volume = CalculateVolume(channel);

	vol = MAX(0, 10000 + volume);

	if (camera_change == 1 || old_camera_change == 1)
		vol = 0;

	vol = (vol + vol / 2 + vol / 8 + vol / 128) * master_volume / 16384;

	chan->attr.volume.left = vol;
	chan->attr.volume.right = gSurround ? -vol : vol;	// HMM: why like that?

	if (vol == 0)
		return;

	if (pos == NULL)
		return;

	if (pos->vz == cam_pos->vz && cam_pos->vx == pos->vx)
		return;

	// HACK: player car sounds are not affected with stereo
	if (player[player_id].cameraView == 2)
	{
		if (channel == player[player_id].skidding.chan || channel == player[player_id].wheelnoise.chan)
			return;

		if (player_id == 0)
		{
			if (channel == 0 || channel == 1 || channel == 2)
				return;
		}
		else if (player_id == 1)
		{
			if (channel == 3 || channel == 4 || channel == 5)
				return;
		}
	}

	// calc angle relative to camera angle and source position
	ang = 2048 - (((-ratan2(pos->vz - cam_pos->vz, cam_pos->vx - pos->vx) + 4096 + 3072) % 4096 - (cam_ang - 4096)) % 4096);

	// wtf?
	if (ABS(ang) >= 1024)
	{
		int tmp;
		tmp = 2048 - ABS(ang);

		if (ang < 1)
			ang = -tmp;
		else
			ang = tmp;
	}

	if (proximity > 0)
	{
		int dx, dz;

		dx = cam_pos->vx - pos->vx;
		dz = cam_pos->vz - pos->vz;

		dist = jsqrt(dx * dx + dz * dz);

		if (dist >= 12000)
			proximity = 0;
	}

	// calculate spatial damp for specific side
	if (ang > 0)
	{
		damp = ((vol * 9 / 10) * ang) / 1024;

		if (proximity > 0)
			damp = damp * dist / 12000;

		chan->attr.volume.left = MAX(0, vol - damp);
	}
	else if (ang < 0)
	{
		damp = ((vol * 9 / 10) * -ang) / 1024;

		if (proximity > 0)
			damp = damp * dist / 12000;

		chan->attr.volume.right = gSurround ? -MAX(0, vol - damp) : MAX(0, vol - damp);
	}
}

// [D] [T]
void UpdateVolumeAttributesM(int channel)
{
	int volume;
	int vol;

	volume = CalculateVolume(channel);

	vol = MAX(0, 10000 + volume);

	if (camera_change == 1 || old_camera_change == 1)
		vol = 0;

	vol = (vol + vol / 2 + vol / 8 + vol / 128) * master_volume / 16384;

	channels[channel].attr.volume.left = vol;
	channels[channel].attr.volume.right = vol;
}

// [D] [T]
int CompleteSoundSetup(int channel, int bank, int sample, int pitch, int proximity)
{
	int bpf;
	int rate;
	CHANNEL_DATA* chan;

	rate = samples[bank][sample].samplerate * pitch;
	bpf = (rate / 4096) / 50;

	if (bpf == 0) 
	{
		channel = -1;
	}
	else
	{
		chan = &channels[channel];
		
		if (gSoundMode == 1 && proximity != -1) 
			UpdateVolumeAttributesS(channel, proximity);
		else
			UpdateVolumeAttributesM(channel);

		stop_sound_handler = 1;

		chan->attr.mask =  SPU_VOICE_VOLL | SPU_VOICE_VOLR | SPU_VOICE_VOLMODEL | SPU_VOICE_VOLMODER | SPU_VOICE_PITCH | SPU_VOICE_WDSA;
		chan->attr.addr = samples[bank][sample].address;
		chan->attr.pitch = MIN(rate / 44100, 16383);
		chan->time = (samples[bank][sample].length / bpf) * 2 + 2;
		chan->loop = samples[bank][sample].loop;

		chan->samplerate = samples[bank][sample].samplerate;

		if (sound_paused != 0)
		{
			chan->attr.volume.left = 0;
			chan->attr.volume.right = 0;
		}

		SpuSetVoiceAttr(&chan->attr);
		SpuSetKey(1, chan->attr.voice);

		stop_sound_handler = 0;
	}

	return channel;
}


// [D] [T]
void ComputeDoppler(CHANNEL_DATA *ch)
{
	int dist;
	int seperationrate;

	VECTOR *srcPos;
	long* srcVel; // LONGVECTOR3

	PLAYER *pl;
	int dx, dy, dz;

	srcPos = ch->srcposition;

	if (srcPos == NULL)
	{
		ch->dopplerScale = ONE;
		return;
	}

	srcVel = (long*)ch->srcvelocity;

	pl = &player[ch->player];

	dx = srcPos->vx - pl->cameraPos.vx;
	dy = srcPos->vy + pl->cameraPos.vy;
	dz = srcPos->vz - pl->cameraPos.vz;

	dist = jsqrt(dx * dx + dy * dy + dz * dz);

	dx = (srcPos->vx - pl->cameraPos.vx) + FIXEDH(srcVel[0] - pl->camera_vel[0]);
	dy = (srcPos->vy + pl->cameraPos.vy) + FIXEDH(srcVel[1] - pl->camera_vel[1]);
	dz = (srcPos->vz - pl->cameraPos.vz) + FIXEDH(srcVel[2] - pl->camera_vel[2]);

	seperationrate = jsqrt(dx * dx + dy * dy + dz * dz);

	ch->dopplerScale = (seperationrate - dist) * -3 + ONE;
	ch->cameradist = dist;

	if (ch->dopplerScale << 16 < 0)
		ch->dopplerScale = 0;
}


// [D] [T]
int StartSound(int channel, int bank, int sample, int volume, int pitch)
{
	if (channel < 0)
		channel = GetFreeChannel();

	if (channel < 0 || channel >= MAX_SFX_CHANNELS)	// [A]
		return -1;

	channels[channel].srcposition = NULL;
	channels[channel].srcvolume = volume;

	return CompleteSoundSetup(channel, bank, sample, pitch, 0);
}

// [D] [T]
int Start3DTrackingSound(int channel, int bank, int sample, VECTOR *position, LONGVECTOR3* velocity)
{
	CHANNEL_DATA* chan;
	
	if (channel < 0)
		channel = GetFreeChannel();

	if (channel < 0 || channel >= MAX_SFX_CHANNELS)	// [A]
		return -1;

	chan = &channels[channel];

	chan->srcposition = position;
	chan->srcvelocity = velocity ? velocity : (LONGVECTOR3*)dummylong;
	chan->srcpitch = 4096;
	chan->srcvolume = 0;

	channel = CompleteSoundSetup(channel, bank, sample, 4096, 0);

	ComputeDoppler(&channels[channel]);
	SetChannelPitch(channel, 4096);

	return channel;
}

// [D] [T]
int Start3DSoundVolPitch(int channel, int bank, int sample, int x, int y, int z, int volume, int pitch)
{
	CHANNEL_DATA* chan;

	if (channel < 0)
		channel = GetFreeChannel();

	if (channel < 0 || channel >= MAX_SFX_CHANNELS)	// [A]
		return -1;
	chan = &channels[channel];

	chan->srcposition = &chan->position;
	chan->srcvelocity = (LONGVECTOR3*)dummylong;
	chan->position.vx = x;
	chan->position.vy = y;
	chan->position.vz = z;
	chan->srcvolume = volume;
	chan->srcpitch = pitch;

	channel = CompleteSoundSetup(channel, bank, sample, pitch, 0);

	ComputeDoppler(&channels[channel]);
	SetChannelPitch(channel, pitch);

	return channel;

}

// [D] [T]
void SetChannelPitch(int channel, int pitch)
{
	CHANNEL_DATA* chan;
	int rate;

	if (channel < 0 || channel >= MAX_SFX_CHANNELS)	// [A]
		return;

	if (sound_paused)
		return;
	
	chan = &channels[channel];

	if (chan->time != 0 && pitch > 128)
	{
		chan->srcpitch = pitch;

		rate = (chan->samplerate * ((chan->dopplerScale * pitch) / 4096)) / 44100;

		chan->attr.mask = SPU_VOICE_PITCH;
		chan->attr.pitch = MIN(rate, 16383);

		SpuSetVoiceAttr(&chan->attr);
	}
}

// [D] [T]
void SetChannelVolume(int channel, int volume, int proximity)
{
	CHANNEL_DATA* chan;

	if (channel < 0 || channel >= MAX_SFX_CHANNELS)	// [A]
		return;

	chan = &channels[channel];

	if (sound_paused == 0 && chan->time != 0)
	{
		chan->srcvolume = volume;

		if (gSoundMode == 1)
			UpdateVolumeAttributesS(channel, proximity);
		else
			UpdateVolumeAttributesM(channel);

		chan->attr.mask = SPU_VOICE_VOLL | SPU_VOICE_VOLR | SPU_VOICE_VOLMODEL | SPU_VOICE_VOLMODER;
		SpuSetVoiceAttr(&chan->attr);
	}
}

// [D] [T]
void SetChannelPosition3(int channel, VECTOR *position, LONGVECTOR3* velocity, int volume, int pitch, int proximity)
{
	CHANNEL_DATA* chan;

	if (channel < 0 || channel >= MAX_SFX_CHANNELS)
		return;

	if (camera_change == 1 || old_camera_change == 1 || sound_paused != 0)
		return;

	chan = &channels[channel];
		
	chan->srcposition = position;
	chan->srcvelocity = velocity ? velocity : (LONGVECTOR3*)dummylong;
	chan->srcvolume = volume;

	if (gSoundMode == 1)
		UpdateVolumeAttributesS(channel, proximity);
	else 
		UpdateVolumeAttributesM(channel);

	chan->attr.mask = SPU_VOICE_VOLL | SPU_VOICE_VOLR | SPU_VOICE_VOLMODEL | SPU_VOICE_VOLMODER;

	SpuSetVoiceAttr(&chan->attr);

	ComputeDoppler(&channels[channel]);

	SetChannelPitch(channel, pitch);
}


static int music_pause_max;

// [D] [T]
void PauseXM(void)
{
	int fade;

	if (music_paused != 0)
		return;

	music_pause_max = 0;
	fade = 96;

	while (fade < music_pause_max)	// [A] WTF?
		fade += 96;

	if (Song_ID != -1)	// [A] bug fix
		XM_Pause(Song_ID);

	music_paused = 1;
}


// [D] [T]
void PauseSFX(void)
{
	int i, fade;

	if (sound_paused)
		return;

	pause.max = 0;

	// set the faded volume
	for (i = 0; i < MAX_SFX_CHANNELS; i++)
	{
		int vl;
		SpuGetVoiceVolume(i, (short*)&pause.voll[i], (short*)&pause.volr[i]);

		vl = MAX(pause.voll[i], pause.volr[i]);

		if (pause.max < vl) 
			pause.max = vl;

		pause.lev = gMasterVolume + 10000;
	}

	fade = 0;

	// fade out each voice
	while (fade < pause.max)
	{
		for (i = 0; i < MAX_SFX_CHANNELS; i++)
		{
			SpuSetVoiceVolume(i, MAX(0, pause.voll[i] - fade), MAX(0, pause.volr[i] - fade));
		}

		fade += 96;
	}

	// pause by zeroing pitch
	for (i = 0; i < MAX_SFX_CHANNELS; i++)
	{
		SpuGetVoicePitch(i, &pause.pitch[i]);
		SpuSetVoicePitch(i, 0);
	}

	sound_paused = 1;
}

// [D] [T]
void PauseSound(void)
{
	SetReverbInGameState(0);
	PauseXM();
	PauseSFX();
	PauseXA();
}

// [D] [T]
void UnPauseXM(void)
{
	int fade;
	
	if (Song_ID == -1)
		return;

	if (music_paused == 0)
		return;

	fade = 0;

	do {
		fade += 96;
	} while (fade < music_pause_max);	// [A] WTF?

	XM_Restart(Song_ID);
	music_paused = 0;
}

// [D] [T]
void UnPauseSFX(void)
{
	int i, fade, vol;

	if (!sound_paused)
		return;

	// restore pitch
	for (i = 0; i < MAX_SFX_CHANNELS; i++)
	{
		SpuSetVoicePitch(i, pause.pitch[i]);
	}

	vol = 10000 + gMasterVolume;

	// restore volume levels
	if (pause.lev != 0 && pause.lev != vol)
	{
		for (i = 0; i < MAX_SFX_CHANNELS; i++)
		{
			pause.voll[i] = pause.voll[i] * vol / pause.lev;
			pause.volr[i] = pause.volr[i] * vol / pause.lev;
		}
	}

	fade = 0;

	// fade out each voice
	while (fade < pause.max)
	{
		for (i = 0; i < MAX_SFX_CHANNELS; i++)
		{
			SpuSetVoiceVolume(i, MIN(fade, pause.voll[i]), MIN(fade, pause.volr[i]));
		}

		fade += 96;
	}
	
	sound_paused = 0;
}

// [D] [T]
void UnPauseSound(void)
{
	UnPauseXM();
	UnPauseSFX();
	ResumeXA();
}

//  [D] [T]
void StopChannel(int channel)
{
	u_char lock;
	int vsync;

	if (channel < 0 || channel >= MAX_SFX_CHANNELS)	
		return;

	lock = channels[channel].locked;
	vsync = VSync(-1);

	SpuSetKey(0, channels[channel].attr.voice);

	// PSX SPU keyoff sometimes does not work for first time due to it's nature
	do {
		if (SpuGetKeyStatus(channels[channel].attr.voice) == 0)
			break;
#ifdef __EMSCRIPTEN__
		emscripten_sleep(0);
#endif
	} while (VSync(-1) - vsync < 8);

	ClearChannelFields(channel);
	
	channels[channel].locked = lock;
}

// [D] [T]
void StopAllChannels(void)
{
	int ct;

	for (ct = 0; ct < MAX_SFX_CHANNELS; ct++)
	{
		StopChannel(ct);
		VSync(0);
	}
}
	
// [D] [T]
void LockChannel(int channel)
{
	if (channel < 0 || channel >= MAX_SFX_CHANNELS)	// [A]
		return;

	channels[channel].locked = 1;
}

// [D] [T]
void UnlockChannel(int c)
{
	if (c < 0 || c >= MAX_SFX_CHANNELS)	// [A]
		return;

	channels[c].locked = 0;
}

// [D] [T]
int LoadSoundBank(char *address, int length, int bank)
{
	int slength;
	int spuaddress;
	int num_samples;
	int ct;

	spuaddress = bankaddr[bank];

	num_samples = *(int *)address;
	slength = num_samples * sizeof(SAMPLE_DATA) + sizeof(int);

	// copy sample info
	memcpy((u_char*)samples[bank], (u_char*)(address + sizeof(int)), num_samples * sizeof(SAMPLE_DATA));

	// transfer sample data
	SpuSetTransferMode(SPU_TRANSFER_BY_DMA);

	SpuSetTransferStartAddr(spuaddress);
	SpuWrite((unsigned char*)address + slength, length - slength);
	
	SpuIsTransferCompleted(SPU_TRANSFER_WAIT);

	for(ct = 0; ct < num_samples; ct++)
	{
		samples[bank][ct].address += spuaddress;
	}

	return num_samples;
}

// [D] [T]
void UpdateXMSamples(int num_samps)
{
	int i;
	for (i = 0; i < num_samps; i++)
		XM_SetVAGAddress(VABID, i, samples[0][i].address);
}

// [D] [T]
int LoadSoundBankDynamic(char *address, int length, int dbank)
{
	int i;
	int slength;
	int num_samples;

	if (address == NULL) 
	{
		switch (length)
		{
			case 0:
				if (dbank == 0)
				{
					lsbTabs.append = 0;
					printInfo("*---LSBD(): successful init---*\n");
				}
				break;
			case 1:
				lsbTabs.memtop = lsbTabs.addr;
				printInfo("*---LSBD(): saved memtop---*\n");
				break;
			case 2:
				lsbTabs.addr = lsbTabs.memtop;
				printInfo("*---LSBD(): goneto memtop---*\n");
				break;
			case 3:
				lsbTabs.bnktop[dbank] = lsbTabs.count[dbank];
				printInfo("*---LSBD(): saved banktop %d---*\n", lsbTabs.bnktop[dbank]);
				break;
			case 4:
				lsbTabs.count[dbank] = lsbTabs.bnktop[dbank];
				printInfo("*---LSBD(): goneto banktop %d---*\n", lsbTabs.count[dbank]);
				break;
		}

		return 0;
	}

	// init bank samples
	if (lsbTabs.append == 0) 
	{
		lsbTabs.addr = bankaddr[1];
		
		for(i = 0; i < 7; i++)
			lsbTabs.count[i] = 0;
		
		lsbTabs.append = 1;
	}

	num_samples = *(int *)address;
	slength = num_samples * sizeof(SAMPLE_DATA) + sizeof(int);

	memcpy((u_char*)(samples[dbank] + lsbTabs.count[dbank]), (u_char*)(address + sizeof(int)), num_samples * sizeof(SAMPLE_DATA));

	SpuSetTransferMode(SPU_TRANSFER_BY_DMA);

	SpuSetTransferStartAddr(lsbTabs.addr);
	SpuWrite((unsigned char*)address + slength, length - slength);

	SpuIsTransferCompleted(SPU_TRANSFER_WAIT);

	// set proper address for samples
	for(i = 0; i < num_samples; i++)
	{
		samples[dbank][lsbTabs.count[dbank]].address += lsbTabs.addr;
		lsbTabs.count[dbank]++;
	}
	
	lsbTabs.addr += length - slength;

	return num_samples;
}

// [D] [T]
void FreeXM(void)
{
	if (Song_ID == -1)
		return;

	XM_Exit();
	XM_FreeAllSongIDs();
	XM_CloseVAB2(VABID);

	Song_ID = -1;
	VABID = -1;
}

// [D] [T]
void StartXM(int reverb)
{
	int ct;

	if (Song_ID == -1)
		return;
	
	XM_PlayStart(Song_ID, -1);
	SpuSetReverb(1);

	for(ct = MAX_SFX_CHANNELS; ct < SPU_CHANNEL_COUNT; ct++)
	{
		SpuSetReverbVoice(reverb, SPU_VOICECH(ct));
	}
}

// [D] [T]
void StopXM(void)
{
	XM_PlayStop(Song_ID);
}

// [D] [T]
int GetFreeChannel(int force)
{
	int channel;
	int it;
	int least;

	char status[SPU_CHANNEL_COUNT];
	
	SpuGetAllKeysStatus(status);

	channel = 0;

	// find free channel
	while (channel < MAX_SFX_CHANNELS && (channels[channel].locked || status[channel] != SPU_OFF && status[channel] != SPU_RESET))
	{
		channel++;
	}

	if (channel < MAX_SFX_CHANNELS)
		return channel;

	if (!force)
		return -1;
	
	// if not found free channels - free already playing one
	channel = -1;
	least = 0;
	
	for (it = 0; it < MAX_SFX_CHANNELS; it++)
	{
		if (channels[it].locked || channels[it].loop)
			continue;
		
		if (channel == -1 || channels[it].time < least)
		{
			channel = it;
			least = channels[it].time;
		}
	}

	if (channel != -1) 
		StopChannel(channel);

	return channel;
}

// [D] [T]
void AllocateReverb(long mode, long depth)
{
	SpuReverbAttr r_attr;

	if (SpuReserveReverbWorkArea(1) == 1)
	{
		r_attr.mode = mode | SPU_REV_MODE_CLEAR_WA;
		r_attr.mask = SPU_REV_MODE | SPU_REV_DEPTHL | SPU_REV_DEPTHR;
		r_attr.depth.left = depth;
		r_attr.depth.right = r_attr.depth.left;

		SpuSetReverbModeParam(&r_attr);
		SpuSetReverbDepth(&r_attr);
		SpuSetReverb(1);
	}
}

// [D] [T]
int FESound(int sample)
{
	int channel;
	channel = GetFreeChannel();

	if (channel < 0 || channel >= MAX_SFX_CHANNELS)	// [A]
		return -1;

	channels[channel].srcposition = NULL;
	channels[channel].srcvelocity = (LONGVECTOR3*)dummylong;
	channels[channel].srcvolume = 4096;

	return CompleteSoundSetup(channel, 1, sample, 2048, -1);
}

// [D] [T]
int jsqrt(u_int a)
{
	int b0;
	int b1;

	if (a < 2)
		return a;

	b0 = a >> 1;

	if (a < 0x40000000)
	{
		if (b0 >= 0x8000)
			b0 = 0x7fff;
	}
	else
	{
		if (b0 >= 0x10000)
			b0 = 0xffff;
	}

	do
	{
		b1 = b0 + a / b0 >> 1;

		if (ABS(b0 - b1) <= 100)
			break;

		b0 = b1;
	} while (true);

	return b1;
}


