15#define EFFECT_STATE_INACTIVE 0
19 .id = CLSID_EFFECTSCALC,
27 Thread(
"FXCalc", EFFECT_THREAD_MEM, EFFECT_THREAD_PRIO)
106 for (
auto &axis : axes) {
107 axis->setEffectTorque(0);
108 axis->calculateAxisEffects(
isActive());
116 int axisCount = axes.size();
117 int32_t forces[MAX_AXIS] = {0};
124 for (uint8_t fxi = 0; fxi < MAX_EFFECTS; fxi++)
129 if (effect->
state != EFFECT_STATE_INACTIVE && effect->
duration != FFB_EFFECT_DURATION_INFINITE && effect->
duration != 0){
137 effect->
state = EFFECT_STATE_INACTIVE;
138 for(uint8_t axis=0 ; axis < axisCount ; axis++)
144 if (effect->
state == EFFECT_STATE_INACTIVE)
151 for(uint8_t axis=0 ; axis < axisCount ; axis++)
155 forces[axis] += axisforce;
160 for(uint8_t i=0 ; i < axisCount ; i++)
163 axes[i]->setEffectTorque(force);
174 int32_t force_vector = 0;
181 switch (effect->
type){
183 case FFB_EFFECT_CONSTANT:
185 force_vector = (int32_t)magnitude;
189 case FFB_EFFECT_RAMP:
192 int32_t duration = effect->
duration;
197 case FFB_EFFECT_SQUARE:
199 uint32_t elapsed_time = HAL_GetTick() - effect->
startTime;
200 int32_t force = ((elapsed_time + effect->
phase) % ((uint32_t)effect->
period + 2)) < (uint32_t)(effect->
period + 2) / 2 ? -magnitude : magnitude;
201 force_vector = force + effect->
offset;
205 case FFB_EFFECT_TRIANGLE:
210 uint32_t phase = effect->
phase;
211 uint32_t period = effect->
period;
212 float periodF = period;
214 int32_t maxMagnitude =
offset + magnitude;
215 int32_t minMagnitude =
offset - magnitude;
216 float phasetime = (phase * period) / 35999.0;
217 uint32_t timeTemp = elapsed_time + (phasetime*1000);
218 float remainder = (timeTemp % (period*1000)) / 1000;
219 float slope = ((maxMagnitude - minMagnitude) * 2) / periodF;
220 if (remainder > (periodF / 2))
221 force = slope * (periodF - remainder);
223 force = slope * remainder;
224 force += minMagnitude;
225 force_vector = force;
229 case FFB_EFFECT_SAWTOOTHUP:
233 uint32_t phase = effect->
phase;
234 uint32_t period = effect->
period;
235 float periodF = effect->
period;
237 float maxMagnitude =
offset + magnitude;
238 float minMagnitude =
offset - magnitude;
239 float phasetime = (phase * period) / 35999.0;
240 uint32_t timeTemp = elapsed_time + (phasetime*1000);
241 float remainder = (timeTemp % (period*1000)) / 1000;
242 float slope = (maxMagnitude - minMagnitude) / periodF;
243 force_vector = (int32_t)(minMagnitude + slope * (period - remainder));
247 case FFB_EFFECT_SAWTOOTHDOWN:
251 float phase = effect->
phase;
252 uint32_t period = effect->
period;
253 float periodF = effect->
period;
255 float maxMagnitude =
offset + magnitude;
256 float minMagnitude =
offset - magnitude;
257 float phasetime = (phase * period) / 35999.0;
258 uint32_t timeTemp = elapsed_time + (phasetime*1000);
259 float remainder = (timeTemp % (period*1000)) / 1000;
260 float slope = (maxMagnitude - minMagnitude) / periodF;
261 force_vector = (int32_t)(minMagnitude + slope * (remainder));
265 case FFB_EFFECT_SINE:
268 float freq = 1.0f / (float)(std::max<uint16_t>(effect->
period, 2));
269 float phase = (float)effect->
phase / (
float)35999;
270 float sine = sinf(2.0 * M_PI * (t * freq + phase)) * magnitude;
271 force_vector = (int32_t)(effect->
offset + sine);
279 return (force_vector * effect->
gain) / 255;
290 int32_t result_torque = 0;
294 metric_t *metrics = axes[axis]->getMetrics();
298 switch (effect->
type)
300 case FFB_EFFECT_CONSTANT:
303 if(effect->
filter[axis] !=
nullptr) {
305 if (effect->
filter[axis]->getFc() < 0.5 && effect->
filter[0]->getFc() != 0.0)
307 forceVector = effect->
filter[axis]->process(forceVector);
312 case FFB_EFFECT_RAMP:
313 case FFB_EFFECT_SQUARE:
314 case FFB_EFFECT_TRIANGLE:
315 case FFB_EFFECT_SAWTOOTHUP:
316 case FFB_EFFECT_SAWTOOTHDOWN:
317 case FFB_EFFECT_SINE:
319 result_torque = -forceVector * angle_ratio;
323 case FFB_EFFECT_SPRING:
343 case FFB_EFFECT_FRICTION:
345 float speed = metrics->
speed * INTERNAL_SCALER_FRICTION;
354 if (abs((int32_t)speed -
offset) > deadBand){
357 speed -= (
offset + (deadBand * (speed <
offset ? -1 : 1)) );
360 float rampupFactor = 1.0;
361 if (fabs (speed) < speedRampupCeil) {
363 float phaseRad = M_PI * ((fabs (speed) / speedRampupCeil) - 0.5);
364 rampupFactor = ( 1 + sin(phaseRad ) ) / 2;
368 int8_t sign = speed >= 0 ? 1 : -1;
370 force = coeff * rampupFactor * sign;
377 result_torque -= effect->
filter[axis]->process( (((
gain.friction + 1) * force) >> 8) * angle_ratio *
scaler.friction);
382 case FFB_EFFECT_DAMPER:
385 float speed = metrics->
speed * INTERNAL_SCALER_DAMPER;
391 case FFB_EFFECT_INERTIA:
393 float accel = metrics->
accel * INTERNAL_SCALER_INERTIA;
417 uint8_t idx,
float scale,
float angle_ratio)
422 float gainfactor = (float)(
gain+1) / 256.0;
425 if (abs(metric -
offset) > deadBand){
430 coefficient /= 0x7fff;
433 metric = metric - (
offset + (deadBand * (metric <
offset ? -1 : 1)) );
441 return force * angle_ratio;
452 if(effect->
duration == FFB_EFFECT_DURATION_INFINITE || effect->
duration == 0){
456 uint32_t elapsed_time = HAL_GetTick() - effect->
startTime;
457 if (elapsed_time < effect->attackTime && effect->
attackTime != 0)
475 std::function<void(std::unique_ptr<Biquad> &)> fnptr = [=](std::unique_ptr<Biquad> &
filter){};
477 switch (effect->
type)
479 case FFB_EFFECT_DAMPER:
480 fnptr = [=,
this](std::unique_ptr<Biquad> &
filter){
487 case FFB_EFFECT_FRICTION:
488 fnptr = [=,
this](std::unique_ptr<Biquad> &
filter){
495 case FFB_EFFECT_INERTIA:
496 fnptr = [=,
this](std::unique_ptr<Biquad> &
filter){
503 case FFB_EFFECT_CONSTANT:
504 fnptr = [=,
this](std::unique_ptr<Biquad> &
filter){
514 for (
int i=0; i<MAX_AXIS; i++) {
534 uint16_t filterStorage;
535 if (
Flash_Read(ADR_FFB_CF_FILTER, &filterStorage))
537 uint32_t freq = filterStorage & 0x1FF;
538 uint8_t q = (filterStorage >> 9) & 0x7F;
543 if (
Flash_Read(ADR_FFB_FR_FILTER, &filterStorage))
545 uint32_t freq = filterStorage & 0x1FF;
546 uint8_t q = (filterStorage >> 9) & 0x7F;
551 if (
Flash_Read(ADR_FFB_DA_FILTER, &filterStorage))
553 uint32_t freq = filterStorage & 0x1FF;
554 uint8_t q = (filterStorage >> 9) & 0x7F;
559 if (
Flash_Read(ADR_FFB_IN_FILTER, &filterStorage))
561 uint32_t freq = filterStorage & 0x1FF;
562 uint8_t q = (filterStorage >> 9) & 0x7F;
586 uint16_t filterStorage;
589 filterStorage = (uint16_t)
filter[0].constant.freq & 0x1FF;
590 filterStorage |= ( (uint16_t)
filter[0].constant.q & 0x7F ) << 9 ;
595 filterStorage = (uint16_t)
filter[CUSTOM_PROFILE_ID].
friction.freq & 0x1FF;
596 filterStorage |= ( (uint16_t)
filter[CUSTOM_PROFILE_ID].friction.q & 0x7F ) << 9 ;
600 filterStorage = (uint16_t)
filter[CUSTOM_PROFILE_ID].
damper.freq & 0x1FF;
601 filterStorage |= ( (uint16_t)
filter[CUSTOM_PROFILE_ID].damper.q & 0x7F ) << 9 ;
605 filterStorage = (uint16_t)
filter[CUSTOM_PROFILE_ID].
inertia.freq & 0x1FF;
606 filterStorage |= ( (uint16_t)
filter[CUSTOM_PROFILE_ID].inertia.q & 0x7F ) << 9 ;
640 for (uint8_t i = 0; i < MAX_EFFECTS; i++)
642 if (
effects[i].type == type_effect)
651 if(type > 0 && type < 32){
672 if(type > 0 && type < 32){
684 if(type > 0 && type < 13) {
685 uint8_t arrayLocation = type - 1;
697 std::string effects_list =
"";
706 static const char *
effects[12] = {
"Constant,",
"Ramp,",
"Square,",
"Sine,",
"Triangle,",
"Sawtooth Up,",
"Sawtooth Down,",
"Spring,",
"Damper,",
"Inertia,",
"Friction,",
"Custom,"};
708 for (
int i=0;i < 12; i++) {
714 effects_list.pop_back();
717 bool firstItem =
true;
718 for (
int i=0;i < 12; i++) {
719 if (!firstItem) effects_list +=
", ";
720 effects_list +=
"{\"max\":" + std::to_string(
effects_stats[i].max[axis]);
722 effects_list +=
", \"nb\":" + std::to_string(
effects_stats[i].nb) +
"}";
728 return effects_list.c_str();
738 for (
int i=0;i < 12; i++) {
752 replies.emplace_back(
filter[0].constant.freq);
762 replies.emplace_back(
"scale:"+std::to_string(this->
qfloatScaler));
766 replies.emplace_back(
filter[0].constant.q);
795 for (
int i=0; i<12; i++) {
809 axis = std::min<uint8_t>(cmd.
adr,MAX_AXIS);
821 replies.emplace_back(
"scale:"+std::to_string(this->
scaler.spring));
827 replies.emplace_back(
"scale:"+std::to_string(this->
scaler.friction)+
",factor:"+std::to_string(INTERNAL_SCALER_FRICTION));
833 replies.emplace_back(
"scale:"+std::to_string(this->
scaler.damper)+
",factor:"+std::to_string(INTERNAL_SCALER_DAMPER));
839 replies.emplace_back(
"scale:"+std::to_string(this->
scaler.inertia)+
",factor:"+std::to_string(INTERNAL_SCALER_INERTIA));
956 std::vector<CommandReply> replies;
980 if(idx < this->
effects.size()){
983 for(
int i=0; i< MAX_AXIS; i++) {
985 effects[idx].filter[i].reset(
nullptr);
995 if(type > FFB_EFFECT_NONE && type < FFB_EFFECT_CUSTOM+1){
996 for(uint8_t i=0;i<
effects.size();i++){
997 if(
effects[i].type == FFB_EFFECT_NONE){
1018 return (1000000.0/periodAvg);
1032 return (1000000.0/periodAvg);
EffectsCalculator_commands
@ frictionPctSpeedToRampup
void registerCommand(const char *cmd, const ID cmdid, const char *help=nullptr, uint32_t flags=0)
static CommandStatus handleGetSet(const ParsedCommand &cmd, std::vector< CommandReply > &replies, TVal &value)
CommandHandler(const char *clsname, uint16_t clsid, uint8_t instance=0)
static void broadcastCommandReplyAsync(const std::vector< CommandReply > &reply, CommandHandler *handler, uint32_t cmdId, CMDtype type=CMDtype::get)
void logEffectState(uint8_t type, uint8_t state)
const ClassIdentifier getInfo()
Command handlers always have class infos. Works well with ChoosableClass.
std::string listEffectsUsed(bool details=false, uint8_t axis=0)
void updateFilterSettingsForEffects(uint8_t type_effect)
int32_t calcComponentForce(FFB_Effect *effect, int32_t forceVector, std::vector< std::unique_ptr< Axis > > &axes, uint8_t axis)
int32_t find_free_effect(uint8_t type)
void updateSamplerate(float newSamplerate)
void setActive(bool active)
int32_t getEnvelopeMagnitude(FFB_Effect *effect)
void checkFilterCoeff(biquad_constant_t *filter, uint32_t freq, uint8_t q)
void calculateEffects(std::vector< std::unique_ptr< Axis > > &axes)
int32_t calcConditionEffectForce(FFB_Effect *effect, float metric, uint8_t gain, uint8_t idx, float scale, float angle_ratio)
uint8_t frictionPctSpeedToRampup
void setGain(uint8_t gain)
CommandStatus command(const ParsedCommand &cmd, std::vector< CommandReply > &replies)
std::array< effect_stat_t, 12 > effects_stats
virtual ~EffectsCalculator()
void calcStatsEffectType(uint8_t type, int32_t force, uint8_t axis)
effect_biquad_t filter[2]
std::array< effect_stat_t, 12 > effects_statslast
static ClassIdentifier info
int32_t calcNonConditionEffectForce(FFB_Effect *effect)
std::array< FFB_Effect, max_effects > effects
virtual void setFilters(FFB_Effect *effect)
void logEffectType(uint8_t type, bool remove=false)
void resetLoggedActiveEffects(bool reinit)
void free_effect(uint16_t idx)
virtual uint32_t getConstantForceRate()
virtual void fxUpdateEvent()
virtual void cfUpdateEvent()
FastMovingAverage< float > fxPeriodAvg
FastMovingAverage< float > cfUpdatePeriodAvg
virtual uint32_t getRate()
void Delay(const TickType_t Delay)
Thread(const std::string Name, uint16_t StackDepth, UBaseType_t Priority)
bool Flash_Write(uint16_t adr, uint16_t dat)
bool Flash_Read(uint16_t adr, uint16_t *buf, bool checkempty=true)
void setClipLed(uint8_t on)
uint16_t negativeSaturation
int16_t negativeCoefficient
int16_t positiveCoefficient
uint16_t positiveSaturation
std::unique_ptr< Biquad > filter[MAX_AXIS]
float axisMagnitudes[MAX_AXIS]
FFB_Effect_Condition conditions[MAX_AXIS]