/************************************************************************\
 * File Version Information
 * $Header: /Altair32v2/dazzler.c 20    11/08/06 9:21p Racini $
 ************************************************************************/
/************************************************************************\
  MITS Altair Emulator
  Cromemco Dazzler hardware emulation module

  Copyright (c) 2004-2006 Richard A. Cini

	This is the hardware support module for the Cromemco Dazzler color
	graphics video board and Joybox joystick.

	The Dazzler is accessed via two ports, 16q and 17q (0x0e and 0x0f).
	Port 16q is the control register and port 17q is effectively the
	data register. The Dazzler connects to a standard color composite
	video monitor or a TV using a direct connection or an RF modulator,
	respectively.

	The Dazzler is made up of a two-board set. Board 1 is the video graphics
	controller. Output is a 1-volt negative sync NTSC video signal into a
	52-ohm load. Board 2 is a 1mbps DMA controller which manages memory
	access and device I/O to the host.

	Screen resolution (in "elements") is as follows:

							Memory Size
					512 bytes		2048 bytes
					=========		==========
	"Normal" mode	32 x 32			 64 x  64
	"X4" mode		64 x 64			128 x 128

	Each matrix unit is called an "element", and each element is represented
	in memory by a nibble of memory (thus, each byte of memory describes two
	ajacent elements of the screen picture).

	For example, element information is stored as follows ("normal" color
	mode; in B&W, each nibble defines the 4-bit gray scale):

		+---+---+---+---+---+---+---+---+
		| I2| G2| B2| R2| I1| G1| B1| R1|
		+---+---+---+---+---+---+---+---+
		MSB.............|.............LSB


	In "X4" mode, each one byte of memory represents eight ajacent picture
	elements in a 4-across by 2-high matrix. The picture is generated on
	screen by scanning it in 4, 512-byte quadrants in an "across and down"
	fashion.

	NTSC TV is displayed with a 4:3 aspect ratio so although the Dazzler
	has a perfectly square resolution, there may be appearances of stretching
	when displayed on a real TV. Looking at screen captures, however, there
	doesn't appear to be stretching, so who knows.

	This module also provides emulation support for the Cromemco Joybox, a
	joystick device that connected to the D+7A digital/analog I/O board and
	provided an XY joystick and control buttons. Certain of the Cromemco
	games required the Joybox. The Joybox is assigned to ports 30Q-36Q
	(0x18-0x1e).

Change Log:
  2004/07/30  RAC -- Began coding.
  2004/09/16  RAC -- First clean compile; began testing.
  2004/11/01  RAC -- Additional changes. Code works (sort of), but
  						demo programs don't produce expected output.
  						Still trying to find "simple" commercial programs
  						in binary form to test.
  2006/05/12  RAC -- RELEASE MARKER -- v3.10.0200
  2006/06/02  RS  -- Screen draw routines rewritten; thUpdateScreen() is threaded;
                     SOL timer used to resume thread at 16.6ms intervals; color
                     mappings changed
  2006/06/12  RS  -- Port 0e read status corrected (ref Gotcha game source code);
                     client area is now HOST_RES on startup; window creation
                     border metrics corrected; crBlack=crBrBlack=crGray0=RGB(0,0,0)
  2006/07/15  RS  -- Black out screen when disabled; port logic moved to thread
  2006/07/19  RS  -- Keyboard joysticks processed by Dazzler window (no tcp)
  2006/08/31  RS  -- Added real joystick support
  2006/11/15  RAC -- RELEASE MARKER -- v3.20.0400
  
\************************************************************************/

#include <windows.h>	// required for all Windows applications
#include <mmsystem.h>
#include <commctrl.h>	// Windows common controls
#include <stdlib.h>		// C standard lib
#include <string.h>		// C-lib - strings
#include <stdio.h>		// C-lib - I/O included in altair32.h
#include <process.h>	// thread stuff
#include "altair32.h"	// includes "resource.h" and exports from other major modules
#include "i8080.h"		// for memory access macros
#include "comcthlp.h"	// common control macros


/**  Typedefs & Defines  **************************************/
//comment for fixed, uncomment for resizable
//#define DAZ_SCALE 1		// 1=resizable window. draws slower, but more useful
#ifndef DAZ_SCALE
#define DAZ_SCALE 0		// 0=fixed size window
#endif

// --NOTE-- HOST_RES must be a multiple of 128 to work properly --NOTE--
#if DAZ_SCALE		// client area initial resize size
//#define HOST_RES	128		// x1
#define HOST_RES	256		// x2
//#define HOST_RES	384		// x3
//#define HOST_RES	512		// x4
//#define HOST_RES	640		// x5
#else				// client area fixed size
//#define HOST_RES	128		// x1
//#define HOST_RES	256		// x2
#define HOST_RES	384		// x3
//#define HOST_RES	512		// x4
//#define HOST_RES	640		// x5
#endif

// BGR COLORREF macro
#define BGR(b,g,r) ((DWORD)(((BYTE)(r)|((WORD)(g) << 8))|(((DWORD)(BYTE)(b)) << 16)))
// BGR COLORREF macros for colors
// low intensity
#define crBlack		BGR(0x00,0x00,0x00) // 0
#define crRed		BGR(0x00,0x00,0x80)	// 1
#define crGreen		BGR(0x00,0x80,0x00)	// 2
#define crYellow	BGR(0x00,0x80,0x80)	// 3
#define crBlue		BGR(0x80,0x00,0x00)	// 4
#define crMagenta	BGR(0x80,0x00,0x80)	// 5
#define crCyan		BGR(0x80,0x80,0x00)	// 6
#define crWhite		BGR(0x80,0x80,0x80)	// 7
// high intensity
#define crBrBlack	BGR(0x00,0x00,0x00)	// 8
#define crBrRed		BGR(0x00,0x00,0xff)	// 9
#define crBrGreen	BGR(0x00,0xff,0x00)	// a
#define crBrYellow	BGR(0x00,0xff,0xff)	// b
#define crBrBlue	BGR(0xff,0x00,0x00)	// c
#define crBrMagenta	BGR(0xff,0x00,0xff)	// d
#define crBrCyan	BGR(0xff,0xff,0x00)	// e
#define crBrWhite	BGR(0xff,0xff,0xff)	// f

// RGB COLORREF macros for grayscale
#define crGray0		RGB(0x00,0x00,0x00)
#define crGray1		RGB(0x10,0x10,0x10)
#define crGray2		RGB(0x20,0x20,0x20)
#define crGray3		RGB(0x30,0x30,0x30)
#define crGray4		RGB(0x40,0x40,0x40)
#define crGray5		RGB(0x50,0x50,0x50)
#define crGray6		RGB(0x60,0x60,0x60)
#define crGray7		RGB(0x70,0x70,0x70)
#define crGray8		RGB(0x80,0x80,0x80)
#define crGray9		RGB(0x90,0x90,0x90)
#define crGrayA		RGB(0xa0,0xa0,0xa0)
#define crGrayB		RGB(0xb0,0xb0,0xb0)
#define crGrayC		RGB(0xc0,0xc0,0xc0)
#define crGrayD		RGB(0xd0,0xd0,0xd0)
#define crGrayE		RGB(0xe0,0xe0,0xe0)
#define crGrayF		RGB(0xf0,0xf0,0xf0)

typedef enum tagTHREAD {
	STOP,
	RUN
} threadcmd_t;

// screenstate
typedef struct tag_SCREENSTATE {
	int DazReg1;		// port 0eh register
	int DazReg1rd;		// read port 0eh register
	int DazReg2;		// port 0fh register
	int redraw;			// 0 = no resizing flag
	int fourmsctr;		// 4ms counter
	int htmr_dazzler;	// 0 = not using a timer
	threadcmd_t threadcmd;	// RUN/STOP
	HANDLE hEventVid;	// Dazzler
	HANDLE hThreadVid;	// thread handle
	//unsigned long threadIDvid;
	unsigned int threadIDvid;
	HWND hwndVid;		// dazzler window handle
	HDC	hdcVid;			// hDC
} screenstate_t;

// dazzler port stuff
#define DAZ_CR1_ENABLED			0x80
#define DAZ_CR1_MEMWINDOW		0x7f

#define DAZ_CR2_X4_2K			0x60	// res | size
#define DAZ_CR2_X4				0x40	// resolution mode normal/X4
#define DAZ_CR2_2K				0x20	// picture size 512b/2k
#define DAZ_CR2_INCOLOR_IBGR	0x1f	// all bits related to color
#define DAZ_CR2_INCOLOR			0x10	// color mode C/B&W
#define DAZ_CR2_INTENSITY		0x08	// intensity bit
#define DAZ_CR2_IBGR			0x0f	// intensity | color bits


/**  Public Prototypes  ***************************************/
//exported in altair32.h
/*
void DAZZLER_Init( void );
void DAZZLER_Destroy(void);
void DAZZLER_VideoWin(VidWinMsg_t msg, int arg);
int dazz0eh(int io, int data);
int dazz0fh(int io, int data);
int joybtns( int, int );
int joy1x( int, int );
int joy1y( int, int );
int joy2x( int, int );
int joy2y( int, int );
int joy1z( int, int );
int joy2z( int, int );
*/


/**  Module Prototypes  ***************************************/
static BOOL CALLBACK lpWndProcVidScreen( HWND, UINT, WPARAM, LPARAM );
static unsigned long __stdcall thUpdateScreen(void* pArguments);
static void tcb_dazzler(unsigned int arg1, unsigned int arg2);
static BOOL ActivateVideoWindow( void );
static BOOL InactivateVideoWindow( void );
static BOOL CloseVideoWindow( void );
static BOOL CreateVideoWindow( void );
static INT HOST_OpenJoystick( void );

/**  Locals  **************************************************/
static screenstate_t screenstate;
static COLORREF colDazColors[] = {
	crBlack,  crRed,  crGreen,  crYellow,  crBlue,  crMagenta,  crCyan,  crWhite,
	crBrBlack,crBrRed,crBrGreen,crBrYellow,crBrBlue,crBrMagenta,crBrCyan,crBrWhite
};
static COLORREF colDazBW[] = {
	crGray0,crGray1,crGray2,crGray3,crGray4,crGray5,crGray6,crGray7,
	crGray8,crGray9,crGrayA,crGrayB,crGrayC,crGrayD,crGrayE,crGrayF
};

//joystick stuff
static int doodle=0;	// start in fast mode
static int p18=0xff;	// buttons up (pull-up resistors on port)
static int p19=0;		// j1x centered
static int s19=3;
static int p1a=0;		// j1y centered
static int s1a=3;
static int p1b=0;		// j2x centered
static int s1b=3;
static int p1c=0;		// j2y centered
static int s1c=3;
static int fast[]={-128,-64,-32,0,32,64,127};

static int p19TMP,p1aTMP;	// temp signed x,y , may exceed -128 thru 127
static int CenterX=128;		// default positive centers, use 'D' to calibrate
static int CenterY=128;
static int xPos   =128;		// default positive x,y
static int yPos   =128;
static UINT JOYDeviceID=31;	// default joystick ID
static UINT JOYbuttons =0;
static UINT JOYaxis    =0;
static char buf[64];


/**************************************************************\
|** PUBLIC ROUTINES  ******************************************|
\**************************************************************/

/*--  DAZZLER_Init  -------------------------------------------

	Create WndClass and initialize screenstate.

	Params:		HINSTANCE
	Uses:		winstate, screenstate
	Returns:	nothing
	Comments:	Called from init.c. Registers WndClass for Dazzler
				video window and performs other initialization chores.

			//dev ver called from InitEmu altair.c
-------------------------------------------------------------*/
void DAZZLER_Init( void )
{
	static int regd=0;

	if (!regd){
	    WNDCLASSEX wndclass;
		// Initialize wndclass
	    wndclass.cbSize        = sizeof(WNDCLASSEX);
	    wndclass.style         = CS_OWNDC;
	    wndclass.lpfnWndProc   = (WNDPROC)lpWndProcVidScreen;
	    wndclass.cbClsExtra    = 0;
	    wndclass.cbWndExtra    = 0;
	    wndclass.hInstance     = winstate.hInst;
	    wndclass.hIcon         = LoadIcon(winstate.hInst, "dazzleico");
		wndclass.hCursor	   = LoadCursor(NULL,IDC_ARROW);
		wndclass.hbrBackground = GetStockObject(BLACK_BRUSH);
	    wndclass.lpszMenuName  = NULL;
	    wndclass.lpszClassName = "VideoWClass";
	    wndclass.hIconSm       = NULL;

	    RegisterClassEx(&wndclass);
		regd=1;
	}

	// Initialize screenstate
	screenstate.DazReg1		=0;	// port 0eh register
	screenstate.DazReg1rd 	=0;	// read port 0eh register
	screenstate.DazReg2		=0;	// port 0fh register
	screenstate.redraw		=0;	// 0 = no resizing flag
	screenstate.fourmsctr	=0;	// 4ms counter
	screenstate.htmr_dazzler=0;	// 0 = SOL timer not initialized
	screenstate.threadcmd	=RUN;	// STOP = exit thread
	screenstate.hEventVid	=NULL;	// Dazzler
	screenstate.hThreadVid	=NULL;	// thread handle
	screenstate.threadIDvid =0;
	screenstate.hwndVid		=NULL;	// dazzler window handle
	screenstate.hdcVid		=NULL;	// hDC
}

/*--  DAZZLER_Destroy  ----------------------------------------

	Destroy Dazzler stuff on exit.

	Params:		None
	Uses:		screenstate
	Returns:	Nothing
	Comments:	None

			//dev ver called after scheduler thread exits
			//from ts_power PWR_OFF
			//from _OnClose in altair32.c
-------------------------------------------------------------*/
void DAZZLER_Destroy(void)
{
	// if dazzler thread exists, tell it to exit
/*	if (screenstate.hThreadVid){
		DWORD lpExitCode=STILL_ACTIVE;
		//int cnt=10000;

		screenstate.threadcmd=STOP;
		//while ((lpExitCode==STILL_ACTIVE) && (--cnt > 0)){
		while (lpExitCode==STILL_ACTIVE){
			ResumeThread(screenstate.hThreadVid);
			Sleep(1);
			if(0==GetExitCodeThread(screenstate.hThreadVid,&lpExitCode))
				break; //error
		}
		if (lpExitCode==STILL_ACTIVE){
			HOST_ThrowErrMsg("Error exiting dazzler thread in dazzler.c!");
			FatalAppExit(0,"Can't continue");
		}
		CloseHandle(screenstate.hThreadVid);
		CloseHandle(screenstate.hEventVid);
		screenstate.hThreadVid=NULL;
		screenstate.hEventVid=NULL;
		screenstate.htmr_dazzler=0;
		screenstate.threadcmd=RUN;

		if (screenstate.hdcVid){
			ReleaseDC(screenstate.hwndVid,screenstate.hdcVid);
			screenstate.hdcVid=NULL;
		}
		if (screenstate.hwndVid){
			DestroyWindow(screenstate.hwndVid); //???
			screenstate.hwndVid=NULL;
			//HOST_ThrowErrMsg("What are you doing, Dave?");
		}
	}
*/
}


/*--  DAZZLER_VideoWin  ---------------------------------------

	Process commands from the UI.

	Params:		message, (int)argument (not used)
	Uses:		Nothing
	Returns:	Nothing
	Comments:	Activate called from _OnCommand IDM_VIDEO altair.c
				Video window management routines
-------------------------------------------------------------*/
void DAZZLER_VideoWin(VidWinMsg_t msg, int arg)
{

    switch (msg) {
		case VWM_Activate:		ActivateVideoWindow();		break;
		case VWM_Inactivate:	InactivateVideoWindow();	break;
		case VWM_Close:			CloseVideoWindow();			break;
		case VWM_Create:		CreateVideoWindow();		break;
		default:
	    	ASSERT(0);
    }
}


/*--  dazz0eh  ------------------------------------------------

	Device I/O register #1 R/W

	Params:		io_direction, data
	Uses:
	Returns:	On read, status of port. On write, NMF.
	Comments:

		Control Register 1 (Port 16Q OUT):

		+---+---+---+---+---+---+---+---+
		| E |A15|A14|A13|A12|A11|A10| A9|
		+---+---+---+---+---+---+---+---+

		E = If this bit is 1, Dazzler output is enabled. If the
		bit is zero, Dazzler output is disabled.

		Axx = Starting address in computer memory of the picture
		to be displayed by the Dazzler. The picture may require
		512 bytes or 2k bytes depending on the mode set with port
		17Q. For what it's worth, the document specifies that the
		picture memory should be static RAM with an access time of
		1uS or better.


		Control Register 1 (Port 16Q IN):

		+---+---+---+---+---+---+---+---+
		| L |EOF|      U N U S E D      |
		+---+---+---+---+---+---+---+---+

		L = Odd/even line indicator. When 0, board is scanning
			odd lines. When 1, board is scanning even lines.
		EOF = Active-low signal. Goes low for 4ms between frames
			to indicate the end of the frame.

		Note on TV/composite video. Each frame is composed of two
		fields of 262.5 scan lines. The field consists of 242.5
		active lines and 20 inactive lines and is scanned in
		1/60 of a second (.01666 seconds or 16.7ms). An entire
		frame is scanned in an odd/even interlace over 1/30
		of a second (33.3ms). The inactive lines are effectively
		used for audio. The horizontal scanning	frequency is
		15.734kHz.

		http://www.maxim-ic.com/appnotes.cfm/appnote_number/734

		A frame of video begins with the horizontal blanking
		interval which lasts 10.9uS, followed by the actual scan
		line information for the remaining 52.6uS. The blanking
		interval starts with a delay of 1.5uS (called the "front
		porch"). This is followed by the HSYNC going low for
		4.7uS (called the "sync tip"). Next is a 0.6uS
		"breezeway" between the	start of the blanking interval
		and the colorburst. The	colorburst lasts for 2.5uS
		(8-10 cycles of 3.58MHz), followed by the "back porch"
		of 1.6uS. The video info represents the balance of the
		time slot.
-------------------------------------------------------------*/
int dazz0eh(int io, int data)
{

	switch (io){
		case 0:		//READ
			return screenstate.DazReg1rd;

		case 1:		//WRITE
			screenstate.DazReg1 = data;
	}
	return (0xff);
}


/*--  dazz0fh  -----------------------------------------------

	Device I/O register #2 W - Picture Format

	Params:		io_direction, data
	Uses:
	Returns:
	Comments:

	Control Register 2 (Port 17Q OUT):

		+---+---+---+---+---+---+---+---+
		| X | E | S | C | I | B | G | R |
		+---+---+---+---+---+---+---+---+

		X = not used
		E = Resolution mode. If 1, "X4" mode (64^2 for 512-byte
			picture and 128^2 for 2k picture). If 0, "normal"
			mode (32^2 for 512-byte picture, 64^2 for 2k picture).
			Color and intensity set by each 4-bit nibble
			in system memory. In X4 mode, color and intensity
			is set by the IBGR nibble. It appears that the
			X4 mode only supports 2k B&W pictures (element is either
			ON or OFF).
		S = Picture size. If 1, 2k picture size. If 0, 512 byte
			picture size.
		C = Color mode. If 1, picture is in color. If 0, picture
			is in B&W.
		I/B/G/R = Intensity and color bits. Nibble is used in X4
			mode to set the color of the picture or the gray scale
			in a B&W picture. Nibble is not used in normal
			resolution mode.

-------------------------------------------------------------*/
int dazz0fh(int io, int data)
{
	switch (io){
		case 0:		//READ -- not supported
			break;

		case 1:		//WRITE
			screenstate.DazReg2 = data;
	}
	return (0xff);
}


/** PUBLIC JOYSTICK PORTS **/
int joybtns(int io, int data)	// BUTTONS JOY1 JOY2
{
	// D7   D6   D5   D4   D3   D2   D1   D0
	//J2-4 J2-3 J2-2 J2-1 J1-4 J1-3 J1-2 J1-1
	//
	// Negative logic: inputs are pulled to Vcc through a
	//	10k resistor and the switch connects to GND. A '0'
	//  on the input signifies the switch is closed.
	if (io==0) return (p18 & 0xff);	// j1-j2 buttons
	return 0;
}

int joy1x(int io, int data)	// JOY1 X
{
	// Speaker is connected to analog output on port 031
	if (io==0) return (p19 & 0xff);	// j1 x-axis
	Beep(1000, 1);	//speaker
	return 0;
}

int joy1y(int io, int data)	// JOY1 Y
{
	if (io==0) return (p1a & 0xff);	// j1 y-axis
	return 0;
}

int joy2x(int io, int data)	// JOY2 X
{
	// Speaker is connected to analog output on port 033 on real
	// setup but that would be too much beeping for an emulation
	// so we just ignore it.
	if (io==0) return (p1b & 0xff); // j2 x
//	Beep(1000,1);	//speaker
	return 0;
}

int joy2y(int io, int data)	// JOY2 Y
{
	if (io==0) return (p1c & 0xff); // j2 y
	return 0;
}

int joy1z(int io, int data)	// felix
{
	// This is a special mod. The real JoyBox did not
	// have a z-axis nor a port assignment.
	if (io==0) return 0; //(pot1 & 0xff);	// felix mod
	return 0;
}

int joy2z(int io, int data)	// felix
{
	// This is a special mod. The real JoyBox did not
	// have a z-axis nor a port assignment.
	if (io==0) return 0; //(pot2 & 0xff);	// felix mod
	return 0;
}


/**************************************************************\
|** PRIVATE ROUTINES  *****************************************|
\**************************************************************/
/**************************************************************\
*
*	FUNCTION:	lpWndProcVidScreen(HWND, UINT, WPARAM, LPARAM)
*
*	PURPOSE:	WndProc for Dazzler video screen container
*
*	MESSAGES:	standard Windows messages
*
*	COMMENTS:	WndProc for video screen window
*
*	joy buttons 1234 and sticks left,right,up,down
*	are latched (logically stay pressed)
*	this allows for combinations of buttons
*	and to hold the current stick position
*
*
*	'=' select fast mode 'reset' (for chase, spacewar, tankwar)
*	    release all buttons and center both sticks and beeps
*
*	'-' select slow mode 'doodle' (for doodle, track)
*		release all buttons do NOT center sticks and beeps
*
*
*	joy1 buttons 1234   joy2 buttons 1234
*	keyboard ->  1234                7890
*
*	              E
* 				W   R                 I
*  joy1 stick  S (D) F  joy2 stick J (K) L
*				X   V                 ,
*				  C
*
*	'D' centers joy1 stick
*		?in fast mode releases button 1 (spacewar,tankwar fire button)
*		?in slow mode releases buttons 1234 (doodle colors)
*
*	'K' centers joy2 stick
*		other actions same as 'D'
*
*	joysticks should work with upper or lower case
*
*	Message crackers are not available for the MM functions.
*
\**************************************************************/
static BOOL CALLBACK lpWndProcVidScreen(HWND hWnd,
										UINT iMsg,
										WPARAM wParam,
										LPARAM lParam)
{

	unsigned char nData;
	HDC hdc;
	PAINTSTRUCT ps;
	RECT rc;


	switch (iMsg){
		case WM_CREATE:
			if (HOST_OpenJoystick() != JOYERR_NOERROR){
				HOST_ThrowErrMsg("Unable to open joystick.");
				break; //return TRUE;
			}
			// capture joystick if 2 or more axes
			if (JOYaxis>1)
			if (JOYERR_NOERROR != joySetCapture(hWnd, JOYDeviceID, 0, FALSE)){
				HOST_ThrowErrMsg("Couldn't capture the joystick.");
				break; //return TRUE;
			}
			break; //return TRUE;

		case WM_ERASEBKGND:
			screenstate.redraw = 1;
			return (DefWindowProc (hWnd, iMsg, wParam, lParam));

		case WM_PAINT:
			hdc=BeginPaint(hWnd, &ps);
			GetClientRect(hWnd, &rc);
			FillRect(hdc,&rc,GetStockObject(BLACK_BRUSH));
			EndPaint(hWnd, &ps);
			screenstate.redraw = 1;
			break;

		case MM_JOY1MOVE:				// changed position
		case MM_JOY2MOVE:
			// wParam					// JOY_BUTTON1/2/3/4

			// Win9x seems to return a 16 bit value for position
			// where 0 is up or left and 65535 is right or down
			// we need to scale that into a -128 thru 127 range
			//0 thru 255 xPos = (lParam & 0xff00)>>8;
			//0 thru 255 yPos = (lParam & 0xff000000)>>24;

			// average last pos with current to reduce analog pot jitter
			xPos = (xPos + ((lParam & 0xff00)>>8)) >> 1;
			yPos = (yPos + ((lParam & 0xff000000)>>24)) >> 1;

			p19TMP= xPos-CenterX; // signed offset from center
			// check range
			if (p19TMP>127)
				p19=127;
			else
			if (p19TMP<-128)
				p19=-128;
			else
				p19=p19TMP;

			p1aTMP= 0-(yPos-CenterY); // same as x but change sign
			if (p1aTMP>127)
				p1a=127;
			else
			if (p1aTMP<-128)
				p1a=-128;
			else
				p1a=p1aTMP;

			// show processed x,y
			wsprintf(buf, "Coord (X,Y): (%d,%d)\n", p19, p1a);
			Status_SetText(hwndStatusBar, STATBAR_READY, 0, buf);
			break;

		//case MM_JOY1ZMOVE: // felix z axis not supported to this level
		//case MM_JOY2ZMOVE:
		//	break;

		case MM_JOY1BUTTONDOWN:		// button is down
		case MM_JOY2BUTTONDOWN:
			// wParam	JOY_BUTTON1CHG/2CHG/3CHG/4CHG button has changed state
			//			PLUS one or more of JOY_BUTTON1/2/3/4
			// xPos = LOWORD(lParam);
			// yPos = HIWORD(lParam);

			// we can use momentary buttons with real joysticks
			// just check for button that has changed so joystick can coexist with keyboard
			if(wParam & JOY_BUTTON1CHG){
				p18 &= 0xfe;
			}else
			if(wParam & JOY_BUTTON2CHG){
				p18 &= 0xfd;
			}else
			if(wParam & JOY_BUTTON3CHG){
				p18 &= 0xfb;
			}else
			if(wParam & JOY_BUTTON4CHG){
				p18 &= 0xf7;
			}
		    break;

		case MM_JOY1BUTTONUP:		// button is up
		case MM_JOY2BUTTONUP:

			// same params as JOYxBUTTONDOWN
			if(wParam & JOY_BUTTON1CHG){
				p18 |= 0x01;
			}else
			if(wParam & JOY_BUTTON2CHG){
				p18 |= 0x02;
			}else
			if(wParam & JOY_BUTTON3CHG){
				p18 |= 0x04;
			}else
			if(wParam & JOY_BUTTON4CHG){
				p18 |= 0x08;
			}
			break;

		case WM_CHAR:
			nData = wParam & 0x7f;

			if (nData > 0x3f) nData &= 0x5f; // conv to uppercase

			switch (nData){
	//stick2-y-down
				case 0x2c: if(doodle){if(p1c>-128){p1c-=1;}}else{if(s1c>0){s1c--;p1c=fast[s1c];}}break;
	//slow mode +/- 1
				case '-': doodle=1;p18=0xff;Beep(1000,1);break;

				case 0x2e: break;
				case 0x2f: break;

				case '0': p18^=0x80; break;	// joy2-4
				case '1': p18^=0x01; break;	// joy1-1
				case '2': p18^=0x02; break;	// joy1-2
				case '3': p18^=0x04; break;	// joy1-3
				case '4': p18^=0x08; break;	// joy1-4
				case '5': break;
				case '6': break;
				case '7': p18^=0x10; break;	// joy2-1
				case '8': p18^=0x20; break;	// joy2-2
				case '9': p18^=0x40; break;	// joy2-3

				case 0x3a: break;
				case 0x3b: break;
				case 0x3c: break;

	//fast mode fast[n]
				case '=': doodle=0;p18=0xff;p19=p1a=0;s19=s1a=3;p1b=p1c=0;s1b=s1c=3;Beep(1000,1);break;
				case 0x3e: break;
				case 0x3f: break;
				case 0x40: break;
				case 'A': break;
				case 'B': break;

	//stick1-y-down
				case 'C': if(doodle){if(p1a>-128){p1a-=1;}}else{if(s1a>0){s1a--;p1a=fast[s1a];}}break;
	//stick1-xycenter
				case 'D':
					p19=p1a=0;
					s19=s1a=3;
					// need x,y min to use joystick
					if (JOYaxis>1){ // calibrate
						// use current as center
						CenterX = xPos;
						CenterY = yPos;
					}
					break;
	//stick1-y-up
				case 'E': if(doodle){if(p1a<127){p1a+=1;}}else{if(s1a<6){s1a++;p1a=fast[s1a];}}break;
	//stick1-x-right
				case 'F': if(doodle){if(p19<127){p19+=1;}}else{if(s19<6){s19++;p19=fast[s19];}}break;
				case 'G': break;
				case 'H': break;
	//stick2-y-up
				case 'I': if(doodle){if(p1c<127){p1c+=1;}}else{if(s1c<6){s1c++;p1c=fast[s1c];}}break;
	//stick2-x-left
				case 'J': if(doodle){if(p1b>-128){p1b-=1;}}else{if(s1b>0){s1b--;p1b=fast[s1b];}}break;
	//stick2-xycenter
				case 'K': p1b=p1c=0;s1b=s1c=3;break;
	//stick2-y-right
				case 'L': if(doodle){if(p1b<127){p1b+=1;}}else{if(s1b<6){s1b++;p1b=fast[s1b];}}break;
				case 'M': break;
				case 'N': break;
				case 'O': break;
				case 'P': break;
				case 'Q': break;
	//stick1-xy-ru
				case 'R': if(doodle){if(p19<127){p19+=1;}if(p1a<127){p1a+=1;}}
							else{if(s19<6){s19++;p19=fast[s19];}if(s1a<6){s1a++;p1a=fast[s1a];}}break;
	//stick1-x-left
				case 'S': if(doodle){if(p19>-128){p19-=1;}}else{if(s19>0){s19--;p19=fast[s19];}}break;
				case 'T': break;
				case 'U': break;
	//stick1-xy-rd
				case 'V': if(doodle){if(p19<127){p19+=1;}if(p1a>-128){p1a-=1;}}
							else{if(s19<6){s19++;p19=fast[s19];}if(s1a>0){s1a--;p1a=fast[s1a];}}break;
	//stick1-xy-lu
				case 'W': if(doodle){if(p19>-128){p19-=1;}if(p1a<127){p1a+=1;}}
							else{if(s19>0){s19--;p19=fast[s19];}if(s1a<6){s1a++;p1a=fast[s1a];}}break;
	//stick1-xy-ld
				case 'X': if(doodle){if(p19>-128){p19-=1;}if(p1a>-128){p1a-=1;}}
							else{if(s19>0){s19--;p19=fast[s19];}if(s1a>0){s1a--;p1a=fast[s1a];}}break;
				case 'Y': break;
				case 'Z': break;
			}

			break;

		case WM_CLOSE:
			CloseVideoWindow();
			break;

		default:
			return (DefWindowProc(hWnd, iMsg, wParam, lParam));
	}
	return FALSE;
}


/*--  ActivateVideoWindow  ------------------------------------

	Startup Dazzler threads and create/show window if needed.

	Params:		none
	Uses:		TimerCreate()
	Returns:	TRUE if window and thread created/exist
	Comments:
-------------------------------------------------------------*/
static BOOL ActivateVideoWindow( void )
{
	// If window does not exist, create it
	if (FALSE == CreateVideoWindow()){
		return FALSE;
	}

	// If event does not exist, create it
	if (screenstate.hEventVid == NULL) {
		screenstate.hEventVid = CreateEvent( NULL, FALSE, FALSE, NULL );
		if (screenstate.hEventVid == NULL) {
			HOST_ThrowErrMsg("CreateEvent in ActiveateVideoWindow failed!.\n");
			// more error handling??
			return FALSE;
		}
	}

	// If thread does not exist, create it
	if (screenstate.hThreadVid == 0) {
		// start the dazzler thread running
//		screenstate.hThreadVid = CreateThread((LPSECURITY_ATTRIBUTES)0,
		screenstate.hThreadVid = (HANDLE)_beginthreadex((LPSECURITY_ATTRIBUTES)0,
												0,
												&thUpdateScreen,
												screenstate.hEventVid,
												0,
												&screenstate.threadIDvid);

		if (screenstate.hThreadVid == 0) {
			HOST_ThrowErrMsg("Create thUpdateScreen thread failed!.\n");
			CloseHandle(screenstate.hEventVid);
			screenstate.hEventVid=NULL;
			// more error handling??
			return FALSE;
		}
		WaitForSingleObject(screenstate.hEventVid, INFINITE);	// wait for thread to init
//HOST_ThrowErrMsg("Dazzler created.");
	}

	ShowWindow(screenstate.hwndVid, SW_SHOW);

	// if timer does not exist, create it
	if (0==screenstate.htmr_dazzler)
		screenstate.htmr_dazzler=TimerCreate(TIMER_US(4167), tcb_dazzler, 0, 0);

	return TRUE;
}


/*--  InactivateVideoWindow  ----------------------------------

	Hides the Dazzler video window.

	Params:		none
	Uses:		screenstate
	Returns:	always TRUE
	Comments:	called from ts_power PWR_OFF
-------------------------------------------------------------*/
static BOOL InactivateVideoWindow( void )
{
	screenstate.htmr_dazzler=0; //at power on SCHED_TimerInit() clears all timers

	// Hide the window...don't destroy it
	if (screenstate.hwndVid){
	    ShowWindow(screenstate.hwndVid, SW_HIDE);
	}
	return TRUE;
}


/*--  CloseVideoWindow  ---------------------------------------

	Hides the Dazzler video window.

	Params:		none
	Uses:		screenstate
	Returns:	always TRUE
	Comments: 	called from WM_CLOSE dazzler.c
-------------------------------------------------------------*/
static BOOL CloseVideoWindow( void )
{
	// Hide the window...don't destroy it
    if (screenstate.hwndVid){
	    ShowWindow(screenstate.hwndVid, SW_HIDE);
	}
	return TRUE;
}


/*--  CreateVideoWindow  --------------------------------------

	Create the container window for the Dazzler display.

	Params:		none
	Uses:		screenstate
	Returns:	TRUE if window created/exists
	Comments:
-------------------------------------------------------------*/
static BOOL CreateVideoWindow( void )
{
	// Create up container window if is doesn't exist already
	if (screenstate.hwndVid == NULL){

#if DAZ_SCALE
		screenstate.hwndVid = CreateWindow(
			"VideoWClass",				// window class name
			"Dazzler Video Display",	// window caption
			WS_OVERLAPPEDWINDOW ,		// window style
			0,							// Horizontal position.
			0,							// Vertical position.
			HOST_RES	+ (GetSystemMetrics(SM_CXFRAME) * 2),	// window width
			HOST_RES	+ GetSystemMetrics(SM_CYCAPTION)
						+ (GetSystemMetrics(SM_CYFRAME) * 2),	// window height
			(HWND)NULL,			// overlapped has no parent window handle
			(HMENU)NULL,		// window menu handle
			winstate.hInst,		// program instance handle
			(LPVOID)NULL);		// creation parameters
#else
		screenstate.hwndVid = CreateWindow(
			"VideoWClass",				// window class name
			"Dazzler Video Display",	// window caption
			WS_OVERLAPPEDWINDOW ^ WS_THICKFRAME ^ WS_MAXIMIZEBOX ,
			0,							// Horizontal position.
			0,							// Vertical position.
			HOST_RES	+ (GetSystemMetrics(SM_CXFIXEDFRAME) * 2),	// window width
			HOST_RES	+ GetSystemMetrics(SM_CYCAPTION)
						+ (GetSystemMetrics(SM_CYFIXEDFRAME) * 2),	// window height
			(HWND)NULL,			// overlapped has no parent window handle
			(HMENU)NULL,		// window menu handle
			winstate.hInst,		// program instance handle
			(LPVOID)NULL);		// creation parameters
#endif

		if (screenstate.hwndVid == NULL){
			HOST_ThrowErrMsg("CreateWindow in CreateVideoWindow failed!.\n");
			// more error handling??
			return FALSE;
		}
	}
    return TRUE;
}


/*--  tcb_dazzler  -----------------------------------------

	Thread stuff.

	Params:		Not used
	Uses:		SOL Timer functions
	Returns:	Nothing
	Comments:	Resume the dazzler thread @16.6ms intervals if
				previous thread is finished
-------------------------------------------------------------*/
static void tcb_dazzler (unsigned int arg1, unsigned int arg2)
{
	// restart ~4 ms timer for this routine
	if (screenstate.htmr_dazzler){
		TimerCreate(TIMER_US(4167), tcb_dazzler, 0, 0);
		// 4 ms count
		screenstate.fourmsctr=7&screenstate.fourmsctr+1;
	} else {
		return;
	}

	switch (screenstate.fourmsctr){

// 0/33.3ms
		case 0:	screenstate.DazReg1rd=0x3F;
				break;
		case 1:	screenstate.DazReg1rd=0x7F;
				if (0==IsIconic(screenstate.hwndVid))
					ResumeThread(screenstate.hThreadVid);
				break;
		case 2:	break;	//7f
		case 3:	break;	//7f

// 16.6ms
		case 4:	screenstate.DazReg1rd=0xFF;
				break;
		case 5:	if (0==IsIconic(screenstate.hwndVid))
					ResumeThread(screenstate.hThreadVid);
				break; 	//ff
		case 6:	break;	//ff
		case 7:	break;	//ff
	}
}


/*--  thUpdateScreen  -----------------------------------------

	This is the actual DAZZLER thread.

	Params:		pArguments
	Uses:		tcb_dazzler()
	Returns:	colorful blocks in dazzler window
	Comments:
-------------------------------------------------------------*/
static unsigned long __stdcall thUpdateScreen(void* pArguments)
{
	byte Tmp[2048];		// Mem[] buffer
	byte Scr[2048];		// screen buffer
	int enabled=0;
	int thrReg1=0;
	int thrReg2=0;
	int tmpReg2;
	int firstbyte, secondbyte;
	int oddline[] = {0x01, 0x02, 0x10, 0x20};
	int evenline[] = {0x04, 0x08, 0x40, 0x80};
	int lx,rx,ty,by;
	int hq,vq;
	int currow, curcol, bitslot;
	int vpix, hpix;
	int quads;
	int wipe;
	int dma;
	uint16 usdma;		// must be unsigned 16 bit
	HDC hdc;
	HBRUSH hbr1;
	HBRUSH oldhbr1;
	HBRUSH hbr2;
	HBRUSH oldhbr2;
	RECT rc;
	HANDLE hEvent = (HANDLE)pArguments;

	/*--init once--*/

	screenstate.hdcVid=GetDC(screenstate.hwndVid);
	hdc=screenstate.hdcVid;

	// we need a valid DC to draw in this window
	if (hdc){

#if DAZ_SCALE
		// init scaling
		SetMapMode(hdc, MM_ANISOTROPIC);
		SetWindowExtEx(hdc, HOST_RES,HOST_RES, NULL);
		// scale here once...
		GetClientRect(screenstate.hwndVid, &rc);
		SetViewportExtEx(hdc, rc.right, rc.bottom, NULL);
#endif

		for (dma=0;dma<2048;dma++){Scr[dma]=0;}
	}

	// let wait in ActivateVideoWindow know we finished init
	SetEvent(hEvent);


/*--enter thread loop----------------*/

  while(1){

	/*--suspend this loop--*/

	SuspendThread(screenstate.hThreadVid);

	/*--loop resumed--*/

	// if application is closing exit this thread
	if (screenstate.threadcmd==STOP){
		hdc=NULL;
		//_endthreadex(0);	// opt if _beginthreadex used
		//ExitThread(0);	// opt if CreateThread used
		return 0;			// exit thread or keep compiler happy
	}

	// if no valid DC supend this thread
	if (NULL == hdc)
		continue;

	// if our window has been messed up, we need to redraw
	wipe=screenstate.redraw;
	if (wipe){
		screenstate.redraw=0;
#if DAZ_SCALE
		// rescale client area
		GetClientRect(screenstate.hwndVid, &rc);
		SetViewportExtEx(hdc, rc.right, rc.bottom, NULL);
#endif
	}

	// get thread copy of 0e port reg
	thrReg1=screenstate.DazReg1;
	// get temp copy of 0f reg for comparisons
	tmpReg2=screenstate.DazReg2;

	// if dazzler card is or being disabled
	if (0==(thrReg1 & DAZ_CR1_ENABLED)){
		if (wipe || enabled){
			enabled=0;
			SetRect(&rc,0,0,HOST_RES,HOST_RES);
			FillRect(hdc,&rc,GetStockObject(BLACK_BRUSH));
		}
		// dazzler card is disabled, window is black
		// suspend the thread
		continue;
	}

	// dazzler is enabled
	// are there any other reasons to redraw the window?
	if ( // was disabled
		(0==enabled)
		|| // or display mode has changed
		((thrReg2 & DAZ_CR2_X4_2K)!=(tmpReg2 & DAZ_CR2_X4_2K))
		|| // or Daz is in high res and color/mode has changed
		((thrReg2 & DAZ_CR2_X4)&&((thrReg2 & DAZ_CR2_INCOLOR_IBGR)!=(tmpReg2 & DAZ_CR2_INCOLOR_IBGR)))
		)
	{
		enabled=1;
		wipe=1;
	}

	// whatever is in the registers/ram right now
	// right or wrong is what we're going to use
	thrReg2	= tmpReg2;
	quads	= ((thrReg2 & DAZ_CR2_2K) >> 5) + 1;	// 1x1 2x2
	hpix	= HOST_RES / ((((thrReg2 & DAZ_CR2_X4) >> 6) + 1) * 32 * quads);
	vpix 	= hpix;
	usdma	= (thrReg1 & DAZ_CR1_MEMWINDOW) << 9;
	// our buffers of ram start at 0 and can be indexed by one int
	for (dma=0;dma<2048;dma++){Tmp[dma]=Mem[usdma++];}
	dma		= 0;


	// 16 color modes
	if (0==(thrReg2 & DAZ_CR2_X4)){	// 16color 32x32(512), 16color 64x64(2048)

	/*--begin 16 color draw----*/

		ty=0;
		for (vq=0;vq<quads;vq++){
			hq=0;
			for (currow=0;currow<32;currow++){
				if (hq==2)dma-=512;
				lx=0;
				rx=hpix;
				for (hq=0;hq<quads;hq++){
					if (hq==1)dma+=496;
					for (curcol=0;curcol<16;curcol++){
						firstbyte=Tmp[dma];
						if (wipe || (firstbyte!=Scr[dma])){ // check all 8bits first (2 cells)
							Scr[dma]=firstbyte;
							secondbyte=(firstbyte&0xf0)>>4;		// right cell
							firstbyte&=0x0f;					// left cell low nybbles
							// create brushes
							if (thrReg2 & DAZ_CR2_INCOLOR){
								hbr1=(HBRUSH)CreateSolidBrush(colDazColors[firstbyte]);
								oldhbr1=SelectObject(hdc,hbr1);
								hbr2=(HBRUSH)CreateSolidBrush(colDazColors[secondbyte]);
								oldhbr2=SelectObject(hdc,hbr2);
							}
							else{
								hbr1=(HBRUSH)CreateSolidBrush(colDazBW[firstbyte]);
								oldhbr1=SelectObject(hdc,hbr1);
								hbr2=(HBRUSH)CreateSolidBrush(colDazBW[secondbyte]);
								oldhbr2=SelectObject(hdc,hbr2);
							}

							// Draw left cell...
							SetRect(&rc,lx,ty,lx+hpix,ty+vpix);
							FillRect(hdc,&rc,hbr1);
							// Draw right cell...
							SetRect(&rc,rx,ty,rx+hpix,ty+vpix);
 							FillRect(hdc,&rc,hbr2);
							// clean up brushes
							SelectObject(hdc,oldhbr1);
							DeleteObject(hbr1);
							SelectObject(hdc,oldhbr2);
							DeleteObject(hbr2);
						} //else
						lx=rx+hpix;
						rx=lx+hpix;
						dma+=1;
					}
				}
				ty+=vpix;
			}
		}
	}
	/*--end 16 color--*/

	else {	// this is route for 'low color' modes  1color 64x64(512), 1color 128x128 (2048)
		// X 4 drawing mode
		/*
			The 128x128 1color Screen is drawn in quadrants:

								0 | 1
								-----
								2 | 3

			addresses:	   0- 511 |  512-1023	(2k buffer)
						=====================
						1024-1535 | 1536-2047

			Each quadrant is 16x32 bytes in size, so quadrant 0 looks like:

				Line00/01:   0- 15
				Line02/03:  16- 31
				 . . .
				Line62/63: 496-511

			Each byte of memory represents eight ajacent picture elements:

					  --- BYTE 0 ---  --- BYTE 1 ---
			Line n:   D0  D1  D4  D5  D0  D1  D4  D5
			Line n+1: D2  D3  D6  D7  D2  D3  D6  D7

			So, the effective size, in elements, of each quadrant is 64x64,
			producing a total element array of 128x128.
		*/

	/*--begin 1 color draw--*/

		// Create brushes
		if (thrReg2 & DAZ_CR2_INCOLOR){
			hbr1 = (HBRUSH)CreateSolidBrush(colDazColors[thrReg2 & DAZ_CR2_IBGR]);
		//	if (thrReg2 & DAZ_CR2_INTENSITY)
		//		hbr2 = (HBRUSH)CreateSolidBrush(crBrBlack);
		//	else
		//		hbr2 = (HBRUSH)CreateSolidBrush(crBlack);
		}
		else{
			hbr1 = (HBRUSH)CreateSolidBrush(colDazBW[thrReg2 & DAZ_CR2_IBGR]);
		//	hbr2 = (HBRUSH)CreateSolidBrush(crGray0);
		}
		hbr2 = (HBRUSH)CreateSolidBrush(colDazBW[0]);	//black

 		oldhbr1 = SelectObject(hdc, hbr1);
 		oldhbr2 = SelectObject(hdc, hbr2);

		// if screen/window parameters have changed
		// just erase the whole window with background brush
		if (wipe){
			SetRect(&rc,0,0,HOST_RES,HOST_RES);
			FillRect(hdc,&rc,hbr2);
		}

		ty=0;
		by=vpix;
		for (vq=0;vq<quads;vq++){
			hq=0;
			for (currow=0;currow<32;currow++){
				if (hq==2)dma-=512;
				lx=0;
				for (hq=0;hq<quads;hq++){
					if(hq==1)dma+=496;
					for (curcol=0;curcol<16;curcol++){
						firstbyte=Tmp[dma];
						if (wipe){ // we already wiped the background, redraw the foreground
							Scr[dma]=firstbyte;
							for (bitslot=0;bitslot<4; bitslot++){
								// Draw top cell (maybe)
								if (firstbyte & oddline[bitslot]){
									SetRect(&rc,lx,ty,lx+hpix,ty+vpix);
									FillRect(hdc,&rc,hbr1);
								}
								// Draw bottom cell (maybe)
								if (firstbyte & evenline[bitslot]){
									SetRect(&rc,lx,by,lx+hpix,by+vpix);
									FillRect(hdc,&rc,hbr1);
								}
								lx+=hpix;
							}
						}
						else { // no wipe, just redraw the bits that have changed
							if (firstbyte!=Scr[dma]){ // if 1 of 8 bits has changed check all 8
								for (bitslot=0;bitslot<4; bitslot++){
									// Draw top cell (maybe)
									if ((firstbyte & oddline[bitslot])!=(Scr[dma] & oddline[bitslot])){
										SetRect(&rc,lx,ty,lx+hpix,ty+vpix);
										if (firstbyte & oddline[bitslot]) FillRect(hdc,&rc,hbr1);
										else FillRect(hdc,&rc,hbr2);
									}
									// Draw bottom cell (maybe)
									if ((firstbyte & evenline[bitslot])!=(Scr[dma] & evenline[bitslot])){
										SetRect(&rc,lx,by,lx+hpix,by+vpix);
										if (firstbyte & evenline[bitslot]) FillRect(hdc,&rc,hbr1);
										else FillRect(hdc,&rc,hbr2);
									}
									lx+=hpix;
								}
								Scr[dma]=firstbyte;
							}else{
								lx+=(4*hpix);
							}
						}
						dma+=1;
					}
				}
				ty=by+vpix;
				by=ty+vpix;
			}
		}
		// clean-up our brushes
		SelectObject(hdc,oldhbr1);
		DeleteObject(hbr1);
		SelectObject(hdc,oldhbr2);
		DeleteObject(hbr2);
	}
	/*--end 1 color--*/

  } // while(1)

  return 0;	// keep compiler happy
}


/*--  HOST_OpenJoystick  --------------------------------------

	Open the joystick device.

	Params:		None
	Uses:		Nothing
	Returns:	Device ID
	Comments:
-------------------------------------------------------------*/
static INT HOST_OpenJoystick( void ){

	// Let's capture only one joystick for now and use it for the
	// primary Joybox stick
	JOYCAPS joycaps1;
	JOYINFO joyinfo;	 // reusable
	UINT wNumDevs, wDeviceID1;
	BOOL bDev1Attached, bDev2Attached; //(MMRESULT)

	// any joystick drivers?
	if(0==(wNumDevs = joyGetNumDevs()))
		return MMSYSERR_NODRIVER;

	// Since you could concevably have 16 joysticks, this probably
	// should be rewritten as a loop through an array to test and
	// assign up to 'n' joystick IDs.
	//
	// JOYSTICKIDx can be 0-15. We test only the first two and select
	// the one that responds.
	bDev1Attached = (JOYERR_UNPLUGGED != joyGetPos(JOYSTICKID1, &joyinfo));
	bDev2Attached = (wNumDevs == 2) && (JOYERR_UNPLUGGED != joyGetPos(JOYSTICKID2, &joyinfo));
	if(bDev1Attached || bDev2Attached)	// assign JoyIDs
		wDeviceID1 = bDev1Attached ? JOYSTICKID1 : JOYSTICKID2;
	else
		return JOYERR_UNPLUGGED;

	// We have joyid 0 or 1, what does it support?
	if (JOYERR_NOERROR==joyGetDevCaps(wDeviceID1, &joycaps1, sizeof(JOYCAPS))){
		JOYaxis    =joycaps1.wMaxAxes;
		JOYbuttons =joycaps1.wMaxButtons;
		JOYDeviceID=wDeviceID1;
		return JOYERR_NOERROR;
	}
	else {	// oops
		return MMSYSERR_INVALPARAM;
	}

}
/*  end of file: dazzler.c  */
