//@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 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(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; }