Skip to content

Add GamepadRumble output controller (XInput gamepad vibration) - #25

Closed
kara2010 wants to merge 3 commits into
DirectOutput:masterfrom
kara2010:gamepad-rumble
Closed

kara2010 wants to merge 3 commits into
DirectOutput:masterfrom
kara2010:gamepad-rumble

Conversation

@kara2010

@kara2010 kara2010 commented Aug 30, 2026 •

Copy link
Copy Markdown

This adds an output controller that turns DOF output activity into vibration on
an XInput gamepad (Xbox controller).

Motivation

Desktop and VR players usually have no feedback hardware, so DOF is not part of
their setup at all — even though the config database already knows exactly which
ROM event drives which toy for well over a thousand tables. Routing those outputs
to a gamepad's rumble motors gives game-accurate haptics (shaker, knocker, gear
motors, slingshots, bumpers) without any cabinet electronics.

Implementation

GamepadRumble inherits OutputControllerFlexCompleteBase and implements the
usual four methods, following the pattern described for custom controllers. It
has no external dependencies: XInputSetState is called through P/Invoke on
xinput1_4.dll, falling back to xinput9_1_0.dll on older systems. Failures are
swallowed, so a missing or disconnected controller is a no-op.

The strongest active output drives the vibration.

Configuration

Setting Meaning
ControllerIndex XInput slot (0-3)
Strength overall strength in percent
PortWeights per-port weighting as port=percent pairs
SustainMs how long an output runs at full power before fading
SustainFadeMs fade duration
SustainLevel level a continuously running output settles at

A complete, commented example is included as
config/examples/Cabinet.GamepadRumble.xml. It also contains the
LedWizEquivalent mapping, which is the part that actually routes the ini file
data to the controller outputs:

<GamepadRumble>
  <Name>GamepadRumble</Name>
  <ControllerIndex>0</ControllerIndex>
  <Strength>100</Strength>
  <PortWeights>3=50</PortWeights>
  <SustainMs>500</SustainMs>
  <SustainFadeMs>800</SustainFadeMs>
  <SustainLevel>0</SustainLevel>
  <NumberOfOutputs>32</NumberOfOutputs>
</GamepadRumble>

Why the sustain fade

Some toys follow the raw solenoid state and therefore stay on for as long as the
mechanism runs — the train in Cactus Canyon is a good example, where the gear
column is S17/S37/S38 with no duration. A real cabinet motor may happily run
for a minute, but a gamepad buzzing at full power for the same time is just
unpleasant. Sustained outputs are therefore faded back towards SustainLevel
(0 = silent after the initial kick), while short hits stay untouched.

PortWeights serves the same purpose at a coarser level: because port numbers
mean the same thing on every table with the usual config tool assignment, whole
categories can be toned down (the default tones gear motors down to 50%).

Since the base class only calls UpdateOutputs when a value actually changes, a
small background ticker re-runs the computation every 50 ms so the fade also
applies while the value stays constant.

Cooperating with other applications

A gamepad has a single vibration channel, so two programs driving the same pad
would overwrite each other. To avoid that, the controller publishes its current
motor values to a small named shared memory block (Local\VPXDOFRumbleShare) and
sends the per-motor maximum of its own value and whatever another application has
published there. That lets a second program adding its own effects — a simulator
with physics-based feedback, for example — coexist with DOF instead of the two
cancelling each other out.

The block is created on demand and values older than five seconds are ignored, so
the mechanism is inert when nothing else is running. Happy to drop this part if
you would rather keep the controller free of any such integration point.

Documentation

Documentation/66_OutputControllers_BuiltIn.md gets a section covering the
controller, its properties, the required LedWizEquivalent routing and the
reasoning behind the sustain fade, including the setup steps needed because
there is no hardware to auto detect. Documentation/17_Supported Hardware.md
gets a short entry so players without any feedback hardware can find out that
this option exists at all.

Compatibility

Purely additive — a new class, its .csproj entry, an example config and
documentation.
Nothing existing is touched, and the controller is only instantiated if it is
explicitly present in a cabinet config, so existing setups are unaffected.
Built and tested against the current master on Windows x64 with an Xbox
controller across a range of tables.


Implemented with AI assistance; built, tested and reviewed on real hardware.

Adds an output controller that turns DOF output activity into vibration
on an XInput gamepad (Xbox controller). It targets desktop and VR setups
that have no feedback hardware: the DOF config database already knows
which ROM event drives which toy, so routing those outputs to a gamepad
gives game-accurate haptics without any cabinet electronics.

The controller inherits OutputControllerFlexCompleteBase and implements
the usual four methods. Configuration (cabinet config):

  ControllerIndex  XInput slot (0-3)
  Strength         overall strength in percent
  PortWeights      per-port weighting, "port=percent" pairs, so whole
                   categories can be toned down (with the usual config
                   tool assignment port 3 is the gear motor, etc.)
  SustainMs        how long an output runs at full power before fading
  SustainFadeMs    fade duration
  SustainLevel     level a continuously running output settles at

The sustain fade exists because some toys follow the raw solenoid state
and stay on for as long as the mechanism runs (e.g. the train in Cactus
Canyon). A real cabinet motor may happily run for a minute; a gamepad
buzzing at full power for the same time is unpleasant, so sustained
outputs are faded back while short hits stay untouched.

Since the base class only calls UpdateOutputs when a value changes, a
small ticker thread re-runs the computation so the fade also applies
while the value stays constant.
Documents the controller settings and, importantly, the LedWizEquivalent
mapping that routes the ini file data to the controller outputs - without
that part nothing reaches the gamepad.
Adds a section to the built in output controllers page describing the
controller, its properties, the required LedWizEquivalent routing and the
reasoning behind the sustain fade, plus an entry on the supported hardware
page so users without feedback hardware can find it in the first place.
@kara2010

Copy link
Copy Markdown
Author

Closing this for now - I would like to spend more time testing and refining it before asking for your review. Sorry for the noise, I will open a fresh PR when it is properly ready.

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

Successfully merging this pull request may close these issues.

1 participant