Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Something is wrong with the feeder #10

Open
FahrulID opened this issue Nov 12, 2021 · 11 comments
Open

Something is wrong with the feeder #10

FahrulID opened this issue Nov 12, 2021 · 11 comments

Comments

@FahrulID
Copy link

So, I've been working on making a virtual gamepad, but when I'm making it, there are some issues I've found on the making

  1. When creating the controller, the axis value will not be exactly zero
    image

  2. I Believe the left analog axis is error, when it's the first value that changed and the analog is in the first quadrant ( 0 to 90 degrees ) and the third quadrant ( 180 - 270 degrees ). The right analog is just fine. It will look like this
    image

Note: I've tried another tester. I've verified my input value. And etc. And in conclusion, I thought it's probably in the library itself.

The Code ( Bear with my code, lol ) :
`const feeder = require('./vigem');
var controllers = {};

const express = require('express');
const app = express();
const http = require('http').createServer(app);
const io = require('socket.io')(http);
const port = process.env.PORT || 7200;

const buttons = {
BOTTOM_BUTTON : "A",
RIGHT_BUTTON : "B",
TOP_BUTTON : "Y",
LEFT_BUTTON : "X",
RIGHT_SHOULDER : "RIGHT_SHOULDER",
LEFT_SHOULDER : "LEFT_SHOULDER",
START : "START",
BACK : "BACK"
}

const axes = {
ANALOG_LEFT : "left",
ANALOG_RIGHT: "right"
}

const shoulder = {
LEFT_TRIGGER : "left",
RIGHT_TRIGGER: "right"
}

// Set root to xbox layout
app.get('/', (req, res) => {
return res.sendFile(__dirname + "/static/s-new.html");
});

// Set /static as root so the assets and stuffs can be loaded
app.use('/', express.static(__dirname + '/static'));

// Socket.io
io.on('connection', (socket) => {
let notificationCallback = function(data)
{
if(data)
{
io.to(socket.id).emit("vibration", true);
} else {
io.to(socket.id).emit("vibration", false);
}
}
controllers[socket.id] = new feeder("x360", notificationCallback);
let client = controllers[socket.id];

// console.log(`a user connected with socketID of ${socket.id}`);

// // Sending to all client
// socket.emit("message", "can you hear me?");

// // Sending to specific client
// io.to(socket.id).emit("message", "I just met you");

socket.on('disconnect', () => {
    console.log('user disconnected');
    client.disconnect();
});

socket.on('message', (tes) => {
    switch(tes.inputType)
    {
        case "axis":
            if(tes.axis == "ANALOG_LEFT" || tes.axis == "ANALOG_RIGHT")
            {
                tes.x = client.clampIt(tes.x, tes.r)
                tes.y = client.clampIt(tes.y, tes.r)
                console.log(tes.x, tes.y)
                client.xbox360SetAxisValue(`${axes[tes.axis]}X`, tes.x );
                client.xbox360SetAxisValue(`${axes[tes.axis]}Y`, tes.y );
                client.update();
            }
            break;
        case "triggerAxis":
            if(tes.axis == "LEFT_TRIGGER" || tes.axis == "RIGHT_TRIGGER")
            {
                tes.value = tes.value/tes.max;
                client.xbox360SetAxisValue(`${shoulder[tes.axis]}Trigger`, tes.value );
                client.update();
            }
            break;
        case "button":
            client.xbox360SetButtonValue(buttons[tes.button], !!tes.v);
            break;
    }
    //console.log(`${JSON.stringify(tes)}`);
});

});

http.listen(port, () => {
console.log(Listenting on ${port});
});`

`const ViGEmClient = require('vigemclient');

class feeder {
constructor(controller, notificationCallback)
{
switch(controller) {
case "x360":
this._controllerName = "x360";
this.xbox360feeder(notificationCallback);
// this.xbox360reset();
break;
case "ds4":
this._controllerName = "ds4";
this.ds4feeder(notificationCallback);
break;
default:
this._controllerName = "x360";
this.xbox360feeder(notificationCallback);
}
}

connectClient()
{
    this.client = new ViGEmClient();
    let err = this.client.connect(); // establish connection to the ViGEmBus driver
    return err;
}

connectController(controllerName)
{
    switch(controllerName) {
        case "x360":
            this.controller = this.client.createX360Controller(); //Spawn x360 virtual controller
            break;
        case "ds4":
            this.controller = this.client.createDS4Controller(); //Spawn ds4 virtual controller
            break;
        default:
            this.controller = this.client.createX360Controller(); //Spawn x360 virtual controller
    }
    let err = this.controller.connect();
    return err;
}

outputIDs(controllerName)
{
    console.log("Vendor ID:", this.controller.vendorID);
    console.log("Product ID:", this.controller.productID);
    console.log("Index:", this.controller.index);
    console.log("Type:", this.controller.type);
    // if(controllerName == "x360")
    // {
    //     console.log("User index:", this.controller.userIndex);
    // }
}

clamp(value, min, max) {
    return Math.min(max, Math.max(min, value));
}

update()
{
    this.controller.update(); // update manually for better performance
}

disconnect()
{
    let err = this.controller.disconnect(); // update manually for better performance
    return err;
}

convertAngleToAxis(a, s) // To convert Angle and Strength of a circle to X and Y of square
{
    let x = (s/100)*Math.cos(a*Math.PI/180);
    let y = (s/100)*Math.sin(a*Math.PI/180);
    let xSquare = 0.5*Math.sqrt(2+2*Math.sqrt(2)*x+Math.pow(x,2)-Math.pow(y,2)) - 0.5*Math.sqrt(2-2*Math.sqrt(2)*x+Math.pow(x,2)-Math.pow(y,2)); // x = ½ √( 2 + 2u√2 + u² - v² ) - ½ √( 2 - 2u√2 + u² - v² )
    let ySquare = 0.5*Math.sqrt(2+2*Math.sqrt(2)*y-Math.pow(x,2)+Math.pow(y,2)) - 0.5*Math.sqrt(2-2*Math.sqrt(2)*y-Math.pow(x,2)+Math.pow(y,2)); // y = ½ √( 2 + 2v√2 - u² + v² ) - ½ √( 2 - 2v√2 - u² + v² )
    if(isNaN(xSquare) && isNaN(ySquare)) {
        if(x>=0)
        {
            xSquare = 1;
        } else {
            xSquare = -1;
        }
        if(y>=0)
        {
            ySquare = 1;
        } else {
            ySquare = -1;
        }
    }
    return {x: xSquare, y: ySquare};
}

convertCircleCoordToSquareCoord(u, v)
{
    u = u / 35 * 1; // 35 = radius of the joystick // u,v element u^2 + v^2 <= 1
    v = -v / 35 * 1; // 35 = radius of the joystick // u,v element u^2 + v^2 <= 1 // The y is inverted, from the client so we need to invert it here
    let xSquare = 0.5*Math.sqrt(2+2*Math.sqrt(2)*u+Math.pow(u,2)-Math.pow(v,2)) - 0.5*Math.sqrt(2-2*Math.sqrt(2)*u+Math.pow(u,2)-Math.pow(v,2)); // x = ½ √( 2 + 2u√2 + u² - v² ) - ½ √( 2 - 2u√2 + u² - v² )
    let ySquare = 0.5*Math.sqrt(2+2*Math.sqrt(2)*v-Math.pow(u,2)+Math.pow(v,2)) - 0.5*Math.sqrt(2-2*Math.sqrt(2)*v-Math.pow(u,2)+Math.pow(v,2)); // y = ½ √( 2 + 2v√2 - u² + v² ) - ½ √( 2 - 2v√2 - u² + v² )
    if(isNaN(xSquare) && isNaN(ySquare)) {
        if(u>=0)
        {
            xSquare = 1;
        } else {
            xSquare = -1;
        }
        if(v>=0)
        {
            ySquare = 1;
        } else {
            ySquare = -1;
        }
    }
    return {x: xSquare, y: ySquare};
}

clampIt(value, max)
{
    return value/max;
}

//--------------------------------------------------[ XBOX 360 Feeder ]--------------------------------------------------//

xbox360reset()
{
    let value = 0;
    this.controller.button.X.setValue(value); // press X button
    this.controller.button.Y.setValue(value); // press Y button
    this.controller.button.A.setValue(value); // press A button
    this.controller.button.B.setValue(value); // press B button
    //     this.controller.button.DPAD_UP.setValue(value); // press DPAD_UP button
    //     this.controller.button.DPAD_DOWN.setValue(value); // press DPAD_DOWN button
    //     this.controller.button.DPAD_LEFT.setValue(value); // press DPAD_LEFT button
    //     this.controller.button.DPAD_RIGHT.setValue(value); // press DPAD_RIGHT button
    this.controller.button.START.setValue(value); // press START button
    this.controller.button.BACK.setValue(value); // press BACK button
    this.controller.button.LEFT_THUMB.setValue(value); // press LEFT_THUMB button
    this.controller.button.RIGHT_THUMB.setValue(value); // press RIGHT_THUMB button
    this.controller.button.LEFT_SHOULDER.setValue(value); // press LEFT_SHOULDER button
    this.controller.button.RIGHT_SHOULDER.setValue(value); // press RIGHT_SHOULDER button
    this.controller.button.GUIDE.setValue(value); // press GUIDE button
    this.controller.axis.leftX.setValue(value); // Left analog X
    this.controller.axis.leftY.setValue(value); // Left analog Y
    this.controller.axis.leftTrigger.setValue(value); // Left Trigger
    this.controller.axis.rightX.setValue(value); // Right analog X
    this.controller.axis.rightY.setValue(value); // Right analog Y
    this.controller.axis.rightTrigger.setValue(value); // Right Trigger
    this.controller.axis.dpadHorz.setValue(value); // Horizontal DPAD
    this.controller.axis.dpadVert.setValue(value); // Horizontal DPAD
}

xbox360feeder(notificationCallback)
{
    let connectClient = this.connectClient(); // Connecting to ViGEmBus driver
    if(connectClient == null) // Checking if connectclient returning null which what we wanted
    {
        let errConnectController = this.connectController(this._controllerName); // Connecting to virtual controller
        if (errConnectController) // Checking if errconnectcontroller returning true which is error happenned
        {
            console.log(errConnectController.message); // Outputting the error message
            process.exit(1);
        }

        this.outputIDs(this._controllerName); // Outputting vendor id and stuffs

        // Controller notification
        this.controller.on("notification", data => {
            console.log("notification", data);
            // if(data.LargeMotor > 0 || data.SmallMotor > 0)
            // {
            //     notificationCallback(true);
            // } else {
            //     notificationCallback(false);
            // }
        });

        // this.controller.updateMode = "manual"; // requires manual calls to controller.update()

        let tes = 0;
        let testing = setInterval(function()
        { 
            if(tes%2 == 0)
            {
                notificationCallback(true);
            } else {
                notificationCallback(false);
            }

            if(tes == 3){
                clearInterval(testing);
            }
            tes++;
        }, 10000);
    }
}

xbox360SetButtonValue(buttonName, value)
{
    let err = null;

    if(value !== true && value !== false)
    {
        err = true;
    }

    if(!err)
    {
        switch(buttonName) {
            case "X":
                this.controller.button.X.setValue(value); // press X button
                break;
            case "Y":
                this.controller.button.Y.setValue(value); // press Y button
                break;
            case "A":
                this.controller.button.A.setValue(value); // press A button
                break;
            case "B":
                this.controller.button.B.setValue(value); // press B button
                break;
            // case "DPAD_UP":
            //     this.controller.button.DPAD_UP.setValue(value); // press DPAD_UP button
            //     break;
            // case "DPAD_DOWN":
            //     this.controller.button.DPAD_DOWN.setValue(value); // press DPAD_DOWN button
            //     break;
            // case "DPAD_LEFT":
            //     this.controller.button.DPAD_LEFT.setValue(value); // press DPAD_LEFT button
            //     break;
            // case "DPAD_RIGHT":
            //     this.controller.button.DPAD_RIGHT.setValue(value); // press DPAD_RIGHT button
            //     break;
            case "START":
                this.controller.button.START.setValue(value); // press START button
                break;
            case "BACK":
                this.controller.button.BACK.setValue(value); // press BACK button
                break;
            case "LEFT_THUMB":
                this.controller.button.LEFT_THUMB.setValue(value); // press LEFT_THUMB button
                break;
            case "RIGHT_THUMB":
                this.controller.button.RIGHT_THUMB.setValue(value); // press RIGHT_THUMB button
                break;
            case "LEFT_SHOULDER":
                this.controller.button.LEFT_SHOULDER.setValue(value); // press LEFT_SHOULDER button
                break;
            case "RIGHT_SHOULDER":
                this.controller.button.RIGHT_SHOULDER.setValue(value); // press RIGHT_SHOULDER button
                break;
            case "GUIDE":
                this.controller.button.GUIDE.setValue(value); // press GUIDE button
                break;
            default:
                err = false;
        }
    }

    if(this.controller.updateMode == "manual")
    {
        this.update();
    }
    return err;
}

xbox360ButtonToggle(index)
{
    let err = null;
    let buttons = Object.keys(this.controller.button);

    if(index > buttons.length)
    {
        err = true;
    }

    if(!err)
    {
        this.controller.button[buttons[index]].setValue(!this.controller.button[buttons[index]].value); // invert button value
    }

    this.update();
    return err;
}

xbox360SetAxisValue(axisName, value) 
{
    let err = null;

    if((value == null) && (typeof(value) == "number"))
    {
        err = true;
    }

    if(!err)
    {


        switch(axisName)
        {
            case "leftX":
                this.controller.axis.leftX.setValue(value); // Left analog X
                break;
            case "leftY":
                this.controller.axis.leftY.setValue(value); // Left analog Y
                break;
            case "leftTrigger":
                this.controller.axis.leftTrigger.setValue(value); // Left Trigger
                break;
            case "rightX":
                this.controller.axis.rightX.setValue(value); // Right analog X
                break;
            case "rightY":
                this.controller.axis.rightY.setValue(value); // Right analog Y
                break;
            case "rightTrigger":
                this.controller.axis.rightTrigger.setValue(value); // Right Trigger
                break;
            case "dpadHorz":
                this.controller.axis.dpadHorz.setValue(value); // Horizontal DPAD
                break;
            case "dpadVert":
                this.controller.axis.dpadVert.setValue(value); // Horizontal DPAD
                break;
            default:
                err = false;
        }
    }

    if(this.controller.updateMode == "manual")
    {
        this.update();
    }
    return err;
}

//--------------------------------------------------[ XBOX 360 Feeder ]--------------------------------------------------//

}

module.exports = feeder;`

@jangxx
Copy link
Owner

jangxx commented Nov 13, 2021

I didn't read through all of your code just yet, but let me try to understand what your problem is. You're creating a new feeder and are then reading the values of the axis and are then not getting all 0's for every value? That is really strange and should not happen, I'll try to reproduce your issue and then get back to you.

@FahrulID
Copy link
Author

In summary :

  1. When the controller first get created, the value of the left axis and right axis doesn't exactly zero ( Drifting )
  2. When i move the left axis for the first time ( first buttons to be moved, the error only occur when the analog is between 0-90 degrees or 180-270 degrees ), even though i have fed the right value, the x axis will always be zero. Until either i moved out of the angle or i reuse the button. It only occur when the first time load the controller.

@jangxx
Copy link
Owner

jangxx commented Nov 15, 2021

I tried reproducing your issues, and while I did find out some interesting things, I unfortunately couldn't reproduce them.

For your first issue I created a minimal example:

const ViGEmClient = require('vigemclient');

const client = new ViGEmClient();

const connErr = client.connect()

if (connErr !== null) {
	console.log("Error while connecting to the ViGEmBus driver:", connErr.message);
	process.exit(1);
}

const controller = client.createX360Controller();

let err = controller.connect();

if (err) {
	console.log("Error while connecting to the virtual controller:", err.message);
	process.exit(1);
}

controller.update(); // send an update to the driver for good measure

console.log(controller.axis.leftX.value, 
	controller.axis.leftY.value, 
	controller.axis.rightX.value, 
	controller.axis.rightY.value);

and as expected the output was 0 0 0 0. Everything else wouldn't have made any sense anyway, since those getters only ever display values from within JavaScript, so it's literally impossible for them to drift or contain values that you didn't set yourself.

For the second issue I'd be interested to hear how you actually tested that. If you simply set the value immediately as you start your application and then open the "Game Controllers" thing that Windows provides it will indeed not show anything, since that tool only shows changes that occurred after the menu was opened. If you instead set a timeout and then open the menu before the report was sent to the driver, you will actually see that first update as well.

@kyleryanbanks
Copy link

kyleryanbanks commented May 8, 2022

I think I'm seeing a similar issue.

I'm trying to make an electron app that serves an angular web page that controls a programmable controller. I'm a big fighting game nerd, so I'm using this to create a game overlay that allows you to set up input sequences while playing any fighting game (most games have disappointing training mode options related to playback/record).

I'm mostly focused on just manipulating the reports wButtons value and that seems to be working correctly, but it seems like any update to the controller causes the axes to shift to the top-left.

On app init, I instantiate but don't connect. After calling connect, I see the controller register with window and the inputs seem valid and correct.

When I click my 'neutral' button. It sets wButtons and wSpecial to no inputs and no directions and calls update() on the controller. After this action, it loses all of the axes information and you see the axes input shift to the top right. The controller report still shows the correct values, so it seems like it's some issue with the interface to the raw vigemclient code.

I've just been using the raw windows controller tool, which is definitely pretty limited.

https://github.com/kyleryanbanks/game-ng12

All of the code is public here.

If you pull the repo > 'npm i' > 'nx serve' (wait until finished) > open new terminal 'nx serve roost' should run the app.

Click connect, open windows controller view > open controller > click neutral > see axes update.

It usually happens after the first time, but while writing this it took 2 clicks on 'neutral' at one point to get it to happen.

setNeutral() {
this.controller._report.reportObj.wButtons = 8;
this.controller._report.reportObj.bSpecial = 0;
this.controller.update();
console.log(this.report);
}

after connect - no axes
image

after neutral - top-right
image

Depending on what screen has focus you may or may not see the affect of the axes. I noticed that the axes were off after trying to open other windows apps while connected to vigem.

You can see in both cases that the controller report doesn't have any readings. This seems like it would be a pretty major issue if the root vigemclient did this after every update, so I'm hoping maybe the node interface just isn't passing all of the data correctly or something.

Thanks

@jangxx
Copy link
Owner

jangxx commented May 8, 2022

Okay I think I found and fixed the issue. DS4 controllers have their analog stick values mapped from [-1,1] onto [0,255], which means that a "neutral" state is not actually 0 but 127. Unfortunately I overlooked this fact in the reset() method of the respective reports. This issue is now fixed and I can confirm that the analog sticks are no longer moved in the upper left corner.

One thing that I see in your code that I would definitely recommend against however is accessing or mutating any members which start with an underscore (like _report). These members are not "public" and can change at any time between updates, even patch-level updates. The controller has a built-in reset() method if you want to reset it properly ;)

One more thing: 1.4.0 also adds a reset() method to the InputAxis. The intended way to reset only parts of the report is now to use InputButton.setValue(false) and InputAxis.reset() in a loop over the respective objects (ideally while temporarily setting the updateMode to manual).

@kyleryanbanks
Copy link

kyleryanbanks commented May 8, 2022

Eventually, the plan is to have this as a transparent stream overlay and electron's already a bit heavy on resources so I'm worried about performance later on.

I want my eventual feed loop to be as thin as possible so setting the button value directly means I'm just saving and iterating over a list of numbers instead of a list of report objects and then having to build the value manually on every loop at run time. It's something like 3 functions calls per loop vs breaking apart and iterating over an object and doing up to 15 function calls per loop?

I'd be happy to get off private variables if there were an official way to set buttons and special directly instead of being forced to go through the input/axis classes?

Also, wButton = 0 gets set during reset(), but that sets the dpad to up. I'm manually setting to 8 to return the controller to a neutral state.

I don't know if the direction map is coming from you or root vigem, but I would definitely use reset() directly if it put the controller back to neutral.

Can we tweak the direction map to have 0 be no direction or reset to set wButtons to 8?

@kyleryanbanks
Copy link

https://www.loom.com/share/33052a8568724353892f8e9df1be4f2c

Here's a short demo of where I stopped last night if you'd like to see it running.

Eventually I'll make some UI elements for the list so it's a bit more user friendly, but I'm trying to force myself to not get caught up on look and feel before functionality is in place.

@jangxx
Copy link
Owner

jangxx commented May 8, 2022

Also, wButton = 0 gets set during reset(), but that sets the dpad to up. I'm manually setting to 8 to return the controller to a neutral state.

Another thing I overlooked, fixed in 1.4.1, thanks.

I don't know if the direction map is coming from you or root vigem, but I would definitely use reset() directly if it put the controller back to neutral.

The direction map was written by me and, like you said, there was a bug in it. It should of course set the dpad to its neutral state as well.

I'd be happy to get off private variables if there were an official way to set buttons and special directly instead of being forced to go through the input/axis classes?

Well, the way to set them directly is to go through the classes. The idea of this library was to abstract the report away completely so that you can focus on the actual axes and buttons without having to worry about bitmasks or the weird differences between the X360 and DS4 reports (axes always go from -1 to 1 or 0 to 1 for example instead of their native ranges).

Also I'm wondering why the performance of resetting the controller to neutral is even of that big an importance to you anyway. Usually that is not something you have to do, ever, or at the very least not very often. The way this library is intended to be used is to just update the specific buttons and axes that you want to change and then setting them to not pressed or neutral afterwards. And since you're only ever touching the axes and buttons you are actually changing for the next update, you're not iterating over all of them.

@kyleryanbanks
Copy link

I'm focused on creating a UI that allows users to program controller sequences rather than individual button presses that supports 60 fps playback.

I'm essentially precompiling the button presses so that I don't have to do any of that calculation during the playback loop.

How do you suggest capturing the user inputs in a way that doesn't require me to unpack the object and call the button inputs on every frame? I want the playback loop to be as concise as possible.

Your API is great for live control, but I think it adds in overhead if you're trying to stream sequences.

@jangxx
Copy link
Owner

jangxx commented May 9, 2022

Yea, you are not wrong. Maybe I could expose the report objects (so you can instantiate them yourself) and then provide a sendReport method, so that you could just prepare/precompile a list of reports and then send them straight to the driver one after another with almost no overhead.

@kyleryanbanks
Copy link

kyleryanbanks commented May 9, 2022

The vast majority of my use case is probably focused around wButtons, with maybe some rare complex cases that need special or left axis.

I think still having a few options to access more low level API only makes the library more robust so it doesn't seem like there's much downside.

While I don't love the thought of carrying around lists of objects where I mainly only use one property, it would be nice to have a single call that sets the report and updates immediately. I only have wButtons wired up at the moment so it's only the one set and update, but I assume the eventual loop is set buttons, special, and maybe axis and then update, so sendReport definitely makes things a bit cleaner there.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants