15#define EFFECT_STATE_INACTIVE 0
19 .id = CLSID_EFFECTSCALC,
27 Thread(
"FXCalc", EFFECT_THREAD_MEM, EFFECT_THREAD_PRIO)
97 for (
auto &axis : axes) {
98 axis->setEffectTorque(0);
99 axis->calculateAxisEffects(
isActive());
107 int axisCount = axes.size();
108 int32_t forces[MAX_AXIS] = {0};
115 for (uint8_t fxi = 0; fxi < MAX_EFFECTS; fxi++)
120 if (effect->
state != EFFECT_STATE_INACTIVE && effect->
duration != FFB_EFFECT_DURATION_INFINITE && effect->
duration != 0){
128 effect->
state = EFFECT_STATE_INACTIVE;
129 for(uint8_t axis=0 ; axis < axisCount ; axis++)
135 if (effect->
state == EFFECT_STATE_INACTIVE)
142 for(uint8_t axis=0 ; axis < axisCount ; axis++)
146 forces[axis] += axisforce;
151 for(uint8_t i=0 ; i < axisCount ; i++)
153 int32_t force = clip<int32_t, int32_t>(forces[i], -0x7fff, 0x7fff);
154 axes[i]->setEffectTorque(force);
165 int32_t force_vector = 0;
172 switch (effect->
type){
174 case FFB_EFFECT_CONSTANT:
176 force_vector = (int32_t)magnitude;
180 case FFB_EFFECT_RAMP:
182 uint32_t elapsed_time = HAL_GetTick() - effect->
startTime;
183 int32_t duration = effect->
duration;
188 case FFB_EFFECT_SQUARE:
190 uint32_t elapsed_time = HAL_GetTick() - effect->
startTime;
191 int32_t force = ((elapsed_time + effect->
phase) % ((uint32_t)effect->
period + 2)) < (uint32_t)(effect->
period + 2) / 2 ? -magnitude : magnitude;
192 force_vector = force + effect->
offset;
196 case FFB_EFFECT_TRIANGLE:
200 uint32_t elapsed_time = HAL_GetTick() - effect->
startTime;
201 uint32_t phase = effect->
phase;
202 uint32_t period = effect->
period;
203 float periodF = period;
205 int32_t maxMagnitude =
offset + magnitude;
206 int32_t minMagnitude =
offset - magnitude;
207 uint32_t phasetime = (phase * period) / 35999;
208 uint32_t timeTemp = elapsed_time + phasetime;
209 float remainder = timeTemp % period;
210 float slope = ((maxMagnitude - minMagnitude) * 2) / periodF;
211 if (remainder > (periodF / 2))
212 force = slope * (periodF - remainder);
214 force = slope * remainder;
215 force += minMagnitude;
216 force_vector = force;
220 case FFB_EFFECT_SAWTOOTHUP:
223 uint32_t elapsed_time = HAL_GetTick() - effect->
startTime;
224 uint32_t phase = effect->
phase;
225 uint32_t period = effect->
period;
226 float periodF = effect->
period;
228 float maxMagnitude =
offset + magnitude;
229 float minMagnitude =
offset - magnitude;
230 int32_t phasetime = (phase * period) / 35999;
231 uint32_t timeTemp = elapsed_time + phasetime;
232 float remainder = timeTemp % period;
233 float slope = (maxMagnitude - minMagnitude) / periodF;
234 force_vector = (int32_t)(minMagnitude + slope * (period - remainder));
238 case FFB_EFFECT_SAWTOOTHDOWN:
241 uint32_t elapsed_time = HAL_GetTick() - effect->
startTime;
242 float phase = effect->
phase;
243 uint32_t period = effect->
period;
244 float periodF = effect->
period;
246 float maxMagnitude =
offset + magnitude;
247 float minMagnitude =
offset - magnitude;
248 int32_t phasetime = (phase * period) / 35999;
249 uint32_t timeTemp = elapsed_time + phasetime;
250 float remainder = timeTemp % period;
251 float slope = (maxMagnitude - minMagnitude) / periodF;
252 force_vector = (int32_t)(minMagnitude + slope * (remainder));
256 case FFB_EFFECT_SINE:
258 float t = HAL_GetTick() - effect->
startTime;
259 float freq = 1.0f / (float)(std::max<uint16_t>(effect->
period, 2));
260 float phase = (float)effect->
phase / (
float)35999;
261 float sine = sinf(2.0 * M_PI * (t * freq + phase)) * magnitude;
262 force_vector = (int32_t)(effect->
offset + sine);
270 return (force_vector * effect->
gain) / 255;
281 int32_t result_torque = 0;
285 metric_t *metrics = axes[axis]->getMetrics();
289 switch (effect->
type)
291 case FFB_EFFECT_CONSTANT:
294 if(effect->
filter[axis] !=
nullptr) {
296 if (effect->
filter[axis]->getFc() < 0.5 && effect->
filter[0]->getFc() != 0.0)
298 forceVector = effect->
filter[axis]->process(forceVector);
303 case FFB_EFFECT_RAMP:
304 case FFB_EFFECT_SQUARE:
305 case FFB_EFFECT_TRIANGLE:
306 case FFB_EFFECT_SAWTOOTHUP:
307 case FFB_EFFECT_SAWTOOTHDOWN:
308 case FFB_EFFECT_SINE:
310 result_torque = -forceVector * angle_ratio;
314 case FFB_EFFECT_SPRING:
334 case FFB_EFFECT_FRICTION:
336 float speed = metrics->
speed * INTERNAL_SCALER_FRICTION;
345 if (abs((int32_t)speed -
offset) > deadBand){
348 speed -= (
offset + (deadBand * (speed <
offset ? -1 : 1)) );
351 float rampupFactor = 1.0;
352 if (fabs (speed) < speedRampupCeil) {
354 float phaseRad = M_PI * ((fabs (speed) / speedRampupCeil) - 0.5);
355 rampupFactor = ( 1 + sin(phaseRad ) ) / 2;
359 int8_t sign = speed >= 0 ? 1 : -1;
361 force = coeff * rampupFactor * sign;
373 case FFB_EFFECT_DAMPER:
376 float speed = metrics->
speed * INTERNAL_SCALER_DAMPER;
382 case FFB_EFFECT_INERTIA:
384 float accel = metrics->
accel * INTERNAL_SCALER_INERTIA;
408 uint8_t idx,
float scale,
float angle_ratio)
413 float gainfactor = (float)(
gain+1) / 256.0;
416 if (abs(metric -
offset) > deadBand){
421 coefficient /= 0x7fff;
424 metric = metric - (
offset + (deadBand * (metric <
offset ? -1 : 1)) );
426 force = clip<int32_t, int32_t>((coefficient * gainfactor * scale * (
float)(metric)),
432 return force * angle_ratio;
443 if(effect->
duration == FFB_EFFECT_DURATION_INFINITE || effect->
duration == 0){
447 uint32_t elapsed_time = HAL_GetTick() - effect->
startTime;
448 if (elapsed_time < effect->attackTime && effect->
attackTime != 0)
466 std::function<void(std::unique_ptr<Biquad> &)> fnptr = [=](std::unique_ptr<Biquad> &
filter){};
468 switch (effect->
type)
470 case FFB_EFFECT_DAMPER:
471 fnptr = [=,
this](std::unique_ptr<Biquad> &
filter){
478 case FFB_EFFECT_FRICTION:
479 fnptr = [=,
this](std::unique_ptr<Biquad> &
filter){
486 case FFB_EFFECT_INERTIA:
487 fnptr = [=,
this](std::unique_ptr<Biquad> &
filter){
494 case FFB_EFFECT_CONSTANT:
495 fnptr = [=,
this](std::unique_ptr<Biquad> &
filter){
505 for (
int i=0; i<MAX_AXIS; i++) {
525 uint16_t filterStorage;
526 if (
Flash_Read(ADR_FFB_CF_FILTER, &filterStorage))
528 uint32_t freq = filterStorage & 0x1FF;
529 uint8_t q = (filterStorage >> 9) & 0x7F;
534 if (
Flash_Read(ADR_FFB_FR_FILTER, &filterStorage))
536 uint32_t freq = filterStorage & 0x1FF;
537 uint8_t q = (filterStorage >> 9) & 0x7F;
542 if (
Flash_Read(ADR_FFB_DA_FILTER, &filterStorage))
544 uint32_t freq = filterStorage & 0x1FF;
545 uint8_t q = (filterStorage >> 9) & 0x7F;
550 if (
Flash_Read(ADR_FFB_IN_FILTER, &filterStorage))
552 uint32_t freq = filterStorage & 0x1FF;
553 uint8_t q = (filterStorage >> 9) & 0x7F;
577 uint16_t filterStorage;
580 filterStorage = (uint16_t)
filter[0].constant.freq & 0x1FF;
586 filterStorage = (uint16_t)
filter[CUSTOM_PROFILE_ID].
friction.freq & 0x1FF;
587 filterStorage |= ( (uint16_t)
filter[CUSTOM_PROFILE_ID].
friction.
q & 0x7F ) << 9 ;
591 filterStorage = (uint16_t)
filter[CUSTOM_PROFILE_ID].
damper.freq & 0x1FF;
592 filterStorage |= ( (uint16_t)
filter[CUSTOM_PROFILE_ID].
damper.
q & 0x7F ) << 9 ;
596 filterStorage = (uint16_t)
filter[CUSTOM_PROFILE_ID].
inertia.freq & 0x1FF;
597 filterStorage |= ( (uint16_t)
filter[CUSTOM_PROFILE_ID].
inertia.
q & 0x7F ) << 9 ;
625 filter->q = clip<uint8_t, uint8_t>(q,0,127);
631 for (uint8_t i = 0; i < MAX_EFFECTS; i++)
633 if (
effects[i].type == type_effect)
642 if(type > 0 && type < 32){
663 if(type > 0 && type < 32){
675 if(type > 0 && type < 13) {
676 uint8_t arrayLocation = type - 1;
688 std::string effects_list =
"";
697 static const char *
effects[12] = {
"Constant,",
"Ramp,",
"Square,",
"Sine,",
"Triangle,",
"Sawtooth Up,",
"Sawtooth Down,",
"Spring,",
"Damper,",
"Inertia,",
"Friction,",
"Custom,"};
699 for (
int i=0;i < 12; i++) {
705 effects_list.pop_back();
708 bool firstItem =
true;
709 for (
int i=0;i < 12; i++) {
710 if (!firstItem) effects_list +=
", ";
711 effects_list +=
"{\"max\":" + std::to_string(
effects_stats[i].max[axis]);
713 effects_list +=
", \"nb\":" + std::to_string(
effects_stats[i].nb) +
"}";
719 return effects_list.c_str();
729 for (
int i=0;i < 12; i++) {
743 replies.emplace_back(
filter[0].constant.freq);
753 replies.emplace_back(
"scale:"+std::to_string(this->
qfloatScaler));
757 replies.emplace_back(
filter[0].constant.q);
786 for (
int i=0; i<12; i++) {
800 axis = std::min<uint8_t>(cmd.
adr,MAX_AXIS);
812 replies.emplace_back(
"scale:"+std::to_string(this->
scaler.
spring));
818 replies.emplace_back(
"scale:"+std::to_string(this->
scaler.
friction)+
",factor:"+std::to_string(INTERNAL_SCALER_FRICTION));
824 replies.emplace_back(
"scale:"+std::to_string(this->
scaler.
damper)+
",factor:"+std::to_string(INTERNAL_SCALER_DAMPER));
830 replies.emplace_back(
"scale:"+std::to_string(this->
scaler.
inertia)+
",factor:"+std::to_string(INTERNAL_SCALER_INERTIA));
908 uint8_t pct = clip<uint8_t, uint8_t>(cmd.
val, 0, 100);
919 uint32_t value = clip<uint32_t, uint32_t>(cmd.
val, 0, 1);
947 std::vector<CommandReply> replies;
971 if(idx < this->
effects.size()){
974 for(
int i=0; i< MAX_AXIS; i++) {
976 effects[idx].filter[i].reset(
nullptr);
986 if(type > FFB_EFFECT_NONE && type < FFB_EFFECT_CUSTOM+1){
987 for(uint8_t i=0;i<
effects.size();i++){
988 if(
effects[i].type == FFB_EFFECT_NONE){
1004 if((HAL_GetTick() -
lastFxUpdate) > 1000 || periodAvg == 0){
1009 return (1000.0/periodAvg);
1018 if((HAL_GetTick() -
lastCfUpdate) > 1000 || periodAvg == 0){
1023 return (1000.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)
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 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)
const uint32_t calcfrequency
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()
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 calcStatsEffectType(uint8_t type, int16_t force, uint8_t axis)
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)
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]
biquad_constant_t constant
biquad_constant_t friction
biquad_constant_t inertia