diff --git a/src/Etterna/MinaCalc/Dependent/HD_PatternMods/Roll.h b/src/Etterna/MinaCalc/Dependent/HD_PatternMods/Roll.h index 483ccdce55..4029ef2fe9 100644 --- a/src/Etterna/MinaCalc/Dependent/HD_PatternMods/Roll.h +++ b/src/Etterna/MinaCalc/Dependent/HD_PatternMods/Roll.h @@ -2,27 +2,6 @@ #include "../IntervalHandInfo.h" #include "../HD_Sequencers/GenericSequencing.h" -static const int max_seq_parts = 4; - -// contains parts of a modifier constructed from sequences -struct mod_parts -{ - std::array _parts = { 0, 0, 0, 0 }; -}; - -// contains sequence -struct RollSeq -{ -}; - -// update logic -struct Roll_Sequencer -{ - RollSeq _rs; - - void advance(const meta_type& mt, SequencerGeneral& _seq) {} -}; - struct RollMod { const CalcPatternMod _pmod = Roll; @@ -30,71 +9,170 @@ struct RollMod #pragma region params - float window_param = 2.F; + float min_mod = 0.9F; + float max_mod = 1.F; + float base = 0.1F; + float jj_scaler = .5F; - float min_mod = 0.95F; - float max_mod = 1.05F; - float base = 0.85F; + // ms apart for 2 taps to be considered a jumpjack + // 0.075 is 200 bpm 16th trills + // 0.050 is 300 bpm + // 0.037 is 400 bpm + // 0.020 is 750 bpm (375 bpm 64th) + float ms_threshold = 0.0501F; - float cv_reset = 1.F; + // changes the direction and sharpness of the result curve + // as the jumpjack width is between 0 and ms_threshold + // a higher number here makes numbers closer to ms_threshold + // worth more -- the falloff occurs late + float diff_falloff_power = 1.F; const std::vector> _params{ - { "window_param", &window_param }, - { "min_mod", &min_mod }, { "max_mod", &max_mod }, { "base", &base }, + + { "jj_scaler", &jj_scaler }, + { "ms_threshold", &ms_threshold }, + { "diff_falloff_power", &diff_falloff_power }, }; #pragma endregion params and param map - int window = 0; - Roll_Sequencer _roll{}; - CalcMovingWindow _mw_pmod; + // indices + int lc = 0; + int rc = 0; + + // a "problem" is a rough value of jumpjackyness + // whereas a 1 is 1 jump and the worst possible flam is nearly 0 + // this tracks amount of "problems" in consecutive intervals + float current_problems = 0.F; - float moving_cv = cv_reset; - float pmod = min_mod; + float pmod = neutral; + + // timestamps of notes in columns + std::array _left_times{}; + std::array _right_times{}; + +#pragma region generic functions void full_reset() { - moving_cv = (moving_cv + cv_reset); + _left_times.fill(s_init); + _right_times.fill(s_init); + current_problems = 0.F; + lc = 0; + rc = 0; + pmod = neutral; } - void setup() + void setup() {} + +#pragma endregion + + void check() { - window = - std::clamp(static_cast(window_param), 1, max_moving_window_size); + // check times in parallel + // any times within the window count as the jumpishjack + // just ... determine the degree of jumpy the jumpyjack is + // using ms ... or something + auto lindex = 0; + auto rindex = 0; + while (lindex < lc && rindex < rc) { + const auto& l = _left_times.at(lindex); + const auto& r = _right_times.at(rindex); + const auto diff = fabsf(l - r); + + if (diff <= ms_threshold) { + lindex++; + rindex++; + + // given time_scaler = 1 + // diff of ms_threshold gives a v of 1 + // meaning "1 jumpjack" or "1 problem" + // but a flammy one, is worth not so much of a jumpjack + // (this function at x=[0,1] begins slow and drops fast) + // using std::pow for accuracy here + const auto x = std::pow(diff / std::max(ms_threshold, 0.00001F), + diff_falloff_power); + const auto v = 1 + (x / (x - 2)); + current_problems += v; + } else { + // failed case + // throw the oldest value and try again... + if (l > r) { + rindex++; + } else if (r > l) { + lindex++; + } else { + // this case exists to prevent infinite loops + // it should never happen unless you put bad values in + // params + lindex++; + rindex++; + } + } + } } - void complete_seq() {} - - void advance_sequencing(const meta_type& mt, SequencerGeneral& _seq) + void advance_sequencing(const col_type& ct, const float& time_s) { - //_roll.advance(mt, _seq); + if (lc >= max_rows_for_single_interval || + rc >= max_rows_for_single_interval) { + // completely impossible condition + // checking for sanity and safety + return; + } + + switch (ct) { + case col_left: { + _left_times.at(lc++) = time_s; + break; + } + case col_right: { + _right_times.at(rc++) = time_s; + break; + } + case col_ohjump: { + _left_times.at(lc++) = time_s; + _right_times.at(rc++) = time_s; + break; + } + default: + break; + } } - void set_pmod(const ItvHandInfo& itvhi, const SequencerGeneral& /*_seq*/) + void set_pmod(const ItvHandInfo& itvhi) { - if (itvhi.get_taps_nowi() == 0) { + // no taps, no jj + if (itvhi.get_taps_nowi() == 0 || current_problems == 0.F) { pmod = neutral; return; } - pmod = std::clamp(1.F, min_mod, max_mod); + pmod = + 1 - (((current_problems * 2.F) * jj_scaler) / itvhi.get_taps_nowf()); + pmod = std::clamp(base + pmod, min_mod, max_mod); } - auto operator()(const ItvHandInfo& itvhi, const SequencerGeneral& _seq) - -> float + auto operator()(const ItvHandInfo& itvhi) -> float { - - set_pmod(itvhi, _seq); + check(); + set_pmod(itvhi); interval_end(); - _mw_pmod(pmod); - return neutral; - //_mw_pmod.get_mean_of_window(window); + return pmod; } - void interval_end() {} + void interval_end() + { + // reset every interval when finished + current_problems = 0.F; + _left_times.fill(s_init); + _right_times.fill(s_init); + lc = 0; + rc = 0; + } }; #pragma once diff --git a/src/Etterna/MinaCalc/Dependent/HD_PatternMods/WideRangeJJ.h b/src/Etterna/MinaCalc/Dependent/HD_PatternMods/WideRangeJJ.h index e5cf0fc981..d284583240 100644 --- a/src/Etterna/MinaCalc/Dependent/HD_PatternMods/WideRangeJJ.h +++ b/src/Etterna/MinaCalc/Dependent/HD_PatternMods/WideRangeJJ.h @@ -1,7 +1,8 @@ #pragma once #include "../IntervalHandInfo.h" -/// Hand-Dependent PatternMod detecting jump jacks but considering flammyness +/// Hand-Dependent PatternMod detecting jump jacks but considering flammyness. +/// Collects the number of consecutive jumpjacks, largest sequence of them struct WideRangeJJMod { const CalcPatternMod _pmod = { WideRangeJJ }; @@ -12,32 +13,41 @@ struct WideRangeJJMod float window_param = 6.F; // how many jumpjacks are required for the pmod to not be neutral // it considers the entire combined moving window set by window_param - float jj_required = 26.F; + float jj_required = 20.F; float min_mod = 0.25F; float max_mod = 1.F; - float total_scaler = 2.F; + float total_scaler = 2.3F; + float cur_interval_tap_scaler = 1.2F; // ms apart for 2 taps to be considered a jumpjack - float ms_threshold = 0.017F; + // 0.075 is 200 bpm 16th trills + // 0.050 is 300 bpm + // 0.037 is 400 bpm + // 0.020 is 750 bpm (375 bpm 64th) + float ms_threshold = 0.065F; - // the scaler will change how quickly a non-ohj flam is worth nothing - // scaler = 1 means a flam of ms_threshold span is worth 0 - // scaler > 1 will lower the ms_threshold artificially - // this can be useful if we want to "accept" values - // but throw away some that are close but not worth caring about - float time_scaler = 1.F; + // add this much to the pmod before sqrt when below threshold + float calming_comp = 0.05F; + + // changes the direction and sharpness of the result curve + // as the jumpjack width is between 0 and ms_threshold + // a higher number here makes numbers closer to ms_threshold + // worth more -- the falloff occurs late + float diff_falloff_power = 6.F; const std::vector> _params{ { "intervals_to_consider", &window_param }, { "jumpjacks_required_in_combined_window", &jj_required }, + { "jumpjack_total_scaler", &total_scaler }, + { "cur_interval_tap_scaler", &cur_interval_tap_scaler }, { "min_mod", &min_mod }, { "max_mod", &max_mod }, - { "total_scaler", &total_scaler }, + { "calming_comp", &calming_comp }, { "ms_threshold", &ms_threshold }, - { "time_scaler", &time_scaler }, + { "diff_falloff_power", &diff_falloff_power }, }; #pragma endregion params and param map @@ -48,26 +58,29 @@ struct WideRangeJJMod int rc = 0; // moving window of "problems" + // specifically, the longest consecutive series of "problems" // a "problem" is a rough value of jumpjackyness // whereas a 1 is 1 jump and the worst possible flam is nearly 0 // this tracks amount of "problems" in consecutive intervals - CalcMovingWindow _mw_problems; - float interval_problems = 0.F; + CalcMovingWindow _mw_max_problems{}; + float current_problems = 0.F; + float max_interval_problems = 0.F; float pmod = neutral; // timestamps of notes in columns - std::array _left_times; - std::array _right_times; + std::array _left_times{}; + std::array _right_times{}; #pragma region generic functions void full_reset() { - _mw_problems.zero(); + _mw_max_problems.zero(); _left_times.fill(s_init); _right_times.fill(s_init); - interval_problems = 0.F; + current_problems = 0.F; + max_interval_problems = 0.F; lc = 0; rc = 0; @@ -89,6 +102,9 @@ struct WideRangeJJMod // using ms ... or something auto lindex = 0; auto rindex = 0; + auto jumpJacking = false; + auto failedLeft = 0; + auto failedRight = 0; while (lindex < lc && rindex < rc) { const auto& l = _left_times.at(lindex); const auto& r = _right_times.at(rindex); @@ -98,25 +114,51 @@ struct WideRangeJJMod lindex++; rindex++; + // werent previously jumpjacking, restart at 0 + if (!jumpJacking) { + current_problems = 0.F; + } + // given time_scaler = 1 // diff of ms_threshold gives a value of 1 // meaning "1 jumpjack" or "1 problem" // but a flammy one, is worth not so much of a jumpjack - const auto v = - 1 - ((diff / std::max(0.1F, time_scaler)) / ms_threshold); - interval_problems += v; + // x=0 would be y=1, a jump + // using std::pow for accuracy here + const auto x = std::pow(diff / std::max(ms_threshold, 0.00001F), + diff_falloff_power); + const auto v = 1 + (x / (x - 2)); + current_problems += v; + if (current_problems > max_interval_problems) { + max_interval_problems = current_problems; + } + jumpJacking = true; } else { // failed case // throw the oldest value and try again... if (l > r) { rindex++; + if (failedRight) { + jumpJacking = false; + } + failedRight = true; } else if (r > l) { lindex++; + if (failedLeft) { + jumpJacking = false; + } + failedLeft = true; } else { // this case exists to prevent infinite loops // it should never happen unless you put bad values in params lindex++; rindex++; + + if (failedLeft || failedRight) { + jumpJacking = false; + } + failedLeft = true; + failedRight = true; } } } @@ -153,29 +195,27 @@ struct WideRangeJJMod void set_pmod(const ItvHandInfo& itvhi) { - // no taps, no jj - if (itvhi.get_taps_windowi(window) == 0 || - _mw_problems.get_total_for_window(window) == 0.F) { - pmod = neutral; - return; + const auto taps_in_window = + itvhi.get_taps_windowf(window) * cur_interval_tap_scaler; + const auto problems_in_window = + _mw_max_problems.get_total_for_windowf(window) * total_scaler; + + // no taps or below threshold, or actionable condition + if (taps_in_window == 0.F || problems_in_window < jj_required) { + // when below threshold, the pmod will drift back to neutral + // ideally take less than 5 intervals to drift + pmod = fastsqrt(pmod + std::clamp(calming_comp, 0.F, 1.F)); + } else { + pmod = taps_in_window / problems_in_window * 0.75; } - if (_mw_problems.get_total_for_window(window) < jj_required) { - pmod = neutral; - return; - } - - pmod = - itvhi.get_taps_windowf(window) / - ((_mw_problems.get_total_for_windowf(window) * 2.F) * total_scaler); - pmod = std::clamp(pmod, min_mod, max_mod); } auto operator()(const ItvHandInfo& itvhi) -> float { check(); - _mw_problems(interval_problems); + _mw_max_problems(max_interval_problems); set_pmod(itvhi); @@ -185,9 +225,9 @@ struct WideRangeJJMod void interval_end() { - // we could count these in metanoteinfo but let's do it here for now, // reset every interval when finished - interval_problems = 0.F; + current_problems = 0.F; + max_interval_problems = 0.F; _left_times.fill(s_init); _right_times.fill(s_init); lc = 0; diff --git a/src/Etterna/MinaCalc/MinaCalc.cpp b/src/Etterna/MinaCalc/MinaCalc.cpp index 26367ae9f5..59506badfb 100644 --- a/src/Etterna/MinaCalc/MinaCalc.cpp +++ b/src/Etterna/MinaCalc/MinaCalc.cpp @@ -726,7 +726,7 @@ Calc::InitAdjDiff(Calc& calc, const int& hand) Stream, OHTrill, VOHTrill, - // Roll, + Roll, Chaos, WideRangeRoll, WideRangeJumptrill, @@ -752,9 +752,9 @@ Calc::InitAdjDiff(Calc& calc, const int& hand) // WideRangeRoll, // OHTrill, VOHTrill, + Roll, RanMan, FlamJam, - // Roll, // WideRangeAnchor, }, @@ -769,7 +769,7 @@ Calc::InitAdjDiff(Calc& calc, const int& hand) WideRangeJJ, OHTrill, VOHTrill, - // Roll + Roll, // RanMan, FlamJam, HSDensity, @@ -798,7 +798,7 @@ Calc::InitAdjDiff(Calc& calc, const int& hand) OHTrill, VOHTrill, Balance, - // Roll, + Roll, OHJumpMod, //Chaos, WideRangeJumptrill, @@ -904,9 +904,9 @@ Calc::InitAdjDiff(Calc& calc, const int& hand) *adj_diff = calc.init_base_diff_vals.at(hand).at(TechBase).at(i) * pmod_product_cur_interval.at(ss) * basescalers.at(ss) / - //max( - //fastpow(calc.pmod_vals.at(hand).at(CJ).at(i), 2.F), - //1.F) / + max( + fastpow(calc.pmod_vals.at(hand).at(CJ).at(i), 2.F), + 1.F) / fastsqrt(calc.pmod_vals.at(hand).at(OHJumpMod).at(i)); break; default: @@ -1012,7 +1012,7 @@ MinaSDCalcDebug( } } -int mina_calc_version = 487; +int mina_calc_version = 488; auto GetCalcVersion() -> int { diff --git a/src/Etterna/MinaCalc/Ulbu.h b/src/Etterna/MinaCalc/Ulbu.h index 8df9e36e85..d4c930ab5c 100644 --- a/src/Etterna/MinaCalc/Ulbu.h +++ b/src/Etterna/MinaCalc/Ulbu.h @@ -225,7 +225,7 @@ struct TheGreatBazoinkazoinkInTheSky /// an example, actually all sequencing should be done in objects /// following rm_sequencing's template and be stored in mhi, and then /// passed to whichever mods need them, but that's for later - void handle_row_dependent_pattern_advancement() + void handle_row_dependent_pattern_advancement(const float& row_time) { _ohj.advance_sequencing(_mhi->_ct, _mhi->_bt); _cjohj.advance_sequencing(_mhi->_ct, _mhi->_bt); @@ -241,9 +241,9 @@ struct TheGreatBazoinkazoinkInTheSky _seq.get_sc_ms_now(_mhi->_ct)); _wrjt.advance_sequencing( _mhi->_bt, _mhi->_mt, _mhi->_last_mt, _seq._mw_any_ms); - _wrjj.advance_sequencing(_mhi->_ct, _mri->time); + _wrjj.advance_sequencing(_mhi->_ct, row_time); _ch.advance_sequencing(_seq._mw_any_ms); - _roll.advance_sequencing(_mhi->_mt, _seq); + _roll.advance_sequencing(_mhi->_ct, row_time); } void setup_dependent_mods() @@ -273,7 +273,7 @@ struct TheGreatBazoinkazoinkInTheSky PatternMods::set_dependent( hand, _bal._pmod, _bal(_mitvhi._itvhi), itv, _calc); PatternMods::set_dependent( - hand, _roll._pmod, _roll(_mitvhi._itvhi, _seq), itv, _calc); + hand, _roll._pmod, _roll(_mitvhi._itvhi), itv, _calc); PatternMods::set_dependent( hand, _ch._pmod, _ch(_mitvhi._itvhi.get_taps_nowi()), itv, _calc); PatternMods::set_dependent( @@ -459,7 +459,7 @@ struct TheGreatBazoinkazoinkInTheSky _mitvhi._itvhi.set_col_taps(ct); // advance sequencing for all hand dependent mods - handle_row_dependent_pattern_advancement(); + handle_row_dependent_pattern_advancement(row_time); /* jackspeed, and tech use various adjust ms bases that * are sequenced here, meaning they are order dependent @@ -590,6 +590,7 @@ struct TheGreatBazoinkazoinkInTheSky load_params_for_mod(¶ms, _voht._params, _voht.name); load_params_for_mod(¶ms, _ch._params, _ch.name); load_params_for_mod(¶ms, _rm._params, _rm.name); + load_params_for_mod(¶ms, _roll._params, _roll.name); load_params_for_mod(¶ms, _wrb._params, _wrb.name); load_params_for_mod(¶ms, _wrr._params, _wrr.name); load_params_for_mod(¶ms, _wrjt._params, _wrjt.name); @@ -626,6 +627,7 @@ struct TheGreatBazoinkazoinkInTheSky calcparams->AppendChild(make_mod_param_node(_voht._params, _voht.name)); calcparams->AppendChild(make_mod_param_node(_ch._params, _ch.name)); calcparams->AppendChild(make_mod_param_node(_rm._params, _rm.name)); + calcparams->AppendChild(make_mod_param_node(_roll._params, _roll.name)); calcparams->AppendChild(make_mod_param_node(_wrb._params, _wrb.name)); calcparams->AppendChild(make_mod_param_node(_wrr._params, _wrr.name)); calcparams->AppendChild(make_mod_param_node(_wrjt._params, _wrjt.name));