@@ -3,23 +3,33 @@
// Refer to the license.txt file included.
#include " InputCommon/ControllerInterface/ForceFeedback/ForceFeedbackDevice.h"
#include < algorithm>
#include < array>
#include < string>
#include " Common/Thread.h"
namespace ciface
{
namespace ForceFeedback
{
// template instantiation
template class ForceFeedbackDevice ::Force<DICONSTANTFORCE>;
template class ForceFeedbackDevice ::Force<DIRAMPFORCE>;
template class ForceFeedbackDevice ::Force<DIPERIODIC>;
// 100Hz which homebrew docs very roughly imply is within WiiMote normal
// range, used for periodic haptic effects though often ignored by devices
constexpr int RUMBLE_PERIOD = DI_SECONDS / 100 ;
// This needs to be at least as long as the longest rumble that might ever be played.
// Too short and it's going to stop in the middle of a long effect.
// "INFINITE" is invalid for ramp effects and probably not sensible.
constexpr int RUMBLE_LENGTH_MAX = DI_SECONDS * 10 ;
// Template instantiation:
template class ForceFeedbackDevice ::TypedForce<DICONSTANTFORCE>;
template class ForceFeedbackDevice ::TypedForce<DIRAMPFORCE>;
template class ForceFeedbackDevice ::TypedForce<DIPERIODIC>;
struct ForceType
{
GUID guid;
const std::string name;
const char * name;
};
static const ForceType force_type_names[] = {
@@ -36,43 +46,79 @@ static const ForceType force_type_names[] = {
// {GUID_Friction, "Friction"},
};
bool ForceFeedbackDevice::InitForceFeedback ( const LPDIRECTINPUTDEVICE8 device, int cAxes )
void ForceFeedbackDevice::DeInitForceFeedback ( )
{
if (cAxes == 0 )
return false ;
if (!m_run_thread.TestAndClear ())
return ;
SignalUpdateThread ();
m_update_thread.join ();
}
// TODO: check for DIDC_FORCEFEEDBACK in devcaps?
void ForceFeedbackDevice::ThreadFunc ()
{
Common::SetCurrentThreadName (" ForceFeedback update thread" );
// temporary
DWORD rgdwAxes[ 2 ] = {DIJOFS_X, DIJOFS_Y};
LONG rglDirection[ 2 ] = {- 200 , 0 } ;
while (m_run_thread. IsSet ())
{
m_update_event. Wait () ;
DIEFFECT eff;
memset (&eff, 0 , sizeof (eff));
eff.dwSize = sizeof (DIEFFECT);
for (auto output : Outputs ())
{
auto & force = *static_cast <Force*>(output);
force.UpdateOutput ();
}
}
for (auto output : Outputs ())
{
auto & force = *static_cast <Force*>(output);
force.Release ();
}
}
void ForceFeedbackDevice::SignalUpdateThread ()
{
m_update_event.Set ();
}
bool ForceFeedbackDevice::InitForceFeedback (const LPDIRECTINPUTDEVICE8 device, int axis_count)
{
if (axis_count == 0 )
return false ;
// We just use the X axis (for wheel left/right).
// Gamepads seem to not care which axis you use.
// These are temporary for creating the effect:
std::array<DWORD, 1 > rgdwAxes = {DIJOFS_X};
std::array<LONG, 1 > rglDirection = {-200 };
DIEFFECT eff{};
eff.dwSize = sizeof (eff);
eff.dwFlags = DIEFF_CARTESIAN | DIEFF_OBJECTOFFSETS;
eff.dwDuration = INFINITE; // (4 * DI_SECONDS)
eff.dwDuration = RUMBLE_LENGTH_MAX;
eff.dwSamplePeriod = 0 ;
eff.dwGain = DI_FFNOMINALMAX;
eff.dwTriggerButton = DIEB_NOTRIGGER;
eff.dwTriggerRepeatInterval = 0 ;
eff.cAxes = std::min<DWORD>(1 , cAxes);
eff.rgdwAxes = rgdwAxes;
eff.rglDirection = rglDirection;
// initialize parameters
DICONSTANTFORCE diCF = {-10000 };
diCF.lMagnitude = DI_FFNOMINALMAX;
DIRAMPFORCE diRF = {0 };
DIPERIODIC diPE = {0 };
// doesn't seem needed
// DIENVELOPE env;
// eff.lpEnvelope = &env;
// ZeroMemory(&env, sizeof(env));
// env.dwSize = sizeof(env);
for (const ForceType& f : force_type_names)
eff.cAxes = DWORD (rgdwAxes.size ());
eff.rgdwAxes = rgdwAxes.data ();
eff.rglDirection = rglDirection.data ();
eff.dwStartDelay = 0 ;
// Initialize parameters with zero force (their current state).
DICONSTANTFORCE diCF{};
diCF.lMagnitude = 0 ;
DIRAMPFORCE diRF{};
diRF.lStart = diRF.lEnd = 0 ;
DIPERIODIC diPE{};
diPE.dwMagnitude = 0 ;
// Is it sensible to have a zero-offset?
diPE.lOffset = 0 ;
diPE.dwPhase = 0 ;
diPE.dwPeriod = RUMBLE_PERIOD;
for (auto & f : force_type_names)
{
if (f.guid == GUID_ConstantForce)
{
@@ -86,7 +132,7 @@ bool ForceFeedbackDevice::InitForceFeedback(const LPDIRECTINPUTDEVICE8 device, i
}
else
{
// all other forces need periodic parameters
// All other forces need periodic parameters:
eff.cbTypeSpecificParams = sizeof (DIPERIODIC);
eff.lpvTypeSpecificParams = &diPE;
}
@@ -95,15 +141,15 @@ bool ForceFeedbackDevice::InitForceFeedback(const LPDIRECTINPUTDEVICE8 device, i
if (SUCCEEDED (device->CreateEffect (f.guid , &eff, &pEffect, nullptr )))
{
if (f.guid == GUID_ConstantForce)
AddOutput (new ForceConstant (f.name , pEffect));
AddOutput (new ForceConstant (this , f.name , pEffect, diCF ));
else if (f.guid == GUID_RampForce)
AddOutput (new ForceRamp (f.name , pEffect));
AddOutput (new ForceRamp (this , f.name , pEffect, diRF ));
else
AddOutput (new ForcePeriodic (f.name , pEffect));
AddOutput (new ForcePeriodic (this , f.name , pEffect, diPE ));
}
}
// disable autocentering
// Disable autocentering:
if (Outputs ().size ())
{
DIPROPDWORD dipdw;
@@ -113,95 +159,113 @@ bool ForceFeedbackDevice::InitForceFeedback(const LPDIRECTINPUTDEVICE8 device, i
dipdw.diph .dwHow = DIPH_DEVICE;
dipdw.dwData = DIPROPAUTOCENTER_OFF;
device->SetProperty (DIPROP_AUTOCENTER, &dipdw.diph );
m_run_thread.Set ();
m_update_thread = std::thread (&ForceFeedbackDevice::ThreadFunc, this );
}
return true ;
}
template <typename P>
ForceFeedbackDevice::Force <P>::~Force ()
void ForceFeedbackDevice::TypedForce <P>::PlayEffect ()
{
m_iface-> Stop () ;
m_iface-> Unload ( );
m_iface-> Release ( );
}
DIEFFECT eff{} ;
eff. dwSize = sizeof (eff );
eff. cbTypeSpecificParams = sizeof (m_params );
eff. lpvTypeSpecificParams = &m_params;
template <typename P>
void ForceFeedbackDevice::Force<P>::Update()
{
DIEFFECT eff = {};
eff.dwSize = sizeof (DIEFFECT);
eff.dwFlags = DIEFF_CARTESIAN | DIEFF_OBJECTOFFSETS;
eff.cbTypeSpecificParams = sizeof (P);
eff.lpvTypeSpecificParams = ¶ms;
// set params and start effect
m_iface->SetParameters (&eff, DIEP_TYPESPECIFICPARAMS | DIEP_START);
m_effect->SetParameters (&eff, DIEP_START | DIEP_TYPESPECIFICPARAMS);
}
template <typename P>
void ForceFeedbackDevice::Force <P>::Stop ()
void ForceFeedbackDevice::TypedForce <P>::StopEffect ()
{
m_iface ->Stop ();
m_effect ->Stop ();
}
template <>
void ForceFeedbackDevice::ForceConstant::SetState ( const ControlState state )
bool ForceFeedbackDevice::ForceConstant::UpdateParameters ( int magnitude )
{
const LONG new_val = LONG ( 10000 * state) ;
const auto old_magnitude = m_params. lMagnitude ;
if (params.lMagnitude == new_val)
return ;
m_params.lMagnitude = magnitude;
params.lMagnitude = new_val;
if (new_val)
Update ();
else
Stop ();
return old_magnitude != m_params.lMagnitude ;
}
template <>
void ForceFeedbackDevice::ForceRamp::SetState ( const ControlState state )
bool ForceFeedbackDevice::ForceRamp::UpdateParameters ( int magnitude )
{
const LONG new_val = LONG ( 10000 * state) ;
const auto old_magnitude = m_params. lStart ;
if (params.lStart == new_val)
return ;
// Having the same "start" and "end" here is a bit odd..
// But ramp forces don't really make sense for our rumble effects anyways..
m_params.lStart = m_params.lEnd = magnitude;
params.lStart = params.lEnd = new_val;
if (new_val)
Update ();
else
Stop ();
return old_magnitude != m_params.lStart ;
}
template <>
void ForceFeedbackDevice::ForcePeriodic::SetState ( const ControlState state )
bool ForceFeedbackDevice::ForcePeriodic::UpdateParameters ( int magnitude )
{
const DWORD new_val = DWORD ( 10000 * state) ;
const auto old_magnitude = m_params. dwMagnitude ;
if (params.dwMagnitude == new_val)
return ;
m_params.dwMagnitude = magnitude;
params.dwMagnitude = new_val;
if (new_val)
Update ();
else
Stop ();
return old_magnitude != m_params.dwMagnitude ;
}
template <typename P>
ForceFeedbackDevice::Force<P>::Force(const std::string& name, LPDIRECTINPUTEFFECT iface)
: m_name(name), m_iface(iface)
ForceFeedbackDevice::TypedForce<P>::TypedForce(ForceFeedbackDevice* parent, const char * name,
LPDIRECTINPUTEFFECT effect, const P& params)
: Force(parent, name, effect), m_params(params)
{
memset (¶ms, 0 , sizeof (params));
}
template <typename P>
std::string ForceFeedbackDevice::Force<P>::GetName() const
void ForceFeedbackDevice::TypedForce<P>::UpdateEffect(int magnitude)
{
if (UpdateParameters (magnitude))
{
if (magnitude)
PlayEffect ();
else
StopEffect ();
}
}
std::string ForceFeedbackDevice::Force::GetName () const
{
return m_name;
}
ForceFeedbackDevice::Force::Force (ForceFeedbackDevice* parent, const char * name,
LPDIRECTINPUTEFFECT effect)
: m_effect(effect), m_parent(*parent), m_name(name), m_desired_magnitude()
{
}
void ForceFeedbackDevice::Force::SetState (ControlState state)
{
const auto new_val = int (DI_FFNOMINALMAX * state);
if (m_desired_magnitude.exchange (new_val) != new_val)
m_parent.SignalUpdateThread ();
}
void ForceFeedbackDevice::Force::UpdateOutput ()
{
UpdateEffect (m_desired_magnitude);
}
void ForceFeedbackDevice::Force::Release ()
{
// This isn't in the destructor because it should happen before the device is released.
m_effect->Stop ();
m_effect->Unload ();
m_effect->Release ();
}
} // namespace ForceFeedback
} // namespace ciface