diff options
| author | Stanisław Halik <sthalik@misaki.pl> | 2018-01-20 11:35:40 +0100 | 
|---|---|---|
| committer | GitHub <noreply@github.com> | 2018-01-20 11:35:40 +0100 | 
| commit | 11b3fff51e39cd98c62b0b59fe95aa51f8b30627 (patch) | |
| tree | 9b6d365566a8a9934bb7b5dce7b924aa0bc8bbf4 | |
| parent | c49530ad99720fa638191db667610542bec3c696 (diff) | |
| parent | 911e9deb712bafb5f622e215e286744696e90617 (diff) | |
Merge pull request #727 from cpuwolf/pt4-rebase
add Wiimote supporting in point tracker
26 files changed, 5058 insertions, 0 deletions
| 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.icoBinary files differ new file mode 100644 index 00000000..44204a68 --- /dev/null +++ b/tracker-pt/tracker-wii/Resources/wii.ico diff --git a/tracker-pt/tracker-wii/Resources/wii.png b/tracker-pt/tracker-wii/Resources/wii.pngBinary files differ new file mode 100644 index 00000000..d83b6e77 --- /dev/null +++ b/tracker-pt/tracker-wii/Resources/wii.png 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 @@ +<?xml version="1.0" encoding="utf-8"?> +<!DOCTYPE TS> +<TS version="2.1" language="nl_NL"> +</TS> 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 @@ +<?xml version="1.0" encoding="utf-8"?> +<!DOCTYPE TS> +<TS version="2.1" language="ru_RU"> +</TS> 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 @@ +<?xml version="1.0" encoding="utf-8"?> +<!DOCTYPE TS> +<TS version="2.1"> +</TS> 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 @@ +<RCC> +    <qresource prefix="/"> +        <file>Resources/wii.png</file> +    </qresource> +</RCC> 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 <sthalik@misaki.pl> + * Copyright (c) 2017-2018 Wei Shuai <cpuwolf@gmail.com> + * + * 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 <opencv2/imgproc.hpp> + +#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<WIIFrame>()->mat; +	struct wii_info& wii = frame_.as<WIIFrame>()->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 <sthalik@misaki.pl> + * Copyright (c) 2017-2018 Wei Shuai <cpuwolf@gmail.com> + * + * 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 <functional> +#include <memory> +#include <tuple> + +#include <opencv2/core.hpp> +#include <opencv2/videoio.hpp> + +#include <QString> + +#include <wiiyourself/wiimote.h> +#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 <sthalik@misaki.pl> +* Copyright (c) 2017-2018 Wei Shuai <cpuwolf@gmail.com> +*/ + +#include "wii_frame.hpp" + +#include "compat/math.hpp" + +#include <cstring> +#include <tuple> + +#include <opencv2/imgproc.hpp> + +using namespace pt_module; + +WIIPreview& WIIPreview::operator=(const pt_frame& frame_) +{ +    const cv::Mat& frame = frame_.as_const<const WIIFrame>()->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 <sthalik@misaki.pl> +* Copyright (c) 2017-2018 Wei Shuai <cpuwolf@gmail.com> +*/ +#pragma once + +#include "pt-api.hpp" + +#include <opencv2/core.hpp> +#include <QImage> + + + +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 <memory> + +static const QString module_name = "tracker-wii-pt"; + +using namespace pt_module; + +struct wii_pt_module_traits final : pt_runtime_traits +{ +    pointer<pt_camera> make_camera() const override +    { +        return pointer<pt_camera>(new WIICamera(module_name)); +    } + +    pointer<pt_point_extractor> make_point_extractor() const override +    { +        return pointer<pt_point_extractor>(new WIIPointExtractor(module_name)); +    } + +    QString get_module_name() const override +    { +        return module_name; +    } + +    pointer<pt_frame> make_frame() const override +    { +        return pointer<pt_frame>(new WIIFrame); +    } + +    pointer<pt_preview> make_preview(int w, int h) const override +    { +        return pointer<pt_preview>(new WIIPreview(w, h)); +    } +}; + +struct wii_tracker_pt : Tracker_PT +{ +    wii_tracker_pt() : Tracker_PT(pointer<pt_runtime_traits>(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 <sthalik@misaki.pl> + * Copyright (c) 2017-2018 Wei Shuai <cpuwolf@gmail.com> + * + * 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 <opencv2/videoio.hpp> + +#undef PREVIEW +//#define PREVIEW + +#if defined PREVIEW +#   include <opencv2/highgui.hpp> +#endif + +#include <cmath> +#include <algorithm> +#include <cinttypes> +#include <memory> + +#include <QDebug> + +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<vec2>& 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<vec2>& points) +{ +	const cv::Mat& frame = frame_.as_const<WIIFrame>()->mat; +	const struct wii_info& wii = frame_.as_const<WIIFrame>()->wii; +	cv::Mat& preview_frame = *preview_frame_.as<WIIPreview>(); + +	//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 <sthalik@misaki.pl> + * Copyright (c) 2017-2018 Wei Shuai <cpuwolf@gmail.com> + * + * 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 <vector> + +#include <opencv2/core.hpp> +#include <opencv2/imgproc.hpp> + +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<vec2>& 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<vec2>& 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 <tchar.h> (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 @@ +<?xml version="1.0" encoding="utf-8"?> +<!DOCTYPE TS> +<TS version="2.1" language="nl_NL"> +</TS> 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 @@ +<?xml version="1.0" encoding="utf-8"?> +<!DOCTYPE TS> +<TS version="2.1" language="ru_RU"> +</TS> 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 @@ +<?xml version="1.0" encoding="utf-8"?> +<!DOCTYPE TS> +<TS version="2.1"> +</TS> 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 <xstring> (I don't use it - or just enable C++ exceptions) +# pragma warning(disable: 4530) +//  auto-link with the necessary libs +# pragma comment(lib, "setupapi.lib")	 +# pragma comment(lib, "hid.lib")		// for HID API (from DDK) +# pragma comment(lib, "winmm.lib")		// for timeGetTime() +#endif // _MSC_VER + +#include "wiimote.h" +#include <setupapi.h> +extern "C" { +# ifdef __MINGW32__ +#  include <ddk/hidsdi.h>// from WinDDK +# else +#  include <hidsdi.h> +# endif +} +#include <sys/types.h>	// for _stat +#include <sys/stat.h>	// " +#include <process.h>	// for _beginthreadex() +#ifdef __BORLANDC__ +# include <cmath.h>		// for orientation +#else +# include <math.h>		// " +#endif +#include <mmreg.h>		// for WAVEFORMATEXTENSIBLE +#include <mmsystem.h>	// for timeGetTime() + +// apparently not defined in some compilers: +#ifndef min +# define min(a,b)	(((a) < (b)) ? (a) : (b)) +#endif +// ------------------------------------------------------------------------------------ +// helpers +// ------------------------------------------------------------------------------------ +template<class T> inline T sign  (const T& val)  { return (val<0)? T(-1) : T(1); } +template<class T> inline T square(const T& val)  { return val*val; } +#define ARRAY_ENTRIES(array)	(sizeof(array)/sizeof(array[0])) + +// ------------------------------------------------------------------------------------ +//  Tracing & Debugging +// ------------------------------------------------------------------------------------ +#define PREFIX	_T("WiiYourself! : ") + +// comment these to auto-strip their code from the library: +//  (they currently use OutputDebugString() via _TRACE() - change to suit) +#if (_MSC_VER >= 1400) // VC 2005+ (earlier versions don't support variable args) +# define TRACE(fmt, ...) _TRACE(PREFIX          fmt          _T("\n"), __VA_ARGS__) +# define WARN(fmt, ...)  _TRACE(PREFIX _T("* ") fmt _T(" *") _T("\n"), __VA_ARGS__) +#elif defined(__MINGW32__) +# define TRACE(fmt, ...) _TRACE(PREFIX          fmt          _T("\n") , ##__VA_ARGS__) +# define WARN(fmt, ...)  _TRACE(PREFIX _T("* ") fmt _T(" *") _T("\n") , ##__VA_ARGS__) +#endif +// uncomment any of these for deeper debugging: +//#define DEEP_TRACE(fmt, ...) _TRACE(PREFIX _T("|") fmt _T("\n"), __VA_ARGS__)    // VC 2005+ +//#define DEEP_TRACE(fmt, ...) _TRACE(PREFIX _T("|") fmt _T("\n") , ##__VA_ARGS__) // mingw +//#define BEEP_DEBUG_READS +//#define BEEP_DEBUG_WRITES +//#define BEEP_ON_ORIENTATION_ESTIMATE +//#define BEEP_ON_PERIODIC_STATUSREFRESH + +// internals: auto-strip code from the macros if they weren't defined +#ifndef TRACE +# define TRACE +#endif +#ifndef DEEP_TRACE +# define DEEP_TRACE +#endif +#ifndef WARN +# define WARN +#endif +// ------------------------------------------------------------------------------------ +static void _cdecl _TRACE (const TCHAR* fmt, ...) +	{ +	static TCHAR buffer[256]; +	if (!fmt) return; + +	va_list	 argptr; +	va_start (argptr, fmt); +#if (_MSC_VER >= 1400) // VC 2005+ +	_vsntprintf_s(buffer, ARRAY_ENTRIES(buffer), _TRUNCATE, fmt, argptr); +#else +	_vsntprintf  (buffer, ARRAY_ENTRIES(buffer),			 fmt, argptr); +#endif +	va_end (argptr); + +	OutputDebugString(buffer); +	} + +// ------------------------------------------------------------------------------------ +//  wiimote +// ------------------------------------------------------------------------------------ +// class statics +HMODULE		   wiimote::HidDLL				  = NULL; +unsigned	   wiimote::_TotalCreated		  = 0; +unsigned	   wiimote::_TotalConnected		  = 0; +hidwrite_ptr   wiimote::_HidD_SetOutputReport = NULL; + +// (keep in sync with 'speaker_freq'): +const unsigned wiimote::FreqLookup [TOTAL_FREQUENCIES] =  +								{    0, 4200, 3920, 3640, 3360, +								  3130,	2940, 2760, 2610, 2470 }; + +const TCHAR*   wiimote::ButtonNameFromBit		 [TOTAL_BUTTON_BITS] = +								{ _T("Left") , _T("Right"), _T("Down"), _T("Up"), +								  _T("Plus") , _T("??")   , _T("??")  , _T("??") , +								  _T("Two")  , _T("One")  , _T("B")   , _T("A") , +								  _T("Minus"), _T("??")   , _T("??")  , _T("Home") }; + +const TCHAR*   wiimote::ClassicButtonNameFromBit [TOTAL_BUTTON_BITS] = +								{ _T("??")   , _T("TrigR")  , _T("Plus") , _T("Home"), +								  _T("Minus"), _T("TrigL") , _T("Down") , _T("Right") , +								  _T("Up")   , _T("Left")   , _T("ZR")   , _T("X") , +								  _T("A")    , _T("Y")      , _T("B")    , _T("ZL") }; +// ------------------------------------------------------------------------------------ +wiimote::wiimote () +	: +	DataRead			 (CreateEvent(NULL, FALSE, FALSE, NULL)), +	Handle				 (INVALID_HANDLE_VALUE), +	ReportType			 (IN_BUTTONS), +	bStatusReceived		 (false), // for output method detection +	bConnectInProgress	 (true ), +	bInitInProgress		 (false), +	bEnablingMotionPlus	 (false), +	bConnectionLost		 (false), // set if write fails after connection +	bMotionPlusDetected	 (false), +	bMotionPlusEnabled	 (false), +	bMotionPlusExtension (false), +	bCalibrateAtRest	 (false), +	bUseHIDwrite		 (false), // if OS supports it +	ChangedCallback		 (NULL), +	CallbackTriggerFlags (CHANGED_ALL), +	InternalChanged		 (NO_CHANGE), +	CurrentSample		 (NULL), +	HIDwriteThread		 (NULL), +	ReadParseThread		 (NULL), +	SampleThread		 (NULL), +	AsyncRumbleThread	 (NULL), +	AsyncRumbleTimeout	 (0), +	UniqueID			 (0)	// not _guaranteed_ unique, see comments in header +#ifdef ID2_FROM_DEVICEPATH		// (see comments in header) +	// UniqueID2			 (0)	 +#endif +	{ +	_ASSERT(DataRead != INVALID_HANDLE_VALUE); +				 +	// if this is the first wiimote object, detect & enable HID write support +	if(++_TotalCreated == 1) +		{ +		HidDLL = LoadLibrary(_T("hid.dll")); +		_ASSERT(HidDLL); +		if(!HidDLL) +			WARN(_T("Couldn't load hid.dll - shouldn't happen!")); +		else{ +			_HidD_SetOutputReport = (hidwrite_ptr) +									GetProcAddress(HidDLL, "HidD_SetOutputReport"); +			if(_HidD_SetOutputReport) +				TRACE(_T("OS supports HID writes.")); +			else +				TRACE(_T("OS doesn't support HID writes.")); +			} +		} + +	// clear our public and private state data completely (including deadzones) +	Clear		  (true); +	Internal.Clear(true); + +	// and the state recording vars +	memset(&Recording, 0, sizeof(Recording)); + +	// for overlapped IO (Read/WriteFile) +	memset(&Overlapped, 0, sizeof(Overlapped)); +	Overlapped.hEvent	  = DataRead; +	Overlapped.Offset	  = +	Overlapped.OffsetHigh = 0; + +	// for async HID output method +	InitializeCriticalSection(&HIDwriteQueueLock); +	// for polling +	InitializeCriticalSection(&StateLock); + +	// request millisecond timer accuracy +	timeBeginPeriod(1);		 +	} +// ------------------------------------------------------------------------------------ +wiimote::~wiimote () +	{ +	Disconnect(); + +	// events & critical sections are kept open for the lifetime of the object, +	//  so tidy them up here: +	if(DataRead != INVALID_HANDLE_VALUE) +		CloseHandle(DataRead); + +	DeleteCriticalSection(&HIDwriteQueueLock); +	DeleteCriticalSection(&StateLock); + +	// tidy up timer accuracy request +	timeEndPeriod(1);		 + +	// release HID DLL (for dynamic HID write method) +	if((--_TotalCreated == 0) && HidDLL) +		{ +		FreeLibrary(HidDLL); +		HidDLL				  = NULL; +		_HidD_SetOutputReport = NULL; +		} +	} + +// ------------------------------------------------------------------------------------ +bool wiimote::Connect (unsigned wiimote_index, bool force_hidwrites) +	{ +	if(wiimote_index == FIRST_AVAILABLE) +		TRACE(_T("Connecting first available Wiimote:")); +	else +		TRACE(_T("Connecting Wiimote %u:"), wiimote_index); + +	// auto-disconnect if user is being naughty +	if(IsConnected()) +		Disconnect(); + +	// get the GUID of the HID class +	GUID guid; +	HidD_GetHidGuid(&guid); + +	// get a handle to all devices that are part of the HID class +	// Brian: Fun fact:  DIGCF_PRESENT worked on my machine just fine.  I reinstalled +	//   Vista, and now it no longer finds the Wiimote with that parameter enabled... +	HDEVINFO dev_info = SetupDiGetClassDevs(&guid, NULL, NULL, DIGCF_DEVICEINTERFACE);// | DIGCF_PRESENT); +	if(!dev_info) { +		WARN(_T("couldn't get device info")); +		return false; +		} + +	// enumerate the devices +	SP_DEVICE_INTERFACE_DATA didata; +	didata.cbSize = sizeof(didata); +	 +	unsigned index			= 0; +	unsigned wiimotes_found = 0; +	while(SetupDiEnumDeviceInterfaces(dev_info, NULL, &guid, index, &didata)) +		{ +		// get the buffer size for this device detail instance +		DWORD req_size = 0; +		SetupDiGetDeviceInterfaceDetail(dev_info, &didata, NULL, 0, &req_size, NULL); + +		// (bizarre way of doing it) create a buffer large enough to hold the +		//  fixed-size detail struct components, and the variable string size +		SP_DEVICE_INTERFACE_DETAIL_DATA *didetail = +								(SP_DEVICE_INTERFACE_DETAIL_DATA*) new BYTE[req_size]; +		_ASSERT(didetail); +		didetail->cbSize = sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA); + +		// now actually get the detail struct +		if(!SetupDiGetDeviceInterfaceDetail(dev_info, &didata, didetail, +											req_size, &req_size, NULL)) { +			WARN(_T("couldn't get devinterface info for %u"), index); +			break; +			} + +		// open a shared handle to the device to query it (this will succeed even +		//  if the wiimote is already Connect()'ed) +		DEEP_TRACE(_T(".. querying device %s"), didetail->DevicePath); +		Handle = CreateFile(didetail->DevicePath, 0, FILE_SHARE_READ|FILE_SHARE_WRITE, +												  NULL, OPEN_EXISTING, 0, NULL); +		if(Handle == INVALID_HANDLE_VALUE) { +			DEEP_TRACE(_T(".... failed with err %x (probably harmless)."),  +					   GetLastError()); +			goto skip; +			} +	 +		// get the device attributes +		HIDD_ATTRIBUTES attrib; +		attrib.Size = sizeof(attrib); +		if(HidD_GetAttributes(Handle, &attrib)) +			{ +			// is this a wiimote? +			if((attrib.VendorID != VID) || (attrib.ProductID != PID)) +				goto skip; + +			// yes, but is it the one we're interested in? +			++wiimotes_found; +			if((wiimote_index != FIRST_AVAILABLE) && +			   (wiimote_index != wiimotes_found)) +				goto skip; + +			// the wiimote is installed, but it may not be currently paired: +			if(wiimote_index == FIRST_AVAILABLE) +				TRACE(_T(".. opening Wiimote %u:"), wiimotes_found); +			else +				TRACE(_T(".. opening:")); + + +			// re-open the handle, but this time we don't allow write sharing +			//  (that way subsequent calls can still _discover_ wiimotes above, but +			//   will correctly fail here if they're already connected) +			CloseHandle(Handle); +			 +			// note this also means that if another application has already opened +			//  the device, the library can no longer connect it (this may happen +			//  with software that enumerates all joysticks in the system, because +			//  even though the wiimote is not a standard joystick (and can't +			//  be read as such), it unfortunately announces itself to the OS +			//  as one.  The SDL library was known to do grab wiimotes like this. +			//  If you cannot stop the application from doing it, you may change the +			//  call below to open the device in full shared mode - but then the +			//  library can no longer detect if you've already connected a device +			//  and will allow you to connect it twice!  So be careful ... +			Handle = CreateFile(didetail->DevicePath, GENERIC_READ|GENERIC_WRITE, +													FILE_SHARE_READ| FILE_SHARE_WRITE, +													NULL, OPEN_EXISTING, +													FILE_FLAG_OVERLAPPED, NULL); +			if(Handle == INVALID_HANDLE_VALUE) { +				TRACE(_T(".... failed with err %x"), GetLastError()); +				goto skip; +				} + +			// clear the wiimote state & buffers +			Clear		  (false);		// preserves existing deadzones +			Internal.Clear(false);		// " +			InternalChanged = NO_CHANGE; +			memset(ReadBuff , 0, sizeof(ReadBuff)); +			bConnectionLost	   = false; +			bConnectInProgress = true; // don't parse extensions or request regular +									   //  updates until complete +			// enable async reading +			BeginAsyncRead(); + +			// autodetect which write method the Bluetooth stack supports, +			//  by requesting the wiimote status report: +			if(force_hidwrites && !_HidD_SetOutputReport) { +				TRACE(_T(".. can't force HID writes (not supported)")); +				force_hidwrites = false; +				} + +			if(force_hidwrites) +				TRACE(_T(".. (HID writes forced)")); +			else{ +				//  - try WriteFile() first as it's the most efficient (it uses +				//     harware interrupts where possible and is async-capable): +				bUseHIDwrite = false; +				RequestStatusReport(); +				//  and wait for the report to arrive: +				DWORD last_time = timeGetTime(); +				while(!bStatusReceived && ((timeGetTime()-last_time) < 500)) +					Sleep(10); +				TRACE(_T(".. WriteFile() %s."), bStatusReceived? _T("succeeded") : +																 _T("failed")); +				} + +			// try HID write method (if supported) +			if(!bStatusReceived && _HidD_SetOutputReport) +				{ +				bUseHIDwrite = true; +				RequestStatusReport(); +				// wait for the report to arrive: +				DWORD last_time = timeGetTime(); +				while(!bStatusReceived && ((timeGetTime()-last_time) < 500)) +					Sleep(10); +				// did we get it? +				TRACE(_T(".. HID write %s."), bStatusReceived? _T("succeeded") : +															   _T("failed")); +				} + +			// still failed? +			if(!bStatusReceived) { +				WARN(_T("output failed - wiimote is not connected (or confused).")); +				Disconnect(); +				goto skip; +				} + +//Sleep(500); +			// reset it +			Reset(); + +			// read the wiimote calibration info +			ReadCalibration(); + +			// allow the result(s) to come in (so that the caller can immediately test +			//  MotionPlusConnected() +			Sleep(300); // note, don't need it on my system, better to be safe though + +			// connected succesfully: +			_TotalConnected++; + +			// use the first incomding analogue sensor values as the 'at rest' +			//  offsets (only supports the Balance Board currently) +			bCalibrateAtRest = true; + +			// refresh the public state from the internal one (so that everything +			//  is available straight away +			RefreshState(); + +			// attempt to construct a unique hardware ID from the calibration +			//  data bytes (this is obviously not guaranteed to be unique across +			//  all devices, but may work fairly well in practice... ?) +			memcpy(&UniqueID, &CalibrationInfo, sizeof(CalibrationInfo)); + +			_ASSERT(UniqueID != 0); // if this fires, the calibration data didn't +									//  arrive - this shouldn't happen + +#ifdef ID2_FROM_DEVICEPATH		// (see comments in header) +			// create a 2nd alternative id by simply adding all the characters +			//  in the device path to create a single number +			UniqueID2 = 0; +			for(unsigned index=0; index<_tcslen(didetail->DevicePath); index++) +				UniqueID2 += didetail->DevicePath[index]; +#endif +			// and show when we want to trigger the next periodic status request +			//  (for battery level and connection loss detection) +			NextStatusTime		= timeGetTime() + REQUEST_STATUS_EVERY_MS; +			NextMPlusDetectTime = timeGetTime() + DETECT_MPLUS_EVERY_MS; +			MPlusDetectCount	= DETECT_MPLUS_COUNT; + +			// tidy up +			delete[] (BYTE*)didetail; +			break; +			} +skip: +		// tidy up +		delete[] (BYTE*)didetail; + +		if(Handle != INVALID_HANDLE_VALUE) { +			CloseHandle(Handle); +			Handle = INVALID_HANDLE_VALUE; +			} +		// if this was the specified wiimote index, abort +		if((wiimote_index != FIRST_AVAILABLE) && +		   (wiimote_index == (wiimotes_found-1))) +		   break; + +		index++; +		} + +	// clean up our list +	SetupDiDestroyDeviceInfoList(dev_info); + +	bConnectInProgress = false; +	if(IsConnected()) { +		TRACE(_T(".. connected!")); +		// notify the callbacks (if requested to do so) +		if(CallbackTriggerFlags & CONNECTED) +			{ +			ChangedNotifier(CONNECTED, Internal); +			if(ChangedCallback) +				ChangedCallback(*this, CONNECTED, Internal); +			} +		return true; +		} +	TRACE(_T(".. connection failed.")); +	return false; +	} +// ------------------------------------------------------------------------------------ +void wiimote::CalibrateAtRest () +	{ +	_ASSERT(IsConnected()); +	if(!IsConnected()) +		return; + +	// the app calls this to remove 'at rest' offsets from the analogue sensor +	//  values (currently only works for the Balance Board): +	if(IsBalanceBoard()) { +		TRACE(_T(".. removing 'at rest' BBoard offsets.")); +		Internal.BalanceBoard.AtRestKg = Internal.BalanceBoard.Kg; +		RefreshState(); +		} +	} +// ------------------------------------------------------------------------------------ +void wiimote::Disconnect () +	{ +	if(Handle == INVALID_HANDLE_VALUE) +		return; + +	TRACE(_T("Disconnect().")); +	 +	if(IsConnected()) +		{ +		_ASSERT(_TotalConnected > 0); // sanity +		_TotalConnected--; +		 +		if(!bConnectionLost) +			Reset(); +		} + +	CloseHandle(Handle); +	Handle = INVALID_HANDLE_VALUE; +	UniqueID  = 0; +#ifdef ID2_FROM_DEVICEPATH		// (see comments in header) +	UniqueID2 = 0; +#endif + +	// close the read thread +	if(ReadParseThread) { +		// unblock it so it can realise we're closing and exit straight away +		SetEvent(DataRead); +		WaitForSingleObject(ReadParseThread, 3000); +		CloseHandle(ReadParseThread); +		ReadParseThread	= NULL; +		} +	// close the rumble thread +	if(AsyncRumbleThread) { +		WaitForSingleObject(AsyncRumbleThread, 3000); +		CloseHandle(AsyncRumbleThread); +		AsyncRumbleThread  = NULL; +		AsyncRumbleTimeout = 0; +		} +	// and the sample streaming thread +	if(SampleThread) { +		WaitForSingleObject(SampleThread, 3000); +		CloseHandle(SampleThread); +		SampleThread = NULL; +		} + +#ifndef USE_DYNAMIC_HIDQUEUE +	HID.Deallocate(); +#endif + +	bStatusReceived = false; + +	// and clear the state +	Clear		  (false); // (preserves deadzones) +	Internal.Clear(false); // " +	InternalChanged = NO_CHANGE; +	} +// ------------------------------------------------------------------------------------ +void wiimote::Reset () +	{ +	TRACE(_T("Resetting wiimote.")); +	 +	if(bMotionPlusEnabled) +		DisableMotionPlus(); + +	// stop updates (by setting report type to non-continuous, buttons-only) +	if(IsBalanceBoard()) +		SetReportType(IN_BUTTONS_BALANCE_BOARD, false); +	else +		SetReportType(IN_BUTTONS, false); + +	SetRumble	 (false); +	SetLEDs		 (0x00); +//	MuteSpeaker  (true); +	EnableSpeaker(false); + +	Sleep(150); // avoids loosing the extension calibration data on Connect() +	} +// ------------------------------------------------------------------------------------ +unsigned __stdcall wiimote::ReadParseThreadfunc (void* param) +	{ +	// this thread waits for the async ReadFile() to deliver data & parses it. +	//  it also requests periodic status updates, deals with connection loss +	//  and ends state recordings with a specific duration: +	_ASSERT(param); +	wiimote    &remote	   = *(wiimote*)param; +	OVERLAPPED &overlapped = remote.Overlapped; +	unsigned exit_code	   = 0; // (success) + +	while(1) +		{ +		// wait until the overlapped read completes, or the timeout is reached: +		DWORD wait = WaitForSingleObject(overlapped.hEvent, 500); + +		// before we deal with the result, let's do some housekeeping: + +		//  if we were recently Disconect()ed, exit now +		if(remote.Handle == INVALID_HANDLE_VALUE) { +			DEEP_TRACE(_T("read thread: wiimote was disconnected")); +			break; +			} +		//  ditto if the connection was lost (eg. through a failed write) +		if(remote.bConnectionLost) +			{ +connection_lost: +			TRACE(_T("read thread: connection to wiimote was lost")); +			remote.Disconnect(); +			remote.InternalChanged = (state_change_flags) +								(remote.InternalChanged | CONNECTION_LOST); +			// report via the callback (if any) +			if(remote.CallbackTriggerFlags & CONNECTION_LOST) +				{ +				remote.ChangedNotifier(CONNECTION_LOST, remote.Internal); +				if(remote.ChangedCallback) +					remote.ChangedCallback(remote, CONNECTION_LOST, remote.Internal); +				} +			break; +			} + +		DWORD time = timeGetTime(); +		//  periodic events (but not if we're streaming audio, +		//					 we don't want to cause a glitch) +		if(remote.IsConnected() && !remote.bInitInProgress && +		   !remote.IsPlayingAudio()) +			{ +			// status request due?  +			if(time > remote.NextStatusTime) +				{ +#ifdef BEEP_ON_PERIODIC_STATUSREFRESH +				Beep(2000,50); +#endif +				remote.RequestStatusReport(); +				// and schedule the next one +				remote.NextStatusTime = time + REQUEST_STATUS_EVERY_MS; +				} +			// motion plus detection due? +			if(!remote.IsBalanceBoard()		&& +//			   !remote.bConnectInProgress   && +			   !remote.bMotionPlusExtension && +			   (remote.Internal.ExtensionType != MOTION_PLUS) && +			   (remote.Internal.ExtensionType != PARTIALLY_INSERTED) && +			   (time > remote.NextMPlusDetectTime)) +				{ +				remote.DetectMotionPlusExtensionAsync(); +				// we try several times in quick succession before the next +				//  delay: +				if(--remote.MPlusDetectCount == 0) { +					remote.NextMPlusDetectTime = time + DETECT_MPLUS_EVERY_MS; +					remote.MPlusDetectCount    = DETECT_MPLUS_COUNT; +#ifdef _DEBUG +					TRACE(_T("--")); +#endif +					} +				} +			} + +		//  if we're state recording and have reached the specified duration, stop +		if(remote.Recording.bEnabled && (remote.Recording.EndTimeMS != UNTIL_STOP) && +		   (time >= remote.Recording.EndTimeMS)) +		   remote.Recording.bEnabled = false; + +		// now handle the wait result: +		//  did the wait time out? +		if(wait == WAIT_TIMEOUT) { +			DEEP_TRACE(_T("read thread: timed out")); +			continue; // wait again +			} +		//  did an error occurr? +		if(wait != WAIT_OBJECT_0) { +			DEEP_TRACE(_T("read thread: error waiting!")); +			remote.bConnectionLost = true; +			// deal with it straight away to avoid a longer delay +			goto connection_lost; +			} +	 +		// data was received: +#ifdef BEEP_DEBUG_READS +		Beep(500,1); +#endif +		DWORD read = 0; +		//  get the data read result +		GetOverlappedResult(remote.Handle, &overlapped, &read, TRUE); +		//  if we read data, parse it +		if(read) { +			DEEP_TRACE(_T("read thread: parsing data")); +			remote.OnReadData(read); +			} +		else +			DEEP_TRACE(_T("read thread: didn't get any data??")); +		} + +	TRACE(_T("(ending read thread)")); +#ifdef BEEP_DEBUG_READS +	if(exit_code != 0) +		Beep(200,1000); +#endif +	return exit_code; +	} +// ------------------------------------------------------------------------------------ +bool wiimote::BeginAsyncRead () +	{ +	// (this is also called before we're fully connected) +	if(Handle == INVALID_HANDLE_VALUE) +		return false; + +	DEEP_TRACE(_T(".. starting async read")); +#ifdef BEEP_DEBUG_READS +	Beep(1000,1); +#endif + +	DWORD read; +	if (!ReadFile(Handle, ReadBuff, REPORT_LENGTH, &read, &Overlapped)) { +		DWORD err = GetLastError(); +		if(err != ERROR_IO_PENDING) { +			DEEP_TRACE(_T(".... ** ReadFile() failed! **")); +			return false; +			} +		} + +	// launch the completion wait/callback thread +	if(!ReadParseThread) { +		ReadParseThread = (HANDLE)_beginthreadex(NULL, 0, ReadParseThreadfunc, +												 this, 0, NULL); +		DEEP_TRACE(_T(".... creating read thread")); +		_ASSERT(ReadParseThread); +		if(!ReadParseThread) +			return false; +		SetThreadPriority(ReadParseThread, WORKER_THREAD_PRIORITY); +		} + +	// if ReadFile completed while we called, signal the thread to proceed +	if(read) { +		DEEP_TRACE(_T(".... got data right away")); +		SetEvent(DataRead); +		} +	return true; +	} +// ------------------------------------------------------------------------------------ +void wiimote::OnReadData (DWORD bytes_read) +	{ +	_ASSERT(bytes_read == REPORT_LENGTH); + +	// copy our input buffer +	BYTE buff [REPORT_LENGTH]; +	memcpy(buff, ReadBuff, bytes_read); + +	// start reading again +	BeginAsyncRead(); + +	// parse it +	ParseInput(buff); +	} +// ------------------------------------------------------------------------------------ +void wiimote::SetReportType (input_report type, bool continuous) +	{ +	_ASSERT(IsConnected()); +	if(!IsConnected()) +		return; + +	// the balance board only uses one type of report +	_ASSERT(!IsBalanceBoard() || type == IN_BUTTONS_BALANCE_BOARD); +	if(IsBalanceBoard() && (type != IN_BUTTONS_BALANCE_BOARD)) +		return; + +#ifdef TRACE +	#define TYPE2NAME(_type)	(type==_type)? _T(#_type) +	const TCHAR* name = TYPE2NAME(IN_BUTTONS)				: +						TYPE2NAME(IN_BUTTONS_ACCEL_IR)		: +						TYPE2NAME(IN_BUTTONS_ACCEL_EXT)		: +						TYPE2NAME(IN_BUTTONS_ACCEL_IR_EXT)	: +						TYPE2NAME(IN_BUTTONS_BALANCE_BOARD) : +						_T("(unknown??)"); +	TRACE(_T("ReportType: %s (%s)"), name, (continuous? _T("continuous") : +														_T("non-continuous"))); +#endif +	ReportType = type; + +	switch(type) +		{ +		case IN_BUTTONS_ACCEL_IR: +			EnableIR(wiimote_state::ir::EXTENDED); +			break; +		case IN_BUTTONS_ACCEL_IR_EXT: +			EnableIR(wiimote_state::ir::BASIC); +			break; +		default: +			DisableIR(); +			break; +		} + +	BYTE buff [REPORT_LENGTH] = {0}; +	buff[0] = OUT_TYPE; +	buff[1] = (continuous ? 0x04 : 0x00) | GetRumbleBit(); +	buff[2] = (BYTE)type; +	WriteReport(buff); +//	Sleep(15); +	} +// ------------------------------------------------------------------------------------ +void wiimote::SetLEDs (BYTE led_bits) +	{ +	_ASSERT(IsConnected()); +	if(!IsConnected() || bInitInProgress) +		return; + +	_ASSERT(led_bits <= 0x0f); +	led_bits &= 0xf; +	 +	BYTE buff [REPORT_LENGTH] = {0}; +	buff[0] = OUT_LEDs; +	buff[1] = (led_bits<<4) | GetRumbleBit(); +	WriteReport(buff); + +	Internal.LED.Bits = led_bits; +	} +// ------------------------------------------------------------------------------------ +void wiimote::SetRumble (bool on) +	{ +	_ASSERT(IsConnected()); +	if(!IsConnected()) +		return; + +	if(Internal.bRumble == on) +		return; + +	Internal.bRumble = on; + +	// if we're streaming audio, we don't need to send a report (sending it makes +	// the audio glitch, and the rumble bit is sent with every report anyway) +	if(IsPlayingAudio()) +		return; + +	BYTE buff [REPORT_LENGTH] = {0}; +	buff[0] = OUT_STATUS; +	buff[1] = on? 0x01 : 0x00; +	WriteReport(buff); +	} +// ------------------------------------------------------------------------------------ +unsigned __stdcall wiimote::AsyncRumbleThreadfunc (void* param) +	{ +	// auto-disables rumble after x milliseconds: +	_ASSERT(param); +	wiimote &remote = *(wiimote*)param; +	 +	while(remote.IsConnected()) +		{ +		if(remote.AsyncRumbleTimeout) +			{ +			DWORD current_time = timeGetTime(); +			if(current_time >= remote.AsyncRumbleTimeout) +				{ +				if(remote.Internal.bRumble) +					remote.SetRumble(false); +				remote.AsyncRumbleTimeout = 0; +				} +			Sleep(1); +			} +		else +			Sleep(4); +		} +	return 0; +	} +// ------------------------------------------------------------------------------------ +void wiimote::RumbleForAsync (unsigned milliseconds) +	{ +	// rumble for a fixed amount of time +	_ASSERT(IsConnected()); +	if(!IsConnected()) +		return; + +	SetRumble(true); + +	// show how long thread should wait to disable rumble again +	// (it it's currently rumbling it will just extend the time) +	AsyncRumbleTimeout = timeGetTime() + milliseconds; + +	// create the thread? +	if(AsyncRumbleThread) +		return; + +	AsyncRumbleThread = (HANDLE)_beginthreadex(NULL, 0, AsyncRumbleThreadfunc, this, +											   0, NULL); +	_ASSERT(AsyncRumbleThread); +	if(!AsyncRumbleThread) { +		WARN(_T("couldn't create rumble thread!")); +		return; +		} +	SetThreadPriority(AsyncRumbleThread, WORKER_THREAD_PRIORITY); +	} +// ------------------------------------------------------------------------------------ +void wiimote::RequestStatusReport () +	{ +	// (this can be called before we're fully connected) +	_ASSERT(Handle != INVALID_HANDLE_VALUE); +	if(Handle == INVALID_HANDLE_VALUE) +		return; + +	BYTE buff [REPORT_LENGTH] = {0}; +	buff[0] = OUT_STATUS; +	buff[1] = GetRumbleBit(); +	WriteReport(buff); +	} +// ------------------------------------------------------------------------------------ +bool wiimote::ReadAddress (int address, short size) +	{ +	// asynchronous +	BYTE buff [REPORT_LENGTH] = {0}; +	buff[0] = OUT_READMEMORY; +	buff[1] = (BYTE)(((address & 0xff000000) >> 24) | GetRumbleBit()); +	buff[2] = (BYTE)( (address & 0x00ff0000) >> 16); +	buff[3] = (BYTE)( (address & 0x0000ff00) >>  8); +	buff[4] = (BYTE)( (address & 0x000000ff)); +	buff[5] = (BYTE)( (size	   & 0xff00	   ) >>  8); +	buff[6] = (BYTE)( (size	   & 0xff)); +	return WriteReport(buff); +	} +// ------------------------------------------------------------------------------------ +void wiimote::WriteData (int address, BYTE size, const BYTE* buff) +	{ +	// asynchronous +	BYTE write [REPORT_LENGTH] = {0}; +	write[0] = OUT_WRITEMEMORY; +	write[1] = (BYTE)(((address & 0xff000000) >> 24) | GetRumbleBit()); +	write[2] = (BYTE)( (address & 0x00ff0000) >> 16); +	write[3] = (BYTE)( (address & 0x0000ff00) >>  8); +	write[4] = (BYTE)( (address & 0x000000ff)); +	write[5] = size; +	memcpy(write+6, buff, size); +	WriteReport(write); +	} +// ------------------------------------------------------------------------------------ +int wiimote::ParseInput (BYTE* buff) +	{ +	int changed = 0; + +	// lock our internal state (so RefreshState() is blocked until we're done +	EnterCriticalSection(&StateLock); + +	switch(buff[0]) +		{ +		case IN_BUTTONS: +			DEEP_TRACE(_T(".. parsing buttons.")); +			changed |= ParseButtons(buff); +			break; + +		case IN_BUTTONS_ACCEL: +			DEEP_TRACE(_T(".. parsing buttons/accel.")); +			changed |= ParseButtons(buff); +			if(!IsBalanceBoard()) +				changed |= ParseAccel(buff); +			break; + +		case IN_BUTTONS_ACCEL_EXT: +			DEEP_TRACE(_T(".. parsing extenion/accel.")); +			changed |= ParseButtons(buff); +			changed |= ParseExtension(buff, 6); +			if(!IsBalanceBoard()) +				changed |= ParseAccel(buff); +			break; + +		case IN_BUTTONS_ACCEL_IR: +			DEEP_TRACE(_T(".. parsing ir/accel.")); +			changed |= ParseButtons(buff); +			if(!IsBalanceBoard()) { +				changed |= ParseAccel(buff); +				changed |= ParseIR(buff); +				} +			break; + +		case IN_BUTTONS_ACCEL_IR_EXT: +			DEEP_TRACE(_T(".. parsing ir/extenion/accel.")); +			changed |= ParseButtons(buff); +			changed |= ParseExtension(buff, 16); +			if(!IsBalanceBoard()) { +				changed |= ParseAccel(buff); +				changed |= ParseIR	   (buff); +				} +			break; + +		case IN_BUTTONS_BALANCE_BOARD: +			DEEP_TRACE(_T(".. parsing buttson/balance.")); +			changed |= ParseButtons(buff); +			changed |= ParseExtension(buff, 3); +			break; + +		case IN_READADDRESS: +			DEEP_TRACE(_T(".. parsing read address.")); +			changed |= ParseButtons	   (buff); +			changed |= ParseReadAddress(buff); +			break; + +		case IN_STATUS: +			DEEP_TRACE(_T(".. parsing status.")); +			changed |= ParseStatus(buff); +			// show that we received the status report (used for output method +			//  detection during Connect()) +			bStatusReceived = true; +			break; +		 +		default: +			DEEP_TRACE(_T(".. ** unknown input ** (happens).")); +			///_ASSERT(0); +			//Debug.WriteLine("Unknown report type: " + type.ToString()); +			LeaveCriticalSection(&StateLock); +			return false; +		} + +	// if we're recording and some state we care about has changed, insert it into +	//  the state history +	if(Recording.bEnabled && (changed & Recording.TriggerFlags)) +		{ +		DEEP_TRACE(_T(".. adding state to history")); +		state_event event; +		event.time_ms = timeGetTime(); +		event.state	  = *(wiimote_state*)this; +		Recording.StateHistory->push_back(event); +		} + +	// for polling: show which state has changed since the last RefreshState() +	InternalChanged = (state_change_flags)(InternalChanged | changed); + +	LeaveCriticalSection(&StateLock); + +	// callbacks: call it (if set & state the app is interested in has changed) +	if(changed & CallbackTriggerFlags) +		{ +		DEEP_TRACE(_T(".. calling state change callback")); +		ChangedNotifier((state_change_flags)changed, Internal); +		if(ChangedCallback) +			ChangedCallback(*this, (state_change_flags)changed, Internal); +		} +	 +	DEEP_TRACE(_T(".. parse complete.")); +	return true; +	} +// ------------------------------------------------------------------------------------ +state_change_flags wiimote::RefreshState () +	{ +	// nothing changed since the last call? +	if(InternalChanged == NO_CHANGE) +		return NO_CHANGE; + +	// copy the internal state to our public data members: +	//  synchronise the interal state with the read/parse thread (we don't want +	//   values changing during the copy) +	EnterCriticalSection(&StateLock); +	 +	// remember which state changed since the last call +	state_change_flags changed = InternalChanged; +	 +	// preserve the application-set deadzones (if any) +	joystick::deadzone nunchuk_deadzone	     = Nunchuk.Joystick.DeadZone; +	joystick::deadzone classic_joyl_deadzone = ClassicController.JoystickL.DeadZone; +	joystick::deadzone classic_joyr_deadzone = ClassicController.JoystickR.DeadZone; +		 +	 // copy the internal state to the public one +	*(wiimote_state*)this = Internal; +	InternalChanged		  = NO_CHANGE; +	  +	 // restore the application-set deadzones +	Nunchuk.Joystick.DeadZone			 = nunchuk_deadzone; +	ClassicController.JoystickL.DeadZone = classic_joyl_deadzone; +	ClassicController.JoystickR.DeadZone = classic_joyr_deadzone; + +	LeaveCriticalSection(&StateLock); +	 +	return changed; +	} +// ------------------------------------------------------------------------------------ +void wiimote::DetectMotionPlusExtensionAsync () +	{ +#ifdef _DEBUG +	TRACE(_T("(looking for motion plus)")); +#endif +	// show that we're expecting the result shortly +	MotionPlusDetectCount++; +	// MotionPLus reports at a different address than other extensions (until +	//  activated, when it maps itself into the usual extension registers), so +	//  try to detect it first: +	ReadAddress(REGISTER_MOTIONPLUS_DETECT, 6); +	} +// ------------------------------------------------------------------------------------ +bool wiimote::EnableMotionPlus () +	{ +	_ASSERT(bMotionPlusDetected); +	if(!bMotionPlusDetected) +		return false; +	if(bMotionPlusEnabled) +		return true; + +	TRACE(_T("Enabling Motion Plus:")); +	 +	bMotionPlusExtension = false; +	bInitInProgress		 = true; +	bEnablingMotionPlus	 = true; + +	// Initialize it: +	WriteData(REGISTER_MOTIONPLUS_INIT  , 0x55); +//	Sleep(50); +	// Enable it (this maps it to the standard extension port): +	WriteData(REGISTER_MOTIONPLUS_ENABLE, 0x04); +//	Sleep(50); +Sleep(500); +	return true; +	} +// ------------------------------------------------------------------------------------ +bool wiimote::DisableMotionPlus () +	{ + 	if(!bMotionPlusDetected || !bMotionPlusEnabled) +		return false; + +	TRACE(_T("Disabling Motion Plus:")); + +	// disable it (this makes standard extensions visible again) +	WriteData(REGISTER_EXTENSION_INIT1, 0x55); +	return true; +	} +// ------------------------------------------------------------------------------------ +void wiimote::InitializeExtension () +	{ +	TRACE(_T("Initialising Extension.")); +	// wibrew.org: The new way to initialize the extension is by writing 0x55 to +	//	0x(4)A400F0, then writing 0x00 to 0x(4)A400FB. It works on all extensions, and +	//  makes the extension type bytes unencrypted. This means that you no longer have +	//  to decrypt the extension bytes using the transform listed above.  +	bInitInProgress = true; +_ASSERT(Internal.bExtension); +	// only initialize if it's not a MotionPlus +	if(!bEnablingMotionPlus) { +		WriteData  (REGISTER_EXTENSION_INIT1, 0x55); +		WriteData  (REGISTER_EXTENSION_INIT2, 0x00); +		} +	else +		bEnablingMotionPlus = false; +	 +	ReadAddress(REGISTER_EXTENSION_TYPE , 6); +	} +// ------------------------------------------------------------------------------------ +int wiimote::ParseStatus (BYTE* buff) +	{ +	// parse the buttons +	int changed = ParseButtons(buff); +			 +	// get the battery level +	BYTE battery_raw = buff[6]; +	if(Internal.BatteryRaw != battery_raw) +		changed |= BATTERY_CHANGED; +	Internal.BatteryRaw	 = battery_raw; +	// it is estimated that ~200 is the maximum battery level +	Internal.BatteryPercent = battery_raw / 2; + +	// there is also a flag that shows if the battery is nearly empty +	bool drained = buff[3] & 0x01; +	if(drained != bBatteryDrained) +		{ +		bBatteryDrained = drained; +		if(drained) +			changed |= BATTERY_DRAINED; +		} + +	// leds +	BYTE leds = buff[3] >> 4; +	if(leds != Internal.LED.Bits) +		changed |= LEDS_CHANGED; +	Internal.LED.Bits = leds; + +	// don't handle extensions until a connection is complete +//	if(bConnectInProgress) +//		return changed; + +	bool extension = ((buff[3] & 0x02) != 0); +//	TRACE(_T("(extension = %s)"), (extension? _T("TRUE") : _T("false"))); + +	if(extension != Internal.bExtension) +		{ +		if(!Internal.bExtension) +			{ +			TRACE(_T("Extension connected:")); +			Internal.bExtension = true; +			InitializeExtension(); +			} +		else{ +			TRACE(_T("Extension disconnected.")); +			Internal.bExtension	   = false; +			Internal.ExtensionType = wiimote_state::NONE; +			bMotionPlusEnabled	   = false; +			bMotionPlusExtension   = false; +			bMotionPlusDetected	   = false; +			bInitInProgress		   = false; +			bEnablingMotionPlus	   = false; +			changed				  |= EXTENSION_DISCONNECTED; +			// renable reports +//			SetReportType(ReportType); +			} +		} +	 +	return changed; +	} +// ------------------------------------------------------------------------------------ +int wiimote::ParseButtons (BYTE* buff) +	{ +	int changed = 0; +	 +//	WORD bits = *(WORD*)(buff+1); +	WORD bits = *(WORD*)(buff+1) & Button.ALL; + +	if(bits != Internal.Button.Bits) +		changed |= BUTTONS_CHANGED; +	Internal.Button.Bits = bits; +	 +	return changed; +	} +// ------------------------------------------------------------------------------------ +bool wiimote::EstimateOrientationFrom (wiimote_state::acceleration &accel) +	{ +	// Orientation estimate from acceleration data (shared between wiimote and nunchuk) +	//  return true if the orientation was updated + +	//  assume the controller is stationary if the acceleration vector is near +	//  1g for several updates (this may not always be correct) +	float length_sq = square(accel.X) + square(accel.Y) + square(accel.Z); + +	// TODO: as I'm comparing _squared_ length, I really need different +	//		  min/max epsilons... +	#define DOT(x1,y1,z1, x2,y2,z2)	((x1*x2) + (y1*y2) + (z1*z2)) + +	static const float epsilon = 0.2f; +	if((length_sq >= (1.f-epsilon)) && (length_sq <= (1.f+epsilon))) +		{ +		if(++WiimoteNearGUpdates < 2) +			return false; +		 +		// wiimote seems to be stationary:  normalize the current acceleration +		//  (ie. the assumed gravity vector) +		float inv_len = 1.f / sqrt(length_sq); +		float x = accel.X * inv_len; +		float y = accel.Y * inv_len; +		float z = accel.Z * inv_len; + +		// copy the values +		accel.Orientation.X = x; +		accel.Orientation.Y = y; +		accel.Orientation.Z = z; + +		// and extract pitch & roll from them: +		// (may not be optimal) +		float pitch = -asin(y)    * 57.2957795f; +//		float roll  =  asin(x)    * 57.2957795f; +        float roll  =  atan2(x,z) * 57.2957795f; +		if(z < 0) { +			pitch = (y < 0)?  180 - pitch : -180 - pitch; +			roll  = (x < 0)? -180 - roll  :  180 - roll; +			} + +		accel.Orientation.Pitch = pitch; +		accel.Orientation.Roll  = roll; + +		// show that we just updated orientation +		accel.Orientation.UpdateAge = 0; +#ifdef BEEP_ON_ORIENTATION_ESTIMATE +		Beep(2000, 1); +#endif +		return true; // updated +		} + +	// not updated this time: +	WiimoteNearGUpdates	= 0; +	// age the last orientation update +	accel.Orientation.UpdateAge++; +	return false; +	} +// ------------------------------------------------------------------------------------ +void wiimote::ApplyJoystickDeadZones (wiimote_state::joystick &joy) +	{ +	// apply the deadzones to each axis (if set) +	if((joy.DeadZone.X > 0.f) && (joy.DeadZone.X <= 1.f)) +		{ +		if(fabs(joy.X) <= joy.DeadZone.X) +			joy.X = 0; +		else{ +			joy.X -= joy.DeadZone.X * sign(joy.X); +			joy.X /= 1.f - joy.DeadZone.X; +			} +		} +	if((joy.DeadZone.Y > 0.f) && (joy.DeadZone.Y <= 1.f)) +		{ +		if(fabs(joy.Y) <= joy.DeadZone.Y) +			joy.Y = 0; +		else{ +			joy.Y -= joy.DeadZone.Y * sign(joy.Y); +			joy.Y /= 1.f - joy.DeadZone.Y; +			} +		} +	} +// ------------------------------------------------------------------------------------ +int wiimote::ParseAccel (BYTE* buff) +	{ +	int changed = 0; +	 +	BYTE raw_x = buff[3]; +	BYTE raw_y = buff[4]; +	BYTE raw_z = buff[5]; + +	if((raw_x != Internal.Acceleration.RawX) || +	   (raw_y != Internal.Acceleration.RawY) || +	   (raw_z != Internal.Acceleration.RawZ)) +	   changed |= ACCEL_CHANGED; +	 +	Internal.Acceleration.RawX = raw_x; +	Internal.Acceleration.RawY = raw_y; +	Internal.Acceleration.RawZ = raw_z; + +	// avoid / 0.0 when calibration data hasn't arrived yet +	if(Internal.CalibrationInfo.X0) +		{ +		Internal.Acceleration.X = +					((float)Internal.Acceleration.RawX  - Internal.CalibrationInfo.X0) /  +					((float)Internal.CalibrationInfo.XG - Internal.CalibrationInfo.X0); +		Internal.Acceleration.Y = +					((float)Internal.Acceleration.RawY  - Internal.CalibrationInfo.Y0) / +					((float)Internal.CalibrationInfo.YG - Internal.CalibrationInfo.Y0); +		Internal.Acceleration.Z = +					((float)Internal.Acceleration.RawZ  - Internal.CalibrationInfo.Z0) / +					((float)Internal.CalibrationInfo.ZG - Internal.CalibrationInfo.Z0); +		} +	else{ +		Internal.Acceleration.X = +		Internal.Acceleration.Y = +		Internal.Acceleration.Z = 0.f; +		} + +	// see if we can estimate the orientation from the current values +	if(EstimateOrientationFrom(Internal.Acceleration)) +		changed |= ORIENTATION_CHANGED; +	 +	return changed; +	} +// ------------------------------------------------------------------------------------ +int wiimote::ParseIR (BYTE* buff) +	{ +	if(Internal.IR.Mode == wiimote_state::ir::OFF) +		return NO_CHANGE; + +	// avoid garbage values when the MotionPlus is enabled, but the app is +	//  still using the extended IR report type +	if(bMotionPlusEnabled && (Internal.IR.Mode == wiimote_state::ir::EXTENDED)) +		return NO_CHANGE; + +	// take a copy of the existing IR state (so we can detect changes) +	wiimote_state::ir prev_ir = Internal.IR; + +	// only updates the other values if the dots are visible (so that the last +	//  valid values stay unmodified) +	switch(Internal.IR.Mode) +		{ +		case wiimote_state::ir::BASIC: +			// 2 dots are encoded in 5 bytes, so read 2 at a time +			for(unsigned step=0; step<2; step++) +				{ +				ir::dot &dot0 = Internal.IR.Dot[step*2  ]; +				ir::dot &dot1 = Internal.IR.Dot[step*2+1]; +				const unsigned offs = 6 + (step*5); // 5 bytes for 2 dots + +				dot0.bVisible = !(buff[offs  ] == 0xff && buff[offs+1] == 0xff); +				dot1.bVisible = !(buff[offs+3] == 0xff && buff[offs+4] == 0xff); +			 +				if(dot0.bVisible) { +					dot0.RawX = buff[offs  ] | ((buff[offs+2] >> 4) & 0x03) << 8;; +					dot0.RawY = buff[offs+1] | ((buff[offs+2] >> 6) & 0x03) << 8;; +					dot0.X    = 1.f - (dot0.RawX / (float)wiimote_state::ir::MAX_RAW_X); +					dot0.Y    =	      (dot0.RawY / (float)wiimote_state::ir::MAX_RAW_Y); +					} +				if(dot1.bVisible) { +					dot1.RawX = buff[offs+3] | ((buff[offs+2] >> 0) & 0x03) << 8; +					dot1.RawY = buff[offs+4] | ((buff[offs+2] >> 2) & 0x03) << 8; +					dot1.X    = 1.f - (dot1.RawX / (float)wiimote_state::ir::MAX_RAW_X); +					dot1.Y    =	      (dot1.RawY / (float)wiimote_state::ir::MAX_RAW_Y); +					} +				} +			break; +		 +		case wiimote_state::ir::EXTENDED: +			// each dot is encoded into 3 bytes +			for(unsigned index=0; index<4; index++) +				{ +				ir::dot &dot = Internal.IR.Dot[index]; +				const unsigned offs = 6 + (index * 3); +			 +				dot.bVisible = !(buff[offs  ]==0xff && buff[offs+1]==0xff && +							   buff[offs+2]==0xff); +				if(dot.bVisible) { +					dot.RawX = buff[offs  ] | ((buff[offs+2] >> 4) & 0x03) << 8; +					dot.RawY = buff[offs+1] | ((buff[offs+2] >> 6) & 0x03) << 8; +					dot.X    = 1.f - (dot.RawX / (float)wiimote_state::ir::MAX_RAW_X); +					dot.Y    =	     (dot.RawY / (float)wiimote_state::ir::MAX_RAW_Y); +					dot.Size = buff[offs+2] & 0x0f; +					} +				} +			break; + +		case wiimote_state::ir::FULL: +			_ASSERT(0); // not supported yet; +			break; +		} + +	return memcmp(&prev_ir, &Internal.IR, sizeof(Internal.IR))? IR_CHANGED : 0; +	} +// ------------------------------------------------------------------------------------ +inline float wiimote::GetBalanceValue (short sensor, short min, short mid, short max) +	{ +	if(max == mid || mid == min) +		return 0; + +	float val = (sensor < mid)? +					68.0f * ((float)(sensor - min) / (mid - min)) : +					68.0f * ((float)(sensor - mid) / (max - mid)) + 68.0f; +	 +	// divide by four (so that each sensor is correct) +	return val * 0.25f; +	} +// ------------------------------------------------------------------------------------ +int wiimote::ParseExtension (BYTE *buff, unsigned offset) +	{ +	int changed = 0; +	 +	switch(Internal.ExtensionType) +		{ +		case wiimote_state::NUNCHUK: +			{ +			// buttons +			bool c = (buff[offset+5] & 0x02) == 0; +			bool z = (buff[offset+5] & 0x01) == 0; +			 +			if((c != Internal.Nunchuk.C) || (z != Internal.Nunchuk.Z)) +				changed |= NUNCHUK_BUTTONS_CHANGED; +			 +			Internal.Nunchuk.C = c; +			Internal.Nunchuk.Z = z; + +			// acceleration +			{ +			wiimote_state::acceleration &accel = Internal.Nunchuk.Acceleration; +			 +			BYTE raw_x = buff[offset+2]; +			BYTE raw_y = buff[offset+3]; +			BYTE raw_z = buff[offset+4]; +			if((raw_x != accel.RawX) || (raw_y != accel.RawY) || (raw_z != accel.RawZ)) +				changed |= NUNCHUK_ACCEL_CHANGED; + +			accel.RawX = raw_x; +			accel.RawY = raw_y; +			accel.RawZ = raw_z; + +			wiimote_state::nunchuk::calibration_info &calib = +													Internal.Nunchuk.CalibrationInfo; +			accel.X = ((float)raw_x - calib.X0) / ((float)calib.XG - calib.X0); +			accel.Y = ((float)raw_y - calib.Y0) / ((float)calib.YG - calib.Y0); +			accel.Z = ((float)raw_z - calib.Z0) / ((float)calib.ZG - calib.Z0); + +			// try to extract orientation from the accel: +			if(EstimateOrientationFrom(accel)) +				changed |= NUNCHUK_ORIENTATION_CHANGED; +			} +			{ +			// joystick: +			wiimote_state::joystick &joy = Internal.Nunchuk.Joystick; + +			float raw_x = buff[offset+0]; +			float raw_y = buff[offset+1]; +			 +			if((raw_x != joy.RawX) || (raw_y != joy.RawY)) +				changed |= NUNCHUK_JOYSTICK_CHANGED; + +			joy.RawX = raw_x; +			joy.RawY = raw_y; + +			// apply the calibration data +			wiimote_state::nunchuk::calibration_info &calib = +													Internal.Nunchuk.CalibrationInfo; +			if(Internal.Nunchuk.CalibrationInfo.MaxX != 0x00) +				joy.X = ((float)raw_x - calib.MidX) / ((float)calib.MaxX - calib.MinX); +			if(calib.MaxY != 0x00) +				joy.Y = ((float)raw_y - calib.MidY) / ((float)calib.MaxY - calib.MinY); + +			// i prefer the outputs to range -1 - +1 (note this also affects the +			//  deadzone calculations) +			joy.X *= 2;	joy.Y *= 2; + +			// apply the public deadzones to the internal state (if set) +			joy.DeadZone = Nunchuk.Joystick.DeadZone; +			ApplyJoystickDeadZones(joy); +			} +			} +			break; +		 +		case wiimote_state::CLASSIC: +		case wiimote_state::GH3_GHWT_GUITAR: +		case wiimote_state::GHWT_DRUMS: +			{ +			// buttons: +			WORD bits = *(WORD*)(buff+offset+4); +			bits = ~bits; // need to invert bits since 0 is down, and 1 is up + +			if(bits != Internal.ClassicController.Button.Bits) +				changed |= CLASSIC_BUTTONS_CHANGED; + +			Internal.ClassicController.Button.Bits = bits; + +			// joysticks: +			wiimote_state::joystick &joyL = Internal.ClassicController.JoystickL; +			wiimote_state::joystick &joyR = Internal.ClassicController.JoystickR; + +			float l_raw_x = (float) (buff[offset+0] & 0x3f); +			float l_raw_y = (float) (buff[offset+1] & 0x3f); +			float r_raw_x = (float)((buff[offset+2]		    >> 7) | +								   ((buff[offset+1] & 0xc0) >> 5) | +								   ((buff[offset+0] & 0xc0) >> 3)); +			float r_raw_y = (float) (buff[offset+2] & 0x1f); + +			if((joyL.RawX != l_raw_x) || (joyL.RawY != l_raw_y)) +				changed |= CLASSIC_JOYSTICK_L_CHANGED; +			if((joyR.RawX != r_raw_x) || (joyR.RawY != r_raw_y)) +				changed |= CLASSIC_JOYSTICK_R_CHANGED; + +			joyL.RawX = l_raw_x; joyL.RawY = l_raw_y; +			joyR.RawX = r_raw_x; joyR.RawY = r_raw_y; + +			// apply calibration +			wiimote_state::classic_controller::calibration_info &calib = +											Internal.ClassicController.CalibrationInfo; +			if(calib.MaxXL != 0x00) +				joyL.X = (joyL.RawX - calib.MidXL) / ((float)calib.MaxXL - calib.MinXL); +			if(calib.MaxYL != 0x00) +				joyL.Y = (joyL.RawY - calib.MidYL) / ((float)calib.MaxYL - calib.MinYL); +			if(calib.MaxXR != 0x00) +				joyR.X = (joyR.RawX - calib.MidXR) / ((float)calib.MaxXR - calib.MinXR); +			if(calib.MaxYR != 0x00) +				joyR.Y = (joyR.RawY - calib.MidYR) / ((float)calib.MaxYR - calib.MinYR); + +			// i prefer the joystick outputs to range -1 - +1 (note this also affects +			//  the deadzone calculations) +			joyL.X *= 2; joyL.Y *= 2; joyR.X *= 2; joyR.Y *= 2; + +			// apply the public deadzones to the internal state (if set) +			joyL.DeadZone = ClassicController.JoystickL.DeadZone; +			joyR.DeadZone = ClassicController.JoystickR.DeadZone; +			ApplyJoystickDeadZones(joyL); +			ApplyJoystickDeadZones(joyR); + +			// triggers +			BYTE raw_trigger_l = ((buff[offset+2] & 0x60) >> 2) | +								  (buff[offset+3]		  >> 5); +			BYTE raw_trigger_r =   buff[offset+3] & 0x1f; +			 +			if((raw_trigger_l != Internal.ClassicController.RawTriggerL) || +			   (raw_trigger_r != Internal.ClassicController.RawTriggerR)) +			   changed |= CLASSIC_TRIGGERS_CHANGED; +			 +			Internal.ClassicController.RawTriggerL  = raw_trigger_l; +			Internal.ClassicController.RawTriggerR  = raw_trigger_r; + +			if(calib.MaxTriggerL != 0x00) +				Internal.ClassicController.TriggerL = +									 (float)Internal.ClassicController.RawTriggerL /  +									((float)calib.MaxTriggerL -	calib.MinTriggerL); +			if(calib.MaxTriggerR != 0x00) +				Internal.ClassicController.TriggerR = +									 (float)Internal.ClassicController.RawTriggerR /  +									((float)calib.MaxTriggerR - calib.MinTriggerR); +			} +			break; + +		case BALANCE_BOARD: +			{ +			wiimote_state::balance_board::sensors_raw prev_raw = +														Internal.BalanceBoard.Raw; +			Internal.BalanceBoard.Raw.TopR	  = +						(short)((short)buff[offset+0] << 8 | buff[offset+1]); +			Internal.BalanceBoard.Raw.BottomR = +						(short)((short)buff[offset+2] << 8 | buff[offset+3]); +			Internal.BalanceBoard.Raw.TopL	  = +						(short)((short)buff[offset+4] << 8 | buff[offset+5]); +			Internal.BalanceBoard.Raw.BottomL = +						(short)((short)buff[offset+6] << 8 | buff[offset+7]); + +			if((Internal.BalanceBoard.Raw.TopL    != prev_raw.TopL)    || +			   (Internal.BalanceBoard.Raw.TopR    != prev_raw.TopR)    || +			   (Internal.BalanceBoard.Raw.BottomL != prev_raw.BottomL) || +			   (Internal.BalanceBoard.Raw.BottomR != prev_raw.BottomR)) +				changed |= BALANCE_WEIGHT_CHANGED; + +			Internal.BalanceBoard.Kg.TopL	 = +				GetBalanceValue(Internal.BalanceBoard.Raw.TopL, +								Internal.BalanceBoard.CalibrationInfo.Kg0 .TopL, +								Internal.BalanceBoard.CalibrationInfo.Kg17.TopL, +								Internal.BalanceBoard.CalibrationInfo.Kg34.TopL); +			Internal.BalanceBoard.Kg.TopR	 = +				GetBalanceValue(Internal.BalanceBoard.Raw.TopR, +								Internal.BalanceBoard.CalibrationInfo.Kg0 .TopR, +								Internal.BalanceBoard.CalibrationInfo.Kg17.TopR, +								Internal.BalanceBoard.CalibrationInfo.Kg34.TopR); +			Internal.BalanceBoard.Kg.BottomL = +				GetBalanceValue(Internal.BalanceBoard.Raw.BottomL, +								Internal.BalanceBoard.CalibrationInfo.Kg0 .BottomL, +								Internal.BalanceBoard.CalibrationInfo.Kg17.BottomL, +								Internal.BalanceBoard.CalibrationInfo.Kg34.BottomL); +			Internal.BalanceBoard.Kg.BottomR = +				GetBalanceValue(Internal.BalanceBoard.Raw.BottomR, +								Internal.BalanceBoard.CalibrationInfo.Kg0 .BottomR, +								Internal.BalanceBoard.CalibrationInfo.Kg17.BottomR, +								Internal.BalanceBoard.CalibrationInfo.Kg34.BottomR); +			 +			// uses these as the 'at rest' offsets? (immediately after Connect(), +			//  or if the app called CalibrateAtRest()) +			if(bCalibrateAtRest) { +				bCalibrateAtRest = false; +				TRACE(_T(".. Auto-removing 'at rest' BBoard offsets.")); +				Internal.BalanceBoard.AtRestKg = Internal.BalanceBoard.Kg; +				} + +			// remove the 'at rest' offsets +			Internal.BalanceBoard.Kg.TopL	 -= BalanceBoard.AtRestKg.TopL; +			Internal.BalanceBoard.Kg.TopR	 -= BalanceBoard.AtRestKg.TopR; +			Internal.BalanceBoard.Kg.BottomL -= BalanceBoard.AtRestKg.BottomL; +			Internal.BalanceBoard.Kg.BottomR -= BalanceBoard.AtRestKg.BottomR; + +			// compute the average +			Internal.BalanceBoard.Kg.Total	  = Internal.BalanceBoard.Kg.TopL    + +												Internal.BalanceBoard.Kg.TopR    + +												Internal.BalanceBoard.Kg.BottomL + +												Internal.BalanceBoard.Kg.BottomR; +			// and convert to Lbs +			const float KG2LB = 2.20462262f; +			Internal.BalanceBoard.Lb		  = Internal.BalanceBoard.Kg; +			Internal.BalanceBoard.Lb.TopL	 *= KG2LB; +			Internal.BalanceBoard.Lb.TopR	 *= KG2LB; +			Internal.BalanceBoard.Lb.BottomL *= KG2LB; +			Internal.BalanceBoard.Lb.BottomR *= KG2LB; +			Internal.BalanceBoard.Lb.Total	 *= KG2LB; +			} +			break; + +		case MOTION_PLUS: +			{ +			bMotionPlusDetected = true; +			bMotionPlusEnabled  = true; + +			short yaw   = ((unsigned short)buff[offset+3] & 0xFC)<<6 | +						   (unsigned short)buff[offset+0]; +			short pitch = ((unsigned short)buff[offset+5] & 0xFC)<<6 | +                           (unsigned short)buff[offset+2]; +			short roll  = ((unsigned short)buff[offset+4] & 0xFC)<<6 | +				           (unsigned short)buff[offset+1]; + +			// we get one set of bogus values when the MotionPlus is disconnected, +			//  so ignore them +			if((yaw != 0x3fff) || (pitch != 0x3fff) || (roll != 0x3fff)) +				{ +				wiimote_state::motion_plus::sensors_raw &raw = Internal.MotionPlus.Raw; +	 +				if((raw.Yaw != yaw) || (raw.Pitch != pitch) || (raw.Roll  != roll)) +					changed |= MOTIONPLUS_SPEED_CHANGED; + +				raw.Yaw   = yaw; +				raw.Pitch = pitch; +				raw.Roll  = roll; +	 +				// convert to float values +				bool    yaw_slow = (buff[offset+3] & 0x2) == 0x2; +				bool  pitch_slow = (buff[offset+3] & 0x1) == 0x1; +				bool   roll_slow = (buff[offset+4] & 0x2) == 0x2; +				float y_scale    =   yaw_slow? 0.05f : 0.25f; +				float p_scale    = pitch_slow? 0.05f : 0.25f; +				float r_scale    =  roll_slow? 0.05f : 0.25f; +			 +				Internal.MotionPlus.Speed.Yaw   = -(raw.Yaw   - 0x1F7F) * y_scale; +		        Internal.MotionPlus.Speed.Pitch = -(raw.Pitch - 0x1F7F) * p_scale; +			    Internal.MotionPlus.Speed.Roll  = -(raw.Roll  - 0x1F7F) * r_scale; +	 +				// show if there's an extension plugged into the MotionPlus: +				bool extension = buff[offset+4] & 1; +				if(extension != bMotionPlusExtension) +					{ +					if(extension) { +						TRACE(_T(".. MotionPlus extension found.")); +						changed |= MOTIONPLUS_EXTENSION_CONNECTED; +						} +					else{ +						TRACE(_T(".. MotionPlus' extension disconnected.")); +						changed |= MOTIONPLUS_EXTENSION_DISCONNECTED; +						} +					} +				bMotionPlusExtension = extension; +				} +			// while we're getting data, the plus is obviously detected/enabled +//			bMotionPlusDetected = bMotionPlusEnabled = true; +			} +			break; +		} +	 +	return changed; +	} +// ------------------------------------------------------------------------------------ +int wiimote::ParseReadAddress (BYTE* buff) +	{ +	// decode the address that was queried: +	int address = buff[4]<<8 | buff[5]; +	int size    = buff[3] >> 4; +	int changed	= 0; + +	if((buff[3] & 0x08) != 0) { +		WARN(_T("error: read address not valid.")); +		_ASSERT(0); +		return NO_CHANGE; +		} +	// address read failed (write-only)? +	else if((buff[3] & 0x07) != 0) +		{ +		// this also happens when attempting to detect a non-existant MotionPlus +		if(MotionPlusDetectCount) +			{ +			--MotionPlusDetectCount; +			if(Internal.ExtensionType == MOTION_PLUS) +				{ +				if(bMotionPlusDetected) +					TRACE(_T(".. MotionPlus removed.")); +				bMotionPlusDetected  = false; +				bMotionPlusEnabled   = false; +				// the MotionPlus can sometimes get confused - initializing +				//  extenions fixes it: +//				if(address == 0xfa) +//					InitializeExtension(); +				} +			} +		else +			WARN(_T("error: attempt to read from write-only register 0x%X."), buff[3]); + +		return NO_CHANGE; +		} + +	// *NOTE*: this is a major (but convenient) hack!  The returned data only +	//          contains the lower two bytes of the address that was queried. +	//			as these don't collide between any of the addresses/registers +	//			we currently read, it's OK to match just those two bytes + +	// skip the header +	buff += 6; + +	switch(address) +		{ +		case (REGISTER_CALIBRATION & 0xffff): +			{ +			_ASSERT(size == 6); +			TRACE(_T(".. got wiimote calibration.")); +			Internal.CalibrationInfo.X0 = buff[0]; +			Internal.CalibrationInfo.Y0 = buff[1]; +			Internal.CalibrationInfo.Z0 = buff[2]; +			Internal.CalibrationInfo.XG = buff[4]; +			Internal.CalibrationInfo.YG = buff[5]; +			Internal.CalibrationInfo.ZG = buff[6]; +			//changed |= CALIBRATION_CHANGED;	 +			} +			break; +			 +		// note: this covers both the normal extension and motion plus extension +		//        addresses (0x4a400fa / 0x4a600fa) +		case (REGISTER_EXTENSION_TYPE & 0xffff): +			{ +			_ASSERT(size == 5); +			QWORD type = *(QWORD*)buff; + +//			TRACE(_T("(found extension 0x%I64x)"), type); + +			static const QWORD NUNCHUK		       = 0x000020A40000ULL; +			static const QWORD CLASSIC		       = 0x010120A40000ULL; +			static const QWORD GH3_GHWT_GUITAR     = 0x030120A40000ULL; +			static const QWORD GHWT_DRUMS	       = 0x030120A40001ULL; +			static const QWORD BALANCE_BOARD	   = 0x020420A40000ULL; +			static const QWORD MOTION_PLUS		   = 0x050420A40000ULL; +			static const QWORD MOTION_PLUS_DETECT  = 0x050020a60000ULL; +			static const QWORD MOTION_PLUS_DETECT2 = 0x050420a60000ULL; +			static const QWORD PARTIALLY_INSERTED  = 0xffffffffffffULL; + +			// MotionPlus: _before_ it's been activated +			if((type == MOTION_PLUS_DETECT) || (type == MOTION_PLUS_DETECT2)) +				{ +				if(!bMotionPlusDetected) { +					TRACE(_T("Motion Plus detected!")); +					changed |= MOTIONPLUS_DETECTED; +					} +				bMotionPlusDetected = true; +				--MotionPlusDetectCount; +				break; +				} + +			#define IF_TYPE(id)	if(type == id) { \ +									/* sometimes it comes in more than once */ \ +									if(Internal.ExtensionType == wiimote_state::id)\ +										break; \ +									Internal.ExtensionType = wiimote_state::id; + +			// MotionPlus: once it's activated & mapped to the standard ext. port +			IF_TYPE(MOTION_PLUS) +				TRACE(_T(".. Motion Plus!")); +				// and start a query for the calibration data +				ReadAddress(REGISTER_EXTENSION_CALIBRATION, 16); +				bMotionPlusDetected = true; +				} +			else IF_TYPE(NUNCHUK) +				TRACE(_T(".. Nunchuk!")); +				bMotionPlusEnabled = false; +				// and start a query for the calibration data +				ReadAddress(REGISTER_EXTENSION_CALIBRATION, 16); +				} +			else IF_TYPE(CLASSIC) +				TRACE(_T(".. Classic Controller!")); +				bMotionPlusEnabled = false; +				// and start a query for the calibration data +				ReadAddress(REGISTER_EXTENSION_CALIBRATION, 16); +				} +			else IF_TYPE(GH3_GHWT_GUITAR) +				// sometimes it comes in more than once? +				TRACE(_T(".. GH3/GHWT Guitar Controller!")); +				bMotionPlusEnabled = false; +				// and start a query for the calibration data +				ReadAddress(REGISTER_EXTENSION_CALIBRATION, 16); +				} +			else IF_TYPE(GHWT_DRUMS) +				TRACE(_T(".. GHWT Drums!")); +				bMotionPlusEnabled = false; +				// and start a query for the calibration data +				ReadAddress(REGISTER_EXTENSION_CALIBRATION, 16); +				} +			else IF_TYPE(BALANCE_BOARD) +				TRACE(_T(".. Balance Board!")); +				bMotionPlusEnabled = false; +				// and start a query for the calibration data +				ReadAddress(REGISTER_BALANCE_CALIBRATION, 24); +				} +			else if(type == PARTIALLY_INSERTED) { +				// sometimes it comes in more than once? +				if(Internal.ExtensionType == wiimote_state::PARTIALLY_INSERTED) +					Sleep(50); +				TRACE(_T(".. partially inserted!")); +				bMotionPlusEnabled = false; +				Internal.ExtensionType = wiimote_state::PARTIALLY_INSERTED; +				changed |= EXTENSION_PARTIALLY_INSERTED; +				// try initializing the extension again by requesting another +				//  status report (this usually fixes it) +				Internal.bExtension = false; +				RequestStatusReport(); +				} +			else{ +				TRACE(_T("unknown extension controller found (0x%I64x)"), type); +				} +			} +			break; +		 +		case (REGISTER_EXTENSION_CALIBRATION & 0xffff): +		case (REGISTER_BALANCE_CALIBRATION   & 0xffff): +			{ +//			_ASSERT(((Internal.ExtensionType == BALANCE_BOARD) && (size == 31)) || +//					((Internal.ExtensionType != BALANCE_BOARD) && (size == 15))); + +			switch(Internal.ExtensionType) +				{ +				case wiimote_state::NUNCHUK: +					{ +					wiimote_state::nunchuk::calibration_info +						&calib = Internal.Nunchuk.CalibrationInfo; + +					calib.X0   = buff[ 0]; +					calib.Y0   = buff[ 1]; +					calib.Z0   = buff[ 2]; +					calib.XG   = buff[ 4]; +					calib.YG   = buff[ 5]; +					calib.ZG   = buff[ 6]; +					calib.MaxX = buff[ 8]; +					calib.MinX = buff[ 9]; +					calib.MidX = buff[10]; +					calib.MaxY = buff[11]; +					calib.MinY = buff[12]; +					calib.MidY = buff[13]; + +					changed |= NUNCHUK_CONNECTED;//|NUNCHUK_CALIBRATION_CHANGED; +					// reenable reports +//					SetReportType(ReportType); +					} +					break; +				 +				case wiimote_state::CLASSIC: +				case wiimote_state::GH3_GHWT_GUITAR: +				case wiimote_state::GHWT_DRUMS: +					{ +					wiimote_state::classic_controller::calibration_info +						&calib = Internal.ClassicController.CalibrationInfo; +					 +					calib.MaxXL = buff[ 0] >> 2; +					calib.MinXL = buff[ 1] >> 2; +					calib.MidXL = buff[ 2] >> 2; +					calib.MaxYL = buff[ 3] >> 2; +					calib.MinYL = buff[ 4] >> 2; +					calib.MidYL = buff[ 5] >> 2; +					calib.MaxXR = buff[ 6] >> 3; +					calib.MinXR = buff[ 7] >> 3; +					calib.MidXR = buff[ 8] >> 3; +					calib.MaxYR = buff[ 9] >> 3; +					calib.MinYR = buff[10] >> 3; +					calib.MidYR = buff[11] >> 3; +					// this doesn't seem right... +					//	calib.MinTriggerL = buff[12] >> 3; +					//	calib.MaxTriggerL = buff[14] >> 3; +					//	calib.MinTriggerR = buff[13] >> 3; +					//	calib.MaxTriggerR = buff[15] >> 3; +					calib.MinTriggerL = 0; +					calib.MaxTriggerL = 31; +					calib.MinTriggerR = 0; +					calib.MaxTriggerR = 31; + +					changed |= CLASSIC_CONNECTED;//|CLASSIC_CALIBRATION_CHANGED; +					// reenable reports +//					SetReportType(ReportType); +					} +					break; + +				case BALANCE_BOARD: +					{ +					// first part, 0 & 17kg calibration values +					wiimote_state::balance_board::calibration_info +						&calib = Internal.BalanceBoard.CalibrationInfo; + +					calib.Kg0 .TopR	   = (short)((short)buff[0] << 8 | buff[1]); +					calib.Kg0 .BottomR = (short)((short)buff[2] << 8 | buff[3]); +					calib.Kg0 .TopL	   = (short)((short)buff[4] << 8 | buff[5]); +					calib.Kg0 .BottomL = (short)((short)buff[6] << 8 | buff[7]); + +					calib.Kg17.TopR	   = (short)((short)buff[8] << 8 | buff[9]); +					calib.Kg17.BottomR = (short)((short)buff[10] << 8 | buff[11]); +					calib.Kg17.TopL	   = (short)((short)buff[12] << 8 | buff[13]); +					calib.Kg17.BottomL = (short)((short)buff[14] << 8 | buff[15]); + +					// 2nd part is scanned above +					} +					break; + +				case MOTION_PLUS: +					{ +					// TODO: not known how the calibration values work +					changed |= MOTIONPLUS_ENABLED; +					bMotionPlusEnabled = true; +					bInitInProgress	   = false; +					// reenable reports +//					SetReportType(ReportType); +					} +					break; +				} +			case 0x34: +				{ +				if(Internal.ExtensionType == BALANCE_BOARD) +					{ +					wiimote_state::balance_board::calibration_info +						&calib = Internal.BalanceBoard.CalibrationInfo; + +					// 2nd part of the balance board calibration, +					//  34kg calibration values +					calib.Kg34.TopR    = (short)((short)buff[0] << 8 | buff[1]); +					calib.Kg34.BottomR = (short)((short)buff[2] << 8 | buff[3]); +					calib.Kg34.TopL    = (short)((short)buff[4] << 8 | buff[5]); +					calib.Kg34.BottomL = (short)((short)buff[6] << 8 | buff[7]); +						 +					changed |= BALANCE_CONNECTED; +					// reenable reports +					SetReportType(IN_BUTTONS_BALANCE_BOARD); +					} +				// else unknown what these are for +				} +			bInitInProgress = false; +			} +			break; + +		default: +//			_ASSERT(0); // shouldn't happen +			break; +		} +	 +	return changed; +	} +// ------------------------------------------------------------------------------------ +void wiimote::ReadCalibration () +	{ +	TRACE(_T("Requestion wiimote calibration:")); +	// this appears to change the report type to 0x31 +	ReadAddress(REGISTER_CALIBRATION, 7); +	} +// ------------------------------------------------------------------------------------ +void wiimote::EnableIR (wiimote_state::ir::mode mode) +	{ +	Internal.IR.Mode = mode; + +	BYTE buff [REPORT_LENGTH] = {0}; +	buff[0] = OUT_IR; +	buff[1] = 0x04 | GetRumbleBit(); +	WriteReport(buff); + +	memset(buff, 0, REPORT_LENGTH); +	buff[0] = OUT_IR2; +	buff[1] = 0x04 | GetRumbleBit(); +	WriteReport(buff); + +	static const BYTE ir_sens1[] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x90, 0x00, +									0xc0}; +	static const BYTE ir_sens2[] = {0x40, 0x00}; + +	WriteData(REGISTER_IR, 0x08); +	Sleep(25); 	// wait a little to make IR more reliable (for some) +	WriteData(REGISTER_IR_SENSITIVITY_1, sizeof(ir_sens1), ir_sens1); +	WriteData(REGISTER_IR_SENSITIVITY_2, sizeof(ir_sens2), ir_sens2); +	WriteData(REGISTER_IR_MODE, (BYTE)mode); +	} +// ------------------------------------------------------------------------------------ +void wiimote::DisableIR () +	{ +	Internal.IR.Mode = wiimote_state::ir::OFF; + +	BYTE buff [REPORT_LENGTH] = {0}; +	buff[0] = OUT_IR; +	buff[1] = GetRumbleBit(); +	WriteReport(buff); + +	memset(buff, 0, REPORT_LENGTH); +	buff[0] = OUT_IR2; +	buff[1] = GetRumbleBit(); +	WriteReport(buff); +	} +// ------------------------------------------------------------------------------------ +unsigned __stdcall wiimote::HIDwriteThreadfunc (void* param) +	{ +	_ASSERT(param); +	TRACE(_T("(starting HID write thread)")); +	wiimote &remote = *(wiimote*)param; + +	while(remote.Handle != INVALID_HANDLE_VALUE) +		{ +		// try to write the oldest entry in the queue +#ifdef USE_DYNAMIC_HIDQUEUE +		if(!remote.HIDwriteQueue.empty()) +#else +		if(!remote.HID.IsEmpty()) +#endif +			{ +#ifdef BEEP_DEBUG_WRITES +			Beep(1500,1); +#endif +			EnterCriticalSection(&remote.HIDwriteQueueLock); +#ifdef USE_DYNAMIC_HIDQUEUE +			 BYTE *buff = remote.HIDwriteQueue.front(); +			 _ASSERT(buff); +#else +			 BYTE *buff = remote.HID.Queue[remote.HID.ReadIndex].Report; +#endif +			LeaveCriticalSection(&remote.HIDwriteQueueLock); + +			if(!_HidD_SetOutputReport(remote.Handle, buff, REPORT_LENGTH)) +				{ +				DWORD err = GetLastError(); +if(err==ERROR_BUSY) +TRACE(_T("**** HID WRITE: BUSY ****")); +else if(err == ERROR_NOT_READY) +TRACE(_T("**** HID WRITE: NOT READY ****")); + +				if((err != ERROR_BUSY)	&&	 // "the requested resource is in use" +				   (err != ERROR_NOT_READY)) // "the device is not ready" +					{ +					if(err == ERROR_NOT_SUPPORTED) { +						WARN(_T("BT Stack doesn't suport HID writes!")); +						goto remove_entry; +						} +					else{ +						DEEP_TRACE(_T("HID write failed (err %u)! - "), err); +						// if this worked previously, the connection was probably lost +						if(remote.IsConnected()) +							remote.bConnectionLost = true; +						} +					//_T("aborting write thread"), err); +					//return 911; +					} +				} +			else{ +remove_entry: +				EnterCriticalSection(&remote.HIDwriteQueueLock); +#ifdef USE_DYNAMIC_HIDQUEUE +				 remote.HIDwriteQueue.pop(); +				 delete[] buff; +#else +				 remote.HID.ReadIndex++; +				 remote.HID.ReadIndex &= (hid::MAX_QUEUE_ENTRIES-1); +#endif +				LeaveCriticalSection(&remote.HIDwriteQueueLock); +				} +			} +		Sleep(1); +		} + +	TRACE(_T("ending HID write thread")); +	return 0; +	} +// ------------------------------------------------------------------------------------ +bool wiimote::WriteReport (BYTE *buff) +	{ +#ifdef BEEP_DEBUG_WRITES +	Beep(2000,1); +#endif + +#ifdef _DEBUG +	#define DEEP_TRACE_TYPE(type)	case OUT_##type: DEEP_TRACE(_T("WriteReport: ")\ +																_T(#type)); break +	switch(buff[0]) +		{ +		DEEP_TRACE_TYPE(NONE); +		DEEP_TRACE_TYPE(LEDs); +		DEEP_TRACE_TYPE(TYPE); +		DEEP_TRACE_TYPE(IR); +		DEEP_TRACE_TYPE(SPEAKER_ENABLE); +		DEEP_TRACE_TYPE(STATUS); +		DEEP_TRACE_TYPE(WRITEMEMORY); +		DEEP_TRACE_TYPE(READMEMORY); +		DEEP_TRACE_TYPE(SPEAKER_DATA); +		DEEP_TRACE_TYPE(SPEAKER_MUTE); +		DEEP_TRACE_TYPE(IR2); +		default: +			TRACE(_T("WriteReport: type [%02x][%02x]"), buff[1], buff[2]); +		} +#endif + +	if(bUseHIDwrite) +		{ +		// HidD_SetOutputReport: +: works on MS Bluetooth stacks (WriteFile doesn't). +		//						 -: is synchronous, so make it async +		if(!HIDwriteThread) +			{ +			HIDwriteThread = (HANDLE)_beginthreadex(NULL, 0, HIDwriteThreadfunc, +													this, 0, NULL); +			_ASSERT(HIDwriteThread); +			if(!HIDwriteThread) { +				WARN(_T("couldn't create HID write thread!")); +				return false; +				} +			SetThreadPriority(HIDwriteThread, WORKER_THREAD_PRIORITY); +			} + +		// insert the write request into the thread's queue +#ifdef USE_DYNAMIC_HIDQUEUE +		EnterCriticalSection(&HIDwriteQueueLock); +		 BYTE *buff_copy = new BYTE[REPORT_LENGTH]; +#else +		// allocate the HID write queue once +		if(!HID.Queue && !HID.Allocate()) +			return false; + +		EnterCriticalSection(&HIDwriteQueueLock); +		 BYTE *buff_copy = HID.Queue[HID.WriteIndex].Report; +#endif +		 memcpy(buff_copy, buff, REPORT_LENGTH); + +#ifdef USE_DYNAMIC_HIDQUEUE +		 HIDwriteQueue.push(buff_copy); +#else +		 HID.WriteIndex++; +		 HID.WriteIndex &= (HID.MAX_QUEUE_ENTRIES-1); + +		 // check if the fixed report queue has overflown: +		 //  if this ASSERT triggers, the HID write queue (that stores reports +		 //   for asynchronous output by HIDwriteThreadfunc) has overflown. +		 //  this can happen if the connection with the wiimote has been lost +		 //   and in that case is harmless. +		 // +		 //  if it happens during normal operation though you need to increase +		 //   hid::MAX_QUEUE_ENTRIES to the next power-of-2 (see comments) +		 //   _and_ email me the working setting so I can update the next release +		 _ASSERT(HID.WriteIndex != HID.ReadIndex); +#endif +		LeaveCriticalSection(&HIDwriteQueueLock); +		return true; +		} + +	// WriteFile: +	DWORD written; +	if(!WriteFile(Handle, buff, REPORT_LENGTH, &written, &Overlapped)) +		{ +		DWORD error = GetLastError(); +		if(error != ERROR_IO_PENDING) { +			TRACE(_T("WriteFile failed, err: %u!"), error); +			// if it worked previously, assume we lost the connection +			if(IsConnected()) +				bConnectionLost = true; +#ifndef USE_DYNAMIC_HIDQUEUE +			HID.Deallocate(); +#endif +			return false; +			} +		} +	return true; +	} +// ------------------------------------------------------------------------------------ +// experimental speaker support: +// ------------------------------------------------------------------------------------ +bool wiimote::MuteSpeaker (bool on) +	{ +	_ASSERT(IsConnected()); +	if(!IsConnected()) +		return false; + +	if(Internal.Speaker.bMuted == on) +		return true; + +	if(on) TRACE(_T("muting speaker."  )); +	else   TRACE(_T("unmuting speaker.")); + +	BYTE buff [REPORT_LENGTH] = {0}; +	buff[0] = OUT_SPEAKER_MUTE; +	buff[1] = (on? 0x04 : 0x00) | GetRumbleBit(); +	if(!WriteReport(buff)) +		return false; +	Sleep(1); +	Internal.Speaker.bMuted = on; +	return true; +	} +// ------------------------------------------------------------------------------------ +bool wiimote::EnableSpeaker (bool on) +	{ +	_ASSERT(IsConnected()); +	if(!IsConnected()) +		return false; + +	if(Internal.Speaker.bEnabled == on) +		return true; + +	if(on) TRACE(_T("enabling speaker.")); else TRACE(_T("disabling speaker.")); + +	BYTE buff [REPORT_LENGTH] = {0}; +	buff[0] = OUT_SPEAKER_ENABLE; +	buff[1] = (on? 0x04 : 0x00) | GetRumbleBit(); +	if(!WriteReport(buff)) +		return false; + +	if(!on) { +		Internal.Speaker.Freq   = FREQ_NONE; +		Internal.Speaker.Volume = 0; +		MuteSpeaker(true); +		} + +	Internal.Speaker.bEnabled = on; +	return true; +	} +// ------------------------------------------------------------------------------------ +#ifdef TR4 // TEMP, ignore + extern int hzinc; +#endif +// ------------------------------------------------------------------------------------ +unsigned __stdcall wiimote::SampleStreamThreadfunc (void* param) +	{ +	TRACE(_T("(starting sample thread)")); +	// sends a simple square wave sample stream +	wiimote &remote = *(wiimote*)param; +	 +	static BYTE squarewave_report[REPORT_LENGTH] = +		{ OUT_SPEAKER_DATA, 20<<3, 0xC3,0xC3,0xC3,0xC3,0xC3,0xC3,0xC3,0xC3,0xC3,0xC3, +								   0xC3,0xC3,0xC3,0xC3,0xC3,0xC3,0xC3,0xC3,0xC3,0xC3, }; +	static BYTE sample_report [REPORT_LENGTH] =  +		{ OUT_SPEAKER_DATA, 0 }; + +	bool		   last_playing    = false; +	DWORD		   frame		   = 0; +	DWORD		   frame_start     = 0; +	unsigned	   total_samples   = 0; +	unsigned	   sample_index	   = 0; +	wiimote_sample *current_sample = NULL; +	 +	// TODO: duration!! +	while(remote.IsConnected()) +		{ +		bool playing = remote.IsPlayingAudio(); +		 +		if(!playing) +			Sleep(1); +		else{ +			const unsigned freq_hz  = FreqLookup[remote.Internal.Speaker.Freq]; +#ifdef TR4 +			const float    frame_ms = 1000 / ((freq_hz+hzinc) / 40.f); // 20bytes = 40 samples per write +#else +			const float    frame_ms = 1000 / (freq_hz		  / 40.f); // 20bytes = 40 samples per write +#endif + +			// has the sample just changed? +			bool sample_changed = (current_sample != remote.CurrentSample); +			current_sample		= (wiimote_sample*)remote.CurrentSample; + +// (attempts to minimise glitches, doesn't seem to help though) +//#define FIRSTFRAME_IS_SILENT	// send all-zero for first frame + +#ifdef FIRSTFRAME_IS_SILENT +			bool silent_frame = false; +#endif +			if(!last_playing || sample_changed) { +				frame		  = 0; +				frame_start   = timeGetTime(); +				total_samples = current_sample? current_sample->length : 0; +				sample_index  = 0; +#ifdef FIRSTFRAME_IS_SILENT +				silent_frame  = true; +#endif +				} + +			// are we streaming a sample? +			if(current_sample) +				{ +				if(sample_index < current_sample->length) +					{ +					// (remember that samples are 4bit, ie. 2 per byte) +					unsigned samples_left   = (current_sample->length - sample_index); +					unsigned report_samples = min(samples_left, (unsigned)40); +					// round the entries up to the nearest multiple of 2 +					unsigned report_entries = (report_samples+1) >> 1; +					 +					sample_report[1] = (BYTE)((report_entries<<3) | +											  remote.GetRumbleBit()); +#ifdef FIRSTFRAME_IS_SILENT +					if(silent_frame) { +						// send all-zeroes +						for(unsigned index=0; index<report_entries; index++) +							sample_report[2+index] = 0; +						remote.WriteReport(sample_report); +						} +					else +#endif +					{ +						for(unsigned index=0; index<report_entries; index++) +							sample_report[2+index] = +									current_sample->samples[(sample_index>>1)+index]; +						remote.WriteReport(sample_report); +						sample_index += report_samples; +						} +					} +				else{ +					// we reached the sample end +					remote.CurrentSample		   = NULL; +					current_sample				   = NULL; +					remote.Internal.Speaker.Freq   = FREQ_NONE; +					remote.Internal.Speaker.Volume = 0; +					} +				} +			// no, a squarewave +			else{ +				squarewave_report[1] = (20<<3) | remote.GetRumbleBit(); +				remote.WriteReport(squarewave_report); +#if 0 +				// verify that we're sending at the correct rate (we are) +				DWORD elapsed		   = (timeGetTime()-frame_start); +				unsigned total_samples = frame * 40; +				float elapsed_secs	   = elapsed / 1000.f; +				float sent_persec	   = total_samples / elapsed_secs; +#endif +				} + +			frame++; + +			// send the first two buffers immediately? (attempts to lessen startup +			//  startup glitches by assuming we're filling a small sample +			//  (or general input) buffer on the wiimote) - doesn't seem to help +//			if(frame > 2) { +				while((timeGetTime()-frame_start) < (unsigned)(frame*frame_ms)) +					Sleep(1); +//				} +			} + +		last_playing = playing; +		} +	 +	TRACE(_T("(ending sample thread)")); +	return 0; +	} +// ------------------------------------------------------------------------------------ +bool wiimote::Load16bitMonoSampleWAV (const TCHAR* filepath, wiimote_sample &out) +	{ +	// converts unsigned 16bit mono .wav audio data to the 4bit ADPCM variant +	//  used by the Wiimote (at least the closest match so far), and returns +	//  the data in a BYTE array (caller must delete[] it when no longer needed): +	memset(&out, 0, sizeof(out)); + +	TRACE(_T("Loading '%s'"), filepath); + +	FILE *file; +#if (_MSC_VER >= 1400) // VC 2005+ +	_tfopen_s(&file, filepath, _T("rb")); +#else +	file = _tfopen(filepath, _T("rb")); +#endif +	_ASSERT(file); +	if(!file) { +		WARN(_T("Couldn't open '%s"), filepath); +		return false; +		} + +	// parse the .wav file +	struct riff_chunkheader { +		char  ckID [4]; +		DWORD ckSize; +		char  formType [4]; +		}; +	struct chunk_header { +		char  ckID [4]; +		DWORD ckSize; +		}; +	union { +		WAVEFORMATEX		 x; +		WAVEFORMATEXTENSIBLE xe; +		} wf = {0}; + +	riff_chunkheader riff_chunkheader; +	chunk_header	 chunk_header; +	speaker_freq	 freq = FREQ_NONE; + +	#define READ(data)			if(fread(&data, sizeof(data), 1, file) != 1) { \ +									TRACE(_T(".wav file corrupt"));			   \ +									fclose(file);							   \ +									return false;							   \ +									} +	#define READ_SIZE(ptr,size)	if(fread(ptr, size, 1, file) != 1) {		   \ +									TRACE(_T(".wav file corrupt"));			   \ +									fclose(file);							   \ +									return false;							   \ +									} +	// read the riff chunk header +	READ(riff_chunkheader); + +	// valid RIFF file? +	_ASSERT(!strncmp(riff_chunkheader.ckID, "RIFF", 4)); +	if(strncmp(riff_chunkheader.ckID, "RIFF", 4)) +		goto unsupported; // nope +	// valid WAV variant? +	_ASSERT(!strncmp(riff_chunkheader.formType, "WAVE", 4)); +	if(strncmp(riff_chunkheader.formType, "WAVE", 4)) +		goto unsupported; // nope + +	// find the format & data chunks +	while(1) +		{ +		READ(chunk_header); +		 +		if(!strncmp(chunk_header.ckID, "fmt ", 4)) +			{ +			// not a valid .wav file? +			if(chunk_header.ckSize < 16 || +			   chunk_header.ckSize > sizeof(WAVEFORMATEXTENSIBLE)) +				goto unsupported; + +			READ_SIZE((BYTE*)&wf.x, chunk_header.ckSize); + +			// now we know it's true wav file +			bool extensible = (wf.x.wFormatTag == WAVE_FORMAT_EXTENSIBLE); +			int format	    = extensible? wf.xe.SubFormat.Data1 : +										  wf.x .wFormatTag; +			// must be uncompressed PCM (the format comparisons also work on +			//  the 'extensible' header, even though they're named differently) +			if(format != WAVE_FORMAT_PCM) { +				TRACE(_T(".. not uncompressed PCM")); +				goto unsupported; +				} + +			// must be mono, 16bit +			if((wf.x.nChannels != 1) || (wf.x.wBitsPerSample != 16)) { +				TRACE(_T(".. %d bit, %d channel%s"), wf.x.wBitsPerSample, +													 wf.x.nChannels, +													(wf.x.nChannels>1? _T("s"):_T(""))); +				goto unsupported; +				} + +			// must be _near_ a supported speaker frequency range (but allow some +			//  tolerance, especially as the speaker freq values aren't final yet): +			unsigned	   sample_freq = wf.x.nSamplesPerSec; +			const unsigned epsilon	   = 100; // for now +			 +			for(unsigned index=1; index<ARRAY_ENTRIES(FreqLookup); index++) +				{ +				if((sample_freq+epsilon) >= FreqLookup[index] && +				   (sample_freq-epsilon) <= FreqLookup[index]) { +					freq = (speaker_freq)index; +					TRACE(_T(".. using speaker freq %u"), FreqLookup[index]); +					break; +					} +				} +			if(freq == FREQ_NONE) { +				WARN(_T("Couldn't (loosely) match .wav samplerate %u Hz to speaker"), +					 sample_freq); +				goto unsupported; +				} +			} +		else if(!strncmp(chunk_header.ckID, "data", 4)) +			{ +			// make sure we got a valid fmt chunk first +			if(!wf.x.nBlockAlign) +				goto corrupt_file; + +			// grab the data +			unsigned total_samples = chunk_header.ckSize / wf.x.nBlockAlign; +			if(total_samples == 0) +				goto corrupt_file; +			 +			short *samples = new short[total_samples]; +			size_t read = fread(samples, 2, total_samples, file); +			fclose(file); +			if(read != total_samples) +				{ +				if(read == 0) { +					delete[] samples; +					goto corrupt_file; +					} +				// got a different number, but use them anyway +				WARN(_T("found %s .wav audio data than expected (%u/%u samples)"), +					((read < total_samples)? _T("less") : _T("more")), +					read, total_samples); + +				total_samples = read; +				} + +			// and convert them +			bool res = Convert16bitMonoSamples(samples, true, total_samples, freq, +											   out); +			delete[] samples; +			return res; +			} +		else{ +			// unknown chunk, skip its data +			DWORD chunk_bytes = (chunk_header.ckSize + 1) & ~1L; +			if(fseek(file, chunk_bytes, SEEK_CUR)) +				goto corrupt_file; +			} +		} + +corrupt_file: +	WARN(_T(".wav file is corrupt")); +	fclose(file); +	return false; + +unsupported: +	WARN(_T(".wav file format not supported (must be mono 16bit PCM)")); +	fclose(file); +	return false; +	} +// ------------------------------------------------------------------------------------ +bool wiimote::Load16BitMonoSampleRAW (const TCHAR*   filepath, +									  bool		     _signed, +									  speaker_freq   freq, +									  wiimote_sample &out) +	{ +	// converts (.wav style) unsigned 16bit mono raw data to the 4bit ADPCM variant +	//  used by the Wiimote, and returns the data in a BYTE array (caller must +	//  delete[] it when no longer needed): +	memset(&out, 0, sizeof(out)); + +	// get the length of the file +	struct _stat file_info; +	if(_tstat(filepath, &file_info)) { +		WARN(_T("couldn't get filesize for '%s'"), filepath); +		return false; +		} +	 +	DWORD len = file_info.st_size; +	_ASSERT(len); +	if(!len) { +		WARN(_T("zero-size sample file '%s'"), filepath); +		return false; +		} + +	unsigned total_samples = (len+1) / 2; // round up just in case file is corrupt +	// allocate a buffer to hold the samples to convert +	short *samples = new short[total_samples];  +	_ASSERT(samples); +	if(!samples) { +		TRACE(_T("Couldn't open '%s"), filepath); +		return false; +		} + +	// load them +	FILE *file; +	bool res; +#if (_MSC_VER >= 1400) // VC 2005+ +	_tfopen_s(&file, filepath, _T("rb")); +#else +	file = _tfopen(filepath, _T("rb")); +#endif +	_ASSERT(file); +	if(!file) { +		TRACE(_T("Couldn't open '%s"), filepath); +        goto error; +        } + +	res = (fread(samples, 1, len, file) == len); +	fclose(file); +	if(!res) { +		WARN(_T("Couldn't load file '%s'"), filepath); +		goto error; +		} + +	// and convert them +	res = Convert16bitMonoSamples(samples, _signed, total_samples, freq, out); +	delete[] samples; +	return res; + +error: +	delete[] samples; +	return false; +	} +// ------------------------------------------------------------------------------------ +bool wiimote::Convert16bitMonoSamples (const short*   samples, +									   bool		      _signed, +									   DWORD		  length, +									   speaker_freq   freq, +									   wiimote_sample &out) +	{ +	// converts 16bit mono sample data to the native 4bit format used by the Wiimote, +	//  and returns the data in a BYTE array (caller must delete[] when no +	//  longer needed): +	memset(&out, 0, sizeof(0)); + +	_ASSERT(samples && length); +	if(!samples || !length) +		return false; + +	// allocate the output buffer +	out.samples = new BYTE[length]; +	_ASSERT(out.samples); +	if(!out.samples) +		return false; + +	// clear it +	memset(out.samples, 0, length); +	out.length = length; +	out.freq   = freq; + +	// ADPCM code, adapted from +	//  http://www.wiindows.org/index.php/Talk:Wiimote#Input.2FOutput_Reports +	static const int index_table[16] = {  -1,  -1,  -1,  -1,   2,   4,   6,   8, +										  -1,  -1,  -1,  -1,   2,   4,   6,   8 }; +	static const int diff_table [16] = {   1,   3,   5,   7,   9,  11,  13,  15, +										  -1,  -3,  -5,  -7,  -9, -11, -13,  15 }; +	static const int step_scale [16] = { 230, 230, 230, 230, 307, 409, 512, 614, +										 230, 230, 230, 230, 307, 409, 512, 614 }; +	// Encode to ADPCM, on initialization set adpcm_prev_value to 0 and adpcm_step +	//  to 127 (these variables must be preserved across reports) +	int adpcm_prev_value = 0; +	int adpcm_step		 = 127; + +	for(size_t i=0; i<length; i++) +		{ +		// convert to 16bit signed +		int value = samples[i];// (8bit) << 8);// | samples[i]; // dither it? +		if(!_signed) +			value -= 32768; +		// encode: +		int  diff = value - adpcm_prev_value; +		BYTE encoded_val = 0; +		if(diff < 0) { +			encoded_val |= 8; +			diff = -diff; +			} +		diff = (diff << 2) / adpcm_step; +		if (diff > 7) +			diff = 7; +		encoded_val |= diff; +		adpcm_prev_value += ((adpcm_step * diff_table[encoded_val]) / 8); +		if(adpcm_prev_value  >  0x7fff) +			adpcm_prev_value =  0x7fff; +		if(adpcm_prev_value  < -0x8000) +			adpcm_prev_value = -0x8000; +		adpcm_step = (adpcm_step * step_scale[encoded_val]) >> 8; +		if(adpcm_step < 127) +			adpcm_step = 127; +		if(adpcm_step > 24567) +			adpcm_step = 24567; +		if(i & 1) +			out.samples[i>>1] |= encoded_val; +		else +			out.samples[i>>1] |= encoded_val << 4; +		} + +	return true; +	} +// ------------------------------------------------------------------------------------ +bool wiimote::PlaySample (const wiimote_sample &sample, BYTE volume,  +						  speaker_freq freq_override) +	{ +	_ASSERT(IsConnected()); +	if(!IsConnected()) +		return false; + +	speaker_freq freq = freq_override? freq_override : sample.freq; + +	TRACE(_T("playing sample.")); +	EnableSpeaker(true); +	MuteSpeaker  (true); + +#if 0 +	// combine everything into one write - faster, seems to work? +	BYTE bytes[9] = { 0x00, 0x00, 0x00, 10+freq, vol, 0x00, 0x00, 0x01, 0x01 }; +	WriteData(0x04a20001, sizeof(bytes), bytes); +#else +	// Write 0x01 to register 0x04a20009  +	WriteData(0x04a20009, 0x01); +	// Write 0x08 to register 0x04a20001  +	WriteData(0x04a20001, 0x08); +	// Write 7-byte configuration to registers 0x04a20001-0x04a20008  +	BYTE bytes[7] = { 0x00, 0x00, 0x00, 10+(BYTE)freq, volume, 0x00, 0x00 }; +	WriteData(0x04a20001, sizeof(bytes), bytes); +	// + Write 0x01 to register 0x04a20008  +	WriteData(0x04a20008, 0x01); +#endif + +	Internal.Speaker.Freq   = freq; +	Internal.Speaker.Volume = volume; +	CurrentSample			= &sample; + +	MuteSpeaker(false); + +	return StartSampleThread(); +	} +// ------------------------------------------------------------------------------------ +bool wiimote::StartSampleThread () +	{ +	if(SampleThread) +		return true; + +	SampleThread = (HANDLE)_beginthreadex(NULL, 0, SampleStreamThreadfunc, +										  this, 0, NULL); +	_ASSERT(SampleThread); +	if(!SampleThread) { +		WARN(_T("couldn't create sample thread!")); +		MuteSpeaker  (true); +		EnableSpeaker(false);	 +		return false; +		} +	SetThreadPriority(SampleThread, WORKER_THREAD_PRIORITY); +	return true; +	} +// ------------------------------------------------------------------------------------ +bool wiimote::PlaySquareWave (speaker_freq freq, BYTE volume) +	{ +	_ASSERT(IsConnected()); +	if(!IsConnected()) +		return false; + +	// if we're already playing a sample, stop it first +	if(IsPlayingSample()) +		CurrentSample = NULL; +	// if we're already playing a square wave at this freq and volume, return +	else if(IsPlayingAudio() && (Internal.Speaker.Freq   == freq) && +								(Internal.Speaker.Volume == volume)) +		return true; + +	TRACE(_T("playing square wave.")); +	// stop playing samples +	CurrentSample = 0; + +	EnableSpeaker(true); +	MuteSpeaker  (true); + +#if 0 +	// combined everything into one write - much faster, seems to work? +	BYTE bytes[9] = { 0x00, 0x00, 0x00, freq, volume, 0x00, 0x00, 0x01, 0x1 }; +	WriteData(0x04a20001, sizeof(bytes), bytes); +#else +	// write 0x01 to register 0xa20009  +	WriteData(0x04a20009, 0x01); +	// write 0x08 to register 0xa20001  +	WriteData(0x04a20001, 0x08); +	// write default sound mode (4bit ADPCM, we assume) 7-byte configuration +	//  to registers 0xa20001-0xa20008  +	BYTE bytes[7] = { 0x00, 0x00, 0x00, 10+(BYTE)freq, volume, 0x00, 0x00 }; +	WriteData(0x04a20001, sizeof(bytes), bytes); +	// write 0x01 to register 0xa20008  +	WriteData(0x04a20008, 0x01); +#endif + +	Internal.Speaker.Freq   = freq; +	Internal.Speaker.Volume = volume; + +	MuteSpeaker(false); +	return StartSampleThread(); +	} +// ------------------------------------------------------------------------------------ +void wiimote::RecordState (state_history	  &events_out, +						   unsigned			  max_time_ms, +						   state_change_flags change_trigger) +	{ +	// user being naughty? +	if(Recording.bEnabled) +		StopRecording(); + +	// clear the list +	if(!events_out.empty()) +		events_out.clear(); + +	// start recording  +	Recording.StateHistory = &events_out; +	Recording.StartTimeMS  = timeGetTime(); +	Recording.EndTimeMS    = Recording.StartTimeMS + max_time_ms; +	Recording.TriggerFlags = change_trigger; +	// as this call happens outside the read/parse thread, set the boolean +	//  which will enable reocrding last, so that all params are in place. +	// TODO: * stricly speaking this only works on VC2005+ or better, as it +	//		   automatically places a memory barrier on volatile variables - earlier/ +	//         other compilers may reorder the assignments!). * +	Recording.bEnabled	   = true; +	} +// ------------------------------------------------------------------------------------ +void wiimote::StopRecording () +	{ +	if(!Recording.bEnabled) +		return; + +	Recording.bEnabled = false; +	// make sure the read/parse thread has time to notice the change (else it might +	//  still write one more state to the list) +	Sleep(10); // too much? +	} +// ------------------------------------------------------------------------------------ +// ------------------------------------------------------------------------------------ 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 <windows.h> +#include <tchar.h>		// auto Unicode/Ansi support +#include <queue>		// for HID write method +#include <list>			// for state recording +  +#ifndef QWORD + typedef unsigned __int64 QWORD; +#endif + +#ifdef _MSC_VER			   // VC-specific: _DEBUG build only _ASSERT() sanity checks +# include <crtdbg.h> +#elif defined(__MINGW32__) // define NDEBUG to disable assert +# include <assert.h> +# 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_event> 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<BYTE*> 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<<index)) != 0); } +		} LED; + +	 BYTE BatteryRaw;		// 0 - ~200 (it seems 200 *may* be the maximum charge) +	 BYTE BatteryPercent;	// (using the above assumption, where 200 raw = 100%) +	 bool bBatteryDrained;	// battery is nearly flat +	 bool bRumble; +	 bool bExtension;		// an extension (eg. Nunchuk) is connected. + +	// speaker state: +	struct speaker +		{ +		 bool		  bEnabled; +		 bool		  bMuted; +		 speaker_freq Freq; +		 BYTE		  Volume; +		} Speaker; + +	// the extension plugged into the Wiimote (if any) +	enum extension_type +		{ +		NONE = 0, +		NUNCHUK, +		CLASSIC, +		GH3_GHWT_GUITAR, // GH3 or GHWT Guitar (treated as Classic) +		GHWT_DRUMS,		 // not yet used +		BALANCE_BOARD, +		MOTION_PLUS, +		PARTIALLY_INSERTED, +		}; +	 extension_type	ExtensionType; + +	// joystick struct (shared between Nunchuk & Classic Controller) +	struct joystick +		{ +		friend class wiimote; +		 +		// raw unprocessed coordinates: +		float RawX, RawY; + +		// processed coordinates in approximately -1 - +1 range (includes calibration +		//  data and deadzones) - note that due to calibration inaccuracies, the +		//  extremes may be slightly over/under (+-)1.0. +		float X, Y; + +		// a 'deadzone' is a user-defined range near the joystick center which +		//  is treated as zero (joysticks often drift a little even at the center +		//  position).  you can set a deadzone for each axis at any time, range is +		//  0.0 (off) to 1.0 (entire range - not useful :). try 0.03. +		struct deadzone +			{ +			 float X, Y; +			} DeadZone; +		}; + +	// Nunchuk state (if connected) +	struct nunchuk +		{ +		struct calibration_info +			{ +			 BYTE X0, Y0, Z0; +			 BYTE XG, YG, ZG; +			 BYTE MinX, MidX, MaxX; +			 BYTE MinY, MidY, MaxY; +			} CalibrationInfo; + +		acceleration  Acceleration; +		joystick	  Joystick; +		 bool C; +		 bool Z; +		} Nunchuk; + +	// Classic Controller state (if connected) +	struct classic_controller +		{ +		// calibration information (stored on the controller) +		struct calibration_info +			{ +			 BYTE MinXL, MidXL, MaxXL; +			 BYTE MinYL, MidYL, MaxYL; +			 BYTE MinXR, MidXR, MaxXR; +			 BYTE MinYR, MidYR, MaxYR; +			 BYTE MinTriggerL, MaxTriggerL; +			 BYTE MinTriggerR, MaxTriggerR; +			} 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 Minus		() const { return (Bits & MINUS)  != 0; } +			inline  bool Home		() const { return (Bits & HOME)   != 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; } +			inline  bool X			() const { return (Bits & _X)	  != 0; } +			inline  bool Y			() const { return (Bits & _Y)	  != 0; } +			inline  bool ZL			() const { return (Bits & _ZL)	  != 0; } +			inline  bool ZR			() const { return (Bits & _ZR)	  != 0; } +			inline  bool TriggerL	() const { return (Bits & TRIG_L) != 0; } +			inline  bool TriggerR	() const { return (Bits & TRIG_R) != 0; } +			 +			// all 15 buttons stored as bits (set = pressed) +			 WORD Bits; + +			// button bitmasks (little-endian order) +			enum mask +				{ +				TRIG_R  = 0x0002, +				PLUS    = 0x0004, +				HOME    = 0x0008, +				MINUS   = 0x0010, +				TRIG_L  = 0x0020, +				DOWN    = 0x0040, +				RIGHT   = 0x0080, +				UP      = 0x0100, +				LEFT    = 0x0200, +				_ZR     = 0x0400, +				_X      = 0x0800, +				_A      = 0x1000, +				_Y      = 0x2000, +				_B      = 0x4000, +				_ZL     = 0x8000, +				// +				ALL		= TRIG_R|PLUS|HOME|MINUS|TRIG_L|DOWN|RIGHT|UP|LEFT| +						  _ZR|_X|_A|_Y|_B|_ZL, +				}; +			} Button; + +		// joysticks +		joystick JoystickL; +		joystick JoystickR; + +		// triggers +		BYTE  RawTriggerL, RawTriggerR; +		float TriggerL, TriggerR; +		} ClassicController; + +	struct balance_board +		{ +		// values for each of the board's 4 sensors: +		//  (these values are always exposed unmodifed) +		struct sensors_raw +			{ +			short	 TopR; +			short	 TopL; +			short BottomR; +			short BottomL; +			}; +		struct sensors_f +			{ +			float	 TopL; +			float	 TopR; +			float BottomL; +			float BottomR; +			 +			float Total; // sum of the 4 corner weights +			}; + +		// calibration info +		struct calibration_info +			{ +			sensors_raw Kg0;	// calibration at  0 Kg +			sensors_raw Kg17;	// "			  17 Kg +			sensors_raw Kg34;	// "			  34 Kg +			} CalibrationInfo; + +		// state: +		sensors_raw	Raw;	  // raw values (per sensor) +		sensors_f	AtRestKg; // set by Connect() and CalibrateAtRest() +		//  (the values below have their 'at-rest' offsets automatically removed) +		sensors_f	Kg;		  // kilograms  (per sensor) +		sensors_f	Lb;		  // pounds	  (per sensor) +		} BalanceBoard; + +	struct motion_plus +		{ +		//  (these values are always exposed unmodifed) +		struct sensors_raw +			{ +			short Yaw; +			short Pitch; +			short Roll; +			}; +		struct sensors_f +			{ +			float Yaw; +			float Pitch; +			float Roll; +			}; + +		// state: +		sensors_raw	Raw; +		sensors_f	Speed; +		} MotionPlus; + +	// ---- internal use only ---- +	protected:  +		 unsigned WiimoteNearGUpdates; +		 unsigned NunchukNearGUpdates; + +		void Clear (bool including_deadzones) +			{ +			joystick::deadzone nunchuk_deadzone, +							   classic_joyl_deadzone, +							   classic_joyr_deadzone; +		 +			// preserve the deadzone settings? +			if(!including_deadzones) { +				nunchuk_deadzone	  = Nunchuk.Joystick.DeadZone; +				classic_joyl_deadzone = ClassicController.JoystickL.DeadZone; +				classic_joyr_deadzone = ClassicController.JoystickR.DeadZone; +				} + +			memset(this, 0, sizeof(wiimote_state)); + +			// restore the deadzones? +			if(!including_deadzones) { +				Nunchuk.Joystick.DeadZone			 = nunchuk_deadzone; +				ClassicController.JoystickL.DeadZone = classic_joyl_deadzone; +				ClassicController.JoystickR.DeadZone = classic_joyr_deadzone; +				} +			} +	}; + +#endif // _WIIMOTE_STATE_H
\ No newline at end of file | 
