You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
4126 lines
132 KiB
4126 lines
132 KiB
//@doc
|
|
/******************************************************
|
|
**
|
|
** @module EFFECT.CPP | InternalEffect implementation file
|
|
**
|
|
** Description:
|
|
**
|
|
** History:
|
|
** Created 1/05/98 Matthew L. Coill (mlc)
|
|
**
|
|
** 23-Mar-99 waltw IsReallyPlaying now uses data returned by
|
|
** Transmit instead of obsolete GetStatusGateData
|
|
**
|
|
**
|
|
** (c) 1986-1998 Microsoft Corporation. All Rights Reserved.
|
|
******************************************************/
|
|
|
|
#include "Effect.h"
|
|
#include "DPack.h"
|
|
#include "DTrans.h"
|
|
#include "FFDevice.h"
|
|
#include "joyregst.hpp"
|
|
#include "Registry.h"
|
|
#include "CritSec.h"
|
|
#include "Midi_Obj.hpp"
|
|
#include <math.h>
|
|
|
|
DWORD g_TotalModifiable = DIEP_DURATION | DIEP_SAMPLEPERIOD | DIEP_GAIN | DIEP_TRIGGERBUTTON |
|
|
DIEP_TRIGGERREPEATINTERVAL | DIEP_AXES | DIEP_DIRECTION | DIEP_ENVELOPE |
|
|
DIEP_TYPESPECIFICPARAMS;
|
|
|
|
//#define ZEP_PRODUCT_ID 0x7F
|
|
#define ZEP_PRODUCT_ID 0x15
|
|
|
|
#define DOWNLOAD_OP_200 0x02
|
|
#define STORE_ACTION_200 0x00
|
|
#define PLAYSUPER_ACTION_200 0x01
|
|
#define PLAYSOLO_ACTION_200 0x02
|
|
#define AXIS_ANGLE_200 0x00
|
|
#define AXIS_X_200 0x01
|
|
#define AXIS_Y_200 0x02
|
|
|
|
// VFX Default items
|
|
#define DEFAULT_VFX_SAMPLEPERIOD 2000
|
|
#define DEFAULT_VFX_EFFECT_DIRECTION 0
|
|
#define DEFAULT_VFX_EFFECT_GAIN 10000
|
|
#define DEFAULT_VFX_EFFECT_DURATION 1000
|
|
|
|
// Max DIDistance = 10,000 so 10,000/100(percent) = 100
|
|
#define DIDISTANCE_TO_PERCENT 100
|
|
|
|
#define COEFFICIENT_SCALE_1XX 100
|
|
#define DURATION_SCALE_1XX 1000
|
|
#define GAIN_PERCENTAGE_SCALE 100
|
|
#define FREQUENCY_SCALE_1XX 1000000
|
|
#define TIME_SCALE_1XX 1000
|
|
#define ENVELOPE_TIME_TICKS_1XX 2
|
|
|
|
#define DURATION_SCALE_200 2000
|
|
#define ANGLE_SCALE_200 281.25
|
|
#define POSITIVE_PERCENT_SCALE 1.588
|
|
#define FRICTION_SCALE_200 157.5
|
|
#define GAIN_SCALE_200 78.7
|
|
#define PHASE_SCALE_200 2.197
|
|
#define WAVELET_SCALE_200 39.215
|
|
//#define WAVELET_DISTANCE_200 1215
|
|
#define WAVELET_DISTANCE_200 2440
|
|
//#define WAVELET_DISTANCE_SCALE_200 39.193
|
|
#define WAVELET_DISTANCE_SCALE_200 78.74
|
|
#define BEHAVIOUR_CENTER_SCALE_200 158
|
|
#define BEHAVIOUR_CENTER_200 63
|
|
#define MAX_TIME_200 32766000
|
|
|
|
// Mofidify index for version 200
|
|
#define INDEX_BE_DURATION_200 0
|
|
#define INDEX_DELAY_200 1
|
|
#define INDEX_DIRECTIONANGLE_200 2
|
|
#define INDEX_D1F1_200 (BYTE)3
|
|
#define INDEX_D2F2_200 4
|
|
#define INDEX_D3F3_200 5
|
|
#define INDEX_D4F4_200 6
|
|
#define INDEX_BE_BUTTONMAP_200 7
|
|
#define INDEX_BE_BUTTONREPEAT_200 8
|
|
#define INDEX_BE_GAIN_200 9
|
|
#define INDEX_BE_CENTER_200 10
|
|
#define INDEX_BE_REPEAT_200 11
|
|
|
|
// Friction Modify Indecies
|
|
#define INDEX_FE_DURATION_200 0
|
|
#define INDEX_FE_DELAY_200 1
|
|
#define INDEX_FE_DIRECTIONANGLE_200 2
|
|
#define INDEX_FE_COEEFICIENT_200 3
|
|
#define INDEX_FE_BUTTONMAP_200 4
|
|
#define INDEX_FE_BUTTONREPEAT_200 5
|
|
#define INDEX_FE_GAIN_200 6
|
|
#define INDEX_FE_REPEAT_200 7
|
|
|
|
// CustomForce Modify Indecies
|
|
#define INDEX_CF_DURATION_200 0
|
|
#define INDEX_CF_DELAY_200 1
|
|
#define INDEX_CF_DIRECTIONANGLE_200 2
|
|
#define INDEX_CF_GAIN_200 3
|
|
#define INDEX_CF_STARTPERCENT_200 4
|
|
#define INDEX_CF_ATTTACK_TIME_200 5
|
|
#define INDEX_CF_SUSTAINPERCENT_200 6
|
|
#define INDEX_CF_FADESTART_200 7
|
|
#define INDEX_CF_ENDPERCENT_200 8
|
|
#define INDEX_CF_OFFSET_200 9
|
|
#define INDEX_CF_FORCESAMPLE_200 10
|
|
#define INDEX_CF_SAMPLE_PERIOD_200 11
|
|
#define INDEX_CF_BUTTONMAP_200 12
|
|
#define INDEX_CF_BUTTONREPEAT_200 13
|
|
#define INDEX_CF_REPEAT_200 14
|
|
|
|
// Periodic Effect Modify Indecies
|
|
#define INDEX_PE_DURATION_200 0
|
|
#define INDEX_PE_DIRECTIONANGLE_200 2
|
|
#define INDEX_PE_GAIN_200 3
|
|
#define INDEX_PE_PHASE_200 4
|
|
#define INDEX_PE_STARTPERCENT_200 5
|
|
#define INDEX_PE_ATTTACK_TIME_200 6
|
|
#define INDEX_PE_SUSTAINPERCENT_200 7
|
|
#define INDEX_PE_FADESTART_200 8
|
|
#define INDEX_PE_ENDPERCENT_200 9
|
|
#define INDEX_PE_PERIOD_200 10
|
|
#define INDEX_PE_OFFSET_200 11
|
|
#define INDEX_PE_SAMPLE_PERIOD_200 12
|
|
#define INDEX_PE_BUTTONMAP_200 13
|
|
#define INDEX_PE_BUTTONREPEAT_200 14
|
|
#define INDEX_PE_REPEAT_200 15
|
|
|
|
// ConstantForce Effect Modify Indecies
|
|
#define INDEX_CE_DURATION_200 0
|
|
#define INDEX_CE_GAIN_200 3
|
|
#define INDEX_CE_STARTPERCENT_200 4
|
|
#define INDEX_CE_ATTTACK_TIME_200 5
|
|
#define INDEX_CE_SUSTAINPERCENT_200 6
|
|
#define INDEX_CE_FADESTART_200 7
|
|
#define INDEX_CE_ENDPERCENT_200 8
|
|
#define INDEX_CE_MAGNITUDE_200 9
|
|
#define INDEX_CE_BUTTONMAP_200 11
|
|
#define INDEX_CE_BUTTONREPEAT_200 12
|
|
#define INDEX_CE_REPEAT_200 13
|
|
|
|
// Default system param defines (for 1XX)
|
|
#define DEF_XY_CONST 22500
|
|
#define DEF_ROT_CONST 17272
|
|
#define DEF_SLDR_CONST 126
|
|
#define DEF_AJ_POS 4
|
|
#define DEF_AJ_ROT 2
|
|
#define DEF_AJ_SLDR 2
|
|
#define DEF_SPR_SCL ((DWORD)-256)
|
|
#define DEF_BMP_SCL 60
|
|
#define DEF_DMP_SCL ((DWORD)-3436)
|
|
#define DEF_INERT_SCL ((DWORD)-2562)
|
|
#define DEF_VEL_OFFSET_SCL 54
|
|
#define DEF_ACC_OFFSET_SCL 40
|
|
#define DEF_Y_MOT_BOOST 19661
|
|
#define DEF_X_MOT_SATURATION 254
|
|
|
|
BYTE g_TriggerMap1XX[] = { 0x00, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x80, 0x80 };
|
|
BYTE g_TriggerMap200[] = { 0x00, 0x01, 0x02, 0x04, 0x10, 0x20, 0x40, 0x80, 0x08, 0x08, 0x08 };
|
|
|
|
/******************* Envelope1XX class *******************/
|
|
Envelope1XX::Envelope1XX(DIENVELOPE* pDIEnvelope, DWORD baseLine, DWORD duration)
|
|
{
|
|
// Zero out this monster
|
|
::memset(this, 0, sizeof(Envelope1XX));
|
|
|
|
if (pDIEnvelope == NULL) {
|
|
m_SustainTime = duration;
|
|
m_SustainPercent = 100;
|
|
return;
|
|
}
|
|
|
|
// Attack/sustain/decay sum to duration
|
|
m_AttackTime = pDIEnvelope->dwAttackTime/ENVELOPE_TIME_TICKS_1XX;
|
|
if (duration != INFINITE) { // Inifinite duration has only attack
|
|
m_SustainTime = (duration- pDIEnvelope->dwFadeTime)/ENVELOPE_TIME_TICKS_1XX;
|
|
}
|
|
|
|
// What is the real maximum
|
|
DWORD maxAmp = baseLine;
|
|
if (maxAmp < pDIEnvelope->dwAttackLevel) {
|
|
maxAmp = pDIEnvelope->dwAttackLevel;
|
|
}
|
|
if (maxAmp < pDIEnvelope->dwFadeLevel) {
|
|
maxAmp = pDIEnvelope->dwFadeLevel;
|
|
}
|
|
|
|
maxAmp /= 100; // For percentage conversion
|
|
if (maxAmp == 0) { // Avoid a nasty division error
|
|
m_SustainPercent = 100; // Sustain is full (others are 0)
|
|
} else {
|
|
m_StartPercent = pDIEnvelope->dwAttackLevel/maxAmp;
|
|
m_SustainPercent = baseLine/maxAmp;
|
|
m_EndPercent = pDIEnvelope->dwFadeLevel/maxAmp;
|
|
}
|
|
}
|
|
|
|
|
|
/******************* Envelope200 class *******************/
|
|
Envelope200::Envelope200(DIENVELOPE* pDIEnvelope, DWORD sustain, DWORD duration, HRESULT& hr)
|
|
{
|
|
// Zero out this monster
|
|
::memset(this, 0, sizeof(Envelope200));
|
|
m_FadeStart = WORD(duration/DURATION_SCALE_200);
|
|
|
|
DWORD calc = sustain; // -- Modifcation above, now done on gain
|
|
|
|
// DI Doesn't specify an envelope
|
|
if (pDIEnvelope == NULL) {
|
|
m_SustainPercent = BYTE(calc/GAIN_SCALE_200); // Base sustain of magnitude
|
|
return;
|
|
}
|
|
|
|
// The sun of attack and fade must be less than MAX_TIME
|
|
if ((pDIEnvelope->dwAttackTime + pDIEnvelope->dwFadeTime) > MAX_TIME_200) {
|
|
hr = DI_TRUNCATED;
|
|
if (pDIEnvelope->dwAttackTime > MAX_TIME_200) {
|
|
pDIEnvelope->dwAttackTime = MAX_TIME_200;
|
|
}
|
|
pDIEnvelope->dwFadeTime = MAX_TIME_200 - pDIEnvelope->dwAttackTime;
|
|
}
|
|
|
|
// Attack/sustain/decay sum to duration
|
|
m_AttackTime = WORD(pDIEnvelope->dwAttackTime/DURATION_SCALE_200);
|
|
if (duration != INFINITE) { // Inifinite duration has only attack (fade-time == DURATION)
|
|
m_FadeStart = WORD((duration - pDIEnvelope->dwFadeTime)/DURATION_SCALE_200);
|
|
if (m_FadeStart < m_AttackTime) { // We don't want to fade before the end of the attack!
|
|
m_FadeStart = m_AttackTime;
|
|
}
|
|
}
|
|
|
|
m_SustainPercent = BYTE(float(calc)/GAIN_SCALE_200);
|
|
calc = pDIEnvelope->dwAttackLevel;
|
|
if (calc > 10000) {
|
|
calc = 10000;
|
|
hr = DI_TRUNCATED;
|
|
}
|
|
m_StartPercent = BYTE(float(calc)/GAIN_SCALE_200);
|
|
calc = (pDIEnvelope->dwFadeLevel);
|
|
if (calc > 10000) {
|
|
calc = 10000;
|
|
hr = DI_TRUNCATED;
|
|
}
|
|
m_EndPercent = BYTE(float(calc)/GAIN_SCALE_200);
|
|
}
|
|
|
|
|
|
/******************* InternalEffect class ******************/
|
|
InternalEffect::InternalEffect() :
|
|
m_EffectID(0),
|
|
m_DeviceEffectID(0),
|
|
m_Duration(0),
|
|
m_Gain(0),
|
|
m_TriggerPlayButton(0),
|
|
m_AxisMask(0),
|
|
m_EffectAngle(0),
|
|
m_PercentX(0),
|
|
m_PercentY(0),
|
|
m_PercentAdjustment(0),
|
|
m_AxesReversed(FALSE),
|
|
m_IsPossiblyPlaying(FALSE)
|
|
{
|
|
}
|
|
|
|
InternalEffect::~InternalEffect()
|
|
{
|
|
}
|
|
|
|
|
|
// Static Creation Functions
|
|
InternalEffect* InternalEffect::CreateSpring()
|
|
{
|
|
if (g_ForceFeedbackDevice.GetFirmwareVersionMajor() == 1) {
|
|
return new BehaviouralEffect1XX(ET_BE_SPRING);
|
|
} else { // Assume newer firmware versions will come with an update, or work with this
|
|
return new BehaviouralEffect200(ET_SPRING_200);
|
|
}
|
|
}
|
|
|
|
InternalEffect* InternalEffect::CreateDamper()
|
|
{
|
|
if (g_ForceFeedbackDevice.GetFirmwareVersionMajor() == 1) {
|
|
return new BehaviouralEffect1XX(ET_BE_DAMPER);
|
|
} else { // Assume newer firmware versions will come with an update, or work with this
|
|
return new BehaviouralEffect200(ET_DAMPER_200);
|
|
}
|
|
}
|
|
|
|
InternalEffect* InternalEffect::CreateInertia()
|
|
{
|
|
if (g_ForceFeedbackDevice.GetFirmwareVersionMajor() == 1) {
|
|
return new BehaviouralEffect1XX(ET_BE_INERTIA);
|
|
} else { // Assume newer firmware versions will come with an update, or work with this
|
|
return new BehaviouralEffect200(ET_INERTIA_200);
|
|
}
|
|
}
|
|
|
|
InternalEffect* InternalEffect::CreateFriction()
|
|
{
|
|
if (g_ForceFeedbackDevice.GetFirmwareVersionMajor() == 1) {
|
|
return new FrictionEffect1XX();
|
|
} else { // Assume newer firmware versions will come with an update, or work with this
|
|
return new FrictionEffect200();
|
|
}
|
|
}
|
|
|
|
InternalEffect* InternalEffect::CreateRTCSpring()
|
|
{
|
|
if (g_ForceFeedbackDevice.GetFirmwareVersionMajor() == 1) {
|
|
return new RTCSpring1XX();
|
|
} else { // Assume newer firmware versions will come with an update, or work with this
|
|
return new RTCSpring200();
|
|
}
|
|
}
|
|
|
|
InternalEffect* InternalEffect::CreateSystemEffect()
|
|
{
|
|
if (g_ForceFeedbackDevice.GetFirmwareVersionMajor() == 1) {
|
|
return new SystemEffect1XX();
|
|
} else { // Assume newer firmware versions will come with an update, or work with this
|
|
return NULL; // NYI
|
|
}
|
|
}
|
|
|
|
InternalEffect* InternalEffect::CreateSine()
|
|
{
|
|
if (g_ForceFeedbackDevice.GetFirmwareVersionMajor() == 1) {
|
|
return new PeriodicEffect1XX(ET_SE_SINE);
|
|
} else { // Assume newer firmware versions will come with an update, or work with this
|
|
return new PeriodicEffect200(ET_SINE_200);
|
|
}
|
|
}
|
|
|
|
InternalEffect* InternalEffect::CreateSquare()
|
|
{
|
|
if (g_ForceFeedbackDevice.GetFirmwareVersionMajor() == 1) {
|
|
return new PeriodicEffect1XX(ET_SE_SQUAREHIGH);
|
|
} else { // Assume newer firmware versions will come with an update, or work with this
|
|
return new PeriodicEffect200(ET_SQUARE_200);
|
|
}
|
|
}
|
|
|
|
InternalEffect* InternalEffect::CreateTriangle()
|
|
{
|
|
if (g_ForceFeedbackDevice.GetFirmwareVersionMajor() == 1) {
|
|
return new PeriodicEffect1XX(ET_SE_TRIANGLEUP);
|
|
} else { // Assume newer firmware versions will come with an update, or work with this
|
|
return new PeriodicEffect200(ET_TRIANGLE_200);
|
|
}
|
|
}
|
|
|
|
InternalEffect* InternalEffect::CreateSawtoothUp()
|
|
{
|
|
if (g_ForceFeedbackDevice.GetFirmwareVersionMajor() == 1) {
|
|
return NULL;
|
|
} else { // Assume newer firmware versions will come with an update, or work with this
|
|
return new SawtoothEffect200(TRUE);
|
|
}
|
|
}
|
|
|
|
InternalEffect* InternalEffect::CreateSawtoothDown()
|
|
{
|
|
if (g_ForceFeedbackDevice.GetFirmwareVersionMajor() == 1) {
|
|
return NULL;
|
|
} else { // Assume newer firmware versions will come with an update, or work with this
|
|
return new SawtoothEffect200(FALSE);
|
|
}
|
|
}
|
|
|
|
InternalEffect* InternalEffect::CreateCustomForce()
|
|
{
|
|
if (g_ForceFeedbackDevice.GetFirmwareVersionMajor() == 1) {
|
|
return NULL;
|
|
} else { // Assume newer firmware versions will come with an update, or work with this
|
|
return new CustomForceEffect200();
|
|
}
|
|
}
|
|
|
|
InternalEffect* InternalEffect::CreateConstantForce()
|
|
{
|
|
if (g_ForceFeedbackDevice.GetFirmwareVersionMajor() == 1) {
|
|
return new PeriodicEffect1XX(ET_SE_CONSTANT_FORCE);
|
|
} else { // Assume newer firmware versions will come with an update, or work with this
|
|
return new ConstantForceEffect200();
|
|
}
|
|
}
|
|
|
|
InternalEffect* InternalEffect::CreateRamp()
|
|
{
|
|
if (g_ForceFeedbackDevice.GetFirmwareVersionMajor() == 1) {
|
|
return new PeriodicEffect1XX(ET_SE_RAMPUP);
|
|
} else { // Assume newer firmware versions will come with an update, or work with this
|
|
return new RampEffect200();
|
|
}
|
|
}
|
|
|
|
|
|
InternalEffect* InternalEffect::CreateWall()
|
|
{
|
|
if (g_ForceFeedbackDevice.GetFirmwareVersionMajor() == 1) {
|
|
return NULL;
|
|
} else { // Assume newer firmware versions will come with an update, or work with this
|
|
return new WallEffect200();
|
|
}
|
|
}
|
|
|
|
/*
|
|
InternalEffect* InternalEffect::CreateDelay()
|
|
{
|
|
if (g_ForceFeedbackDevice.GetFirmwareVersionMajor() == 1) {
|
|
return new DelayEffect1XX();
|
|
} else { // Assume newer firmware versions will come with an update, or work with this
|
|
return new BehaviouralEffect1XX(ET_DELAY_200);
|
|
}
|
|
}
|
|
*/
|
|
|
|
InternalEffect* InternalEffect::CreateFromVFX(const DIEFFECT& diOriginal, EFFECT effect, ENVELOPE envelope, BYTE* pEffectParms, DWORD paramSize, HRESULT& hr)
|
|
{
|
|
InternalEffect* pReturnEffect = NULL;
|
|
|
|
if (g_ForceFeedbackDevice.GetFirmwareVersionMajor() == 1) {
|
|
return NULL;
|
|
}
|
|
|
|
// Fill in the DIEFFECT structure
|
|
DIEFFECT diEffect;
|
|
diEffect.dwSize = sizeof(DIEFFECT);
|
|
diEffect.dwFlags = DIEFF_POLAR | DIEFF_OBJECTOFFSETS;
|
|
|
|
// Only set from file, if default is asked for
|
|
if (diOriginal.dwDuration == DEFAULT_VFX_EFFECT_DURATION) {
|
|
diEffect.dwDuration = effect.m_Duration * 1000; // Zero will work as infinite just fine
|
|
} else {
|
|
diEffect.dwDuration = diOriginal.dwDuration;
|
|
}
|
|
|
|
// If zero is sent use the one from the file else use the one sent
|
|
if (diOriginal.dwSamplePeriod == 0) {
|
|
diEffect.dwSamplePeriod = 1000000/effect.m_ForceOutputRate;
|
|
} else {
|
|
diEffect.dwSamplePeriod = diOriginal.dwSamplePeriod;
|
|
}
|
|
|
|
// Only set from file if default is sent
|
|
if (diOriginal.dwGain == DEFAULT_VFX_EFFECT_GAIN) {
|
|
diEffect.dwGain = effect.m_Gain * 100;
|
|
} else {
|
|
diEffect.dwGain = diOriginal.dwGain;
|
|
}
|
|
|
|
// Need to find which bit is set
|
|
if (diOriginal.dwTriggerButton == DIEB_NOTRIGGER) {
|
|
if (effect.m_ButtonPlayMask == 0) {
|
|
diEffect.dwTriggerButton = DIEB_NOTRIGGER;
|
|
} else {
|
|
DWORD butt = effect.m_ButtonPlayMask;
|
|
short int buttNum = 0;
|
|
while ((butt & 1) == 0) {
|
|
butt >>= 1;
|
|
buttNum++;
|
|
ASSUME(buttNum >= 32);
|
|
}
|
|
diEffect.dwTriggerButton = DIDFT_MAKEINSTANCE(buttNum);
|
|
}
|
|
diEffect.dwTriggerRepeatInterval = 0;
|
|
} else {
|
|
diEffect.dwTriggerButton = diOriginal.dwTriggerButton;
|
|
diEffect.dwTriggerRepeatInterval = diOriginal.dwTriggerRepeatInterval;
|
|
}
|
|
|
|
diEffect.cbTypeSpecificParams = 0;
|
|
diEffect.lpvTypeSpecificParams = NULL;
|
|
|
|
diEffect.cAxes = 2;
|
|
diEffect.rgdwAxes = new DWORD[2];
|
|
if (diEffect.rgdwAxes == NULL)
|
|
{
|
|
goto do_dealloc;
|
|
}
|
|
diEffect.rgdwAxes[0] = DIJOFS_X;
|
|
diEffect.rgdwAxes[1] = DIJOFS_Y;
|
|
|
|
diEffect.rglDirection = new LONG[2];
|
|
if (diEffect.rglDirection == NULL)
|
|
{
|
|
goto do_dealloc;
|
|
}
|
|
if (diOriginal.rglDirection[0] == DEFAULT_VFX_EFFECT_DIRECTION) {
|
|
diEffect.rglDirection[0] = effect.m_DirectionAngle2D * 100;
|
|
} else {
|
|
diEffect.rglDirection[0] = diOriginal.rglDirection[0];
|
|
}
|
|
diEffect.rglDirection[1] = 0;
|
|
|
|
// Envelope
|
|
diEffect.lpEnvelope = new DIENVELOPE;
|
|
if (diEffect.lpEnvelope == NULL)
|
|
{
|
|
goto do_dealloc;
|
|
}
|
|
if (diOriginal.lpEnvelope != NULL) {
|
|
::memcpy(diEffect.lpEnvelope, diOriginal.lpEnvelope, sizeof(DIENVELOPE));
|
|
} else {
|
|
diEffect.lpEnvelope->dwSize = sizeof(DIENVELOPE);
|
|
diEffect.lpEnvelope->dwAttackLevel = envelope.m_StartAmp * (diEffect.dwGain/100);
|
|
diEffect.lpEnvelope->dwFadeLevel = envelope.m_EndAmp * (diEffect.dwGain/100);
|
|
diEffect.dwGain = diEffect.dwGain/100 * envelope.m_SustainAmp;
|
|
if (envelope.m_Type == TIME) { // time is in MSECs
|
|
diEffect.lpEnvelope->dwAttackTime = envelope.m_Attack * 1000;
|
|
diEffect.lpEnvelope->dwFadeTime = envelope.m_Decay * 1000;
|
|
} else { // Percentage of total time
|
|
diEffect.lpEnvelope->dwAttackTime = envelope.m_Attack * (diEffect.dwDuration/100);
|
|
diEffect.lpEnvelope->dwFadeTime = envelope.m_Decay * (diEffect.dwDuration/100);
|
|
}
|
|
}
|
|
|
|
switch(effect.m_Type) {
|
|
case EF_BEHAVIOR: {
|
|
DICONDITION* pDICondition = new DICONDITION[2];
|
|
if (pDICondition == NULL)
|
|
{
|
|
goto do_dealloc;
|
|
}
|
|
::memset(pDICondition, 0, sizeof(DICONDITION) * 2);
|
|
diEffect.cbTypeSpecificParams = sizeof(DICONDITION)*2;
|
|
diEffect.lpvTypeSpecificParams = pDICondition;
|
|
|
|
switch (effect.m_SubType) { // Switch for parameter filling
|
|
case BE_SPRING_2D:
|
|
case BE_DAMPER_2D:
|
|
case BE_INERTIA_2D: {
|
|
BE_SPRING_2D_PARAM* pParams = (BE_SPRING_2D_PARAM*)pEffectParms;
|
|
pDICondition[1].lOffset = pParams->m_YAxisCenter * 100;
|
|
pDICondition[1].lPositiveCoefficient = pParams->m_YKconstant * 100;
|
|
pDICondition[1].lNegativeCoefficient = pDICondition[1].lPositiveCoefficient;
|
|
pDICondition[1].dwPositiveSaturation = 10000;
|
|
pDICondition[1].dwNegativeSaturation = 10000;
|
|
// purposely fall through for first axis
|
|
}
|
|
case BE_SPRING:
|
|
case BE_DAMPER:
|
|
case BE_INERTIA: {
|
|
BE_SPRING_PARAM* pParams = (BE_SPRING_PARAM*)pEffectParms;
|
|
pDICondition[0].lOffset = pParams->m_AxisCenter * 100;
|
|
pDICondition[0].lPositiveCoefficient = pParams->m_Kconstant * 100;
|
|
pDICondition[0].lNegativeCoefficient = pDICondition[0].lPositiveCoefficient;
|
|
pDICondition[0].dwPositiveSaturation = 10000;
|
|
pDICondition[0].dwNegativeSaturation = 10000;
|
|
break;
|
|
}
|
|
case BE_FRICTION_2D: {
|
|
BE_FRICTION_2D_PARAM* pParams = (BE_FRICTION_2D_PARAM*)pEffectParms;
|
|
pDICondition[1].lPositiveCoefficient = pParams->m_YFconstant * 100;
|
|
pDICondition[1].lNegativeCoefficient = pDICondition[1].lPositiveCoefficient;
|
|
pDICondition[1].dwPositiveSaturation = 10000;
|
|
pDICondition[1].dwNegativeSaturation = 10000;
|
|
// purposely fall through for first axis
|
|
}
|
|
case BE_FRICTION: {
|
|
BE_FRICTION_PARAM* pParams = (BE_FRICTION_PARAM*)pEffectParms;
|
|
pDICondition[0].lPositiveCoefficient = pParams->m_Fconstant * 100;
|
|
pDICondition[0].lNegativeCoefficient = pDICondition[0].lPositiveCoefficient;
|
|
pDICondition[0].dwPositiveSaturation = 10000;
|
|
pDICondition[0].dwNegativeSaturation = 10000;
|
|
break;
|
|
}
|
|
case BE_WALL: { // This one is strangly simple
|
|
delete pDICondition;
|
|
diEffect.cbTypeSpecificParams = sizeof(BE_WALL_PARAM);
|
|
diEffect.lpvTypeSpecificParams = new BE_WALL_PARAM;
|
|
if (diEffect.lpvTypeSpecificParams != NULL) {
|
|
::memcpy(diEffect.lpvTypeSpecificParams, pEffectParms, sizeof(BE_WALL_PARAM));
|
|
// need to convert to DI values
|
|
BE_WALL_PARAM* pWallParms = (BE_WALL_PARAM*)(diEffect.lpvTypeSpecificParams);
|
|
pWallParms->m_WallConstant = pWallParms->m_WallConstant * 100;
|
|
pWallParms->m_WallAngle = pWallParms->m_WallAngle * 100;
|
|
pWallParms->m_WallDistance = pWallParms->m_WallDistance * 100;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
switch (effect.m_SubType) { // Switch for Creation
|
|
case BE_SPRING:
|
|
case BE_SPRING_2D: {
|
|
pReturnEffect = CreateSpring();
|
|
break;
|
|
}
|
|
case BE_DAMPER:
|
|
case BE_DAMPER_2D: {
|
|
pReturnEffect = CreateDamper();
|
|
break;
|
|
}
|
|
case BE_INERTIA:
|
|
case BE_INERTIA_2D: {
|
|
pReturnEffect = CreateInertia();
|
|
break;
|
|
}
|
|
case BE_FRICTION:
|
|
case BE_FRICTION_2D: {
|
|
pReturnEffect = CreateFriction();
|
|
break;
|
|
}
|
|
case BE_WALL: {
|
|
pReturnEffect = CreateWall();
|
|
break;
|
|
}
|
|
}
|
|
pDICondition = NULL;
|
|
break;
|
|
}
|
|
case EF_USER_DEFINED: {
|
|
UD_PARAM* pParams = (UD_PARAM*)pEffectParms;
|
|
DICUSTOMFORCE* pUserDefined = new DICUSTOMFORCE;
|
|
if (pUserDefined != NULL) {
|
|
diEffect.cbTypeSpecificParams = sizeof(DICUSTOMFORCE);
|
|
diEffect.lpvTypeSpecificParams = pUserDefined;
|
|
pUserDefined->cChannels = 1;
|
|
pUserDefined->dwSamplePeriod = diEffect.dwSamplePeriod;
|
|
pUserDefined->cSamples = pParams->m_NumVectors;
|
|
pUserDefined->rglForceData = pParams->m_pForceData; // Don't copy the data here, just push the pointer
|
|
for (UINT nextSample = 0; nextSample < pUserDefined->cSamples; nextSample++) {
|
|
ASSUME(pUserDefined->rglForceData[nextSample] <= 100 && pUserDefined->rglForceData[nextSample] >= -100); // Assume it is in the SWForce range (-100..100)
|
|
pUserDefined->rglForceData[nextSample] *= 100;
|
|
}
|
|
|
|
pReturnEffect = CreateCustomForce();
|
|
}
|
|
|
|
pUserDefined = NULL;
|
|
break;
|
|
}
|
|
case EF_SYNTHESIZED: { // Fill in the periodic structure
|
|
SE_PARAM* pParams = (SE_PARAM*)pEffectParms;
|
|
if (effect.m_SubType == SE_CONSTANT_FORCE) { // Special case
|
|
DICONSTANTFORCE* pCForce= new DICONSTANTFORCE;
|
|
if (pCForce != NULL) {
|
|
diEffect.cbTypeSpecificParams = sizeof(DICONSTANTFORCE);
|
|
diEffect.lpvTypeSpecificParams = pCForce;
|
|
pCForce->lMagnitude = pParams->m_MaxAmp * 100;
|
|
pCForce = NULL;
|
|
pReturnEffect = CreateConstantForce();
|
|
}
|
|
break;
|
|
}
|
|
if (effect.m_SubType == SE_RAMPUP || effect.m_SubType == SE_RAMPDOWN) { // Yet another special case
|
|
DIRAMPFORCE* pRForce = new DIRAMPFORCE;
|
|
if (pRForce != NULL) {
|
|
diEffect.cbTypeSpecificParams = sizeof(DIRAMPFORCE);
|
|
diEffect.lpvTypeSpecificParams = pRForce;
|
|
if (effect.m_SubType == SE_RAMPUP) {
|
|
pRForce->lStart = pParams->m_MinAmp * 100;
|
|
pRForce->lEnd = pParams->m_MaxAmp * 100;
|
|
} else { // RampDown (special case of a special case)
|
|
pRForce->lStart = pParams->m_MaxAmp * 100;
|
|
pRForce->lEnd = pParams->m_MinAmp * 100;
|
|
}
|
|
pRForce = NULL;
|
|
pReturnEffect = CreateRamp();
|
|
}
|
|
break;
|
|
}
|
|
|
|
|
|
if (pParams->m_SampleRate == 0) {
|
|
pParams->m_SampleRate = DEFAULT_JOLT_FORCE_RATE;
|
|
}
|
|
if (effect.m_ForceOutputRate == 0) {
|
|
effect.m_ForceOutputRate = DEFAULT_JOLT_FORCE_RATE;
|
|
}
|
|
|
|
DIPERIODIC* pPeriodic = new DIPERIODIC;
|
|
if (pPeriodic == NULL) {
|
|
break;
|
|
}
|
|
diEffect.cbTypeSpecificParams = sizeof(DIPERIODIC);
|
|
diEffect.lpvTypeSpecificParams = pPeriodic;
|
|
pPeriodic->dwMagnitude = (pParams->m_MaxAmp - pParams->m_MinAmp)/2 * 100;
|
|
pPeriodic->lOffset = (pParams->m_MaxAmp + pParams->m_MinAmp)/2 * 100;
|
|
pPeriodic->dwPhase = 0;
|
|
if (pParams->m_Freq == 0) { // Avoid division by 0
|
|
pPeriodic->dwPeriod = 0;
|
|
} else {
|
|
pPeriodic->dwPeriod = 1000000 / pParams->m_Freq;
|
|
}
|
|
|
|
switch (effect.m_SubType) {
|
|
case SE_SINE: {
|
|
pReturnEffect = CreateSine();
|
|
break;
|
|
}
|
|
case SE_COSINE: {
|
|
pPeriodic->dwPhase = 9000;
|
|
pReturnEffect = CreateSine();
|
|
break;
|
|
}
|
|
case SE_SQUAREHIGH: {
|
|
pReturnEffect = CreateSquare();
|
|
break;
|
|
}
|
|
case SE_SQUARELOW: {
|
|
pPeriodic->dwPhase = 18000;
|
|
pReturnEffect = CreateSquare();
|
|
break;
|
|
}
|
|
case SE_TRIANGLEUP: {
|
|
pReturnEffect = CreateTriangle();
|
|
break;
|
|
}
|
|
case SE_TRIANGLEDOWN: {
|
|
pPeriodic->dwPhase = 18000;
|
|
pReturnEffect = CreateTriangle();
|
|
break;
|
|
}
|
|
|
|
case SE_SAWTOOTHUP: {
|
|
pReturnEffect = CreateSawtoothUp();
|
|
break;
|
|
}
|
|
case SE_SAWTOOTHDOWN: {
|
|
pReturnEffect = CreateSawtoothDown();
|
|
break;
|
|
}
|
|
}
|
|
pPeriodic = NULL;
|
|
break;
|
|
}
|
|
case EF_RTC_SPRING: {
|
|
diEffect.cbTypeSpecificParams = sizeof(RTCSPRING_PARAM);
|
|
diEffect.lpvTypeSpecificParams = new RTCSPRING_PARAM;
|
|
if (diEffect.lpvTypeSpecificParams != NULL) {
|
|
::memcpy(diEffect.lpvTypeSpecificParams, pEffectParms, sizeof(RTCSPRING_PARAM));
|
|
pReturnEffect = CreateRTCSpring();
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (pReturnEffect != NULL) {
|
|
hr = pReturnEffect->Create(diEffect);
|
|
if (FAILED(hr)) {
|
|
delete pReturnEffect;
|
|
pReturnEffect = NULL;
|
|
}
|
|
}
|
|
|
|
do_dealloc:
|
|
// Deallocate allocated DIEFFECT stuff
|
|
if (diEffect.lpvTypeSpecificParams != NULL) {
|
|
delete diEffect.lpvTypeSpecificParams;
|
|
}
|
|
if (diEffect.rglDirection != NULL) {
|
|
delete diEffect.rglDirection;
|
|
}
|
|
if (diEffect.rgdwAxes != NULL) {
|
|
delete diEffect.rgdwAxes;
|
|
}
|
|
if (diEffect.lpEnvelope != NULL) {
|
|
delete diEffect.lpEnvelope;
|
|
}
|
|
|
|
return pReturnEffect;
|
|
}
|
|
|
|
HRESULT InternalEffect::Create(const DIEFFECT& diEffect)
|
|
{
|
|
// We don't support more than 2 axes, and 0 is probably an error
|
|
if ((diEffect.cAxes > 2) || (diEffect.cAxes == 0)) {
|
|
return SFERR_NO_SUPPORT;
|
|
}
|
|
|
|
HRESULT hr = SUCCESS;
|
|
|
|
// Set up the axis mask
|
|
m_AxisMask = 0;
|
|
for (unsigned int axisIndex = 0; axisIndex < diEffect.cAxes; axisIndex++) {
|
|
DWORD axisNumber = DIDFT_GETINSTANCE(diEffect.rgdwAxes[axisIndex]);
|
|
m_AxisMask |= 1 << axisNumber;
|
|
}
|
|
m_AxesReversed = (DIDFT_GETINSTANCE(diEffect.rgdwAxes[0]) == 1);
|
|
|
|
// Set the trigger play button
|
|
if (diEffect.dwTriggerButton != DIEB_NOTRIGGER) {
|
|
m_TriggerPlayButton = DIDFT_GETINSTANCE(diEffect.dwTriggerButton) + 1;
|
|
if (m_TriggerPlayButton == 9) { // We don't support button 9 playback (start button?)
|
|
return SFERR_NO_SUPPORT;
|
|
}
|
|
if (m_TriggerPlayButton > 10) { // We don't support mapping above 10
|
|
return SFERR_NO_SUPPORT;
|
|
}
|
|
} else {
|
|
m_TriggerPlayButton = 0;
|
|
}
|
|
|
|
m_TriggerRepeat = diEffect.dwTriggerRepeatInterval;
|
|
if (m_TriggerRepeat > MAX_TIME_200) {
|
|
hr = DI_TRUNCATED;
|
|
m_TriggerRepeat = MAX_TIME_200;
|
|
}
|
|
|
|
// Check coordinate sytems and change to polar
|
|
if (diEffect.dwFlags & DIEFF_SPHERICAL) { // We don't support sperical (3 axis force)
|
|
return SFERR_NO_SUPPORT; // .. since got by axis check, programmer goofed up
|
|
}
|
|
if (diEffect.dwFlags & DIEFF_POLAR) {
|
|
if (diEffect.cAxes != 2) { // Polar coordinate must have two axes of data (because DX says so)
|
|
return SFERR_INVALID_PARAM;
|
|
}
|
|
m_EffectAngle = diEffect.rglDirection[0]; // in [0] even if reversed
|
|
if (m_AxesReversed) { // Indicates (-1, 0) as origin instead of (0, -1)
|
|
m_EffectAngle += 27000;
|
|
}
|
|
m_EffectAngle %= 36000;
|
|
} else if (diEffect.dwFlags & DIEFF_CARTESIAN) { // Convert to polar
|
|
if (diEffect.cAxes == 1) { // Fairly easy conversion
|
|
if (X_AXIS & m_AxisMask) {
|
|
m_EffectAngle = 9000;
|
|
} else {
|
|
m_EffectAngle = 0;
|
|
}
|
|
} else { // Multiple axis cartiesian
|
|
int xDirection = diEffect.rglDirection[0];
|
|
int yDirection = diEffect.rglDirection[1];
|
|
if (m_AxesReversed == TRUE) {
|
|
yDirection = xDirection;
|
|
xDirection = diEffect.rglDirection[1];
|
|
}
|
|
double angle = atan2(double(yDirection), double(xDirection)) * 180.0/3.14159;
|
|
// Switch it to a proper quadrant integer
|
|
int nAngle = 90;
|
|
if (angle >= 0.0) {
|
|
nAngle -= int(angle + 0.5);
|
|
} else {
|
|
nAngle -= int(angle - 0.5);
|
|
}
|
|
if (nAngle < 0) {
|
|
nAngle += 360;
|
|
} else if (nAngle >= 360) {
|
|
nAngle -= 360;
|
|
}
|
|
m_EffectAngle = nAngle * 100;
|
|
}
|
|
} else { // What, is there some other format?
|
|
ASSUME_NOT_REACHED();
|
|
return SFERR_INVALID_PARAM; // Untill someone says otherwise there was an error
|
|
}
|
|
|
|
// Find the percent for each axis (for axis mapping)
|
|
double projectionAngle = double(m_EffectAngle)/18000.0 * 3.14159; // Convert to radians
|
|
// Sin^2(a) + Cos^2(a) = 1
|
|
double xProj = ::sin(projectionAngle); // DI has 0 degs at (1, 0) not (0, 1)
|
|
double yProj = ::cos(projectionAngle);
|
|
xProj *= xProj;
|
|
yProj *= yProj;
|
|
m_PercentX = DWORD(xProj * 100.0 + 0.05);
|
|
m_PercentY = DWORD(yProj * 100.0 + 0.05);
|
|
|
|
// Duration and gain
|
|
m_Duration = diEffect.dwDuration;
|
|
if (m_Duration == INFINITE) {
|
|
m_Duration = 0; // 0 represents infinite
|
|
} else if (m_Duration > MAX_TIME_200) {
|
|
hr = DI_TRUNCATED;
|
|
m_Duration = MAX_TIME_200;
|
|
}
|
|
m_Gain = diEffect.dwGain;
|
|
if (m_Gain > 10000) {
|
|
hr = DI_TRUNCATED;
|
|
m_Gain = 10000;
|
|
}
|
|
|
|
// Sample period
|
|
m_SamplePeriod = diEffect.dwSamplePeriod;
|
|
if (m_SamplePeriod > MAX_TIME_200) {
|
|
hr = DI_TRUNCATED;
|
|
m_SamplePeriod = MAX_TIME_200;
|
|
} else if (m_SamplePeriod == 0) { // Indicates a default should be used
|
|
m_SamplePeriod = 2000; // 500htz is the default (2000 micro-secs period)
|
|
}
|
|
|
|
return hr;
|
|
}
|
|
|
|
HRESULT InternalEffect::Modify(InternalEffect& diEffect, DWORD modFlags)
|
|
{
|
|
g_pDataPackager->ClearPackets();
|
|
return SFERR_NO_SUPPORT;
|
|
}
|
|
|
|
|
|
BYTE InternalEffect::ComputeChecksum(const DataPacket& packet, short int numFields)
|
|
{
|
|
if (packet.m_pData == NULL) {
|
|
ASSUME_NOT_REACHED();
|
|
return 0;
|
|
}
|
|
|
|
BYTE checkSum = 0;
|
|
for (short int index = 5; index < numFields; index++) { // Skip header
|
|
checkSum += packet.m_pData[index];
|
|
}
|
|
return ((-checkSum) & 0x7f);
|
|
}
|
|
|
|
void InternalEffect::FillSysExHeader(DataPacket& packet) const
|
|
{
|
|
// SysEx Header
|
|
packet.m_pData[0] = SYS_EX_CMD; // SysEX CMD
|
|
packet.m_pData[1] = 0; // Escape to Manufacturer ID
|
|
packet.m_pData[2] = MS_MANUFACTURER_ID & 0x7f; // Manufacturer High Byte
|
|
packet.m_pData[3] = (MS_MANUFACTURER_ID >> 8) & 0x7f; // Manufacturer Low Byte (note shifted 8!)
|
|
}
|
|
|
|
void InternalEffect::FillHeader1XX(DataPacket& packet, BYTE effectType, BYTE effectID) const
|
|
{
|
|
FillSysExHeader(packet);
|
|
packet.m_pData[4] = JOLT_PRODUCT_ID; // Product ID
|
|
|
|
// What to do params
|
|
packet.m_pData[5] = DNLOAD_DATA | DL_PLAY_STORE | X_AXIS | Y_AXIS; // OpCode
|
|
packet.m_pData[6] = effectType; // Effect Type
|
|
packet.m_pData[7] = effectID; // Effect or NEW_EFFECT_ID
|
|
|
|
// Effect parms
|
|
int duration = m_Duration/DURATION_SCALE_1XX;
|
|
packet.m_pData[8] = BYTE(duration & 0x7F); // Duration Low MidiByte
|
|
packet.m_pData[9] = BYTE(duration >> 7) & 0x7F; // Duration High MidiByte
|
|
}
|
|
|
|
void InternalEffect::FillHeader200(DataPacket& packet, BYTE effectType, BYTE effectID) const
|
|
{
|
|
FillSysExHeader(packet);
|
|
packet.m_pData[4] = ZEP_PRODUCT_ID; // Product ID
|
|
|
|
// What to do params
|
|
// packet.m_pData[5] = DOWNLOAD_OP_200 | STORE_ACTION_200 | X_AXIS_200 | Y_AXIS_200; // OpCode
|
|
packet.m_pData[5] = (DOWNLOAD_OP_200 << 4) | (STORE_ACTION_200 << 2) | AXIS_ANGLE_200; // OpCode
|
|
packet.m_pData[6] = effectType; // Effect Type
|
|
packet.m_pData[7] = effectID; // Effect or NEW_EFFECT_ID
|
|
|
|
// Effect parms
|
|
unsigned short int duration = 0;
|
|
if (m_Duration != 0) {
|
|
duration = unsigned short(m_Duration/DURATION_SCALE_200);
|
|
if (duration == 0) {
|
|
duration = 1; // We don't want to round down to 0 (infinite)
|
|
}
|
|
}
|
|
packet.m_pData[8] = BYTE(duration & 0x7F); // Duration Low MidiByte
|
|
packet.m_pData[9] = BYTE(duration >> 7) & 0x7F; // Duration High MidiByte
|
|
}
|
|
|
|
HRESULT InternalEffect::FillModifyPacket1XX(BYTE packetIndex, BYTE paramIndex, DWORD value) const
|
|
{
|
|
DataPacket* setIndexPacket = g_pDataPackager->GetPacket(packetIndex);
|
|
if ((setIndexPacket == NULL) || (!setIndexPacket->AllocateBytes(3))) {
|
|
g_pDataPackager->ClearPackets();
|
|
return SFERR_DRIVER_ERROR;
|
|
}
|
|
setIndexPacket->m_pData[0] = EFFECT_CMD | DEFAULT_MIDI_CHANNEL;
|
|
setIndexPacket->m_pData[1] = SET_INDEX | BYTE(paramIndex << 2);
|
|
setIndexPacket->m_pData[2] = m_DeviceEffectID & 0x7F;
|
|
setIndexPacket->m_AckNackMethod = g_ForceFeedbackDevice.GetAckNackMethod(REGBITS_SETINDEX);
|
|
setIndexPacket->m_NumberOfRetries = MAX_RETRY_COUNT; // Probably differentiate this
|
|
|
|
// Packet to set modify data[index] of current effect
|
|
DataPacket* modifyParamPacket = g_pDataPackager->GetPacket(packetIndex+1);
|
|
if ((modifyParamPacket == NULL) || (!modifyParamPacket->AllocateBytes(3))) {
|
|
g_pDataPackager->ClearPackets();
|
|
return SFERR_DRIVER_ERROR;
|
|
}
|
|
|
|
modifyParamPacket->m_pData[0] = MODIFY_CMD | DEFAULT_MIDI_CHANNEL;
|
|
modifyParamPacket->m_pData[1] = BYTE(value & 0x7f);
|
|
modifyParamPacket->m_pData[2] = BYTE(value >> 7) & 0x7f;
|
|
modifyParamPacket->m_AckNackMethod = g_ForceFeedbackDevice.GetAckNackMethod(REGBITS_MODIFYPARAM);
|
|
modifyParamPacket->m_NumberOfRetries = MAX_RETRY_COUNT; // Probably differentiate this
|
|
|
|
return SUCCESS;
|
|
}
|
|
|
|
HRESULT InternalEffect::FillModifyPacket200(BYTE packetIndex, BYTE paramIndex, DWORD value) const
|
|
{
|
|
BYTE low = BYTE(value & 0x7F);
|
|
BYTE high = BYTE(value >> 7) & 0x7F;
|
|
return FillModifyPacket200(packetIndex, paramIndex, low, high);
|
|
}
|
|
|
|
HRESULT InternalEffect::FillModifyPacket200(BYTE packetIndex, BYTE paramIndex, BYTE low, BYTE high) const
|
|
{
|
|
DataPacket* modifyPacket = g_pDataPackager->GetPacket(packetIndex);
|
|
if ((modifyPacket == NULL) || (!modifyPacket->AllocateBytes(6))) {
|
|
g_pDataPackager->ClearPackets();
|
|
return SFERR_DRIVER_ERROR;
|
|
}
|
|
BYTE id = m_DeviceEffectID;
|
|
if (id == 0) {
|
|
id = m_EffectID;
|
|
}
|
|
modifyPacket->m_pData[0] = MODIFY_CMD_200;
|
|
modifyPacket->m_pData[1] = 0; // Temporary for checksum calc.
|
|
modifyPacket->m_pData[2] = paramIndex & 0x3F;
|
|
modifyPacket->m_pData[3] = id & 0x7F;
|
|
modifyPacket->m_pData[4] = low & 0x7F;
|
|
modifyPacket->m_pData[5] = high & 0x7F;
|
|
|
|
// New checksum method just to be annoying
|
|
BYTE checksum = 0;
|
|
for (int i = 0; i < 6; i++) {
|
|
checksum += modifyPacket->m_pData[i];
|
|
}
|
|
checksum = 0 - checksum;
|
|
checksum &= 0xFF;
|
|
modifyPacket->m_pData[1] = BYTE(checksum & 0x7F);
|
|
modifyPacket->m_pData[2] |= BYTE(checksum >> 1) & 0x40;
|
|
|
|
modifyPacket->m_AckNackMethod = g_ForceFeedbackDevice.GetAckNackMethod(REGBITS_MODIFYPARAM);
|
|
modifyPacket->m_NumberOfRetries = MAX_RETRY_COUNT; // Probably differentiate this
|
|
|
|
return SUCCESS;
|
|
}
|
|
|
|
BOOL InternalEffect::IsReallyPlaying(BOOL& multiCheckStop)
|
|
{
|
|
if (multiCheckStop == TRUE) {
|
|
return m_IsPossiblyPlaying;
|
|
}
|
|
multiCheckStop = TRUE;
|
|
|
|
if (m_IsPossiblyPlaying == FALSE) {
|
|
return FALSE;
|
|
}
|
|
|
|
// Do actual check
|
|
// Create a command/data packet - send it of to the stick
|
|
HRESULT hr = g_pDataPackager->GetEffectStatus(m_DeviceEffectID);
|
|
if (hr != SUCCESS) {
|
|
return TRUE;
|
|
}
|
|
ACKNACK ackNack;
|
|
hr = g_pDataTransmitter->Transmit(ackNack); // Send it off
|
|
if (hr != SUCCESS) {
|
|
return TRUE;
|
|
}
|
|
|
|
// Use result returned by GetAckNackData in Transmit
|
|
DWORD dwIn = ackNack.dwEffectStatus;
|
|
|
|
// Interpret result (cooked RUNNING_MASK_200 becomes SWDEV_STS_EFFECT_RUNNING)
|
|
m_IsPossiblyPlaying = ((g_ForceFeedbackDevice.GetDriverVersionMajor() != 1) && (dwIn & SWDEV_STS_EFFECT_RUNNING));
|
|
|
|
return m_IsPossiblyPlaying;
|
|
}
|
|
|
|
/******************* BehviouralEffect class ******************/
|
|
HRESULT BehaviouralEffect::Create(const DIEFFECT& diEffect)
|
|
{
|
|
// Validation Check
|
|
ASSUME_NOT_NULL(diEffect.lpvTypeSpecificParams);
|
|
if (diEffect.lpvTypeSpecificParams == NULL) {
|
|
return SFERR_INVALID_PARAM;
|
|
}
|
|
|
|
// Let the base class do its magic
|
|
HRESULT hr = InternalEffect::Create(diEffect);
|
|
if (FAILED(hr)) {
|
|
return hr;
|
|
}
|
|
|
|
// What axis is where
|
|
short int xIndex = 0;
|
|
short int yIndex = 1;
|
|
if (m_AxesReversed) { // Reverse the index
|
|
xIndex = 1;
|
|
yIndex = 0;
|
|
}
|
|
|
|
if (diEffect.cAxes == 2) {
|
|
if (diEffect.cbTypeSpecificParams == sizeof(DICONDITION)) { // Angled condition
|
|
m_AxisMask &= ~Y_AXIS; // Pretend there is only one axis
|
|
xIndex = 0; // Doesn't matter if reversed
|
|
} else if (diEffect.cbTypeSpecificParams == sizeof(DICONDITION) * 2) { // Two axis effect does no axis mapping
|
|
m_PercentX = 100;
|
|
m_PercentY = 0;
|
|
} else {
|
|
return SFERR_INVALID_PARAM;
|
|
}
|
|
}
|
|
|
|
// Fill the type specific (reverses array if needed)
|
|
BYTE* pTypeSpecificArray = (BYTE*)m_ConditionData;
|
|
BYTE* pDITypeSpecific = (BYTE*)(diEffect.lpvTypeSpecificParams);
|
|
|
|
if ((m_AxisMask & X_AXIS) != 0) {
|
|
::memcpy(pTypeSpecificArray, pDITypeSpecific + xIndex, sizeof(DICONDITION));
|
|
} else {
|
|
::memset(pTypeSpecificArray, 0, sizeof(DICONDITION)); // No X zero it out
|
|
}
|
|
if ((m_AxisMask & Y_AXIS) != 0) {
|
|
::memcpy(pTypeSpecificArray + sizeof(DICONDITION), pDITypeSpecific + yIndex, sizeof(DICONDITION));
|
|
} else {
|
|
::memset(pTypeSpecificArray + sizeof(DICONDITION), 0, sizeof(DICONDITION)); // No Y zero it out
|
|
}
|
|
|
|
// Fix for Andretti, it thinks 0 is default
|
|
if (m_ConditionData[0].dwPositiveSaturation == 0) {
|
|
m_ConditionData[0].dwPositiveSaturation = 10000;
|
|
}
|
|
if (m_ConditionData[0].dwNegativeSaturation == 0) {
|
|
m_ConditionData[0].dwNegativeSaturation = 10000;
|
|
}
|
|
|
|
// Check for overzealous numbers
|
|
for (UINT i = 0; i < diEffect.cAxes; i++) {
|
|
if (m_ConditionData[0].lOffset > 10000) {
|
|
hr = DI_TRUNCATED;
|
|
m_ConditionData[0].lOffset = 10000;
|
|
} else if (m_ConditionData[0].lOffset < -10000) {
|
|
hr = DI_TRUNCATED;
|
|
m_ConditionData[0].lOffset = 10000;
|
|
}
|
|
if (m_ConditionData[0].lPositiveCoefficient > 10000) {
|
|
hr = DI_TRUNCATED;
|
|
m_ConditionData[0].lPositiveCoefficient = 10000;
|
|
} else if (m_ConditionData[0].lPositiveCoefficient < -10000) {
|
|
hr = DI_TRUNCATED;
|
|
m_ConditionData[0].lPositiveCoefficient = -10000;
|
|
}
|
|
if (m_ConditionData[0].lNegativeCoefficient > 10000) {
|
|
hr = DI_TRUNCATED;
|
|
m_ConditionData[0].lNegativeCoefficient = 10000;
|
|
} else if (m_ConditionData[0].lNegativeCoefficient < -10000) {
|
|
hr = DI_TRUNCATED;
|
|
m_ConditionData[0].lNegativeCoefficient = -10000;
|
|
}
|
|
if (m_ConditionData[0].dwPositiveSaturation > 10000) {
|
|
hr = DI_TRUNCATED;
|
|
m_ConditionData[0].dwPositiveSaturation = 10000;
|
|
}
|
|
if (m_ConditionData[0].dwNegativeSaturation > 10000) {
|
|
hr = DI_TRUNCATED;
|
|
m_ConditionData[0].dwNegativeSaturation = 10000;
|
|
}
|
|
if (m_ConditionData[0].lDeadBand > 10000) {
|
|
hr = DI_TRUNCATED;
|
|
m_ConditionData[0].lDeadBand = 10000;
|
|
} else if (m_ConditionData[0].lDeadBand < -10000) {
|
|
hr = DI_TRUNCATED;
|
|
m_ConditionData[0].lDeadBand = -10000;
|
|
}
|
|
}
|
|
|
|
return hr;
|
|
}
|
|
|
|
/******************* BehaviouralEffect1XX class ******************/
|
|
HRESULT BehaviouralEffect1XX::FillCreatePacket(DataPacket& packet) const
|
|
{
|
|
// Packet to set modify data[index] of current effect
|
|
if (!packet.AllocateBytes(22)) {
|
|
return SFERR_DRIVER_ERROR;
|
|
}
|
|
|
|
// Fill in the Generic Effect Information
|
|
FillHeader1XX(packet, m_TypeID, NEW_EFFECT_ID);
|
|
|
|
packet.m_pData[10]= BYTE(g_TriggerMap1XX[m_TriggerPlayButton] & 0x7F); // Button play mask Low MidiByte
|
|
packet.m_pData[11]= BYTE(g_TriggerMap1XX[m_TriggerPlayButton] >> 7) & 0x7F; // Button play mask High MidiByte
|
|
|
|
// Behavioural Specific Parms
|
|
int twoByte = ((ConstantX()/COEFFICIENT_SCALE_1XX) * m_Gain) / GAIN_PERCENTAGE_SCALE;
|
|
packet.m_pData[12]= twoByte & 0x7F; // Spring/Damper/... Constant X Low
|
|
packet.m_pData[13]= (twoByte >> 7) & 0x7F; // Spring/Damper/... Constant X High
|
|
twoByte = ((ConstantY()/COEFFICIENT_SCALE_1XX) * m_Gain) / GAIN_PERCENTAGE_SCALE;
|
|
packet.m_pData[14]= twoByte & 0x7F; // Spring/Damper/... Constant Y Low
|
|
packet.m_pData[15]= (twoByte >> 7) & 0x7F; // Spring/Damper/... Constant Y High
|
|
twoByte = CenterX()/COEFFICIENT_SCALE_1XX;
|
|
packet.m_pData[16]= twoByte & 0x7F; // Spring/Damper/... Center X Low
|
|
packet.m_pData[17]= (twoByte >> 7) & 0x7F; // Spring/Damper/... Center X High
|
|
twoByte = CenterY()/COEFFICIENT_SCALE_1XX;
|
|
packet.m_pData[18]= twoByte & 0x7F; // Spring/Damper/... Center Y Low
|
|
packet.m_pData[19]= (twoByte >> 7) & 0x7F; // Spring/Damper/... Center Y High
|
|
packet.m_pData[20]= ComputeChecksum(packet, 20); // Checksum
|
|
|
|
// End of packet
|
|
packet.m_pData[21]= MIDI_EOX; // End of SysEX packet
|
|
|
|
return SUCCESS;
|
|
}
|
|
|
|
HRESULT BehaviouralEffect1XX::Modify(InternalEffect& newEffect, DWORD modFlags)
|
|
{
|
|
g_pDataPackager->ClearPackets();
|
|
AdjustModifyParams(newEffect, modFlags);
|
|
|
|
HRESULT hr = SUCCESS;
|
|
BehaviouralEffect1XX* pEffect = (BehaviouralEffect1XX*)(&newEffect);
|
|
BYTE nextPacket = 0;
|
|
if (modFlags & DIEP_DURATION) {
|
|
hr = FillModifyPacket1XX(nextPacket, INDEX_DURATION, pEffect->m_Duration/DURATION_SCALE_1XX);
|
|
nextPacket += 2;
|
|
}
|
|
if (modFlags & DIEP_TRIGGERBUTTON) {
|
|
hr = FillModifyPacket1XX(nextPacket, INDEX_TRIGGERBUTTONMASK, g_TriggerMap1XX[pEffect->m_TriggerPlayButton]);
|
|
nextPacket += 2;
|
|
}
|
|
|
|
BOOL gainChanged = modFlags & DIEP_GAIN;
|
|
if (modFlags & DIEP_TYPESPECIFICPARAMS) { // Find which ones
|
|
if (ConstantX() != pEffect->ConstantX() || gainChanged) {
|
|
hr = FillModifyPacket1XX(nextPacket, INDEX_X_COEEFICIENT,
|
|
((pEffect->ConstantX()/COEFFICIENT_SCALE_1XX) * pEffect->m_Gain) / GAIN_PERCENTAGE_SCALE);
|
|
nextPacket += 2;
|
|
}
|
|
if (ConstantY() != pEffect->ConstantY() || gainChanged) {
|
|
hr = FillModifyPacket1XX(nextPacket, INDEX_Y_COEEFICIENT,
|
|
((pEffect->ConstantY()/COEFFICIENT_SCALE_1XX) * pEffect->m_Gain) / GAIN_PERCENTAGE_SCALE);
|
|
nextPacket += 2;
|
|
}
|
|
if (m_HasCenter == TRUE) {
|
|
if (CenterX() != pEffect->CenterX()) {
|
|
hr = FillModifyPacket1XX(nextPacket, INDEX_X_CENTER, pEffect->CenterX()/COEFFICIENT_SCALE_1XX);
|
|
nextPacket += 2;
|
|
}
|
|
if (CenterY() != pEffect->CenterY()) {
|
|
hr = FillModifyPacket1XX(nextPacket, INDEX_Y_CENTER, pEffect->CenterY()/COEFFICIENT_SCALE_1XX);
|
|
}
|
|
}
|
|
}
|
|
|
|
return hr;
|
|
}
|
|
|
|
void BehaviouralEffect1XX::AdjustModifyParams(InternalEffect& newEffect, DWORD& modFlags) const
|
|
{
|
|
// Check to see if values being modified are acceptable
|
|
DWORD possMod = DIEP_DURATION | DIEP_GAIN | DIEP_TRIGGERBUTTON | DIEP_TYPESPECIFICPARAMS;
|
|
if ((g_TotalModifiable & modFlags & possMod) == 0) { // Nothing to modify?
|
|
modFlags = 0;
|
|
return;
|
|
}
|
|
|
|
BehaviouralEffect1XX* pEffect = (BehaviouralEffect1XX*)(&newEffect);
|
|
unsigned short numPackets = 0;
|
|
if (modFlags & DIEP_DURATION) {
|
|
if (m_Duration == pEffect->m_Duration) {
|
|
modFlags &= ~DIEP_DURATION; // Remove duration flag, unchanged
|
|
} else {
|
|
numPackets += 2;
|
|
}
|
|
}
|
|
if (modFlags & DIEP_TRIGGERBUTTON) {
|
|
if (m_TriggerPlayButton == pEffect->m_TriggerPlayButton) {
|
|
modFlags &= ~DIEP_TRIGGERBUTTON; // Remove trigger flag, unchanged
|
|
} else {
|
|
numPackets += 2;
|
|
}
|
|
}
|
|
|
|
int numTypeSpecificChanged = 0;
|
|
if (modFlags & DIEP_GAIN) { // Did gain really change
|
|
numTypeSpecificChanged = 4;
|
|
} else {
|
|
modFlags &= ~DIEP_GAIN;
|
|
}
|
|
|
|
// If type specific or gain
|
|
if ((modFlags & DIEP_TYPESPECIFICPARAMS) || (numTypeSpecificChanged == 0)) { // Find which ones
|
|
if (numTypeSpecificChanged == 0) { // Kx, Ky not already taken care of by gain?
|
|
if (ConstantX() != pEffect->ConstantX()) {
|
|
numTypeSpecificChanged = 2;
|
|
}
|
|
if (ConstantY() != pEffect->ConstantY()) {
|
|
numTypeSpecificChanged += 2;
|
|
}
|
|
}
|
|
if ((m_HasCenter == TRUE) && (modFlags & DIEP_TYPESPECIFICPARAMS)) { // Don't check these if gain only
|
|
if (CenterX() != pEffect->CenterX()) {
|
|
numTypeSpecificChanged += 2;
|
|
}
|
|
if (CenterY() != pEffect->CenterY()) {
|
|
numTypeSpecificChanged += 2;
|
|
}
|
|
|
|
}
|
|
}
|
|
if (numTypeSpecificChanged == 0) {
|
|
modFlags &= ~DIEP_TYPESPECIFICPARAMS; // No type specific changed
|
|
} else {
|
|
numPackets += (unsigned short)numTypeSpecificChanged;
|
|
}
|
|
|
|
if (numPackets == 0) { // That was easy nothing changed
|
|
return;
|
|
}
|
|
|
|
g_pDataPackager->AllocateDataPackets(numPackets);
|
|
}
|
|
|
|
/******************* RTCSpring1XX class ******************/
|
|
RTCSpring1XX::RTCSpring1XX() : BehaviouralEffect()
|
|
{
|
|
m_EffectID = SYSTEM_RTCSPRING_ID;
|
|
|
|
// Set defaults
|
|
m_ConditionData[0].lPositiveCoefficient = DEFAULT_RTC_KX;
|
|
m_ConditionData[1].lPositiveCoefficient = DEFAULT_RTC_KY;
|
|
m_ConditionData[0].lOffset = DEFAULT_RTC_X0;
|
|
m_ConditionData[1].lOffset = DEFAULT_RTC_Y0;
|
|
m_ConditionData[0].dwPositiveSaturation = DEFAULT_RTC_XSAT;
|
|
m_ConditionData[1].dwPositiveSaturation = DEFAULT_RTC_YSAT;
|
|
m_ConditionData[0].lDeadBand = DEFAULT_RTC_XDBAND;
|
|
m_ConditionData[1].lDeadBand = DEFAULT_RTC_YDBAND;
|
|
}
|
|
|
|
HRESULT RTCSpring1XX::Create(const DIEFFECT& diEffect)
|
|
{
|
|
// Validation Check
|
|
if (diEffect.cbTypeSpecificParams != sizeof(DICONDITION) * 2) {
|
|
return SFERR_INVALID_PARAM;
|
|
}
|
|
if (diEffect.lpvTypeSpecificParams == NULL) {
|
|
ASSUME_NOT_REACHED();
|
|
return SFERR_INVALID_PARAM;
|
|
}
|
|
|
|
// Get the data
|
|
DICONDITION* condition = (DICONDITION*)(diEffect.lpvTypeSpecificParams);
|
|
m_ConditionData[0].lPositiveCoefficient = condition[0].lPositiveCoefficient;
|
|
m_ConditionData[1].lPositiveCoefficient = condition[1].lPositiveCoefficient;
|
|
m_ConditionData[0].lOffset = condition[0].lOffset;
|
|
m_ConditionData[1].lOffset = condition[1].lOffset;
|
|
m_ConditionData[0].dwPositiveSaturation = condition[0].dwPositiveSaturation;
|
|
m_ConditionData[1].dwPositiveSaturation = condition[1].dwPositiveSaturation;
|
|
m_ConditionData[0].lDeadBand = condition[0].lDeadBand;
|
|
m_ConditionData[1].lDeadBand = condition[1].lDeadBand;
|
|
|
|
return SUCCESS;
|
|
}
|
|
|
|
HRESULT RTCSpring1XX::FillCreatePacket(DataPacket& packet) const
|
|
{
|
|
ASSUME_NOT_REACHED();
|
|
return SUCCESS;
|
|
}
|
|
|
|
HRESULT RTCSpring1XX::Modify(InternalEffect& newEffect, DWORD modFlags)
|
|
{
|
|
// Sanity Check
|
|
if (g_pDataPackager == NULL) {
|
|
ASSUME_NOT_REACHED();
|
|
return SFERR_DRIVER_ERROR;
|
|
}
|
|
g_pDataPackager->ClearPackets();
|
|
|
|
RTCSpring1XX* pNewRTCSpring = (RTCSpring1XX*)(&newEffect);
|
|
|
|
// Find number of packets needed
|
|
unsigned short numPackets = 0;
|
|
if (ConstantX() != pNewRTCSpring->ConstantX()) {
|
|
numPackets += 2;
|
|
}
|
|
if (ConstantY() != pNewRTCSpring->ConstantY()) {
|
|
numPackets += 2;
|
|
}
|
|
if (CenterX() != pNewRTCSpring->CenterX()) {
|
|
numPackets += 2;
|
|
}
|
|
if (CenterY() != pNewRTCSpring->CenterY()) {
|
|
numPackets += 2;
|
|
}
|
|
if (SaturationX() != pNewRTCSpring->SaturationX()) {
|
|
numPackets += 2;
|
|
}
|
|
if (SaturationY() != pNewRTCSpring->SaturationY()) {
|
|
numPackets += 2;
|
|
}
|
|
if (DeadBandX() != pNewRTCSpring->DeadBandX()) {
|
|
numPackets += 2;
|
|
}
|
|
if (DeadBandY() != pNewRTCSpring->DeadBandY()) {
|
|
numPackets += 2;
|
|
}
|
|
|
|
if (numPackets == 0) {
|
|
return SUCCESS;
|
|
}
|
|
|
|
// Allocate a packets for sending modify command
|
|
if (!g_pDataPackager->AllocateDataPackets(numPackets)) {
|
|
return SFERR_DRIVER_ERROR;
|
|
}
|
|
|
|
// Fill the packets
|
|
BYTE nextPacket = 0;
|
|
if (ConstantX() != pNewRTCSpring->ConstantX()) {
|
|
FillModifyPacket1XX(nextPacket, INDEX_RTC_COEEFICIENT_X, pNewRTCSpring->ConstantX());
|
|
nextPacket += 2;
|
|
}
|
|
if (ConstantY() != pNewRTCSpring->ConstantY()) {
|
|
FillModifyPacket1XX(nextPacket, INDEX_RTC_COEEFICIENT_Y, pNewRTCSpring->ConstantY());
|
|
nextPacket += 2;
|
|
}
|
|
if (CenterX() != pNewRTCSpring->CenterX()) {
|
|
FillModifyPacket1XX(nextPacket, INDEX_RTC_CENTER_X, pNewRTCSpring->CenterX());
|
|
nextPacket += 2;
|
|
}
|
|
if (CenterY() != pNewRTCSpring->CenterY()) {
|
|
FillModifyPacket1XX(nextPacket, INDEX_RTC_CENTER_Y, pNewRTCSpring->CenterY());
|
|
nextPacket += 2;
|
|
}
|
|
if (SaturationX() != pNewRTCSpring->SaturationX()) {
|
|
FillModifyPacket1XX(nextPacket, INDEX_RTC_SATURATION_X, pNewRTCSpring->SaturationX());
|
|
nextPacket += 2;
|
|
}
|
|
if (SaturationY() != pNewRTCSpring->SaturationY()) {
|
|
FillModifyPacket1XX(nextPacket, INDEX_RTC_SATURATION_Y, pNewRTCSpring->SaturationY());
|
|
nextPacket += 2;
|
|
}
|
|
if (DeadBandX() != pNewRTCSpring->DeadBandX()) {
|
|
FillModifyPacket1XX(nextPacket, INDEX_RTC_DEADBAND_X, pNewRTCSpring->DeadBandX());
|
|
nextPacket += 2;
|
|
}
|
|
if (DeadBandY() != pNewRTCSpring->DeadBandY()) {
|
|
FillModifyPacket1XX(nextPacket, INDEX_RTC_DEADBAND_Y, pNewRTCSpring->DeadBandY());
|
|
}
|
|
|
|
return SUCCESS;
|
|
}
|
|
|
|
/******************* BehaviouralEffect200 class ******************/
|
|
BYTE BehaviouralEffect200::GetRepeatIndex() const
|
|
{
|
|
return INDEX_BE_REPEAT_200;
|
|
}
|
|
|
|
void BehaviouralEffect200::ComputeDsAndFs()
|
|
{
|
|
// Assume single axis for now (X) - Will have to add second axis mapping
|
|
|
|
// Figure out distances
|
|
// long int d2 = m_ConditionData[0].lOffset - m_ConditionData[0].lDeadBand;
|
|
// long int d3 = m_ConditionData[0].lOffset + m_ConditionData[0].lDeadBand;
|
|
long int d2 = - m_ConditionData[0].lDeadBand;
|
|
long int d3 = m_ConditionData[0].lDeadBand;
|
|
long int d1 = -10000;
|
|
long int d4 = 10000;
|
|
|
|
// Compute force at -100 percent position
|
|
float negCoeff = float(m_ConditionData[0].lNegativeCoefficient)/float(10000.0);
|
|
long int f100Neg = long int((10000 + d2) * negCoeff);
|
|
// Check vs proper saturation
|
|
if (negCoeff > 0) {
|
|
if (unsigned long(f100Neg) > m_ConditionData[0].dwNegativeSaturation) {
|
|
f100Neg = m_ConditionData[0].dwNegativeSaturation;
|
|
d1 = long int (-1 * f100Neg/negCoeff) - d2; // Refigure D1
|
|
}
|
|
} else if (negCoeff < 0) {
|
|
if (unsigned long(-f100Neg) > m_ConditionData[0].dwNegativeSaturation) {
|
|
f100Neg = -1 * m_ConditionData[0].dwNegativeSaturation;
|
|
d1 = long int (-1 * f100Neg/negCoeff) - d2; // Refigure D1
|
|
}
|
|
}
|
|
// Compute Force include axis-mapping
|
|
f100Neg = long int(f100Neg/100 * (m_PercentX + (m_PercentY * g_ForceFeedbackDevice.GetYMappingPercent(m_TypeID))/100));
|
|
m_Fs[0] = BYTE(float(f100Neg/DIDISTANCE_TO_PERCENT + 100)/POSITIVE_PERCENT_SCALE) & 0x7F;
|
|
|
|
// Compute force at +100 percent position
|
|
float posCoeff = float(-m_ConditionData[0].lPositiveCoefficient)/float(10000.0);
|
|
long int f100Pos = long int((10000 - d3) * posCoeff);
|
|
// Check vs proper saturation
|
|
if (posCoeff > 0) {
|
|
if (unsigned long(f100Pos) > m_ConditionData[0].dwPositiveSaturation) {
|
|
f100Pos = m_ConditionData[0].dwPositiveSaturation;
|
|
d4 = long int (f100Pos/posCoeff) + d3; // Refigure D4
|
|
}
|
|
} else if (posCoeff < 0) {
|
|
if (unsigned long(-f100Pos) > m_ConditionData[0].dwPositiveSaturation) {
|
|
f100Pos = -1 * m_ConditionData[0].dwPositiveSaturation;
|
|
d4 = long int(f100Pos/posCoeff) + d3; // Refigure D4
|
|
}
|
|
}
|
|
// Compute Force include axis-mapping
|
|
f100Pos = long int(f100Pos/100 * (m_PercentX + (m_PercentY * g_ForceFeedbackDevice.GetYMappingPercent(m_TypeID))/100));
|
|
m_Fs[3] = BYTE(float(f100Pos/DIDISTANCE_TO_PERCENT + 100)/POSITIVE_PERCENT_SCALE) & 0x7F;
|
|
|
|
|
|
// Convert to device percentages (0 to +126 --- +63 = 0 percent)
|
|
m_Ds[0] = BYTE(float(d1/DIDISTANCE_TO_PERCENT + 100)/POSITIVE_PERCENT_SCALE) & 0x7F;
|
|
m_Ds[1] = BYTE(float(d2/DIDISTANCE_TO_PERCENT + 100)/POSITIVE_PERCENT_SCALE) & 0x7F;
|
|
m_Ds[2] = BYTE(float(d3/DIDISTANCE_TO_PERCENT + 100)/POSITIVE_PERCENT_SCALE) & 0x7F;
|
|
m_Ds[3] = BYTE(float(d4/DIDISTANCE_TO_PERCENT + 100)/POSITIVE_PERCENT_SCALE) & 0x7F;
|
|
|
|
if (m_TypeID == ET_SPRING_200) { // Add proper offsets (squished by max percent)
|
|
// Convert forces to device percentages (0 to +126 --- +63 = 0 percent)
|
|
// m_Fs[0] - Done above
|
|
LONG offset = LONG(float(float(g_ForceFeedbackDevice.GetSpringOffset())/10000.0) * float(f100Neg));
|
|
m_Fs[1] = BYTE(float(offset/DIDISTANCE_TO_PERCENT + 100)/POSITIVE_PERCENT_SCALE) & 0x7F;
|
|
offset = LONG(float(float(g_ForceFeedbackDevice.GetSpringOffset())/10000.0) * float(f100Pos));
|
|
m_Fs[2] = BYTE(float(offset/DIDISTANCE_TO_PERCENT + 100)/POSITIVE_PERCENT_SCALE) & 0x7F;
|
|
// m_Fs[3] - Done above
|
|
} else {
|
|
// Convert forces to device percentages (0 to +126 --- +63 = 0 percent)
|
|
// m_Fs[0] - Done above
|
|
m_Fs[1] = 63; // 0
|
|
m_Fs[2] = 63; // 0
|
|
// m_Fs[3] - Done above
|
|
}
|
|
}
|
|
|
|
HRESULT BehaviouralEffect200::Create(const DIEFFECT& diEffect)
|
|
{
|
|
HRESULT hr = BehaviouralEffect::Create(diEffect);
|
|
if (FAILED(hr)) {
|
|
return hr;
|
|
}
|
|
|
|
// Compute behavioural params
|
|
ComputeDsAndFs();
|
|
return hr;
|
|
}
|
|
|
|
UINT BehaviouralEffect200::GetModifyOnlyNeeded() const
|
|
{
|
|
UINT retCount = 0;
|
|
|
|
if (m_TriggerPlayButton != 0) { // Trigger Button
|
|
retCount++;
|
|
}
|
|
if (m_TriggerRepeat != 0) { // Trigger repeat
|
|
retCount++;
|
|
}
|
|
if (m_Gain != 10000) { // Gain
|
|
retCount++;
|
|
}
|
|
if (m_ConditionData[0].lOffset != 0) { // Center of behaviour
|
|
retCount++;
|
|
}
|
|
|
|
return retCount;
|
|
}
|
|
|
|
HRESULT BehaviouralEffect200::FillModifyOnlyParms() const
|
|
{
|
|
if (g_pDataPackager == NULL) {
|
|
ASSUME_NOT_REACHED(); // This is only called from DataPackager::Create()
|
|
return SFERR_DRIVER_ERROR;
|
|
}
|
|
|
|
HRESULT hr = SUCCESS;
|
|
BYTE nextPacket = 1;
|
|
if (m_TriggerPlayButton != 0) { // Trigger Button
|
|
hr = FillModifyPacket200(nextPacket, INDEX_BE_BUTTONMAP_200, g_TriggerMap200[m_TriggerPlayButton]);
|
|
nextPacket++;
|
|
}
|
|
if (m_TriggerRepeat != 0) { // Trigger repeat
|
|
hr = FillModifyPacket200(nextPacket, INDEX_BE_BUTTONREPEAT_200, m_TriggerRepeat/DURATION_SCALE_200);
|
|
nextPacket++;
|
|
}
|
|
if (m_Gain != 10000) { // Gain
|
|
hr = FillModifyPacket200(nextPacket, INDEX_BE_GAIN_200, DWORD(float(m_Gain)/GAIN_SCALE_200));
|
|
nextPacket++;
|
|
}
|
|
if (m_ConditionData[0].lOffset != 0) { // Center of behaviour
|
|
long int deviceUnits = m_ConditionData[0].lOffset/BEHAVIOUR_CENTER_SCALE_200 + BEHAVIOUR_CENTER_200;
|
|
BYTE lowByte = BYTE(deviceUnits & 0x7F);
|
|
BYTE highByte = BYTE(deviceUnits >> 7) & 0x7F;
|
|
hr = FillModifyPacket200(nextPacket, INDEX_BE_CENTER_200, lowByte, highByte);
|
|
nextPacket++;
|
|
}
|
|
|
|
return hr;
|
|
}
|
|
|
|
HRESULT BehaviouralEffect200::FillCreatePacket(DataPacket& packet) const
|
|
{
|
|
// Packet to set modify data[index] of current effect
|
|
if (!packet.AllocateBytes(21)) {
|
|
return SFERR_DRIVER_ERROR;
|
|
}
|
|
|
|
// Fill in the Generic Effect Information
|
|
FillHeader200(packet, m_TypeID, NEW_EFFECT_ID);
|
|
|
|
// All of the below items fit in one MidiByte (0..126/127) after conversion
|
|
unsigned short effectAngle = unsigned short(float(m_EffectAngle)/ANGLE_SCALE_200);
|
|
packet.m_pData[10]= BYTE(effectAngle & 0x7F); // Effect Angle
|
|
|
|
// Computed in create
|
|
packet.m_pData[11]= m_Ds[0];
|
|
packet.m_pData[12]= m_Fs[0];
|
|
packet.m_pData[13]= m_Ds[1];
|
|
packet.m_pData[14]= m_Fs[1];
|
|
packet.m_pData[15]= m_Ds[2];
|
|
packet.m_pData[16]= m_Fs[2];
|
|
packet.m_pData[17]= m_Ds[3];
|
|
packet.m_pData[18]= m_Fs[3];
|
|
|
|
// End this puppy
|
|
packet.m_pData[19]= ComputeChecksum(packet, 19); // Checksum
|
|
packet.m_pData[20]= MIDI_EOX; // End of SysEX packet
|
|
|
|
return SUCCESS;
|
|
}
|
|
|
|
|
|
HRESULT BehaviouralEffect200::Modify(InternalEffect& newEffect, DWORD modFlags)
|
|
{
|
|
g_pDataPackager->ClearPackets();
|
|
HRESULT adjustResult = AdjustModifyParams(newEffect, modFlags);
|
|
if (FAILED(adjustResult)) {
|
|
return adjustResult;
|
|
}
|
|
|
|
HRESULT hr = SUCCESS;
|
|
BehaviouralEffect200* pEffect = (BehaviouralEffect200*)(&newEffect);
|
|
BYTE nextPacket = 0;
|
|
if (modFlags & DIEP_DURATION) {
|
|
hr = FillModifyPacket200(nextPacket, INDEX_BE_DURATION_200, pEffect->m_Duration/DURATION_SCALE_200);
|
|
nextPacket++;
|
|
}
|
|
if (modFlags & DIEP_TRIGGERBUTTON) {
|
|
hr = FillModifyPacket200(nextPacket, INDEX_BE_BUTTONMAP_200, g_TriggerMap200[pEffect->m_TriggerPlayButton]);
|
|
nextPacket++;
|
|
}
|
|
if (modFlags & DIEP_TRIGGERREPEATINTERVAL) {
|
|
hr = FillModifyPacket200(nextPacket, INDEX_BE_BUTTONREPEAT_200, pEffect->m_TriggerRepeat/DURATION_SCALE_200);
|
|
nextPacket++;
|
|
}
|
|
|
|
if (modFlags & DIEP_GAIN) {
|
|
hr = FillModifyPacket200(nextPacket, INDEX_BE_GAIN_200, DWORD(float(pEffect->m_Gain)/GAIN_SCALE_200));
|
|
nextPacket++;
|
|
}
|
|
|
|
if (modFlags & DIEP_TYPESPECIFICPARAMS) { // Send changed items
|
|
for (int i = 0; i < 4; i++) {
|
|
if ((m_Ds[i] != pEffect->m_Ds[i]) || (m_Fs[i] != pEffect->m_Fs[i])) {
|
|
hr = FillModifyPacket200(nextPacket, INDEX_D1F1_200 + i, pEffect->m_Ds[i], pEffect->m_Fs[i]);
|
|
nextPacket++;
|
|
}
|
|
}
|
|
if (m_ConditionData[0].lOffset != pEffect->m_ConditionData[0].lOffset) { // Center of behaviour
|
|
long int deviceUnits = pEffect->m_ConditionData[0].lOffset/BEHAVIOUR_CENTER_SCALE_200 + BEHAVIOUR_CENTER_200;
|
|
BYTE lowByte = BYTE(deviceUnits & 0x7F);
|
|
BYTE highByte = BYTE(deviceUnits >> 7) & 0x7F;
|
|
hr = FillModifyPacket200(nextPacket, INDEX_BE_CENTER_200, lowByte, highByte);
|
|
nextPacket++;
|
|
}
|
|
}
|
|
|
|
if (hr == SUCCESS) {
|
|
return adjustResult;
|
|
}
|
|
|
|
return hr;
|
|
}
|
|
|
|
HRESULT BehaviouralEffect200::AdjustModifyParams(InternalEffect& newEffect, DWORD& modFlags)
|
|
{
|
|
// Check to see if values being modified are acceptable
|
|
DWORD possMod = DIEP_DURATION | DIEP_GAIN | DIEP_TRIGGERBUTTON | DIEP_TRIGGERREPEATINTERVAL | DIEP_TYPESPECIFICPARAMS | DIEP_DIRECTION;
|
|
/* if ((g_TotalModifiable & modFlags & possMod) == 0) { // Nothing to modify?
|
|
modFlags = 0;
|
|
return SFERR_NO_SUPPORT;
|
|
}
|
|
*/
|
|
BehaviouralEffect200* pEffect = (BehaviouralEffect200*)(&newEffect);
|
|
unsigned short numPackets = 0;
|
|
|
|
HRESULT hr = SUCCESS;;
|
|
|
|
if ((modFlags & (~possMod & DIEP_ALLPARAMS)) != 0) {
|
|
modFlags &= possMod;
|
|
hr = S_FALSE; // Cannot modify all they asked for
|
|
}
|
|
|
|
BOOL playingChecked = FALSE;
|
|
if (modFlags & DIEP_DURATION) {
|
|
if (m_Duration == pEffect->m_Duration) {
|
|
modFlags &= ~DIEP_DURATION; // Remove duration flag, unchanged
|
|
} else {
|
|
if (IsReallyPlaying(playingChecked) == TRUE) {
|
|
return DIERR_EFFECTPLAYING;
|
|
} else {
|
|
numPackets++;
|
|
}
|
|
}
|
|
}
|
|
if (modFlags & DIEP_TRIGGERBUTTON) {
|
|
if (m_TriggerPlayButton == pEffect->m_TriggerPlayButton) {
|
|
modFlags &= ~DIEP_TRIGGERBUTTON; // Remove trigger flag, unchanged
|
|
} else {
|
|
numPackets++;
|
|
}
|
|
}
|
|
if (modFlags & DIEP_TRIGGERREPEATINTERVAL) {
|
|
if (m_TriggerRepeat == pEffect->m_TriggerRepeat) {
|
|
modFlags &= ~DIEP_TRIGGERREPEATINTERVAL; // Remove trigger repeat flag, unchanged
|
|
} else {
|
|
numPackets++;
|
|
}
|
|
}
|
|
if (modFlags & DIEP_GAIN) { // Did gain really change
|
|
if (m_Gain == pEffect->m_Gain) {
|
|
modFlags &= ~DIEP_GAIN; // Remove trigger flag, unchanged
|
|
} else {
|
|
numPackets++;
|
|
}
|
|
}
|
|
if (modFlags & DIEP_DIRECTION) {
|
|
modFlags |= DIEP_TYPESPECIFICPARAMS;
|
|
}
|
|
if (modFlags & DIEP_TYPESPECIFICPARAMS) { // Find which ones (if any)
|
|
int numTypeSpecificChanged = 0;
|
|
for (int i = 0; i < 4; i++) {
|
|
// Ds and Fs are changed togeather
|
|
if ((m_Ds[i] != pEffect->m_Ds[i]) || (m_Fs[i] != pEffect->m_Fs[i])) {
|
|
numTypeSpecificChanged++;
|
|
}
|
|
}
|
|
if (m_ConditionData[0].lOffset != pEffect->m_ConditionData[0].lOffset) { // Center of behaviour
|
|
numTypeSpecificChanged++;
|
|
}
|
|
if (numTypeSpecificChanged == 0) {
|
|
modFlags &= ~DIEP_TYPESPECIFICPARAMS; // No type specific changed
|
|
} else {
|
|
numPackets += (USHORT)numTypeSpecificChanged;
|
|
}
|
|
}
|
|
|
|
if (numPackets != 0) { // Was anything really changed
|
|
g_pDataPackager->AllocateDataPackets(numPackets);
|
|
}
|
|
|
|
return hr;
|
|
}
|
|
|
|
/******************* RTCSpring200 class ******************/
|
|
RTCSpring200::RTCSpring200() : BehaviouralEffect200(ET_SPRING_200)
|
|
{
|
|
m_EffectID = ID_RTCSPRING_200;
|
|
|
|
// Set defaults
|
|
m_ConditionData[0].lPositiveCoefficient = DEFAULT_RTC_KX;
|
|
m_ConditionData[1].lPositiveCoefficient = DEFAULT_RTC_KY;
|
|
m_ConditionData[0].lNegativeCoefficient = DEFAULT_RTC_KX;
|
|
m_ConditionData[1].lNegativeCoefficient = DEFAULT_RTC_KY;
|
|
m_ConditionData[0].lOffset = DEFAULT_RTC_X0;
|
|
m_ConditionData[1].lOffset = DEFAULT_RTC_Y0;
|
|
m_ConditionData[0].dwPositiveSaturation = DEFAULT_RTC_XSAT;
|
|
m_ConditionData[1].dwPositiveSaturation = DEFAULT_RTC_YSAT;
|
|
m_ConditionData[0].dwNegativeSaturation = DEFAULT_RTC_XSAT;
|
|
m_ConditionData[1].dwNegativeSaturation = DEFAULT_RTC_YSAT;
|
|
m_ConditionData[0].lDeadBand = DEFAULT_RTC_XDBAND;
|
|
m_ConditionData[1].lDeadBand = DEFAULT_RTC_YDBAND;
|
|
}
|
|
|
|
HRESULT RTCSpring200::Create(const DIEFFECT& diEffect)
|
|
{
|
|
// Validation Check
|
|
ASSUME_NOT_NULL(diEffect.lpvTypeSpecificParams);
|
|
if (diEffect.lpvTypeSpecificParams == NULL) {
|
|
return SFERR_INVALID_PARAM;
|
|
}
|
|
if (diEffect.cbTypeSpecificParams == sizeof(DICONDITION)*2) {
|
|
::memcpy(m_ConditionData, diEffect.lpvTypeSpecificParams, sizeof(DICONDITION)*2);
|
|
} else if (diEffect.cbTypeSpecificParams == sizeof(DICONDITION)) {
|
|
::memcpy(m_ConditionData, diEffect.lpvTypeSpecificParams, sizeof(DICONDITION));
|
|
::memset(m_ConditionData + 1, 0, sizeof(DICONDITION));
|
|
} else if (diEffect.cbTypeSpecificParams == sizeof(RTCSPRING_PARAM)) {
|
|
::memset(m_ConditionData, 0, sizeof(DICONDITION)*2);
|
|
|
|
RTCSPRING_PARAM* pOldRTCParam = (RTCSPRING_PARAM*)(diEffect.lpvTypeSpecificParams);
|
|
m_ConditionData[0].lPositiveCoefficient = pOldRTCParam->m_XKConstant;
|
|
m_ConditionData[1].lPositiveCoefficient = pOldRTCParam->m_YKConstant;
|
|
m_ConditionData[0].lNegativeCoefficient = pOldRTCParam->m_XKConstant;
|
|
m_ConditionData[1].lNegativeCoefficient = pOldRTCParam->m_YKConstant;
|
|
m_ConditionData[0].lOffset = pOldRTCParam->m_XAxisCenter;
|
|
m_ConditionData[1].lOffset = pOldRTCParam->m_YAxisCenter;
|
|
m_ConditionData[0].dwPositiveSaturation = DWORD(pOldRTCParam->m_XSaturation);
|
|
m_ConditionData[1].dwPositiveSaturation = DWORD(pOldRTCParam->m_YSaturation);
|
|
m_ConditionData[0].dwNegativeSaturation = DWORD(pOldRTCParam->m_XSaturation);
|
|
m_ConditionData[1].dwNegativeSaturation = DWORD(pOldRTCParam->m_YSaturation);
|
|
m_ConditionData[0].lDeadBand = pOldRTCParam->m_XDeadBand;
|
|
m_ConditionData[1].lDeadBand = pOldRTCParam->m_YDeadBand;
|
|
} else {
|
|
return SFERR_INVALID_PARAM;
|
|
}
|
|
|
|
// Check parameters for truncation
|
|
BOOL truncated = FALSE;
|
|
if (m_ConditionData[0].lPositiveCoefficient > 10000) {
|
|
truncated = TRUE;
|
|
m_ConditionData[0].lPositiveCoefficient = 10000;
|
|
} else if (m_ConditionData[0].lPositiveCoefficient < -10000) {
|
|
truncated = TRUE;
|
|
m_ConditionData[0].lPositiveCoefficient = -10000;
|
|
}
|
|
if (m_ConditionData[0].lNegativeCoefficient > 10000) {
|
|
truncated = TRUE;
|
|
m_ConditionData[0].lNegativeCoefficient = 10000;
|
|
} else if (m_ConditionData[0].lNegativeCoefficient < -10000) {
|
|
truncated = TRUE;
|
|
m_ConditionData[0].lNegativeCoefficient = -10000;
|
|
}
|
|
if (m_ConditionData[0].lOffset > 10000) {
|
|
truncated = TRUE;
|
|
m_ConditionData[0].lOffset = 10000;
|
|
} else if (m_ConditionData[0].lOffset < -10000) {
|
|
truncated = TRUE;
|
|
m_ConditionData[0].lOffset = -10000;
|
|
}
|
|
if (m_ConditionData[0].dwPositiveSaturation > 10000) {
|
|
truncated = TRUE;
|
|
m_ConditionData[0].dwPositiveSaturation = 10000;
|
|
}
|
|
if (m_ConditionData[0].dwNegativeSaturation > 10000) {
|
|
truncated = TRUE;
|
|
m_ConditionData[0].dwNegativeSaturation = 10000;
|
|
}
|
|
if (m_ConditionData[0].lDeadBand > 10000) {
|
|
truncated = TRUE;
|
|
m_ConditionData[0].lDeadBand = 10000;
|
|
} else if (m_ConditionData[0].lDeadBand < -10000) {
|
|
truncated = TRUE;
|
|
m_ConditionData[0].lDeadBand = -10000;
|
|
}
|
|
|
|
m_PercentX = 100;
|
|
m_PercentY = 0;
|
|
|
|
// Compute behavioural params
|
|
ComputeDsAndFs();
|
|
if (truncated == TRUE) {
|
|
return DI_TRUNCATED;
|
|
}
|
|
return SUCCESS;
|
|
}
|
|
|
|
HRESULT RTCSpring200::FillCreatePacket(DataPacket& packet) const
|
|
{
|
|
return SUCCESS;
|
|
}
|
|
|
|
UINT RTCSpring200::GetModifyOnlyNeeded() const
|
|
{
|
|
return 3; // One is added for create. There is no Create, so we return 1 less than needed
|
|
}
|
|
|
|
HRESULT RTCSpring200::FillModifyOnlyParms() const
|
|
{
|
|
if (g_pDataPackager == NULL) {
|
|
ASSUME_NOT_REACHED(); // This is only called from DataPackager::Create()
|
|
return SFERR_DRIVER_ERROR;
|
|
}
|
|
|
|
for (BYTE i = 0; i < 4; i++) {
|
|
FillModifyPacket200(i, INDEX_D1F1_200 + i, m_Ds[i], m_Fs[i]);
|
|
}
|
|
|
|
return SUCCESS;
|
|
}
|
|
|
|
|
|
HRESULT RTCSpring200::Modify(InternalEffect& newEffect, DWORD modFlags)
|
|
{
|
|
// Sanity Check
|
|
if (g_pDataPackager == NULL) {
|
|
ASSUME_NOT_REACHED();
|
|
return SFERR_DRIVER_ERROR;
|
|
}
|
|
g_pDataPackager->AllocateDataPackets(4);
|
|
RTCSpring200* pNewRTCSpring = (RTCSpring200*)(&newEffect);
|
|
for (BYTE i = 0; i < 4; i++) {
|
|
FillModifyPacket200(i, INDEX_D1F1_200 + i, pNewRTCSpring->m_Ds[i], pNewRTCSpring->m_Fs[i]);
|
|
}
|
|
|
|
return SUCCESS;
|
|
}
|
|
|
|
|
|
/************** FictionEffect1XX class **********************/
|
|
override HRESULT FrictionEffect1XX::FillCreatePacket(DataPacket& packet) const
|
|
{
|
|
// Packet to set modify data[index] of current effect
|
|
if (!packet.AllocateBytes(22)) {
|
|
return SFERR_DRIVER_ERROR;
|
|
}
|
|
|
|
// Fill in the Generic Effect Information
|
|
FillHeader1XX(packet, m_TypeID, NEW_EFFECT_ID);
|
|
|
|
packet.m_pData[10]= BYTE(g_TriggerMap1XX[m_TriggerPlayButton] & 0x7F); // Button play mask Low MidiByte
|
|
packet.m_pData[11]= BYTE(g_TriggerMap1XX[m_TriggerPlayButton] >> 7) & 0x7F; // Button play mask High MidiByte
|
|
|
|
// Friction Specific Parms
|
|
int twoByte = ((ConstantX()/COEFFICIENT_SCALE_1XX) * m_Gain) / GAIN_PERCENTAGE_SCALE;
|
|
packet.m_pData[12]= twoByte & 0x7F; // Spring/Damper/... Constant X Low
|
|
packet.m_pData[13]= (twoByte >> 7) & 0x7F; // Spring/Damper/... Constant X High
|
|
twoByte = ((ConstantY()/COEFFICIENT_SCALE_1XX) * m_Gain) / GAIN_PERCENTAGE_SCALE;
|
|
packet.m_pData[14]= twoByte & 0x7F; // Spring/Damper/... Constant Y Low
|
|
packet.m_pData[15]= (twoByte >> 7) & 0x7F; // Spring/Damper/... Constant Y High
|
|
|
|
packet.m_pData[20]= ComputeChecksum(packet, 20); // Checksum
|
|
|
|
// End of packet
|
|
packet.m_pData[21]= MIDI_EOX; // End of SysEX packet
|
|
|
|
return SUCCESS;
|
|
}
|
|
|
|
/******************* FrictionEffect200 class *********************/
|
|
BYTE FrictionEffect200::GetRepeatIndex() const
|
|
{
|
|
return INDEX_FE_REPEAT_200;
|
|
}
|
|
|
|
UINT FrictionEffect200::GetModifyOnlyNeeded() const
|
|
{
|
|
UINT retCount = 0;
|
|
|
|
if (m_TriggerPlayButton != 0) { // Trigger Button
|
|
retCount++;
|
|
}
|
|
if (m_TriggerRepeat != 0) { // Trigger repeat
|
|
retCount++;
|
|
}
|
|
if (m_Gain != 10000) { // Gain
|
|
retCount++;
|
|
}
|
|
|
|
return retCount;
|
|
}
|
|
|
|
HRESULT FrictionEffect200::FillModifyOnlyParms() const
|
|
{
|
|
if (g_pDataPackager == NULL) {
|
|
ASSUME_NOT_REACHED(); // This is only called from DataPackager::Create()
|
|
return SFERR_DRIVER_ERROR;
|
|
}
|
|
|
|
HRESULT hr = SUCCESS;
|
|
BYTE nextPacket = 1;
|
|
if (m_TriggerPlayButton != 0) { // Trigger Button
|
|
hr = FillModifyPacket200(nextPacket, INDEX_FE_BUTTONMAP_200, g_TriggerMap200[m_TriggerPlayButton]);
|
|
nextPacket++;
|
|
}
|
|
if (m_TriggerRepeat != 0) { // Trigger repeat
|
|
hr = FillModifyPacket200(nextPacket, INDEX_FE_BUTTONREPEAT_200, m_TriggerRepeat/DURATION_SCALE_200);
|
|
nextPacket++;
|
|
}
|
|
if (m_Gain != 10000) { // Gain
|
|
hr = FillModifyPacket200(nextPacket, INDEX_FE_GAIN_200, DWORD(float(m_Gain)/GAIN_SCALE_200));
|
|
nextPacket++;
|
|
}
|
|
|
|
return hr;
|
|
}
|
|
|
|
HRESULT FrictionEffect200::FillCreatePacket(DataPacket& packet) const
|
|
{
|
|
// Packet to set modify data[index] of current effect
|
|
if (!packet.AllocateBytes(14)) {
|
|
return SFERR_DRIVER_ERROR;
|
|
}
|
|
|
|
// Fill in the Generic Effect Information
|
|
FillHeader200(packet, m_TypeID, NEW_EFFECT_ID);
|
|
|
|
// All of the below items fit in one MidiByte (0..126/127) after conversion
|
|
unsigned short effectAngle = unsigned short(float(m_EffectAngle)/ANGLE_SCALE_200);
|
|
packet.m_pData[10]= BYTE(effectAngle & 0x7F); // Effect Angle
|
|
|
|
// Computed in create
|
|
packet.m_pData[11]= BYTE(float(ConstantX() + 10000)/FRICTION_SCALE_200) & 0x7F;
|
|
|
|
// End this puppy
|
|
packet.m_pData[12]= ComputeChecksum(packet, 12); // Checksum
|
|
packet.m_pData[13]= MIDI_EOX; // End of SysEX packet
|
|
|
|
return SUCCESS;
|
|
}
|
|
|
|
HRESULT FrictionEffect200::Modify(InternalEffect& newEffect, DWORD modFlags)
|
|
{
|
|
g_pDataPackager->ClearPackets();
|
|
|
|
HRESULT adjustResult = AdjustModifyParams(newEffect, modFlags);
|
|
if (FAILED(adjustResult)) {
|
|
return adjustResult;
|
|
}
|
|
|
|
FrictionEffect200* pEffect = (FrictionEffect200*)(&newEffect);
|
|
BYTE nextPacket = 0;
|
|
DWORD calc_byte;
|
|
|
|
HRESULT hr = SUCCESS;
|
|
if (modFlags & DIEP_DURATION) {
|
|
hr = FillModifyPacket200(nextPacket, INDEX_FE_DURATION_200, pEffect->m_Duration/DURATION_SCALE_200);
|
|
nextPacket++;
|
|
}
|
|
|
|
if (modFlags & DIEP_TRIGGERBUTTON) {
|
|
hr = FillModifyPacket200(nextPacket, INDEX_FE_BUTTONMAP_200, g_TriggerMap200[pEffect->m_TriggerPlayButton]);
|
|
nextPacket++;
|
|
}
|
|
if (modFlags & DIEP_TRIGGERREPEATINTERVAL) {
|
|
hr = FillModifyPacket200(nextPacket, INDEX_FE_BUTTONREPEAT_200, pEffect->m_TriggerRepeat/DURATION_SCALE_200);
|
|
nextPacket++;
|
|
}
|
|
if (modFlags & DIEP_GAIN) {
|
|
calc_byte = DWORD(float(pEffect->m_Gain)/GAIN_SCALE_200);
|
|
hr = FillModifyPacket200(nextPacket, INDEX_FE_GAIN_200, calc_byte);
|
|
nextPacket++;
|
|
}
|
|
|
|
if (modFlags & DIEP_SAMPLEPERIOD) {
|
|
calc_byte = DWORD(pEffect->m_SamplePeriod/DURATION_SCALE_200);
|
|
hr = FillModifyPacket200(nextPacket, INDEX_PE_SAMPLE_PERIOD_200, calc_byte);
|
|
nextPacket++;
|
|
}
|
|
|
|
if (modFlags & DIEP_TYPESPECIFICPARAMS) { // Send changed items
|
|
if (ConstantX() != pEffect->ConstantX()) {
|
|
calc_byte = DWORD(float(pEffect->ConstantX() + 10000)/FRICTION_SCALE_200) & 0x7F;
|
|
hr = FillModifyPacket200(nextPacket, INDEX_FE_COEEFICIENT_200, calc_byte);
|
|
nextPacket++;
|
|
}
|
|
}
|
|
|
|
if (hr == SUCCESS) {
|
|
return adjustResult;
|
|
}
|
|
return hr;
|
|
}
|
|
|
|
HRESULT FrictionEffect200::AdjustModifyParams(InternalEffect& newEffect, DWORD& modFlags)
|
|
{
|
|
// Check to see if values being modified are acceptable
|
|
DWORD possMod = DIEP_DURATION | DIEP_GAIN | DIEP_TRIGGERBUTTON | DIEP_TRIGGERREPEATINTERVAL | DIEP_TYPESPECIFICPARAMS | DIEP_DIRECTION;
|
|
/* if ((g_TotalModifiable & modFlags & possMod) == 0) { // Nothing to modify?
|
|
modFlags = 0;
|
|
return SFERR_NO_SUPPORT;
|
|
}
|
|
*/
|
|
|
|
FrictionEffect200* pEffect = (FrictionEffect200*)(&newEffect);
|
|
unsigned short numPackets = 0;
|
|
|
|
BOOL playingChecked = FALSE;
|
|
HRESULT hr = SUCCESS;
|
|
|
|
if ((modFlags & (~possMod & DIEP_ALLPARAMS)) != 0) {
|
|
modFlags &= possMod;
|
|
hr = S_FALSE; // Cannot modify all they asked for
|
|
}
|
|
|
|
if (modFlags & DIEP_DURATION) {
|
|
if (m_Duration == pEffect->m_Duration) {
|
|
modFlags &= ~DIEP_DURATION; // Remove duration flag, unchanged
|
|
} else {
|
|
if (IsReallyPlaying(playingChecked) == TRUE) {
|
|
return DIERR_EFFECTPLAYING;
|
|
} else {
|
|
numPackets++;
|
|
}
|
|
}
|
|
}
|
|
if (modFlags & DIEP_TRIGGERBUTTON) {
|
|
if (m_TriggerPlayButton == pEffect->m_TriggerPlayButton) {
|
|
modFlags &= ~DIEP_TRIGGERBUTTON; // Remove trigger flag, unchanged
|
|
} else {
|
|
numPackets++;
|
|
}
|
|
}
|
|
if (modFlags & DIEP_TRIGGERREPEATINTERVAL) {
|
|
if (m_TriggerRepeat == pEffect->m_TriggerRepeat) {
|
|
modFlags &= ~DIEP_TRIGGERREPEATINTERVAL; // Remove trigger reapeat flag, unchanged
|
|
} else {
|
|
numPackets++;
|
|
}
|
|
}
|
|
if (modFlags & DIEP_GAIN) {
|
|
if (m_Gain == pEffect->m_Gain) {
|
|
modFlags &= ~DIEP_GAIN; // Remove gain flag, unchanged
|
|
} else {
|
|
numPackets++;
|
|
}
|
|
}
|
|
|
|
if (modFlags & DIEP_SAMPLEPERIOD) {
|
|
if (m_SamplePeriod == pEffect->m_SamplePeriod) {
|
|
modFlags &= ~DIEP_SAMPLEPERIOD; // Remove sample period flag, unchanged
|
|
} else {
|
|
numPackets++;
|
|
}
|
|
}
|
|
if (modFlags & DIEP_DIRECTION) {
|
|
modFlags |= DIEP_TYPESPECIFICPARAMS;
|
|
}
|
|
if (modFlags & DIEP_TYPESPECIFICPARAMS) { // Type specific change flagged
|
|
// Computed in create
|
|
if (ConstantX() == pEffect->ConstantX()) {
|
|
modFlags &= ~DIEP_TYPESPECIFICPARAMS; // Nothing really changed
|
|
} else {
|
|
numPackets++;
|
|
}
|
|
}
|
|
|
|
if (numPackets != 0) {
|
|
g_pDataPackager->AllocateDataPackets(numPackets);
|
|
}
|
|
|
|
return hr;
|
|
}
|
|
|
|
/******************* PeriodicEffect class *********************/
|
|
PeriodicEffect::PeriodicEffect() : InternalEffect(),
|
|
m_pEnvelope(NULL)
|
|
{
|
|
::memset(&m_PeriodicData, 0, sizeof(m_PeriodicData));
|
|
}
|
|
|
|
PeriodicEffect::~PeriodicEffect()
|
|
{
|
|
if (m_pEnvelope != NULL) {
|
|
delete m_pEnvelope;
|
|
m_pEnvelope = NULL;
|
|
}
|
|
}
|
|
|
|
HRESULT PeriodicEffect::Create(const DIEFFECT& diEffect)
|
|
{
|
|
// Validation Check
|
|
ASSUME_NOT_NULL(diEffect.lpvTypeSpecificParams);
|
|
if (diEffect.lpvTypeSpecificParams == NULL) {
|
|
return SFERR_INVALID_PARAM;
|
|
}
|
|
if (diEffect.cbTypeSpecificParams != sizeof(m_PeriodicData)) {
|
|
return SFERR_INVALID_PARAM;
|
|
}
|
|
|
|
// Let the base class do its magic
|
|
HRESULT hr = InternalEffect::Create(diEffect);
|
|
if (FAILED(hr)) {
|
|
return hr;
|
|
}
|
|
|
|
// Fill the type specific
|
|
::memcpy(&m_PeriodicData, diEffect.lpvTypeSpecificParams, sizeof(m_PeriodicData));
|
|
if (m_PeriodicData.dwMagnitude > 10000) {
|
|
m_PeriodicData.dwMagnitude = 10000;
|
|
hr = DI_TRUNCATED;
|
|
}
|
|
if (m_PeriodicData.lOffset > 10000) {
|
|
m_PeriodicData.lOffset = 10000;
|
|
hr = DI_TRUNCATED;
|
|
} else if (m_PeriodicData.lOffset < -10000) {
|
|
m_PeriodicData.lOffset = -10000;
|
|
hr = DI_TRUNCATED;
|
|
}
|
|
if (m_PeriodicData.dwPeriod > MAX_TIME_200) {
|
|
m_PeriodicData.dwPeriod = MAX_TIME_200;
|
|
hr = DI_TRUNCATED;
|
|
}
|
|
|
|
return hr;
|
|
}
|
|
|
|
/******************* PeriodicEffect1XX class ******************/
|
|
HRESULT PeriodicEffect1XX::Create(const DIEFFECT& diEffect)
|
|
{
|
|
if (diEffect.lpvTypeSpecificParams == NULL) {
|
|
ASSUME_NOT_REACHED(); // DI is flaking
|
|
return SFERR_INVALID_PARAM;
|
|
}
|
|
|
|
HRESULT hr;
|
|
if (diEffect.cbTypeSpecificParams == sizeof(DICONSTANTFORCE)) { // Special case Constant Force
|
|
if (m_TypeID != ET_SE_CONSTANT_FORCE) {
|
|
ASSUME_NOT_REACHED(); // DI is flaking
|
|
return SFERR_INVALID_PARAM;
|
|
}
|
|
DIPERIODIC periodic;
|
|
DICONSTANTFORCE* pConstantForce = (DICONSTANTFORCE*)(diEffect.lpvTypeSpecificParams);
|
|
if (pConstantForce->lMagnitude < 0) {
|
|
periodic.lOffset = -1; // We use offset to indicate sign
|
|
periodic.dwMagnitude = DWORD(0 - pConstantForce->lMagnitude);
|
|
} else {
|
|
periodic.lOffset = 1;
|
|
periodic.dwMagnitude = DWORD(pConstantForce->lMagnitude);
|
|
}
|
|
periodic.dwPhase = 0;
|
|
periodic.dwPeriod = 0; // Conversion will make a freq of 1
|
|
DIEFFECT* pDIEffect = (DIEFFECT*)(&diEffect); // Hack to temp remove constness
|
|
pDIEffect->cbTypeSpecificParams = sizeof(DIPERIODIC);
|
|
pDIEffect->lpvTypeSpecificParams = &periodic; // Replace Constant Force Parms with periodic
|
|
hr = PeriodicEffect::Create(diEffect);
|
|
pDIEffect->cbTypeSpecificParams = sizeof(DICONSTANTFORCE);
|
|
pDIEffect->lpvTypeSpecificParams = pConstantForce; // Return parms back
|
|
} else if (diEffect.cbTypeSpecificParams == sizeof(DIRAMPFORCE)) { // Special case RampForce
|
|
if (m_TypeID != ET_SE_RAMPUP) {
|
|
ASSUME_NOT_REACHED(); // DI is flaking
|
|
return SFERR_INVALID_PARAM;
|
|
}
|
|
DIPERIODIC periodic;
|
|
DIRAMPFORCE* pRampForce = (DIRAMPFORCE*)(diEffect.lpvTypeSpecificParams);
|
|
if (pRampForce->lStart < pRampForce->lEnd) {
|
|
m_TypeID = ET_SE_RAMPDOWN;
|
|
periodic.dwMagnitude = DWORD(pRampForce->lEnd - pRampForce->lStart)/2;
|
|
} else {
|
|
periodic.dwMagnitude = DWORD(pRampForce->lStart - pRampForce->lEnd)/2;
|
|
}
|
|
periodic.lOffset = (pRampForce->lStart + pRampForce->lEnd)/2;
|
|
periodic.dwPeriod = 0; // Conversion will make a freq of 1
|
|
DIEFFECT* pDIEffect = (DIEFFECT*)(&diEffect); // Hack to temp remove constness
|
|
pDIEffect->cbTypeSpecificParams = sizeof(DIPERIODIC);
|
|
pDIEffect->lpvTypeSpecificParams = &periodic; // Replace Constant Force Parms with periodic
|
|
hr = PeriodicEffect::Create(diEffect);
|
|
pDIEffect->cbTypeSpecificParams = sizeof(DIRAMPFORCE);
|
|
pDIEffect->lpvTypeSpecificParams = pRampForce; // Return parms back
|
|
} else {
|
|
hr = PeriodicEffect::Create(diEffect);
|
|
}
|
|
|
|
// How did creation go?
|
|
if (hr != SUCCESS) {
|
|
return hr; // Not so good
|
|
}
|
|
|
|
ASSUME(m_pEnvelope == NULL);
|
|
m_pEnvelope = new Envelope1XX(diEffect.lpEnvelope, Offset(), m_Duration);
|
|
|
|
// Is it closer to sine or cosine
|
|
if (m_TypeID == ET_SE_SINE) {
|
|
if (((Phase() >= 4500) && (Phase() < 13500)) || ((Phase() >= 22500) && (Phase() < 31500))) {
|
|
m_TypeID = ET_SE_COSINE;
|
|
}
|
|
} else if ((Phase() >= 9000) && (Phase() < 27000)) {
|
|
if (m_TypeID == SE_SQUAREHIGH) {
|
|
m_TypeID = SE_SQUARELOW;
|
|
} else if (m_TypeID == SE_TRIANGLEUP) {
|
|
m_TypeID = SE_TRIANGLEDOWN;
|
|
}
|
|
}
|
|
return SUCCESS;
|
|
}
|
|
|
|
void PeriodicEffect1XX::DIToJolt(DWORD mag, DWORD off, DWORD gain, DWORD& max, DWORD& min) const
|
|
{
|
|
if (m_TypeID == ET_SE_CONSTANT_FORCE) {
|
|
ASSUME(off == -1 || off == 1); // Indicates sign for ConstantForce
|
|
min = 0;
|
|
max = off * (mag/GAIN_PERCENTAGE_SCALE) * gain;
|
|
} else {
|
|
DWORD half = ((mag/GAIN_PERCENTAGE_SCALE) * gain)/2;
|
|
min = off - half;
|
|
max = off + half;
|
|
}
|
|
}
|
|
|
|
DWORD PeriodicEffect1XX::DIPeriodToJoltFreq(DWORD period)
|
|
{
|
|
if (period == 0) {
|
|
return 1;
|
|
}
|
|
DWORD freq = FREQUENCY_SCALE_1XX/period;
|
|
if (freq == 0) {
|
|
freq = 1;
|
|
} else if (freq > 500) {
|
|
freq = 500;
|
|
}
|
|
return freq;
|
|
}
|
|
|
|
|
|
HRESULT PeriodicEffect1XX::FillCreatePacket(DataPacket& packet) const
|
|
{
|
|
// Sanity check
|
|
if (m_pEnvelope == NULL) {
|
|
ASSUME_NOT_REACHED(); // Should never happen
|
|
return SFERR_DRIVER_ERROR;
|
|
}
|
|
|
|
// Packet to set modify data[index] of current effect
|
|
if (!packet.AllocateBytes(34)) {
|
|
return SFERR_DRIVER_ERROR;
|
|
}
|
|
|
|
// Fill in the Generic Effect Information
|
|
FillHeader1XX(packet, m_TypeID, NEW_EFFECT_ID);
|
|
|
|
// Periodic Specific Parms (Freq, Max, Min)
|
|
packet.m_pData[10]= BYTE(g_TriggerMap1XX[m_TriggerPlayButton] & 0x7F); // Button play mask Low MidiByte
|
|
packet.m_pData[11]= BYTE(g_TriggerMap1XX[m_TriggerPlayButton] >> 7) & 0x7F; // Button play mask High MidiByte
|
|
packet.m_pData[12] = BYTE(m_EffectAngle & 0x7F); // DirectionAngle Low
|
|
packet.m_pData[13] = BYTE(m_EffectAngle >> 7) & 0x7F; // DirectionAngle High
|
|
packet.m_pData[14] = 100; // Gain (encoded in coeficents)
|
|
packet.m_pData[15]= BYTE(500 & 0x7F); // ForceOutput Rate LowByte
|
|
packet.m_pData[16]= BYTE(500 >> 7) & 0x7F; // ForceOutput Rate HighByte
|
|
packet.m_pData[17]= 0x7F; // Percent LowByte
|
|
packet.m_pData[18]= 0; // Percent HighByte
|
|
|
|
// Envelope
|
|
// Envelope1XX* pEnvelope = dynamic_cast<Envelope1XX*>(m_pEnvelope);
|
|
Envelope1XX* pEnvelope = (Envelope1XX*)(m_pEnvelope);
|
|
packet.m_pData[19] = BYTE(pEnvelope->m_StartPercent & 0x7F); // Initial attack level
|
|
packet.m_pData[20] = BYTE(pEnvelope->m_AttackTime& 0x7F); // AttackTime Low
|
|
packet.m_pData[21] = BYTE(pEnvelope->m_AttackTime>> 7) & 0x7F; // AttackTime High
|
|
packet.m_pData[22] = BYTE(pEnvelope->m_SustainPercent & 0x7F); // Sustain level
|
|
packet.m_pData[23] = BYTE(pEnvelope->m_SustainTime & 0x7F); // SustainTime Low
|
|
packet.m_pData[24] = BYTE(pEnvelope->m_SustainTime >> 7) & 0x7F; // SustainTime High
|
|
packet.m_pData[25] = BYTE(pEnvelope->m_EndPercent & 0x7F); // What to decay to
|
|
// -- Duration of decay is ficugred from total duration
|
|
|
|
DWORD freq = DIPeriodToJoltFreq(Period());
|
|
packet.m_pData[26]= BYTE(freq & 0x7F); // Frequency LowByte
|
|
packet.m_pData[27]= BYTE(freq >> 7) & 0x7F; // Frequency HighByte
|
|
DWORD max, min;
|
|
DIToJolt(Magnitude(), Offset(), m_Gain, max, min);
|
|
min /= COEFFICIENT_SCALE_1XX;
|
|
max /= COEFFICIENT_SCALE_1XX;
|
|
packet.m_pData[28]= BYTE(max & 0x7F); // Max LowByte
|
|
packet.m_pData[29]= BYTE(max >> 7) & 0x7F; // Max HighByte
|
|
packet.m_pData[30]= BYTE(min & 0x7F); // Min LowByte
|
|
packet.m_pData[31]= BYTE(min >> 7) & 0x7F; // Min HighByte
|
|
|
|
packet.m_pData[32]= ComputeChecksum(packet, 32); // Checksum
|
|
|
|
// End of packet
|
|
packet.m_pData[33]= MIDI_EOX; // End of SysEX packet
|
|
|
|
return SUCCESS;
|
|
}
|
|
|
|
HRESULT PeriodicEffect1XX::AdjustModifyParams(InternalEffect& newEffect, DWORD& modFlags) const
|
|
{
|
|
// Check to see if values being modified are acceptable
|
|
DWORD possMod = DIEP_DURATION | DIEP_GAIN | DIEP_TRIGGERBUTTON | DIEP_TYPESPECIFICPARAMS;
|
|
if ((g_TotalModifiable & modFlags & possMod) == 0) { // Nothing to modify?
|
|
modFlags = 0;
|
|
return SFERR_NO_SUPPORT;
|
|
}
|
|
|
|
PeriodicEffect1XX* pEffect = (PeriodicEffect1XX*)(&newEffect);
|
|
unsigned short numPackets = 0;
|
|
if (modFlags & DIEP_DURATION) {
|
|
if (m_Duration == pEffect->m_Duration) {
|
|
modFlags &= ~DIEP_DURATION; // Remove duration flag, unchanged
|
|
} else {
|
|
numPackets += 2;
|
|
}
|
|
}
|
|
if (modFlags & DIEP_TRIGGERBUTTON) {
|
|
if (m_TriggerPlayButton == pEffect->m_TriggerPlayButton) {
|
|
modFlags &= ~DIEP_TRIGGERBUTTON; // Remove trigger flag, unchanged
|
|
} else {
|
|
numPackets += 2;
|
|
}
|
|
}
|
|
|
|
// Either gain or type specific changed
|
|
if (modFlags & (DIEP_GAIN | DIEP_TYPESPECIFICPARAMS)) {
|
|
DWORD oldMax, oldMin, newMax, newMin;
|
|
DIToJolt(Magnitude(), Offset(), m_Gain, oldMax, oldMin);
|
|
DIToJolt(pEffect->Magnitude(), pEffect->Offset(), pEffect->m_Gain, newMax, newMin);
|
|
|
|
int numTypeSpecificChanged = 0;
|
|
// Max changed?
|
|
if (oldMax != newMax) {
|
|
numTypeSpecificChanged = 2;
|
|
}
|
|
// Min changed?
|
|
if (oldMin != newMin) {
|
|
numTypeSpecificChanged += 2;
|
|
}
|
|
if (modFlags & DIEP_TYPESPECIFICPARAMS) { // Don't check these if gain only
|
|
// Phase (1XX cannot change)
|
|
if (Phase() != pEffect->Phase()) {
|
|
return SFERR_NO_SUPPORT;
|
|
}
|
|
// Period
|
|
if (Period() != pEffect->Period()) {
|
|
numTypeSpecificChanged += 2;
|
|
}
|
|
}
|
|
if (numTypeSpecificChanged == 0) {
|
|
modFlags &= ~(DIEP_TYPESPECIFICPARAMS | DIEP_GAIN); // Nothing really changed
|
|
} else {
|
|
numPackets += (USHORT)numTypeSpecificChanged;
|
|
}
|
|
}
|
|
|
|
if (numPackets != 0) {
|
|
g_pDataPackager->AllocateDataPackets(numPackets);
|
|
}
|
|
|
|
return SUCCESS;
|
|
}
|
|
|
|
HRESULT PeriodicEffect1XX::Modify(InternalEffect& newEffect, DWORD modFlags)
|
|
{
|
|
g_pDataPackager->ClearPackets();
|
|
HRESULT hr = AdjustModifyParams(newEffect, modFlags);
|
|
if (hr != SUCCESS) {
|
|
return hr;
|
|
}
|
|
|
|
PeriodicEffect1XX* pEffect = (PeriodicEffect1XX*)(&newEffect);
|
|
BYTE nextPacket = 0;
|
|
if (modFlags & DIEP_DURATION) {
|
|
hr = FillModifyPacket1XX(nextPacket, INDEX_DURATION, pEffect->m_Duration/DURATION_SCALE_1XX);
|
|
nextPacket += 2;
|
|
}
|
|
if (modFlags & DIEP_TRIGGERBUTTON) {
|
|
hr = FillModifyPacket1XX(nextPacket, INDEX_TRIGGERBUTTONMASK, g_TriggerMap1XX[pEffect->m_TriggerPlayButton]);
|
|
nextPacket += 2;
|
|
}
|
|
|
|
if (modFlags & (DIEP_GAIN | DIEP_TYPESPECIFICPARAMS)) {
|
|
DWORD oldMax, oldMin, newMax, newMin;
|
|
DIToJolt(Magnitude(), Offset(), m_Gain, oldMax, oldMin);
|
|
DIToJolt(pEffect->Magnitude(), pEffect->Offset(), pEffect->m_Gain, newMax, newMin);
|
|
|
|
if (oldMax != newMax) {
|
|
hr = FillModifyPacket1XX(nextPacket, INDEX_X_COEEFICIENT, (newMax/COEFFICIENT_SCALE_1XX));
|
|
nextPacket += 2;
|
|
}
|
|
if (oldMin != newMin) {
|
|
hr = FillModifyPacket1XX(nextPacket, INDEX_X_COEEFICIENT, (newMin/COEFFICIENT_SCALE_1XX));
|
|
nextPacket += 2;
|
|
}
|
|
if (Period() != pEffect->Period()) {
|
|
hr = FillModifyPacket1XX(nextPacket, INDEX_X_CENTER, pEffect->Period()/COEFFICIENT_SCALE_1XX);
|
|
nextPacket += 2;
|
|
}
|
|
}
|
|
return hr;
|
|
}
|
|
|
|
/******************* PeriodicEffect200 class ******************/
|
|
BYTE PeriodicEffect200::GetRepeatIndex() const
|
|
{
|
|
return INDEX_PE_REPEAT_200;
|
|
}
|
|
|
|
long int PeriodicEffect200::Phase() const
|
|
{
|
|
long int phase = m_PeriodicData.dwPhase;
|
|
if (m_EffectAngle <= 18000) {
|
|
phase += 18000;
|
|
phase %= 36000;
|
|
}
|
|
return phase;
|
|
}
|
|
|
|
|
|
HRESULT PeriodicEffect200::Create(const DIEFFECT& diEffect)
|
|
{
|
|
HRESULT hr = PeriodicEffect::Create(diEffect);
|
|
|
|
// How did creation go?
|
|
if (FAILED(hr)) {
|
|
return hr; // Not so good
|
|
}
|
|
|
|
ASSUME(m_pEnvelope == NULL);
|
|
m_PercentAdjustment = m_PercentX + (m_PercentY * g_ForceFeedbackDevice.GetYMappingPercent(m_TypeID))/100;
|
|
m_Gain = m_Gain/100 * m_PercentAdjustment;
|
|
m_pEnvelope = new Envelope200(diEffect.lpEnvelope, Magnitude(), m_Duration, hr);
|
|
|
|
return hr;
|
|
}
|
|
|
|
UINT PeriodicEffect200::GetModifyOnlyNeeded() const
|
|
{
|
|
UINT retCount = 0;
|
|
|
|
if ((m_SamplePeriod/DURATION_SCALE_200) != 1) {
|
|
retCount++;
|
|
}
|
|
if (m_TriggerPlayButton != 0) { // Trigger Button
|
|
retCount++;
|
|
}
|
|
if (m_TriggerRepeat != 0) { // Trigger repeat
|
|
retCount++;
|
|
}
|
|
|
|
return retCount;
|
|
}
|
|
|
|
HRESULT PeriodicEffect200::FillModifyOnlyParms() const
|
|
{
|
|
if (g_pDataPackager == NULL) {
|
|
ASSUME_NOT_REACHED(); // This is only called from DataPackager::Create()
|
|
return SFERR_DRIVER_ERROR;
|
|
}
|
|
|
|
HRESULT hr = SUCCESS;
|
|
BYTE nextPacket = 1;
|
|
|
|
DWORD calc_byte = DWORD(m_SamplePeriod/DURATION_SCALE_200);
|
|
if (calc_byte != 1) { // Sample period
|
|
hr = FillModifyPacket200(nextPacket, INDEX_PE_SAMPLE_PERIOD_200, calc_byte);
|
|
nextPacket++;
|
|
}
|
|
if (m_TriggerPlayButton != 0) { // Trigger Button
|
|
hr = FillModifyPacket200(nextPacket, INDEX_PE_BUTTONMAP_200, g_TriggerMap200[m_TriggerPlayButton]);
|
|
nextPacket++;
|
|
}
|
|
if (m_TriggerRepeat != 0) { // Trigger repeat
|
|
hr = FillModifyPacket200(nextPacket, INDEX_PE_BUTTONREPEAT_200, m_TriggerRepeat/DURATION_SCALE_200);
|
|
nextPacket++;
|
|
}
|
|
|
|
return hr;
|
|
}
|
|
|
|
HRESULT PeriodicEffect200::FillCreatePacket(DataPacket& packet) const
|
|
{
|
|
// Sanity check
|
|
if (m_pEnvelope == NULL) {
|
|
ASSUME_NOT_REACHED(); // Should never happen
|
|
return SFERR_DRIVER_ERROR;
|
|
}
|
|
|
|
// Packet to set modify data[index] of current effect
|
|
if (!packet.AllocateBytes(26)) {
|
|
return SFERR_DRIVER_ERROR;
|
|
}
|
|
|
|
// Fill in the Generic Effect Information
|
|
FillHeader200(packet, m_TypeID, NEW_EFFECT_ID);
|
|
|
|
unsigned short calc_byte = unsigned short(float(m_EffectAngle)/ANGLE_SCALE_200);
|
|
packet.m_pData[10]= BYTE(calc_byte & 0x7F); // Effect Angle
|
|
calc_byte = unsigned short(float(m_Gain)/GAIN_SCALE_200);
|
|
packet.m_pData[11]= BYTE(calc_byte & 0x7F); // Gain
|
|
calc_byte = unsigned short(float(Phase())/PHASE_SCALE_200);
|
|
packet.m_pData[12]= BYTE(calc_byte & 0x7F); // Phase Low
|
|
packet.m_pData[13]= BYTE(calc_byte >> 7) & 0x7F; // Phase High
|
|
|
|
// Envelope
|
|
Envelope200* pEnvelope = (Envelope200*)(m_pEnvelope);
|
|
packet.m_pData[14] = BYTE(pEnvelope->m_StartPercent & 0x7F); // Initial attack level
|
|
packet.m_pData[15] = BYTE(pEnvelope->m_AttackTime& 0x7F); // AttackTime Low
|
|
packet.m_pData[16] = BYTE(pEnvelope->m_AttackTime>> 7) & 0x7F; // AttackTime High
|
|
packet.m_pData[17] = BYTE(pEnvelope->m_SustainPercent & 0x7F); // Sustain level
|
|
packet.m_pData[18] = BYTE(pEnvelope->m_FadeStart & 0x7F); // SustainTime Low
|
|
packet.m_pData[19] = BYTE(pEnvelope->m_FadeStart >> 7) & 0x7F; // SustainTime High
|
|
packet.m_pData[20] = BYTE(pEnvelope->m_EndPercent & 0x7F); // What to decay to
|
|
// -- Duration of decay is figured by the firmware from total duration
|
|
|
|
calc_byte = unsigned short(Period()/DURATION_SCALE_200);
|
|
if (calc_byte == 0) {
|
|
calc_byte = 1;
|
|
}
|
|
packet.m_pData[21] = BYTE(calc_byte & 0x7F); // Period Low MidiByte
|
|
packet.m_pData[22] = BYTE(calc_byte >> 7) & 0x7F; // Period High MidiByte
|
|
|
|
// Convert offset to device percentage (0 to +126 --- +63 = 0 percent)
|
|
calc_byte = unsigned short(float(Offset()/DIDISTANCE_TO_PERCENT + 100)/POSITIVE_PERCENT_SCALE);
|
|
packet.m_pData[23] = BYTE(calc_byte & 0x7F); // Offset
|
|
|
|
packet.m_pData[24]= ComputeChecksum(packet, 24); // Checksum
|
|
|
|
// End of packet
|
|
packet.m_pData[25]= MIDI_EOX; // End of SysEX packet
|
|
|
|
return SUCCESS;
|
|
}
|
|
|
|
HRESULT PeriodicEffect200::AdjustModifyParams(InternalEffect& newEffect, DWORD& modFlags)
|
|
{
|
|
// Check to see if values being modified are acceptable
|
|
DWORD possMod = DIEP_DURATION | DIEP_GAIN | DIEP_TRIGGERBUTTON | DIEP_TRIGGERREPEATINTERVAL | DIEP_TYPESPECIFICPARAMS | DIEP_SAMPLEPERIOD | DIEP_ENVELOPE | DIEP_DIRECTION;
|
|
if ((g_TotalModifiable & modFlags & possMod) == 0) { // Nothing to modify?
|
|
modFlags = 0;
|
|
return SFERR_NO_SUPPORT;
|
|
}
|
|
|
|
PeriodicEffect200* pEffect = (PeriodicEffect200*)(&newEffect);
|
|
unsigned short numPackets = 0;
|
|
|
|
HRESULT hr = SUCCESS;
|
|
|
|
if ((modFlags & (DIEP_AXES)) != 0) {
|
|
hr = S_FALSE; // Cannot modify all they asked for
|
|
}
|
|
|
|
BOOL playingChecked = FALSE;
|
|
if (modFlags & DIEP_DURATION) {
|
|
if (m_Duration == pEffect->m_Duration) {
|
|
modFlags &= ~DIEP_DURATION; // Remove duration flag, unchanged
|
|
} else {
|
|
if (IsReallyPlaying(playingChecked) == TRUE) {
|
|
return DIERR_EFFECTPLAYING;
|
|
} else {
|
|
modFlags |= DIEP_ENVELOPE; // Duration change forces envelope change
|
|
numPackets++;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (modFlags & DIEP_DIRECTION) {
|
|
modFlags |= (DIEP_GAIN | DIEP_TYPESPECIFICPARAMS);
|
|
if (m_EffectAngle != pEffect->m_EffectAngle) {
|
|
numPackets++;
|
|
} else {
|
|
modFlags &= ~DIEP_DIRECTION;
|
|
}
|
|
}
|
|
if (modFlags & DIEP_TYPESPECIFICPARAMS) { // Check type specific
|
|
int numTypeSpecificChanged = 0;
|
|
if (Phase() != pEffect->Phase()) {
|
|
numTypeSpecificChanged++;
|
|
}
|
|
if (Period() != pEffect->Period()) {
|
|
numTypeSpecificChanged++;
|
|
}
|
|
if (Offset() != pEffect->Offset()) {
|
|
numTypeSpecificChanged++;
|
|
}
|
|
if (Magnitude() != pEffect->Magnitude()) {
|
|
modFlags |= DIEP_ENVELOPE; // This effects the envelope
|
|
}
|
|
if (numTypeSpecificChanged == 0) {
|
|
modFlags &= ~DIEP_TYPESPECIFICPARAMS; // Nothing really changed
|
|
} else {
|
|
numPackets += (USHORT)numTypeSpecificChanged;
|
|
}
|
|
}
|
|
if (modFlags & DIEP_ENVELOPE) {
|
|
int numEnvelopeChanged = 0;
|
|
|
|
if (m_pEnvelope == NULL || pEffect->m_pEnvelope == NULL) {
|
|
ASSUME_NOT_REACHED(); // Envelope should always be created!
|
|
} else {
|
|
Envelope200* pOldEnvelope = (Envelope200*)(m_pEnvelope);
|
|
Envelope200* pNewEnvelope = (Envelope200*)(pEffect->m_pEnvelope);
|
|
if (pOldEnvelope->m_AttackTime != pNewEnvelope->m_AttackTime) {
|
|
numEnvelopeChanged++;
|
|
}
|
|
if (pOldEnvelope->m_FadeStart != pNewEnvelope->m_FadeStart) {
|
|
numEnvelopeChanged++;
|
|
}
|
|
if (pOldEnvelope->m_StartPercent != pNewEnvelope->m_StartPercent) {
|
|
numEnvelopeChanged++;
|
|
}
|
|
if (pOldEnvelope->m_SustainPercent != pNewEnvelope->m_SustainPercent) {
|
|
numEnvelopeChanged++;
|
|
}
|
|
if (pOldEnvelope->m_EndPercent != pNewEnvelope->m_EndPercent) {
|
|
numEnvelopeChanged++;
|
|
}
|
|
}
|
|
|
|
if (numEnvelopeChanged == 0) {
|
|
modFlags &= ~DIEP_ENVELOPE; // Remove envelope flag, unchanged
|
|
} else {
|
|
numPackets += (USHORT)numEnvelopeChanged;
|
|
}
|
|
}
|
|
if (modFlags & DIEP_TRIGGERBUTTON) {
|
|
if (m_TriggerPlayButton == pEffect->m_TriggerPlayButton) {
|
|
modFlags &= ~DIEP_TRIGGERBUTTON; // Remove trigger flag, unchanged
|
|
} else {
|
|
numPackets++;
|
|
}
|
|
}
|
|
if (modFlags & DIEP_TRIGGERREPEATINTERVAL) {
|
|
if (m_TriggerRepeat == pEffect->m_TriggerRepeat) {
|
|
modFlags &= ~DIEP_TRIGGERREPEATINTERVAL; // Remove trigger reapeat flag, unchanged
|
|
} else {
|
|
numPackets++;
|
|
}
|
|
}
|
|
if (modFlags & DIEP_GAIN) {
|
|
if (m_Gain == pEffect->m_Gain) {
|
|
modFlags &= ~DIEP_GAIN; // Remove gain flag, unchanged
|
|
} else {
|
|
numPackets++;
|
|
}
|
|
}
|
|
|
|
if (modFlags & DIEP_SAMPLEPERIOD) {
|
|
if (m_SamplePeriod == pEffect->m_SamplePeriod) {
|
|
modFlags &= ~DIEP_SAMPLEPERIOD; // Remove sample period flag, unchanged
|
|
} else {
|
|
numPackets++;
|
|
}
|
|
}
|
|
|
|
if (numPackets != 0) {
|
|
g_pDataPackager->AllocateDataPackets(numPackets);
|
|
}
|
|
|
|
return hr;
|
|
}
|
|
|
|
HRESULT PeriodicEffect200::Modify(InternalEffect& newEffect, DWORD modFlags)
|
|
{
|
|
g_pDataPackager->ClearPackets();
|
|
HRESULT adjustReturned = AdjustModifyParams(newEffect, modFlags);
|
|
if (FAILED(adjustReturned)) {
|
|
return adjustReturned;
|
|
}
|
|
|
|
HRESULT hr = SUCCESS;
|
|
PeriodicEffect200* pEffect = (PeriodicEffect200*)(&newEffect);
|
|
BYTE nextPacket = 0;
|
|
DWORD calc_byte;
|
|
|
|
if (modFlags & DIEP_DURATION) {
|
|
hr = FillModifyPacket200(nextPacket, INDEX_PE_DURATION_200, pEffect->m_Duration/DURATION_SCALE_200);
|
|
nextPacket++;
|
|
}
|
|
|
|
if (modFlags & DIEP_DIRECTION) {
|
|
hr = FillModifyPacket200(nextPacket, INDEX_PE_DIRECTIONANGLE_200, unsigned short(float(pEffect->m_EffectAngle)/ANGLE_SCALE_200));
|
|
nextPacket++;
|
|
}
|
|
|
|
if (modFlags & DIEP_ENVELOPE) {
|
|
Envelope200* pOldEnvelope = (Envelope200*)(m_pEnvelope);
|
|
Envelope200* pNewEnvelope = (Envelope200*)(pEffect->m_pEnvelope);
|
|
if (pOldEnvelope->m_StartPercent != pNewEnvelope->m_StartPercent) {
|
|
hr = FillModifyPacket200(nextPacket, INDEX_PE_STARTPERCENT_200, pNewEnvelope->m_StartPercent);
|
|
nextPacket++;
|
|
}
|
|
if (pOldEnvelope->m_AttackTime != pNewEnvelope->m_AttackTime) {
|
|
hr = FillModifyPacket200(nextPacket, INDEX_PE_ATTTACK_TIME_200, pNewEnvelope->m_AttackTime);
|
|
nextPacket++;
|
|
}
|
|
if (pOldEnvelope->m_SustainPercent != pNewEnvelope->m_SustainPercent) {
|
|
hr = FillModifyPacket200(nextPacket, INDEX_PE_SUSTAINPERCENT_200, pNewEnvelope->m_SustainPercent);
|
|
nextPacket++;
|
|
}
|
|
if (pOldEnvelope->m_FadeStart != pNewEnvelope->m_FadeStart) {
|
|
hr = FillModifyPacket200(nextPacket, INDEX_PE_FADESTART_200, pNewEnvelope->m_FadeStart);
|
|
nextPacket++;
|
|
}
|
|
if (pOldEnvelope->m_EndPercent != pNewEnvelope->m_EndPercent) {
|
|
hr = FillModifyPacket200(nextPacket, INDEX_PE_ENDPERCENT_200, pNewEnvelope->m_EndPercent);
|
|
nextPacket++;
|
|
}
|
|
}
|
|
if (modFlags & DIEP_TRIGGERBUTTON) {
|
|
hr = FillModifyPacket200(nextPacket, INDEX_PE_BUTTONMAP_200, g_TriggerMap200[pEffect->m_TriggerPlayButton]);
|
|
nextPacket++;
|
|
}
|
|
if (modFlags & DIEP_TRIGGERREPEATINTERVAL) {
|
|
hr = FillModifyPacket200(nextPacket, INDEX_PE_BUTTONREPEAT_200, pEffect->m_TriggerRepeat/DURATION_SCALE_200);
|
|
nextPacket++;
|
|
}
|
|
if (modFlags & DIEP_GAIN) {
|
|
calc_byte = DWORD(float(pEffect->m_Gain)/GAIN_SCALE_200);
|
|
hr = FillModifyPacket200(nextPacket, INDEX_PE_GAIN_200, calc_byte);
|
|
nextPacket++;
|
|
}
|
|
|
|
if (modFlags & DIEP_SAMPLEPERIOD) {
|
|
calc_byte = DWORD(pEffect->m_SamplePeriod/DURATION_SCALE_200);
|
|
hr = FillModifyPacket200(nextPacket, INDEX_PE_SAMPLE_PERIOD_200, calc_byte);
|
|
nextPacket++;
|
|
}
|
|
|
|
if (modFlags & DIEP_TYPESPECIFICPARAMS) { // Send changed items
|
|
if (Phase() != pEffect->Phase()) {
|
|
calc_byte = DWORD(float(pEffect->Phase())/PHASE_SCALE_200);
|
|
hr = FillModifyPacket200(nextPacket, INDEX_PE_PHASE_200, calc_byte);
|
|
nextPacket++;
|
|
}
|
|
if (Period() != pEffect->Period()) {
|
|
hr = FillModifyPacket200(nextPacket, INDEX_PE_PERIOD_200, pEffect->Period()/DURATION_SCALE_200);
|
|
nextPacket++;
|
|
}
|
|
if (Offset() != pEffect->Offset()) {
|
|
calc_byte = DWORD(float(pEffect->Offset()/DIDISTANCE_TO_PERCENT + 100)/POSITIVE_PERCENT_SCALE);
|
|
hr = FillModifyPacket200(nextPacket, INDEX_PE_OFFSET_200, calc_byte);
|
|
nextPacket++;
|
|
}
|
|
}
|
|
|
|
if (hr == SUCCESS) {
|
|
return adjustReturned;
|
|
}
|
|
return hr;
|
|
}
|
|
|
|
/******************* SawtoothEffect200 class ******************/
|
|
HRESULT SawtoothEffect200::Create(const DIEFFECT& diEffect)
|
|
{
|
|
HRESULT retVal = PeriodicEffect200::Create(diEffect);
|
|
|
|
if ((m_IsUp && (m_EffectAngle < 18000)) || (!m_IsUp && (m_EffectAngle > 18000))) { // SawtoothUp use 0 degrees
|
|
m_EffectAngle = 18000;
|
|
} else { // SawtoothDown use 180 degress
|
|
m_EffectAngle = 0;
|
|
}
|
|
|
|
return retVal;
|
|
}
|
|
|
|
long int SawtoothEffect200::Phase() const
|
|
{
|
|
return m_PeriodicData.dwPhase;
|
|
}
|
|
|
|
/******************* RampEffect200 class ******************/
|
|
HRESULT RampEffect200::Create(const DIEFFECT& diEffect)
|
|
{
|
|
// Sanity checks
|
|
if (diEffect.cbTypeSpecificParams != sizeof(DIRAMPFORCE)) {
|
|
ASSUME_NOT_REACHED();
|
|
return SFERR_INVALID_PARAM;
|
|
}
|
|
if (diEffect.lpvTypeSpecificParams == NULL) {
|
|
ASSUME_NOT_REACHED();
|
|
return SFERR_INVALID_PARAM;
|
|
}
|
|
|
|
// Ramps cannot have infinite duration
|
|
if (diEffect.dwDuration == INFINITE) {
|
|
ASSUME_NOT_REACHED();
|
|
return SFERR_INVALID_PARAM;
|
|
}
|
|
|
|
// Create periodic structure and put in the correct values
|
|
DIPERIODIC periodic;
|
|
DIRAMPFORCE* pRampForce = (DIRAMPFORCE*)(diEffect.lpvTypeSpecificParams);
|
|
periodic.dwPhase = 0;
|
|
periodic.dwPeriod = diEffect.dwDuration;
|
|
periodic.lOffset = -(pRampForce->lStart + pRampForce->lEnd)/2;
|
|
if (pRampForce->lStart < pRampForce->lEnd) { // What direction are we
|
|
m_IsUp = TRUE;
|
|
periodic.dwMagnitude = DWORD(pRampForce->lEnd - pRampForce->lStart)/2;
|
|
} else {
|
|
m_IsUp = FALSE;
|
|
periodic.dwMagnitude = DWORD(pRampForce->lStart - pRampForce->lEnd)/2;
|
|
}
|
|
|
|
// Replace the periodic structure (ignorning constness), call sawtooth create, then put the old back
|
|
DIEFFECT* pDIEffect = (DIEFFECT*)(&diEffect); // Hack to temp remove constness
|
|
pDIEffect->cbTypeSpecificParams = sizeof(DIPERIODIC);
|
|
pDIEffect->lpvTypeSpecificParams = &periodic; // Replace Ramp Parms with periodic
|
|
HRESULT retVal = SawtoothEffect200::Create(diEffect);
|
|
pDIEffect->cbTypeSpecificParams = sizeof(DIRAMPFORCE);
|
|
pDIEffect->lpvTypeSpecificParams = pRampForce; // Return parms back
|
|
|
|
// We are done
|
|
return retVal;
|
|
}
|
|
|
|
override HRESULT RampEffect200::Modify(InternalEffect& newEffect, DWORD modFlags)
|
|
{
|
|
if (modFlags & DIEP_DURATION) {
|
|
modFlags |= DIEP_TYPESPECIFICPARAMS;
|
|
}
|
|
if (modFlags & DIEP_TYPESPECIFICPARAMS) {
|
|
RampEffect200* pEffect = (RampEffect200*)(&newEffect);
|
|
if (m_IsUp != pEffect->m_IsUp) {
|
|
modFlags |= DIEP_DIRECTION;
|
|
}
|
|
}
|
|
|
|
return SawtoothEffect200::Modify(newEffect, modFlags);
|
|
}
|
|
|
|
|
|
/******************* ConstantForceEffect class ******************/
|
|
ConstantForceEffect::ConstantForceEffect() : InternalEffect(),
|
|
m_pEnvelope(NULL)
|
|
{
|
|
}
|
|
|
|
ConstantForceEffect::~ConstantForceEffect()
|
|
{
|
|
if (m_pEnvelope != NULL) {
|
|
delete m_pEnvelope;
|
|
m_pEnvelope = NULL;
|
|
}
|
|
}
|
|
|
|
HRESULT ConstantForceEffect::Create(const DIEFFECT& diEffect)
|
|
{
|
|
// Zero out old struct
|
|
::memset(&m_ConstantForceData, 0, sizeof(m_ConstantForceData));
|
|
|
|
// Validation Check
|
|
if (diEffect.lpvTypeSpecificParams == NULL) {
|
|
ASSUME_NOT_REACHED();
|
|
return SFERR_INVALID_PARAM;
|
|
}
|
|
if (diEffect.cbTypeSpecificParams != sizeof(m_ConstantForceData)) {
|
|
ASSUME_NOT_REACHED();
|
|
return SFERR_INVALID_PARAM;
|
|
}
|
|
|
|
// Let the base class do its magic
|
|
HRESULT hr = InternalEffect::Create(diEffect);
|
|
if (FAILED(hr)) {
|
|
return hr;
|
|
}
|
|
|
|
// Copy data to local store
|
|
::memcpy(&m_ConstantForceData, diEffect.lpvTypeSpecificParams, sizeof(m_ConstantForceData));
|
|
|
|
// Check the data for truncation
|
|
if (m_ConstantForceData.lMagnitude > 10000) {
|
|
m_ConstantForceData.lMagnitude = 10000;
|
|
hr = DI_TRUNCATED;
|
|
} else if (m_ConstantForceData.lMagnitude < -10000) {
|
|
m_ConstantForceData.lMagnitude = -10000;
|
|
hr = DI_TRUNCATED;
|
|
}
|
|
|
|
return hr;
|
|
}
|
|
|
|
|
|
/******************* ConstantForceEffect200 class ******************/
|
|
BYTE ConstantForceEffect200::GetRepeatIndex() const
|
|
{
|
|
return INDEX_CE_REPEAT_200;
|
|
}
|
|
|
|
HRESULT ConstantForceEffect200::Create(const DIEFFECT& diEffect)
|
|
{
|
|
HRESULT hr = ConstantForceEffect::Create(diEffect);
|
|
|
|
// How did creation go?
|
|
if (FAILED(hr)) {
|
|
return hr; // Not so good
|
|
}
|
|
|
|
ASSUME(m_pEnvelope == NULL);
|
|
m_PercentAdjustment = m_PercentX + (m_PercentY * g_ForceFeedbackDevice.GetYMappingPercent(ET_CONSTANTFORCE_200))/100;
|
|
m_Gain = m_Gain/100 * m_PercentAdjustment;
|
|
if (m_ConstantForceData.lMagnitude < 0) {
|
|
m_pEnvelope = new Envelope200(diEffect.lpEnvelope, -Magnitude(), m_Duration, hr);
|
|
m_ConstantForceData.lMagnitude = -10000;
|
|
} else {
|
|
m_pEnvelope = new Envelope200(diEffect.lpEnvelope, Magnitude(), m_Duration, hr);
|
|
m_ConstantForceData.lMagnitude = 10000;
|
|
}
|
|
|
|
if (m_EffectAngle <= 18000) {
|
|
m_ConstantForceData.lMagnitude *= -1;
|
|
}
|
|
|
|
return hr;
|
|
}
|
|
|
|
UINT ConstantForceEffect200::GetModifyOnlyNeeded() const
|
|
{
|
|
UINT retCount = 0;
|
|
|
|
if (m_TriggerPlayButton != 0) { // Trigger Button
|
|
retCount++;
|
|
}
|
|
if (m_TriggerRepeat != 0) { // Trigger repeat
|
|
retCount++;
|
|
}
|
|
|
|
return retCount;
|
|
}
|
|
|
|
HRESULT ConstantForceEffect200::FillModifyOnlyParms() const
|
|
{
|
|
if (g_pDataPackager == NULL) {
|
|
ASSUME_NOT_REACHED(); // This is only called from DataPackager::Create()
|
|
return SFERR_DRIVER_ERROR;
|
|
}
|
|
|
|
HRESULT hr = SUCCESS;
|
|
BYTE nextPacket = 1;
|
|
if (m_TriggerPlayButton != 0) { // Trigger Button
|
|
hr = FillModifyPacket200(nextPacket, INDEX_CE_BUTTONMAP_200, g_TriggerMap200[m_TriggerPlayButton]);
|
|
nextPacket++;
|
|
}
|
|
if (m_TriggerRepeat != 0) { // Trigger repeat
|
|
hr = FillModifyPacket200(nextPacket, INDEX_CE_BUTTONREPEAT_200, m_TriggerRepeat/DURATION_SCALE_200);
|
|
nextPacket++;
|
|
}
|
|
|
|
return hr;
|
|
}
|
|
|
|
HRESULT ConstantForceEffect200::FillCreatePacket(DataPacket& packet) const
|
|
{
|
|
// Sanity check
|
|
if (m_pEnvelope == NULL) {
|
|
ASSUME_NOT_REACHED(); // Should never happen
|
|
return SFERR_DRIVER_ERROR;
|
|
}
|
|
|
|
// Packet to set modify data[index] of current effect
|
|
if (!packet.AllocateBytes(22)) {
|
|
return SFERR_DRIVER_ERROR;
|
|
}
|
|
|
|
// Fill in the Generic Effect Information
|
|
FillHeader200(packet, ET_CONSTANTFORCE_200, NEW_EFFECT_ID);
|
|
|
|
unsigned short calc_byte = unsigned short(float(m_EffectAngle)/ANGLE_SCALE_200);
|
|
packet.m_pData[10]= BYTE(calc_byte & 0x7F); // Effect Angle
|
|
calc_byte = unsigned short(float(m_Gain)/GAIN_SCALE_200);
|
|
packet.m_pData[11]= BYTE(calc_byte & 0x7F); // Gain
|
|
|
|
// Envelope
|
|
Envelope200* pEnvelope = (Envelope200*)(m_pEnvelope);
|
|
packet.m_pData[12] = BYTE(pEnvelope->m_StartPercent & 0x7F); // Initial attack level
|
|
packet.m_pData[13] = BYTE(pEnvelope->m_AttackTime& 0x7F); // AttackTime Low
|
|
packet.m_pData[14] = BYTE(pEnvelope->m_AttackTime>> 7) & 0x7F; // AttackTime High
|
|
packet.m_pData[15] = BYTE(pEnvelope->m_SustainPercent & 0x7F); // Sustain level
|
|
packet.m_pData[16] = BYTE(pEnvelope->m_FadeStart & 0x7F); // SustainTime Low
|
|
packet.m_pData[17] = BYTE(pEnvelope->m_FadeStart >> 7) & 0x7F; // SustainTime High
|
|
packet.m_pData[18] = BYTE(pEnvelope->m_EndPercent & 0x7F); // What to decay to
|
|
// -- Duration of decay is figured by the firmware from total duration
|
|
|
|
/* short directionHack = 1;
|
|
if (m_EffectAngle <= 18000) {
|
|
directionHack = -1;
|
|
}
|
|
*/
|
|
// Convert magnitude to device percentage (0 to +126 --- +63 = 0 percent)
|
|
// calc_byte = unsigned short(float(directionHack * Magnitude()/DIDISTANCE_TO_PERCENT + 100)/POSITIVE_PERCENT_SCALE);
|
|
calc_byte = unsigned short(float(Magnitude()/DIDISTANCE_TO_PERCENT + 100)/POSITIVE_PERCENT_SCALE);
|
|
packet.m_pData[19] = BYTE(calc_byte & 0x7F); // Offset
|
|
|
|
packet.m_pData[20]= ComputeChecksum(packet, 20); // Checksum
|
|
|
|
// End of packet
|
|
packet.m_pData[21]= MIDI_EOX; // End of SysEX packet
|
|
|
|
return SUCCESS;
|
|
}
|
|
|
|
HRESULT ConstantForceEffect200::Modify(InternalEffect& newEffect, DWORD modFlags)
|
|
{
|
|
g_pDataPackager->ClearPackets();
|
|
HRESULT adjustResult = AdjustModifyParams(newEffect, modFlags);
|
|
if (FAILED(adjustResult)) {
|
|
return adjustResult;
|
|
}
|
|
|
|
HRESULT hr = SUCCESS;
|
|
ConstantForceEffect200* pEffect = (ConstantForceEffect200*)(&newEffect);
|
|
BYTE nextPacket = 0;
|
|
if (modFlags & DIEP_DURATION) {
|
|
hr = FillModifyPacket200(nextPacket, INDEX_CE_DURATION_200, pEffect->m_Duration/DURATION_SCALE_200);
|
|
nextPacket++;
|
|
}
|
|
if (modFlags & DIEP_ENVELOPE) {
|
|
Envelope200* pOldEnvelope = (Envelope200*)(m_pEnvelope);
|
|
Envelope200* pNewEnvelope = (Envelope200*)(pEffect->m_pEnvelope);
|
|
if (pOldEnvelope->m_StartPercent != pNewEnvelope->m_StartPercent) {
|
|
hr = FillModifyPacket200(nextPacket, INDEX_CE_STARTPERCENT_200, pNewEnvelope->m_StartPercent);
|
|
nextPacket++;
|
|
}
|
|
if (pOldEnvelope->m_AttackTime != pNewEnvelope->m_AttackTime) {
|
|
hr = FillModifyPacket200(nextPacket, INDEX_CE_ATTTACK_TIME_200, pNewEnvelope->m_AttackTime);
|
|
nextPacket++;
|
|
}
|
|
if (pOldEnvelope->m_SustainPercent != pNewEnvelope->m_SustainPercent) {
|
|
hr = FillModifyPacket200(nextPacket, INDEX_CE_SUSTAINPERCENT_200, pNewEnvelope->m_SustainPercent);
|
|
nextPacket++;
|
|
}
|
|
if (pOldEnvelope->m_FadeStart != pNewEnvelope->m_FadeStart) {
|
|
hr = FillModifyPacket200(nextPacket, INDEX_CE_FADESTART_200, pNewEnvelope->m_FadeStart);
|
|
nextPacket++;
|
|
}
|
|
if (pOldEnvelope->m_EndPercent != pNewEnvelope->m_EndPercent) {
|
|
hr = FillModifyPacket200(nextPacket, INDEX_CE_ENDPERCENT_200, pNewEnvelope->m_EndPercent);
|
|
nextPacket++;
|
|
}
|
|
}
|
|
if (modFlags & DIEP_TRIGGERBUTTON) {
|
|
hr = FillModifyPacket200(nextPacket, INDEX_CE_BUTTONMAP_200, g_TriggerMap200[pEffect->m_TriggerPlayButton]);
|
|
nextPacket++;
|
|
}
|
|
if (modFlags & DIEP_TRIGGERREPEATINTERVAL) {
|
|
hr = FillModifyPacket200(nextPacket, INDEX_CE_BUTTONREPEAT_200, pEffect->m_TriggerRepeat/DURATION_SCALE_200);
|
|
nextPacket++;
|
|
}
|
|
if (modFlags & DIEP_GAIN) {
|
|
DWORD calc = DWORD(float(pEffect->m_Gain)/GAIN_SCALE_200);
|
|
hr = FillModifyPacket200(nextPacket, INDEX_CE_GAIN_200, calc);
|
|
nextPacket++;
|
|
}
|
|
if (modFlags & DIEP_TYPESPECIFICPARAMS) { // Send changed items
|
|
if (Magnitude() != pEffect->Magnitude()) {
|
|
/* short directionHack = 1;
|
|
if (pEffect->m_EffectAngle <= 18000) {
|
|
directionHack = -1;
|
|
}
|
|
DWORD calc = DWORD(float(directionHack * pEffect->Magnitude()/DIDISTANCE_TO_PERCENT + 100)/POSITIVE_PERCENT_SCALE);
|
|
*/ DWORD calc = DWORD(float(pEffect->Magnitude()/DIDISTANCE_TO_PERCENT + 100)/POSITIVE_PERCENT_SCALE);
|
|
hr = FillModifyPacket200(nextPacket, INDEX_CE_MAGNITUDE_200, calc);
|
|
nextPacket++;
|
|
}
|
|
}
|
|
|
|
// Did adjust have anything bad to say?
|
|
if (hr == SUCCESS) {
|
|
return adjustResult;
|
|
}
|
|
return hr;
|
|
}
|
|
|
|
HRESULT ConstantForceEffect200::AdjustModifyParams(InternalEffect& newEffect, DWORD& modFlags)
|
|
{
|
|
// Check to see if values being modified are acceptable
|
|
DWORD possMod = DIEP_DURATION | DIEP_GAIN | DIEP_TRIGGERBUTTON | DIEP_TRIGGERREPEATINTERVAL | DIEP_TYPESPECIFICPARAMS | DIEP_ENVELOPE | DIEP_DIRECTION;
|
|
if ((g_TotalModifiable & modFlags & possMod) == 0) { // Nothing to modify?
|
|
modFlags = 0;
|
|
return S_FALSE;
|
|
}
|
|
|
|
BOOL playingChecked = FALSE;
|
|
HRESULT hr = SUCCESS;
|
|
|
|
if ((modFlags & (DIEP_SAMPLEPERIOD | DIEP_AXES)) != 0) {
|
|
hr = S_FALSE; // Cannot modify all they asked for
|
|
}
|
|
|
|
ConstantForceEffect200* pEffect = (ConstantForceEffect200*)(&newEffect);
|
|
unsigned short numPackets = 0;
|
|
if (modFlags & DIEP_DURATION) {
|
|
if (m_Duration == pEffect->m_Duration) {
|
|
modFlags &= ~DIEP_DURATION; // Remove duration flag, unchanged
|
|
} else {
|
|
if (IsReallyPlaying(playingChecked) == TRUE) {
|
|
return DIERR_EFFECTPLAYING;
|
|
} else {
|
|
modFlags |= DIEP_ENVELOPE; // Duration change forces envelope change
|
|
numPackets++;
|
|
}
|
|
}
|
|
}
|
|
if (modFlags & DIEP_DIRECTION) {
|
|
modFlags |= (DIEP_GAIN | DIEP_TYPESPECIFICPARAMS); // If angle is greater than 180 magnitude is inverted
|
|
}
|
|
if (modFlags & DIEP_GAIN) {
|
|
if (m_Gain == pEffect->m_Gain) {
|
|
modFlags &= ~DIEP_GAIN; // Remove gain flag, unchanged
|
|
} else {
|
|
numPackets++;
|
|
}
|
|
}
|
|
if (modFlags & DIEP_TYPESPECIFICPARAMS) {
|
|
if (Magnitude() == pEffect->Magnitude()) {
|
|
modFlags &= ~DIEP_TYPESPECIFICPARAMS; // Nothing typespecific really changed
|
|
} else {
|
|
numPackets++;
|
|
}
|
|
modFlags |= DIEP_ENVELOPE;
|
|
}
|
|
if (modFlags & DIEP_ENVELOPE) {
|
|
int numEnvelopeChanged = 0;
|
|
|
|
if (m_pEnvelope == NULL || pEffect->m_pEnvelope == NULL) {
|
|
ASSUME_NOT_REACHED(); // Envelope should always be created!
|
|
} else {
|
|
Envelope200* pOldEnvelope = (Envelope200*)(m_pEnvelope);
|
|
Envelope200* pNewEnvelope = (Envelope200*)(pEffect->m_pEnvelope);
|
|
if (pOldEnvelope->m_AttackTime != pNewEnvelope->m_AttackTime) {
|
|
numEnvelopeChanged++;
|
|
}
|
|
if (pOldEnvelope->m_FadeStart != pNewEnvelope->m_FadeStart) {
|
|
numEnvelopeChanged++;
|
|
}
|
|
if (pOldEnvelope->m_StartPercent != pNewEnvelope->m_StartPercent) {
|
|
numEnvelopeChanged++;
|
|
}
|
|
if (pOldEnvelope->m_SustainPercent != pNewEnvelope->m_SustainPercent) {
|
|
numEnvelopeChanged++;
|
|
}
|
|
if (pOldEnvelope->m_EndPercent != pNewEnvelope->m_EndPercent) {
|
|
numEnvelopeChanged++;
|
|
}
|
|
}
|
|
|
|
if (numEnvelopeChanged == 0) {
|
|
modFlags &= ~DIEP_ENVELOPE; // Remove envelope flag, unchanged
|
|
} else {
|
|
numPackets += (USHORT)numEnvelopeChanged;
|
|
}
|
|
}
|
|
if (modFlags & DIEP_TRIGGERBUTTON) {
|
|
if (m_TriggerPlayButton == pEffect->m_TriggerPlayButton) {
|
|
modFlags &= ~DIEP_TRIGGERBUTTON; // Remove trigger flag, unchanged
|
|
} else {
|
|
numPackets++;
|
|
}
|
|
}
|
|
if (modFlags & DIEP_TRIGGERREPEATINTERVAL) {
|
|
if (m_TriggerRepeat == pEffect->m_TriggerRepeat) {
|
|
modFlags &= ~DIEP_TRIGGERREPEATINTERVAL; // Remove trigger repeat flag, unchanged
|
|
} else {
|
|
numPackets++;
|
|
}
|
|
}
|
|
|
|
if (numPackets != 0) {
|
|
g_pDataPackager->AllocateDataPackets(numPackets);
|
|
}
|
|
|
|
return hr;
|
|
}
|
|
|
|
/******************* class CustomForceEffect ***********************/
|
|
CustomForceEffect::CustomForceEffect() : InternalEffect()
|
|
{
|
|
m_CustomForceData.rglForceData = NULL;
|
|
}
|
|
|
|
CustomForceEffect::~CustomForceEffect()
|
|
{
|
|
if (m_CustomForceData.rglForceData != NULL) {
|
|
delete[] (m_CustomForceData.rglForceData);
|
|
m_CustomForceData.rglForceData = NULL;
|
|
}
|
|
}
|
|
|
|
HRESULT CustomForceEffect::Create(const DIEFFECT& diEffect)
|
|
{
|
|
// Zero out old struct
|
|
::memset(&m_CustomForceData, 0, sizeof(m_CustomForceData));
|
|
|
|
// Validation Check
|
|
if (diEffect.lpvTypeSpecificParams == NULL) {
|
|
ASSUME_NOT_REACHED();
|
|
return SFERR_INVALID_PARAM;
|
|
}
|
|
if (diEffect.cbTypeSpecificParams != sizeof(m_CustomForceData)) {
|
|
ASSUME_NOT_REACHED();
|
|
return SFERR_INVALID_PARAM;
|
|
}
|
|
DICUSTOMFORCE* pDICustom = (DICUSTOMFORCE*)(diEffect.lpvTypeSpecificParams);
|
|
if (pDICustom->cChannels == 0) {
|
|
ASSUME_NOT_REACHED();
|
|
return SFERR_INVALID_PARAM;
|
|
}
|
|
|
|
// We are a little different from every one else (instead of 0 beiing devcie default, it is custom sample period)
|
|
BOOL useCustomForceSamplePeriod = (diEffect.dwSamplePeriod == 0);
|
|
|
|
// Let the base class do its magic
|
|
HRESULT hr = InternalEffect::Create(diEffect);
|
|
if (FAILED(hr)) {
|
|
return hr;
|
|
}
|
|
|
|
// Copy data to local store
|
|
m_CustomForceData.cChannels = pDICustom->cChannels;
|
|
m_CustomForceData.dwSamplePeriod = pDICustom->dwSamplePeriod;
|
|
if (m_CustomForceData.dwSamplePeriod > MAX_TIME_200) {
|
|
m_CustomForceData.dwSamplePeriod = MAX_TIME_200;
|
|
hr = DI_TRUNCATED;
|
|
}
|
|
if (useCustomForceSamplePeriod) {
|
|
if (m_CustomForceData.dwSamplePeriod == 0) { // These both can't be zero
|
|
return SFERR_NO_SUPPORT;
|
|
}
|
|
m_SamplePeriod = m_CustomForceData.dwSamplePeriod;
|
|
}
|
|
m_CustomForceData.cSamples = pDICustom->cSamples;
|
|
|
|
long int* pForceData = NULL;
|
|
try { // They could probably ask for anything
|
|
pForceData = new long int[m_CustomForceData.cSamples];
|
|
} catch (...) {
|
|
return SFERR_DRIVER_ERROR;
|
|
}
|
|
if (pForceData == NULL)
|
|
{
|
|
return SFERR_DRIVER_ERROR;
|
|
}
|
|
::memcpy(pForceData, pDICustom->rglForceData, sizeof(long int) * m_CustomForceData.cSamples);
|
|
m_CustomForceData.rglForceData = pForceData;
|
|
pForceData = NULL;
|
|
|
|
return hr;
|
|
}
|
|
|
|
/******************* class CustomForceEffect200 ***********************/
|
|
CustomForceEffect200::CustomForceEffect200() : CustomForceEffect(),
|
|
m_pEnvelope(NULL)
|
|
{
|
|
}
|
|
|
|
CustomForceEffect200::~CustomForceEffect200()
|
|
{
|
|
if (m_pEnvelope != NULL) {
|
|
delete m_pEnvelope;
|
|
m_pEnvelope = NULL;
|
|
}
|
|
}
|
|
|
|
BYTE CustomForceEffect200::GetRepeatIndex() const
|
|
{
|
|
return INDEX_CF_REPEAT_200;
|
|
}
|
|
|
|
HRESULT CustomForceEffect200::Create(const DIEFFECT& diEffect)
|
|
{
|
|
HRESULT hr = CustomForceEffect::Create(diEffect);
|
|
|
|
// How did creation go?
|
|
if (FAILED(hr)) {
|
|
return hr; // Not so good
|
|
}
|
|
|
|
ASSUME(m_pEnvelope == NULL);
|
|
m_PercentAdjustment = m_PercentX + (m_PercentY * g_ForceFeedbackDevice.GetYMappingPercent(ET_CUSTOMFORCE_200))/100;
|
|
m_Gain = m_Gain/100 * m_PercentAdjustment;
|
|
m_pEnvelope = new Envelope200(diEffect.lpEnvelope, 10000, m_Duration, hr); // Sustain Mag is 100 percent
|
|
|
|
return hr;
|
|
}
|
|
|
|
UINT CustomForceEffect200::GetModifyOnlyNeeded() const
|
|
{
|
|
UINT retCount = 0;
|
|
|
|
if (m_TriggerPlayButton != 0) { // Trigger Button
|
|
retCount++;
|
|
}
|
|
if (m_TriggerRepeat != 0) { // Trigger repeat
|
|
retCount++;
|
|
}
|
|
|
|
// Envelope parms
|
|
ASSUME_NOT_NULL(m_pEnvelope);
|
|
if (m_pEnvelope != NULL) { // Already converted to device units
|
|
if (m_pEnvelope->m_StartPercent != 127) {
|
|
retCount++;
|
|
}
|
|
if (m_pEnvelope->m_AttackTime != 0) {
|
|
retCount++;
|
|
}
|
|
if (m_pEnvelope->m_SustainPercent != 127) {
|
|
retCount++;
|
|
}
|
|
if (m_pEnvelope->m_FadeStart != 0) {
|
|
retCount++;
|
|
}
|
|
if (m_pEnvelope->m_EndPercent != 127) {
|
|
retCount++;
|
|
}
|
|
}
|
|
|
|
return retCount;
|
|
}
|
|
|
|
HRESULT CustomForceEffect200::FillModifyOnlyParms() const
|
|
{
|
|
if (g_pDataPackager == NULL) {
|
|
ASSUME_NOT_REACHED(); // This is only called from DataPackager::Create()
|
|
return SFERR_DRIVER_ERROR;
|
|
}
|
|
|
|
HRESULT hr = SUCCESS;
|
|
BYTE nextPacket = 1;
|
|
|
|
if (m_TriggerPlayButton != 0) { // Trigger Button
|
|
hr = FillModifyPacket200(nextPacket, INDEX_CF_BUTTONMAP_200, g_TriggerMap200[m_TriggerPlayButton]);
|
|
nextPacket++;
|
|
}
|
|
if (m_TriggerRepeat != 0) { // Trigger repeat
|
|
hr = FillModifyPacket200(nextPacket, INDEX_CF_BUTTONREPEAT_200, m_TriggerRepeat/DURATION_SCALE_200);
|
|
nextPacket++;
|
|
}
|
|
|
|
// Envelope parms
|
|
ASSUME_NOT_NULL(m_pEnvelope);
|
|
if (m_pEnvelope != NULL) { // Already converted to device units
|
|
if (m_pEnvelope->m_StartPercent != 127) {
|
|
hr = FillModifyPacket200(nextPacket, INDEX_CF_STARTPERCENT_200, m_pEnvelope->m_StartPercent);
|
|
nextPacket++;
|
|
}
|
|
if (m_pEnvelope->m_AttackTime != 0) {
|
|
hr = FillModifyPacket200(nextPacket, INDEX_CF_ATTTACK_TIME_200, m_pEnvelope->m_AttackTime);
|
|
nextPacket++;
|
|
}
|
|
if (m_pEnvelope->m_SustainPercent != 127) {
|
|
hr = FillModifyPacket200(nextPacket, INDEX_CF_SUSTAINPERCENT_200, m_pEnvelope->m_SustainPercent);
|
|
nextPacket++;
|
|
}
|
|
if (m_pEnvelope->m_FadeStart != 0) {
|
|
hr = FillModifyPacket200(nextPacket, INDEX_CF_FADESTART_200, m_pEnvelope->m_FadeStart);
|
|
nextPacket++;
|
|
}
|
|
if (m_pEnvelope->m_EndPercent != 127) {
|
|
hr = FillModifyPacket200(nextPacket, INDEX_CF_ENDPERCENT_200, m_pEnvelope->m_EndPercent);
|
|
nextPacket++;
|
|
}
|
|
}
|
|
|
|
return hr;
|
|
}
|
|
|
|
HRESULT CustomForceEffect200::FillCreatePacket(DataPacket& packet) const
|
|
{
|
|
if (m_CustomForceData.cChannels == 0) {
|
|
ASSUME_NOT_REACHED(); // Already checked this
|
|
return SFERR_INVALID_PARAM;
|
|
}
|
|
|
|
// Create waveform packet
|
|
BYTE* pWaveForm = NULL;
|
|
try { // Who knows how much we are being asked to allocate
|
|
pWaveForm = new BYTE[m_CustomForceData.cSamples / m_CustomForceData.cChannels * 2]; // This is the max possible
|
|
} catch(...) {
|
|
return SFERR_DRIVER_ERROR;
|
|
}
|
|
if (pWaveForm == NULL)
|
|
{
|
|
return SFERR_DRIVER_ERROR;
|
|
}
|
|
|
|
HRESULT hr = SUCCESS;
|
|
|
|
// Fill wavelet packet
|
|
if (m_CustomForceData.rglForceData[0] > 10000) {
|
|
m_CustomForceData.rglForceData[0] = 10000;
|
|
hr = DI_TRUNCATED;
|
|
} else if (m_CustomForceData.rglForceData[0] < -10000) {
|
|
m_CustomForceData.rglForceData[0] = -10000;
|
|
hr = DI_TRUNCATED;
|
|
}
|
|
unsigned short wave_byte = unsigned short(float(m_CustomForceData.rglForceData[0] + 10000)/WAVELET_SCALE_200);
|
|
pWaveForm[0] = BYTE(wave_byte & 0x3F);
|
|
pWaveForm[1] = BYTE(wave_byte >> 6) & 0x07;
|
|
UINT waveletCount = 2;
|
|
UINT iPrev = 0;
|
|
for (UINT i = (0 + m_CustomForceData.cChannels); i < m_CustomForceData.cSamples; i += m_CustomForceData.cChannels) {
|
|
if (m_CustomForceData.rglForceData[i] > 10000) {
|
|
m_CustomForceData.rglForceData[i] = 10000;
|
|
hr = DI_TRUNCATED;
|
|
} else if (m_CustomForceData.rglForceData[i] < -10000) {
|
|
m_CustomForceData.rglForceData[i] = -10000;
|
|
hr = DI_TRUNCATED;
|
|
}
|
|
LONG distance = m_CustomForceData.rglForceData[i] - m_CustomForceData.rglForceData[iPrev];
|
|
if ((distance >= 0) && (distance <= WAVELET_DISTANCE_200) ||
|
|
(distance < 0) && (-distance <= WAVELET_DISTANCE_200)) { // Relative (single byte)
|
|
wave_byte = unsigned short((float(distance + WAVELET_DISTANCE_200)/WAVELET_DISTANCE_SCALE_200) + 0.5f);
|
|
ASSUME(wave_byte <= 62);
|
|
pWaveForm[waveletCount++] = (BYTE(wave_byte) & 0x3F) | 0x40;
|
|
} else { // Non relative (double byte)
|
|
wave_byte = unsigned short(float(m_CustomForceData.rglForceData[i] + 10000)/WAVELET_SCALE_200);
|
|
pWaveForm[waveletCount++] = BYTE(wave_byte & 0x3F);
|
|
pWaveForm[waveletCount++] = BYTE(wave_byte >> 6) & 0x07;
|
|
}
|
|
iPrev = i;
|
|
}
|
|
|
|
|
|
// Packet to set modify data[index] of current effect
|
|
UINT totalBytes = 19 + waveletCount; // Header (10) + Fixed (7) + Footer(2) + waveBytes
|
|
if (!packet.AllocateBytes(totalBytes)) {
|
|
return SFERR_DRIVER_ERROR;
|
|
}
|
|
|
|
// Fill in the Generic Effect Information
|
|
FillHeader200(packet, ET_CUSTOMFORCE_200, NEW_EFFECT_ID);
|
|
|
|
// unsigned short calc_byte = unsigned short(float(m_EffectAngle)/ANGLE_SCALE_200);
|
|
// packet.m_pData[10]= BYTE(calc_byte & 0x7F); // Effect Angle
|
|
if (m_EffectAngle <= 18000) { // Custom for device units
|
|
packet.m_pData[10]= BYTE(64);
|
|
} else {
|
|
packet.m_pData[10]= BYTE(0);
|
|
}
|
|
|
|
unsigned short calc_byte = unsigned short(float(m_Gain)/GAIN_SCALE_200);
|
|
packet.m_pData[11]= BYTE(calc_byte & 0x7F); // Gain
|
|
|
|
calc_byte = unsigned short(m_CustomForceData.dwSamplePeriod/DURATION_SCALE_200);
|
|
packet.m_pData[12] = BYTE(calc_byte & 0x7F); // Force Sample Interval Low
|
|
packet.m_pData[13] = BYTE(calc_byte >> 7) & 0x7F; // Force Sample Interval High
|
|
|
|
calc_byte = unsigned short(m_SamplePeriod/DURATION_SCALE_200);
|
|
packet.m_pData[14] = BYTE(calc_byte & 0x7F); // Force Output Interval Low
|
|
packet.m_pData[15] = BYTE(calc_byte >> 7) & 0x7F; // Force Output Interval High
|
|
|
|
packet.m_pData[16] = 63; // Constant force offset (0 - DI Doesn't support)
|
|
|
|
// Fill in the wavelet info (already converted to device units
|
|
for (UINT nextWaveIndex = 0; nextWaveIndex < waveletCount; nextWaveIndex++) {
|
|
packet.m_pData[17 + nextWaveIndex] = pWaveForm[nextWaveIndex];
|
|
}
|
|
|
|
packet.m_pData[totalBytes-2]= ComputeChecksum(packet, totalBytes-2); // Checksum
|
|
|
|
// End of packet
|
|
packet.m_pData[totalBytes-1]= MIDI_EOX; // End of SysEX packet
|
|
|
|
// Clean up
|
|
delete[] pWaveForm; // can't modify it, no need to save it
|
|
pWaveForm = NULL;
|
|
|
|
return hr;
|
|
}
|
|
|
|
HRESULT CustomForceEffect200::Modify(InternalEffect& newEffect, DWORD modFlags)
|
|
{
|
|
g_pDataPackager->ClearPackets();
|
|
|
|
HRESULT adjustResult = AdjustModifyParams(newEffect, modFlags);
|
|
if (FAILED(adjustResult)) {
|
|
return adjustResult;
|
|
}
|
|
HRESULT hr = SUCCESS;
|
|
|
|
CustomForceEffect200* pEffect = (CustomForceEffect200*)(&newEffect);
|
|
BYTE nextPacket = 0;
|
|
if (modFlags & DIEP_DURATION) {
|
|
hr = FillModifyPacket200(nextPacket, INDEX_CF_DURATION_200, pEffect->m_Duration/DURATION_SCALE_200);
|
|
nextPacket++;
|
|
}
|
|
if (modFlags & DIEP_ENVELOPE) {
|
|
Envelope200* pOldEnvelope = (Envelope200*)(m_pEnvelope);
|
|
Envelope200* pNewEnvelope = (Envelope200*)(pEffect->m_pEnvelope);
|
|
if (pOldEnvelope->m_StartPercent != pNewEnvelope->m_StartPercent) {
|
|
hr = FillModifyPacket200(nextPacket, INDEX_CF_STARTPERCENT_200, pNewEnvelope->m_StartPercent);
|
|
nextPacket++;
|
|
}
|
|
if (pOldEnvelope->m_AttackTime != pNewEnvelope->m_AttackTime) {
|
|
hr = FillModifyPacket200(nextPacket, INDEX_CF_ATTTACK_TIME_200, pNewEnvelope->m_AttackTime);
|
|
nextPacket++;
|
|
}
|
|
if (pOldEnvelope->m_SustainPercent != pNewEnvelope->m_SustainPercent) {
|
|
hr = FillModifyPacket200(nextPacket, INDEX_CF_SUSTAINPERCENT_200, pNewEnvelope->m_SustainPercent);
|
|
nextPacket++;
|
|
}
|
|
if (pOldEnvelope->m_FadeStart != pNewEnvelope->m_FadeStart) {
|
|
hr = FillModifyPacket200(nextPacket, INDEX_CF_FADESTART_200, pNewEnvelope->m_FadeStart);
|
|
nextPacket++;
|
|
}
|
|
if (pOldEnvelope->m_EndPercent != pNewEnvelope->m_EndPercent) {
|
|
hr = FillModifyPacket200(nextPacket, INDEX_CF_ENDPERCENT_200, pNewEnvelope->m_EndPercent);
|
|
nextPacket++;
|
|
}
|
|
}
|
|
if (modFlags & DIEP_DIRECTION) {
|
|
if (pEffect->m_EffectAngle > 18000) {
|
|
hr = FillModifyPacket200(nextPacket, INDEX_CF_DIRECTIONANGLE_200, 0, 0);
|
|
} else {
|
|
hr = FillModifyPacket200(nextPacket, INDEX_CF_DIRECTIONANGLE_200, BYTE(64), 0);
|
|
}
|
|
nextPacket++;
|
|
}
|
|
if (modFlags & DIEP_TRIGGERBUTTON) {
|
|
hr = FillModifyPacket200(nextPacket, INDEX_CF_BUTTONMAP_200, g_TriggerMap200[pEffect->m_TriggerPlayButton]);
|
|
nextPacket++;
|
|
}
|
|
if (modFlags & DIEP_TRIGGERREPEATINTERVAL) {
|
|
hr = FillModifyPacket200(nextPacket, INDEX_CF_BUTTONREPEAT_200, pEffect->m_TriggerRepeat/DURATION_SCALE_200);
|
|
nextPacket++;
|
|
}
|
|
if (modFlags & DIEP_GAIN) {
|
|
DWORD calc = DWORD(float(pEffect->m_Gain)/GAIN_SCALE_200);
|
|
hr = FillModifyPacket200(nextPacket, INDEX_CF_GAIN_200, calc);
|
|
nextPacket++;
|
|
}
|
|
if (modFlags & DIEP_SAMPLEPERIOD) {
|
|
hr = FillModifyPacket200(nextPacket, INDEX_CF_SAMPLE_PERIOD_200, DWORD(pEffect->m_SamplePeriod/DURATION_SCALE_200));
|
|
nextPacket++;
|
|
}
|
|
if (modFlags & DIEP_TYPESPECIFICPARAMS) { // Send changed items
|
|
hr = FillModifyPacket200(nextPacket, INDEX_CF_FORCESAMPLE_200, DWORD(pEffect->m_CustomForceData.dwSamplePeriod)/DURATION_SCALE_200);
|
|
nextPacket++;
|
|
}
|
|
|
|
if (hr == SUCCESS) {
|
|
return adjustResult;
|
|
}
|
|
return hr;
|
|
}
|
|
|
|
HRESULT CustomForceEffect200::AdjustModifyParams(InternalEffect& newEffect, DWORD& modFlags)
|
|
{
|
|
// Check to see if values being modified are acceptable
|
|
DWORD possMod = DIEP_DURATION | DIEP_GAIN | DIEP_TRIGGERBUTTON | DIEP_TRIGGERREPEATINTERVAL | DIEP_TYPESPECIFICPARAMS | DIEP_ENVELOPE | DIEP_SAMPLEPERIOD | DIEP_DIRECTION;
|
|
if ((g_TotalModifiable & modFlags & possMod) == 0) { // Nothing to modify?
|
|
modFlags = 0;
|
|
return SFERR_NO_SUPPORT;
|
|
}
|
|
|
|
CustomForceEffect200* pEffect = (CustomForceEffect200*)(&newEffect);
|
|
unsigned short numPackets = 0;
|
|
HRESULT hr = SUCCESS;
|
|
|
|
if ((modFlags & (DIEP_AXES)) != 0) {
|
|
hr = S_FALSE; // Cannot modify all they asked for
|
|
}
|
|
|
|
BOOL playingChecked = FALSE;
|
|
if (modFlags & DIEP_DURATION) {
|
|
if (m_Duration == pEffect->m_Duration) {
|
|
modFlags &= ~DIEP_DURATION; // Remove duration flag, unchanged
|
|
} else {
|
|
if (IsReallyPlaying(playingChecked) == FALSE) {
|
|
modFlags |= DIEP_ENVELOPE; // Duration change forces envelope change
|
|
numPackets++;
|
|
} else {
|
|
return DIERR_EFFECTPLAYING;
|
|
}
|
|
}
|
|
}
|
|
if (modFlags & DIEP_DIRECTION) {
|
|
modFlags &= ~DIEP_DIRECTION;
|
|
if (m_EffectAngle != pEffect->m_EffectAngle) {
|
|
modFlags |= DIEP_GAIN;
|
|
if (int(m_EffectAngle / 18000) != int(pEffect->m_EffectAngle / 18000)) {
|
|
modFlags |= DIEP_DIRECTION;
|
|
numPackets++;
|
|
}
|
|
}
|
|
}
|
|
if (modFlags & DIEP_ENVELOPE) {
|
|
int numEnvelopeChanged = 0;
|
|
|
|
if (m_pEnvelope == NULL || pEffect->m_pEnvelope == NULL) {
|
|
ASSUME_NOT_REACHED(); // Envelope should always be created!
|
|
} else {
|
|
Envelope200* pOldEnvelope = (Envelope200*)(m_pEnvelope);
|
|
Envelope200* pNewEnvelope = (Envelope200*)(pEffect->m_pEnvelope);
|
|
if (pOldEnvelope->m_AttackTime != pNewEnvelope->m_AttackTime) {
|
|
numEnvelopeChanged++;
|
|
}
|
|
if (pOldEnvelope->m_FadeStart != pNewEnvelope->m_FadeStart) {
|
|
numEnvelopeChanged++;
|
|
}
|
|
if (pOldEnvelope->m_StartPercent != pNewEnvelope->m_StartPercent) {
|
|
numEnvelopeChanged++;
|
|
}
|
|
if (pOldEnvelope->m_SustainPercent != pNewEnvelope->m_SustainPercent) {
|
|
numEnvelopeChanged++;
|
|
}
|
|
if (pOldEnvelope->m_EndPercent != pNewEnvelope->m_EndPercent) {
|
|
numEnvelopeChanged++;
|
|
}
|
|
}
|
|
|
|
if (numEnvelopeChanged == 0) {
|
|
modFlags &= ~DIEP_ENVELOPE; // Remove envelope flag, unchanged
|
|
} else {
|
|
numPackets += (USHORT)numEnvelopeChanged;
|
|
}
|
|
}
|
|
if (modFlags & DIEP_TRIGGERBUTTON) {
|
|
if (m_TriggerPlayButton == pEffect->m_TriggerPlayButton) {
|
|
modFlags &= ~DIEP_TRIGGERBUTTON; // Remove trigger flag, unchanged
|
|
} else {
|
|
numPackets++;
|
|
}
|
|
}
|
|
if (modFlags & DIEP_TRIGGERREPEATINTERVAL) {
|
|
if (m_TriggerRepeat == pEffect->m_TriggerRepeat) {
|
|
modFlags &= ~DIEP_TRIGGERREPEATINTERVAL; // Remove trigger repeat flag, unchanged
|
|
} else {
|
|
numPackets++;
|
|
}
|
|
}
|
|
if (modFlags & DIEP_GAIN) {
|
|
if (m_Gain == pEffect->m_Gain) {
|
|
modFlags &= ~DIEP_GAIN; // Remove gain flag, unchanged
|
|
} else {
|
|
numPackets++;
|
|
}
|
|
}
|
|
if (modFlags & DIEP_SAMPLEPERIOD) {
|
|
if (m_SamplePeriod == pEffect->m_SamplePeriod) {
|
|
modFlags &= ~DIEP_SAMPLEPERIOD; // Remove sample period flag, unchanged
|
|
} else {
|
|
numPackets++;
|
|
}
|
|
}
|
|
if (modFlags & DIEP_TYPESPECIFICPARAMS) {
|
|
if (m_CustomForceData.dwSamplePeriod == pEffect->m_CustomForceData.dwSamplePeriod) {
|
|
modFlags &= ~DIEP_TYPESPECIFICPARAMS; // Nothing typespecific really changed
|
|
} else {
|
|
numPackets ++;
|
|
}
|
|
}
|
|
|
|
if (numPackets != 0) {
|
|
g_pDataPackager->AllocateDataPackets(numPackets);
|
|
}
|
|
|
|
return hr;
|
|
}
|
|
|
|
|
|
/******************* class WallEffect ***********************/
|
|
HRESULT WallEffect::Create(const DIEFFECT& diEffect)
|
|
{
|
|
// Zero out old struct
|
|
::memset(&m_WallData, 0, sizeof(BE_WALL_PARAM));
|
|
|
|
// Validation Check
|
|
if (diEffect.lpvTypeSpecificParams == NULL) {
|
|
ASSUME_NOT_REACHED();
|
|
return SFERR_INVALID_PARAM;
|
|
}
|
|
if (diEffect.cbTypeSpecificParams != sizeof(BE_WALL_PARAM)) {
|
|
ASSUME_NOT_REACHED();
|
|
return SFERR_INVALID_PARAM;
|
|
}
|
|
|
|
// Let the base class do its magic
|
|
HRESULT hr = InternalEffect::Create(diEffect);
|
|
if (FAILED(hr)) {
|
|
return hr;
|
|
}
|
|
|
|
// Copy data to local store
|
|
::memcpy(&m_WallData, diEffect.lpvTypeSpecificParams, sizeof(BE_WALL_PARAM));
|
|
m_WallData.m_WallAngle %= 36000; // No telling what we might have been sent
|
|
|
|
if (m_WallData.m_WallDistance > 10000) {
|
|
m_WallData.m_WallDistance = 10000;
|
|
hr = DI_TRUNCATED;
|
|
}
|
|
if (m_WallData.m_WallConstant > 10000) {
|
|
m_WallData.m_WallConstant = 10000;
|
|
hr = DI_TRUNCATED;
|
|
} else if (m_WallData.m_WallConstant < -10000) {
|
|
m_WallData.m_WallConstant = -10000;
|
|
hr = DI_TRUNCATED;
|
|
}
|
|
|
|
return hr;
|
|
}
|
|
|
|
/******************* class WallEffect200 ***********************/
|
|
BYTE WallEffect200::GetRepeatIndex() const
|
|
{
|
|
return INDEX_BE_REPEAT_200;
|
|
}
|
|
|
|
HRESULT WallEffect200::Create(const DIEFFECT& diEffect)
|
|
{
|
|
::memset(m_Ds, 0, 4);
|
|
::memset(m_Fs, 0, 4);
|
|
|
|
HRESULT hr = WallEffect::Create(diEffect);
|
|
if (FAILED(hr)) {
|
|
return hr;
|
|
}
|
|
|
|
ComputeDsAndFs();
|
|
return hr;
|
|
}
|
|
|
|
void WallEffect200::ComputeDsAndFs()
|
|
{
|
|
// Projection method - not so smart
|
|
double xProjection = m_WallData.m_WallDistance;
|
|
|
|
// With angles greater than 180 Wall flips across X axis
|
|
BOOL wallInner = (m_WallData.m_WallType == WALL_INNER);
|
|
if (m_WallData.m_WallAngle > 18000) {
|
|
xProjection = -xProjection;
|
|
} else if (xProjection == 0) {
|
|
wallInner = !wallInner;
|
|
}
|
|
|
|
BYTE xProjectionScaled = BYTE((xProjection/double(DIDISTANCE_TO_PERCENT) + 100.0)/POSITIVE_PERCENT_SCALE) & 0x7F;
|
|
if (((xProjection <= 0) && (wallInner)) || ((xProjection > 0) && (!wallInner))) {
|
|
m_Fs[0] = BYTE(float(-m_WallData.m_WallConstant/DIDISTANCE_TO_PERCENT + 100)/POSITIVE_PERCENT_SCALE) & 0x7F;
|
|
m_Ds[0] = xProjectionScaled;
|
|
m_Ds[2] = 127; // Positive Max
|
|
} else {
|
|
m_Fs[0] = BYTE(float(m_WallData.m_WallConstant/DIDISTANCE_TO_PERCENT + 100)/POSITIVE_PERCENT_SCALE) & 0x7F;
|
|
m_Ds[0] = 0; // Negative Max
|
|
m_Ds[2] = xProjectionScaled;
|
|
}
|
|
|
|
// Simplistic mapping: if there is no mapping value is nothing for Y walls
|
|
if (g_ForceFeedbackDevice.GetYMappingPercent(ET_WALL_200) == 0) {
|
|
if (m_WallData.m_WallAngle == 0000 || m_WallData.m_WallAngle == 18000) { // Y axis walls
|
|
m_Fs[0] = 63; // 0
|
|
}
|
|
}
|
|
|
|
m_Ds[1] = m_Ds[0];
|
|
m_Ds[3] = m_Ds[2];
|
|
m_Fs[1] = m_Fs[0];
|
|
m_Fs[2] = m_Fs[0];
|
|
m_Fs[3] = m_Fs[0];
|
|
}
|
|
|
|
UINT WallEffect200::GetModifyOnlyNeeded() const
|
|
{
|
|
UINT retCount = 0;
|
|
|
|
if (m_TriggerPlayButton != 0) { // Trigger Button
|
|
retCount++;
|
|
}
|
|
if (m_TriggerRepeat != 0) { // Trigger repeat
|
|
retCount++;
|
|
}
|
|
if (m_Gain != 10000) { // Gain
|
|
retCount++;
|
|
}
|
|
|
|
return retCount;
|
|
}
|
|
|
|
HRESULT WallEffect200::FillModifyOnlyParms() const
|
|
{
|
|
if (g_pDataPackager == NULL) {
|
|
ASSUME_NOT_REACHED(); // This is only called from DataPackager::Create()
|
|
return SFERR_DRIVER_ERROR;
|
|
}
|
|
|
|
HRESULT hr = SUCCESS;
|
|
BYTE nextPacket = 1;
|
|
if (m_TriggerPlayButton != 0) { // Trigger Button
|
|
hr = FillModifyPacket200(nextPacket, INDEX_BE_BUTTONMAP_200, g_TriggerMap200[m_TriggerPlayButton]);
|
|
nextPacket++;
|
|
}
|
|
if (m_TriggerRepeat != 0) { // Trigger repeat
|
|
hr = FillModifyPacket200(nextPacket, INDEX_BE_BUTTONREPEAT_200, m_TriggerRepeat/DURATION_SCALE_200);
|
|
nextPacket++;
|
|
}
|
|
if (m_Gain != 10000) { // Gain
|
|
hr = FillModifyPacket200(nextPacket, INDEX_BE_GAIN_200, DWORD(float(m_Gain)/GAIN_SCALE_200));
|
|
nextPacket++;
|
|
}
|
|
|
|
return hr;
|
|
}
|
|
|
|
HRESULT WallEffect200::FillCreatePacket(DataPacket& packet) const
|
|
{
|
|
// Packet to set modify data[index] of current effect
|
|
if (!packet.AllocateBytes(21)) {
|
|
return SFERR_DRIVER_ERROR;
|
|
}
|
|
|
|
// Fill in the Generic Effect Information
|
|
FillHeader200(packet, ET_WALL_200, NEW_EFFECT_ID);
|
|
|
|
// All of the below items fit in one MidiByte (0..126/127) after conversion
|
|
packet.m_pData[10]= 0; // Effect Angle is along positive x.
|
|
|
|
// Computed in create
|
|
packet.m_pData[11]= m_Ds[0];
|
|
packet.m_pData[12]= m_Fs[0];
|
|
packet.m_pData[13]= m_Ds[1];
|
|
packet.m_pData[14]= m_Fs[1];
|
|
packet.m_pData[15]= m_Ds[2];
|
|
packet.m_pData[16]= m_Fs[2];
|
|
packet.m_pData[17]= m_Ds[3];
|
|
packet.m_pData[18]= m_Fs[3];
|
|
|
|
// End this puppy
|
|
packet.m_pData[19]= ComputeChecksum(packet, 19); // Checksum
|
|
packet.m_pData[20]= MIDI_EOX; // End of SysEX packet
|
|
|
|
return SUCCESS;
|
|
}
|
|
|
|
HRESULT WallEffect200::Modify(InternalEffect& newEffect, DWORD modFlags)
|
|
{
|
|
g_pDataPackager->ClearPackets();
|
|
HRESULT adjustResult = AdjustModifyParams(newEffect, modFlags);
|
|
if (FAILED(adjustResult)) {
|
|
return adjustResult;
|
|
}
|
|
|
|
HRESULT hr = SUCCESS;
|
|
WallEffect200* pEffect = (WallEffect200*)(&newEffect);
|
|
BYTE nextPacket = 0;
|
|
if (modFlags & DIEP_DURATION) {
|
|
hr = FillModifyPacket200(nextPacket, INDEX_BE_DURATION_200, pEffect->m_Duration/DURATION_SCALE_200);
|
|
nextPacket++;
|
|
}
|
|
if (modFlags & DIEP_TRIGGERBUTTON) {
|
|
hr = FillModifyPacket200(nextPacket, INDEX_BE_BUTTONMAP_200, g_TriggerMap200[pEffect->m_TriggerPlayButton]);
|
|
nextPacket++;
|
|
}
|
|
if (modFlags & DIEP_TRIGGERREPEATINTERVAL) {
|
|
hr = FillModifyPacket200(nextPacket, INDEX_BE_BUTTONREPEAT_200, DWORD(float(pEffect->m_TriggerRepeat)/DURATION_SCALE_200));
|
|
nextPacket++;
|
|
}
|
|
if (modFlags & DIEP_GAIN) {
|
|
hr = FillModifyPacket200(nextPacket, INDEX_BE_GAIN_200, DWORD(float(pEffect->m_Gain)/GAIN_SCALE_200));
|
|
nextPacket++;
|
|
}
|
|
if (modFlags & DIEP_TYPESPECIFICPARAMS) { // Send changed items
|
|
for (int i = 0; i < 4; i++) {
|
|
if ((m_Ds[i] != pEffect->m_Ds[i]) || (m_Fs[i] != pEffect->m_Fs[i])) {
|
|
hr = FillModifyPacket200(nextPacket, INDEX_D1F1_200 + i, pEffect->m_Ds[i], pEffect->m_Fs[i]);
|
|
nextPacket++;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (hr == SUCCESS) {
|
|
return adjustResult;
|
|
}
|
|
return hr;
|
|
}
|
|
|
|
HRESULT WallEffect200::AdjustModifyParams(InternalEffect& newEffect, DWORD& modFlags)
|
|
{
|
|
// Check to see if values being modified are acceptable
|
|
DWORD possMod = DIEP_DURATION | DIEP_GAIN | DIEP_TRIGGERBUTTON | DIEP_TRIGGERREPEATINTERVAL | DIEP_TYPESPECIFICPARAMS;
|
|
/* if ((g_TotalModifiable & modFlags & possMod) == 0) { // Nothing to modify?
|
|
modFlags = 0;
|
|
return SFERR_NO_SUPPORT;
|
|
}
|
|
*/
|
|
|
|
WallEffect200* pEffect = (WallEffect200*)(&newEffect);
|
|
unsigned short numPackets = 0;
|
|
|
|
HRESULT hr = SUCCESS;
|
|
if ((modFlags & (~possMod & DIEP_ALLPARAMS)) != 0) {
|
|
modFlags &= possMod;
|
|
hr = S_FALSE; // Cannot modify all they asked for
|
|
}
|
|
|
|
BOOL playingChecked = FALSE;
|
|
if (modFlags & DIEP_DURATION) {
|
|
if (m_Duration == pEffect->m_Duration) {
|
|
modFlags &= ~DIEP_DURATION; // Remove duration flag, unchanged
|
|
} else {
|
|
if (IsReallyPlaying(playingChecked) == FALSE) {
|
|
numPackets++;
|
|
} else {
|
|
return DIERR_EFFECTPLAYING;
|
|
}
|
|
}
|
|
}
|
|
if (modFlags & DIEP_TRIGGERBUTTON) {
|
|
if (m_TriggerPlayButton == pEffect->m_TriggerPlayButton) {
|
|
modFlags &= ~DIEP_TRIGGERBUTTON; // Remove trigger flag, unchanged
|
|
} else {
|
|
numPackets++;
|
|
}
|
|
}
|
|
if (modFlags & DIEP_TRIGGERREPEATINTERVAL) {
|
|
if (m_TriggerRepeat == pEffect->m_TriggerRepeat) {
|
|
modFlags &= ~DIEP_TRIGGERREPEATINTERVAL; // Remove trigger repeat flag, unchanged
|
|
} else {
|
|
numPackets++;
|
|
}
|
|
}
|
|
if (modFlags & DIEP_GAIN) { // Did gain really change
|
|
if (m_Gain == pEffect->m_Gain) {
|
|
modFlags &= ~DIEP_GAIN; // Remove trigger flag, unchanged
|
|
} else {
|
|
numPackets++;
|
|
}
|
|
}
|
|
// if (modFlags & DIEP_DIRECTION) {
|
|
// modFlags |= DIEP_TYPESPECIFICPARAMS;
|
|
// }
|
|
if (modFlags & DIEP_TYPESPECIFICPARAMS) { // Find which ones (if any)
|
|
int numTypeSpecificChanged = 0;
|
|
for (int i = 0; i < 4; i++) {
|
|
// Ds and Fs are changed togeather
|
|
if ((m_Ds[i] != pEffect->m_Ds[i]) || (m_Fs[i] != pEffect->m_Fs[i])) {
|
|
numTypeSpecificChanged++;
|
|
}
|
|
}
|
|
if (numTypeSpecificChanged == 0) {
|
|
modFlags &= ~DIEP_TYPESPECIFICPARAMS; // No type specific changed
|
|
} else {
|
|
numPackets += (USHORT)numTypeSpecificChanged;
|
|
}
|
|
}
|
|
|
|
if (numPackets != 0) { // That was easy nothing changed
|
|
g_pDataPackager->AllocateDataPackets(numPackets);
|
|
}
|
|
|
|
return hr;
|
|
}
|
|
|
|
/******************* class SystemEffect1XX ***********************/
|
|
SystemEffect1XX::SystemEffect1XX() : SystemEffect(),
|
|
m_SystemStickData()
|
|
{
|
|
}
|
|
|
|
HRESULT SystemEffect1XX::Create(const DIEFFECT& diEffect)
|
|
{
|
|
// Validation Check
|
|
if (diEffect.cbTypeSpecificParams != sizeof(SystemStickData1XX)) {
|
|
return SFERR_INVALID_PARAM;
|
|
}
|
|
if (diEffect.lpvTypeSpecificParams == NULL) {
|
|
ASSUME_NOT_REACHED();
|
|
return SFERR_INVALID_PARAM;
|
|
}
|
|
|
|
// Get the data from the DIEFFECT
|
|
::memcpy(&m_SystemStickData, diEffect.lpvTypeSpecificParams, sizeof(SystemStickData1XX));
|
|
|
|
return SUCCESS;
|
|
}
|
|
|
|
HRESULT SystemEffect1XX::FillCreatePacket(DataPacket& packet) const
|
|
{
|
|
ASSUME_NOT_REACHED();
|
|
return SUCCESS;
|
|
}
|
|
|
|
HRESULT SystemEffect1XX::Modify(InternalEffect& newEffect, DWORD modFlags)
|
|
{
|
|
// Sanity Check
|
|
if (g_pDataPackager == NULL) {
|
|
ASSUME_NOT_REACHED();
|
|
return SFERR_DRIVER_ERROR;
|
|
}
|
|
|
|
SystemEffect1XX* pNewSystemEffect = (SystemEffect1XX*)(&newEffect);
|
|
|
|
// Find number of packets needed
|
|
unsigned short numPackets = 28; // Always need to send system commands when asked (have no idea what is on the stick
|
|
|
|
// Allocate a packets for sending modify command
|
|
if (!g_pDataPackager->AllocateDataPackets(numPackets)) {
|
|
return SFERR_DRIVER_ERROR;
|
|
}
|
|
|
|
// Fill the packets
|
|
BYTE nextPacket = 0;
|
|
FillModifyPacket1XX(nextPacket, INDEX0, pNewSystemEffect->m_SystemStickData.dwXYConst/2);
|
|
nextPacket += 2;
|
|
FillModifyPacket1XX(nextPacket, INDEX1, pNewSystemEffect->m_SystemStickData.dwRotConst/2);
|
|
nextPacket += 2;
|
|
FillModifyPacket1XX(nextPacket, INDEX2, pNewSystemEffect->m_SystemStickData.dwSldrConst);
|
|
nextPacket += 2;
|
|
FillModifyPacket1XX(nextPacket, INDEX3, pNewSystemEffect->m_SystemStickData.dwAJRot);
|
|
nextPacket += 2;
|
|
FillModifyPacket1XX(nextPacket, INDEX4, pNewSystemEffect->m_SystemStickData.dwAJRot);
|
|
nextPacket += 2;
|
|
FillModifyPacket1XX(nextPacket, INDEX5, pNewSystemEffect->m_SystemStickData.dwAJSldr);
|
|
nextPacket += 2;
|
|
FillModifyPacket1XX(nextPacket, INDEX6, pNewSystemEffect->m_SystemStickData.dwSprScl);
|
|
nextPacket += 2;
|
|
FillModifyPacket1XX(nextPacket, INDEX7, pNewSystemEffect->m_SystemStickData.dwBmpScl);
|
|
nextPacket += 2;
|
|
FillModifyPacket1XX(nextPacket, INDEX8, pNewSystemEffect->m_SystemStickData.dwDmpScl);
|
|
nextPacket += 2;
|
|
FillModifyPacket1XX(nextPacket, INDEX9, pNewSystemEffect->m_SystemStickData.dwInertScl);
|
|
nextPacket += 2;
|
|
FillModifyPacket1XX(nextPacket, INDEX10, pNewSystemEffect->m_SystemStickData.dwVelOffScl);
|
|
nextPacket += 2;
|
|
FillModifyPacket1XX(nextPacket, INDEX11, pNewSystemEffect->m_SystemStickData.dwAccOffScl);
|
|
nextPacket += 2;
|
|
FillModifyPacket1XX(nextPacket, INDEX12, pNewSystemEffect->m_SystemStickData.dwYMotBoost/2);
|
|
nextPacket += 2;
|
|
FillModifyPacket1XX(nextPacket, INDEX13, pNewSystemEffect->m_SystemStickData.dwXMotSat);
|
|
|
|
return SUCCESS;
|
|
}
|
|
|
|
/******************* class SystemStickData1XX ***********************/
|
|
SystemStickData1XX::SystemStickData1XX()
|
|
{
|
|
dwXYConst = DEF_XY_CONST;
|
|
dwRotConst = DEF_ROT_CONST;
|
|
dwSldrConst = DEF_SLDR_CONST;
|
|
dwAJPos = DEF_AJ_POS;
|
|
dwAJRot = DEF_AJ_ROT;
|
|
dwAJSldr = DEF_AJ_SLDR;
|
|
dwSprScl = DEF_SPR_SCL;
|
|
dwBmpScl = DEF_BMP_SCL;
|
|
dwDmpScl = DEF_DMP_SCL;
|
|
dwInertScl = DEF_INERT_SCL;
|
|
dwVelOffScl = DEF_VEL_OFFSET_SCL;
|
|
dwAccOffScl = DEF_ACC_OFFSET_SCL;
|
|
dwYMotBoost = DEF_Y_MOT_BOOST;
|
|
dwXMotSat = DEF_X_MOT_SATURATION;
|
|
dwReserved = 0;
|
|
dwMasterGain = 0;
|
|
}
|
|
|
|
#define REGSTR_VAL_JOYSTICK_PARAMS "JoystickParams"
|
|
void SystemStickData1XX::SetFromRegistry(DWORD dwDeviceID)
|
|
{
|
|
// try to open the registry key
|
|
HKEY hkey = joyOpenOEMForceFeedbackKey(dwDeviceID);
|
|
if (hkey != NULL) {
|
|
RegistryKey ffKey(hkey);
|
|
ffKey.ShouldClose(TRUE);
|
|
DWORD numBytes = sizeof(SystemStickData1XX);
|
|
if (ffKey.QueryValue(REGSTR_VAL_JOYSTICK_PARAMS, (BYTE*)this, numBytes) == SUCCESS) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
// We were either not able to open the key or get the value (Use defaults)
|
|
dwXYConst = DEF_XY_CONST;
|
|
dwRotConst = DEF_ROT_CONST;
|
|
dwSldrConst = DEF_SLDR_CONST;
|
|
dwAJPos = DEF_AJ_POS;
|
|
dwAJRot = DEF_AJ_ROT;
|
|
dwAJSldr = DEF_AJ_SLDR;
|
|
dwSprScl = DEF_SPR_SCL;
|
|
dwBmpScl = DEF_BMP_SCL;
|
|
dwDmpScl = DEF_DMP_SCL;
|
|
dwInertScl = DEF_INERT_SCL;
|
|
dwVelOffScl = DEF_VEL_OFFSET_SCL;
|
|
dwAccOffScl = DEF_ACC_OFFSET_SCL;
|
|
dwYMotBoost = DEF_Y_MOT_BOOST;
|
|
dwXMotSat = DEF_X_MOT_SATURATION;
|
|
dwReserved = 0;
|
|
dwMasterGain = 0;
|
|
}
|