Etterna 0.74.4
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,
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, GBracketing,
60};
61
63{
64 static void set_agnostic(const CalcPatternMod& pmod,
65 const float& val,
66 const int& pos,
67 Calc& calc)
68 {
69 calc.pmod_vals.at(left_hand).at(pmod).at(pos) = val;
70 }
71
72 static void set_dependent(const int& hand,
73 const CalcPatternMod& pmod,
74 const float& val,
75 const int& pos,
76 Calc& calc)
77 {
78 calc.pmod_vals.at(hand).at(pmod).at(pos) = val;
79 }
80
81 static void run_agnostic_smoothing_pass(const int& end_itv, Calc& calc)
82 {
83 for (const auto& pmod : agnostic_mods) {
84 Smooth(calc.pmod_vals.at(left_hand).at(pmod), neutral, end_itv);
85 }
86 }
87
88 static void run_dependent_smoothing_pass(const int& end_itv, Calc& calc)
89 {
90 for (const auto& pmod : dependent_mods) {
91 for (auto& h : calc.pmod_vals) {
92 Smooth(h.at(pmod), neutral, end_itv);
93 }
94 }
95 }
96
97 static void bruh_they_the_same(const int& end_itv, Calc& calc)
98 {
99 for (const auto& pmod : agnostic_mods) {
100 for (auto i = 0; i < end_itv; i++) {
101 calc.pmod_vals.at(right_hand).at(pmod).at(i) =
102 calc.pmod_vals.at(left_hand).at(pmod).at(i);
103 }
104 }
105 }
106};
107
110inline auto
111time_to_itv_idx(const float& time) -> int
112{
113 // Offset time by half a millisecond to consistently break ties when a row
114 // lies on an interval boundary. This is worst on files at bpms like 180
115 // where the milliseconds between 16ths cannot be represented exactly by a
116 // float but in exact arithemetic regularly align with the interval
117 // boundaries. Offsetting here makes the calc more robust to bpm
118 // fluctuations, mines at the start of the file, etc, which could move notes
119 // into different intervals
120 return static_cast<int>((time + 0.0005f) / interval_span);
121}
122
123inline auto
124itv_idx_to_time(const int& idx) -> float
125{
126 return static_cast<float>(idx) * interval_span;
127}
128
131inline auto
132fast_walk_and_check_for_skip(const std::vector<NoteInfo>& ni,
133 const float& rate,
134 Calc& calc,
135 const float& offset = 0.F) -> bool
136{
137 // an inf rowtime means 0 bpm or some other odd gimmick that may break things
138 // skip this file
139 // nan/inf can occur before the end of the file
140 // but the way these are generated, the last should be the largest
141 // therefore if any are inf, this is inf
142 if (std::isinf(ni.back().rowTime) || std::isnan(ni.back().rowTime))
143 return true;
144
145 /* add 1 to convert index to size, we're just using this to guess due to
146 * potential float precision differences, the actual numitv will be set at
147 * the end */
148 calc.numitv = time_to_itv_idx(ni.back().rowTime / rate) + 1;
149
150 // are there more intervals than our emplaced max
151 if (calc.numitv >= static_cast<int>(calc.itv_size.size())) {
152 // hard cap for memory considerations
153 if (calc.numitv >= max_intervals)
154 return true;
155 // accesses can happen way at the end so give it some breathing room
156 calc.resize_interval_dependent_vectors(calc.numitv + 2);
157 }
158
159 // for various reasons we actually have to do this, scan the file and make
160 // sure each successive row time is greater than the last
161 for (auto i = 1; i < static_cast<int>(ni.size()); ++i) {
162 if (ni.at(i - 1).rowTime >= ni.at(i).rowTime) {
163 return true;
164 }
165 }
166
167 // set up extra keycount information
168 const auto max_keycount_notes = keycount_to_bin(calc.keycount);
169 auto all_columns_without_middle = max_keycount_notes;
170 if (ignore_middle_column) {
171 all_columns_without_middle = mask_to_remove_middle_column(calc.keycount);
172 }
173 auto left_hand_mask = left_mask(calc.keycount) & all_columns_without_middle;
174 auto right_hand_mask = right_mask(calc.keycount) & all_columns_without_middle;
175
176 // left, right
177 calc.hand_col_masks = { left_hand_mask, right_hand_mask };
178 // all columns from left to rightmost
179 calc.col_masks.clear();
180 calc.col_masks.reserve(calc.keycount);
181 for (unsigned i = 0; i < calc.keycount; i++) {
182 calc.col_masks.push_back(1 << i);
183 }
184
185 /* now we can attempt to construct notinfo that includes column count and
186 * rate adjusted row time, both of which are derived data that both pmod
187 * loops require */
188 auto itv = 0;
189 auto last_itv = 0;
190 auto row_counter = 0;
191 auto scaled_time = 0.F;
192 for (auto i : ni) {
193
194 // it's at least 25 nps per finger, throw it out
195 if (row_counter >= max_rows_for_single_interval) {
196 return true;
197 }
198
199 const auto& ri = i;
200
201 // either not a 4k file or malformed
202 if (ri.notes < 0 || ri.notes > max_keycount_notes) {
203 return true;
204 }
205
206 // 90000 bpm flams may produce 0s due to float precision, we can ignore
207 // this for now, there should be no /0 errors due to it
208 /*if (i > 0) {
209 assert(zoop > scaled_time);
210 }*/
211
212 scaled_time = (i.rowTime + offset) / rate;
213
214 // set current interval and current scaled time
215 itv = time_to_itv_idx(scaled_time);
216
217 // new interval, reset row counter and set new last interval
218 if (itv > last_itv) {
219
220 // we're using static arrays so if we skip over some empty intervals
221 // we have to go back and set their row counts to 0
222 if (itv - last_itv > 1) {
223 for (auto j = last_itv + 1; j < itv; ++j) {
224 calc.itv_size.at(j) = 0;
225 }
226 }
227
228 calc.itv_size.at(last_itv) = row_counter;
229
230 last_itv = itv;
231 row_counter = 0;
232 }
233
234 auto& nri = calc.adj_ni.at(itv).at(row_counter);
235
236 nri.row_notes = ri.notes;
237 nri.row_count = column_count(ri.notes);
238 nri.row_time = scaled_time;
239
240 // how many columns have a note on them per hand
241 nri.hand_counts[left_hand] = std::popcount(ri.notes & left_hand_mask);
242 nri.hand_counts[right_hand] = std::popcount(ri.notes & right_hand_mask);
243
244 // make sure row_count adds up...
245 // this validates that the mask is correct
246 assert(nri.hand_counts[left_hand] + nri.hand_counts[right_hand] ==
247 nri.row_count);
248
249 ++row_counter;
250 }
251
252 // take care to set the proper values for the last row, the set logic block
253 // won't be hit on it
254 if (itv - last_itv > 1) {
255 for (auto j = last_itv + 1; j < itv; ++j) {
256 calc.itv_size.at(j) = 0;
257 }
258 }
259
260 calc.itv_size.at(itv) = row_counter;
261
262 // make sure we only set up to the interval/row we actually use
263 calc.numitv = itv + 1;
264 return false;
265}
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:63