summaryrefslogtreecommitdiffhomepage
path: root/wiiyourself/wiimote.cpp
diff options
context:
space:
mode:
authorStanisław Halik <sthalik@misaki.pl>2018-01-26 07:02:00 +0100
committerGitHub <noreply@github.com>2018-01-26 07:02:00 +0100
commit5b93efaa8223a5fbaf15367f1f4df12411f6afde (patch)
tree76177039890366c4ec685e6409f1ada04d73f82d /wiiyourself/wiimote.cpp
parent87f087261814fa03dddd4f173ad01dea99bd2255 (diff)
parentef1172e936c054946cb8a4b5bed7e995b3136ebb (diff)
Merge pull request #735 from cpuwolf/wii2
tracker/wii: change setting UI & reorg wiiyourself
Diffstat (limited to 'wiiyourself/wiimote.cpp')
-rw-r--r--wiiyourself/wiimote.cpp2806
1 files changed, 2806 insertions, 0 deletions
diff --git a/wiiyourself/wiimote.cpp b/wiiyourself/wiimote.cpp
new file mode 100644
index 00000000..46130cca
--- /dev/null
+++ b/wiiyourself/wiimote.cpp
@@ -0,0 +1,2806 @@
+// _______________________________________________________________________________
+//
+// - 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
+#endif
+#ifndef DEEP_TRACE
+# define DEEP_TRACE
+#endif
+#ifndef WARN
+# define WARN
+#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* 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* 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;
+ 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;
+ }
+
+ #define IF_TYPE(id) if(type == id) { \
+ /* sometimes it comes in more than once */ \
+ if(Internal.ExtensionType == wiimote_state::id)\
+ break; \
+ Internal.ExtensionType = wiimote_state::id;
+
+ // 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;
+ }
+ 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)
+ {
+ // 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 = {0};
+
+ 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] = { 0x00, 0x00, 0x00, 10+(BYTE)freq, volume, 0x00, 0x00 };
+ 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] = { 0x00, 0x00, 0x00, 10+(BYTE)freq, volume, 0x00, 0x00 };
+ 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?
+ }
+// ------------------------------------------------------------------------------------
+// ------------------------------------------------------------------------------------