diff options
Diffstat (limited to 'tracker-pt/wiiyourself/wiimote.cpp')
-rw-r--r-- | tracker-pt/wiiyourself/wiimote.cpp | 2806 |
1 files changed, 0 insertions, 2806 deletions
diff --git a/tracker-pt/wiiyourself/wiimote.cpp b/tracker-pt/wiiyourself/wiimote.cpp deleted file mode 100644 index 46130cca..00000000 --- a/tracker-pt/wiiyourself/wiimote.cpp +++ /dev/null @@ -1,2806 +0,0 @@ -// _______________________________________________________________________________ -// -// - 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? - } -// ------------------------------------------------------------------------------------ -// ------------------------------------------------------------------------------------ |