// BASS 3D Test, copyright (c) 1999-2004 Ian Luck.

#include <windows.h>
#include <commctrl.h>
#include <math.h>
#include "bass.h"
#include "3dtest.h"

HWND win=NULL;

/* channel (sample/music) info structure */
typedef struct {
	DWORD channel;			// the channel
	BASS_3DVECTOR pos,vel;	// position,velocity
	int dir;				// direction of the channel
} Channel;

Channel *chans=NULL;		// the channels
int chanc=0,chan=-1;		// number of channels, current channel
HBRUSH brush1,brush2;	// brushes

#define TIMERPERIOD	50		// timer period (ms)
#define MAXDIST		50		// maximum distance of the channels (m)
#define SPEED		12		// speed of the channels' movement (m/s)

/* Display error dialogs */
void Error(char *es)
{
	char mes[200];
	sprintf(mes,"%s\n(error code: %d)",es,BASS_ErrorGetCode());
	MessageBox(win,mes,"Error",0);
}

/* Messaging macros */
#define ITEM(id) GetDlgItem(win,id)
#define MESS(id,m,w,l) SendDlgItemMessage(win,id,m,(WPARAM)w,(LPARAM)l)
#define LM(m,w,l) MESS(ID_LIST,m,w,l)
#define EM(m,w,l) MESS(ID_EAX,m,w,l)

void CALLBACK Update(HWND win, UINT m, UINT i, DWORD t)
{
	HDC dc;
    RECT r;
	int c,x,y,cx,cy;
	int save;
	HPEN pen;

	win=ITEM(ID_DISPLAY);
	dc=GetDC(win);
	save=SaveDC(dc);
	GetClientRect(win,&r);
	cx=r.right/2;
	cy=r.bottom/2;

	/* Draw center circle */
	SelectObject(dc,GetStockObject(GRAY_BRUSH));
	Ellipse(dc,cx-4,cy-4,cx+4,cy+4);

	pen=CreatePen(PS_SOLID,2,GetSysColor(COLOR_BTNFACE));
	SelectObject(dc,pen);

	for (c=0;c<chanc;c++) {
		/* If the channel's playing then update it's position */
		if (BASS_ChannelIsActive(chans[c].channel)==BASS_ACTIVE_PLAYING) {
			/* Check if channel has reached the max distance */
			if (chans[c].pos.z>=MAXDIST || chans[c].pos.z<=-MAXDIST)
				chans[c].vel.z=-chans[c].vel.z;
			if (chans[c].pos.x>=MAXDIST || chans[c].pos.x<=-MAXDIST)
				chans[c].vel.x=-chans[c].vel.x;
			/* Update channel position */
			chans[c].pos.z+=chans[c].vel.z*TIMERPERIOD/1000;
			chans[c].pos.x+=chans[c].vel.x*TIMERPERIOD/1000;
			BASS_ChannelSet3DPosition(chans[c].channel,&chans[c].pos,NULL,&chans[c].vel);
		}
		/* Draw the channel position indicator */
		x=cx+(int)((cx-7)*chans[c].pos.x/MAXDIST);
		y=cy-(int)((cy-7)*chans[c].pos.z/MAXDIST);
		if (chan==c)
			SelectObject(dc,brush1);
		else
			SelectObject(dc,brush2);
		Ellipse(dc,x-6,y-6,x+6,y+6);
	}
	/* Apply the 3D changes */
	BASS_Apply3D();

	RestoreDC(dc,save);
	DeleteObject(pen);
	ReleaseDC(win,dc);
}

/* Update the button states */
void UpdateButtons()
{
	int a;
	EnableWindow(ITEM(ID_REMOVE),chan==-1?FALSE:TRUE);
	EnableWindow(ITEM(ID_PLAY),chan==-1?FALSE:TRUE);
	EnableWindow(ITEM(ID_STOP),chan==-1?FALSE:TRUE);
	for (a=0;a<5;a++)
		EnableWindow(ITEM(ID_LEFT+a),chan==-1?FALSE:TRUE);
	if (chan!=-1) {
		MESS(ID_LEFT,BM_SETCHECK,chans[chan].dir==ID_LEFT?BST_CHECKED:BST_UNCHECKED,0);
		MESS(ID_RIGHT,BM_SETCHECK,chans[chan].dir==ID_RIGHT?BST_CHECKED:BST_UNCHECKED,0);
		MESS(ID_FRONT,BM_SETCHECK,chans[chan].dir==ID_FRONT?BST_CHECKED:BST_UNCHECKED,0);
		MESS(ID_BEHIND,BM_SETCHECK,chans[chan].dir==ID_BEHIND?BST_CHECKED:BST_UNCHECKED,0);
		MESS(ID_NONE,BM_SETCHECK,!chans[chan].dir?BST_CHECKED:BST_UNCHECKED,0);
	}
}

BOOL CALLBACK dialogproc(HWND h,UINT m,WPARAM w,LPARAM l)
{
	static OPENFILENAME ofn;
	static char path[MAX_PATH];

	switch (m) {
		case WM_COMMAND:
			switch (LOWORD(w)) {
				case ID_EAX:
					/* Change the EAX environment */
					if (HIWORD(w)==CBN_SELCHANGE) {
						int s=EM(CB_GETCURSEL,0,0);
						if (!s)
							BASS_SetEAXParameters(-1,0,-1,-1); // off (volume=0)
						else
							BASS_SetEAXParameters(s-1,-1,-1,-1);
					}
					break;
				case ID_LIST:
					/* Change the selected channel */
					if (HIWORD(w)!=LBN_SELCHANGE) break;
					chan=LM(LB_GETCURSEL,0,0);
					if (chan==LB_ERR) chan=-1;
					UpdateButtons();
					break;
				case ID_LEFT:
					chans[chan].dir=ID_LEFT;
					/* Make the channel move past the left of you */
					/* Set speed in m/s */
					chans[chan].vel.z=SPEED;
					chans[chan].vel.x=0;
					/* Set positon to the left */
					chans[chan].pos.x=-6;
					/* Reset display */
					InvalidateRect(GetDlgItem(h,ID_DISPLAY),NULL,TRUE);
					break;
				case ID_RIGHT:
					chans[chan].dir=ID_RIGHT;
					/* Make the channel move past the right of you */
					chans[chan].vel.z=SPEED;
					chans[chan].vel.x=0;
					/* Set positon to the right */
					chans[chan].pos.x=6;
					InvalidateRect(GetDlgItem(h,ID_DISPLAY),NULL,TRUE);
					break;
				case ID_FRONT:
					chans[chan].dir=ID_FRONT;
					/* Make the channel move past the front of you */
					chans[chan].vel.x=SPEED;
					chans[chan].vel.z=0;
					/* Set positon to in front */
					chans[chan].pos.z=6;
					InvalidateRect(GetDlgItem(h,ID_DISPLAY),NULL,TRUE);
					break;
				case ID_BEHIND:
					chans[chan].dir=ID_BEHIND;
					/* Make the channel move past the back of you */
					chans[chan].vel.x=SPEED;
					chans[chan].vel.z=0;
					/* Set positon to behind */
					chans[chan].pos.z=-6;
					InvalidateRect(GetDlgItem(h,ID_DISPLAY),NULL,TRUE);
					break;
				case ID_NONE:
					chans[chan].dir=0;
					/* Make the channel stop moving */
					chans[chan].vel.x=chans[chan].vel.z=0;
					break;
				case ID_ADD:
					{
						char file[MAX_PATH]="";
						DWORD newchan;
						ofn.lpstrFile=file;
						if (GetOpenFileName(&ofn)) {
							memcpy(path,file,ofn.nFileOffset);
							path[ofn.nFileOffset-1]=0;
							/* Load a music or sample from "file" */
							if ((newchan=BASS_MusicLoad(FALSE,file,0,0,BASS_MUSIC_RAMP|BASS_MUSIC_LOOP|BASS_MUSIC_3D,0))
								|| (newchan=BASS_SampleLoad(FALSE,file,0,0,1,BASS_SAMPLE_LOOP|BASS_SAMPLE_3D))) {
								Channel *c;
								chanc++;
								chans=(Channel*)realloc((void*)chans,chanc*sizeof(Channel));
								c=chans+chanc-1;
								memset(c,0,sizeof(Channel));
								c->channel=newchan;
								BASS_SampleGetChannel(newchan,FALSE); // initialize sample channel
								LM(LB_ADDSTRING,0,file);
							} else
								Error("Can't load file (note samples must be mono)");
						}
					}
					break;
				case ID_REMOVE:
					{
						Channel *c=chans+chan;
						BASS_SampleFree(c->channel);
						BASS_MusicFree(c->channel);
						memcpy(c,c+1,(chanc-chan-1)*sizeof(Channel));
						chanc--;
						LM(LB_DELETESTRING,chan,0);
						chan=-1;
						UpdateButtons();
						InvalidateRect(GetDlgItem(h,ID_DISPLAY),NULL,TRUE);
					}
					break;
				case ID_PLAY:
					BASS_ChannelPlay(chans[chan].channel,FALSE);
					break;
				case ID_STOP:
					BASS_ChannelPause(chans[chan].channel);
					break;
				case IDCANCEL:
					DestroyWindow(h);
					break;
			}
			break;

		case WM_HSCROLL:
			if (l) {
				int pos=SendMessage((HWND)l,TBM_GETPOS,0,0);
				switch (GetDlgCtrlID((HWND)l)) {
					case ID_ROLLOFF: // change the rolloff factor
						BASS_Set3DFactors(-1,pow(2,(pos-10)/5.0),-1);
						break;
					case ID_DOPPLER: // change the doppler factor
						BASS_Set3DFactors(-1,-1,pow(2,(pos-10)/5.0));
						break;
				}
			}
			break;

		case WM_INITDIALOG:
			win=h;
			brush1=CreateSolidBrush(0xff);
			brush2=CreateSolidBrush(0);

			EM(CB_ADDSTRING,0,"Off");
			EM(CB_ADDSTRING,0,"Generic");
			EM(CB_ADDSTRING,0,"Padded Cell");
			EM(CB_ADDSTRING,0,"Room");
			EM(CB_ADDSTRING,0,"Bathroom");
			EM(CB_ADDSTRING,0,"Living Room");
			EM(CB_ADDSTRING,0,"Stone Room");
			EM(CB_ADDSTRING,0,"Auditorium");
			EM(CB_ADDSTRING,0,"Concert Hall");
			EM(CB_ADDSTRING,0,"Cave");
			EM(CB_ADDSTRING,0,"Arena");
			EM(CB_ADDSTRING,0,"Hangar");
			EM(CB_ADDSTRING,0,"Carpeted Hallway");
			EM(CB_ADDSTRING,0,"Hallway");
			EM(CB_ADDSTRING,0,"Stone Corridor");
			EM(CB_ADDSTRING,0,"Alley");
			EM(CB_ADDSTRING,0,"Forest");
			EM(CB_ADDSTRING,0,"City");
			EM(CB_ADDSTRING,0,"Mountains");
			EM(CB_ADDSTRING,0,"Quarry");
			EM(CB_ADDSTRING,0,"Plain");
			EM(CB_ADDSTRING,0,"Parking Lot");
			EM(CB_ADDSTRING,0,"Sewer Pipe");
			EM(CB_ADDSTRING,0,"Under Water");
			EM(CB_ADDSTRING,0,"Drugged");
			EM(CB_ADDSTRING,0,"Dizzy");
			EM(CB_ADDSTRING,0,"Psychotic");
			EM(CB_SETCURSEL,0,0);

			MESS(ID_ROLLOFF,TBM_SETRANGE,FALSE,MAKELONG(0,20));
			MESS(ID_ROLLOFF,TBM_SETPOS,TRUE,10);
			MESS(ID_DOPPLER,TBM_SETRANGE,FALSE,MAKELONG(0,20));
			MESS(ID_DOPPLER,TBM_SETPOS,TRUE,10);

			SetTimer(h,1,TIMERPERIOD,&Update);
			GetCurrentDirectory(MAX_PATH,path);
			memset(&ofn,0,sizeof(ofn));
			ofn.lStructSize=sizeof(ofn);
			ofn.hwndOwner=h;
			ofn.nMaxFile=MAX_PATH;
			ofn.lpstrInitialDir=path;
			ofn.Flags=OFN_HIDEREADONLY|OFN_EXPLORER;
			ofn.lpstrFilter="wav/mo3/xm/mod/s3m/it/mtm\0*.wav;*.mo3;*.xm;*.mod;*.s3m;*.it;*.mtm\0"
				"All files\0*.*\0\0";
			return 1;

		case WM_DESTROY:
			KillTimer(h,1);
			DeleteObject(brush1);
			DeleteObject(brush2);
			if (chans) free(chans);
			PostQuitMessage(0);
			break;
	}
	return 0;
}


// Simple device selector dialog stuff begins here
BOOL CALLBACK devicedialogproc(HWND h,UINT m,WPARAM w,LPARAM l)
{
	switch (m) {
		case WM_COMMAND:
			switch (LOWORD(w)) {
				case ID_DEVLIST:
					if (HIWORD(w)!=LBN_DBLCLK) break;
				case IDOK:
					{
						int device=SendDlgItemMessage(h,ID_DEVLIST,LB_GETCURSEL,0,0);
						device=SendDlgItemMessage(h,ID_DEVLIST,LB_GETITEMDATA,device,0); // get device #
						EndDialog(h,device);
					}
					break;
			}
			break;

		case WM_INITDIALOG:
			{
				char text[100],*d;
				int c,idx;
				for (c=1;d=BASS_GetDeviceDescription(c);c++) { // device 1 = 1st real device
					strcpy(text,d);
					/* Check if the device supports 3D */
					if (!BASS_Init(c,44100,BASS_DEVICE_3D,h,NULL))
						continue; // no 3D support
					if (BASS_GetEAXParameters(NULL,NULL,NULL,NULL))
						strcat(text," [EAX]"); // it has EAX
					BASS_Free();
					idx=SendDlgItemMessage(h,ID_DEVLIST,LB_ADDSTRING,0,(int)text);
					SendDlgItemMessage(h,ID_DEVLIST,LB_SETITEMDATA,idx,c); // store device #
					
				}
				SendDlgItemMessage(h,ID_DEVLIST,LB_SETCURSEL,0,0);
			}
			return 1;
	}
	return 0;
}
// Device selector stuff ends here

int PASCAL WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,LPSTR lpCmdLine, int nCmdShow)
{
	MSG msg;
	int device;

	/* Check that BASS 2.1 was loaded */
	if (BASS_GetVersion()!=MAKELONG(2,1)) {
		MessageBox(0,"BASS version 2.1 was not loaded","Incorrect BASS.DLL",0);
		return 0;
	}

	{ // enable trackbar support
		INITCOMMONCONTROLSEX cc={sizeof(cc),ICC_BAR_CLASSES};
		InitCommonControlsEx(&cc);
	}

	/* Create the main window */
	if (!CreateDialog(hInstance,MAKEINTRESOURCE(1000),NULL,&dialogproc)) {
		Error("Can't create window");
		return 0;
	}

	/* Let the user choose an output device */
	device=DialogBox(hInstance,(char*)2000,win,&devicedialogproc);

	/* Initialize the output device */
	if (!BASS_Init(device,44100,BASS_DEVICE_3D,win,NULL)) {
		Error("Can't initialize output device");
		EndDialog(win,0);
		return 0;
	}

	/* Use meters as distance unit, real world rolloff, real doppler effect */
	BASS_Set3DFactors(1,1,1);
	/* Turn EAX off (volume=0), if error then EAX is not supported */
	if (BASS_SetEAXParameters(-1,0,-1,-1))
		EnableWindow(GetDlgItem(win,ID_EAX),TRUE);

	while (GetMessage(&msg,NULL,0,0)>0) {
		if (!IsDialogMessage(win,&msg)) {
			TranslateMessage(&msg);
			DispatchMessage(&msg);
		}
	}
	
	BASS_Free();

	return 0;
}
