From fe685580e02100a49e3dd205dae61cc28d4d3487 Mon Sep 17 00:00:00 2001 From: "Adam D. Ruppe" Date: Sun, 4 Jan 2015 20:45:03 -0500 Subject: [PATCH] first draft of high level interface (not final) --- joystick.d | 680 ++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 668 insertions(+), 12 deletions(-) diff --git a/joystick.d b/joystick.d index 02decee..801df89 100644 --- a/joystick.d +++ b/joystick.d @@ -1,5 +1,41 @@ -/* - On Linux, I'll just use /dev/input/js*. It is easy and works with everything I care about. +/** + HIGH LEVEL NOTES + + This will offer a pollable state of two styles of controller: a PS1 or an XBox 360. + + 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 @@ -12,7 +48,7 @@ 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 joyGetPositionEx (the dpad is the POV hat and the right stick is + 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. @@ -29,6 +65,452 @@ so I'm going to dynamically load it all and fallback on the old one if it fails. */ +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; +} + +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] + ); + 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. + ); + + void readJoystickEvents(int fd) { + js_event event; + + while(true) { + int r = read(fd, &event, event.sizeof); + if(r == -1) { + import core.stdc.errno; + if(errno == EAGAIN || EWOULDBLOCK) + break; + else assert(0); + } + assert(r == event.sizeof); + + int 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 && joystickMapping[player] is null) { + joystickMapping[player] = &ps1Mapping; + } + } + } + } + if(event.type & JS_EVENT_BUTTON) { + 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) { + version(with_eventloop) { + import arsd.eventloop; + joystickFds[player] = fd; + makeNonBlocking(fd); + addFileEventListeners(fd, &readJoystickEvents, null, null); + } + + 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); + } + + 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! + short axisPosition(Axis axis) { + return axisPositionHelper(axis, ¤t); + } + + /* private */ + + // old state + bool oldButtonIsPressed(Button button) { + return buttonIsPressedHelper(button, &old); + } + + short oldAxisPosition(Axis axis) { + return axisPositionHelper(axis, &old); + } + + short axisPositionHelper(Axis axis, JoystickState* what) { + 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); + if(!it) + it = axisPositionHelperRaw(PS1AnalogAxes.horizontalLeftStick, what); + return it; + } + + if(axis == PS1AnalogAxes.verticalDpad || axis == PS1AnalogAxes.verticalLeftStick) { + auto it = axisPositionHelperRaw(PS1AnalogAxes.verticalDpad, what); + if(!it) + it = axisPositionHelperRaw(PS1AnalogAxes.verticalLeftStick, what); + return it; + } + } + + return axisPositionHelperRaw(axis, what); + } + + static short normalizeAxis(short value) { + if(value > -8000 && value < 8000) + return 0; // the deadzone gives too much useless junk + return value; + } + + 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.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) { + 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.sThumbRX); + case XBox360Axes.verticalDpad: + return (what.Gamepad.wButtons & XINPUT_GAMEPAD_DPAD_UP) ? short.min : + (what.Gamepad.wButtons & XINPUT_GAMEPAD_DPAD_DOWN) ? short.max : + 0; + case XBox360Axes.horizontalDpad: + return (what.Gamepad.wButtons & XINPUT_GAMEPAD_DPAD_LEFT) ? short.min : + (what.Gamepad.wButtons & XINPUT_GAMEPAD_DPAD_RIGHT) ? short.max : + 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_UP) ? short.min : + (what.Gamepad.wButtons & XINPUT_GAMEPAD_DPAD_DOWN) ? short.max : + 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_LEFT) ? short.min : + (what.Gamepad.wButtons & XINPUT_GAMEPAD_DPAD_RIGHT) ? short.max : + 0; + if(got == 0) + got = what.Gamepad.sThumbLY; + + return normalizeAxis(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]))) + throw new Exception("wtf"); + } + + auto it = JoystickUpdate(player, previous[player], joystickState[player]); + + previous[player] = joystickState[player]; + + return it; +} + +// -------------------------------- +// Low level interface +// -------------------------------- version(Windows) { @@ -81,8 +563,10 @@ version(Windows) { pragma(lib, "winmm"); + version(arsd_js_test) void main() { /* + // winmm test auto window = new SimpleWindow(500, 500); joySetCapture(window.impl.hwnd, 0, 0, false); @@ -100,7 +584,9 @@ version(Windows) { import std.stdio; - WindowXInput x; + // xinput test + + WindowsXInput x; if(!x.loadDll()) { writeln("Load DLL failed"); return; @@ -160,7 +646,7 @@ version(Windows) { enum XINPUT_GAMEPAD_DPAD_RIGHT = 0x0008; enum XINPUT_GAMEPAD_START = 0x0010; enum XINPUT_GAMEPAD_BACK = 0x0020; - enum XINPUT_GAMEPAD_LEFT_THUMB = 0x0040; + 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; @@ -169,17 +655,21 @@ version(Windows) { 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 - WORD wRightMotorSpeed; // high frequency motor + 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 WindowXInput { + struct WindowsXInput { HANDLE dll; bool loadDll() { // try Windows 8 first @@ -197,8 +687,14 @@ version(Windows) { } ~this() { - if(dll !is null) + unloadDll(); + } + + void unloadDll() { + if(dll !is null) { FreeLibrary(dll); + dll = null; + } } // These are all dynamically loaded from the DLL @@ -245,11 +741,15 @@ version(linux) { // 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, @@ -274,10 +774,10 @@ version(linux) { // Use if analog is turned on enum PS1AnalogAxes { - horiziontalLeftStick = 0, + horizontalLeftStick = 0, verticalLeftStick, verticalRightStick, - horiziontalRightStick, + horizontalRightStick, horizontalDpad, verticalDpad, } @@ -302,12 +802,15 @@ version(linux) { verticalLeftStick, lt, horizontalRightStick, - verticalLeftStick, + 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); @@ -336,4 +839,157 @@ version(linux) { close(fd); printf("\n"); } + + 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(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[]; + } + } +