Skip to content
/ MMVJ Public

Intuitive force feedback-enabled mouse steering and more: versatile mice and MIDI to virtual joysticks mapper with configurable transformation pipeline, for Linux.

License

Notifications You must be signed in to change notification settings

leosat/MMVJ

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

1 Commit
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

MMVJ - Mice and MIDI to Virtual Joysticks (Transforming) Mapper for Linux.

Skipto Features ...
Skipto Configuration ...
Skipto Build from source ...
Skipto Usage ...
Goto FAQ ...


NB 1: latest release, when built with "gui" feature (enabled by default), includes steering indicator window, enable it with

--enable-steering-indicator-gui

as well as steering telemetry and runtime settings tweaking (for basic parameters at the moment), both - including force feedback impact.

--enable-steering-telemetry-gui

The following image shows visual debugging of force feedback application in mouse to joystick steering transformation: blue line is user input pre-filtered, green - force feedback signal (it reacts to the yellow), yellow - resultant joystick axis position post filtering and force feedback application. In this case force feedback effect injected and seen playing in the picture - is the "spring" effect.

Spring Force Feedback effect plotted (green)

NB 2: Regarding steering emulation functionality in particular, force feedback works perfectly with "Richard Burns Rally" (tested the latest variations with NGP) and many other titles like "Euro truck sim", "Race Room", "Rush rally 3" which use Constant force effect to report already calculated forces to the steering wheel. Some of other effects like Spring (e.g. used in "Rfactor 1"), Friction and Ramp are also supported (including envelope (fade-in/fade-out) and delay/repeat, but not trigger (WIP)). Damper, Intertia and Periodic (waveforms generators) effects are not yet supported (WIP). Until then we just fake the support for all those "other" effects (with warning emitted) if user configures them as supported in config. Stay tuned. If seeing trouble with it in your particular case and wish to help, please run the program with --debug-ff flag and send me the the output.


Some early video demos of MMVJ being applied in raw rally simulation:


> if you wish to make a review, please make sure to see the development status notice and if having any troubles with the app, please contact the author. I'd be totally happy to get direct feedback <


Features.

  • Uses MIDI devices or mouse/mice and trackpads as inputs. Match multiple mouse and MIDI devices by name and map them to virtual joysticks (mice devices can be optionally "hidden"  from desktop usage by the xinput Linux command) [1].
  • Virtual joysticks creation and usage as output and auxiliary input (see "hold factor") devices.
  • Supports config validation and hot-reload on configuration file changes.
    • If renewed config has errors, reports the error and continues running with previous configuration.
    • Supports configurable joysticks persistence across engine online hot-restarts on configuration changes.
  • Mappings of many inputs to many outputs with ...
  • Out of the box advanced transformation pipline steps: curves, filters, intuitive steering emulation and more.
    • Combine those discrete steps arbitrarily to achieve desired effects.
    • A detail about steering transformation for use in simracing, flight and other simulator gaming:
      • Supports force feedback: accepts constant force and other effects (see the FAQ why this matters).
      • Supports configurable autocentering (if not force feedback available or as an auxiliary behavior).
      • Supports intuitive emulation of hands holding the steering wheel with different force  (a.k.a "hold factor")  
        affecting the two mentioned above.
  • Mouse and MIDI monitor and learn modes: to automatically discover input devices and [TODO] generate relevant YAML configuration.
  • Steering indicator window displaying current joystick axis position for a joystick being affected by steering mapping transformation.
  • Flexible telemetry graphing: graph values out of arbtrary transformation stages to visually debug effects of curves, filters, force feedback effects (WIP)
  • Input devices hot-plugging (TODO)
    • Reloads on devices plug/unplug events to match the renewed HW configuration without manual application restart requirement.

[1] One fancy example: using 2 mice devices, mapping one's X movement to steering, Y movement to "hold factor" and a button to handbreak, whereas Y movement of the second mouse mapped to two separate brake and throttle axis of a target virtual joystick (it's achievable by accumulating relative input and mapping upper subrange of the integrated value to throttle and lower subrange (inverted) to breaking (configuration examples using this technique are coming soon))


IMPORTANT #1: For force feedback to work under Wine

make sure you override joysticks to be DInput and not XInput in Wine control panel, because XInpit controls do not support this kind of FFB, which is specific to steering wheels and not gamepads.

Open Wine Control panel and go to "Game controllers", or run in terminal with e.g.

wine control joy.cpl

(make sure this wine is the one you are running your game with in case you have many of them in the system. If using Lutris or alike open wine control panel from the GUI to configure the proper one). If joystick is present among XInput ones, select the controller on the left and press "Override" button on the right. Go to DInput tab and check that joystick is selected, check that ConstantForce is displayed in the list of force feedback effects.   


IMPORTANT #2:

If using the demo/default config, make sure your make your game to use axis X as steering, not Y (where hold factor is mapped to...). They are both mapped to mouse movement, but X is mapped to left-to-right movement whereas Y is mapped to inwards-outwards movement of the mouse.  


IMPORTANT #3: it's very subtle and idividual per simulation context.

E.g. steering an RWD group B rally car feels totally different from controlling a FWD group A or a modern AWD one. And is also different from steering an airplane... or a spaceship or... whatever! Riding on snow will have different characteristics than going to tarmac or gravel... multiply it all by weather conditions, surface variations, tires type and wear, etc.

So, power users may have different configurations for different use-cases. They need align the setup with application context for the best performance possible.

As an example, for a particular rally car and type of ride one MAY want to 

* ... Decrease or increase autocentering timing (make it snappy or disable it by setting to 0),
* ... Decrease of increase force feedback influence: to balance between help with self-alignment resulting in e.g. steering wheel counter-rotations while going sideways vs reducing strain not to fight too much of a prominent FFB if feeling that too much excessive counter-movement is required.
* ... Use harder or softer smoothing on user input (lower steering.smoothing_alpha, e.g. 0.2 or increase smoothing or do the opposite for more quicker response). Think of simulating a heavier or a lighter steering wheel.
* ... Apply some filtering for force feedback signal.
* ... Use a flatter curve for user input (e.g. a power curve between 1.0 and 1.1) to keep response in the center as agile as at the extremes, or more concave-up one (1.3 or more )- to make it less responsive in the center, or maybe even a bit less than 1 like 0.975 to make it more responsive in the center and less - at the sides.
* ... Maybe after steering transformation step install another concave up power curve (power > 1.0) to make overal steering wheel movement (not only user input, but including force feedback and autocentering) gentler in the center.
* ... Add a low-pass/one-euro filter at the end of pipeline to finally smoothen the overal wheel movement eliminating to much high-frequency movements.

Who knows what a user may consider comfortable based on personal preferences, hardware specifics and target application context?

Whatever you are tweaking, see the telemetry graphing, device monitoring and --debug modes are there to help you with it.

While the configuration guide is WIP (configuration format is being stabilized), please read the demo configuration file version with comments.


Requirements.

  • Linux (uses evdev/uinput).
  • Rust >= 1.88.0.
  • ALSA for Rust crate midir for MIDI devices access (usually included by default in desktop Linux installations).
  • Membership in the input group or (generally not advised) root access.

To test joysticks behaviours:


Installation.

Prerequisites: permissions.

# Add user to input group (for virtual joystick creation)
sudo usermod -a -G input $USER
# Logout and login again for group changes to take effect

# Enable uinput module (for force feedback)
sudo modprobe uinput

Binary releases.

Download binary pre-releases here or build manually with cargo if binary release doesn't work on your system (takes a few minutes, see below for instructions).  

Build from Source.

# Clone the repository
git clone --depth 1 https://github.com/leosat/MMVJ

# Enter the repository clone directory
cd MMVJ

############## >>> BUILD <<< ###############
# >>> Build the project with GUI and MIDI support (Mice devices are enabled in core)
cargo build --release -j4

# >>> This is same as:
# cargo build --release -j4 --no-default-features --features "gui midi"

# >>> Do not enable neither GUI nor MIDI support: 
# cargo build --release -j4 --no-default-features --features ""

# >>> Enable only mice and MIDI suport, but no GUI: 
# cargo build --release -j4 --no-default-features --features "midi"

############## >>> FIRST RUN <<< ###############
# Display help
./target/release/mmvj --help

# Run mapping engine with default configuration (conf/default.yaml).
./target/release/mmvj

Usage.

Basic Usage.

# Run mapping engine with default configuration.
./target/release/mmvj

# Run mapping engine with custom configuration file.
./target/release/mmvj -c my_config.yaml

# Enable debug output.
./target/release/mmvj --debug

Utility Commands.

# List available MIDI devices.
./target/release/mmvj enum-midi

# Monitor MIDI messages from a device.
./target/release/mmvj monitor-midi "Korg"

# Auto-learn MIDI controls.
./target/release/mmvj midi-learn

# List available mouse devices.
./target/release/mmvj enum-mice

# Monitor mouse events.
./target/release/mmvj monitor-mouse

# Validate configuration file.
./target/release/mmvj validate-config

Configuration.

The application uses YAML configuration files to define:

  • Inputs: MIDI Devices.
  • Inputs: Mouse Devices.
  • Outputs: Virtual Joysticks: specifying properties and controls.
  • Mappings: multiple inputs can map to multiple outputs, each mapping having separate transformation pipeline.

Mapping Transformation Pipeline Steps.

  • Clamping: can be used to saturate values at low/high bounds and optionally override current associated value range.
  • Inversion: for both relative inputs or absolute values, within the defined range.
  • Integration: linearly accumulates relative inputs within a specified range.
  • Curves: linear, quadratic, cubic, S-curve, smoothstep, exponential, etc.
  • Steering: emulating intuitive steering with...
    • Autocentering with configurable dynamics via halflife-parametrized exponential decay. Very useful when no force feedback available.
    • Force feedback application to augment or be used instead of autocentering.
      • Force feedback filtering/smoothing.
    • Steering Wheel "hands hold factor" emulating how firmly your hands are holding the steering wheel.
      • Affects autocentering and force feedback application dynamics.
    • Alpha-smoothing and arbitrary user input filtering.
  • Filter for pedals emulation: enabling smoother or intercorrelated pedal movements with 
    • Rize and fall rates
    • Fall rate hold factor: other control state can be assigned to facilitate fall rate e.g. clutch fall rate can depend on throttle control value.
    • Fall timeout can be used to facilitate value change without immediately going to "off" state (useful when discrete MIDI note events with distinct velocities are mapped to such a control). Further optional moving average filtering can facilitate this to simulate smoother value change.
  • General filters:
    • EMA (exponential moving average).
    • Low-pass, OneEuro.
    • WIP: High-pass, band-pass with configurable steepness.
    • TODO: Convolution with custom kernels.

Configuration file reference:

Example Configuration:

[i] Simplest example:

global:
  idle_tick_update_rate: 60
  persistent_joysticks: true

midi_devices:
  my_midi_keyboards:
    match_name_regex: ".*microKEY2.*"
    controls:
      my_pitch_wheel: PITCH_WHEEL

virtual_joysticks:
  gamepad:
    enabled: true
    persistent: true
    name: "Virtual Gamepad"
    properties:
      vendor_id: 0x123
      product_id: 0x456
      version: 0x789
    controls:
      axis_mundi: ABS_X

mappings:
  - source:
      device: my_midi_keyboards
      control: my_pitch_wheel
    destination:
      joystick: gamepad
      control: axis_mundi
    transformation:                     
      - s_curve:
          steepness: 8.0

High-level architecture dependencies.

The application is built with:

  • Tokio: Async runtime for concurrent I/O.
  • midir: MIDI device access.
  • evdev: Linux input device access, virtual device creation.
  • serde: Configuration serialization.

Performance

  • Low latency: < 1ms processing time.
  • High update rate, configurable to 10000 Hz.

Troubleshooting.

Binary release run problems:

AppImage release.  

fuse: mount failed: Permission denied
Cannot mount AppImage, please check your FUSE setup.
You might still be able to extract the contents of this AppImage 
if you run it with the --appimage-extract option. 
See https://github.com/AppImage/AppImageKit/wiki/FUSE 
for more information
---------------------------------

Solution: run without trying to use fuse:


./mmvj --appimage-extract-and-run

Raw binary release, problems with a dynamic library.

Libasound.so.2 not found (needed for MIDI devices input)

# Install a libasound2* library, 
# e.g. on Debian/Mint/Ubuntu/:

sudo apt-get install libasound2

Any other library:

Rebuild from source code, see instructions above.

Permission denied errors.

# Option 1: Add to input group (recommended)
sudo usermod -a -G input $USER
# Logout and login

# Option 2: Run as root (not recommended)
sudo ./mmvj

Force Feedback isn't working.

# Load uinput module
sudo modprobe uinput

# Check if module is loaded
lsmod | grep uinput

# Make uinput persistent
echo "uinput" | sudo tee -a /etc/modules

MIDI Device not found.

# List all MIDI devices
./mmvj enum-midi

# Check ALSA MIDI devices
aconnect -l

# Check permissions
ls -la /dev/snd/

WARNING/DISCLAIMER:

This application is in active development state and is used as a toy project by the author to learn the new programming language (with all the consequences) and is provided as is without any warranties. Not everything works and it is far from ideal currently. Nevertheless, while still in development I'm finding it already quite useful and capable, so, I've decided to opensource it and provide for those who are looking for such a tool. When/if the project reaches production state, this warning will not be here.  

For any questions (or anything else) feel free to contact me at leonid.satanovsky@gmail.com (Leonid Satanovskiy).

HELPFUL TOOLS:

https://github.com/berarma/ffbtools  

License.

All rights reserved. Copyright: Leonid Satanovskiy.
When this app reaches production state this will be changed.
/* "GNU is not Unix." */ 

Contributing.

Pull requests are not yet accepted
because please see the WARNING/DISCLAIMER at the top.
It will change as soon as the project gets in production-ready state.