// _______________________________________________________________________________
//
//	 - WiiYourself! - native C++ Wiimote library  v1.15
//	  (c) gl.tter 2007-10 - http://gl.tter.org
//
//	  see License.txt for conditions of use.  see History.txt for change log.
// _______________________________________________________________________________
//
//  wiimote.cpp  (tab = 4 spaces)

// VC-specifics:
#ifdef _MSC_VER 
 // disable warning "C++ exception handler used, but unwind semantics are not enabled."
 //				     in <xstring> (I don't use it - or just enable C++ exceptions)
# pragma warning(disable: 4530)
//  auto-link with the necessary libs
# pragma comment(lib, "setupapi.lib")	
# pragma comment(lib, "hid.lib")		// for HID API (from DDK)
# pragma comment(lib, "winmm.lib")		// for timeGetTime()
#endif // _MSC_VER

#include "wiimote.h"
#include <setupapi.h>
extern "C" {
# ifdef __MINGW32__
#  include <ddk/hidsdi.h>// from WinDDK
# else
#  include <hidsdi.h>
# endif
}
#include <sys/types.h>	// for _stat
#include <sys/stat.h>	// "
#include <process.h>	// for _beginthreadex()
#ifdef __BORLANDC__
# include <cmath.h>		// for orientation
#else
# include <math.h>		// "
#endif
#include <mmreg.h>		// for WAVEFORMATEXTENSIBLE
#include <mmsystem.h>	// for timeGetTime()

// apparently not defined in some compilers:
#ifndef min
# define min(a,b)	(((a) < (b)) ? (a) : (b))
#endif
// ------------------------------------------------------------------------------------
// helpers
// ------------------------------------------------------------------------------------
template<class T> inline T sign(const T& val) { return (val < 0) ? T(-1) : T(1); }
template<class T> inline T square(const T& val) { return val * val; }
#define ARRAY_ENTRIES(array)	(sizeof(array)/sizeof(array[0]))

// ------------------------------------------------------------------------------------
//  Tracing & Debugging
// ------------------------------------------------------------------------------------
#define PREFIX	_T("WiiYourself! : ")

// comment these to auto-strip their code from the library:
//  (they currently use OutputDebugString() via _TRACE() - change to suit)
#if (_MSC_VER >= 1400) // VC 2005+ (earlier versions don't support variable args)
# define TRACE(fmt, ...) _TRACE(PREFIX          fmt          _T("\n"), __VA_ARGS__)
# define WARN(fmt, ...)  _TRACE(PREFIX _T("* ") fmt _T(" *") _T("\n"), __VA_ARGS__)
#elif defined(__MINGW32__)
# define TRACE(fmt, ...) _TRACE(PREFIX          fmt          _T("\n") , ##__VA_ARGS__)
# define WARN(fmt, ...)  _TRACE(PREFIX _T("* ") fmt _T(" *") _T("\n") , ##__VA_ARGS__)
#endif
// uncomment any of these for deeper debugging:
//#define DEEP_TRACE(fmt, ...) _TRACE(PREFIX _T("|") fmt _T("\n"), __VA_ARGS__)    // VC 2005+
//#define DEEP_TRACE(fmt, ...) _TRACE(PREFIX _T("|") fmt _T("\n") , ##__VA_ARGS__) // mingw
//#define BEEP_DEBUG_READS
//#define BEEP_DEBUG_WRITES
//#define BEEP_ON_ORIENTATION_ESTIMATE
//#define BEEP_ON_PERIODIC_STATUSREFRESH

// internals: auto-strip code from the macros if they weren't defined
#ifndef TRACE
# define TRACE(...) (void)0
#endif
#ifndef DEEP_TRACE
# define DEEP_TRACE(...) (void)0
#endif
#ifndef WARN
# define WARN(...) (void)0
#endif
// ------------------------------------------------------------------------------------
static void __cdecl _TRACE(const TCHAR* fmt, ...)
{
	static TCHAR buffer[256];
	if (!fmt) return;

	va_list	 argptr;
	va_start(argptr, fmt);
#if (_MSC_VER >= 1400) // VC 2005+
	_vsntprintf_s(buffer, ARRAY_ENTRIES(buffer), _TRUNCATE, fmt, argptr);
#else
	_vsntprintf(buffer, ARRAY_ENTRIES(buffer), fmt, argptr);
#endif
	va_end(argptr);

	OutputDebugString(buffer);
}

// ------------------------------------------------------------------------------------
//  wiimote
// ------------------------------------------------------------------------------------
// class statics
HMODULE		   wiimote::HidDLL = NULL;
unsigned	   wiimote::_TotalCreated = 0;
unsigned	   wiimote::_TotalConnected = 0;
hidwrite_ptr   wiimote::_HidD_SetOutputReport = NULL;

// (keep in sync with 'speaker_freq'):
const unsigned wiimote::FreqLookup[TOTAL_FREQUENCIES] =
{ 0, 4200, 3920, 3640, 3360,
  3130,	2940, 2760, 2610, 2470 };

const TCHAR* const wiimote::ButtonNameFromBit[TOTAL_BUTTON_BITS] =
{ _T("Left") , _T("Right"), _T("Down"), _T("Up"),
  _T("Plus") , _T("??")   , _T("??")  , _T("??") ,
  _T("Two")  , _T("One")  , _T("B")   , _T("A") ,
  _T("Minus"), _T("??")   , _T("??")  , _T("Home") };

const TCHAR* const wiimote::ClassicButtonNameFromBit[TOTAL_BUTTON_BITS] =
{ _T("??")   , _T("TrigR")  , _T("Plus") , _T("Home"),
  _T("Minus"), _T("TrigL") , _T("Down") , _T("Right") ,
  _T("Up")   , _T("Left")   , _T("ZR")   , _T("X") ,
  _T("A")    , _T("Y")      , _T("B")    , _T("ZL") };
// ------------------------------------------------------------------------------------
wiimote::wiimote()
	:
	DataRead(CreateEvent(NULL, FALSE, FALSE, NULL)),
	Handle(INVALID_HANDLE_VALUE),
	ReportType(IN_BUTTONS),
	bStatusReceived(false), // for output method detection
	bConnectInProgress(true),
	bInitInProgress(false),
	bEnablingMotionPlus(false),
	bConnectionLost(false), // set if write fails after connection
	bMotionPlusDetected(false),
	bMotionPlusEnabled(false),
	bMotionPlusExtension(false),
	bCalibrateAtRest(false),
	bUseHIDwrite(false), // if OS supports it
	ChangedCallback(NULL),
	CallbackTriggerFlags(CHANGED_ALL),
	InternalChanged(NO_CHANGE),
	CurrentSample(NULL),
	HIDwriteThread(NULL),
	ReadParseThread(NULL),
	SampleThread(NULL),
	AsyncRumbleThread(NULL),
	AsyncRumbleTimeout(0),
	UniqueID(0)	// not _guaranteed_ unique, see comments in header
#ifdef ID2_FROM_DEVICEPATH		// (see comments in header)
	// UniqueID2			 (0)	
#endif
{
	_ASSERT(DataRead != INVALID_HANDLE_VALUE);

	// if this is the first wiimote object, detect & enable HID write support
	if (++_TotalCreated == 1)
	{
		HidDLL = LoadLibrary(_T("hid.dll"));
		_ASSERT(HidDLL);
		if (!HidDLL)
			WARN(_T("Couldn't load hid.dll - shouldn't happen!"));
		else {
			_HidD_SetOutputReport = (hidwrite_ptr)
				GetProcAddress(HidDLL, "HidD_SetOutputReport");
			if (_HidD_SetOutputReport)
				TRACE(_T("OS supports HID writes."));
			else
				TRACE(_T("OS doesn't support HID writes."));
		}
	}

	// clear our public and private state data completely (including deadzones)
	Clear(true);
	Internal.Clear(true);

	// and the state recording vars
	memset(&Recording, 0, sizeof(Recording));

	// for overlapped IO (Read/WriteFile)
	memset(&Overlapped, 0, sizeof(Overlapped));
	Overlapped.hEvent = DataRead;
	Overlapped.Offset =
		Overlapped.OffsetHigh = 0;

	// for async HID output method
	InitializeCriticalSection(&HIDwriteQueueLock);
	// for polling
	InitializeCriticalSection(&StateLock);

	// request millisecond timer accuracy
	timeBeginPeriod(1);
}
// ------------------------------------------------------------------------------------
wiimote::~wiimote()
{
	Disconnect();

	// events & critical sections are kept open for the lifetime of the object,
	//  so tidy them up here:
	if (DataRead != INVALID_HANDLE_VALUE)
		CloseHandle(DataRead);

	DeleteCriticalSection(&HIDwriteQueueLock);
	DeleteCriticalSection(&StateLock);

	// tidy up timer accuracy request
	timeEndPeriod(1);

	// release HID DLL (for dynamic HID write method)
	if ((--_TotalCreated == 0) && HidDLL)
	{
		FreeLibrary(HidDLL);
		HidDLL = NULL;
		_HidD_SetOutputReport = NULL;
	}
}

// ------------------------------------------------------------------------------------
bool wiimote::Connect(unsigned wiimote_index, bool force_hidwrites)
{
	if (wiimote_index == FIRST_AVAILABLE)
		TRACE(_T("Connecting first available Wiimote:"));
	else
		TRACE(_T("Connecting Wiimote %u:"), wiimote_index);

	// auto-disconnect if user is being naughty
	if (IsConnected())
		Disconnect();

	// get the GUID of the HID class
	GUID guid;
	HidD_GetHidGuid(&guid);

	// get a handle to all devices that are part of the HID class
	// Brian: Fun fact:  DIGCF_PRESENT worked on my machine just fine.  I reinstalled
	//   Vista, and now it no longer finds the Wiimote with that parameter enabled...
	HDEVINFO dev_info = SetupDiGetClassDevs(&guid, NULL, NULL, DIGCF_DEVICEINTERFACE);// | DIGCF_PRESENT);
	if (!dev_info) {
		WARN(_T("couldn't get device info"));
		return false;
	}

	// enumerate the devices
	SP_DEVICE_INTERFACE_DATA didata;
	didata.cbSize = sizeof(didata);

	unsigned index = 0;
	unsigned wiimotes_found = 0;
	while (SetupDiEnumDeviceInterfaces(dev_info, NULL, &guid, index, &didata))
	{
		// get the buffer size for this device detail instance
		DWORD req_size = 0;
		SetupDiGetDeviceInterfaceDetail(dev_info, &didata, NULL, 0, &req_size, NULL);

		// (bizarre way of doing it) create a buffer large enough to hold the
		//  fixed-size detail struct components, and the variable string size
		SP_DEVICE_INTERFACE_DETAIL_DATA *didetail =
			(SP_DEVICE_INTERFACE_DETAIL_DATA*) new BYTE[req_size];
		_ASSERT(didetail);
		didetail->cbSize = sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA);

		// now actually get the detail struct
		if (!SetupDiGetDeviceInterfaceDetail(dev_info, &didata, didetail,
			req_size, &req_size, NULL)) {
			WARN(_T("couldn't get devinterface info for %u"), index);
			break;
		}

		// open a shared handle to the device to query it (this will succeed even
		//  if the wiimote is already Connect()'ed)
		DEEP_TRACE(_T(".. querying device %s"), didetail->DevicePath);
		Handle = CreateFile(didetail->DevicePath, 0, FILE_SHARE_READ | FILE_SHARE_WRITE,
			NULL, OPEN_EXISTING, 0, NULL);
		if (Handle == INVALID_HANDLE_VALUE) {
			DEEP_TRACE(_T(".... failed with err %x (probably harmless)."),
				GetLastError());
			goto skip;
		}

		// get the device attributes
		HIDD_ATTRIBUTES attrib;
		attrib.Size = sizeof(attrib);
		if (HidD_GetAttributes(Handle, &attrib))
		{
			// is this a wiimote?
			if ((attrib.VendorID != VID) || (attrib.ProductID != PID))
				goto skip;

			// yes, but is it the one we're interested in?
			++wiimotes_found;
			if ((wiimote_index != FIRST_AVAILABLE) &&
				(wiimote_index != wiimotes_found))
				goto skip;

			// the wiimote is installed, but it may not be currently paired:
			if (wiimote_index == FIRST_AVAILABLE)
				TRACE(_T(".. opening Wiimote %u:"), wiimotes_found);
			else
				TRACE(_T(".. opening:"));


			// re-open the handle, but this time we don't allow write sharing
			//  (that way subsequent calls can still _discover_ wiimotes above, but
			//   will correctly fail here if they're already connected)
			CloseHandle(Handle);

			// note this also means that if another application has already opened
			//  the device, the library can no longer connect it (this may happen
			//  with software that enumerates all joysticks in the system, because
			//  even though the wiimote is not a standard joystick (and can't
			//  be read as such), it unfortunately announces itself to the OS
			//  as one.  The SDL library was known to do grab wiimotes like this.
			//  If you cannot stop the application from doing it, you may change the
			//  call below to open the device in full shared mode - but then the
			//  library can no longer detect if you've already connected a device
			//  and will allow you to connect it twice!  So be careful ...
			Handle = CreateFile(didetail->DevicePath, GENERIC_READ | GENERIC_WRITE,
				FILE_SHARE_READ | FILE_SHARE_WRITE,
				NULL, OPEN_EXISTING,
				FILE_FLAG_OVERLAPPED, NULL);
			if (Handle == INVALID_HANDLE_VALUE) {
				TRACE(_T(".... failed with err %x"), GetLastError());
				goto skip;
			}

			// clear the wiimote state & buffers
			Clear(false);		// preserves existing deadzones
			Internal.Clear(false);		// "
			InternalChanged = NO_CHANGE;
			memset(ReadBuff, 0, sizeof(ReadBuff));
			bConnectionLost = false;
			bConnectInProgress = true; // don't parse extensions or request regular
									   //  updates until complete
			// enable async reading
			BeginAsyncRead();

			// autodetect which write method the Bluetooth stack supports,
			//  by requesting the wiimote status report:
			if (force_hidwrites && !_HidD_SetOutputReport) {
				TRACE(_T(".. can't force HID writes (not supported)"));
				force_hidwrites = false;
			}

			if (force_hidwrites)
				TRACE(_T(".. (HID writes forced)"));
			else {
				//  - try WriteFile() first as it's the most efficient (it uses
				//     harware interrupts where possible and is async-capable):
				bUseHIDwrite = false;
				RequestStatusReport();
				//  and wait for the report to arrive:
				DWORD last_time = timeGetTime();
				while (!bStatusReceived && ((timeGetTime() - last_time) < 500))
					Sleep(10);
				TRACE(_T(".. WriteFile() %s."), bStatusReceived ? _T("succeeded") :
					_T("failed"));
			}

			// try HID write method (if supported)
			if (!bStatusReceived && _HidD_SetOutputReport)
			{
				bUseHIDwrite = true;
				RequestStatusReport();
				// wait for the report to arrive:
				DWORD last_time = timeGetTime();
				while (!bStatusReceived && ((timeGetTime() - last_time) < 500))
					Sleep(10);
				// did we get it?
				TRACE(_T(".. HID write %s."), bStatusReceived ? _T("succeeded") :
					_T("failed"));
			}

			// still failed?
			if (!bStatusReceived) {
				WARN(_T("output failed - wiimote is not connected (or confused)."));
				Disconnect();
				goto skip;
			}

			//Sleep(500);
						// reset it
			Reset();

			// read the wiimote calibration info
			ReadCalibration();

			// allow the result(s) to come in (so that the caller can immediately test
			//  MotionPlusConnected()
			Sleep(300); // note, don't need it on my system, better to be safe though

			// connected succesfully:
			_TotalConnected++;

			// use the first incomding analogue sensor values as the 'at rest'
			//  offsets (only supports the Balance Board currently)
			bCalibrateAtRest = true;

			// refresh the public state from the internal one (so that everything
			//  is available straight away
			RefreshState();

			// attempt to construct a unique hardware ID from the calibration
			//  data bytes (this is obviously not guaranteed to be unique across
			//  all devices, but may work fairly well in practice... ?)
			memcpy(&UniqueID, &CalibrationInfo, sizeof(CalibrationInfo));

			_ASSERT(UniqueID != 0); // if this fires, the calibration data didn't
									//  arrive - this shouldn't happen

#ifdef ID2_FROM_DEVICEPATH		// (see comments in header)
			// create a 2nd alternative id by simply adding all the characters
			//  in the device path to create a single number
			UniqueID2 = 0;
			for (unsigned index = 0; index < _tcslen(didetail->DevicePath); index++)
				UniqueID2 += didetail->DevicePath[index];
#endif
			// and show when we want to trigger the next periodic status request
			//  (for battery level and connection loss detection)
			NextStatusTime = timeGetTime() + REQUEST_STATUS_EVERY_MS;
			NextMPlusDetectTime = timeGetTime() + DETECT_MPLUS_EVERY_MS;
			MPlusDetectCount = DETECT_MPLUS_COUNT;

			// tidy up
			delete[](BYTE*)didetail;
			break;
		}
	skip:
		// tidy up
		delete[](BYTE*)didetail;

		if (Handle != INVALID_HANDLE_VALUE) {
			CloseHandle(Handle);
			Handle = INVALID_HANDLE_VALUE;
		}
		// if this was the specified wiimote index, abort
		if ((wiimote_index != FIRST_AVAILABLE) &&
			(wiimote_index == (wiimotes_found - 1)))
			break;

		index++;
	}

	// clean up our list
	SetupDiDestroyDeviceInfoList(dev_info);

	bConnectInProgress = false;
	if (IsConnected()) {
		TRACE(_T(".. connected!"));
		// notify the callbacks (if requested to do so)
		if (CallbackTriggerFlags & CONNECTED)
		{
			ChangedNotifier(CONNECTED, Internal);
			if (ChangedCallback)
				ChangedCallback(*this, CONNECTED, Internal);
		}
		return true;
	}
	TRACE(_T(".. connection failed."));
	return false;
}
// ------------------------------------------------------------------------------------
void wiimote::CalibrateAtRest()
{
	_ASSERT(IsConnected());
	if (!IsConnected())
		return;

	// the app calls this to remove 'at rest' offsets from the analogue sensor
	//  values (currently only works for the Balance Board):
	if (IsBalanceBoard()) {
		TRACE(_T(".. removing 'at rest' BBoard offsets."));
		Internal.BalanceBoard.AtRestKg = Internal.BalanceBoard.Kg;
		RefreshState();
	}
}
// ------------------------------------------------------------------------------------
void wiimote::Disconnect()
{
	if (Handle == INVALID_HANDLE_VALUE)
		return;

	TRACE(_T("Disconnect()."));

	if (IsConnected())
	{
		_ASSERT(_TotalConnected > 0); // sanity
		_TotalConnected--;

		if (!bConnectionLost)
			Reset();
	}

	CloseHandle(Handle);
	Handle = INVALID_HANDLE_VALUE;
	UniqueID = 0;
#ifdef ID2_FROM_DEVICEPATH		// (see comments in header)
	UniqueID2 = 0;
#endif

	// close the read thread
	if (ReadParseThread) {
		// unblock it so it can realise we're closing and exit straight away
		SetEvent(DataRead);
		WaitForSingleObject(ReadParseThread, 3000);
		CloseHandle(ReadParseThread);
		ReadParseThread = NULL;
	}
	// close the rumble thread
	if (AsyncRumbleThread) {
		WaitForSingleObject(AsyncRumbleThread, 3000);
		CloseHandle(AsyncRumbleThread);
		AsyncRumbleThread = NULL;
		AsyncRumbleTimeout = 0;
	}
	// and the sample streaming thread
	if (SampleThread) {
		WaitForSingleObject(SampleThread, 3000);
		CloseHandle(SampleThread);
		SampleThread = NULL;
	}

#ifndef USE_DYNAMIC_HIDQUEUE
	HID.Deallocate();
#endif

	bStatusReceived = false;

	// and clear the state
	Clear(false); // (preserves deadzones)
	Internal.Clear(false); // "
	InternalChanged = NO_CHANGE;
}
// ------------------------------------------------------------------------------------
void wiimote::Reset()
{
	TRACE(_T("Resetting wiimote."));

	if (bMotionPlusEnabled)
		DisableMotionPlus();

	// stop updates (by setting report type to non-continuous, buttons-only)
	if (IsBalanceBoard())
		SetReportType(IN_BUTTONS_BALANCE_BOARD, false);
	else
		SetReportType(IN_BUTTONS, false);

	SetRumble(false);
	SetLEDs(0x00);
	//	MuteSpeaker  (true);
	EnableSpeaker(false);

	Sleep(150); // avoids loosing the extension calibration data on Connect()
}
// ------------------------------------------------------------------------------------
unsigned __stdcall wiimote::ReadParseThreadfunc(void* param)
{
	// this thread waits for the async ReadFile() to deliver data & parses it.
	//  it also requests periodic status updates, deals with connection loss
	//  and ends state recordings with a specific duration:
	_ASSERT(param);
	wiimote    &remote = *(wiimote*)param;
	OVERLAPPED &overlapped = remote.Overlapped;
	unsigned exit_code = 0; // (success)

	while (1)
	{
		// wait until the overlapped read completes, or the timeout is reached:
		DWORD wait = WaitForSingleObject(overlapped.hEvent, 500);

		// before we deal with the result, let's do some housekeeping:

		//  if we were recently Disconect()ed, exit now
		if (remote.Handle == INVALID_HANDLE_VALUE) {
			DEEP_TRACE(_T("read thread: wiimote was disconnected"));
			break;
		}
		//  ditto if the connection was lost (eg. through a failed write)
		if (remote.bConnectionLost)
		{
		connection_lost:
			TRACE(_T("read thread: connection to wiimote was lost"));
			remote.Disconnect();
			remote.InternalChanged = (state_change_flags)
				(remote.InternalChanged | CONNECTION_LOST);
			// report via the callback (if any)
			if (remote.CallbackTriggerFlags & CONNECTION_LOST)
			{
				remote.ChangedNotifier(CONNECTION_LOST, remote.Internal);
				if (remote.ChangedCallback)
					remote.ChangedCallback(remote, CONNECTION_LOST, remote.Internal);
			}
			break;
		}

		DWORD time = timeGetTime();
		//  periodic events (but not if we're streaming audio,
		//					 we don't want to cause a glitch)
		if (remote.IsConnected() && !remote.bInitInProgress &&
			!remote.IsPlayingAudio())
		{
			// status request due? 
			if (time > remote.NextStatusTime)
			{
#ifdef BEEP_ON_PERIODIC_STATUSREFRESH
				Beep(2000, 50);
#endif
				remote.RequestStatusReport();
				// and schedule the next one
				remote.NextStatusTime = time + REQUEST_STATUS_EVERY_MS;
			}
			// motion plus detection due?
			if (!remote.IsBalanceBoard() &&
				//			   !remote.bConnectInProgress   &&
				!remote.bMotionPlusExtension &&
				(remote.Internal.ExtensionType != MOTION_PLUS) &&
				(remote.Internal.ExtensionType != PARTIALLY_INSERTED) &&
				(time > remote.NextMPlusDetectTime))
			{
				remote.DetectMotionPlusExtensionAsync();
				// we try several times in quick succession before the next
				//  delay:
				if (--remote.MPlusDetectCount == 0) {
					remote.NextMPlusDetectTime = time + DETECT_MPLUS_EVERY_MS;
					remote.MPlusDetectCount = DETECT_MPLUS_COUNT;
#ifdef _DEBUG
					TRACE(_T("--"));
#endif
				}
			}
		}

		//  if we're state recording and have reached the specified duration, stop
		if (remote.Recording.bEnabled && (remote.Recording.EndTimeMS != UNTIL_STOP) &&
			(time >= remote.Recording.EndTimeMS))
			remote.Recording.bEnabled = false;

		// now handle the wait result:
		//  did the wait time out?
		if (wait == WAIT_TIMEOUT) {
			DEEP_TRACE(_T("read thread: timed out"));
			continue; // wait again
		}
		//  did an error occurr?
		if (wait != WAIT_OBJECT_0) {
			DEEP_TRACE(_T("read thread: error waiting!"));
			remote.bConnectionLost = true;
			// deal with it straight away to avoid a longer delay
			goto connection_lost;
		}

		// data was received:
#ifdef BEEP_DEBUG_READS
		Beep(500, 1);
#endif
		DWORD read = 0;
		//  get the data read result
		GetOverlappedResult(remote.Handle, &overlapped, &read, TRUE);
		//  if we read data, parse it
		if (read) {
			DEEP_TRACE(_T("read thread: parsing data"));
			remote.OnReadData(read);
		}
		else
			DEEP_TRACE(_T("read thread: didn't get any data??"));
	}

	TRACE(_T("(ending read thread)"));
#ifdef BEEP_DEBUG_READS
	if (exit_code != 0)
		Beep(200, 1000);
#endif
	return exit_code;
}
// ------------------------------------------------------------------------------------
bool wiimote::BeginAsyncRead()
{
	// (this is also called before we're fully connected)
	if (Handle == INVALID_HANDLE_VALUE)
		return false;

	DEEP_TRACE(_T(".. starting async read"));
#ifdef BEEP_DEBUG_READS
	Beep(1000, 1);
#endif

	DWORD read;
	if (!ReadFile(Handle, ReadBuff, REPORT_LENGTH, &read, &Overlapped)) {
		DWORD err = GetLastError();
		if (err != ERROR_IO_PENDING) {
			DEEP_TRACE(_T(".... ** ReadFile() failed! **"));
			return false;
		}
	}

	// launch the completion wait/callback thread
	if (!ReadParseThread) {
		ReadParseThread = (HANDLE)_beginthreadex(NULL, 0, ReadParseThreadfunc,
			this, 0, NULL);
		DEEP_TRACE(_T(".... creating read thread"));
		_ASSERT(ReadParseThread);
		if (!ReadParseThread)
			return false;
		SetThreadPriority(ReadParseThread, WORKER_THREAD_PRIORITY);
	}

	// if ReadFile completed while we called, signal the thread to proceed
	if (read) {
		DEEP_TRACE(_T(".... got data right away"));
		SetEvent(DataRead);
	}
	return true;
}
// ------------------------------------------------------------------------------------
void wiimote::OnReadData(DWORD bytes_read)
{
	_ASSERT(bytes_read == REPORT_LENGTH);

	// copy our input buffer
	BYTE buff[REPORT_LENGTH];
	memcpy(buff, ReadBuff, bytes_read);

	// start reading again
	BeginAsyncRead();

	// parse it
	ParseInput(buff);
}
// ------------------------------------------------------------------------------------
void wiimote::SetReportType(input_report type, bool continuous)
{
	_ASSERT(IsConnected());
	if (!IsConnected())
		return;

	// the balance board only uses one type of report
	_ASSERT(!IsBalanceBoard() || type == IN_BUTTONS_BALANCE_BOARD);
	if (IsBalanceBoard() && (type != IN_BUTTONS_BALANCE_BOARD))
		return;

#ifdef TRACE
#define TYPE2NAME(_type)	(type==_type)? _T(#_type)
	const TCHAR* name = TYPE2NAME(IN_BUTTONS) :
		TYPE2NAME(IN_BUTTONS_ACCEL_IR) :
		TYPE2NAME(IN_BUTTONS_ACCEL_EXT) :
		TYPE2NAME(IN_BUTTONS_ACCEL_IR_EXT) :
		TYPE2NAME(IN_BUTTONS_BALANCE_BOARD) :
		_T("(unknown??)");
	TRACE(_T("ReportType: %s (%s)"), name, (continuous ? _T("continuous") :
		_T("non-continuous")));
#endif
	ReportType = type;

	switch (type)
	{
	case IN_BUTTONS_ACCEL_IR:
		EnableIR(wiimote_state::ir::EXTENDED);
		break;
	case IN_BUTTONS_ACCEL_IR_EXT:
		EnableIR(wiimote_state::ir::BASIC);
		break;
	default:
		DisableIR();
		break;
	}

	BYTE buff[REPORT_LENGTH] = { 0 };
	buff[0] = OUT_TYPE;
	buff[1] = (continuous ? 0x04 : 0x00) | GetRumbleBit();
	buff[2] = (BYTE)type;
	WriteReport(buff);
	//	Sleep(15);
}
// ------------------------------------------------------------------------------------
void wiimote::SetLEDs(BYTE led_bits)
{
	_ASSERT(IsConnected());
	if (!IsConnected() || bInitInProgress)
		return;

	_ASSERT(led_bits <= 0x0f);
	led_bits &= 0xf;

	BYTE buff[REPORT_LENGTH] = { 0 };
	buff[0] = OUT_LEDs;
	buff[1] = (led_bits << 4) | GetRumbleBit();
	WriteReport(buff);

	Internal.LED.Bits = led_bits;
}
// ------------------------------------------------------------------------------------
void wiimote::SetRumble(bool on)
{
	_ASSERT(IsConnected());
	if (!IsConnected())
		return;

	if (Internal.bRumble == on)
		return;

	Internal.bRumble = on;

	// if we're streaming audio, we don't need to send a report (sending it makes
	// the audio glitch, and the rumble bit is sent with every report anyway)
	if (IsPlayingAudio())
		return;

	BYTE buff[REPORT_LENGTH] = { 0 };
	buff[0] = OUT_STATUS;
	buff[1] = on ? 0x01 : 0x00;
	WriteReport(buff);
}
// ------------------------------------------------------------------------------------
unsigned __stdcall wiimote::AsyncRumbleThreadfunc(void* param)
{
	// auto-disables rumble after x milliseconds:
	_ASSERT(param);
	wiimote &remote = *(wiimote*)param;

	while (remote.IsConnected())
	{
		if (remote.AsyncRumbleTimeout)
		{
			DWORD current_time = timeGetTime();
			if (current_time >= remote.AsyncRumbleTimeout)
			{
				if (remote.Internal.bRumble)
					remote.SetRumble(false);
				remote.AsyncRumbleTimeout = 0;
			}
			Sleep(1);
		}
		else
			Sleep(4);
	}
	return 0;
}
// ------------------------------------------------------------------------------------
void wiimote::RumbleForAsync(unsigned milliseconds)
{
	// rumble for a fixed amount of time
	_ASSERT(IsConnected());
	if (!IsConnected())
		return;

	SetRumble(true);

	// show how long thread should wait to disable rumble again
	// (it it's currently rumbling it will just extend the time)
	AsyncRumbleTimeout = timeGetTime() + milliseconds;

	// create the thread?
	if (AsyncRumbleThread)
		return;

	AsyncRumbleThread = (HANDLE)_beginthreadex(NULL, 0, AsyncRumbleThreadfunc, this,
		0, NULL);
	_ASSERT(AsyncRumbleThread);
	if (!AsyncRumbleThread) {
		WARN(_T("couldn't create rumble thread!"));
		return;
	}
	SetThreadPriority(AsyncRumbleThread, WORKER_THREAD_PRIORITY);
}
// ------------------------------------------------------------------------------------
void wiimote::RequestStatusReport()
{
	// (this can be called before we're fully connected)
	_ASSERT(Handle != INVALID_HANDLE_VALUE);
	if (Handle == INVALID_HANDLE_VALUE)
		return;

	BYTE buff[REPORT_LENGTH] = { 0 };
	buff[0] = OUT_STATUS;
	buff[1] = GetRumbleBit();
	WriteReport(buff);
}
// ------------------------------------------------------------------------------------
bool wiimote::ReadAddress(int address, short size)
{
	// asynchronous
	BYTE buff[REPORT_LENGTH] = { 0 };
	buff[0] = OUT_READMEMORY;
	buff[1] = (BYTE)(((address & 0xff000000) >> 24) | GetRumbleBit());
	buff[2] = (BYTE)((address & 0x00ff0000) >> 16);
	buff[3] = (BYTE)((address & 0x0000ff00) >> 8);
	buff[4] = (BYTE)((address & 0x000000ff));
	buff[5] = (BYTE)((size & 0xff00) >> 8);
	buff[6] = (BYTE)((size & 0xff));
	return WriteReport(buff);
}
// ------------------------------------------------------------------------------------
void wiimote::WriteData(int address, BYTE size, const BYTE* buff)
{
	// asynchronous
	BYTE write[REPORT_LENGTH] = { 0 };
	write[0] = OUT_WRITEMEMORY;
	write[1] = (BYTE)(((address & 0xff000000) >> 24) | GetRumbleBit());
	write[2] = (BYTE)((address & 0x00ff0000) >> 16);
	write[3] = (BYTE)((address & 0x0000ff00) >> 8);
	write[4] = (BYTE)((address & 0x000000ff));
	write[5] = size;
	memcpy(write + 6, buff, size);
	WriteReport(write);
}
// ------------------------------------------------------------------------------------
int wiimote::ParseInput(BYTE* buff)
{
	int changed = 0;

	// lock our internal state (so RefreshState() is blocked until we're done
	EnterCriticalSection(&StateLock);

	switch (buff[0])
	{
	case IN_BUTTONS:
		DEEP_TRACE(_T(".. parsing buttons."));
		changed |= ParseButtons(buff);
		break;

	case IN_BUTTONS_ACCEL:
		DEEP_TRACE(_T(".. parsing buttons/accel."));
		changed |= ParseButtons(buff);
		if (!IsBalanceBoard())
			changed |= ParseAccel(buff);
		break;

	case IN_BUTTONS_ACCEL_EXT:
		DEEP_TRACE(_T(".. parsing extenion/accel."));
		changed |= ParseButtons(buff);
		changed |= ParseExtension(buff, 6);
		if (!IsBalanceBoard())
			changed |= ParseAccel(buff);
		break;

	case IN_BUTTONS_ACCEL_IR:
		DEEP_TRACE(_T(".. parsing ir/accel."));
		changed |= ParseButtons(buff);
		if (!IsBalanceBoard()) {
			changed |= ParseAccel(buff);
			changed |= ParseIR(buff);
		}
		break;

	case IN_BUTTONS_ACCEL_IR_EXT:
		DEEP_TRACE(_T(".. parsing ir/extenion/accel."));
		changed |= ParseButtons(buff);
		changed |= ParseExtension(buff, 16);
		if (!IsBalanceBoard()) {
			changed |= ParseAccel(buff);
			changed |= ParseIR(buff);
		}
		break;

	case IN_BUTTONS_BALANCE_BOARD:
		DEEP_TRACE(_T(".. parsing buttson/balance."));
		changed |= ParseButtons(buff);
		changed |= ParseExtension(buff, 3);
		break;

	case IN_READADDRESS:
		DEEP_TRACE(_T(".. parsing read address."));
		changed |= ParseButtons(buff);
		changed |= ParseReadAddress(buff);
		break;

	case IN_STATUS:
		DEEP_TRACE(_T(".. parsing status."));
		changed |= ParseStatus(buff);
		// show that we received the status report (used for output method
		//  detection during Connect())
		bStatusReceived = true;
		break;

	default:
		DEEP_TRACE(_T(".. ** unknown input ** (happens)."));
		///_ASSERT(0);
		//Debug.WriteLine("Unknown report type: " + type.ToString());
		LeaveCriticalSection(&StateLock);
		return false;
	}

	// if we're recording and some state we care about has changed, insert it into
	//  the state history
	if (Recording.bEnabled && (changed & Recording.TriggerFlags))
	{
		DEEP_TRACE(_T(".. adding state to history"));
		state_event event;
		event.time_ms = timeGetTime();
		event.state = *(wiimote_state*)this;
		Recording.StateHistory->push_back(event);
	}

	// for polling: show which state has changed since the last RefreshState()
	InternalChanged = (state_change_flags)(InternalChanged | changed);

	LeaveCriticalSection(&StateLock);

	// callbacks: call it (if set & state the app is interested in has changed)
	if (changed & CallbackTriggerFlags)
	{
		DEEP_TRACE(_T(".. calling state change callback"));
		ChangedNotifier((state_change_flags)changed, Internal);
		if (ChangedCallback)
			ChangedCallback(*this, (state_change_flags)changed, Internal);
	}

	DEEP_TRACE(_T(".. parse complete."));
	return true;
}
// ------------------------------------------------------------------------------------
state_change_flags wiimote::RefreshState()
{
	// nothing changed since the last call?
	if (InternalChanged == NO_CHANGE)
		return NO_CHANGE;

	// copy the internal state to our public data members:
	//  synchronise the interal state with the read/parse thread (we don't want
	//   values changing during the copy)
	EnterCriticalSection(&StateLock);

	// remember which state changed since the last call
	state_change_flags changed = InternalChanged;

	// preserve the application-set deadzones (if any)
	joystick::deadzone nunchuk_deadzone = Nunchuk.Joystick.DeadZone;
	joystick::deadzone classic_joyl_deadzone = ClassicController.JoystickL.DeadZone;
	joystick::deadzone classic_joyr_deadzone = ClassicController.JoystickR.DeadZone;

	// copy the internal state to the public one
	*(wiimote_state*)this = Internal;
	InternalChanged = NO_CHANGE;

	// restore the application-set deadzones
	Nunchuk.Joystick.DeadZone = nunchuk_deadzone;
	ClassicController.JoystickL.DeadZone = classic_joyl_deadzone;
	ClassicController.JoystickR.DeadZone = classic_joyr_deadzone;

	LeaveCriticalSection(&StateLock);

	return changed;
}
// ------------------------------------------------------------------------------------
void wiimote::DetectMotionPlusExtensionAsync()
{
#ifdef _DEBUG
	TRACE(_T("(looking for motion plus)"));
#endif
	// show that we're expecting the result shortly
	MotionPlusDetectCount++;
	// MotionPLus reports at a different address than other extensions (until
	//  activated, when it maps itself into the usual extension registers), so
	//  try to detect it first:
	ReadAddress(REGISTER_MOTIONPLUS_DETECT, 6);
}
// ------------------------------------------------------------------------------------
bool wiimote::EnableMotionPlus()
{
	_ASSERT(bMotionPlusDetected);
	if (!bMotionPlusDetected)
		return false;
	if (bMotionPlusEnabled)
		return true;

	TRACE(_T("Enabling Motion Plus:"));

	bMotionPlusExtension = false;
	bInitInProgress = true;
	bEnablingMotionPlus = true;

	// Initialize it:
	WriteData(REGISTER_MOTIONPLUS_INIT, 0x55);
	//	Sleep(50);
		// Enable it (this maps it to the standard extension port):
	WriteData(REGISTER_MOTIONPLUS_ENABLE, 0x04);
	//	Sleep(50);
	Sleep(500);
	return true;
}
// ------------------------------------------------------------------------------------
bool wiimote::DisableMotionPlus()
{
	if (!bMotionPlusDetected || !bMotionPlusEnabled)
		return false;

	TRACE(_T("Disabling Motion Plus:"));

	// disable it (this makes standard extensions visible again)
	WriteData(REGISTER_EXTENSION_INIT1, 0x55);
	return true;
}
// ------------------------------------------------------------------------------------
void wiimote::InitializeExtension()
{
	TRACE(_T("Initialising Extension."));
	// wibrew.org: The new way to initialize the extension is by writing 0x55 to
	//	0x(4)A400F0, then writing 0x00 to 0x(4)A400FB. It works on all extensions, and
	//  makes the extension type bytes unencrypted. This means that you no longer have
	//  to decrypt the extension bytes using the transform listed above. 
	bInitInProgress = true;
	_ASSERT(Internal.bExtension);
	// only initialize if it's not a MotionPlus
	if (!bEnablingMotionPlus) {
		WriteData(REGISTER_EXTENSION_INIT1, 0x55);
		WriteData(REGISTER_EXTENSION_INIT2, 0x00);
	}
	else
		bEnablingMotionPlus = false;

	ReadAddress(REGISTER_EXTENSION_TYPE, 6);
}
// ------------------------------------------------------------------------------------
int wiimote::ParseStatus(BYTE* buff)
{
	// parse the buttons
	int changed = ParseButtons(buff);

	// get the battery level
	BYTE battery_raw = buff[6];
	if (Internal.BatteryRaw != battery_raw)
		changed |= BATTERY_CHANGED;
	Internal.BatteryRaw = battery_raw;
	// it is estimated that ~200 is the maximum battery level
	Internal.BatteryPercent = battery_raw / 2;

	// there is also a flag that shows if the battery is nearly empty
	bool drained = buff[3] & 0x01;
	if (drained != bBatteryDrained)
	{
		bBatteryDrained = drained;
		if (drained)
			changed |= BATTERY_DRAINED;
	}

	// leds
	BYTE leds = buff[3] >> 4;
	if (leds != Internal.LED.Bits)
		changed |= LEDS_CHANGED;
	Internal.LED.Bits = leds;

	// don't handle extensions until a connection is complete
//	if(bConnectInProgress)
//		return changed;

	bool extension = ((buff[3] & 0x02) != 0);
	//	TRACE(_T("(extension = %s)"), (extension? _T("TRUE") : _T("false")));

	if (extension != Internal.bExtension)
	{
		if (!Internal.bExtension)
		{
			TRACE(_T("Extension connected:"));
			Internal.bExtension = true;
			InitializeExtension();
		}
		else {
			TRACE(_T("Extension disconnected."));
			Internal.bExtension = false;
			Internal.ExtensionType = wiimote_state::NONE;
			bMotionPlusEnabled = false;
			bMotionPlusExtension = false;
			bMotionPlusDetected = false;
			bInitInProgress = false;
			bEnablingMotionPlus = false;
			changed |= EXTENSION_DISCONNECTED;
			// renable reports
//			SetReportType(ReportType);
		}
	}

	return changed;
}
// ------------------------------------------------------------------------------------
int wiimote::ParseButtons(BYTE* buff)
{
	int changed = 0;

	//	WORD bits = *(WORD*)(buff+1);
	WORD bits = *(WORD*)(buff + 1) & Button.ALL;

	if (bits != Internal.Button.Bits)
		changed |= BUTTONS_CHANGED;
	Internal.Button.Bits = bits;

	return changed;
}
// ------------------------------------------------------------------------------------
bool wiimote::EstimateOrientationFrom(wiimote_state::acceleration &accel)
{
	// Orientation estimate from acceleration data (shared between wiimote and nunchuk)
	//  return true if the orientation was updated

	//  assume the controller is stationary if the acceleration vector is near
	//  1g for several updates (this may not always be correct)
	float length_sq = square(accel.X) + square(accel.Y) + square(accel.Z);

	// TODO: as I'm comparing _squared_ length, I really need different
	//		  min/max epsilons...
#define DOT(x1,y1,z1, x2,y2,z2)	((x1*x2) + (y1*y2) + (z1*z2))

	static const float epsilon = 0.2f;
	if ((length_sq >= (1.f - epsilon)) && (length_sq <= (1.f + epsilon)))
	{
		if (++WiimoteNearGUpdates < 2)
			return false;

		// wiimote seems to be stationary:  normalize the current acceleration
		//  (ie. the assumed gravity vector)
		float inv_len = 1.f / sqrt(length_sq);
		float x = accel.X * inv_len;
		float y = accel.Y * inv_len;
		float z = accel.Z * inv_len;

		// copy the values
		accel.Orientation.X = x;
		accel.Orientation.Y = y;
		accel.Orientation.Z = z;

		// and extract pitch & roll from them:
		// (may not be optimal)
		float pitch = -asin(y)    * 57.2957795f;
		//		float roll  =  asin(x)    * 57.2957795f;
		float roll = atan2(x, z) * 57.2957795f;
		if (z < 0) {
			pitch = (y < 0) ? 180 - pitch : -180 - pitch;
			roll = (x < 0) ? -180 - roll : 180 - roll;
		}

		accel.Orientation.Pitch = pitch;
		accel.Orientation.Roll = roll;

		// show that we just updated orientation
		accel.Orientation.UpdateAge = 0;
#ifdef BEEP_ON_ORIENTATION_ESTIMATE
		Beep(2000, 1);
#endif
		return true; // updated
	}

	// not updated this time:
	WiimoteNearGUpdates = 0;
	// age the last orientation update
	accel.Orientation.UpdateAge++;
	return false;
}
// ------------------------------------------------------------------------------------
void wiimote::ApplyJoystickDeadZones(wiimote_state::joystick &joy)
{
	// apply the deadzones to each axis (if set)
	if ((joy.DeadZone.X > 0.f) && (joy.DeadZone.X <= 1.f))
	{
		if (fabs(joy.X) <= joy.DeadZone.X)
			joy.X = 0;
		else {
			joy.X -= joy.DeadZone.X * sign(joy.X);
			joy.X /= 1.f - joy.DeadZone.X;
		}
	}
	if ((joy.DeadZone.Y > 0.f) && (joy.DeadZone.Y <= 1.f))
	{
		if (fabs(joy.Y) <= joy.DeadZone.Y)
			joy.Y = 0;
		else {
			joy.Y -= joy.DeadZone.Y * sign(joy.Y);
			joy.Y /= 1.f - joy.DeadZone.Y;
		}
	}
}
// ------------------------------------------------------------------------------------
int wiimote::ParseAccel(BYTE* buff)
{
	int changed = 0;

	BYTE raw_x = buff[3];
	BYTE raw_y = buff[4];
	BYTE raw_z = buff[5];

	if ((raw_x != Internal.Acceleration.RawX) ||
		(raw_y != Internal.Acceleration.RawY) ||
		(raw_z != Internal.Acceleration.RawZ))
		changed |= ACCEL_CHANGED;

	Internal.Acceleration.RawX = raw_x;
	Internal.Acceleration.RawY = raw_y;
	Internal.Acceleration.RawZ = raw_z;

	// avoid / 0.0 when calibration data hasn't arrived yet
	if (Internal.CalibrationInfo.X0)
	{
		Internal.Acceleration.X =
			((float)Internal.Acceleration.RawX - Internal.CalibrationInfo.X0) /
			((float)Internal.CalibrationInfo.XG - Internal.CalibrationInfo.X0);
		Internal.Acceleration.Y =
			((float)Internal.Acceleration.RawY - Internal.CalibrationInfo.Y0) /
			((float)Internal.CalibrationInfo.YG - Internal.CalibrationInfo.Y0);
		Internal.Acceleration.Z =
			((float)Internal.Acceleration.RawZ - Internal.CalibrationInfo.Z0) /
			((float)Internal.CalibrationInfo.ZG - Internal.CalibrationInfo.Z0);
	}
	else {
		Internal.Acceleration.X =
			Internal.Acceleration.Y =
			Internal.Acceleration.Z = 0.f;
	}

	// see if we can estimate the orientation from the current values
	if (EstimateOrientationFrom(Internal.Acceleration))
		changed |= ORIENTATION_CHANGED;

	return changed;
}
// ------------------------------------------------------------------------------------
int wiimote::ParseIR(BYTE* buff)
{
	if (Internal.IR.Mode == wiimote_state::ir::OFF)
		return NO_CHANGE;

	// avoid garbage values when the MotionPlus is enabled, but the app is
	//  still using the extended IR report type
	if (bMotionPlusEnabled && (Internal.IR.Mode == wiimote_state::ir::EXTENDED))
		return NO_CHANGE;

	// take a copy of the existing IR state (so we can detect changes)
	wiimote_state::ir prev_ir = Internal.IR;

	// only updates the other values if the dots are visible (so that the last
	//  valid values stay unmodified)
	switch (Internal.IR.Mode)
	{
	case wiimote_state::ir::BASIC:
		// 2 dots are encoded in 5 bytes, so read 2 at a time
		for (unsigned step = 0; step < 2; step++)
		{
			ir::dot &dot0 = Internal.IR.Dot[step * 2];
			ir::dot &dot1 = Internal.IR.Dot[step * 2 + 1];
			const unsigned offs = 6 + (step * 5); // 5 bytes for 2 dots

			dot0.bVisible = !(buff[offs] == 0xff && buff[offs + 1] == 0xff);
			dot1.bVisible = !(buff[offs + 3] == 0xff && buff[offs + 4] == 0xff);

			if (dot0.bVisible) {
				dot0.RawX = buff[offs] | ((buff[offs + 2] >> 4) & 0x03) << 8;;
				dot0.RawY = buff[offs + 1] | ((buff[offs + 2] >> 6) & 0x03) << 8;;
				dot0.X = 1.f - (dot0.RawX / (float)wiimote_state::ir::MAX_RAW_X);
				dot0.Y = (dot0.RawY / (float)wiimote_state::ir::MAX_RAW_Y);
			}
			if (dot1.bVisible) {
				dot1.RawX = buff[offs + 3] | ((buff[offs + 2] >> 0) & 0x03) << 8;
				dot1.RawY = buff[offs + 4] | ((buff[offs + 2] >> 2) & 0x03) << 8;
				dot1.X = 1.f - (dot1.RawX / (float)wiimote_state::ir::MAX_RAW_X);
				dot1.Y = (dot1.RawY / (float)wiimote_state::ir::MAX_RAW_Y);
			}
		}
		break;

	case wiimote_state::ir::EXTENDED:
		// each dot is encoded into 3 bytes
		for (unsigned index = 0; index < 4; index++)
		{
			ir::dot &dot = Internal.IR.Dot[index];
			const unsigned offs = 6 + (index * 3);

			dot.bVisible = !(buff[offs] == 0xff && buff[offs + 1] == 0xff &&
				buff[offs + 2] == 0xff);
			if (dot.bVisible) {
				dot.RawX = buff[offs] | ((buff[offs + 2] >> 4) & 0x03) << 8;
				dot.RawY = buff[offs + 1] | ((buff[offs + 2] >> 6) & 0x03) << 8;
				dot.X = 1.f - (dot.RawX / (float)wiimote_state::ir::MAX_RAW_X);
				dot.Y = (dot.RawY / (float)wiimote_state::ir::MAX_RAW_Y);
				dot.Size = buff[offs + 2] & 0x0f;
			}
		}
		break;

	case wiimote_state::ir::FULL:
		_ASSERT(0); // not supported yet;
		break;
	}

	return memcmp(&prev_ir, &Internal.IR, sizeof(Internal.IR)) ? IR_CHANGED : 0;
}
// ------------------------------------------------------------------------------------
inline float wiimote::GetBalanceValue(short sensor, short min, short mid, short max)
{
	if (max == mid || mid == min)
		return 0;

	float val = (sensor < mid) ?
		68.0f * ((float)(sensor - min) / (mid - min)) :
		68.0f * ((float)(sensor - mid) / (max - mid)) + 68.0f;

	// divide by four (so that each sensor is correct)
	return val * 0.25f;
}
// ------------------------------------------------------------------------------------
int wiimote::ParseExtension(BYTE *buff, unsigned offset)
{
	int changed = 0;

	switch (Internal.ExtensionType)
	{
	case wiimote_state::NUNCHUK:
	{
		// buttons
		bool c = (buff[offset + 5] & 0x02) == 0;
		bool z = (buff[offset + 5] & 0x01) == 0;

		if ((c != Internal.Nunchuk.C) || (z != Internal.Nunchuk.Z))
			changed |= NUNCHUK_BUTTONS_CHANGED;

		Internal.Nunchuk.C = c;
		Internal.Nunchuk.Z = z;

		// acceleration
		{
			wiimote_state::acceleration &accel = Internal.Nunchuk.Acceleration;

			BYTE raw_x = buff[offset + 2];
			BYTE raw_y = buff[offset + 3];
			BYTE raw_z = buff[offset + 4];
			if ((raw_x != accel.RawX) || (raw_y != accel.RawY) || (raw_z != accel.RawZ))
				changed |= NUNCHUK_ACCEL_CHANGED;

			accel.RawX = raw_x;
			accel.RawY = raw_y;
			accel.RawZ = raw_z;

			wiimote_state::nunchuk::calibration_info &calib =
				Internal.Nunchuk.CalibrationInfo;
			accel.X = ((float)raw_x - calib.X0) / ((float)calib.XG - calib.X0);
			accel.Y = ((float)raw_y - calib.Y0) / ((float)calib.YG - calib.Y0);
			accel.Z = ((float)raw_z - calib.Z0) / ((float)calib.ZG - calib.Z0);

			// try to extract orientation from the accel:
			if (EstimateOrientationFrom(accel))
				changed |= NUNCHUK_ORIENTATION_CHANGED;
		}
		{
			// joystick:
			wiimote_state::joystick &joy = Internal.Nunchuk.Joystick;

			float raw_x = buff[offset + 0];
			float raw_y = buff[offset + 1];

			if ((raw_x != joy.RawX) || (raw_y != joy.RawY))
				changed |= NUNCHUK_JOYSTICK_CHANGED;

			joy.RawX = raw_x;
			joy.RawY = raw_y;

			// apply the calibration data
			wiimote_state::nunchuk::calibration_info &calib =
				Internal.Nunchuk.CalibrationInfo;
			if (Internal.Nunchuk.CalibrationInfo.MaxX != 0x00)
				joy.X = ((float)raw_x - calib.MidX) / ((float)calib.MaxX - calib.MinX);
			if (calib.MaxY != 0x00)
				joy.Y = ((float)raw_y - calib.MidY) / ((float)calib.MaxY - calib.MinY);

			// i prefer the outputs to range -1 - +1 (note this also affects the
			//  deadzone calculations)
			joy.X *= 2;	joy.Y *= 2;

			// apply the public deadzones to the internal state (if set)
			joy.DeadZone = Nunchuk.Joystick.DeadZone;
			ApplyJoystickDeadZones(joy);
		}
	}
	break;

	case wiimote_state::CLASSIC:
	case wiimote_state::GH3_GHWT_GUITAR:
	case wiimote_state::GHWT_DRUMS:
	{
		// buttons:
		WORD bits = *(WORD*)(buff + offset + 4);
		bits = ~bits; // need to invert bits since 0 is down, and 1 is up

		if (bits != Internal.ClassicController.Button.Bits)
			changed |= CLASSIC_BUTTONS_CHANGED;

		Internal.ClassicController.Button.Bits = bits;

		// joysticks:
		wiimote_state::joystick &joyL = Internal.ClassicController.JoystickL;
		wiimote_state::joystick &joyR = Internal.ClassicController.JoystickR;

		float l_raw_x = (float)(buff[offset + 0] & 0x3f);
		float l_raw_y = (float)(buff[offset + 1] & 0x3f);
		float r_raw_x = (float)((buff[offset + 2] >> 7) |
			((buff[offset + 1] & 0xc0) >> 5) |
			((buff[offset + 0] & 0xc0) >> 3));
		float r_raw_y = (float)(buff[offset + 2] & 0x1f);

		if ((joyL.RawX != l_raw_x) || (joyL.RawY != l_raw_y))
			changed |= CLASSIC_JOYSTICK_L_CHANGED;
		if ((joyR.RawX != r_raw_x) || (joyR.RawY != r_raw_y))
			changed |= CLASSIC_JOYSTICK_R_CHANGED;

		joyL.RawX = l_raw_x; joyL.RawY = l_raw_y;
		joyR.RawX = r_raw_x; joyR.RawY = r_raw_y;

		// apply calibration
		wiimote_state::classic_controller::calibration_info &calib =
			Internal.ClassicController.CalibrationInfo;
		if (calib.MaxXL != 0x00)
			joyL.X = (joyL.RawX - calib.MidXL) / ((float)calib.MaxXL - calib.MinXL);
		if (calib.MaxYL != 0x00)
			joyL.Y = (joyL.RawY - calib.MidYL) / ((float)calib.MaxYL - calib.MinYL);
		if (calib.MaxXR != 0x00)
			joyR.X = (joyR.RawX - calib.MidXR) / ((float)calib.MaxXR - calib.MinXR);
		if (calib.MaxYR != 0x00)
			joyR.Y = (joyR.RawY - calib.MidYR) / ((float)calib.MaxYR - calib.MinYR);

		// i prefer the joystick outputs to range -1 - +1 (note this also affects
		//  the deadzone calculations)
		joyL.X *= 2; joyL.Y *= 2; joyR.X *= 2; joyR.Y *= 2;

		// apply the public deadzones to the internal state (if set)
		joyL.DeadZone = ClassicController.JoystickL.DeadZone;
		joyR.DeadZone = ClassicController.JoystickR.DeadZone;
		ApplyJoystickDeadZones(joyL);
		ApplyJoystickDeadZones(joyR);

		// triggers
		BYTE raw_trigger_l = ((buff[offset + 2] & 0x60) >> 2) |
			(buff[offset + 3] >> 5);
		BYTE raw_trigger_r = buff[offset + 3] & 0x1f;

		if ((raw_trigger_l != Internal.ClassicController.RawTriggerL) ||
			(raw_trigger_r != Internal.ClassicController.RawTriggerR))
			changed |= CLASSIC_TRIGGERS_CHANGED;

		Internal.ClassicController.RawTriggerL = raw_trigger_l;
		Internal.ClassicController.RawTriggerR = raw_trigger_r;

		if (calib.MaxTriggerL != 0x00)
			Internal.ClassicController.TriggerL =
			(float)Internal.ClassicController.RawTriggerL /
			((float)calib.MaxTriggerL - calib.MinTriggerL);
		if (calib.MaxTriggerR != 0x00)
			Internal.ClassicController.TriggerR =
			(float)Internal.ClassicController.RawTriggerR /
			((float)calib.MaxTriggerR - calib.MinTriggerR);
	}
	break;

	case BALANCE_BOARD:
	{
		wiimote_state::balance_board::sensors_raw prev_raw =
			Internal.BalanceBoard.Raw;
		Internal.BalanceBoard.Raw.TopR =
			(short)((short)buff[offset + 0] << 8 | buff[offset + 1]);
		Internal.BalanceBoard.Raw.BottomR =
			(short)((short)buff[offset + 2] << 8 | buff[offset + 3]);
		Internal.BalanceBoard.Raw.TopL =
			(short)((short)buff[offset + 4] << 8 | buff[offset + 5]);
		Internal.BalanceBoard.Raw.BottomL =
			(short)((short)buff[offset + 6] << 8 | buff[offset + 7]);

		if ((Internal.BalanceBoard.Raw.TopL != prev_raw.TopL) ||
			(Internal.BalanceBoard.Raw.TopR != prev_raw.TopR) ||
			(Internal.BalanceBoard.Raw.BottomL != prev_raw.BottomL) ||
			(Internal.BalanceBoard.Raw.BottomR != prev_raw.BottomR))
			changed |= BALANCE_WEIGHT_CHANGED;

		Internal.BalanceBoard.Kg.TopL =
			GetBalanceValue(Internal.BalanceBoard.Raw.TopL,
				Internal.BalanceBoard.CalibrationInfo.Kg0.TopL,
				Internal.BalanceBoard.CalibrationInfo.Kg17.TopL,
				Internal.BalanceBoard.CalibrationInfo.Kg34.TopL);
		Internal.BalanceBoard.Kg.TopR =
			GetBalanceValue(Internal.BalanceBoard.Raw.TopR,
				Internal.BalanceBoard.CalibrationInfo.Kg0.TopR,
				Internal.BalanceBoard.CalibrationInfo.Kg17.TopR,
				Internal.BalanceBoard.CalibrationInfo.Kg34.TopR);
		Internal.BalanceBoard.Kg.BottomL =
			GetBalanceValue(Internal.BalanceBoard.Raw.BottomL,
				Internal.BalanceBoard.CalibrationInfo.Kg0.BottomL,
				Internal.BalanceBoard.CalibrationInfo.Kg17.BottomL,
				Internal.BalanceBoard.CalibrationInfo.Kg34.BottomL);
		Internal.BalanceBoard.Kg.BottomR =
			GetBalanceValue(Internal.BalanceBoard.Raw.BottomR,
				Internal.BalanceBoard.CalibrationInfo.Kg0.BottomR,
				Internal.BalanceBoard.CalibrationInfo.Kg17.BottomR,
				Internal.BalanceBoard.CalibrationInfo.Kg34.BottomR);

		// uses these as the 'at rest' offsets? (immediately after Connect(),
		//  or if the app called CalibrateAtRest())
		if (bCalibrateAtRest) {
			bCalibrateAtRest = false;
			TRACE(_T(".. Auto-removing 'at rest' BBoard offsets."));
			Internal.BalanceBoard.AtRestKg = Internal.BalanceBoard.Kg;
		}

		// remove the 'at rest' offsets
		Internal.BalanceBoard.Kg.TopL -= BalanceBoard.AtRestKg.TopL;
		Internal.BalanceBoard.Kg.TopR -= BalanceBoard.AtRestKg.TopR;
		Internal.BalanceBoard.Kg.BottomL -= BalanceBoard.AtRestKg.BottomL;
		Internal.BalanceBoard.Kg.BottomR -= BalanceBoard.AtRestKg.BottomR;

		// compute the average
		Internal.BalanceBoard.Kg.Total = Internal.BalanceBoard.Kg.TopL +
			Internal.BalanceBoard.Kg.TopR +
			Internal.BalanceBoard.Kg.BottomL +
			Internal.BalanceBoard.Kg.BottomR;
		// and convert to Lbs
		const float KG2LB = 2.20462262f;
		Internal.BalanceBoard.Lb = Internal.BalanceBoard.Kg;
		Internal.BalanceBoard.Lb.TopL *= KG2LB;
		Internal.BalanceBoard.Lb.TopR *= KG2LB;
		Internal.BalanceBoard.Lb.BottomL *= KG2LB;
		Internal.BalanceBoard.Lb.BottomR *= KG2LB;
		Internal.BalanceBoard.Lb.Total *= KG2LB;
	}
	break;

	case MOTION_PLUS:
	{
		bMotionPlusDetected = true;
		bMotionPlusEnabled = true;

		short yaw = ((unsigned short)buff[offset + 3] & 0xFC) << 6 |
			(unsigned short)buff[offset + 0];
		short pitch = ((unsigned short)buff[offset + 5] & 0xFC) << 6 |
			(unsigned short)buff[offset + 2];
		short roll = ((unsigned short)buff[offset + 4] & 0xFC) << 6 |
			(unsigned short)buff[offset + 1];

		// we get one set of bogus values when the MotionPlus is disconnected,
		//  so ignore them
		if ((yaw != 0x3fff) || (pitch != 0x3fff) || (roll != 0x3fff))
		{
			wiimote_state::motion_plus::sensors_raw &raw = Internal.MotionPlus.Raw;

			if ((raw.Yaw != yaw) || (raw.Pitch != pitch) || (raw.Roll != roll))
				changed |= MOTIONPLUS_SPEED_CHANGED;

			raw.Yaw = yaw;
			raw.Pitch = pitch;
			raw.Roll = roll;

			// convert to float values
			bool    yaw_slow = (buff[offset + 3] & 0x2) == 0x2;
			bool  pitch_slow = (buff[offset + 3] & 0x1) == 0x1;
			bool   roll_slow = (buff[offset + 4] & 0x2) == 0x2;
			float y_scale = yaw_slow ? 0.05f : 0.25f;
			float p_scale = pitch_slow ? 0.05f : 0.25f;
			float r_scale = roll_slow ? 0.05f : 0.25f;

			Internal.MotionPlus.Speed.Yaw = -(raw.Yaw - 0x1F7F) * y_scale;
			Internal.MotionPlus.Speed.Pitch = -(raw.Pitch - 0x1F7F) * p_scale;
			Internal.MotionPlus.Speed.Roll = -(raw.Roll - 0x1F7F) * r_scale;

			// show if there's an extension plugged into the MotionPlus:
			bool extension = buff[offset + 4] & 1;
			if (extension != bMotionPlusExtension)
			{
				if (extension) {
					TRACE(_T(".. MotionPlus extension found."));
					changed |= MOTIONPLUS_EXTENSION_CONNECTED;
				}
				else {
					TRACE(_T(".. MotionPlus' extension disconnected."));
					changed |= MOTIONPLUS_EXTENSION_DISCONNECTED;
				}
			}
			bMotionPlusExtension = extension;
		}
		// while we're getting data, the plus is obviously detected/enabled
//			bMotionPlusDetected = bMotionPlusEnabled = true;
	}
	break;
	}

	return changed;
}
// ------------------------------------------------------------------------------------
int wiimote::ParseReadAddress(BYTE* buff)
{
	// decode the address that was queried:
	int address = buff[4] << 8 | buff[5];
	int size = buff[3] >> 4;
	(void)size;
	int changed = 0;

	if ((buff[3] & 0x08) != 0) {
		WARN(_T("error: read address not valid."));
		_ASSERT(0);
		return NO_CHANGE;
	}
	// address read failed (write-only)?
	else if ((buff[3] & 0x07) != 0)
	{
		// this also happens when attempting to detect a non-existant MotionPlus
		if (MotionPlusDetectCount)
		{
			--MotionPlusDetectCount;
			if (Internal.ExtensionType == MOTION_PLUS)
			{
				if (bMotionPlusDetected)
					TRACE(_T(".. MotionPlus removed."));
				bMotionPlusDetected = false;
				bMotionPlusEnabled = false;
				// the MotionPlus can sometimes get confused - initializing
				//  extenions fixes it:
//				if(address == 0xfa)
//					InitializeExtension();
			}
		}
		else
			WARN(_T("error: attempt to read from write-only register 0x%X."), buff[3]);

		return NO_CHANGE;
	}

	// *NOTE*: this is a major (but convenient) hack!  The returned data only
	//          contains the lower two bytes of the address that was queried.
	//			as these don't collide between any of the addresses/registers
	//			we currently read, it's OK to match just those two bytes

	// skip the header
	buff += 6;

	switch (address)
	{
	case (REGISTER_CALIBRATION & 0xffff):
	{
		_ASSERT(size == 6);
		TRACE(_T(".. got wiimote calibration."));
		Internal.CalibrationInfo.X0 = buff[0];
		Internal.CalibrationInfo.Y0 = buff[1];
		Internal.CalibrationInfo.Z0 = buff[2];
		Internal.CalibrationInfo.XG = buff[4];
		Internal.CalibrationInfo.YG = buff[5];
		Internal.CalibrationInfo.ZG = buff[6];
		//changed |= CALIBRATION_CHANGED;	
	}
	break;

	// note: this covers both the normal extension and motion plus extension
	//        addresses (0x4a400fa / 0x4a600fa)
	case (REGISTER_EXTENSION_TYPE & 0xffff):
	{
		_ASSERT(size == 5);
		QWORD type = *(QWORD*)buff;

		//			TRACE(_T("(found extension 0x%I64x)"), type);

		static const QWORD NUNCHUK = 0x000020A40000ULL;
		static const QWORD CLASSIC = 0x010120A40000ULL;
		static const QWORD GH3_GHWT_GUITAR = 0x030120A40000ULL;
		static const QWORD GHWT_DRUMS = 0x030120A40001ULL;
		static const QWORD BALANCE_BOARD = 0x020420A40000ULL;
		static const QWORD MOTION_PLUS = 0x050420A40000ULL;
		static const QWORD MOTION_PLUS_DETECT = 0x050020a60000ULL;
		static const QWORD MOTION_PLUS_DETECT2 = 0x050420a60000ULL;
		static const QWORD PARTIALLY_INSERTED = 0xffffffffffffULL;

		// MotionPlus: _before_ it's been activated
		if ((type == MOTION_PLUS_DETECT) || (type == MOTION_PLUS_DETECT2))
		{
			if (!bMotionPlusDetected) {
				TRACE(_T("Motion Plus detected!"));
				changed |= MOTIONPLUS_DETECTED;
			}
			bMotionPlusDetected = true;
			--MotionPlusDetectCount;
			break;
		}

		// sometimes we get idempotent ExtensionType events
#define IF_TYPE(id, ...)                                        \
                            if(type == id)                                              \
                            {                                                           \
                                if(Internal.ExtensionType == wiimote_state::id)         \
                                    break;                                              \
                                Internal.ExtensionType = wiimote_state::id;             \
                                { __VA_ARGS__ }                                         \
                            }

			// MotionPlus: once it's activated & mapped to the standard ext. port
		IF_TYPE(MOTION_PLUS,
			TRACE(_T(".. Motion Plus!"));
		// and start a query for the calibration data
		ReadAddress(REGISTER_EXTENSION_CALIBRATION, 16);
		bMotionPlusDetected = true;
		)
							else IF_TYPE(NUNCHUK,
								TRACE(_T(".. Nunchuk!"));
		bMotionPlusEnabled = false;
		// and start a query for the calibration data
		ReadAddress(REGISTER_EXTENSION_CALIBRATION, 16);
		)
		else IF_TYPE(CLASSIC,
			TRACE(_T(".. Classic Controller!"));
		bMotionPlusEnabled = false;
		// and start a query for the calibration data
		ReadAddress(REGISTER_EXTENSION_CALIBRATION, 16);
		)
	else IF_TYPE(GH3_GHWT_GUITAR,
		// sometimes it comes in more than once?
		TRACE(_T(".. GH3/GHWT Guitar Controller!"));
	bMotionPlusEnabled = false;
	// and start a query for the calibration data
	ReadAddress(REGISTER_EXTENSION_CALIBRATION, 16);
	)
						else IF_TYPE(GHWT_DRUMS,
							TRACE(_T(".. GHWT Drums!"));
							bMotionPlusEnabled = false;
							// and start a query for the calibration data
							ReadAddress(REGISTER_EXTENSION_CALIBRATION, 16);
							)
						else IF_TYPE(BALANCE_BOARD,
							TRACE(_T(".. Balance Board!"));
							bMotionPlusEnabled = false;
							// and start a query for the calibration data
							ReadAddress(REGISTER_BALANCE_CALIBRATION, 24);
							)
			else if (type == PARTIALLY_INSERTED) {
								// sometimes it comes in more than once?
								if (Internal.ExtensionType == wiimote_state::PARTIALLY_INSERTED)
									Sleep(50);
								TRACE(_T(".. partially inserted!"));
								bMotionPlusEnabled = false;
								Internal.ExtensionType = wiimote_state::PARTIALLY_INSERTED;
								changed |= EXTENSION_PARTIALLY_INSERTED;
								// try initializing the extension again by requesting another
								//  status report (this usually fixes it)
								Internal.bExtension = false;
								RequestStatusReport();
							}
			else {
				TRACE(_T("unknown extension controller found (0x%I64x)"), type);
			}
	}
	break;

	case (REGISTER_EXTENSION_CALIBRATION & 0xffff):
	case (REGISTER_BALANCE_CALIBRATION & 0xffff):
	{
		//			_ASSERT(((Internal.ExtensionType == BALANCE_BOARD) && (size == 31)) ||
		//					((Internal.ExtensionType != BALANCE_BOARD) && (size == 15)));

		switch (Internal.ExtensionType)
		{
		case wiimote_state::NUNCHUK:
		{
			wiimote_state::nunchuk::calibration_info
				&calib = Internal.Nunchuk.CalibrationInfo;

			calib.X0 = buff[0];
			calib.Y0 = buff[1];
			calib.Z0 = buff[2];
			calib.XG = buff[4];
			calib.YG = buff[5];
			calib.ZG = buff[6];
			calib.MaxX = buff[8];
			calib.MinX = buff[9];
			calib.MidX = buff[10];
			calib.MaxY = buff[11];
			calib.MinY = buff[12];
			calib.MidY = buff[13];

			changed |= NUNCHUK_CONNECTED;//|NUNCHUK_CALIBRATION_CHANGED;
			// reenable reports
//					SetReportType(ReportType);
		}
		break;

		case wiimote_state::CLASSIC:
		case wiimote_state::GH3_GHWT_GUITAR:
		case wiimote_state::GHWT_DRUMS:
		{
			wiimote_state::classic_controller::calibration_info
				&calib = Internal.ClassicController.CalibrationInfo;

			calib.MaxXL = buff[0] >> 2;
			calib.MinXL = buff[1] >> 2;
			calib.MidXL = buff[2] >> 2;
			calib.MaxYL = buff[3] >> 2;
			calib.MinYL = buff[4] >> 2;
			calib.MidYL = buff[5] >> 2;
			calib.MaxXR = buff[6] >> 3;
			calib.MinXR = buff[7] >> 3;
			calib.MidXR = buff[8] >> 3;
			calib.MaxYR = buff[9] >> 3;
			calib.MinYR = buff[10] >> 3;
			calib.MidYR = buff[11] >> 3;
			// this doesn't seem right...
			//	calib.MinTriggerL = buff[12] >> 3;
			//	calib.MaxTriggerL = buff[14] >> 3;
			//	calib.MinTriggerR = buff[13] >> 3;
			//	calib.MaxTriggerR = buff[15] >> 3;
			calib.MinTriggerL = 0;
			calib.MaxTriggerL = 31;
			calib.MinTriggerR = 0;
			calib.MaxTriggerR = 31;

			changed |= CLASSIC_CONNECTED;//|CLASSIC_CALIBRATION_CHANGED;
			// reenable reports
//					SetReportType(ReportType);
		}
		break;

		case BALANCE_BOARD:
		{
			// first part, 0 & 17kg calibration values
			wiimote_state::balance_board::calibration_info
				&calib = Internal.BalanceBoard.CalibrationInfo;

			calib.Kg0.TopR = (short)((short)buff[0] << 8 | buff[1]);
			calib.Kg0.BottomR = (short)((short)buff[2] << 8 | buff[3]);
			calib.Kg0.TopL = (short)((short)buff[4] << 8 | buff[5]);
			calib.Kg0.BottomL = (short)((short)buff[6] << 8 | buff[7]);

			calib.Kg17.TopR = (short)((short)buff[8] << 8 | buff[9]);
			calib.Kg17.BottomR = (short)((short)buff[10] << 8 | buff[11]);
			calib.Kg17.TopL = (short)((short)buff[12] << 8 | buff[13]);
			calib.Kg17.BottomL = (short)((short)buff[14] << 8 | buff[15]);

			// 2nd part is scanned above
		}
		break;

		case MOTION_PLUS:
		{
			// TODO: not known how the calibration values work
			changed |= MOTIONPLUS_ENABLED;
			bMotionPlusEnabled = true;
			bInitInProgress = false;
			// reenable reports
//					SetReportType(ReportType);
		}
		break;
		}
		break;
		// XXX missing break here? -sh
	case 0x34:
	{
		if (Internal.ExtensionType == BALANCE_BOARD)
		{
			wiimote_state::balance_board::calibration_info
				&calib = Internal.BalanceBoard.CalibrationInfo;

			// 2nd part of the balance board calibration,
			//  34kg calibration values
			calib.Kg34.TopR = (short)((short)buff[0] << 8 | buff[1]);
			calib.Kg34.BottomR = (short)((short)buff[2] << 8 | buff[3]);
			calib.Kg34.TopL = (short)((short)buff[4] << 8 | buff[5]);
			calib.Kg34.BottomL = (short)((short)buff[6] << 8 | buff[7]);

			changed |= BALANCE_CONNECTED;
			// reenable reports
			SetReportType(IN_BUTTONS_BALANCE_BOARD);
		}
		// else unknown what these are for
	}
	bInitInProgress = false;
	}
	break;

	default:
		//			_ASSERT(0); // shouldn't happen
		break;
	}

	return changed;
}
// ------------------------------------------------------------------------------------
void wiimote::ReadCalibration()
{
	TRACE(_T("Requestion wiimote calibration:"));
	// this appears to change the report type to 0x31
	ReadAddress(REGISTER_CALIBRATION, 7);
}
// ------------------------------------------------------------------------------------
void wiimote::EnableIR(wiimote_state::ir::mode mode)
{
	Internal.IR.Mode = mode;

	BYTE buff[REPORT_LENGTH] = { 0 };
	buff[0] = OUT_IR;
	buff[1] = 0x04 | GetRumbleBit();
	WriteReport(buff);

	memset(buff, 0, REPORT_LENGTH);
	buff[0] = OUT_IR2;
	buff[1] = 0x04 | GetRumbleBit();
	WriteReport(buff);

	static const BYTE ir_sens1[] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x90, 0x00,
									0xc0 };
	static const BYTE ir_sens2[] = { 0x40, 0x00 };

	WriteData(REGISTER_IR, 0x08);
	Sleep(25); 	// wait a little to make IR more reliable (for some)
	WriteData(REGISTER_IR_SENSITIVITY_1, sizeof(ir_sens1), ir_sens1);
	WriteData(REGISTER_IR_SENSITIVITY_2, sizeof(ir_sens2), ir_sens2);
	WriteData(REGISTER_IR_MODE, (BYTE)mode);
}
// ------------------------------------------------------------------------------------
void wiimote::DisableIR()
{
	Internal.IR.Mode = wiimote_state::ir::OFF;

	BYTE buff[REPORT_LENGTH] = { 0 };
	buff[0] = OUT_IR;
	buff[1] = GetRumbleBit();
	WriteReport(buff);

	memset(buff, 0, REPORT_LENGTH);
	buff[0] = OUT_IR2;
	buff[1] = GetRumbleBit();
	WriteReport(buff);
}
// ------------------------------------------------------------------------------------
unsigned __stdcall wiimote::HIDwriteThreadfunc(void* param)
{
	_ASSERT(param);
	TRACE(_T("(starting HID write thread)"));
	wiimote &remote = *(wiimote*)param;

	while (remote.Handle != INVALID_HANDLE_VALUE)
	{
		// try to write the oldest entry in the queue
#ifdef USE_DYNAMIC_HIDQUEUE
		if (!remote.HIDwriteQueue.empty())
#else
		if (!remote.HID.IsEmpty())
#endif
		{
#ifdef BEEP_DEBUG_WRITES
			Beep(1500, 1);
#endif
			EnterCriticalSection(&remote.HIDwriteQueueLock);
#ifdef USE_DYNAMIC_HIDQUEUE
			BYTE *buff = remote.HIDwriteQueue.front();
			_ASSERT(buff);
#else
			BYTE *buff = remote.HID.Queue[remote.HID.ReadIndex].Report;
#endif
			LeaveCriticalSection(&remote.HIDwriteQueueLock);

			if (!_HidD_SetOutputReport(remote.Handle, buff, REPORT_LENGTH))
			{
				DWORD err = GetLastError();
				if (err == ERROR_BUSY)
					TRACE(_T("**** HID WRITE: BUSY ****"));
				else if (err == ERROR_NOT_READY)
					TRACE(_T("**** HID WRITE: NOT READY ****"));

				if ((err != ERROR_BUSY) &&	 // "the requested resource is in use"
					(err != ERROR_NOT_READY)) // "the device is not ready"
				{
					if (err == ERROR_NOT_SUPPORTED) {
						WARN(_T("BT Stack doesn't suport HID writes!"));
						goto remove_entry;
					}
					else {
						DEEP_TRACE(_T("HID write failed (err %u)! - "), err);
						// if this worked previously, the connection was probably lost
						if (remote.IsConnected())
							remote.bConnectionLost = true;
					}
					//_T("aborting write thread"), err);
					//return 911;
				}
			}
			else {
			remove_entry:
				EnterCriticalSection(&remote.HIDwriteQueueLock);
#ifdef USE_DYNAMIC_HIDQUEUE
				remote.HIDwriteQueue.pop();
				delete[] buff;
#else
				remote.HID.ReadIndex++;
				remote.HID.ReadIndex &= (hid::MAX_QUEUE_ENTRIES - 1);
#endif
				LeaveCriticalSection(&remote.HIDwriteQueueLock);
			}
		}
		Sleep(1);
	}

	TRACE(_T("ending HID write thread"));
	return 0;
}
// ------------------------------------------------------------------------------------
bool wiimote::WriteReport(BYTE *buff)
{
#ifdef BEEP_DEBUG_WRITES
	Beep(2000, 1);
#endif

#ifdef _DEBUG
#define DEEP_TRACE_TYPE(type)	case OUT_##type: DEEP_TRACE(_T("WriteReport: ")\
																_T(#type)); break
	switch (buff[0])
	{
		DEEP_TRACE_TYPE(NONE);
		DEEP_TRACE_TYPE(LEDs);
		DEEP_TRACE_TYPE(TYPE);
		DEEP_TRACE_TYPE(IR);
		DEEP_TRACE_TYPE(SPEAKER_ENABLE);
		DEEP_TRACE_TYPE(STATUS);
		DEEP_TRACE_TYPE(WRITEMEMORY);
		DEEP_TRACE_TYPE(READMEMORY);
		DEEP_TRACE_TYPE(SPEAKER_DATA);
		DEEP_TRACE_TYPE(SPEAKER_MUTE);
		DEEP_TRACE_TYPE(IR2);
	default:
		TRACE(_T("WriteReport: type [%02x][%02x]"), buff[1], buff[2]);
	}
#endif

	if (bUseHIDwrite)
	{
		/* no where to release handle, I have to do it myself */
		if (HIDwriteThread)
		{
			if (WaitForSingleObject(HIDwriteThread, 0) == WAIT_OBJECT_0) {
				CloseHandle(HIDwriteThread);
				HIDwriteThread = NULL;
			}
		}
		// HidD_SetOutputReport: +: works on MS Bluetooth stacks (WriteFile doesn't).
		//						 -: is synchronous, so make it async
		if (!HIDwriteThread)
		{
			HIDwriteThread = (HANDLE)_beginthreadex(NULL, 0, HIDwriteThreadfunc,
				this, 0, NULL);
			_ASSERT(HIDwriteThread);
			if (!HIDwriteThread) {
				WARN(_T("couldn't create HID write thread!"));
				return false;
			}
			SetThreadPriority(HIDwriteThread, WORKER_THREAD_PRIORITY);
		}

		// insert the write request into the thread's queue
#ifdef USE_DYNAMIC_HIDQUEUE
		EnterCriticalSection(&HIDwriteQueueLock);
		BYTE *buff_copy = new BYTE[REPORT_LENGTH];
#else
		// allocate the HID write queue once
		if (!HID.Queue && !HID.Allocate())
			return false;

		EnterCriticalSection(&HIDwriteQueueLock);
		BYTE *buff_copy = HID.Queue[HID.WriteIndex].Report;
#endif
		memcpy(buff_copy, buff, REPORT_LENGTH);

#ifdef USE_DYNAMIC_HIDQUEUE
		HIDwriteQueue.push(buff_copy);
#else
		HID.WriteIndex++;
		HID.WriteIndex &= (HID.MAX_QUEUE_ENTRIES - 1);

		// check if the fixed report queue has overflown:
		//  if this ASSERT triggers, the HID write queue (that stores reports
		//   for asynchronous output by HIDwriteThreadfunc) has overflown.
		//  this can happen if the connection with the wiimote has been lost
		//   and in that case is harmless.
		//
		//  if it happens during normal operation though you need to increase
		//   hid::MAX_QUEUE_ENTRIES to the next power-of-2 (see comments)
		//   _and_ email me the working setting so I can update the next release
		_ASSERT(HID.WriteIndex != HID.ReadIndex);
#endif
		LeaveCriticalSection(&HIDwriteQueueLock);
		return true;
	}

	// WriteFile:
	DWORD written;
	if (!WriteFile(Handle, buff, REPORT_LENGTH, &written, &Overlapped))
	{
		DWORD error = GetLastError();
		if (error != ERROR_IO_PENDING) {
			TRACE(_T("WriteFile failed, err: %u!"), error);
			// if it worked previously, assume we lost the connection
			if (IsConnected())
				bConnectionLost = true;
#ifndef USE_DYNAMIC_HIDQUEUE
			HID.Deallocate();
#endif
			return false;
		}
	}
	return true;
}
// ------------------------------------------------------------------------------------
// experimental speaker support:
// ------------------------------------------------------------------------------------
bool wiimote::MuteSpeaker(bool on)
{
	_ASSERT(IsConnected());
	if (!IsConnected())
		return false;

	if (Internal.Speaker.bMuted == on)
		return true;

	if (on) TRACE(_T("muting speaker."));
	else   TRACE(_T("unmuting speaker."));

	BYTE buff[REPORT_LENGTH] = { 0 };
	buff[0] = OUT_SPEAKER_MUTE;
	buff[1] = (on ? 0x04 : 0x00) | GetRumbleBit();
	if (!WriteReport(buff))
		return false;
	Sleep(1);
	Internal.Speaker.bMuted = on;
	return true;
}
// ------------------------------------------------------------------------------------
bool wiimote::EnableSpeaker(bool on)
{
	_ASSERT(IsConnected());
	if (!IsConnected())
		return false;

	if (Internal.Speaker.bEnabled == on)
		return true;

	if (on) TRACE(_T("enabling speaker.")); else TRACE(_T("disabling speaker."));

	BYTE buff[REPORT_LENGTH] = { 0 };
	buff[0] = OUT_SPEAKER_ENABLE;
	buff[1] = (on ? 0x04 : 0x00) | GetRumbleBit();
	if (!WriteReport(buff))
		return false;

	if (!on) {
		Internal.Speaker.Freq = FREQ_NONE;
		Internal.Speaker.Volume = 0;
		MuteSpeaker(true);
	}

	Internal.Speaker.bEnabled = on;
	return true;
}
// ------------------------------------------------------------------------------------
#ifdef TR4 // TEMP, ignore
extern int hzinc;
#endif
// ------------------------------------------------------------------------------------
unsigned __stdcall wiimote::SampleStreamThreadfunc(void* param)
{
	TRACE(_T("(starting sample thread)"));
	// sends a simple square wave sample stream
	wiimote &remote = *(wiimote*)param;

	static BYTE squarewave_report[REPORT_LENGTH] =
	{ OUT_SPEAKER_DATA, 20 << 3, 0xC3,0xC3,0xC3,0xC3,0xC3,0xC3,0xC3,0xC3,0xC3,0xC3,
							   0xC3,0xC3,0xC3,0xC3,0xC3,0xC3,0xC3,0xC3,0xC3,0xC3, };
	static BYTE sample_report[REPORT_LENGTH] =
	{ OUT_SPEAKER_DATA, 0 };

	bool		   last_playing = false;
	DWORD		   frame = 0;
	DWORD		   frame_start = 0;
	unsigned	   total_samples = 0;
	unsigned	   sample_index = 0;
	wiimote_sample *current_sample = NULL;

	// TODO: duration!!
	while (remote.IsConnected())
	{
		bool playing = remote.IsPlayingAudio();

		if (!playing)
			Sleep(1);
		else {
			const unsigned freq_hz = FreqLookup[remote.Internal.Speaker.Freq];
#ifdef TR4
			const float    frame_ms = 1000 / ((freq_hz + hzinc) / 40.f); // 20bytes = 40 samples per write
#else
			const float    frame_ms = 1000 / (freq_hz / 40.f); // 20bytes = 40 samples per write
#endif

			// has the sample just changed?
			bool sample_changed = (current_sample != remote.CurrentSample);
			current_sample = (wiimote_sample*)remote.CurrentSample;

			// (attempts to minimise glitches, doesn't seem to help though)
			//#define FIRSTFRAME_IS_SILENT	// send all-zero for first frame

#ifdef FIRSTFRAME_IS_SILENT
			bool silent_frame = false;
#endif
			if (!last_playing || sample_changed) {
				frame = 0;
				frame_start = timeGetTime();
				total_samples = current_sample ? current_sample->length : 0;
				sample_index = 0;
#ifdef FIRSTFRAME_IS_SILENT
				silent_frame = true;
#endif
			}

			// are we streaming a sample?
			if (current_sample)
			{
				if (sample_index < current_sample->length)
				{
					// (remember that samples are 4bit, ie. 2 per byte)
					unsigned samples_left = (current_sample->length - sample_index);
					unsigned report_samples = min(samples_left, (unsigned)40);
					// round the entries up to the nearest multiple of 2
					unsigned report_entries = (report_samples + 1) >> 1;

					sample_report[1] = (BYTE)((report_entries << 3) |
						remote.GetRumbleBit());
#ifdef FIRSTFRAME_IS_SILENT
					if (silent_frame) {
						// send all-zeroes
						for (unsigned index = 0; index < report_entries; index++)
							sample_report[2 + index] = 0;
						remote.WriteReport(sample_report);
					}
					else
#endif
					{
						for (unsigned index = 0; index < report_entries; index++)
							sample_report[2 + index] =
							current_sample->samples[(sample_index >> 1) + index];
						remote.WriteReport(sample_report);
						sample_index += report_samples;
					}
				}
				else {
					// we reached the sample end
					remote.CurrentSample = NULL;
					current_sample = NULL;
					remote.Internal.Speaker.Freq = FREQ_NONE;
					remote.Internal.Speaker.Volume = 0;
				}
			}
			// no, a squarewave
			else {
				squarewave_report[1] = (20 << 3) | remote.GetRumbleBit();
				remote.WriteReport(squarewave_report);
#if 0
				// verify that we're sending at the correct rate (we are)
				DWORD elapsed = (timeGetTime() - frame_start);
				unsigned total_samples = frame * 40;
				float elapsed_secs = elapsed / 1000.f;
				float sent_persec = total_samples / elapsed_secs;
#endif
			}

			frame++;

			// send the first two buffers immediately? (attempts to lessen startup
			//  startup glitches by assuming we're filling a small sample
			//  (or general input) buffer on the wiimote) - doesn't seem to help
//			if(frame > 2) {
			while ((timeGetTime() - frame_start) < (unsigned)(frame*frame_ms))
				Sleep(1);
			//				}
		}

		last_playing = playing;
	}

	TRACE(_T("(ending sample thread)"));
	return 0;
}
// ------------------------------------------------------------------------------------
bool wiimote::Load16bitMonoSampleWAV(const TCHAR* filepath, wiimote_sample &out)
{
	// converts unsigned 16bit mono .wav audio data to the 4bit ADPCM variant
	//  used by the Wiimote (at least the closest match so far), and returns
	//  the data in a BYTE array (caller must delete[] it when no longer needed):
	memset(&out, 0, sizeof(out));

	TRACE(_T("Loading '%s'"), filepath);

	FILE *file;
#if (_MSC_VER >= 1400) // VC 2005+
	_tfopen_s(&file, filepath, _T("rb"));
#else
	file = _tfopen(filepath, _T("rb"));
#endif
	_ASSERT(file);
	if (!file) {
		WARN(_T("Couldn't open '%s"), filepath);
		return false;
	}

	// parse the .wav file
	struct riff_chunkheader {
		char  ckID[4];
		DWORD ckSize;
		char  formType[4];
	};
	struct chunk_header {
		char  ckID[4];
		DWORD ckSize;
	};
	union {
		WAVEFORMATEX		 x;
		WAVEFORMATEXTENSIBLE xe;
	} wf = {};

	riff_chunkheader riff_chunkheader;
	chunk_header	 chunk_header;
	speaker_freq	 freq = FREQ_NONE;

#define READ(data)			if(fread(&data, sizeof(data), 1, file) != 1) { \
									TRACE(_T(".wav file corrupt"));			   \
									fclose(file);							   \
									return false;							   \
									}
#define READ_SIZE(ptr,size)	if(fread(ptr, size, 1, file) != 1) {		   \
									TRACE(_T(".wav file corrupt"));			   \
									fclose(file);							   \
									return false;							   \
									}
	// read the riff chunk header
	READ(riff_chunkheader);

	// valid RIFF file?
	_ASSERT(!strncmp(riff_chunkheader.ckID, "RIFF", 4));
	if (strncmp(riff_chunkheader.ckID, "RIFF", 4))
		goto unsupported; // nope
	// valid WAV variant?
	_ASSERT(!strncmp(riff_chunkheader.formType, "WAVE", 4));
	if (strncmp(riff_chunkheader.formType, "WAVE", 4))
		goto unsupported; // nope

	// find the format & data chunks
	while (1)
	{
		READ(chunk_header);

		if (!strncmp(chunk_header.ckID, "fmt ", 4))
		{
			// not a valid .wav file?
			if (chunk_header.ckSize < 16 ||
				chunk_header.ckSize > sizeof(WAVEFORMATEXTENSIBLE))
				goto unsupported;

			READ_SIZE((BYTE*)&wf.x, chunk_header.ckSize);

			// now we know it's true wav file
			bool extensible = (wf.x.wFormatTag == WAVE_FORMAT_EXTENSIBLE);
			int format = extensible ? wf.xe.SubFormat.Data1 :
				wf.x.wFormatTag;
			// must be uncompressed PCM (the format comparisons also work on
			//  the 'extensible' header, even though they're named differently)
			if (format != WAVE_FORMAT_PCM) {
				TRACE(_T(".. not uncompressed PCM"));
				goto unsupported;
			}

			// must be mono, 16bit
			if ((wf.x.nChannels != 1) || (wf.x.wBitsPerSample != 16)) {
				TRACE(_T(".. %d bit, %d channel%s"), wf.x.wBitsPerSample,
					wf.x.nChannels,
					(wf.x.nChannels > 1 ? _T("s") : _T("")));
				goto unsupported;
			}

			// must be _near_ a supported speaker frequency range (but allow some
			//  tolerance, especially as the speaker freq values aren't final yet):
			unsigned	   sample_freq = wf.x.nSamplesPerSec;
			const unsigned epsilon = 100; // for now

			for (unsigned index = 1; index < ARRAY_ENTRIES(FreqLookup); index++)
			{
				if ((sample_freq + epsilon) >= FreqLookup[index] &&
					(sample_freq - epsilon) <= FreqLookup[index]) {
					freq = (speaker_freq)index;
					TRACE(_T(".. using speaker freq %u"), FreqLookup[index]);
					break;
				}
			}
			if (freq == FREQ_NONE) {
				WARN(_T("Couldn't (loosely) match .wav samplerate %u Hz to speaker"),
					sample_freq);
				goto unsupported;
			}
		}
		else if (!strncmp(chunk_header.ckID, "data", 4))
		{
			// make sure we got a valid fmt chunk first
			if (!wf.x.nBlockAlign)
				goto corrupt_file;

			// grab the data
			unsigned total_samples = chunk_header.ckSize / wf.x.nBlockAlign;
			if (total_samples == 0)
				goto corrupt_file;

			short *samples = new short[total_samples];
			size_t read = fread(samples, 2, total_samples, file);
			fclose(file);
			if (read != total_samples)
			{
				if (read == 0) {
					delete[] samples;
					goto corrupt_file;
				}
				// got a different number, but use them anyway
				WARN(_T("found %s .wav audio data than expected (%u/%u samples)"),
					((read < total_samples) ? _T("less") : _T("more")),
					read, total_samples);

				total_samples = read;
			}

			// and convert them
			bool res = Convert16bitMonoSamples(samples, true, total_samples, freq,
				out);
			delete[] samples;
			return res;
		}
		else {
			// unknown chunk, skip its data
			DWORD chunk_bytes = (chunk_header.ckSize + 1) & ~1L;
			if (fseek(file, chunk_bytes, SEEK_CUR))
				goto corrupt_file;
		}
	}

corrupt_file:
	WARN(_T(".wav file is corrupt"));
	fclose(file);
	return false;

unsupported:
	WARN(_T(".wav file format not supported (must be mono 16bit PCM)"));
	fclose(file);
	return false;
}
// ------------------------------------------------------------------------------------
bool wiimote::Load16BitMonoSampleRAW(const TCHAR*   filepath,
	bool		     _signed,
	speaker_freq   freq,
	wiimote_sample &out)
{
	// converts (.wav style) unsigned 16bit mono raw data to the 4bit ADPCM variant
	//  used by the Wiimote, and returns the data in a BYTE array (caller must
	//  delete[] it when no longer needed):
	memset(&out, 0, sizeof(out));

	// get the length of the file
	struct _stat file_info;
	if (_tstat(filepath, &file_info)) {
		WARN(_T("couldn't get filesize for '%s'"), filepath);
		return false;
	}

	DWORD len = file_info.st_size;
	_ASSERT(len);
	if (!len) {
		WARN(_T("zero-size sample file '%s'"), filepath);
		return false;
	}

	unsigned total_samples = (len + 1) / 2; // round up just in case file is corrupt
	// allocate a buffer to hold the samples to convert
	short *samples = new short[total_samples];
	_ASSERT(samples);
	if (!samples) {
		TRACE(_T("Couldn't open '%s"), filepath);
		return false;
	}

	// load them
	FILE *file;
	bool res;
#if (_MSC_VER >= 1400) // VC 2005+
	_tfopen_s(&file, filepath, _T("rb"));
#else
	file = _tfopen(filepath, _T("rb"));
#endif
	_ASSERT(file);
	if (!file) {
		TRACE(_T("Couldn't open '%s"), filepath);
		goto error;
	}

	res = (fread(samples, 1, len, file) == len);
	fclose(file);
	if (!res) {
		WARN(_T("Couldn't load file '%s'"), filepath);
		goto error;
	}

	// and convert them
	res = Convert16bitMonoSamples(samples, _signed, total_samples, freq, out);
	delete[] samples;
	return res;

error:
	delete[] samples;
	return false;
}
// ------------------------------------------------------------------------------------
bool wiimote::Convert16bitMonoSamples(const short*   samples,
	bool		      _signed,
	DWORD		  length,
	speaker_freq   freq,
	wiimote_sample &out)
{
	// converts 16bit mono sample data to the native 4bit format used by the Wiimote,
	//  and returns the data in a BYTE array (caller must delete[] when no
	//  longer needed):
	memset(&out, 0, sizeof(0));

	_ASSERT(samples && length);
	if (!samples || !length)
		return false;

	// allocate the output buffer
	out.samples = new BYTE[length];
	_ASSERT(out.samples);
	if (!out.samples)
		return false;

	// clear it
	memset(out.samples, 0, length);
	out.length = length;
	out.freq = freq;

	// ADPCM code, adapted from
	//  http://www.wiindows.org/index.php/Talk:Wiimote#Input.2FOutput_Reports
	static const int index_table[16] = { -1,  -1,  -1,  -1,   2,   4,   6,   8,
										  -1,  -1,  -1,  -1,   2,   4,   6,   8 };
	static const int diff_table[16] = { 1,   3,   5,   7,   9,  11,  13,  15,
										  -1,  -3,  -5,  -7,  -9, -11, -13,  15 };
	static const int step_scale[16] = { 230, 230, 230, 230, 307, 409, 512, 614,
										 230, 230, 230, 230, 307, 409, 512, 614 };
	// Encode to ADPCM, on initialization set adpcm_prev_value to 0 and adpcm_step
	//  to 127 (these variables must be preserved across reports)
	int adpcm_prev_value = 0;
	int adpcm_step = 127;

	for (size_t i = 0; i < length; i++)
	{
		// convert to 16bit signed
		int value = samples[i];// (8bit) << 8);// | samples[i]; // dither it?
		if (!_signed)
			value -= 32768;
		// encode:
		int  diff = value - adpcm_prev_value;
		BYTE encoded_val = 0;
		if (diff < 0) {
			encoded_val |= 8;
			diff = -diff;
		}
		diff = (diff << 2) / adpcm_step;
		if (diff > 7)
			diff = 7;
		encoded_val |= diff;
		adpcm_prev_value += ((adpcm_step * diff_table[encoded_val]) / 8);
		if (adpcm_prev_value > 0x7fff)
			adpcm_prev_value = 0x7fff;
		if (adpcm_prev_value < -0x8000)
			adpcm_prev_value = -0x8000;
		adpcm_step = (adpcm_step * step_scale[encoded_val]) >> 8;
		if (adpcm_step < 127)
			adpcm_step = 127;
		if (adpcm_step > 24567)
			adpcm_step = 24567;
		if (i & 1)
			out.samples[i >> 1] |= encoded_val;
		else
			out.samples[i >> 1] |= encoded_val << 4;
	}

	return true;
}
// ------------------------------------------------------------------------------------
bool wiimote::PlaySample(const wiimote_sample &sample, BYTE volume,
	speaker_freq freq_override)
{
	_ASSERT(IsConnected());
	if (!IsConnected())
		return false;

	speaker_freq freq = freq_override ? freq_override : sample.freq;

	TRACE(_T("playing sample."));
	EnableSpeaker(true);
	MuteSpeaker(true);

#if 0
	// combine everything into one write - faster, seems to work?
	BYTE bytes[9] = { 0x00, 0x00, 0x00, 10 + freq, vol, 0x00, 0x00, 0x01, 0x01 };
	WriteData(0x04a20001, sizeof(bytes), bytes);
#else
	// Write 0x01 to register 0x04a20009 
	WriteData(0x04a20009, 0x01);
	// Write 0x08 to register 0x04a20001 
	WriteData(0x04a20001, 0x08);
	// Write 7-byte configuration to registers 0x04a20001-0x04a20008 
	BYTE bytes[7] = { '\0', '\0', '\0', BYTE(10 + freq), volume, '\0', '\0' };
	WriteData(0x04a20001, sizeof(bytes), bytes);
	// + Write 0x01 to register 0x04a20008 
	WriteData(0x04a20008, 0x01);
#endif

	Internal.Speaker.Freq = freq;
	Internal.Speaker.Volume = volume;
	CurrentSample = &sample;

	MuteSpeaker(false);

	return StartSampleThread();
}
// ------------------------------------------------------------------------------------
bool wiimote::StartSampleThread()
{
	if (SampleThread)
		return true;

	SampleThread = (HANDLE)_beginthreadex(NULL, 0, SampleStreamThreadfunc,
		this, 0, NULL);
	_ASSERT(SampleThread);
	if (!SampleThread) {
		WARN(_T("couldn't create sample thread!"));
		MuteSpeaker(true);
		EnableSpeaker(false);
		return false;
	}
	SetThreadPriority(SampleThread, WORKER_THREAD_PRIORITY);
	return true;
}
// ------------------------------------------------------------------------------------
bool wiimote::PlaySquareWave(speaker_freq freq, BYTE volume)
{
	_ASSERT(IsConnected());
	if (!IsConnected())
		return false;

	// if we're already playing a sample, stop it first
	if (IsPlayingSample())
		CurrentSample = NULL;
	// if we're already playing a square wave at this freq and volume, return
	else if (IsPlayingAudio() && (Internal.Speaker.Freq == freq) &&
		(Internal.Speaker.Volume == volume))
		return true;

	TRACE(_T("playing square wave."));
	// stop playing samples
	CurrentSample = 0;

	EnableSpeaker(true);
	MuteSpeaker(true);

#if 0
	// combined everything into one write - much faster, seems to work?
	BYTE bytes[9] = { 0x00, 0x00, 0x00, freq, volume, 0x00, 0x00, 0x01, 0x1 };
	WriteData(0x04a20001, sizeof(bytes), bytes);
#else
	// write 0x01 to register 0xa20009 
	WriteData(0x04a20009, 0x01);
	// write 0x08 to register 0xa20001 
	WriteData(0x04a20001, 0x08);
	// write default sound mode (4bit ADPCM, we assume) 7-byte configuration
	//  to registers 0xa20001-0xa20008 
	BYTE bytes[7] = { '\0', '\0', '\0', BYTE(10 + freq), volume, '\0', '\0' };
	WriteData(0x04a20001, sizeof(bytes), bytes);
	// write 0x01 to register 0xa20008 
	WriteData(0x04a20008, 0x01);
#endif

	Internal.Speaker.Freq = freq;
	Internal.Speaker.Volume = volume;

	MuteSpeaker(false);
	return StartSampleThread();
}
// ------------------------------------------------------------------------------------
void wiimote::RecordState(state_history	  &events_out,
	unsigned			  max_time_ms,
	state_change_flags change_trigger)
{
	// user being naughty?
	if (Recording.bEnabled)
		StopRecording();

	// clear the list
	if (!events_out.empty())
		events_out.clear();

	// start recording 
	Recording.StateHistory = &events_out;
	Recording.StartTimeMS = timeGetTime();
	Recording.EndTimeMS = Recording.StartTimeMS + max_time_ms;
	Recording.TriggerFlags = change_trigger;
	// as this call happens outside the read/parse thread, set the boolean
	//  which will enable reocrding last, so that all params are in place.
	// TODO: * stricly speaking this only works on VC2005+ or better, as it
	//		   automatically places a memory barrier on volatile variables - earlier/
	//         other compilers may reorder the assignments!). *
	Recording.bEnabled = true;
}
// ------------------------------------------------------------------------------------
void wiimote::StopRecording()
{
	if (!Recording.bEnabled)
		return;

	Recording.bEnabled = false;
	// make sure the read/parse thread has time to notice the change (else it might
	//  still write one more state to the list)
	Sleep(10); // too much?
}
// ------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------