Etterna 0.74.4
Loading...
Searching...
No Matches
RollJS.h
1#pragma once
2#include "../IntervalHandInfo.h"
3#include "../HD_Sequencers/GenericSequencing.h"
4
6{
7 const CalcPatternMod _pmod = RollJS;
8 const std::string name = "RollJSMod";
9
10#pragma region params
11
12 float min_mod = 0.85F;
13 float max_mod = 1.F;
14 float base = 0.1F;
15 float jj_scaler = 2.F;
16
17 // ms apart for 2 taps to be considered a jumpjack
18 // 0.075 is 200 bpm 16th trills
19 // 0.050 is 300 bpm
20 // 0.037 is 400 bpm
21 // 0.020 is 750 bpm (375 bpm 64th)
22 float ms_threshold = 0.0701F;
23
24 // changes the direction and sharpness of the result curve
25 // as the jumpjack width is between 0 and ms_threshold
26 // a higher number here makes numbers closer to ms_threshold
27 // worth more -- the falloff occurs late
28 float diff_falloff_power = 1.F;
29
30 float required_notes_before_nerf = 6.F;
31
32 const std::vector<std::pair<std::string, float*>> _params{
33 { "min_mod", &min_mod },
34 { "max_mod", &max_mod },
35 { "base", &base },
36
37 { "jj_scaler", &jj_scaler },
38 { "ms_threshold", &ms_threshold },
39 { "diff_falloff_power", &diff_falloff_power },
40 { "required_notes_before_nerf", &required_notes_before_nerf },
41 };
42#pragma endregion params and param map
43
44 // indices
45 int lc = 0;
46 int rc = 0;
47
48 // a "problem" is a rough value of jumpjackyness
49 // whereas a 1 is 1 jump and the worst possible flam is nearly 0
50 // this tracks amount of "problems" in consecutive intervals
51 float current_problems = 0.F;
52
53 float pmod = neutral;
54
55 // timestamps of notes in columns
56 std::array<float, max_rows_for_single_interval> _left_times{};
57 std::array<float, max_rows_for_single_interval> _right_times{};
58
59#pragma region generic functions
60
61 void full_reset()
62 {
63 _left_times.fill(s_init);
64 _right_times.fill(s_init);
65 current_problems = 0.F;
66 lc = 0;
67 rc = 0;
68
69 pmod = neutral;
70 }
71
72 void setup() {}
73
74#pragma endregion
75
76 void check()
77 {
78 // check times in parallel
79 // any times within the window count as the jumpishjack
80 // just ... determine the degree of jumpy the jumpyjack is
81 // using ms ... or something
82 auto lindex = 0;
83 auto rindex = 0;
84 while (lindex < lc && rindex < rc) {
85 const auto& l = _left_times.at(lindex);
86 const auto& r = _right_times.at(rindex);
87 const auto diff = fabsf(l - r);
88
89 if (diff <= ms_threshold) {
90 lindex++;
91 rindex++;
92
93 // given time_scaler = 1
94 // diff of ms_threshold gives a v of 1
95 // meaning "1 jumpjack" or "1 problem"
96 // but a flammy one, is worth not so much of a jumpjack
97 // (this function at x=[0,1] begins slow and drops fast)
98 // using std::pow for accuracy here
99 const auto x = std::pow(diff / std::max(ms_threshold, 0.00001F),
100 diff_falloff_power);
101 const auto v = 1 + (x / (x - 2));
102 current_problems += v;
103 } else {
104 // failed case
105 // throw the oldest value and try again...
106 if (l > r) {
107 rindex++;
108 } else if (r > l) {
109 lindex++;
110 } else {
111 // this case exists to prevent infinite loops
112 // it should never happen unless you put bad values in
113 // params
114 lindex++;
115 rindex++;
116 }
117 }
118 }
119 }
120
121 void advance_sequencing(const col_type& ct, const float& time_s)
122 {
123 if (lc >= max_rows_for_single_interval ||
124 rc >= max_rows_for_single_interval) {
125 // completely impossible condition
126 // checking for sanity and safety
127 return;
128 }
129
130 switch (ct) {
131 case col_left: {
132 _left_times.at(lc++) = time_s;
133 break;
134 }
135 case col_right: {
136 _right_times.at(rc++) = time_s;
137 break;
138 }
139 case col_ohjump: {
140 _left_times.at(lc++) = time_s;
141 _right_times.at(rc++) = time_s;
142 break;
143 }
144 default:
145 break;
146 }
147 }
148
149 void set_pmod(const ItvHandInfo& itvhi)
150 {
151 // no taps, no jj
152 if (itvhi.get_taps_nowi() == 0 || current_problems == 0.F) {
153 pmod = neutral;
154 return;
155 }
156
157 if (itvhi.get_taps_nowf() < required_notes_before_nerf) {
158 pmod = neutral;
159 return;
160 }
161
162 pmod = itvhi.get_taps_nowf() / ((current_problems * 2.F) * jj_scaler);
163 pmod = std::clamp(base + pmod, min_mod, max_mod);
164 }
165
166 auto operator()(const ItvHandInfo& itvhi) -> float
167 {
168 check();
169 set_pmod(itvhi);
170
171 interval_end();
172 return pmod;
173 }
174
175 void interval_end()
176 {
177 // reset every interval when finished
178 current_problems = 0.F;
179 _left_times.fill(s_init);
180 _right_times.fill(s_init);
181 lc = 0;
182 rc = 0;
183 }
184};
185#pragma once
accumulates hand specific info across an interval as it's processed by row
Definition IntervalHandInfo.h:6
auto get_taps_nowi() const -> int
access functions for hand tap counts
Definition IntervalHandInfo.h:135
auto get_taps_nowf() const -> float
cast to float for divisioning and clean screen
Definition IntervalHandInfo.h:141
Definition RollJS.h:6