|
21 | 21 |
|
22 | 22 | // WPM Stuff
|
23 | 23 | static uint8_t current_wpm = 0;
|
24 |
| -static uint16_t wpm_timer = 0; |
| 24 | +static uint32_t wpm_timer = 0; |
| 25 | +#ifndef WPM_UNFILTERED |
| 26 | +static uint32_t smoothing_timer = 0; |
| 27 | +#endif |
25 | 28 |
|
26 |
| -// This smoothing is 40 keystrokes |
27 |
| -static const float wpm_smoothing = WPM_SMOOTHING; |
| 29 | +/* The WPM calculation works by specifying a certain number of 'periods' inside |
| 30 | + * a ring buffer, and we count the number of keypresses which occur in each of |
| 31 | + * those periods. Then to calculate WPM, we add up all of the keypresses in |
| 32 | + * the whole ring buffer, divide by the number of keypresses in a 'word', and |
| 33 | + * then adjust for how much time is captured by our ring buffer. Right now |
| 34 | + * the ring buffer is hardcoded below to be six half-second periods, accounting |
| 35 | + * for a total WPM sampling period of up to three seconds of typing. |
| 36 | + * |
| 37 | + * Whenever our WPM drops to absolute zero due to no typing occurring within |
| 38 | + * any contiguous three seconds, we reset and start measuring fresh, |
| 39 | + * which lets our WPM immediately reach the correct value even before a full |
| 40 | + * three second sampling buffer has been filled. |
| 41 | + */ |
| 42 | +#define MAX_PERIODS (WPM_SAMPLE_PERIODS) |
| 43 | +#define PERIOD_DURATION (1000 * WPM_SAMPLE_SECONDS / MAX_PERIODS) |
| 44 | +#define LATENCY (100) |
| 45 | +static int8_t period_presses[MAX_PERIODS] = {0}; |
| 46 | +static uint8_t current_period = 0; |
| 47 | +static uint8_t periods = 1; |
28 | 48 |
|
29 |
| -void set_current_wpm(uint8_t new_wpm) { current_wpm = new_wpm; } |
| 49 | +#if !defined(WPM_UNFILTERED) |
| 50 | +static uint8_t prev_wpm = 0; |
| 51 | +static uint8_t next_wpm = 0; |
| 52 | +#endif |
30 | 53 |
|
| 54 | +void set_current_wpm(uint8_t new_wpm) { current_wpm = new_wpm; } |
31 | 55 | uint8_t get_current_wpm(void) { return current_wpm; }
|
32 | 56 |
|
33 | 57 | bool wpm_keycode(uint16_t keycode) { return wpm_keycode_kb(keycode); }
|
@@ -68,33 +92,65 @@ __attribute__((weak)) uint8_t wpm_regress_count(uint16_t keycode) {
|
68 | 92 | }
|
69 | 93 | #endif
|
70 | 94 |
|
| 95 | +// Outside 'raw' mode we smooth results over time. |
| 96 | + |
71 | 97 | void update_wpm(uint16_t keycode) {
|
72 | 98 | if (wpm_keycode(keycode)) {
|
73 |
| - if (wpm_timer > 0) { |
74 |
| - uint16_t latest_wpm = 60000 / timer_elapsed(wpm_timer) / WPM_ESTIMATED_WORD_SIZE; |
75 |
| - if (latest_wpm > UINT8_MAX) { |
76 |
| - latest_wpm = UINT8_MAX; |
77 |
| - } |
78 |
| - current_wpm += ceilf((latest_wpm - current_wpm) * wpm_smoothing); |
79 |
| - } |
80 |
| - wpm_timer = timer_read(); |
| 99 | + period_presses[current_period]++; |
81 | 100 | }
|
82 | 101 | #ifdef WPM_ALLOW_COUNT_REGRESSION
|
83 | 102 | uint8_t regress = wpm_regress_count(keycode);
|
84 | 103 | if (regress) {
|
85 |
| - if (current_wpm < regress) { |
86 |
| - current_wpm = 0; |
87 |
| - } else { |
88 |
| - current_wpm -= regress; |
89 |
| - } |
90 |
| - wpm_timer = timer_read(); |
| 104 | + period_presses[current_period]--; |
91 | 105 | }
|
92 | 106 | #endif
|
93 | 107 | }
|
94 | 108 |
|
95 | 109 | void decay_wpm(void) {
|
96 |
| - if (timer_elapsed(wpm_timer) > 1000) { |
97 |
| - current_wpm += (-current_wpm) * wpm_smoothing; |
98 |
| - wpm_timer = timer_read(); |
| 110 | + int32_t presses = period_presses[0]; |
| 111 | + for (int i = 1; i <= periods; i++) { |
| 112 | + presses += period_presses[i]; |
| 113 | + } |
| 114 | + if (presses < 0) { |
| 115 | + presses = 0; |
99 | 116 | }
|
| 117 | + int32_t elapsed = timer_elapsed32(wpm_timer); |
| 118 | + uint32_t duration = (((periods)*PERIOD_DURATION) + elapsed); |
| 119 | + uint32_t wpm_now = (60000 * presses) / (duration * WPM_ESTIMATED_WORD_SIZE); |
| 120 | + wpm_now = (wpm_now > 240) ? 240 : wpm_now; |
| 121 | + |
| 122 | + if (elapsed > PERIOD_DURATION) { |
| 123 | + current_period = (current_period + 1) % MAX_PERIODS; |
| 124 | + period_presses[current_period] = 0; |
| 125 | + periods = (periods < MAX_PERIODS - 1) ? periods + 1 : MAX_PERIODS - 1; |
| 126 | + elapsed = 0; |
| 127 | + /* if (wpm_timer == 0) { */ |
| 128 | + wpm_timer = timer_read32(); |
| 129 | + /* } else { */ |
| 130 | + /* wpm_timer += PERIOD_DURATION; */ |
| 131 | + /* } */ |
| 132 | + } |
| 133 | + if (presses < 2) // don't guess high WPM based on a single keypress. |
| 134 | + wpm_now = 0; |
| 135 | + |
| 136 | +#if defined WPM_LAUNCH_CONTROL |
| 137 | + if (presses == 0) { |
| 138 | + current_period = 0; |
| 139 | + periods = 0; |
| 140 | + wpm_now = 0; |
| 141 | + } |
| 142 | +#endif // WPM_LAUNCH_CONTROL |
| 143 | + |
| 144 | +#ifndef WPM_UNFILTERED |
| 145 | + int32_t latency = timer_elapsed32(smoothing_timer); |
| 146 | + if (latency > LATENCY) { |
| 147 | + smoothing_timer = timer_read32(); |
| 148 | + prev_wpm = current_wpm; |
| 149 | + next_wpm = wpm_now; |
| 150 | + } |
| 151 | + |
| 152 | + current_wpm = prev_wpm + (latency * ((int)next_wpm - (int)prev_wpm) / LATENCY); |
| 153 | +#else |
| 154 | + current_wpm = wpm_now; |
| 155 | +#endif |
100 | 156 | }
|
0 commit comments