From 911e9deb712bafb5f622e215e286744696e90617 Mon Sep 17 00:00:00 2001 From: Wei Shuai Date: Mon, 15 Jan 2018 17:08:48 +0800 Subject: add Wiimote supporting in point tracker 1. extrack point from wii frame 2. wii_camera outputs wii raw data, wii_extractor create preview 3. wiimote fov is about 42 degree 4. wii:change block functions to non-block function "contains WiiYourself! wiimote code by gl.tter http://gl.tter.org" --- tracker-pt/CMakeLists.txt | 3 + tracker-pt/tracker-wii/CMakeLists.txt | 6 + tracker-pt/tracker-wii/Resources/wii.ico | Bin 0 -> 10134 bytes tracker-pt/tracker-wii/Resources/wii.png | Bin 0 -> 2974 bytes tracker-pt/tracker-wii/lang/nl_NL.ts | 4 + tracker-pt/tracker-wii/lang/ru_RU.ts | 4 + tracker-pt/tracker-wii/lang/stub.ts | 4 + tracker-pt/tracker-wii/tracker_wii.qrc | 5 + tracker-pt/tracker-wii/wii_camera.cpp | 227 ++ tracker-pt/tracker-wii/wii_camera.h | 75 + tracker-pt/tracker-wii/wii_frame.cpp | 88 + tracker-pt/tracker-wii/wii_frame.hpp | 61 + tracker-pt/tracker-wii/wii_module.cpp | 72 + tracker-pt/tracker-wii/wii_point_extractor.cpp | 142 ++ tracker-pt/tracker-wii/wii_point_extractor.h | 40 + tracker-pt/wiiyourself/CMakeLists.txt | 1 + tracker-pt/wiiyourself/History.txt | 281 +++ tracker-pt/wiiyourself/License.txt | 42 + tracker-pt/wiiyourself/ReadMe.txt | 202 ++ tracker-pt/wiiyourself/lang/nl_NL.ts | 4 + tracker-pt/wiiyourself/lang/ru_RU.ts | 4 + tracker-pt/wiiyourself/lang/stub.ts | 4 + tracker-pt/wiiyourself/wiimote.cpp | 2806 ++++++++++++++++++++++++ tracker-pt/wiiyourself/wiimote.h | 495 +++++ tracker-pt/wiiyourself/wiimote_common.h | 109 + tracker-pt/wiiyourself/wiimote_state.h | 379 ++++ 26 files changed, 5058 insertions(+) create mode 100644 tracker-pt/tracker-wii/CMakeLists.txt create mode 100644 tracker-pt/tracker-wii/Resources/wii.ico create mode 100644 tracker-pt/tracker-wii/Resources/wii.png create mode 100644 tracker-pt/tracker-wii/lang/nl_NL.ts create mode 100644 tracker-pt/tracker-wii/lang/ru_RU.ts create mode 100644 tracker-pt/tracker-wii/lang/stub.ts create mode 100644 tracker-pt/tracker-wii/tracker_wii.qrc create mode 100644 tracker-pt/tracker-wii/wii_camera.cpp create mode 100644 tracker-pt/tracker-wii/wii_camera.h create mode 100644 tracker-pt/tracker-wii/wii_frame.cpp create mode 100644 tracker-pt/tracker-wii/wii_frame.hpp create mode 100644 tracker-pt/tracker-wii/wii_module.cpp create mode 100644 tracker-pt/tracker-wii/wii_point_extractor.cpp create mode 100644 tracker-pt/tracker-wii/wii_point_extractor.h create mode 100644 tracker-pt/wiiyourself/CMakeLists.txt create mode 100644 tracker-pt/wiiyourself/History.txt create mode 100644 tracker-pt/wiiyourself/License.txt create mode 100644 tracker-pt/wiiyourself/ReadMe.txt create mode 100644 tracker-pt/wiiyourself/lang/nl_NL.ts create mode 100644 tracker-pt/wiiyourself/lang/ru_RU.ts create mode 100644 tracker-pt/wiiyourself/lang/stub.ts create mode 100644 tracker-pt/wiiyourself/wiimote.cpp create mode 100644 tracker-pt/wiiyourself/wiimote.h create mode 100644 tracker-pt/wiiyourself/wiimote_common.h create mode 100644 tracker-pt/wiiyourself/wiimote_state.h (limited to 'tracker-pt') diff --git a/tracker-pt/CMakeLists.txt b/tracker-pt/CMakeLists.txt index ebabd364..2e8446d5 100644 --- a/tracker-pt/CMakeLists.txt +++ b/tracker-pt/CMakeLists.txt @@ -6,3 +6,6 @@ if(OpenCV_FOUND) target_link_libraries(opentrack-tracker-pt-base opentrack-cv ${modules}) endif() add_subdirectory(module) +add_subdirectory(wiiyourself) +add_subdirectory(tracker-wii) + diff --git a/tracker-pt/tracker-wii/CMakeLists.txt b/tracker-pt/tracker-wii/CMakeLists.txt new file mode 100644 index 00000000..a22121ef --- /dev/null +++ b/tracker-pt/tracker-wii/CMakeLists.txt @@ -0,0 +1,6 @@ +find_package(OpenCV 3.0 QUIET) +if(OpenCV_FOUND) + otr_module(tracker-wii) + target_link_libraries(opentrack-tracker-wii opentrack-tracker-pt-base opentrack-tracker-wii-pt-wiiyourself) + target_include_directories(opentrack-tracker-wii PRIVATE "${CMAKE_SOURCE_DIR}/tracker-pt") +endif() \ No newline at end of file diff --git a/tracker-pt/tracker-wii/Resources/wii.ico b/tracker-pt/tracker-wii/Resources/wii.ico new file mode 100644 index 00000000..44204a68 Binary files /dev/null and b/tracker-pt/tracker-wii/Resources/wii.ico differ diff --git a/tracker-pt/tracker-wii/Resources/wii.png b/tracker-pt/tracker-wii/Resources/wii.png new file mode 100644 index 00000000..d83b6e77 Binary files /dev/null and b/tracker-pt/tracker-wii/Resources/wii.png differ diff --git a/tracker-pt/tracker-wii/lang/nl_NL.ts b/tracker-pt/tracker-wii/lang/nl_NL.ts new file mode 100644 index 00000000..9e739505 --- /dev/null +++ b/tracker-pt/tracker-wii/lang/nl_NL.ts @@ -0,0 +1,4 @@ + + + + diff --git a/tracker-pt/tracker-wii/lang/ru_RU.ts b/tracker-pt/tracker-wii/lang/ru_RU.ts new file mode 100644 index 00000000..f62cf2e1 --- /dev/null +++ b/tracker-pt/tracker-wii/lang/ru_RU.ts @@ -0,0 +1,4 @@ + + + + diff --git a/tracker-pt/tracker-wii/lang/stub.ts b/tracker-pt/tracker-wii/lang/stub.ts new file mode 100644 index 00000000..6401616d --- /dev/null +++ b/tracker-pt/tracker-wii/lang/stub.ts @@ -0,0 +1,4 @@ + + + + diff --git a/tracker-pt/tracker-wii/tracker_wii.qrc b/tracker-pt/tracker-wii/tracker_wii.qrc new file mode 100644 index 00000000..a7a50512 --- /dev/null +++ b/tracker-pt/tracker-wii/tracker_wii.qrc @@ -0,0 +1,5 @@ + + + Resources/wii.png + + diff --git a/tracker-pt/tracker-wii/wii_camera.cpp b/tracker-pt/tracker-wii/wii_camera.cpp new file mode 100644 index 00000000..21ff998d --- /dev/null +++ b/tracker-pt/tracker-wii/wii_camera.cpp @@ -0,0 +1,227 @@ +/* Copyright (c) 2012 Patrick Ruoff + * Copyright (c) 2015-2016 Stanislaw Halik + * Copyright (c) 2017-2018 Wei Shuai + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + */ + + +#include "wii_camera.h" +#include "wii_frame.hpp" + +#include "compat/sleep.hpp" +#include "compat/camera-names.hpp" +#include "compat/math-imports.hpp" + +#include + +#include "cv/video-property-page.hpp" + + +using namespace pt_module; + +WIICamera::WIICamera(const QString& module_name) : s { module_name } +{ + cam_info.fps = 70; + cam_info.res_x = 1024; + cam_info.res_y = 768; + cam_info.fov = 42.0f; +} + +QString WIICamera::get_desired_name() const +{ + return desired_name; +} + +QString WIICamera::get_active_name() const +{ + return active_name; +} + +void WIICamera::show_camera_settings() +{ + +} + +WIICamera::result WIICamera::get_info() const +{ + if (cam_info.res_x == 0 || cam_info.res_y == 0) + return result(false, pt_camera_info()); + return result(true, cam_info); +} + +WIICamera::result WIICamera::get_frame(pt_frame& frame_) +{ + cv::Mat& frame = frame_.as()->mat; + struct wii_info& wii = frame_.as()->wii; + + const wii_camera_status new_frame = _get_frame(frame); + //create a fake blank frame + frame = cv::Mat(cam_info.res_x, cam_info.res_y, CV_8UC3, cv::Scalar(0, 0, 0)); + wii.status = new_frame; + + switch (new_frame) + { + case wii_cam_data_change: + _get_status(wii); + _get_points(wii); + break; + case wii_cam_data_no_change: + return result(false, cam_info); + } + + return result(true, cam_info); +} + +pt_camera_open_status WIICamera::start(int idx, int fps, int res_x, int res_y) +{ + m_pDev = new wiimote; + if (m_pDev == NULL) + { + stop(); + return cam_open_error; + } + m_pDev->ChangedCallback = on_state_change; + m_pDev->CallbackTriggerFlags = (state_change_flags)(CONNECTED | + EXTENSION_CHANGED | + MOTIONPLUS_CHANGED); + return cam_open_ok_no_change; +} + +void WIICamera::stop() +{ + onExit = true; + m_pDev->ChangedCallback = NULL; + m_pDev->Disconnect(); + Beep(1000, 200); + if (m_pDev) { + delete m_pDev; + m_pDev = NULL; + } + + desired_name = QString(); + active_name = QString(); + cam_info = pt_camera_info(); + cam_desired = pt_camera_info(); +} + +wii_camera_status WIICamera::_get_frame(cv::Mat& frame) +{ + wii_camera_status ret = wii_cam_wait_for_connect; + + if (!m_pDev->IsConnected()) { + qDebug() << "wii wait"; + if (!m_pDev->Connect(wiimote::FIRST_AVAILABLE)) { + Beep(500, 30); Sleep(1000); + goto goodbye; + } + } + + if (m_pDev->RefreshState() == NO_CHANGE) { + Sleep(1); // don't hog the CPU if nothing changed + ret = wii_cam_data_no_change; + goto goodbye; + } + + // did we loose the connection? + if (m_pDev->ConnectionLost()) + { + goto goodbye; + } + + ret = wii_cam_data_change; +goodbye: + return ret; +} + +bool WIICamera::_get_points(struct wii_info& wii) +{ + bool dot_sizes = (m_pDev->IR.Mode == wiimote_state::ir::EXTENDED); + bool ret = false; + + for (unsigned index = 0; index < 4; index++) + { + wiimote_state::ir::dot &dot = m_pDev->IR.Dot[index]; + if (dot.bVisible) { + wii.Points[index].ux = dot.RawX; + wii.Points[index].uy = dot.RawY; + if (dot_sizes) { + wii.Points[index].isize = dot.Size; + } else { + wii.Points[index].isize = 1; + } + wii.Points[index].bvis = dot.bVisible; + ret = true; + } else { + wii.Points[index].ux = 0; + wii.Points[index].uy = 0; + wii.Points[index].isize = 0; + wii.Points[index].bvis = dot.bVisible; + } + } + return ret; +} + +void WIICamera::_get_status(struct wii_info& wii) +{ + //draw battery status + wii.BatteryPercent = m_pDev->BatteryPercent; + wii.bBatteryDrained = m_pDev->bBatteryDrained; + + //draw horizon + static int p = 0; + static int r = 0; + if (m_pDev->Nunchuk.Acceleration.Orientation.UpdateAge < 10) + { + p = m_pDev->Acceleration.Orientation.Pitch; + r = m_pDev->Acceleration.Orientation.Roll; + } + + wii.Pitch = p; + wii.Roll = r; +} + +void WIICamera::on_state_change(wiimote &remote, + state_change_flags changed, + const wiimote_state &new_state) +{ + // the wiimote just connected + if (changed & CONNECTED) + { + /* wiimote connected */ + remote.SetLEDs(0x0f); + Beep(1000, 300); Sleep(500); + + qDebug() << "wii connected"; + + if (new_state.ExtensionType != wiimote::BALANCE_BOARD) + { + if (new_state.bExtension) + remote.SetReportType(wiimote::IN_BUTTONS_ACCEL_IR_EXT); // no IR dots + else + remote.SetReportType(wiimote::IN_BUTTONS_ACCEL_IR); // IR dots + } + } + // another extension was just connected: + else if (changed & EXTENSION_CONNECTED) + { + + Beep(1000, 200); + + // switch to a report mode that includes the extension data (we will + // loose the IR dot sizes) + // note: there is no need to set report types for a Balance Board. + if (!remote.IsBalanceBoard()) + remote.SetReportType(wiimote::IN_BUTTONS_ACCEL_IR_EXT); + } + else if (changed & EXTENSION_DISCONNECTED) + { + + Beep(200, 300); + + // use a non-extension report mode (this gives us back the IR dot sizes) + remote.SetReportType(wiimote::IN_BUTTONS_ACCEL_IR); + } +} diff --git a/tracker-pt/tracker-wii/wii_camera.h b/tracker-pt/tracker-wii/wii_camera.h new file mode 100644 index 00000000..a7ac9c14 --- /dev/null +++ b/tracker-pt/tracker-wii/wii_camera.h @@ -0,0 +1,75 @@ +/* Copyright (c) 2012 Patrick Ruoff + * Copyright (c) 2015-2016 Stanislaw Halik + * Copyright (c) 2017-2018 Wei Shuai + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + */ + +#pragma once + +#include "pt-api.hpp" + +#include "compat/timer.hpp" + +#include +#include +#include + +#include +#include + +#include + +#include +#include "wii_frame.hpp" + +namespace pt_module { + +struct WIICamera final : pt_camera +{ + WIICamera(const QString& module_name); + + pt_camera_open_status start(int idx, int fps, int res_x, int res_y) override; + void stop() override; + + result get_frame(pt_frame& Frame) override; + result get_info() const override; + + pt_camera_info get_desired() const override { return cam_desired; } + QString get_desired_name() const override; + QString get_active_name() const override; + + operator bool() const override { return (m_pDev); } + + void set_fov(double value) override { fov = value; } + void show_camera_settings() override; + + +private: + wiimote * m_pDev; + static void on_state_change(wiimote &remote, + state_change_flags changed, + const wiimote_state &new_state); + bool onExit = false; + pt_frame internalframe; + + wii_camera_status _get_frame(cv::Mat& Frame); + bool _get_points(struct wii_info&); + void _get_status(struct wii_info&); + + double dt_mean = 0, fov = 42.; + + Timer t; + + pt_camera_info cam_info; + pt_camera_info cam_desired; + QString desired_name, active_name; + + pt_settings s; + + static constexpr inline double dt_eps = 1./384; +}; + +} // ns pt_module diff --git a/tracker-pt/tracker-wii/wii_frame.cpp b/tracker-pt/tracker-wii/wii_frame.cpp new file mode 100644 index 00000000..25228d57 --- /dev/null +++ b/tracker-pt/tracker-wii/wii_frame.cpp @@ -0,0 +1,88 @@ +/* +* Copyright (c) 2015-2016 Stanislaw Halik +* Copyright (c) 2017-2018 Wei Shuai +*/ + +#include "wii_frame.hpp" + +#include "compat/math.hpp" + +#include +#include + +#include + +using namespace pt_module; + +WIIPreview& WIIPreview::operator=(const pt_frame& frame_) +{ + const cv::Mat& frame = frame_.as_const()->mat; + ensure_size(frame_copy, frame_out.cols, frame_out.rows, CV_8UC3); + + if (frame.channels() != 3) + { + once_only(qDebug() << "tracker/pt: camera frame depth: 3 !=" << frame.channels()); + return *this; + } + + const bool need_resize = frame.cols != frame_out.cols || frame.rows != frame_out.rows; + if (need_resize) + cv::resize(frame, frame_copy, cv::Size(frame_out.cols, frame_out.rows), 0, 0, cv::INTER_NEAREST); + else + frame.copyTo(frame_copy); + + return *this; +} + +WIIPreview::WIIPreview(int w, int h) +{ + ensure_size(frame_out, w, h, CV_8UC4); + + frame_out.setTo(cv::Scalar(0, 0, 0, 0)); +} + +QImage WIIPreview::get_bitmap() +{ + int stride = frame_out.step.p[0]; + + if (stride < 64 || stride < frame_out.cols * 4) + { + once_only(qDebug() << "bad stride" << stride + << "for bitmap size" << frame_copy.cols << frame_copy.rows); + return QImage(); + } + + cv::cvtColor(frame_copy, frame_out, cv::COLOR_BGR2BGRA); + + return QImage((const unsigned char*) frame_out.data, + frame_out.cols, frame_out.rows, + stride, + QImage::Format_ARGB32); +} + +void WIIPreview::draw_head_center(double x, double y) +{ + double px_, py_; + + std::tie(px_, py_) = to_pixel_pos(x, y, frame_copy.cols, frame_copy.rows); + + int px = iround(px_), py = iround(py_); + + constexpr int len = 9; + + static const cv::Scalar color(0, 255, 255); + cv::line(frame_copy, + cv::Point(px - len, py), + cv::Point(px + len, py), + color, 1); + cv::line(frame_copy, + cv::Point(px, py - len), + cv::Point(px, py + len), + color, 1); +} + +void WIIPreview::ensure_size(cv::Mat& frame, int w, int h, int type) +{ + if (frame.cols != w || frame.rows != h) + frame = cv::Mat(h, w, type); +} diff --git a/tracker-pt/tracker-wii/wii_frame.hpp b/tracker-pt/tracker-wii/wii_frame.hpp new file mode 100644 index 00000000..4e3f0c95 --- /dev/null +++ b/tracker-pt/tracker-wii/wii_frame.hpp @@ -0,0 +1,61 @@ +/* +* Copyright (c) 2015-2016 Stanislaw Halik +* Copyright (c) 2017-2018 Wei Shuai +*/ +#pragma once + +#include "pt-api.hpp" + +#include +#include + + + +namespace pt_module { + +enum wii_camera_status : unsigned { wii_cam_wait_for_connect, wii_cam_data_no_change, wii_cam_data_change }; + +struct wii_info_points { + unsigned ux; + unsigned uy; + int isize; + bool bvis; +}; + +struct wii_info { + struct wii_info_points Points[4]; + bool bBatteryDrained; + unsigned char BatteryPercent; + float Pitch; + float Roll; + wii_camera_status status; +}; + +struct WIIFrame final : pt_frame +{ + cv::Mat mat; + struct wii_info wii; + + operator const cv::Mat&() const& { return mat; } + operator cv::Mat&() & { return mat; } +}; + +struct WIIPreview final : pt_preview +{ + WIIPreview(int w, int h); + + WIIPreview& operator=(const pt_frame& frame) override; + QImage get_bitmap() override; + void draw_head_center(double x, double y) override; + + operator cv::Mat&() { return frame_copy; } + operator cv::Mat const&() const { return frame_copy; } + +private: + static void ensure_size(cv::Mat& frame, int w, int h, int type); + + bool fresh = true; + cv::Mat frame_copy, frame_color, frame_resize, frame_out; +}; + +} // ns pt_module diff --git a/tracker-pt/tracker-wii/wii_module.cpp b/tracker-pt/tracker-wii/wii_module.cpp new file mode 100644 index 00000000..2f04ec9a --- /dev/null +++ b/tracker-pt/tracker-wii/wii_module.cpp @@ -0,0 +1,72 @@ +#include "ftnoir_tracker_pt.h" +#include "api/plugin-api.hpp" + +#include "wii_camera.h" +#include "wii_frame.hpp" +#include "wii_point_extractor.h" +#include "ftnoir_tracker_pt_dialog.h" + +#include "pt-api.hpp" + +#include + +static const QString module_name = "tracker-wii-pt"; + +using namespace pt_module; + +struct wii_pt_module_traits final : pt_runtime_traits +{ + pointer make_camera() const override + { + return pointer(new WIICamera(module_name)); + } + + pointer make_point_extractor() const override + { + return pointer(new WIIPointExtractor(module_name)); + } + + QString get_module_name() const override + { + return module_name; + } + + pointer make_frame() const override + { + return pointer(new WIIFrame); + } + + pointer make_preview(int w, int h) const override + { + return pointer(new WIIPreview(w, h)); + } +}; + +struct wii_tracker_pt : Tracker_PT +{ + wii_tracker_pt() : Tracker_PT(pointer(new wii_pt_module_traits)) + { + } +}; + + +struct wii_dialog_pt : TrackerDialog_PT +{ + wii_dialog_pt(); +}; + +class wii_metadata_pt : public Metadata +{ + QString name() { return _("WiiPointTracker 1.1"); } + QIcon icon() { return QIcon(":/Resources/wii.png"); } +}; + +// ns pt_module + +using namespace pt_module; + + + +wii_dialog_pt::wii_dialog_pt() : TrackerDialog_PT(module_name) {} + +OPENTRACK_DECLARE_TRACKER(wii_tracker_pt, wii_dialog_pt, wii_metadata_pt) diff --git a/tracker-pt/tracker-wii/wii_point_extractor.cpp b/tracker-pt/tracker-wii/wii_point_extractor.cpp new file mode 100644 index 00000000..7f90fb18 --- /dev/null +++ b/tracker-pt/tracker-wii/wii_point_extractor.cpp @@ -0,0 +1,142 @@ +/* Copyright (c) 2012 Patrick Ruoff + * Copyright (c) 2015-2017 Stanislaw Halik + * Copyright (c) 2017-2018 Wei Shuai + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + */ + +#include "wii_point_extractor.h" + +#include "point_tracker.h" +#include "wii_frame.hpp" + +#include "cv/numeric.hpp" +#include "compat/math.hpp" + +#include + +#undef PREVIEW +//#define PREVIEW + +#if defined PREVIEW +# include +#endif + +#include +#include +#include +#include + +#include + +using namespace types; +using namespace pt_module; + + +WIIPointExtractor::WIIPointExtractor(const QString& module_name) : s(module_name) +{ + +} + +//define a temp draw function +void WIIPointExtractor::_draw_point(cv::Mat& preview_frame, const vec2& p, const cv::Scalar& color, int thinkness) +{ + static constexpr int len = 9; + + cv::Point p2(iround(p[0] * preview_frame.cols + preview_frame.cols / 2), + iround(-p[1] * preview_frame.cols + preview_frame.rows / 2)); + + cv::line(preview_frame, + cv::Point(p2.x - len, p2.y), + cv::Point(p2.x + len, p2.y), + color, + thinkness); + cv::line(preview_frame, + cv::Point(p2.x, p2.y - len), + cv::Point(p2.x, p2.y + len), + color, + thinkness); +}; + +bool WIIPointExtractor::_draw_points(cv::Mat& preview_frame, const struct wii_info &wii, std::vector& points) +{ + points.reserve(4); + points.clear(); + + for (unsigned index = 0; index < 4; index++) + { + const struct wii_info_points &dot = wii.Points[index]; + if (dot.bvis) { + //qDebug() << "wii:" << dot.RawX << "+" << dot.RawY; + + const float W = 1024.0f; + const float H = 768.0f; + const float RX = W - dot.ux; + const float RY = H - dot.uy; + //vec2 dt((dot.RawX - W / 2.0f) / W, -(dot.RawY - H / 2.0f) / W); + //anti-clockwise rotate 2D point + vec2 dt((RX - W / 2.0f) / W, -(RY - H / 2.0f) / W); + + points.push_back(dt); + _draw_point(preview_frame, dt, cv::Scalar(0, 255, 0), dot.isize); + } + } + const bool success = points.size() >= PointModel::N_POINTS; + + return success; +} + +void WIIPointExtractor::_draw_bg(cv::Mat& preview_frame, const struct wii_info &wii) +{ + //draw battery status + cv::line(preview_frame, + cv::Point(0, 0), + cv::Point(preview_frame.cols*wii.BatteryPercent / 100, 0), + (wii.bBatteryDrained ? cv::Scalar(255, 0, 0) : cv::Scalar(0, 140, 0)), + 2); + + //draw horizon + int pdelta = iround((preview_frame.rows / 2) * tan((wii.Pitch)* M_PI / 180.0f)); + int rdelta = iround((preview_frame.cols / 4) * tan((wii.Roll)* M_PI / 180.0f)); + + cv::line(preview_frame, + cv::Point(0, preview_frame.rows / 2 + rdelta - pdelta), + cv::Point(preview_frame.cols, preview_frame.rows / 2 - rdelta - pdelta), + cv::Scalar(80, 80, 80), + 1); +} + + +void WIIPointExtractor::extract_points(const pt_frame& frame_, pt_preview& preview_frame_, std::vector& points) +{ + const cv::Mat& frame = frame_.as_const()->mat; + const struct wii_info& wii = frame_.as_const()->wii; + cv::Mat& preview_frame = *preview_frame_.as(); + + //create a blank frame + //cv::Mat blank_frame(preview_frame.cols, preview_frame.rows, CV_8UC3, cv::Scalar(0, 0, 0)); + //cv::cvtColor(_frame, _frame2, cv::COLOR_BGR2BGRA); + //cv::resize(blank_frame, preview_frame, cv::Size(preview_frame.cols, preview_frame.rows), 0, 0, cv::INTER_NEAREST); + + switch (wii.status) { + case wii_cam_data_change: + _draw_bg(preview_frame, wii); + _draw_points(preview_frame, wii, points); + break; + case wii_cam_wait_for_connect: + char txtbuf[64]; + sprintf(txtbuf, "%s", "wait for WIImote"); + //draw wait text + cv::putText(preview_frame, + txtbuf, + cv::Point(preview_frame.cols / 10, preview_frame.rows / 2), + cv::FONT_HERSHEY_SIMPLEX, + 1, + cv::Scalar(255, 255, 255), + 1); + break; + } +} + diff --git a/tracker-pt/tracker-wii/wii_point_extractor.h b/tracker-pt/tracker-wii/wii_point_extractor.h new file mode 100644 index 00000000..290d1018 --- /dev/null +++ b/tracker-pt/tracker-wii/wii_point_extractor.h @@ -0,0 +1,40 @@ +/* Copyright (c) 2012 Patrick Ruoff + * Copyright (c) 2015-2016 Stanislaw Halik + * Copyright (c) 2017-2018 Wei Shuai + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + */ + +#pragma once + +#include "pt-api.hpp" + +#include + +#include +#include + +namespace pt_module { + +using namespace types; + +class WIIPointExtractor final : public pt_point_extractor +{ +public: + // extracts points from frame and draws some processing info into frame, if draw_output is set + // dt: time since last call in seconds + void extract_points(const pt_frame& frame, pt_preview& preview_frame, std::vector& points) override; + WIIPointExtractor(const QString& module_name); +private: + static constexpr int max_blobs = 16; + + pt_settings s; + void _draw_point(cv::Mat& preview_frame, const vec2& p, const cv::Scalar& color, int thinkness = 1); + bool _draw_points(cv::Mat& preview_frame, const struct wii_info &wii, std::vector& points); + void _draw_bg(cv::Mat& preview_frame, const struct wii_info &wii); +}; + +} // ns impl + diff --git a/tracker-pt/wiiyourself/CMakeLists.txt b/tracker-pt/wiiyourself/CMakeLists.txt new file mode 100644 index 00000000..e16787c7 --- /dev/null +++ b/tracker-pt/wiiyourself/CMakeLists.txt @@ -0,0 +1 @@ +otr_module(tracker-wii-pt-wiiyourself STATIC) diff --git a/tracker-pt/wiiyourself/History.txt b/tracker-pt/wiiyourself/History.txt new file mode 100644 index 00000000..6601dc97 --- /dev/null +++ b/tracker-pt/wiiyourself/History.txt @@ -0,0 +1,281 @@ +____________________________________________________________ + + - WiiYourself! - native C++ Wiimote library v1.15 + (c) gl.tter 2007-10 - http://gl.tter.org +____________________________________________________________ + +History: + +What's new since 1.01a? - Main Features (see ReadMe & full history for details) + + + Balance Board support with automatic offset removal. + + + Seemingly solid MotionPlus support. + + + Library no longer includes project files - just add wiimote.cpp & + header to your project (avoids all build-settings releated issues) + + + better MinGW support: (thanks Elmo) + adds functional _ASSERT/TRACE/WARN/DEEP_TRACE macros + non-MSYS dependent build option via 'make_mingw.bat'. + Demo builds & works under MinGW. + + + new Python wrapper (by Robert Xiao, see 'Python' folder for details) + + + library now compiles on Borland (thanks Griff - demo not tested). + + + many fixes, connections should be more reliable. + + + join my mailing list to give feedback, share ideas & stay informed: + http://gl.tter.org/mailman/listinfo/wiiyourself_gl.tter.org + +1.15 Final: + + fixed MotionPlus detection on stacks that require HID writes! + + Balance Board corner weight values have been quartered (.Total remains + unchanged). The non-raw corner weight incorrectly reported 4x their + real value. + + Wiimote calibration info is more reliably received (it may not have + arrived in many instances) + + exposed a partially-unique device ID (wiimote::UniqueID). This 64bit + number is set during the Connect() call, and is derived from the + device-specific calibration values. It's therefore not guaranteed to + be truly unique (several devices may conceivably hold the same calibration + values). However in practice it is likely to be unique between a + few wiimotes, so it can be used to eg. assign a particular wiimote to the + same player every time. + + internal: made the HID report output queue fixed-size to remove any + glitches from frequent dynamic memory allocation (thanks + Steve). The old STL-queue based code can still be reenabled + by defining USE_DYNAMIC_HIDQUEUE. + +1.15 RC2: + + added Python wrapper by Robert Xiao - you can now use WiiYourself! with + Python! (thanks Robert) + + hopefully fixed MotionPlus connection problems! (send report to my + mailing list) + + + added virtual event-change notifier to the wiimote object (thanks Robert + Xio) - works the same as external callbacks. To use, derive your own + class from the wiimote object and override ChangedNotifier() + + + changed the way callbacks work: + + in previous versions, it was OK to access the wiimote object's state + from callbacks. This required an internal RefreshState() call just + before the callback function is executed - but this could then change + the internal state unexpectedly, so values could change in polling + loops even between the app's own RefreshState() calls. + + to correct this, the callback functions now get a read-only copy + of the newest state passed in, you should only access this copy as + state in the wiimote object is likely out-of-date. + + In short, the wiimote object's exposed state is now _only_ refreshed + by the application, not by callbacks. + + (this also solved Motion+ connection and disabling failures). + + + added new change event 'CONNECTED' + + the demo previously used callbacks to set most of its report types, but + it also set them once shortly after connecting the wiimote, and this could + cause it to set the wrong one, breaking extension data. Instead it now + uses the CONNECTED event in the callback. It's best to only set these + in one place. + +1.15 RC: + + fixed missing Balance Board calibration values for the 34kg category + (thanks Benjamin Lassort). + + + fixed Wiimote disconnecting in certain scenarios (ReportType wasn't + initialised, and this could sometimes be sent to the 'mote, causing + it to disconnect - thanks Robert Xiao). + + + minor changes to support Robert Xiao's WiiYourself! Python Wrapper! + (next release). + +1.14 BETA: + - added new state & callback event: bBatteryDrained / BATTERY_DRAINED + this is sent went the wiimote signals that the batteries are nearly + empty. + - added MotionPlus extension events (ie. for extensions plugged into it): + MOTIONPLUS_EXTENSION_CONNECTED + MOTIONPLUS_EXTENSION_DISCONNECTED + wiimote::MotionPlusHasExtension() + wiimote::DisableMotionPlus() + wiimote::EnableMotionPlus() + + (apps can now decide if they want to disable the MotionPlus to read the + extension instead, see demo for an example) + + ** however **, MotionPlus disabling isn't reliable at the moment (it + rarely works), and so extension connected to an already enabled plus + rarely are activated. Could use some help on this one. + +1.13 BETA: + + ** BALANCE BOARDS no longer require setting a report type! ** + there is only one type for it, and this is now set automatically. + + + 'At Rest' offset removal added (currently only for Balance Boards). + this reads the current analogue sensor values after a Connect() call, + and then subtracts them from future values, to remove any unwanted + offsets (currently ~ +- 0-2.5kg with Balance Boards). 'raw' values + are not affected. + + If the device was not at rest during Connect(), then the app can + remove the current offsets manually via CalibrateAtRest(). + + + ** PRELIMINARY MOTION PLUS SUPPORT! ** + + Motion Plus does not report itself until queried, so it's currently + queried every second. If detected, it is activated and is reported + like any other extension. Note that extensions plugged into the + MotionPlus itself can't currently be used at the same time (it's not + known if this is even possible). Right now you need to unplug the + MotionPlus to use another extension (I will add some way to toggle + the MotionPlus so that another extensions becomes available again) + in the next release. + + According to this interview with the MotionPlus designers + + there are two gyro sensitivity modes, but this has not been reverse + engineered yet. Also I'm not 100% certain of the correctness of the + values (although they seem right), or their actual scale (ie. how many + degrees rotations per second do the float values actually represent)? + + + the Demo has been updated for both devices. + + ReadMe has been updated with new relevant info. + +1.12 BETA: + + ** REMOVED ALL LIBRARY PROJECTS ** + instead just add wiimote.cpp to your application and include the header + as before (this removes all build/project related-problems, like matching + the runtime/Unicode settings etc). + + + Balance Board is now working (thanks to Akihiko's donation of a board!) + + added wiimote::IsBalanceBoard() (Balance Boards are detected as wiimotes + with a permanent BALANCE_BOARD extension). + NOTE: Balance Boards require the IN_BUTTONS_BALANCE_BOARD report type + (see demo). + + changed some of the wiimote_state extension enums to ID a wider variety. + + no more invalid acceleration values from devices that don't support it. + + fixed some project settings. + +1.11 BETA: + + new way to detect extensions (supposedly works on all of them, including + wireless Nunchuks) - only tested on stock Nunchuk. + + longer sleep after SetReportType (may help data not being reported). + +1.1 BETA: + + beta Balance Board support! + + better MinGW support: (thanks Elmo) + adds functional _ASSERT/TRACE/WARN/DEEP_TRACE macros + non-MSYS dependent build option via 'make_mingw.bat'. + Demo builds & works under MinGW. + + directory reorganisation: + - Each compiler has own project dir (VC2005/VC2005/MinGW), + and equivialent lib/ sudir. + + now ships with working VC2005 SP1 / VC2008 / MinGW libraries + (and MinGW DLL). + + library now compiles on Borland (thanks Griff) - demo may not. + +1.01a: (1.01 had incorrect version defines) + + extensions now work when already connected before Connect(), + & also when an EXT SetReportType() is used initially. + + ** renamed wiimote_state::IR::dot::bVisible to 'bVisible'. ** + + Disconnect() now waits for its threads to exit. + + made TRACE/WARN macros VC2005+ specific (as earlier VC versions don't + support variable arg macros). + + corrected wiimote.h Connect() comments (wiimote selection is 1-based, + not 0-based) + +1.00: + + ** major bug fix, write buffer was abused. ** might have caused various + problems. + + ** added delay to EnableIR(), fixed IR init problems for those that + had them (thanks Cameron) **. if you had to use your own delays + to get things to work, try removing them now. + + wiimote_state::classic_controller::buttons::TriggerL() / R were reversed + (thanks Vico). + + patch & Makefile for MSYS / MinGW (thanks Dario). + + updated ReadMe. + +0.99b: + + added support for the Guitar Hero controller (thanks Morgan). + It's just a Classic Controller with a different ID and is read the same, + but can be differentiated via wiimote_state::extension_type::CLASSIC_GUITAR. + +0.96b: + + fix ClassicButtonNameFromBit[] + + fix WIIYOURSELF_VERSION_MINOR2 + +0.95b: + + Classic Controller button fixes (thanks Farshid). + + sightly longer Sleep() in Reset() - hopefully fixes some reports of + wiimote acceleration values not working. + +0.94b: + + deadzones weren't working. + +0.93b: + + ** compiled libs are now stored in /libs ** + + ** up to 4 dots are now available in every IR mode ** + + some 'state_change_flags' weren't quite generated correctly. + - removed 'wiimote_state::polling' flags (redundant, flags are already + returned via RefreshState()). + + various internal improvements + +0.92b: + ** Polling changes ** + - now need to call RefreshState() once before each polling pass (see + header comments & demo). this was done to synchronise the threaded + state updates, so that data integrity is guaranteed. + ** Callback changes ** + - combined 'wiimote_state_changed' and 'extension_state_changed' flags + into 'state_change_flags + - removed 'ExtensionChangedCallback' (only a single callback is used now) + - added 'CallbackTriggerFlags' to minimize callback overhead + (see header comments & demo) + + added Reset() (see header comments) + + button mask TRIGGER is now _B + + Demo: removed 'wiimote2' line (debug leftover) + +0.82b: + ** code/demo failed pre-XP (HID writes require XP+). code now detects + HID write support dynamically. ** + + tidied code & surpressed redundant warning (or just enable C++ exceptions). + + Improved debug output (mainly DEEP_TRACE) + + Connect() can now take (and defaults to) 'FIRST_AVAILABLE' as the wiimote + index (see header comments). + + 'wiimote_sample' is now auto-cleared on construction + + Adjusted max 'theoretical' raw IR coord values (1023x767) to largest + actually observed, to output full 0-1 float range. + + **Inverted** IR X float coord to match traditional 'left = 0' convention + (raw coords unaffected for now). + + Added state recording ability to aid state/motion analysis. See RecordState(); + - removed RequestBatteryUpdate() (battery level is now periodically refreshed) + - disabled ...CALIBRATION_CHANGED flags (not useful) + + Demo : should now work pre-XP. + ReadMe: added Wiimote/PC installation notes (MS stack is especially tricky). + +0.81b: + + connection loss is now detected (via failed writes) + + ConnectionWasLost() added + + report modes renamed for clarity. + + Connect(): added 'force_hidwrites' (for testing only). + + Extension connections now seem to be reliable. + + Battery is now periodically refreshed (also used for loss detection) + + 'BatteryRaw' was set incorrectly + + added 'wiimote::ClassicButtonNameFromBit[]' + + + Demo : Classic Controller data shown. + + Demo : IR dot sizes now reported when possible (only if extension + data isn't requested as they're not available then). + + + License: 'no harm' clause added. + + ReadMe : added build notes etc. + +0.1b: + First release. \ No newline at end of file diff --git a/tracker-pt/wiiyourself/License.txt b/tracker-pt/wiiyourself/License.txt new file mode 100644 index 00000000..fc41a9a0 --- /dev/null +++ b/tracker-pt/wiiyourself/License.txt @@ -0,0 +1,42 @@ +____________________________________________________________ + + - WiiYourself! - native C++ Wiimote library v1.15 + (c) gl.tter 2007-10 - http://gl.tter.org +____________________________________________________________ + + LICENSE: My Wiimote library is free for any use (including + commercial), with the following conditions: + +1) You may not use it to harm anyone, directly or + indirectly. * this includes, but is not limited to, any + kind of direct or indirect MILITARY use or related + research * + + (but bruising egos is fine ;). + +2) Any distribution in binary form (ie. linked with your + program) must include the following text in your + distribiutions's documentation (ReadMe file, help file, + About box and/or splash screen): + + "contains WiiYourself! wiimote code by gl.tter + http://gl.tter.org" + +3) Any distribution in source code form must keep all my + copyright notices intact unmodified (you can add to + them if you've made changes), and must include this + license text (either include this file in your + distribution, or paste its contents into your + distribution's own licence file). + +4) You may not use the code to produce a competing + library, unless you rewrite all of it considerably + (for example to convert it to another language, but + you need to contact me for written permission first). + + Instead please contribute new features, fixes and ideas + to my mailining list (see ReadMe.txt). +__ + +gl.tter (http://gl.tter.org | glATr-i-lDOTnet) + diff --git a/tracker-pt/wiiyourself/ReadMe.txt b/tracker-pt/wiiyourself/ReadMe.txt new file mode 100644 index 00000000..85ca0034 --- /dev/null +++ b/tracker-pt/wiiyourself/ReadMe.txt @@ -0,0 +1,202 @@ +__________________________________________________________________ + + - WiiYourself! - native C++ Wiimote library v1.15 + (c) gl.tter 2007-10 - http://gl.tter.org +__________________________________________________________________ + +This marks the likely-final release of my free & fully-featured + Wiimote native C++ library for Windows. + +Originally based on Brian Peek's 'Managed Wiimote Library' + (http://blogs.msdn.com/coding4fun/archive/2007/03/14/1879033.aspx) + I then rewrote and extended it considerably. + +There's no documentation - check Brian's article for a good +overview and general 'Wiimote with Windows' info - but the +source code has extensive comments, and the demo app should help +you make sense of it all. Any questions, join my mailing list +(below). + +Check License.txt for the (few) conditions of use, and + History.txt for important changes from previous versions. +_____ + +Notes: + + - the library consists only of wiimote.cpp & wiimote.h. Simply add + them to your project and include the header. + + - VC 2005 & 2008 projects, and a MSYS makefile for MinGW for the + demo program are included. + + - for MSYS: + at the MSYS prompt type: make -f Makefile.MSYS + (it will create a folder named MinGW whith the binaries + and proper folder structure) + + - The Windows Driver Development Kit (WDK or DDK) is required to + build (for the HID API). It's a free download from MS page + (no need to register). Currently it's found here (or search + for it on microsoft.com): + + 'Windows Driver Kit (WDK) 7.1.0' + http://www.microsoft.com/whdc/DevTools/WDK/WDKpkg.mspx + + Unfortunately it changes frequently so the instructions below + may be wrong or incomplete. Google or ask on my mailing + list for help if needed: + + - add its 'inc' and 'inc/api' dirs to your include paths + (make sure they are *above* other paths, ie. searched first) + + - add its 'lib/win7/i386' dir to your library paths. + + You can also use earlier versions known as the 'DDK'. The parts + of the HID API I use haven't changed in a long time. These paths + are usually used: + + - add 'inc/wxp' dir to your include paths (*above* others) + - add 'lib/wxp/i386' to your library paths. + + Notes: + - do _not_ add any 'STL' paths from the WDK/DDK as they can cause + build problems. + - the order of your include paths can be important. If you placed + them above the paths and are still getting compilation errors, + try moving them around. + + - The library is Unicode-ready via (see demo project). + + - if you're not using VC you need to link with these libraries: + setupapi.lib + winmm.lib + hid.lib (from the DDK) +__________________________ + +Wiimote installation notes: + + The Wiimote needs to be 'paired' (Bluetooth connected) with the + PC before you can install/use it. Pressing 1 & 2 simultaneously + puts it into 'discoverable' mode for a few seconds (LEDs will + flash - the number of LEDs reflects the battery level). + + It will be detected as 'Nintendo RVL-CNT-01'. + + Stack-specific instructions: + + - Windows' built-in Bluetooth stack: + + 1) open up the Bluetooth control panel. + 2) press _and hold_ 1 & 2 on the wiimote (LEDs flash) until the + installation is complete (otherwise the wiimote usually times + out half-way through the procedure, and although it may seem + to have installed it's never 'connected' and doesn't work). + 3) add a new device - it should find it. don't use a password. + 4) when the installation is fully complete, let go of 1&2. The + Bluetooth panel should now show it 'connected'. + + if something goes wrong you need to uninstall it and try again. + + if you un-pair the wiimote later (see below), it seems you need + to remove and install it all over again to get it to work (if you + know a workaround, let me know). + + - Toshiba stack: + + straight forward, press 1 & 2 on the Wiimote (you don't need to + hold them if you're quick) and click 'New Connection'. + + once found, you can pair it anytime again by right-clicking its + device icon (and pressing 1 & 2 as before) - you can also set up + a desktop shortcut that enters discovery mode immediately. + + - Widcomm stack: + + 1) Open 'My Bluetooth Places'. + 2) Press and hold 1 & 2 (until the process is complete). + 3) Click 'View Devices in Range'. + 4) Wiimote is detected as Nintendo RVL-CNT-01. + 5) Select it, then click 'Bluetooth Setup Wizard'. + 5) Click 'Skip' (no password). + 6) Now it should be connected (you can let go of 1 & 2). + + Troubleshooting: + - the device seems to be connected but the Demo can't find it + (CreateFile() fails with error 5 'Access Denied'), + or - it disconnects almost immediately after connection + or - asks for a password a few seconds after connection + + Try uninstalling all HID devices from Device Manager, and then + redetecting them with 'Scan for hardware changes' (I had all + these problems and that fixed it for me). + + - Other stacks + + similar to the above (contribute instructions?) + + + - Disconnect/un-pair to save power (any stack): + + hold the Wiimote Power button for a few seconds - it automatically + unpairs itself, re-enters pairing mode for a few seconds + (flashing LEDs), then times out and (effecively) switches off. + +__________________________ + +Balance Boards notes: + + Balance Boards are installed using the same procedure as wiimotes, by + holding the 'Sync' button in the battery compartment. The Bluetooth + stack detectes them as 'Nintendo RVL-WBC-01'. + + They report to the library as wiimotes, with a permanent BALANCE_BOARD + extension. They only have one button (A), and no IR/Acceleration/Rumble + or Speaker support. There is only one supported 'report type' so the + library sets this automatically (see the demo for details). + + You can detect them with the wiimote::IsBalanceBoard() call. + + The boards tested so far all report up to ~ +-2.5KB weight offsets even + where there is no weight placed on them (ie. 'at rest'). It is unknown + if this is normal sensor inaccuracy/drift, or if the calibration values + read from the board are interpreted incorrectly. For now the library + automatically subtracts the first incoming sensor values after a Connect() + call from all future non-raw values (the raw values are never modified). + + The offsets used are exposed in wiimote_state::balance_board::AtRestKG. + + - if the board wasn't at rest during the Connect() call these offsets + will be wrong - the app can measure them manually (when the board + is at rest) by calling wiimote::CalibrateAtRest(). + +__________________________ + +MotionPlus notes: + + Once activated it's reported like any other extension, but needs to be + manually enabled (see demo for an example): + - Test first if it's connected via MotionPlusConnected() + - Then call EnableMotionPlus() + - It will then replace any other extension connected to it, unil you + disable the Motion+ again with DisableMotionPlus(). + + The speed values are believed to be correct. The calibration values are not + yet understood, so the data is currently uncalibrated. + + Some technical details are mentioned in this interview with the MotionPlus + hardware/software engineers: + http://uk.wii.com/wii/en_GB/software/iwata_asks_motionplus_volume_1_2162.html#top + + + * Special thanks to the guys at WiiBrew.org, and all contributing hackers * + for figuring out & documenting the wiimote protocols: + http://wiibrew.org/wiki/Wiimote/Extension_Controllers#Wii_Motion_Plus + + +Sign up to the mailing list to stay in the loop, exchange ideas or help each + other out: http://gl.tter.org/mailman/listinfo/wiiyourself_gl.tter.org +__ + +gl.tter (glATr-i-lDOTnet) + + diff --git a/tracker-pt/wiiyourself/lang/nl_NL.ts b/tracker-pt/wiiyourself/lang/nl_NL.ts new file mode 100644 index 00000000..9e739505 --- /dev/null +++ b/tracker-pt/wiiyourself/lang/nl_NL.ts @@ -0,0 +1,4 @@ + + + + diff --git a/tracker-pt/wiiyourself/lang/ru_RU.ts b/tracker-pt/wiiyourself/lang/ru_RU.ts new file mode 100644 index 00000000..f62cf2e1 --- /dev/null +++ b/tracker-pt/wiiyourself/lang/ru_RU.ts @@ -0,0 +1,4 @@ + + + + diff --git a/tracker-pt/wiiyourself/lang/stub.ts b/tracker-pt/wiiyourself/lang/stub.ts new file mode 100644 index 00000000..6401616d --- /dev/null +++ b/tracker-pt/wiiyourself/lang/stub.ts @@ -0,0 +1,4 @@ + + + + diff --git a/tracker-pt/wiiyourself/wiimote.cpp b/tracker-pt/wiiyourself/wiimote.cpp new file mode 100644 index 00000000..46130cca --- /dev/null +++ b/tracker-pt/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 (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 +extern "C" { +# ifdef __MINGW32__ +# include // from WinDDK +# else +# include +# endif +} +#include // for _stat +#include // " +#include // for _beginthreadex() +#ifdef __BORLANDC__ +# include // for orientation +#else +# include // " +#endif +#include // for WAVEFORMATEXTENSIBLE +#include // for timeGetTime() + +// apparently not defined in some compilers: +#ifndef min +# define min(a,b) (((a) < (b)) ? (a) : (b)) +#endif +// ------------------------------------------------------------------------------------ +// helpers +// ------------------------------------------------------------------------------------ +template inline T sign (const T& val) { return (val<0)? T(-1) : T(1); } +template 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; indexsamples[(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= 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 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? + } +// ------------------------------------------------------------------------------------ +// ------------------------------------------------------------------------------------ diff --git a/tracker-pt/wiiyourself/wiimote.h b/tracker-pt/wiiyourself/wiimote.h new file mode 100644 index 00000000..1db2c098 --- /dev/null +++ b/tracker-pt/wiiyourself/wiimote.h @@ -0,0 +1,495 @@ +// _______________________________________________________________________________ +// +// - 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.h (tab = 4 spaces) + +#ifdef _MSC_VER // VC +# pragma once +#endif + +#ifndef _WIIMOTE_H +# define _WIIMOTE_H + +#define WIN32_LEAN_AND_MEAN +#include +#include // auto Unicode/Ansi support +#include // for HID write method +#include // for state recording + +#ifndef QWORD + typedef unsigned __int64 QWORD; +#endif + +#ifdef _MSC_VER // VC-specific: _DEBUG build only _ASSERT() sanity checks +# include +#elif defined(__MINGW32__) // define NDEBUG to disable assert +# include +# define _ASSERT assert +#else +# define _ASSERT(x) ((void)0) // (add your compiler's implementation if you like) +#endif + +#ifdef SWIGWRAPPER // Python Wrapper +#include "Python/wiimote_state.i" +#else +#include "wiimote_state.h" +#endif + +// configs: +//#define USE_DYNAMIC_HIDQUEUE // deprecated + +// we request periodic status report updates to refresh the battery level +// and to detect connection loss (through failed writes) +#define REQUEST_STATUS_EVERY_MS 1000 +#define DETECT_MPLUS_EVERY_MS 1000 +#define DETECT_MPLUS_COUNT 1 // # of tries in quick succession + +// all threads (read/parse, audio streaming, async rumble...) use this priority +#define WORKER_THREAD_PRIORITY THREAD_PRIORITY_HIGHEST + + // internals +#define WIIYOURSELF_VERSION_MAJOR 1 +#define WIIYOURSELF_VERSION_MINOR1 1 +#define WIIYOURSELF_VERSION_MINOR2 5 +//#define WIIYOURSELF_VERSION_BETA // not defined for non-beta releases +#define WIIYOURSELF_VERSION_STR _T("1.15") + +// array sizes +#define TOTAL_BUTTON_BITS 16 // Number of bits for (Classic)ButtonNameFromBit[] +#define TOTAL_FREQUENCIES 10 // Number of frequencies (see speaker_freq[]) + + // clarity +typedef HANDLE EVENT; + + +// state data changes can be signalled to the app via a callback. Set the wiimote +// object's 'ChangedCallback' any time to enable them, or alternatively inherit +// from the wiimote object and override the ChangedNotifier() virtual method. + +// of flags indicating which state has changed since the last callback. +typedef void (*state_changed_callback) (class wiimote &owner, + state_change_flags changed, + const wiimote_state &new_state); + +// internals +typedef BOOLEAN (__stdcall *hidwrite_ptr)(HANDLE HidDeviceObject, + PVOID ReportBuffer, + ULONG ReportBufferLength); + +// (global due to Python wrapper) +struct wiimote_state_event { + DWORD time_ms; // system timestamp in milliseconds + wiimote_state state; + }; + +// wiimote class - connects and manages a wiimote and its optional extensions +// (Nunchuk/Classic Controller), and exposes their state +class wiimote : public wiimote_state + { + public: + wiimote (); + virtual ~wiimote (); + + public: + // these can be used to identify Connect()ed wiimote objects (if both + // are unconnected they will pass the compare as their handles are invalid) + inline bool operator == (const wiimote& remote) + { return Handle == remote.Handle; } + inline bool operator != (const wiimote& remote) + { return Handle != remote.Handle; } + + // wiimote data input mode (use with SetReportType()) + // (only enable what you need to save battery power) + enum input_report + { + // combinations if buttons/acceleration/IR/Extension data + IN_BUTTONS = 0x30, + IN_BUTTONS_ACCEL = 0x31, + IN_BUTTONS_ACCEL_IR = 0x33, // reports IR EXTENDED data (dot sizes) + IN_BUTTONS_ACCEL_EXT = 0x35, + IN_BUTTONS_ACCEL_IR_EXT = 0x37, // reports IR BASIC data (no dot sizes) + IN_BUTTONS_BALANCE_BOARD = 0x32, // must use this for the balance board + }; + // string versions + static const TCHAR* ReportTypeName []; + + + public: // convenience accessors: + inline bool IsConnected () const { return bStatusReceived; } + // if IsConnected() unexpectedly returns false, connection was probably lost + inline bool ConnectionLost () const { return bConnectionLost; } + inline bool IsBalanceBoard () const { return (Internal.bExtension && + (Internal.ExtensionType==wiimote_state::BALANCE_BOARD)); } + inline bool NunchukConnected () const { return (Internal.bExtension && + (Internal.ExtensionType==wiimote_state::NUNCHUK)); } + inline bool ClassicConnected () const { return (Internal.bExtension && + (Internal.ExtensionType==wiimote_state::CLASSIC)); } + inline bool MotionPlusConnected () const { return bMotionPlusDetected; } + inline bool MotionPlusEnabled () const { return bMotionPlusEnabled; } + inline bool MotionPlusHasExtension() const { return bMotionPlusExtension; } + inline bool IsPlayingAudio () const { return (Internal.Speaker.Freq && + Internal.Speaker.Volume); } + inline bool IsPlayingSample () const { return IsPlayingAudio() && + (CurrentSample != NULL); } + inline bool IsUsingHIDwrites () const { return bUseHIDwrite; } + inline bool IsRecordingState () const { return Recording.bEnabled; } + + static inline unsigned TotalConnected() { return _TotalConnected; } + + + public: // data + QWORD UniqueID; // constructed from device-specific calibration info. + // Note this is not guaranteed to be truly unique + // as several devices may contain the same calibration + // vluaes - but unique amongst a small number of + // devices. +#ifdef ID2_FROM_DEVICEPATH + QWORD UniqueID2; // (low-reliabilty, left for reference) + // constructed from the 'device path' string (as + // reported by the OS/stack). This is hopefully + // unique as long as the devices remain installed + // (or at least paired). +#endif + // optional callbacks - set these to your own fuctions (if required) + state_changed_callback ChangedCallback; + // you can avoid unnecessary callback overhead by specifying a mask + // of which state changes should trigger them (default is any) + state_change_flags CallbackTriggerFlags; + // alternatively, inherit from this class and override this virtual function: + virtual void ChangedNotifier (state_change_flags changed, + const wiimote_state &new_state) {}; + + // get the button name from its bit index (some bits are unused) + static const TCHAR* ButtonNameFromBit [TOTAL_BUTTON_BITS]; + static const TCHAR* GetButtonNameFromBit (unsigned index) + { + _ASSERT(index < TOTAL_BUTTON_BITS); + if(index >= TOTAL_BUTTON_BITS) + return _T("[invalid index]"); + return ButtonNameFromBit[index]; + } + + // same for the Classic Controller + static const TCHAR* ClassicButtonNameFromBit [TOTAL_BUTTON_BITS]; + static const TCHAR* GetClassicButtonNameFromBit (unsigned index) + { + _ASSERT(index < TOTAL_BUTTON_BITS); + if(index >= TOTAL_BUTTON_BITS) + return _T("[invalid index]"); + return ClassicButtonNameFromBit[index]; + } + + // get the frequency from speaker_freq enum + static const unsigned FreqLookup [TOTAL_FREQUENCIES]; + static const unsigned GetFreqLookup (unsigned index) + { + _ASSERT(index < TOTAL_FREQUENCIES); + if(index >= TOTAL_FREQUENCIES) + return 0; + return FreqLookup[index]; + } + + public: // methods + // call Connect() first - returns true if wiimote was found & enabled + // - 'wiimote_index' specifies which *installed* (not necessarily + // *connected*) wiimote should be tried (1 = first, 2 = 2nd etc). + // if you just want the first *available* wiimote that isn't already + // in use, pass in FIRST_AVAILABLE (default). + // - 'force_hidwrites' forces HID output method (it's auto-selected + // when needed and less efficient, so only force for testing). + static const unsigned FIRST_AVAILABLE = 0xffffffff; + bool Connect (unsigned wiimote_index = FIRST_AVAILABLE, + bool force_hidwrites = false); + // disconnect from the controller and stop reading data from it + void Disconnect (); + // set wiimote reporting mode (call after Connnect()) + // continous = true forces the wiimote to send constant updates, even when + // nothing has changed. + // = false only sends data when something has changed (note that + // acceleration data will cause frequent updates anyway as it + // jitters even when the wiimote is stationary) + void SetReportType (input_report type, bool continuous = false); + + // toggle the MotionPlus extension. Call MotionPlusDetected() first to + // see if it's attached. Unlike normal extensions, the MotionPlus does + // not report itself as one until enabled. Once done, it then replaces + // any standard extension attached to it, so be sure to disable it + // if you want to read those (it's not currently known of both can + // be read simultaneously). + bool EnableMotionPlus (); + bool DisableMotionPlus (); + + // this is used to remove unwanted 'at rest' offsets, currently only from + // the Balance Board. make sure there is no weight on the board before + // calling this. it reads the current sensor values and then removes them + // offsets from all subsequent KG and LB state values (the 'raw' values + // are never modified). + void CalibrateAtRest (); + // NOTE: the library automatically calls this when the first weight values + // come in after Connect()ion, but if the device wasn't at rest at + // the time the app can call it again later. + + // to read the state via polling (reading the public state data direct from + // the wiimote object) you must call RefreshState() at the top of every pass. + // returns a combination of flags to indicate which state (if any) has + // changed since the last call. + state_change_flags RefreshState (); + + // reset the wiimote (changes report type to non-continuous buttons-only, + // clears LEDs & rumble, mutes & disables speaker) + void Reset (); + // set/clear the wiimote LEDs + void SetLEDs (BYTE led_bits); // bits 0-3 are valid + // set/clear rumble + void SetRumble (bool on); + // alternative - rumble for a fixed amount of time (asynchronous) + void RumbleForAsync (unsigned milliseconds); + + // *experimental* speaker support: + bool MuteSpeaker (bool on); + bool EnableSpeaker (bool on); + bool PlaySquareWave (speaker_freq freq, BYTE volume = 0x40); + // note: PlaySample currently streams from the passed-in wiimote_sample - + // don't delete it until playback has stopped. + bool PlaySample (const wiimote_sample &sample, + BYTE volume = 0x40, + speaker_freq freq_override = FREQ_NONE); + + // 16bit mono sample loading/conversion to native format: + // .wav sample + static bool Load16bitMonoSampleWAV (const TCHAR* filepath, + wiimote_sample &out); + // raw 16bit mono audio data (can be signed or unsigned) + static bool Load16BitMonoSampleRAW (const TCHAR* filepath, + bool _signed, + speaker_freq freq, + wiimote_sample &out); + // converts a 16bit mono sample array to a wiimote_sample + static bool Convert16bitMonoSamples (const short* samples, + bool _signed, + DWORD length, + speaker_freq freq, + wiimote_sample &out); + + // state recording - records state snapshots to a 'state_history' supplied + // by the caller. states are timestamped and only added + // to the list when the specified state changes. +#ifndef SWIG // !Python Wrapper + typedef wiimote_state_event state_event; +#endif + typedef std::list state_history; + static const unsigned UNTIL_STOP = 0xffffffff; + // - pass in a 'state_history' list, and don't destroy/change it until + // recording is stopped. note the list will be cleared first. + // - you can request a specific duration (and use IsRecordingState() to detect + // the end), or UNTIL_STOP. StopRecording() can be called either way. + // - you can use 'change trigger' to specify specific state changes that will + // trigger an insert into the history (others are then ignored). + void RecordState (state_history &events_out, + unsigned max_time_ms = UNTIL_STOP, + state_change_flags change_trigger = CHANGED_ALL); + void StopRecording (); + + + private: // methods + // start reading asynchronously from the controller + bool BeginAsyncRead (); + // request status update (battery level, extension status etc) + void RequestStatusReport (); + // read address or register from Wiimote asynchronously (the result is + // parsed internally whenever it arrives) + bool ReadAddress (int address, short size); + // write a single BYTE to a wiimote address or register + inline void WriteData (int address, BYTE data) { WriteData(address, 1, &data); } + // write a BYTE array to a wiimote address or register + void WriteData (int address, BYTE size, const BYTE* buff); + // callback when data is ready to be processed + void OnReadData (DWORD bytes_read); + // parse individual reports by type + int ParseInput (BYTE* buff); + // detects if MotionPlus is attached (it doesn't report as a normal + // extesnion until it is enabled) + void DetectMotionPlusExtensionAsync (); + // initializes an extension when plugged in. + void InitializeExtension (); + // parses a status report + int ParseStatus (BYTE* buff); + // parses the buttons + int ParseButtons (BYTE* buff); + // parses accelerometer data + int ParseAccel (BYTE* buff); + bool EstimateOrientationFrom(wiimote_state::acceleration &accel); + void ApplyJoystickDeadZones (wiimote_state::joystick &joy); + // parses IR data from report + int ParseIR (BYTE* buff); + // parses data from an extension. + int ParseExtension (BYTE* buff, unsigned offset); + // parses data returned from a read report + int ParseReadAddress (BYTE* buff); + // reads calibration information stored on Wiimote + void ReadCalibration (); + float GetBalanceValue(short sensor, short min, short mid, short max); + // turns on the IR sensor (the mode must match the reporting mode caps) + void EnableIR (wiimote_state::ir::mode mode); + // disables the IR sensor + void DisableIR (); + // writes a report to the Wiimote (NULL = use 'WriteBuff') + bool WriteReport (BYTE* buff); + bool StartSampleThread (); + // returns the rumble BYTE that needs to be sent with reports. + inline BYTE GetRumbleBit () const { return Internal.bRumble? 0x01 : 0x00; } + + // static thread funcs: + static unsigned __stdcall ReadParseThreadfunc (void* param); + static unsigned __stdcall AsyncRumbleThreadfunc (void* param); + static unsigned __stdcall SampleStreamThreadfunc(void* param); + static unsigned __stdcall HIDwriteThreadfunc (void* param); + + private: // data + // wiimote output comands + enum output_report + { + OUT_NONE = 0x00, + OUT_LEDs = 0x11, + OUT_TYPE = 0x12, + OUT_IR = 0x13, + OUT_SPEAKER_ENABLE = 0x14, + OUT_STATUS = 0x15, + OUT_WRITEMEMORY = 0x16, + OUT_READMEMORY = 0x17, + OUT_SPEAKER_DATA = 0x18, + OUT_SPEAKER_MUTE = 0x19, + OUT_IR2 = 0x1a, + }; + // input reports used only internally: + static const int IN_STATUS = 0x20; + static const int IN_READADDRESS = 0x21; + // wiimote device IDs: + static const int VID = 0x057e; // 'Nintendo' + static const int PID = 0x0306; // 'Wiimote' + // we could find this out the hard way using HID, but it's 22 + static const int REPORT_LENGTH = 22; + // wiimote registers + static const int REGISTER_CALIBRATION = 0x0016; + static const int REGISTER_IR = 0x4b00030; + static const int REGISTER_IR_SENSITIVITY_1 = 0x4b00000; + static const int REGISTER_IR_SENSITIVITY_2 = 0x4b0001a; + static const int REGISTER_IR_MODE = 0x4b00033; + static const int REGISTER_EXTENSION_INIT1 = 0x4a400f0; + static const int REGISTER_EXTENSION_INIT2 = 0x4a400fb; + static const int REGISTER_EXTENSION_TYPE = 0x4a400fa; + static const int REGISTER_EXTENSION_CALIBRATION = 0x4a40020; + static const int REGISTER_BALANCE_CALIBRATION = 0x4a40024; + static const int REGISTER_MOTIONPLUS_DETECT = 0x4a600fa; + static const int REGISTER_MOTIONPLUS_INIT = 0x4a600f0; + static const int REGISTER_MOTIONPLUS_ENABLE = 0x4a600fe; + + HANDLE Handle; // read/write device handle + OVERLAPPED Overlapped; // for async Read/WriteFile() IO + HANDLE ReadParseThread; // waits for overlapped reads & parses result + EVENT DataRead; // signals overlapped read complete + bool bUseHIDwrite; // alternative write method (less efficient + // but required for some BT stacks (eg. MS') + // HidD_SetOutputReport is only supported from XP onwards, so detect & + // load it dynamically: + static HMODULE HidDLL; + static hidwrite_ptr _HidD_SetOutputReport; + + volatile bool bStatusReceived; // for output method detection + volatile bool bConnectInProgress; // don't handle extensions until complete + volatile bool bInitInProgress; // stop regular requests until complete + volatile bool bEnablingMotionPlus; // for special init codepath + volatile bool bConnectionLost; // auto-Disconnect()s if set +volatile int MotionPlusDetectCount; // waiting for the result + volatile bool bMotionPlusDetected; + volatile bool bMotionPlusEnabled; + volatile bool bMotionPlusExtension;// detected one plugged into MotionPlus + volatile bool bCalibrateAtRest; // as soon as the first sensor values // come in after a Connect() call. + static unsigned _TotalCreated; + static unsigned _TotalConnected; + input_report ReportType; // type of data the wiimote delivers + // read buffer + BYTE ReadBuff [REPORT_LENGTH]; + // for polling: state is updated on a thread internally, and made only + // made public via RefreshState() + CRITICAL_SECTION StateLock; + wiimote_state Internal; + state_change_flags InternalChanged; // state changes since last RefreshState() + // periodic status report requests (for battery level and connection loss + // detection) + DWORD NextStatusTime; + DWORD NextMPlusDetectTime;// gap between motion plus detections + DWORD MPlusDetectCount; // # of detection tries in quick succesion + // async Hidd_WriteReport() thread + HANDLE HIDwriteThread; +#ifdef USE_DYNAMIC_HIDQUEUE + std::queue HIDwriteQueue; +#else + // fixed-size queue (to eliminate glitches caused by frequent dynamic memory + // allocations) + struct hid + { + hid () : Queue(NULL), ReadIndex(0), WriteIndex(0) {} + + // Increase the static queue size if you get ASSERTs signalling an + // overflow (too many reports queued up before being sent by the write + // thread). These asserts are harmless though if caused as a result of + // loosing the wiimote connection (eg. battery runs out, or wiimote is + // unpaired by holding the power button). + // Note: MAX_QUEUE_ENTRIES _must_ be a power-of-2, as it + // uses index wraparound optimisations. + static const unsigned MAX_QUEUE_ENTRIES = 1<<7; + + inline bool IsEmpty() const { return (ReadIndex == WriteIndex); } + + bool Allocate () { // allocate memory (only when needed) + _ASSERT(!Queue); if(Queue) return true; + ReadIndex = WriteIndex = 0; + Queue = new queue_entry[MAX_QUEUE_ENTRIES]; + _ASSERT(Queue); return (Queue != NULL); + } + void Deallocate () { + if(!Queue) return; + delete[] Queue; Queue = NULL; + ReadIndex = WriteIndex = 0; + } + + struct queue_entry + { + queue_entry() { memset(Report, 0, sizeof(Report)); } + + BYTE Report [REPORT_LENGTH]; + } *Queue; + + unsigned ReadIndex, WriteIndex; + } HID; +#endif + CRITICAL_SECTION HIDwriteQueueLock; // queue must be locked before being modified + + // async rumble + HANDLE AsyncRumbleThread; // automatically disables rumble if requested + volatile DWORD AsyncRumbleTimeout; + // orientation estimation + unsigned WiimoteNearGUpdates; + unsigned NunchukNearGUpdates; + // audio + HANDLE SampleThread; + const wiimote_sample* volatile CurrentSample; // otherwise playing square wave + // state recording + struct recording + { + volatile bool bEnabled; + state_history *StateHistory; + volatile DWORD StartTimeMS; + volatile DWORD EndTimeMS; // can be UNTIL_STOP + unsigned TriggerFlags; // wiimote changes trigger a state event + unsigned ExtTriggerFlags;// extension changes " + } Recording; + }; + +#endif // _WIIMOTE_H \ No newline at end of file diff --git a/tracker-pt/wiiyourself/wiimote_common.h b/tracker-pt/wiiyourself/wiimote_common.h new file mode 100644 index 00000000..c0fd01e1 --- /dev/null +++ b/tracker-pt/wiiyourself/wiimote_common.h @@ -0,0 +1,109 @@ +// _______________________________________________________________________________ +// +// - WiiYourself! - native C++ Wiimote library v1.15 RC +// (c) gl.tter 2007-9 - http://gl.tter.org +// +// see License.txt for conditions of use. see History.txt for change log. +// _______________________________________________________________________________ +// +// wiimote_common.h (tab = 4 spaces) + +// speaker support: +enum speaker_freq + { + // (keep in sync with FreqLookup in wiimote.cpp) + FREQ_NONE = 0, + // my PC can't keep up with these using bUseHIDwrite, so I haven't + // been able to tune them yet + FREQ_4200HZ = 1, + FREQ_3920HZ = 2, + FREQ_3640HZ = 3, + FREQ_3360HZ = 4, + // these were tuned until the square-wave was glitch-free on my remote - + // may not be exactly right + FREQ_3130HZ = 5, // +190 + FREQ_2940HZ = 6, // +180 + FREQ_2760HZ = 7, // +150 + FREQ_2610HZ = 8, // +140 + FREQ_2470HZ = 9, + }; + +// wiimote_sample - holds the audio sample in the native wiimote format +struct wiimote_sample + { + wiimote_sample() : samples(NULL), length(0), freq(FREQ_NONE) {} + BYTE* samples; + DWORD length; + speaker_freq freq; + }; + +// flags & masks that indicate which part(s) of the wiimote state have changed +enum state_change_flags + { + // state didn't change at all + NO_CHANGE = 0, + + // Wiimote specific: + CONNECTED = 1<<0, // wiimote just connected + CONNECTION_LOST = 1<<1, + BATTERY_CHANGED = 1<<2, + BATTERY_DRAINED = 1<<3, // close to empty + LEDS_CHANGED = 1<<4, // (probably redudant as wiimmote never + BUTTONS_CHANGED = 1<<5, // changes them unless requested) + ACCEL_CHANGED = 1<<6, + ORIENTATION_CHANGED = 1<<7, + IR_CHANGED = 1<<8, + // all wiimote flags + WIIMOTE_CHANGED = CONNECTION_LOST|BATTERY_CHANGED|BATTERY_DRAINED| + LEDS_CHANGED|BUTTONS_CHANGED|ACCEL_CHANGED| + ORIENTATION_CHANGED|IR_CHANGED, + // - Extensions -: + // Nunchuk: + NUNCHUK_CONNECTED = 1<<9, + NUNCHUK_BUTTONS_CHANGED = 1<<10, + NUNCHUK_ACCEL_CHANGED = 1<<11, + NUNCHUK_ORIENTATION_CHANGED = 1<<12, + NUNCHUK_JOYSTICK_CHANGED = 1<<13, + // all flags + NUNCHUK_CHANGED = NUNCHUK_CONNECTED|NUNCHUK_BUTTONS_CHANGED| + NUNCHUK_ACCEL_CHANGED|NUNCHUK_ORIENTATION_CHANGED| + NUNCHUK_JOYSTICK_CHANGED, + // Classic Controller (inc. Guitars etc): + CLASSIC_CONNECTED = 1<<14, + CLASSIC_BUTTONS_CHANGED = 1<<15, + CLASSIC_JOYSTICK_L_CHANGED = 1<<16, + CLASSIC_JOYSTICK_R_CHANGED = 1<<17, + CLASSIC_TRIGGERS_CHANGED = 1<<18, + // all flags + CLASSIC_CHANGED = CLASSIC_CONNECTED|CLASSIC_BUTTONS_CHANGED| + CLASSIC_JOYSTICK_L_CHANGED| + CLASSIC_JOYSTICK_R_CHANGED| + CLASSIC_TRIGGERS_CHANGED, + // Balance Board: + BALANCE_CONNECTED = 1<<19, + BALANCE_WEIGHT_CHANGED = 1<<20, + // all flags + BALANCE_CHANGED = BALANCE_CONNECTED|BALANCE_WEIGHT_CHANGED, + + // Motion Plus + MOTIONPLUS_DETECTED = 1<<21, // attached but not enabled + MOTIONPLUS_ENABLED = 1<<22, + MOTIONPLUS_SPEED_CHANGED = 1<<23, + MOTIONPLUS_EXTENSION_CONNECTED = 1<<24, // an extension is found in the + // MotionPlus port + MOTIONPLUS_EXTENSION_DISCONNECTED = 1<<25, // it was disconnected + // all flags + MOTIONPLUS_CHANGED = MOTIONPLUS_DETECTED|MOTIONPLUS_ENABLED| + MOTIONPLUS_SPEED_CHANGED| + MOTIONPLUS_EXTENSION_CONNECTED| + MOTIONPLUS_EXTENSION_DISCONNECTED, + // General: + EXTENSION_DISCONNECTED = 1<<26, + EXTENSION_PARTIALLY_INSERTED = 1<<27, + EXTENSION_CONNECTED = NUNCHUK_CONNECTED|CLASSIC_CONNECTED| + BALANCE_CONNECTED|MOTIONPLUS_ENABLED, + EXTENSION_CHANGED = EXTENSION_DISCONNECTED|NUNCHUK_CHANGED| + CLASSIC_CHANGED|BALANCE_CHANGED|MOTIONPLUS_CHANGED, + // ALL flags: + CHANGED_ALL = WIIMOTE_CHANGED|EXTENSION_CHANGED, + }; diff --git a/tracker-pt/wiiyourself/wiimote_state.h b/tracker-pt/wiiyourself/wiimote_state.h new file mode 100644 index 00000000..1bf167a2 --- /dev/null +++ b/tracker-pt/wiiyourself/wiimote_state.h @@ -0,0 +1,379 @@ +// _______________________________________________________________________________ +// +// - 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_state.h (tab = 4 spaces) + +// the 'wiimote_state' struct contains all the Wiimote and Extension state data +// (buttons etc) - the wiimote class inherits from this and the app can poll +// the data there at any time. +#ifdef _MSC_VER // VC +# pragma once +#endif + +#ifndef _WIIMOTE_STATE_H +# define _WIIMOTE_STATE_H + +#include "wiimote_common.h" + + +// wiimote_state (contains the Wiimote and Extension data and settings) +struct wiimote_state + { + friend class wiimote; // for Clear() + + // calibration information (stored on the Wiimote) + struct calibration_info + { + BYTE X0, Y0, Z0; + BYTE XG, YG, ZG; + } CalibrationInfo; + + // button state: + struct buttons + { + // convenience accessors + inline bool A () const { return (Bits & _A) != 0; } + inline bool B () const { return (Bits & _B) != 0; } + inline bool Plus () const { return (Bits & PLUS) != 0; } + inline bool Home () const { return (Bits & HOME) != 0; } + inline bool Minus () const { return (Bits & MINUS) != 0; } + inline bool One () const { return (Bits & ONE) != 0; } + inline bool Two () const { return (Bits & TWO) != 0; } + inline bool Up () const { return (Bits & UP) != 0; } + inline bool Down () const { return (Bits & DOWN) != 0; } + inline bool Left () const { return (Bits & LEFT) != 0; } + inline bool Right () const { return (Bits & RIGHT) != 0; } + + // all 11 buttons stored as bits (set = pressed) + WORD Bits; + + // button bit masks (little-endian order) + enum mask + { + LEFT = 0x0001, + RIGHT = 0x0002, + DOWN = 0x0004, + UP = 0x0008, + PLUS = 0x0010, + TWO = 0x0100, + ONE = 0x0200, + _B = 0x0400, // ie. trigger + _A = 0x0800, + MINUS = 0x1000, + HOME = 0x8000, + // + ALL = LEFT|RIGHT|DOWN|UP|PLUS|TWO|ONE|_A|_B|MINUS|HOME, + }; + } Button; + + // accelerometers state: + struct acceleration + { + BYTE RawX, RawY, RawZ; + float X, Y, Z; + + // note: experimental! the orientation values can only be safely estimated + // if the controller isn't accelerating (otherwise there is no + // simple way to seperate orientation from acceleration - except + // perhaps using the IR reference and/or some clever assumptions). + // so for now the code only updates orientation if the controller + // appear to be stationary (by checking if the acceleration vector + // length is near 1G for several updates in a row). + // also note that there is no way to detect Yaw from the accelerometer + // alone when it's pointing at the screen (and I'm not curently + // processing IR): + struct orientation + { + float X, Y, Z; + unsigned UpdateAge; // how many acceleration updates ago the last + // orientation estimate was made (if this + // value is high, the values are out-of-date + // and probably shouldn't be used). + // Euler angle support (useful for some things). + // * note that decomposing to Euler angles is complex, not always reliable, + // and also depends on your assumptions about the order each component + // is applied in. you may need to handle this yourself for more + // complex scenarios * + float Pitch; // in degrees (-180 - +180) + float Roll; // " + // float Yaw; + } Orientation; + } Acceleration; + + // IR camera state: + struct ir + { + // in theory the IR imager is 1024x768 and so should report raw coords + // 0-1023 x 0-767. in practice I have never seen them exceed the values + // below, so I'm using them instead to give the full 0-1 float range + // (it's possible that the edge pixels are used for processing, or masked + // out due to being unreliable). let me know if your wiimote reports + // a different range. + static const unsigned MAX_RAW_X = 1016; + static const unsigned MAX_RAW_Y = 760; + + // data mode reported by the IR sensor + enum mode + { + OFF = 0x00, + BASIC = 0x01, // 10 bytes + EXTENDED = 0x03, // 12 bytes + FULL = 0x05, // 16 bytes * 2 (format unknown) + }; + + mode Mode; // read-only (depends on ReportType set) + + struct dot + { + bool bVisible; // other values are not valid if == false + unsigned RawX; + unsigned RawY; + float X; // 0-1 (left-right) + float Y; // " (top -bottom) + int Size; // (not available in BASIC mode) + } Dot[4]; + } IR; + + struct leds + { + // all LEDs stored in bits 0-3 (1 = lit) + BYTE Bits; + + // convenience accessors: + inline bool Lit (unsigned index) + { _ASSERT(index < 4); + return (index >= 4)? false : ((Bits & (1<