Etterna 0.74.4
Loading...
Searching...
No Matches
WideRangeJJ.h
1#pragma once
2#include "../IntervalHandInfo.h"
3
7{
8 const CalcPatternMod _pmod = { WideRangeJJ };
9 const std::string name = "WideRangeJJMod";
10
11#pragma region params
12
13 float window_param = 3.F;
14 // how many jumpjacks are required for the pmod to not be neutral
15 // it considers the entire combined moving window set by window_param
16 float jj_required = 30.F;
17
18 float min_mod = 0.25F;
19 float max_mod = 1.F;
20 float total_scaler = 2.5F;
21 float cur_interval_tap_scaler = 1.2F;
22
23 // ms apart for 2 taps to be considered a jumpjack
24 // 0.075 is 200 bpm 16th trills
25 // 0.050 is 300 bpm
26 // 0.037 is 400 bpm
27 // 0.020 is 750 bpm (375 bpm 64th)
28 float ms_threshold = 0.065F;
29
30 // add this much to the pmod before sqrt when below threshold
31 float calming_comp = 0.05F;
32
33 // changes the direction and sharpness of the result curve
34 // as the jumpjack width is between 0 and ms_threshold
35 // a higher number here makes numbers closer to ms_threshold
36 // worth more -- the falloff occurs late
37 float diff_falloff_power = 6.F;
38
39 const std::vector<std::pair<std::string, float*>> _params{
40 { "intervals_to_consider", &window_param },
41 { "jumpjacks_required_in_combined_window", &jj_required },
42 { "jumpjack_total_scaler", &total_scaler },
43 { "cur_interval_tap_scaler", &cur_interval_tap_scaler },
44
45 { "min_mod", &min_mod },
46 { "max_mod", &max_mod },
47 { "calming_comp", &calming_comp },
48
49 { "ms_threshold", &ms_threshold },
50 { "diff_falloff_power", &diff_falloff_power },
51 };
52#pragma endregion params and param map
53
54 int window = 0;
55
56 // indices
57 int lc = 0;
58 int rc = 0;
59
60 // moving window of "problems"
61 // specifically, the longest consecutive series of "problems"
62 // a "problem" is a rough value of jumpjackyness
63 // whereas a 1 is 1 jump and the worst possible flam is nearly 0
64 // this tracks amount of "problems" in consecutive intervals
65 CalcMovingWindow<float> _mw_max_problems{};
66 float current_problems = 0.F;
67 float max_interval_problems = 0.F;
68
69 float pmod = neutral;
70
71 // timestamps of notes in columns
72 std::array<float, max_rows_for_single_interval> _left_times{};
73 std::array<float, max_rows_for_single_interval> _right_times{};
74
75#pragma region generic functions
76
77 void full_reset()
78 {
79 _mw_max_problems.zero();
80 _left_times.fill(s_init);
81 _right_times.fill(s_init);
82 current_problems = 0.F;
83 max_interval_problems = 0.F;
84 lc = 0;
85 rc = 0;
86
87 pmod = neutral;
88 }
89
90 void setup()
91 {
92 window =
93 std::clamp(static_cast<int>(window_param), 1, max_moving_window_size);
94 }
95
96#pragma endregion
97
98 void check() {
99 // check times in parallel
100 // any times within the window count as the jumpishjack
101 // just ... determine the degree of jumpy the jumpyjack is
102 // using ms ... or something
103 auto lindex = 0;
104 auto rindex = 0;
105 auto jumpJacking = false;
106 auto failedLeft = 0;
107 auto failedRight = 0;
108 while (lindex < lc && rindex < rc) {
109 const auto& l = _left_times.at(lindex);
110 const auto& r = _right_times.at(rindex);
111 const auto diff = fabsf(l - r);
112
113 if (diff < ms_threshold) {
114 lindex++;
115 rindex++;
116
117 // werent previously jumpjacking, restart at 0
118 if (!jumpJacking) {
119 current_problems = 0.F;
120 }
121
122 // given time_scaler = 1
123 // diff of ms_threshold gives a value of 1
124 // meaning "1 jumpjack" or "1 problem"
125 // but a flammy one, is worth not so much of a jumpjack
126 // x=0 would be y=1, a jump
127 // using std::pow for accuracy here
128 const auto x = std::pow(diff / std::max(ms_threshold, 0.00001F),
129 diff_falloff_power);
130 const auto v = 1 + (x / (x - 2));
131 current_problems += v;
132 if (current_problems > max_interval_problems) {
133 max_interval_problems = current_problems;
134 }
135 jumpJacking = true;
136 } else {
137 // failed case
138 // throw the oldest value and try again...
139 if (l > r) {
140 rindex++;
141 if (failedRight) {
142 jumpJacking = false;
143 }
144 failedRight = true;
145 } else if (r > l) {
146 lindex++;
147 if (failedLeft) {
148 jumpJacking = false;
149 }
150 failedLeft = true;
151 } else {
152 // this case exists to prevent infinite loops
153 // it should never happen unless you put bad values in params
154 lindex++;
155 rindex++;
156
157 if (failedLeft || failedRight) {
158 jumpJacking = false;
159 }
160 failedLeft = true;
161 failedRight = true;
162 }
163 }
164 }
165 }
166
167 void advance_sequencing(const col_type& ct,
168 const float& time_s)
169 {
170 if (lc >= max_rows_for_single_interval ||
171 rc >= max_rows_for_single_interval) {
172 // completely impossible condition
173 // checking for sanity and safety
174 return;
175 }
176
177 switch (ct) {
178 case col_left: {
179 _left_times.at(lc++) = time_s;
180 break;
181 }
182 case col_right: {
183 _right_times.at(rc++) = time_s;
184 break;
185 }
186 case col_ohjump: {
187 _left_times.at(lc++) = time_s;
188 _right_times.at(rc++) = time_s;
189 break;
190 }
191 default:
192 break;
193 }
194 }
195
196 void set_pmod(const ItvHandInfo& itvhi)
197 {
198 const auto taps_in_window =
199 itvhi.get_taps_windowf(window) * cur_interval_tap_scaler;
200 const auto problems_in_window =
201 _mw_max_problems.get_total_for_windowf(window) * total_scaler;
202
203 // no taps or below threshold, or actionable condition
204 if (taps_in_window == 0.F || problems_in_window < jj_required) {
205 // when below threshold, the pmod will drift back to neutral
206 // ideally take less than 5 intervals to drift
207 pmod = fastsqrt(pmod + std::clamp(calming_comp, 0.F, 1.F));
208 } else {
209 pmod = taps_in_window / problems_in_window * 0.75F;
210 }
211
212 pmod = std::clamp(pmod, min_mod, max_mod);
213 }
214
215 auto operator()(const ItvHandInfo& itvhi) -> float
216 {
217 check();
218 _mw_max_problems(max_interval_problems);
219
220 set_pmod(itvhi);
221
222 interval_end();
223 return pmod;
224 }
225
226 void interval_end()
227 {
228 // reset every interval when finished
229 current_problems = 0.F;
230 max_interval_problems = 0.F;
231 _left_times.fill(s_init);
232 _right_times.fill(s_init);
233 lc = 0;
234 rc = 0;
235 }
236};
Definition CalcWindow.h:15
void zero()
set everything to zero
Definition CalcWindow.h:210
auto get_total_for_windowf(const int &window) const -> float
Definition CalcWindow.h:100
accumulates hand specific info across an interval as it's processed by row
Definition IntervalHandInfo.h:6
auto get_taps_windowf(const int &window) const -> float
cast to float for divisioning and clean screen
Definition IntervalHandInfo.h:153
Definition WideRangeJJ.h:7