/* BASS Simple Synth, copyright (c) 2001-2002 Ian Luck.
=======================================================
Imports: bass.lib, kernel32.lib
*/

#include <windows.h>
#include <stdio.h>
#include <conio.h>
#include <math.h>
#include "bass.h"

/* display error messages */
void Error(char *text) 
{
	printf("Error(%d): %s\n",BASS_ErrorGetCode(),text);
	BASS_Free();
	ExitProcess(0);
}


#define PI 3.14159265358979323846
#define TABLESIZE 2048
int sinetable[TABLESIZE];	// sine table
#define KEYS 20
WORD keys[KEYS]={
	'Q','2','W','3','E','R','5','T','6','Y','7','U',
	'I','9','O','0','P',219,187,221
};
#define MAXVOL	4000	// higher value = longer fadeout
int vol[KEYS]={0},pos[KEYS];	// keys' volume & pos


/* stream writer */
DWORD CALLBACK WriteStream(HSTREAM handle, short *buffer, DWORD length, DWORD user)
{
	int n,s;
	DWORD c;
	float f;
	memset(buffer,0,length);
	for (n=0;n<KEYS;n++) {
		if (!vol[n]) continue;
		f=pow(2.0,(n+3)/12.0)*TABLESIZE*440.0/44100.0;
		for (c=0;c<length/2 && vol[n];c++) {
			s=sinetable[(int)((pos[n]+c)*f)&(TABLESIZE-1)]*vol[n]/MAXVOL;
			s+=(int)buffer[c];
			if (s>32767) buffer[c]=32767;
			else if (s<-32768) buffer[c]=-32768;
			else buffer[c]=s;
			if (vol[n]<MAXVOL) vol[n]--;
		}
		pos[n]+=c; // update key's sine pos
	}
	return length;
}

void main(int argc, char **argv)
{
	BASS_INFO info;
	HSTREAM str;
	INPUT_RECORD keyin;
	int r;
	float f;

	printf("BASS Simple Sinewave Synth\n"
			"--------------------------\n");

	/* check that BASS 1.6 was loaded */
	if (BASS_GetVersion()!=MAKELONG(1,6)) {
		printf("BASS version 1.6 was not loaded\n");
		return;
	}

	/* setup output - 10ms update period, get latency & use 3D for EAX if available */
	if (!BASS_Init(-1,44100,MAKELONG(BASS_DEVICE_LATENCY|BASS_DEVICE_3D,10),0))
		Error("Can't initialize device");
	BASS_Start();

	/* build sine table */
	for (r=0;r<TABLESIZE;r++)
		sinetable[r]=(int)(sin(2.0*PI*(double)r/TABLESIZE)*7000.0);

	/* default buffer size = update period + device latency + 5ms extra safety */
	info.size=sizeof(info);
	BASS_GetInfo(&info);
	f=BASS_SetBufferLength((10+info.latency+5)/1000.0);
	printf("device latency: %dms\n",info.latency);

	if (info.eax) { /* add some EAX reverb if available */
		printf("using EAX reverb!\n");
		BASS_SetEAXParameters(EAX_PRESET_CONCERTHALL);
		/* create a 3d stream */
		str=BASS_StreamCreate(44100,BASS_SAMPLE_3D|BASS_SAMPLE_MONO,WriteStream,0);
	} else /* no EAX, so create a non-3d stream */
		str=BASS_StreamCreate(44100,BASS_SAMPLE_MONO,WriteStream,0);

	printf("press these keys to play:\n\n"
			"  2 3  5 6 7  9 0  =\n"
			" Q W ER T Y UI O P[ ]\n\n"
			"press -/+ to de/increase the buffer\n"
			"press spacebar to quit\n\n");
	printf("using a %.1fms buffer \r",f*1000.0);

	BASS_StreamPlay(str,0,0);

	while (ReadConsoleInput(GetStdHandle(STD_INPUT_HANDLE),&keyin,1,&r)) {
		int key;
		if (keyin.EventType!=KEY_EVENT) continue;
		if (keyin.Event.KeyEvent.wVirtualKeyCode==VK_SPACE) break;
		if (keyin.Event.KeyEvent.bKeyDown && keyin.Event.KeyEvent.wVirtualKeyCode==VK_SUBTRACT) {
			/* recreate stream with smaller buffer */
			BASS_StreamFree(str);
			f=BASS_SetBufferLength(f-.001);
			printf("using a %.1fms buffer    \r",f*1000.0);
			if (info.eax)
				str=BASS_StreamCreate(44100,BASS_SAMPLE_3D|BASS_SAMPLE_MONO,WriteStream,0);
			else
				str=BASS_StreamCreate(44100,BASS_SAMPLE_MONO,WriteStream,0);
			BASS_StreamPlay(str,0,0);
		}
		if (keyin.Event.KeyEvent.bKeyDown && keyin.Event.KeyEvent.wVirtualKeyCode==VK_ADD) {
			/* recreate stream with larger buffer */
			BASS_StreamFree(str);
			f=BASS_SetBufferLength(f+.001);
			printf("using a %.1fms buffer    \r",f*1000.0);
			if (info.eax)
				str=BASS_StreamCreate(44100,BASS_SAMPLE_3D|BASS_SAMPLE_MONO,WriteStream,0);
			else
				str=BASS_StreamCreate(44100,BASS_SAMPLE_MONO,WriteStream,0);
			BASS_StreamPlay(str,0,0);
		}
		for (key=0;key<KEYS;key++)
			if (keyin.Event.KeyEvent.wVirtualKeyCode==keys[key]) break;
		if (key==KEYS) continue;
		if (keyin.Event.KeyEvent.bKeyDown && vol[key]!=MAXVOL) {
			pos[key]=0;
			vol[key]=MAXVOL; // start key
		} else if (!keyin.Event.KeyEvent.bKeyDown && vol[key])
			vol[key]--; // trigger key fadeout
	}

	BASS_Free();
}
