Etterna 0.75.1
Loading...
Searching...
No Matches
UlbuAcolytes.h
1#pragma once
2#include <vector>
3#include <string>
4#include <bit>
5
6/* PRAISE ULBU FOR IT IS ITS GLORY THAT GIVES OUR LIVES MEANING */
7
8constexpr float interval_span = 0.5F;
9
13inline void
14Smooth(std::vector<float>& input,
15 const float neutral,
16 const int end_interval)
17{
18 auto f2 = neutral;
19 auto f3 = neutral;
20
21 for (auto i = 0; i < end_interval; ++i) {
22 const auto f1 = f2;
23 f2 = f3;
24 f3 = input.at(i);
25 input.at(i) = (f1 + f2 + f3) / 3.F;
26 }
27}
28
32inline void
33MSSmooth(std::vector<float>& input,
34 const float neutral,
35 const int end_interval)
36{
37 auto f2 = neutral;
38
39 for (auto i = 0; i < end_interval; ++i) {
40 const auto f1 = f2;
41 f2 = input.at(i);
42 input.at(i) = (f1 + f2) / 2.F;
43 }
44}
45
46static const std::vector<CalcPatternMod> agnostic_mods = {
47 Stream, JS, HS, CJ, CJDensity, HSDensity,
48 FlamJam, TheThing, TheThing2, GChordStream, OldJumpScaler, HandBalance
49};
50
51static const std::vector<CalcPatternMod> dependent_mods = {
52 OHJumpMod, Balance,
53 Roll, RollJS,
54 OHTrill, VOHTrill,
55 Chaos, WideRangeBalance,
56 WideRangeRoll, WideRangeJumptrill,
57 WideRangeJJ, WideRangeAnchor,
58 RanMan, Minijack,
59 CJOHJump, GStream,
60 GBracketing, HandSwitch,
61 OldAnchorScaler, OldOHJScaler, OldRollScaler
62};
63
65{
66 static void set_agnostic(const CalcPatternMod& pmod,
67 const float& val,
68 const int& pos,
69 Calc& calc)
70 {
71 calc.pmod_vals.at(left_hand).at(pmod).at(pos) = val;
72 }
73
74 static void set_dependent(const int& hand,
75 const CalcPatternMod& pmod,
76 const float& val,
77 const int& pos,
78 Calc& calc)
79 {
80 calc.pmod_vals.at(hand).at(pmod).at(pos) = val;
81 }
82
83 static void run_agnostic_smoothing_pass(const int& end_itv, Calc& calc)
84 {
85 for (const auto& pmod : agnostic_mods) {
86 Smooth(calc.pmod_vals.at(left_hand).at(pmod), neutral, end_itv);
87 }
88 }
89
90 static void run_dependent_smoothing_pass(const int& end_itv, Calc& calc)
91 {
92 for (const auto& pmod : dependent_mods) {
93 for (auto& h : calc.pmod_vals) {
94 Smooth(h.at(pmod), neutral, end_itv);
95 }
96 }
97 }
98
99 static void bruh_they_the_same(const int& end_itv, Calc& calc)
100 {
101 for (const auto& pmod : agnostic_mods) {
102 for (auto i = 0; i < end_itv; i++) {
103 calc.pmod_vals.at(right_hand).at(pmod).at(i) =
104 calc.pmod_vals.at(left_hand).at(pmod).at(i);
105 }
106 }
107 }
108};
109
112inline auto
113time_to_itv_idx(const float& time) -> int
114{
115 // Offset time by half a millisecond to consistently break ties when a row
116 // lies on an interval boundary. This is worst on files at bpms like 180
117 // where the milliseconds between 16ths cannot be represented exactly by a
118 // float but in exact arithemetic regularly align with the interval
119 // boundaries. Offsetting here makes the calc more robust to bpm
120 // fluctuations, mines at the start of the file, etc, which could move notes
121 // into different intervals
122 return static_cast<int>((time + 0.0005f) / interval_span);
123}
124
125inline auto
126itv_idx_to_time(const int& idx) -> float
127{
128 return static_cast<float>(idx) * interval_span;
129}
130
133inline auto
134fast_walk_and_check_for_skip(const std::vector<NoteInfo>& ni,
135 const float& rate,
136 Calc& calc,
137 const float& offset = 0.F) -> bool
138{
139 // an inf rowtime means 0 bpm or some other odd gimmick that may break things
140 // skip this file
141 // nan/inf can occur before the end of the file
142 // but the way these are generated, the last should be the largest
143 // therefore if any are inf, this is inf
144 if (std::isinf(ni.back().rowTime) || std::isnan(ni.back().rowTime))
145 return true;
146
147 /* add 1 to convert index to size, we're just using this to guess due to
148 * potential float precision differences, the actual numitv will be set at
149 * the end */
150 calc.numitv = time_to_itv_idx(ni.back().rowTime / rate) + 1;
151
152 // are there more intervals than our emplaced max
153 if (calc.numitv >= static_cast<int>(calc.itv_size.size())) {
154 // hard cap for memory considerations
155 if (calc.numitv >= max_intervals)
156 return true;
157 // accesses can happen way at the end so give it some breathing room
158 calc.resize_interval_dependent_vectors(calc.numitv + 2);
159 }
160
161 // for various reasons we actually have to do this, scan the file and make
162 // sure each successive row time is greater than the last
163 for (auto i = 1; i < static_cast<int>(ni.size()); ++i) {
164 if (ni.at(i - 1).rowTime >= ni.at(i).rowTime) {
165 return true;
166 }
167 }
168
169 // set up extra keycount information
170 const auto max_keycount_notes = keycount_to_bin(calc.keycount);
171 auto all_columns_without_middle = max_keycount_notes;
172 if (ignore_middle_column) {
173 all_columns_without_middle = mask_to_remove_middle_column(calc.keycount);
174 }
175 // assign all middle taps to the left hand for now (FIND SOMETHING BETTER)
176 auto middle_note_mask = middle_mask(calc.keycount) & max_keycount_notes;
177 auto left_hand_mask = left_mask(calc.keycount) | middle_note_mask & max_keycount_notes;
178 auto right_hand_mask = right_mask(calc.keycount) & all_columns_without_middle;
179
180 // left, right
181 calc.hand_col_masks = { left_hand_mask, right_hand_mask };
182 // all columns from left to rightmost
183 calc.col_masks.clear();
184 calc.col_masks.reserve(calc.keycount);
185 for (unsigned i = 0; i < calc.keycount; i++) {
186 calc.col_masks.push_back(1 << i);
187 }
188
189 /* now we can attempt to construct notinfo that includes column count and
190 * rate adjusted row time, both of which are derived data that both pmod
191 * loops require */
192 auto itv = 0;
193 auto last_itv = 0;
194 auto row_counter = 0;
195 auto scaled_time = 0.F;
196 for (auto i : ni) {
197
198 // it's at least 25 nps per finger, throw it out
199 if (row_counter >= max_rows_for_single_interval) {
200 return true;
201 }
202
203 const auto& ri = i;
204
205 // either not a 4k file or malformed
206 if (ri.notes < 0 || ri.notes > max_keycount_notes) {
207 return true;
208 }
209
210 // 90000 bpm flams may produce 0s due to float precision, we can ignore
211 // this for now, there should be no /0 errors due to it
212 /*if (i > 0) {
213 assert(zoop > scaled_time);
214 }*/
215
216 scaled_time = (i.rowTime + offset) / rate;
217
218 // set current interval and current scaled time
219 itv = time_to_itv_idx(scaled_time);
220
221 // new interval, reset row counter and set new last interval
222 if (itv > last_itv) {
223
224 // we're using static arrays so if we skip over some empty intervals
225 // we have to go back and set their row counts to 0
226 if (itv - last_itv > 1) {
227 for (auto j = last_itv + 1; j < itv; ++j) {
228 calc.itv_size.at(j) = 0;
229 }
230 }
231
232 calc.itv_size.at(last_itv) = row_counter;
233
234 last_itv = itv;
235 row_counter = 0;
236 }
237
238 auto& nri = calc.adj_ni.at(itv).at(row_counter);
239
240 nri.row_notes = ri.notes;
241 nri.row_count = column_count(ri.notes);
242 nri.row_time = scaled_time;
243
244 // how many columns have a note on them per hand
245 nri.hand_counts[left_hand] = std::popcount(ri.notes & left_hand_mask);
246 nri.hand_counts[right_hand] = std::popcount(ri.notes & right_hand_mask);
247
248 // make sure row_count adds up...
249 // this validates that the mask is correct
250 assert(nri.hand_counts[left_hand] + nri.hand_counts[right_hand] ==
251 nri.row_count);
252
253 ++row_counter;
254 }
255
256 // take care to set the proper values for the last row, the set logic block
257 // won't be hit on it
258 if (itv - last_itv > 1) {
259 for (auto j = last_itv + 1; j < itv; ++j) {
260 calc.itv_size.at(j) = 0;
261 }
262 }
263
264 calc.itv_size.at(itv) = row_counter;
265
266 // make sure we only set up to the interval/row we actually use
267 calc.numitv = itv + 1;
268 return false;
269}
Main driver class for the difficulty calculator as a whole.
Definition MinaCalc.h:82
std::array< std::array< std::vector< float >, NUM_CalcPatternMod >, num_hands > pmod_vals
Definition MinaCalc.h:179
Definition UlbuAcolytes.h:65