mirror of https://github.com/adamdruppe/arsd.git
1155 lines
34 KiB
D
1155 lines
34 KiB
D
/++
|
|
|
|
Provides a polling-based API to use gamepads/joysticks on Linux and Windows.
|
|
|
|
Pass `-version=ps1_style` or `-version=xbox_style` to pick your API style - the constants will use the names of the buttons on those controllers and attempt to emulate the other. ps1_style is compatible with more hardware and thus the default. XBox controllers work with either, though.
|
|
|
|
The docs for this file are quite weak, I suggest you view source of [arsd.game] for an example of how it might be used.
|
|
|
|
FIXME: on Linux, certain controller brands will not be recognized and you need to set the mappings yourself, e.g., `version(linux) joystickMapping[0] = &xbox360Mapping;`. I will formalize this into a proper api later.
|
|
+/
|
|
|
|
/+
|
|
XBox360 DDR pad layout:
|
|
|
|
Back Start
|
|
B Up A
|
|
Left Right
|
|
Y Down X
|
|
|
|
XBox360 Butons:
|
|
|
|
Y
|
|
X B
|
|
A
|
|
+/
|
|
|
|
/*
|
|
FIXME: a simple function to integrate with sdpy event loop. templated function
|
|
|
|
HIGH LEVEL NOTES
|
|
|
|
This will offer a pollable state of two styles of controller: a PS1 or an XBox 360.
|
|
|
|
|
|
Actually, maybe I'll combine the two controller types. Make L2 and R2 just digital aliases
|
|
for the triggers, which are analog aliases for it.
|
|
|
|
Then have a virtual left stick which has the dpad aliases, while keeping the other two independent
|
|
(physical dpad and physical left stick).
|
|
|
|
Everything else should basically just work. We'll simply be left with naming and I can do them with
|
|
aliases too.
|
|
|
|
|
|
I do NOT bother with pressure sensitive other buttons, though Xbox original and PS2 had them, they
|
|
have been removed from the newer models. It makes things simpler anyway since we can check "was just
|
|
pressed" instead of all deltas.
|
|
|
|
|
|
The PS1 controller style works for a lot of games:
|
|
* The D-pad is an alias for the left stick. Analog input still works too.
|
|
* L2 and R2 are given as buttons
|
|
* The keyboard works as buttons
|
|
* The mouse is an alias for the right stick
|
|
* Buttons are given as labeled on a playstation controller
|
|
|
|
The XBox controller style works if you need full, modern features:
|
|
* The left stick and D-pad works independently of one another
|
|
the d pad works as additional buttons.
|
|
* The triggers work as independent analog inputs
|
|
note that the WinMM driver doesn't support full independence
|
|
since it is sent as a z-axis. Linux and modern Windows does though.
|
|
* Buttons are labeled as they are on the XBox controller
|
|
* The rumble motors are available, if the underlying driver supports it and noop if not.
|
|
* Audio I/O is available, if the underlying driver supports it. NOT IMPLEMENTED.
|
|
|
|
You chose which one you want at compile time with a -version=xbox_style or -version=ps1_style switch.
|
|
The default is ps1_style which works with xbox controllers too, it just simplifies them.
|
|
|
|
TODO:
|
|
handling keyboard+mouse input as joystick aliases
|
|
remapping support
|
|
network transparent joysticks for at least the basic stuff.
|
|
|
|
=================================
|
|
|
|
LOW LEVEL NOTES
|
|
|
|
On Linux, I'll just use /dev/input/js*. It is easy and works with everything I care about. It can fire
|
|
events to arsd.eventloop and also maintains state internally for polling. You do have to let it get
|
|
events though to handle that input - either doing your own select (etc.) on the js file descriptor,
|
|
or running the event loop (which is what I recommend).
|
|
|
|
On Windows, I'll support the mmsystem messages as far as I can, and XInput for more capabilities
|
|
of the XBox 360 controller. (The mmsystem should support my old PS1 controller and xbox is the
|
|
other one I have. I have PS3 controllers too which would be nice but since they require additional
|
|
drivers, meh.)
|
|
|
|
linux notes:
|
|
all basic input is available, no audio (I think), no force feedback (I think)
|
|
|
|
winmm notes:
|
|
the xbox 360 controller basically works and sends events to the window for the buttons,
|
|
left stick, and triggers. It doesn't send events for the right stick or dpad, but these
|
|
are available through joyGetPosEx (the dpad is the POV hat and the right stick is
|
|
the other axes).
|
|
|
|
The triggers are considered a z-axis with the left one going negative and right going positive.
|
|
|
|
windows xinput notes:
|
|
all xbox 360 controller features are available via a polling api.
|
|
|
|
it doesn't seem to support events. That's OK for games generally though, because we just
|
|
want to check state on each loop.
|
|
|
|
For non-games however, using the traditional message loop is probably easier.
|
|
|
|
XInput is only supported on newer operating systems (Vista I think),
|
|
so I'm going to dynamically load it all and fallback on the old one if
|
|
it fails.
|
|
|
|
|
|
|
|
Other fancy joysticks work low level on linux at least but the high level api reduces them to boredom but like
|
|
hey the events are still there and it still basically works, you'd just have to give a custom mapping.
|
|
*/
|
|
module arsd.joystick;
|
|
|
|
// --------------------------------
|
|
// High level interface
|
|
// --------------------------------
|
|
|
|
version(xbox_style) {
|
|
version(ps1_style)
|
|
static assert(0, "Pass only one xbox_style OR ps1_style");
|
|
} else
|
|
version=ps1_style; // default is PS1 style as it is a lower common denominator
|
|
|
|
version(xbox_style) {
|
|
alias Axis = XBox360Axes;
|
|
alias Button = XBox360Buttons;
|
|
} else version(ps1_style) {
|
|
alias Axis = PS1AnalogAxes;
|
|
alias Button = PS1Buttons;
|
|
}
|
|
|
|
|
|
version(Windows) {
|
|
WindowsXInput wxi;
|
|
}
|
|
|
|
version(OSX) {
|
|
struct JoystickState {}
|
|
}
|
|
|
|
JoystickState[4] joystickState;
|
|
|
|
version(linux) {
|
|
int[4] joystickFds = -1;
|
|
|
|
|
|
// On Linux, we have to track state ourselves since we only get events from the OS
|
|
struct JoystickState {
|
|
short[8] axes;
|
|
ubyte[16] buttons;
|
|
}
|
|
|
|
const(JoystickMapping)*[4] joystickMapping;
|
|
|
|
struct JoystickMapping {
|
|
// maps virtual buttons to real buttons, etc.
|
|
int[__traits(allMembers, Axis).length] axisOffsets = -1;
|
|
int[__traits(allMembers, Button).length] buttonOffsets = -1;
|
|
}
|
|
|
|
/// If you have a real xbox 360 controller, use this mapping
|
|
version(xbox_style) // xbox style maps directly to an xbox controller (of course)
|
|
static immutable xbox360Mapping = JoystickMapping(
|
|
[0,1,2,3,4,5,6,7],
|
|
[0,1,2,3,4,5,6,7,8,9,10, 11,12,13,14]
|
|
);
|
|
else version(ps1_style)
|
|
static immutable xbox360Mapping = JoystickMapping(
|
|
// PS1AnalogAxes index to XBox360Axes values
|
|
[XBox360Axes.horizontalLeftStick,
|
|
XBox360Axes.verticalLeftStick,
|
|
XBox360Axes.verticalRightStick,
|
|
XBox360Axes.horizontalRightStick,
|
|
XBox360Axes.horizontalDpad,
|
|
XBox360Axes.verticalDpad],
|
|
// PS1Buttons index to XBox360Buttons values
|
|
[XBox360Buttons.y, XBox360Buttons.b, XBox360Buttons.a, XBox360Buttons.x,
|
|
cast(XBox360Buttons) -1, cast(XBox360Buttons) -1, // L2 and R2 don't map easily
|
|
XBox360Buttons.lb, XBox360Buttons.rb,
|
|
XBox360Buttons.back, XBox360Buttons.start,
|
|
XBox360Buttons.leftStick, XBox360Buttons.rightStick]
|
|
);
|
|
|
|
|
|
/// For a real ps1 controller
|
|
version(ps1_style)
|
|
static immutable ps1Mapping = JoystickMapping(
|
|
[0,1,2,3,4,5],
|
|
[0,1,2,3,4,5,6,7,8,9,10,11]
|
|
|
|
);
|
|
else version(xbox_style)
|
|
static immutable ps1Mapping = JoystickMapping(
|
|
// FIXME... if we're going to support this at all
|
|
// I think if I were to write a program using the xbox style,
|
|
// I'd just use my xbox controller.
|
|
);
|
|
|
|
/// For Linux only, reads the latest joystick events into the change buffer, if available.
|
|
/// It is non-blocking
|
|
void readJoystickEvents(int fd) {
|
|
js_event event;
|
|
|
|
while(true) {
|
|
auto r = read(fd, &event, event.sizeof);
|
|
if(r == -1) {
|
|
import core.stdc.errno;
|
|
if(errno == EAGAIN || errno == EWOULDBLOCK)
|
|
break;
|
|
else assert(0); // , to!string(fd) ~ " " ~ to!string(errno));
|
|
}
|
|
if(r != event.sizeof)
|
|
throw new Exception("Read something weird off the joystick event fd");
|
|
//import std.stdio; writeln(event);
|
|
|
|
ptrdiff_t player = -1;
|
|
foreach(i, f; joystickFds)
|
|
if(f == fd) {
|
|
player = i;
|
|
break;
|
|
}
|
|
|
|
assert(player >= 0 && player < joystickState.length);
|
|
|
|
if(event.type & JS_EVENT_AXIS) {
|
|
joystickState[player].axes[event.number] = event.value;
|
|
|
|
if(event.type & JS_EVENT_INIT) {
|
|
if(event.number == 5) {
|
|
// After being initialized, if axes[6] == 32767, it seems to be my PS1 controller
|
|
// If axes[5] is -32767, it might be an Xbox controller.
|
|
|
|
if(event.value == -32767 && joystickMapping[player] is null) {
|
|
joystickMapping[player] = &xbox360Mapping;
|
|
}
|
|
} else if(event.number == 6) {
|
|
if((event.value == 32767 || event.value == -32767) && joystickMapping[player] is null) {
|
|
joystickMapping[player] = &ps1Mapping;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if(event.type & JS_EVENT_BUTTON) {
|
|
joystickState[player].buttons[event.number] = event.value ? 255 : 0;
|
|
//writeln(player, " ", event.number, " ", event.value, " ", joystickState[player].buttons[event.number]);//, " != ", event.value ? 255 : 0);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
version(Windows) {
|
|
extern(Windows)
|
|
DWORD function(DWORD, XINPUT_STATE*) getJoystickOSState;
|
|
|
|
extern(Windows)
|
|
DWORD winMMFallback(DWORD id, XINPUT_STATE* state) {
|
|
JOYINFOEX info;
|
|
auto result = joyGetPosEx(id, &info);
|
|
if(result == 0) {
|
|
// FIXME
|
|
|
|
}
|
|
return result;
|
|
}
|
|
|
|
alias JoystickState = XINPUT_STATE;
|
|
}
|
|
|
|
/// Returns the number of players actually connected
|
|
///
|
|
/// The controller ID
|
|
int enableJoystickInput(
|
|
int player1ControllerId = 0,
|
|
int player2ControllerId = 1,
|
|
int player3ControllerId = 2,
|
|
int player4ControllerId = 3)
|
|
{
|
|
version(linux) {
|
|
bool preparePlayer(int player, int id) {
|
|
if(id < 0)
|
|
return false;
|
|
|
|
assert(player >= 0 && player < joystickFds.length);
|
|
assert(id < 10);
|
|
assert(id >= 0);
|
|
char[] filename = "/dev/input/js0\0".dup;
|
|
filename[$-2] = cast(char) (id + '0');
|
|
|
|
int fd = open(filename.ptr, O_RDONLY);
|
|
if(fd > 0) {
|
|
joystickFds[player] = fd;
|
|
|
|
version(with_eventloop) {
|
|
import arsd.eventloop;
|
|
makeNonBlocking(fd);
|
|
addFileEventListeners(fd, &readJoystickEvents, null, null);
|
|
} else {
|
|
// for polling, we will set nonblocking mode anyway,
|
|
// the readJoystickEvents function will handle this fine
|
|
// so we can call it when needed even on like a game timer.
|
|
auto flags = fcntl(fd, F_GETFL, 0);
|
|
if(flags == -1)
|
|
throw new Exception("fcntl get");
|
|
flags |= O_NONBLOCK;
|
|
auto s = fcntl(fd, F_SETFL, flags);
|
|
if(s == -1)
|
|
throw new Exception("fcntl set");
|
|
}
|
|
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
if(!preparePlayer(0, player1ControllerId) ? 1 : 0)
|
|
return 0;
|
|
if(!preparePlayer(1, player2ControllerId) ? 1 : 0)
|
|
return 1;
|
|
if(!preparePlayer(2, player3ControllerId) ? 1 : 0)
|
|
return 2;
|
|
if(!preparePlayer(3, player4ControllerId) ? 1 : 0)
|
|
return 3;
|
|
return 4; // all players successfully initialized
|
|
} else version(Windows) {
|
|
if(wxi.loadDll()) {
|
|
getJoystickOSState = wxi.XInputGetState;
|
|
} else {
|
|
// WinMM fallback
|
|
getJoystickOSState = &winMMFallback;
|
|
}
|
|
|
|
assert(getJoystickOSState !is null);
|
|
|
|
if(getJoystickOSState(player1ControllerId, &(joystickState[0])))
|
|
return 0;
|
|
if(getJoystickOSState(player2ControllerId, &(joystickState[1])))
|
|
return 1;
|
|
if(getJoystickOSState(player3ControllerId, &(joystickState[2])))
|
|
return 2;
|
|
if(getJoystickOSState(player4ControllerId, &(joystickState[3])))
|
|
return 3;
|
|
|
|
return 4;
|
|
} else static assert(0, "Unsupported OS");
|
|
|
|
// return 0;
|
|
}
|
|
|
|
///
|
|
void closeJoysticks() {
|
|
version(linux) {
|
|
foreach(ref fd; joystickFds) {
|
|
if(fd > 0) {
|
|
version(with_eventloop) {
|
|
import arsd.eventloop;
|
|
removeFileEventListeners(fd);
|
|
}
|
|
close(fd);
|
|
}
|
|
fd = -1;
|
|
}
|
|
} else version(Windows) {
|
|
getJoystickOSState = null;
|
|
wxi.unloadDll();
|
|
} else static assert(0);
|
|
}
|
|
|
|
///
|
|
struct JoystickUpdate {
|
|
///
|
|
int player;
|
|
|
|
JoystickState old;
|
|
JoystickState current;
|
|
|
|
/// changes from last update
|
|
bool buttonWasJustPressed(Button button) {
|
|
return buttonIsPressed(button) && !oldButtonIsPressed(button);
|
|
}
|
|
|
|
/// ditto
|
|
bool buttonWasJustReleased(Button button) {
|
|
return !buttonIsPressed(button) && oldButtonIsPressed(button);
|
|
}
|
|
|
|
/// this is normalized down to a 16 step change
|
|
/// and ignores a dead zone near the middle
|
|
short axisChange(Axis axis) {
|
|
return cast(short) (axisPosition(axis) - oldAxisPosition(axis));
|
|
}
|
|
|
|
/// current state
|
|
bool buttonIsPressed(Button button) {
|
|
return buttonIsPressedHelper(button, ¤t);
|
|
}
|
|
|
|
/// Note: UP is negative!
|
|
/// Value will actually be -16 to 16 ish.
|
|
short axisPosition(Axis axis, short digitalFallbackValue = short.max) {
|
|
return axisPositionHelper(axis, ¤t, digitalFallbackValue);
|
|
}
|
|
|
|
/* private */
|
|
|
|
// old state
|
|
bool oldButtonIsPressed(Button button) {
|
|
return buttonIsPressedHelper(button, &old);
|
|
}
|
|
|
|
short oldAxisPosition(Axis axis, short digitalFallbackValue = short.max) {
|
|
return axisPositionHelper(axis, &old, digitalFallbackValue);
|
|
}
|
|
|
|
short axisPositionHelper(Axis axis, JoystickState* what, short digitalFallbackValue = short.max) {
|
|
version(ps1_style) {
|
|
// on PS1, the d-pad and left stick are synonyms for each other
|
|
// the dpad takes precedence, if it is pressed
|
|
|
|
if(axis == PS1AnalogAxes.horizontalDpad || axis == PS1AnalogAxes.horizontalLeftStick) {
|
|
auto it = axisPositionHelperRaw(PS1AnalogAxes.horizontalDpad, what, digitalFallbackValue);
|
|
if(!it)
|
|
it = axisPositionHelperRaw(PS1AnalogAxes.horizontalLeftStick, what, digitalFallbackValue);
|
|
version(linux)
|
|
if(!it)
|
|
it = current.buttons[XBox360Buttons.dpadLeft] ? cast(short)-cast(int)digitalFallbackValue : current.buttons[XBox360Buttons.dpadRight] ? digitalFallbackValue : 0;
|
|
return it;
|
|
}
|
|
|
|
if(axis == PS1AnalogAxes.verticalDpad || axis == PS1AnalogAxes.verticalLeftStick) {
|
|
auto it = axisPositionHelperRaw(PS1AnalogAxes.verticalDpad, what, digitalFallbackValue);
|
|
if(!it)
|
|
it = axisPositionHelperRaw(PS1AnalogAxes.verticalLeftStick, what, digitalFallbackValue);
|
|
version(linux)
|
|
if(!it)
|
|
it = current.buttons[XBox360Buttons.dpadUp] ? cast(short)-cast(int)digitalFallbackValue : current.buttons[XBox360Buttons.dpadDown] ? digitalFallbackValue : 0;
|
|
return it;
|
|
}
|
|
}
|
|
|
|
return axisPositionHelperRaw(axis, what, digitalFallbackValue);
|
|
}
|
|
|
|
static short normalizeAxis(short value) {
|
|
/+
|
|
auto v = normalizeAxisHack(value);
|
|
import std.stdio;
|
|
writeln(value, " :: ", v);
|
|
return v;
|
|
}
|
|
static short normalizeAxisHack(short value) {
|
|
+/
|
|
if(value > -1600 && value < 1600)
|
|
return 0; // the deadzone gives too much useless junk
|
|
return cast(short) (value >>> 11);
|
|
}
|
|
|
|
bool buttonIsPressedHelper(Button button, JoystickState* what) {
|
|
version(linux) {
|
|
int mapping = -1;
|
|
if(auto ptr = joystickMapping[player])
|
|
mapping = ptr.buttonOffsets[button];
|
|
if(mapping != -1)
|
|
return what.buttons[mapping] ? true : false;
|
|
// otherwise what do we do?
|
|
// FIXME
|
|
return false; // the button isn't mapped, figure it isn't there and thus can't be pushed
|
|
} else version(Windows) {
|
|
// on Windows, I'm always assuming it is an XBox 360 controller
|
|
// because that's what I have and the OS supports it so well
|
|
version(xbox_style)
|
|
final switch(button) {
|
|
case XBox360Buttons.a: return (what.Gamepad.wButtons & XINPUT_GAMEPAD_A) ? true : false;
|
|
case XBox360Buttons.b: return (what.Gamepad.wButtons & XINPUT_GAMEPAD_B) ? true : false;
|
|
case XBox360Buttons.x: return (what.Gamepad.wButtons & XINPUT_GAMEPAD_X) ? true : false;
|
|
case XBox360Buttons.y: return (what.Gamepad.wButtons & XINPUT_GAMEPAD_Y) ? true : false;
|
|
|
|
case XBox360Buttons.lb: return (what.Gamepad.wButtons & XINPUT_GAMEPAD_LEFT_SHOULDER) ? true : false;
|
|
case XBox360Buttons.rb: return (what.Gamepad.wButtons & XINPUT_GAMEPAD_RIGHT_SHOULDER) ? true : false;
|
|
|
|
case XBox360Buttons.back: return (what.Gamepad.wButtons & XINPUT_GAMEPAD_BACK) ? true : false;
|
|
case XBox360Buttons.start: return (what.Gamepad.wButtons & XINPUT_GAMEPAD_START) ? true : false;
|
|
|
|
case XBox360Buttons.leftStick: return (what.Gamepad.wButtons & XINPUT_GAMEPAD_LEFT_THUMB) ? true : false;
|
|
case XBox360Buttons.rightStick: return (what.Gamepad.wButtons & XINPUT_GAMEPAD_RIGHT_THUMB) ? true : false;
|
|
|
|
case XBox360Buttons.dpadLeft: return (what.Gamepad.wButtons & XINPUT_GAMEPAD_DPAD_LEFT) ? true : false;
|
|
case XBox360Buttons.dpadRight: return (what.Gamepad.wButtons & XINPUT_GAMEPAD_DPAD_RIGHT) ? true : false;
|
|
case XBox360Buttons.dpadUp: return (what.Gamepad.wButtons & XINPUT_GAMEPAD_DPAD_UP) ? true : false;
|
|
case XBox360Buttons.dpadDown: return (what.Gamepad.wButtons & XINPUT_GAMEPAD_DPAD_DOWN) ? true : false;
|
|
|
|
case XBox360Buttons.xboxLogo: return false;
|
|
}
|
|
else version(ps1_style)
|
|
final switch(button) {
|
|
case PS1Buttons.triangle: return (what.Gamepad.wButtons & XINPUT_GAMEPAD_Y) ? true : false;
|
|
case PS1Buttons.square: return (what.Gamepad.wButtons & XINPUT_GAMEPAD_X) ? true : false;
|
|
case PS1Buttons.cross: return (what.Gamepad.wButtons & XINPUT_GAMEPAD_A) ? true : false;
|
|
case PS1Buttons.circle: return (what.Gamepad.wButtons & XINPUT_GAMEPAD_B) ? true : false;
|
|
|
|
case PS1Buttons.select: return (what.Gamepad.wButtons & XINPUT_GAMEPAD_BACK) ? true : false;
|
|
case PS1Buttons.start: return (what.Gamepad.wButtons & XINPUT_GAMEPAD_START) ? true : false;
|
|
|
|
case PS1Buttons.l1: return (what.Gamepad.wButtons & XINPUT_GAMEPAD_LEFT_SHOULDER) ? true : false;
|
|
case PS1Buttons.r1: return (what.Gamepad.wButtons & XINPUT_GAMEPAD_RIGHT_SHOULDER) ? true : false;
|
|
|
|
case PS1Buttons.l2: return (what.Gamepad.bLeftTrigger > 100);
|
|
case PS1Buttons.r2: return (what.Gamepad.bRightTrigger > 100);
|
|
|
|
case PS1Buttons.l3: return (what.Gamepad.wButtons & XINPUT_GAMEPAD_LEFT_THUMB) ? true : false;
|
|
case PS1Buttons.r3: return (what.Gamepad.wButtons & XINPUT_GAMEPAD_RIGHT_THUMB) ? true : false;
|
|
}
|
|
}
|
|
}
|
|
|
|
short axisPositionHelperRaw(Axis axis, JoystickState* what, short digitalFallbackValue = short.max) {
|
|
version(linux) {
|
|
int mapping = -1;
|
|
if(auto ptr = joystickMapping[player])
|
|
mapping = ptr.axisOffsets[axis];
|
|
if(mapping != -1)
|
|
return normalizeAxis(what.axes[mapping]);
|
|
return 0; // no such axis apparently, let the cooked one do something if it can
|
|
} else version(Windows) {
|
|
// on Windows, assuming it is an XBox 360 controller
|
|
version(xbox_style)
|
|
final switch(axis) {
|
|
case XBox360Axes.horizontalLeftStick:
|
|
return normalizeAxis(what.Gamepad.sThumbLX);
|
|
case XBox360Axes.verticalLeftStick:
|
|
return normalizeAxis(what.Gamepad.sThumbLY);
|
|
case XBox360Axes.horizontalRightStick:
|
|
return normalizeAxis(what.Gamepad.sThumbRX);
|
|
case XBox360Axes.verticalRightStick:
|
|
return normalizeAxis(what.Gamepad.sThumbRY);
|
|
case XBox360Axes.verticalDpad:
|
|
return (what.Gamepad.wButtons & XINPUT_GAMEPAD_DPAD_UP) ? cast(short) -digitalFallbackValue :
|
|
(what.Gamepad.wButtons & XINPUT_GAMEPAD_DPAD_DOWN) ? cast(short) digitalFallbackValue :
|
|
0;
|
|
case XBox360Axes.horizontalDpad:
|
|
return (what.Gamepad.wButtons & XINPUT_GAMEPAD_DPAD_LEFT) ? cast(short) -digitalFallbackValue :
|
|
(what.Gamepad.wButtons & XINPUT_GAMEPAD_DPAD_RIGHT) ? cast(short) digitalFallbackValue :
|
|
0;
|
|
case XBox360Axes.lt:
|
|
return normalizeTrigger(what.Gamepad.bLeftTrigger);
|
|
case XBox360Axes.rt:
|
|
return normalizeTrigger(what.Gamepad.bRightTrigger);
|
|
}
|
|
else version(ps1_style)
|
|
final switch(axis) {
|
|
case PS1AnalogAxes.horizontalDpad:
|
|
case PS1AnalogAxes.horizontalLeftStick:
|
|
short got = (what.Gamepad.wButtons & XINPUT_GAMEPAD_DPAD_LEFT) ? cast(short)-cast(int)digitalFallbackValue :
|
|
(what.Gamepad.wButtons & XINPUT_GAMEPAD_DPAD_RIGHT) ? digitalFallbackValue :
|
|
0;
|
|
if(got == 0)
|
|
got = what.Gamepad.sThumbLX;
|
|
|
|
return normalizeAxis(got);
|
|
case PS1AnalogAxes.verticalDpad:
|
|
case PS1AnalogAxes.verticalLeftStick:
|
|
short got = (what.Gamepad.wButtons & XINPUT_GAMEPAD_DPAD_UP) ? digitalFallbackValue :
|
|
(what.Gamepad.wButtons & XINPUT_GAMEPAD_DPAD_DOWN) ? cast(short)-cast(int)digitalFallbackValue :
|
|
what.Gamepad.sThumbLY;
|
|
|
|
if(got == short.min)
|
|
got++; // to avoid overflow on the axis inversion below
|
|
|
|
return normalizeAxis(cast(short)-cast(int)got);
|
|
case PS1AnalogAxes.horizontalRightStick:
|
|
return normalizeAxis(what.Gamepad.sThumbRX);
|
|
case PS1AnalogAxes.verticalRightStick:
|
|
return normalizeAxis(what.Gamepad.sThumbRY);
|
|
}
|
|
}
|
|
}
|
|
|
|
version(Windows)
|
|
short normalizeTrigger(BYTE b) {
|
|
if(b < XINPUT_GAMEPAD_TRIGGER_THRESHOLD)
|
|
return 0;
|
|
return cast(short)((b << 8)|0xff);
|
|
}
|
|
}
|
|
|
|
///
|
|
JoystickUpdate getJoystickUpdate(int player) {
|
|
static JoystickState[4] previous;
|
|
|
|
version(Windows) {
|
|
assert(getJoystickOSState !is null);
|
|
if(getJoystickOSState(player, &(joystickState[player])))
|
|
return JoystickUpdate();
|
|
//throw new Exception("wtf");
|
|
}
|
|
|
|
auto it = JoystickUpdate(player, previous[player], joystickState[player]);
|
|
|
|
previous[player] = joystickState[player];
|
|
|
|
return it;
|
|
}
|
|
|
|
// --------------------------------
|
|
// Low level interface
|
|
// --------------------------------
|
|
|
|
version(Windows) {
|
|
|
|
import core.sys.windows.windows;
|
|
|
|
alias MMRESULT = UINT;
|
|
|
|
struct JOYINFOEX {
|
|
DWORD dwSize;
|
|
DWORD dwFlags;
|
|
DWORD dwXpos;
|
|
DWORD dwYpos;
|
|
DWORD dwZpos;
|
|
DWORD dwRpos;
|
|
DWORD dwUpos;
|
|
DWORD dwVpos;
|
|
DWORD dwButtons;
|
|
DWORD dwButtonNumber;
|
|
DWORD dwPOV;
|
|
DWORD dwReserved1;
|
|
DWORD dwReserved2;
|
|
}
|
|
|
|
enum : DWORD {
|
|
JOY_POVCENTERED = -1,
|
|
JOY_POVFORWARD = 0,
|
|
JOY_POVBACKWARD = 18000,
|
|
JOY_POVLEFT = 27000,
|
|
JOY_POVRIGHT = 9000
|
|
}
|
|
|
|
extern(Windows)
|
|
MMRESULT joySetCapture(HWND window, UINT stickId, UINT period, BOOL changed);
|
|
|
|
extern(Windows)
|
|
MMRESULT joyGetPosEx(UINT stickId, JOYINFOEX* pji);
|
|
|
|
extern(Windows)
|
|
MMRESULT joyReleaseCapture(UINT stickId);
|
|
|
|
// SEE ALSO:
|
|
// http://msdn.microsoft.com/en-us/library/windows/desktop/dd757105%28v=vs.85%29.aspx
|
|
|
|
// Windows also provides joyGetThreshold, joySetThreshold
|
|
|
|
// there's also JOY2 messages
|
|
enum MM_JOY1MOVE = 0; // FIXME
|
|
enum MM_JOY1BUTTONDOWN = 0; // FIXME
|
|
enum MM_JOY1BUTTONUP = 0; // FIXME
|
|
|
|
pragma(lib, "winmm");
|
|
|
|
version(arsd_js_test)
|
|
void main() {
|
|
/*
|
|
// winmm test
|
|
auto window = new SimpleWindow(500, 500);
|
|
|
|
joySetCapture(window.impl.hwnd, 0, 0, false);
|
|
|
|
window.handleNativeEvent = (HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) {
|
|
import std.stdio;
|
|
writeln(msg, " ", wparam, " ", lparam);
|
|
return 1;
|
|
};
|
|
|
|
window.eventLoop(0);
|
|
|
|
joyReleaseCapture(0);
|
|
*/
|
|
|
|
import std.stdio;
|
|
|
|
// xinput test
|
|
|
|
WindowsXInput x;
|
|
if(!x.loadDll()) {
|
|
writeln("Load DLL failed");
|
|
return;
|
|
}
|
|
|
|
writeln("success");
|
|
|
|
assert(x.XInputSetState !is null);
|
|
assert(x.XInputGetState !is null);
|
|
|
|
XINPUT_STATE state;
|
|
|
|
XINPUT_VIBRATION vibration;
|
|
|
|
if(!x.XInputGetState(0, &state)) {
|
|
writeln("Player 1 detected");
|
|
} else return;
|
|
if(!x.XInputGetState(1, &state)) {
|
|
writeln("Player 2 detected");
|
|
} else writeln("Player 2 not found");
|
|
|
|
DWORD pn;
|
|
foreach(i; 0 .. 60) {
|
|
x.XInputGetState(0, &state);
|
|
if(pn != state.dwPacketNumber) {
|
|
writeln("c: ", state);
|
|
pn = state.dwPacketNumber;
|
|
}
|
|
Sleep(50);
|
|
if(i == 20) {
|
|
vibration.wLeftMotorSpeed = WORD.max;
|
|
vibration.wRightMotorSpeed = WORD.max;
|
|
x.XInputSetState(0, &vibration);
|
|
vibration = XINPUT_VIBRATION.init;
|
|
}
|
|
|
|
if(i == 40)
|
|
x.XInputSetState(0, &vibration);
|
|
}
|
|
}
|
|
|
|
struct XINPUT_GAMEPAD {
|
|
WORD wButtons;
|
|
BYTE bLeftTrigger;
|
|
BYTE bRightTrigger;
|
|
SHORT sThumbLX;
|
|
SHORT sThumbLY;
|
|
SHORT sThumbRX;
|
|
SHORT sThumbRY;
|
|
}
|
|
|
|
// enum XInputGamepadButtons {
|
|
// It is a bitmask of these
|
|
enum XINPUT_GAMEPAD_DPAD_UP = 0x0001;
|
|
enum XINPUT_GAMEPAD_DPAD_DOWN = 0x0002;
|
|
enum XINPUT_GAMEPAD_DPAD_LEFT = 0x0004;
|
|
enum XINPUT_GAMEPAD_DPAD_RIGHT = 0x0008;
|
|
enum XINPUT_GAMEPAD_START = 0x0010;
|
|
enum XINPUT_GAMEPAD_BACK = 0x0020;
|
|
enum XINPUT_GAMEPAD_LEFT_THUMB = 0x0040; // pushing on the stick
|
|
enum XINPUT_GAMEPAD_RIGHT_THUMB = 0x0080;
|
|
enum XINPUT_GAMEPAD_LEFT_SHOULDER = 0x0100;
|
|
enum XINPUT_GAMEPAD_RIGHT_SHOULDER = 0x0200;
|
|
enum XINPUT_GAMEPAD_A = 0x1000;
|
|
enum XINPUT_GAMEPAD_B = 0x2000;
|
|
enum XINPUT_GAMEPAD_X = 0x4000;
|
|
enum XINPUT_GAMEPAD_Y = 0x8000;
|
|
|
|
enum XINPUT_GAMEPAD_LEFT_THUMB_DEADZONE = 7849;
|
|
enum XINPUT_GAMEPAD_RIGHT_THUMB_DEADZONE = 8689;
|
|
enum XINPUT_GAMEPAD_TRIGGER_THRESHOLD = 30;
|
|
|
|
struct XINPUT_STATE {
|
|
DWORD dwPacketNumber;
|
|
XINPUT_GAMEPAD Gamepad;
|
|
}
|
|
|
|
struct XINPUT_VIBRATION {
|
|
WORD wLeftMotorSpeed; // low frequency motor. use any value between 0-65535 here
|
|
WORD wRightMotorSpeed; // high frequency motor. use any value between 0-65535 here
|
|
}
|
|
|
|
struct XINPUT_KEYSTROKE {
|
|
WORD VirtualKey;
|
|
WCHAR Unicode;
|
|
WORD Flags;
|
|
BYTE UserIndex;
|
|
BYTE HidCode;
|
|
}
|
|
|
|
enum XUSER_INDEX_ANY = 0xff;
|
|
|
|
enum VK_PAD_A = 0x5800;
|
|
enum VK_PAD_B = 0x5801;
|
|
enum VK_PAD_X = 0x5802;
|
|
enum VK_PAD_Y = 0x5803;
|
|
enum VK_PAD_RSHOULDER = 0x5804;
|
|
enum VK_PAD_LSHOULDER = 0x5805;
|
|
enum VK_PAD_LTRIGGER = 0x5806;
|
|
enum VK_PAD_RTRIGGER = 0x5807;
|
|
|
|
enum VK_PAD_DPAD_UP = 0x5810;
|
|
enum VK_PAD_DPAD_DOWN = 0x5811;
|
|
enum VK_PAD_DPAD_LEFT = 0x5812;
|
|
enum VK_PAD_DPAD_RIGHT = 0x5813;
|
|
enum VK_PAD_START = 0x5814;
|
|
enum VK_PAD_BACK = 0x5815;
|
|
enum VK_PAD_LTHUMB_PRESS = 0x5816;
|
|
enum VK_PAD_RTHUMB_PRESS = 0x5817;
|
|
|
|
enum VK_PAD_LTHUMB_UP = 0x5820;
|
|
enum VK_PAD_LTHUMB_DOWN = 0x5821;
|
|
enum VK_PAD_LTHUMB_RIGHT = 0x5822;
|
|
enum VK_PAD_LTHUMB_LEFT = 0x5823;
|
|
enum VK_PAD_LTHUMB_UPLEFT = 0x5824;
|
|
enum VK_PAD_LTHUMB_UPRIGHT = 0x5825;
|
|
enum VK_PAD_LTHUMB_DOWNRIGHT = 0x5826;
|
|
enum VK_PAD_LTHUMB_DOWNLEFT = 0x5827;
|
|
|
|
enum VK_PAD_RTHUMB_UP = 0x5830;
|
|
enum VK_PAD_RTHUMB_DOWN = 0x5831;
|
|
enum VK_PAD_RTHUMB_RIGHT = 0x5832;
|
|
enum VK_PAD_RTHUMB_LEFT = 0x5833;
|
|
enum VK_PAD_RTHUMB_UPLEFT = 0x5834;
|
|
enum VK_PAD_RTHUMB_UPRIGHT = 0x5835;
|
|
enum VK_PAD_RTHUMB_DOWNRIGHT = 0x5836;
|
|
enum VK_PAD_RTHUMB_DOWNLEFT = 0x5837;
|
|
|
|
enum XINPUT_KEYSTROKE_KEYDOWN = 0x0001;
|
|
enum XINPUT_KEYSTROKE_KEYUP = 0x0002;
|
|
enum XINPUT_KEYSTROKE_REPEAT = 0x0004;
|
|
|
|
|
|
|
|
struct WindowsXInput {
|
|
HANDLE dll;
|
|
bool loadDll() {
|
|
// try Windows 8 first
|
|
dll = LoadLibraryA("Xinput1_4.dll");
|
|
if(dll is null) // then try Windows Vista
|
|
dll = LoadLibraryA("Xinput9_1_0.dll");
|
|
|
|
if(dll is null)
|
|
return false; // couldn't load it, tell user
|
|
|
|
XInputGetState = cast(typeof(XInputGetState)) GetProcAddress(dll, "XInputGetState");
|
|
XInputSetState = cast(typeof(XInputSetState)) GetProcAddress(dll, "XInputSetState");
|
|
|
|
XInputGetKeystroke = cast(typeof(XInputGetKeystroke)) GetProcAddress(dll, "XInputGetKeystroke");
|
|
|
|
return true;
|
|
}
|
|
|
|
~this() {
|
|
unloadDll();
|
|
}
|
|
|
|
void unloadDll() {
|
|
if(dll !is null) {
|
|
FreeLibrary(dll);
|
|
dll = null;
|
|
}
|
|
}
|
|
|
|
// These are all dynamically loaded from the DLL
|
|
extern(Windows) {
|
|
DWORD function(DWORD, XINPUT_STATE*) XInputGetState;
|
|
DWORD function(DWORD, XINPUT_VIBRATION*) XInputSetState;
|
|
DWORD function(DWORD, DWORD, XINPUT_KEYSTROKE*) XInputGetKeystroke;
|
|
}
|
|
|
|
// there's other functions but I don't use them; my controllers
|
|
// are corded, for example, and I don't have a headset that works
|
|
// with them. But if I get ones, I'll add them too.
|
|
//
|
|
// There's also some Windows 8 and up functions I didn't use, I just
|
|
// wanted the basics.
|
|
}
|
|
}
|
|
|
|
version(linux) {
|
|
|
|
// https://www.kernel.org/doc/Documentation/input/joystick-api.txt
|
|
struct js_event {
|
|
uint time;
|
|
short value;
|
|
ubyte type;
|
|
ubyte number;
|
|
}
|
|
|
|
enum JS_EVENT_BUTTON = 0x01;
|
|
enum JS_EVENT_AXIS = 0x02;
|
|
enum JS_EVENT_INIT = 0x80;
|
|
|
|
import core.sys.posix.unistd;
|
|
import core.sys.posix.fcntl;
|
|
|
|
import std.stdio;
|
|
|
|
struct RawControllerEvent {
|
|
int controller;
|
|
int type;
|
|
int number;
|
|
int value;
|
|
}
|
|
|
|
// These values are determined experimentally on my Linux box
|
|
// and won't necessarily match what you have. I really don't know.
|
|
// TODO: see if these line up on Windows
|
|
}
|
|
|
|
// My hardware:
|
|
// a Sony PS1 dual shock controller on a PSX to USB adapter from Radio Shack
|
|
// and a wired XBox 360 controller from Microsoft.
|
|
|
|
// FIXME: these are the values based on my linux box, but I also use them as the virtual codes
|
|
// I want nicer virtual codes I think.
|
|
|
|
enum PS1Buttons {
|
|
triangle = 0,
|
|
circle,
|
|
cross,
|
|
square,
|
|
l2,
|
|
r2,
|
|
l1,
|
|
r1,
|
|
select,
|
|
start,
|
|
l3,
|
|
r3
|
|
}
|
|
|
|
// Use if analog is turned off
|
|
// Tip: if you just check this OR the analog one it will work in both cases easily enough
|
|
enum PS1Axes {
|
|
horizontalDpad = 0,
|
|
verticalDpad = 1,
|
|
}
|
|
|
|
// Use if analog is turned on
|
|
enum PS1AnalogAxes {
|
|
horizontalLeftStick = 0,
|
|
verticalLeftStick,
|
|
verticalRightStick,
|
|
horizontalRightStick,
|
|
horizontalDpad,
|
|
verticalDpad,
|
|
}
|
|
|
|
|
|
enum XBox360Buttons {
|
|
a = 0,
|
|
b,
|
|
x,
|
|
y,
|
|
lb,
|
|
rb,
|
|
back,
|
|
start,
|
|
xboxLogo,
|
|
leftStick,
|
|
rightStick,
|
|
|
|
dpadLeft,
|
|
dpadRight,
|
|
dpadUp,
|
|
dpadDown,
|
|
}
|
|
|
|
enum XBox360Axes {
|
|
horizontalLeftStick = 0,
|
|
verticalLeftStick,
|
|
lt,
|
|
horizontalRightStick,
|
|
verticalRightStick,
|
|
rt,
|
|
horizontalDpad,
|
|
verticalDpad
|
|
}
|
|
|
|
version(linux) {
|
|
|
|
version(arsd_js_test)
|
|
void main(string[] args) {
|
|
int fd = open(args.length > 1 ? (args[1]~'\0').ptr : "/dev/input/js0".ptr, O_RDONLY);
|
|
assert(fd > 0);
|
|
js_event event;
|
|
|
|
short[8] axes;
|
|
ubyte[16] buttons;
|
|
|
|
printf("\n");
|
|
|
|
while(true) {
|
|
auto r = read(fd, &event, event.sizeof);
|
|
assert(r == event.sizeof);
|
|
|
|
// writef("\r%12s", event);
|
|
if(event.type & JS_EVENT_AXIS) {
|
|
axes[event.number] = event.value >> 12;
|
|
}
|
|
if(event.type & JS_EVENT_BUTTON) {
|
|
buttons[event.number] = cast(ubyte) event.value;
|
|
}
|
|
writef("\r%6s %1s", axes[0..8], buttons[0 .. 16]);
|
|
stdout.flush();
|
|
}
|
|
|
|
close(fd);
|
|
printf("\n");
|
|
}
|
|
|
|
version(joystick_demo)
|
|
version(linux)
|
|
void amain(string[] args) {
|
|
import arsd.simpleaudio;
|
|
|
|
AudioOutput audio = AudioOutput(0);
|
|
|
|
int fd = open(args.length > 1 ? (args[1]~'\0').ptr : "/dev/input/js1".ptr, O_RDONLY | O_NONBLOCK);
|
|
assert(fd > 0);
|
|
js_event event;
|
|
|
|
short[512] buffer;
|
|
|
|
short val = short.max / 4;
|
|
int swap = 44100 / 600;
|
|
int swapCount = swap / 2;
|
|
|
|
short val2 = short.max / 4;
|
|
int swap2 = 44100 / 600;
|
|
int swapCount2 = swap / 2;
|
|
|
|
short[8] axes;
|
|
ubyte[16] buttons;
|
|
|
|
while(true) {
|
|
int r = read(fd, &event, event.sizeof);
|
|
while(r >= 0) {
|
|
import std.conv;
|
|
assert(r == event.sizeof, to!string(r));
|
|
|
|
// writef("\r%12s", event);
|
|
if(event.type & JS_EVENT_AXIS) {
|
|
axes[event.number] = event.value; // >> 12;
|
|
}
|
|
if(event.type & JS_EVENT_BUTTON) {
|
|
buttons[event.number] = cast(ubyte) event.value;
|
|
}
|
|
|
|
|
|
int freq = axes[XBox360Axes.horizontalLeftStick];
|
|
freq += short.max;
|
|
freq /= 100;
|
|
freq += 400;
|
|
|
|
swap = 44100 / freq;
|
|
|
|
val = (cast(int) axes[XBox360Axes.lt] + short.max) / 8;
|
|
|
|
|
|
int freq2 = axes[XBox360Axes.horizontalRightStick];
|
|
freq2 += short.max;
|
|
freq2 /= 1000;
|
|
freq2 += 400;
|
|
|
|
swap2 = 44100 / freq2;
|
|
|
|
val2 = (cast(int) axes[XBox360Axes.rt] + short.max) / 8;
|
|
|
|
|
|
// try to starve the read
|
|
r = read(fd, &event, event.sizeof);
|
|
}
|
|
|
|
for(int i = 0; i < buffer.length / 2; i++) {
|
|
import std.math;
|
|
auto v = cast(ushort) (val * sin(cast(real) swapCount / (2*PI)));
|
|
auto v2 = cast(ushort) (val2 * sin(cast(real) swapCount2 / (2*PI)));
|
|
buffer[i*2] = cast(ushort)(v + v2);
|
|
buffer[i*2+1] = cast(ushort)(v + v2);
|
|
swapCount--;
|
|
swapCount2--;
|
|
if(swapCount == 0) {
|
|
swapCount = swap / 2;
|
|
// val = -val;
|
|
}
|
|
if(swapCount2 == 0) {
|
|
swapCount2 = swap2 / 2;
|
|
// val = -val;
|
|
}
|
|
}
|
|
|
|
|
|
//audio.write(buffer[]);
|
|
}
|
|
|
|
close(fd);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
version(joystick_demo)
|
|
version(Windows)
|
|
void amain() {
|
|
import arsd.simpleaudio;
|
|
auto midi = MidiOutput(0);
|
|
ubyte[16] buffer = void;
|
|
ubyte[] where = buffer[];
|
|
midi.writeRawMessageData(where.midiProgramChange(1, 79));
|
|
|
|
auto x = WindowsXInput();
|
|
x.loadDll();
|
|
|
|
XINPUT_STATE state;
|
|
XINPUT_STATE oldstate;
|
|
DWORD pn;
|
|
while(true) {
|
|
oldstate = state;
|
|
x.XInputGetState(0, &state);
|
|
byte note = 72;
|
|
if(state.dwPacketNumber != oldstate.dwPacketNumber) {
|
|
if((state.Gamepad.wButtons & XINPUT_GAMEPAD_A) && !(oldstate.Gamepad.wButtons & XINPUT_GAMEPAD_A))
|
|
midi.writeRawMessageData(where.midiNoteOn(1, note, 127));
|
|
if(!(state.Gamepad.wButtons & XINPUT_GAMEPAD_A) && (oldstate.Gamepad.wButtons & XINPUT_GAMEPAD_A))
|
|
midi.writeRawMessageData(where.midiNoteOff(1, note, 127));
|
|
|
|
note = 75;
|
|
|
|
if((state.Gamepad.wButtons & XINPUT_GAMEPAD_B) && !(oldstate.Gamepad.wButtons & XINPUT_GAMEPAD_B))
|
|
midi.writeRawMessageData(where.midiNoteOn(1, note, 127));
|
|
if(!(state.Gamepad.wButtons & XINPUT_GAMEPAD_B) && (oldstate.Gamepad.wButtons & XINPUT_GAMEPAD_B))
|
|
midi.writeRawMessageData(where.midiNoteOff(1, note, 127));
|
|
|
|
note = 77;
|
|
|
|
if((state.Gamepad.wButtons & XINPUT_GAMEPAD_X) && !(oldstate.Gamepad.wButtons & XINPUT_GAMEPAD_X))
|
|
midi.writeRawMessageData(where.midiNoteOn(1, note, 127));
|
|
if(!(state.Gamepad.wButtons & XINPUT_GAMEPAD_X) && (oldstate.Gamepad.wButtons & XINPUT_GAMEPAD_X))
|
|
midi.writeRawMessageData(where.midiNoteOff(1, note, 127));
|
|
|
|
note = 79;
|
|
if((state.Gamepad.wButtons & XINPUT_GAMEPAD_Y) && !(oldstate.Gamepad.wButtons & XINPUT_GAMEPAD_Y))
|
|
midi.writeRawMessageData(where.midiNoteOn(1, note, 127));
|
|
if(!(state.Gamepad.wButtons & XINPUT_GAMEPAD_Y) && (oldstate.Gamepad.wButtons & XINPUT_GAMEPAD_Y))
|
|
midi.writeRawMessageData(where.midiNoteOff(1, note, 127));
|
|
|
|
note = 81;
|
|
if((state.Gamepad.wButtons & XINPUT_GAMEPAD_LEFT_SHOULDER) && !(oldstate.Gamepad.wButtons & XINPUT_GAMEPAD_LEFT_SHOULDER))
|
|
midi.writeRawMessageData(where.midiNoteOn(1, note, 127));
|
|
if(!(state.Gamepad.wButtons & XINPUT_GAMEPAD_LEFT_SHOULDER) && (oldstate.Gamepad.wButtons & XINPUT_GAMEPAD_LEFT_SHOULDER))
|
|
midi.writeRawMessageData(where.midiNoteOff(1, note, 127));
|
|
|
|
note = 83;
|
|
if((state.Gamepad.wButtons & XINPUT_GAMEPAD_RIGHT_SHOULDER) && !(oldstate.Gamepad.wButtons & XINPUT_GAMEPAD_RIGHT_SHOULDER))
|
|
midi.writeRawMessageData(where.midiNoteOn(1, note, 127));
|
|
if(!(state.Gamepad.wButtons & XINPUT_GAMEPAD_RIGHT_SHOULDER) && (oldstate.Gamepad.wButtons & XINPUT_GAMEPAD_RIGHT_SHOULDER))
|
|
midi.writeRawMessageData(where.midiNoteOff(1, note, 127));
|
|
}
|
|
|
|
Sleep(1);
|
|
|
|
where = buffer[];
|
|
}
|
|
}
|
|
|