Etterna 0.74.4
Loading...
Searching...
No Matches
GenericSequencing.h
1#pragma once
2#include "../../CalcWindow.h"
3
4/* Contains generic sequencers passed to metahandinfo to be advanced in the row
5 * loop */
6
7// ok this has expanded into more than i bargained for an should be
8// appropriately renamed and commented when i figure out what it is
9
10/* important note about timing names used throughout the calc. anything being
11 * updated with the ms value of this row to the last row with a note in it
12 * independent of any other considerations should use the var name "any_ms", the
13 * name of the ms value from the current row to the last row that contains a
14 * note on the same column should be "sc_ms", for same column ms, and the only
15 * other option is "cc_ms", for cross column ms */
16
17/****** Relevant to anchors ******/
18constexpr float anchor_spacing_buffer_ms = 10.F;
19constexpr float anchor_speed_increase_cutoff_factor = 2.34F;
20static const int anchor_len_cap = 50;
21
22/****** Relevant to jacks ******/
23constexpr float jack_spacing_buffer_ms = 10.F;
24constexpr float jack_speed_increase_cutoff_factor = 1.9F;
25static const int jack_len_cap = 4;
26
27
29constexpr float guaranteed_reset_buffer_ms = 1000.F;
30
31
32enum anch_status
33{
34 reset_too_slow,
35 reset_too_fast,
36
37 // _len > 2, otherwise we would be at the start of a file, or just reset due
38 // to being too fast/slow
39 anchoring,
40 anch_init,
41};
42
45{
46 // what column this anchor is on (will be set on startup by the sequencer)
47 col_type _ct = col_init;
48 anch_status _status = anch_init;
49
63 int _len = 1;
65 float _sc_ms = ms_init;
66
69 float _max_ms = ms_init;
70
73 float _len_cap_ms = ms_init;
74
76 float _last = s_init;
77 float _start = s_init;
78
79 inline virtual void full_reset()
80 {
81 // never reset col_type
82 _sc_ms = ms_init;
83 _max_ms = ms_init;
84 _last = s_init;
85 _start = s_init;
86 _len = 1;
87 _status = anch_init;
88 _len_cap_ms = ms_init;
89 }
90
92 inline virtual void check_status()
93 {
94 switch (_status) {
95 case reset_too_slow:
96 case reset_too_fast:
97 /* i don't like hard cutoffs much but in the interest of
98 * fairness if the current ms value is vastly lower than the
99 * _max_ms, set the start time of the anchor to now and reset, i
100 * can't really think of any way this can be abused in a way
101 * that inflates files, just lots of ways it can underdetect;
102 * we're resetting because we've started on something much
103 * faster or slower, so we know the start of this anchor was
104 * actually the, last note, directly reset _max_ms to the
105 * current ms and len to 2 */
106 _start = _last;
107 _len = 2;
108 break;
109 case anchoring:
110 // increase anchor length and set new cutoff point
111 ++_len;
112 break;
113 case anch_init:
114 // nothing to do
115 break;
116 }
117 }
118
125 inline virtual void set_status() = 0;
127 inline virtual void operator()(const col_type ct, const float& now) = 0;
129 inline virtual float get_ms() = 0;
130
131 virtual ~Finger_Sequencing() = default;
132};
133
136{
137 inline void set_status() override
138 {
139 if (_sc_ms > _max_ms + jack_spacing_buffer_ms) {
140 _status = reset_too_slow;
141 } else if (_sc_ms * jack_speed_increase_cutoff_factor < _max_ms) {
142 _status = reset_too_fast;
143 } else {
144 _status = anchoring;
145 }
146 }
147
148 inline void operator()(const col_type ct, const float& now) override
149 {
150 assert(ct == _ct);
151 _sc_ms = ms_from(now, _last);
152
153 if (ct == col_init) {
154 _last = now;
155 return;
156 }
157
158 set_status();
159 check_status();
160
161 _max_ms = _sc_ms;
162 _last = now;
163 }
164
166 inline float get_ms() override
167 {
168 assert(_sc_ms > 0.F);
169
170 /* return whatever the last calculated value was after this point, this
171 * way we don't let longjacks completely take over */
172 if (_len > jack_len_cap) {
173 return _len_cap_ms;
174 }
175
176 static const auto avg_ms_mult = 1.5F;
177 static const auto anchor_time_buffer_ms = 30.F;
178 static const auto min_ms = 95.F;
179
180 // get total ms
181 const auto total_ms = ms_from(_last, _start);
182
183 // get len (len of 2 (notes) means 1 jack, 3 = 2, etc
184 const auto len = static_cast<float>(_len - 1);
185
186 // get average ms for the jack sequence
187 const auto avg_ms = total_ms / len;
188
189 /* adjust total ms by adding flat and scaled buffers, this depresses
190 * shorter jacks more */
191 const auto adj_total_ms =
192 total_ms + anchor_time_buffer_ms + avg_ms * avg_ms_mult;
193
194 // calculate final adjusted ms average
195 auto ms = adj_total_ms / len;
196
197 // BAD TEMP HACK LUL
198 if (_len == 2) {
199 ms *= 1.1F;
200 ms = ms < 180.F ? 180.F : ms;
201 }
202
203 ms = ms < min_ms ? min_ms : ms;
204
205 if (std::isnan(ms))
206 ms = _max_ms;
207
208 if (_len == jack_len_cap) {
209 _len_cap_ms = ms;
210 }
211
212 return ms;
213 }
214};
215
224{
225 inline void set_status() override
226 {
227 if (_sc_ms > _max_ms + anchor_spacing_buffer_ms) {
228 _status = reset_too_slow;
229 } else if (_sc_ms * anchor_speed_increase_cutoff_factor < _max_ms) {
230 _status = reset_too_fast;
231 } else {
232 _status = anchoring;
233 }
234 }
235
236 inline void operator()(const col_type ct, const float& now) override
237 {
238 assert(ct == _ct);
239 _sc_ms = ms_from(now, _last);
240
241 if (ct == col_init) {
242 _last = now;
243 return;
244 }
245
246 set_status();
247 check_status();
248
249 _max_ms = _sc_ms;
250 _last = now;
251 }
252
255 inline float get_ms() override
256 {
257 assert(_sc_ms > 0.F);
258
259 /* return whatever the last calculated value was after this point, this
260 * way we don't let longjacks completely take over */
261 if (_len > anchor_len_cap) {
262 return _len_cap_ms;
263 }
264
265 static const auto avg_ms_mult = 1.F;
266 static const auto anchor_time_buffer_ms = 0.F;
267 static const auto min_ms = 0.F;
268
269 // get total ms
270 const auto total_ms = ms_from(_last, _start);
271
272 // get len (len of 2 (notes) means 1 jack, 3 = 2, etc
273 const auto len = static_cast<float>(_len - 1);
274
275 // get average ms for the jack sequence
276 const auto avg_ms = total_ms / len;
277
278 /* adjust total ms by adding flat and scaled buffers, this depresses
279 * shorter jacks more */
280 const auto adj_total_ms =
281 total_ms + anchor_time_buffer_ms + avg_ms * avg_ms_mult;
282
283 // calculate final adjusted ms average
284 auto ms = adj_total_ms / len;
285
286 // BAD TEMP HACK LUL
287 if (_len == 2) {
288 ms *= 1.1F;
289 ms = ms < 155.F ? 155.F : ms;
290 }
291
292 ms = ms < min_ms ? min_ms : ms;
293
294 if (std::isnan(ms))
295 ms = _max_ms;
296
297 if (_len == anchor_len_cap) {
298 _len_cap_ms = ms;
299 }
300
301 return ms;
302 }
303};
304
305// CURRENTLY ALSO BEING USED TO STORE THE OLD CC/TC MS VALUES IN MHI...
306// not that this is a great idea but it's appropriate for doing so
308{
310 std::array<std::unique_ptr<Anchor_Sequencing>, num_cols_per_hand> anch;
312 std::array<std::unique_ptr<Jack_Sequencing>, num_cols_per_hand> jack;
313
315 std::array<int, num_cols_per_hand> max_seen = { 0, 0 };
317 std::array<CalcMovingWindow<int>, num_cols_per_hand> _mw_max;
318
320 {
321 for (const auto& c : ct_loop_no_jumps) {
322 anch[c].reset(new Anchor_Sequencing);
323 jack[c].reset(new Jack_Sequencing);
324 anch[c]->_ct = c;
325 jack[c]->_ct = c;
326 }
327 full_reset();
328 }
329
330 void full_reset()
331 {
332 max_seen.fill(0);
333
334 for (const auto& c : ct_loop_no_jumps) {
335 anch.at(c)->full_reset();
336 jack.at(c)->full_reset();
337 _mw_max.at(c).zero();
338 }
339 }
340
341 // derives sc_ms, which sequencer general will pull for its moving window
342 void operator()(const col_type ct, const float& row_time)
343 {
344 // update the one
345 if (ct == col_left || ct == col_right) {
346 auto opposite_col = ct == col_left ? col_right : col_left;
347 (*anch.at(ct))(ct, row_time);
348 (*jack.at(ct))(ct, row_time);
349
350 // set max seen for this col for this interval
351 max_seen.at(ct) = anch.at(ct)->_len > max_seen.at(ct)
352 ? anch.at(ct)->_len
353 : max_seen.at(ct);
354
355 // reset the other column if necessary
356 // this is particularly for jacks -- not resetting this breaks
357 // difficulty
358 if (ms_from(row_time, anch.at(opposite_col)->_last) >
359 guaranteed_reset_buffer_ms) {
360 anch.at(opposite_col)->full_reset();
361 jack.at(opposite_col)->full_reset();
362 }
363 } else if (ct == col_ohjump) {
364
365 // update both
366 for (const auto& c : ct_loop_no_jumps) {
367 (*anch.at(c))(c, row_time);
368 (*jack.at(c))(c, row_time);
369
370 // set max seen
371 max_seen.at(c) = anch.at(c)->_len > max_seen.at(c)
372 ? anch.at(c)->_len
373 : max_seen.at(c);
374 }
375 }
376 }
377
378 // returns max anchor length seen for the requested window
379 [[nodiscard]] auto get_max_for_window_and_col(const col_type& ct,
380 const int& window) const
381 -> int
382 {
383 assert(ct < num_cols_per_hand);
384 return _mw_max.at(ct).get_max_for_window(window);
385 }
386
387 void interval_end()
388 {
389 for (const auto& c : ct_loop_no_jumps) {
390 _mw_max.at(c)(max_seen.at(c));
391 max_seen.at(c) = 0;
392 }
393 }
394
395 auto get_lowest_anchor_ms() -> float
396 {
397 return std::min(anch.at(col_left)->get_ms(),
398 anch.at(col_right)->get_ms());
399 }
400
401 auto get_lowest_jack_ms() -> float
402 {
403 return std::min(jack.at(col_left)->get_ms(),
404 jack.at(col_right)->get_ms());
405 }
406};
407
408/* keep timing stuff here instead of in mhi, use mhi exclusively for pattern
409 * detection */
410
411/* every note has at least 2 ms values associated with it, the ms value from
412 * the last cross column note (on the same hand), and the ms value from the last
413 * note on it's/this column both are useful for different things, and we want to
414 * track both for ohjumps, we will track the ms from the last non-jump on either
415 * finger, there are situations where we may want to consider jumps as having a
416 * cross column ms value of 0 with itself, not sure if they should be set to
417 * this or left at the init values of 5000 though */
418
419// more stuff could/should be moved here? the only major issue with moving _all_
420// sequencers here is loading/setting their params
422{
423 /* should maybe have this contain a struct that just handles timing, or is
424 * that overboard? */
425
429
432
435 std::array<CalcMovingWindow<float>, num_cols_per_hand> _mw_sc_ms;
436
439
443 void set_sc_ms(const col_type& ct)
444 {
445 // single notes are simple
446 if (ct == col_left || ct == col_right) {
447
448 _mw_sc_ms.at(ct)(_as.anch.at(ct)->_sc_ms);
449 }
450
451 // oh jumps mean we do both, we will allow whatever is querying for the
452 // value to choose which column value they want (lower by default)
453 if (ct == col_ohjump) {
454 for (const auto& c : ct_loop_no_jumps) {
455 _mw_sc_ms.at(c)(_as.anch.at(c)->_sc_ms);
456 }
457 }
458 }
459
460 // cc_ms is the time from the current note to the last note in the cross
461 // column, for this we need to take the last row_time on the cross column,
462 // (anchor sequencer has it as _last) and derive a new ms value from it and
463 // the current row_time
464 void set_cc_ms(const col_type& ct, const float& row_time)
465 {
466 // single notes are simple, grab the _last of ct inverted
467 if (ct == col_left || ct == col_right) {
468 _mw_cc_ms(ms_from(row_time, _as.anch.at(invert_col(ct))->_last));
469 }
470
471 /* jumps are tricky, technically we have 2 cc_ms values, but also
472 * technically values are simply the sc_ms values we already calculated,
473 * but inverted, given that the goal however is to provide general
474 * values that various pattern mods can use such that they don't have to
475 * all track their own custom sequences, we should place the lower sc_ms
476 * value in here, since that's the most common use case, if something
477 * needs to specifically handle ohjumps differently, it can do so we do
478 * actually need to set this value so the calcwindow internal cv checks
479 * will work, we can't just shortcut and make a get function which swaps
480 * where it returns from */
481 if (ct == col_ohjump) {
482 _mw_cc_ms(get_sc_ms_now(col_ohjump));
483 }
484 }
485
486 // stuff
487 void advance_sequencing(const col_type& ct,
488 const float& row_time,
489 const float& ms_now)
490 {
491 if (ct != col_ohjump) {
492 auto reset_sequencer = ms_from(row_time, _as.anch.at(ct)->_last) >
493 guaranteed_reset_buffer_ms;
494 if (reset_sequencer) {
495 _as.anch.at(ct)->full_reset();
496 _as.jack.at(ct)->full_reset();
497 _mw_sc_ms.at(ct).fill(ms_init);
498 _mw_cc_ms.fill(ms_init);
499 _mw_any_ms.fill(ms_init);
500 }
501 }
502
503 // update sequencers
504 _as(ct, row_time);
505
506 // i guess we keep track of ms sequencing here instead of mhi, or
507 // somewhere new?
508
509 // sc ms needs to be set first, cc ms will reference it for ohjumps
510 set_sc_ms(ct);
511 set_cc_ms(ct, row_time);
512 _mw_any_ms(ms_now);
513 }
514
515 [[nodiscard]] auto get_sc_ms_now(const col_type& ct,
516 const bool lower = true) const -> float
517 {
518 if (ct == col_init) {
519 return ms_init;
520 }
521
522 // if ohjump, grab the smaller value by default
523 if (ct == col_ohjump) {
524 if (lower) {
525 return _mw_sc_ms[col_left].get_now() <
526 _mw_sc_ms[col_right].get_now()
527 ? _mw_sc_ms[col_left].get_now()
528 : _mw_sc_ms[col_right].get_now();
529 }
530 // return the higher value instead (dunno if we'll ever need
531 // this but it's good to have the option)
532 return _mw_sc_ms[col_left].get_now() >
533 _mw_sc_ms[col_right].get_now()
534 ? _mw_sc_ms[col_left].get_now()
535 : _mw_sc_ms[col_right].get_now();
536 }
537
538 // simple
539 return _mw_sc_ms.at(ct).get_now();
540 }
541
542 auto get_mw_sc_ms(const col_type& ct)
543 {
544 if (ct == col_left || ct == col_ohjump) {
545 return _mw_sc_ms[col_left];
546 }
547 return _mw_sc_ms[col_right];
548 }
549
550 [[nodiscard]] auto get_any_ms_now() const -> float
551 {
552 return _mw_any_ms.get_now();
553 }
554 [[nodiscard]] auto get_cc_ms_now() const -> float
555 {
556 return _mw_cc_ms.get_now();
557 }
558
559 void interval_end() { _as.interval_end(); }
560
561 void full_reset()
562 {
563 _mw_any_ms.fill(ms_init);
564 _mw_cc_ms.fill(ms_init);
565
566 for (const auto& c : ct_loop_no_jumps) {
567 _mw_sc_ms.at(c).fill(ms_init);
568 }
569
570 _as.full_reset();
571 }
572};
Definition GenericSequencing.h:308
std::array< std::unique_ptr< Anchor_Sequencing >, num_cols_per_hand > anch
anchor sequencers for each finger
Definition GenericSequencing.h:310
std::array< int, num_cols_per_hand > max_seen
information for each column to store in the movingwindow_max, this interval
Definition GenericSequencing.h:315
std::array< std::unique_ptr< Jack_Sequencing >, num_cols_per_hand > jack
jack sequencers for each finger
Definition GenericSequencing.h:312
std::array< CalcMovingWindow< int >, num_cols_per_hand > _mw_max
track windows of highest anchor per col seen during an interval
Definition GenericSequencing.h:317
Definition GenericSequencing.h:224
void operator()(const col_type ct, const float &now) override
sequence updating given the column type and time of current row
Definition GenericSequencing.h:236
float get_ms() override
Definition GenericSequencing.h:255
void set_status() override
Definition GenericSequencing.h:225
Definition CalcWindow.h:15
auto get_now() const -> T
get most recent value in moving window
Definition CalcWindow.h:38
Individual anchors, 2 objects per hand on 4k.
Definition GenericSequencing.h:45
virtual float get_ms()=0
returns an adjusted MS average value, not converted to nps
float _len_cap_ms
Definition GenericSequencing.h:73
virtual void set_status()=0
int _len
Definition GenericSequencing.h:63
virtual void check_status()
based on the anchoring status, do an action
Definition GenericSequencing.h:92
virtual void operator()(const col_type ct, const float &now)=0
sequence updating given the column type and time of current row
float _last
row_time of last note on this col
Definition GenericSequencing.h:76
float _sc_ms
same-column ms: time between now and previous tap
Definition GenericSequencing.h:65
float _max_ms
Definition GenericSequencing.h:69
Individual jacks, rather than anchors, with more nuance.
Definition GenericSequencing.h:136
void set_status() override
Definition GenericSequencing.h:137
void operator()(const col_type ct, const float &now) override
sequence updating given the column type and time of current row
Definition GenericSequencing.h:148
float get_ms() override
returns an adjusted MS average value, not converted to nps
Definition GenericSequencing.h:166
Definition GenericSequencing.h:422
AnchorSequencer _as
Definition GenericSequencing.h:438
void set_sc_ms(const col_type &ct)
Definition GenericSequencing.h:443
std::array< CalcMovingWindow< float >, num_cols_per_hand > _mw_sc_ms
Definition GenericSequencing.h:435
CalcMovingWindow< float > _mw_cc_ms
moving window of cc_ms values
Definition GenericSequencing.h:431
CalcMovingWindow< float > _mw_any_ms
Definition GenericSequencing.h:428