MTCKC / ProconXInput

A Windows user-mode XInput driver for the Switch Pro Controller.
198 stars 29 forks source link

Rumble #1

Open CTCaer opened 7 years ago

CTCaer commented 7 years ago

Hi, Now that rumble data is unfolding bit by bit, you can start with these values for basic ramble:

- Small Motor only:  00 65 40 40 00 65 40 40  (320Hz 50%, 160Hz 0%)
- Big Motor only:    00 01 40 62 00 01 40 62  (320Hz 0%, 160Hz 50%)
- Both Motors:       00 65 40 62 00 65 40 62  (320Hz 50%, 160Hz 50%)
- Stop rumble:       00 01 40 40 00 01 40 40  (This stops the rumble right away)

I proposed it like this, because the High resonant frequency feels like a small motor and the low for a big motor. Also 50% (0.5f) is a nice enough intensity.

Try it and tell me your remarks.

MTCKC commented 7 years ago

Oh wow thanks, I'll check this out once I get calibration up and running for the alpha2 release. I heard the rumble data is encoded or checksummed somehow, is that true?

CTCaer commented 7 years ago

It's encoded. But not in an obfuscated way. It does strange things though. So we have 4 bytes for each actuator (left/right joy-con or left/right actuator in pro controller. If you don't want stereo effect both 4 bytes should be the same. Every 2 bytes you control different frequency band. The values are in little-endian so to explain I will have the following example with swapped bytes: For example through packet observation, I saw that high freq is from 00:04 to 01:FC (remember little-endian swapped, you send FC:01 to the controller). Added to this is intensity. But now that we changed frequency from default 320Hz (01 00), it's not +0x64, it's +0x88 for 0.5f.

Also for some reason the increments for high freq are +0x04. Which actually reduces the total intermediate values by four. The switch HID library does this. I want to try the intermediate values though.

Lastly, low band behaves differently. Follow up https://github.com/dekuNukem/Nintendo_Switch_Reverse_Engineering and check when I will update the pull request.

MTCKC commented 7 years ago

Fantastic, thanks for the details. I'll keep an eye out for the low band stuff.

CTCaer commented 7 years ago

Here's a small dummy test C++ code for High and Low Frequency Band testing:

uint16_t hf = 0x0000;
uint16_t hf_band = 0x0000;
uint16_t hf_intensity = 0x0000;
uint16_t lf = 0x0000;
uint16_t lf_intensity = 0x0000;
uint8_t buf[0x8];
int rumble_test_time=0;
uint8_t timer = 0x0;
//test HF Band frequencies
//H.Freq increments of 0x0004, for testing try increment with hf++. In my tests min was 0x0004, for testing try hf=0x00
for (hf=0x04; hf < 0x01FD; hf=hf+0x04)
{
    // HF intesity: increments of 0x0100, max 0xA400. ??0.5f: 0x6400 (freq is 320.0Hz) or 0x8800 (freq !320.0Hz)
    hf_intensity = 0x8800; //0.5f variable freq
    hf_band = hf + hf_intensity;
    buf[0] = buf[4] = hf_band & 0xFF;
    buf[1] = buf[5] = (hf_band >> 8) & 0xFF;

    buf[2] = buf[6] = 0x40; //160.0fHz
    buf[3] = buf[7] = 0x40; //0.0f

    rumble_test_time=0;
        //If you already have a timer set before remove the following 2 lines
        if (time_keeping==0x10)
                time_keeping = 0x0;

    printf("Sent HF: %02x %02x\n", buf[0], buf[1]);
    rumble_test_time++;
    usleep(500000); //Wait half second before changing frequency
    dummy_send_rumble_command(0x10, timer, buf, 0x8); //don't forget to increment the timer!!!
    timer++;
    if (timer==0x10)
        timer=0x0;
}

//test LF Band frequencies.
//L.Freq increments of 0x0001. In my tests min was 0x0001 and not 0x0000
for (uint16_t lf =0x00; lf < 0x7D; hf++)
{
    // LF intesity: increments of 0x0100, min 0x4000, max 0x7200. 0.5f: 0x6200. Needs testing if it changes when freq !=160.0Hz
    lf_intensity = 0x6200; //0.5f variable freq
    buf[0] = buf[4] = 0x40; //320.0fHz
    buf[1] = buf[5] = 0x40; //0.0f

    buf[2] = buf[6] = lf;
    buf[3] = buf[7] = lf_intensity;

    rumble_test_time=0;

        //If you already have a timer set before remove the following 2 lines
        if (time_keeping==0x10)
                time_keeping = 0x0;

    printf("Sent LF: %02x %02x\n", buf[2], buf[3]);
    rumble_test_time++;
    usleep(500000); //Wait half second before changing frequency
    dummy_send_rumble_command(0x10, timer, buf, 0x8); //don't forget to increment the timer!!!
    timer++;
    if (timer==0x10)
        timer=0x0;
}

As you can see, LF is more straight forward, but not thoroughly tested like HF. Wrote it as fast as I could, expect derps..

EDIT: Fix timekeeping Changed wait time. It seems that sending the same value produces a violent vibration.. Deleting while loop..

CTCaer commented 7 years ago

Made some change above btw. Already stared testing. It seams that there's a reason for skipping values in HF. Also the intensity needs to go down when you approach resonant values and you are changing frequencies. That's because the actuator vibrates more due to physics.

CTCaer commented 7 years ago

Part1:

High Band Frequency: Min: 0x0400 - Max: 0xfc01. Step: 0x0400 Byte swapped: Min: 0x0004 - Max: 0x01fc. Step: 0x0004

High Band Intensity: Min: 0x0000 - Max: 0x00FE. Step: 0x0002 | Safe max: 0x00C8 Byte swapped: Min: 0x0000 - Max: 0xFE00. Step: 0x0200 | Safe max: 0xC800

Only use safe maximum for intensity. This is defined by Nintendo to protect the linear actuator.

CTCaer commented 7 years ago

Part2: No encoding here. Each value is one byte.

Low Band Frequency: Min: 0x00 - Max: 0x7F. Step: 0x01 | Range 0x80 - 0xFF works the same with 0x00 - 0x7F Low Band Intensity: Min: 0x40 - Max: 0x7F. Step: 0x01 | Safe max: 0x72

CTCaer commented 7 years ago

Basically for High band, there's a bitwise encoding. We have B1 B2 B3 B4 Byte 1 is H.Freq Byte 2 is H.Intesity Byte 3 is L.Freq Byte 4 is L.Intesity

For High band only, H.Freq is 0x04 - 0xFC. This is 81.75Hz - 320Hz Lower High band To use the 320Hz - 1252.57 Higher high band you need to turn on the LSB on Byte 2.

The frequency range for Low freq band is 40.87Hz - 626.28Hz

Lastly, the frequency goes up Exponentially. Starts at ~1Hz steps and can reach 12-25Hz steps.

There are some more encoded values but there are not used currently in switch. So that's all you need.

CTCaer commented 7 years ago

One last thing I forgot to tell you, The HD rumble does not act like normal ones. Normally a game starts/stops with specific intervals to control the intensity of the rumble. Linear actuator though, does not continue to rumble because of force (no spin). It stops/starts instantly.

So maybe for later versions, you need to implement a normalizer, that adds an attack/decay to the Intesity (that takes into consideration the steps also!) when rumble starts /stops. This way, it will stop the legacy start/stop (which produces violent rumble on joycons).

MTCKC commented 7 years ago

Huh, so you need to continuously send the rumble commands... That makes sense, I never really considered the difference between how a linear actuator rumble would differ from the spinning-mass rumble, and would explain why my one attempt to get rumble working didn't produce any real results.

Man, all this information is incredible. Thank you so much. I feel bad that I'm focusing on other parts of the driver for now, but I'll definitely try to get this stuff in for beta.

CTCaer commented 7 years ago

Don't feel bad for doing what you want. Rumble was a big mystery but now you can give it a try when it's time.

P.S.: I found out that for normal amplitudes (less than 0.5f on resonant frequencies though), it gives a pleasant decay that you can take it into consideration in the future.

P.S.2: Offtopic: It seems, Joy-cons provide 3 HID drivers by getting the right command. Simple HID, NPad and XPad. XPad is made to be used with XInput.

I just updates the SPI structure and next are BT Commands. So keep an eye again if P.S.2 is going to be true.