aboutsummaryrefslogtreecommitdiff
path: root/quantum
diff options
context:
space:
mode:
authorvectorstorm <github@gridbug.org>2021-11-16 05:40:52 +1100
committerGitHub <noreply@github.com>2021-11-16 05:40:52 +1100
commitc9fd69871165eb889be5421df518d8e35b2be027 (patch)
treedffb4a815809e9860a1111304c9ab6872551df58 /quantum
parent36d123e9c5a9ce0e29b9bc22ef87661bf479e299 (diff)
downloadqmk_firmware-c9fd69871165eb889be5421df518d8e35b2be027.tar.gz
qmk_firmware-c9fd69871165eb889be5421df518d8e35b2be027.zip
Reimplements WPM feature to be smaller & precise (#13902)
* Reimplements WPM feature. - Now calculates exact WPM over the last up to three seconds of typing. - WPM_SMOOTHING removed, as it's no longer needed. - WPM_SAMPLE_SECONDS added, to specify how long a period to average WPM over, set to 5 seconds by default. - WPM_SAMPLE_PERIODS added, to specify how many sampling buffers we'll use. Each one uses one extra byte of space. Having more will lead to smoother decay of WPM values. Defaults to 50 (we're saving so many bytes of firmware space I felt like being extravagent, and this change is still a big size saving overall) - WPM_UNFILTERED option added (defaults to unset), which disables all filtering within the WPM feature. This saves some space in the firmware and also reduces latency between typing and the WPM calculation measuring it. (saves 70 bytes in my tests) - WPM_LAUNCH_CONTROL added (defaults to unset). When typing begins while the current displayed WPM value is zero, the WPM calculation only considers the time elapsed since typing began, not the whole WPM_SAMPLE_SECONDS buffer. The result of this is that the displayed WPM value much more rapidly reaches an accurate WPM value, even when results are being filtered. (costs 22 bytes in my tests) - Updates documentation to reflect changed options. Saves about 900 bytes, in my tests, compared against the previous implementation, with default settings. * Apply suggestions from code review Co-authored-by: Sergey Vlasov <sigprof@gmail.com> Co-authored-by: Trevor Powell <trevor@vectorstorm.org> Co-authored-by: Nick Brassel <nick@tzarc.org> Co-authored-by: Sergey Vlasov <sigprof@gmail.com>
Diffstat (limited to 'quantum')
-rw-r--r--quantum/wpm.c98
-rw-r--r--quantum/wpm.h7
2 files changed, 82 insertions, 23 deletions
diff --git a/quantum/wpm.c b/quantum/wpm.c
index cad4cefd5..925e2c416 100644
--- a/quantum/wpm.c
+++ b/quantum/wpm.c
@@ -21,13 +21,37 @@
21 21
22// WPM Stuff 22// WPM Stuff
23static uint8_t current_wpm = 0; 23static uint8_t current_wpm = 0;
24static uint16_t wpm_timer = 0; 24static uint32_t wpm_timer = 0;
25#ifndef WPM_UNFILTERED
26static uint32_t smoothing_timer = 0;
27#endif
25 28
26// This smoothing is 40 keystrokes 29/* The WPM calculation works by specifying a certain number of 'periods' inside
27static const float wpm_smoothing = WPM_SMOOTHING; 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)
45static int8_t period_presses[MAX_PERIODS] = {0};
46static uint8_t current_period = 0;
47static uint8_t periods = 1;
28 48
29void set_current_wpm(uint8_t new_wpm) { current_wpm = new_wpm; } 49#if !defined(WPM_UNFILTERED)
50static uint8_t prev_wpm = 0;
51static uint8_t next_wpm = 0;
52#endif
30 53
54void set_current_wpm(uint8_t new_wpm) { current_wpm = new_wpm; }
31uint8_t get_current_wpm(void) { return current_wpm; } 55uint8_t get_current_wpm(void) { return current_wpm; }
32 56
33bool wpm_keycode(uint16_t keycode) { return wpm_keycode_kb(keycode); } 57bool 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#endif 93#endif
70 94
95// Outside 'raw' mode we smooth results over time.
96
71void update_wpm(uint16_t keycode) { 97void update_wpm(uint16_t keycode) {
72 if (wpm_keycode(keycode)) { 98 if (wpm_keycode(keycode)) {
73 if (wpm_timer > 0) { 99 period_presses[current_period]++;
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();
81 } 100 }
82#ifdef WPM_ALLOW_COUNT_REGRESSION 101#ifdef WPM_ALLOW_COUNT_REGRESSION
83 uint8_t regress = wpm_regress_count(keycode); 102 uint8_t regress = wpm_regress_count(keycode);
84 if (regress) { 103 if (regress) {
85 if (current_wpm < regress) { 104 period_presses[current_period]--;
86 current_wpm = 0;
87 } else {
88 current_wpm -= regress;
89 }
90 wpm_timer = timer_read();
91 } 105 }
92#endif 106#endif
93} 107}
94 108
95void decay_wpm(void) { 109void decay_wpm(void) {
96 if (timer_elapsed(wpm_timer) > 1000) { 110 int32_t presses = period_presses[0];
97 current_wpm += (-current_wpm) * wpm_smoothing; 111 for (int i = 1; i <= periods; i++) {
98 wpm_timer = timer_read(); 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}
diff --git a/quantum/wpm.h b/quantum/wpm.h
index 4af52d2b9..c8e7d2668 100644
--- a/quantum/wpm.h
+++ b/quantum/wpm.h
@@ -22,8 +22,11 @@
22#ifndef WPM_ESTIMATED_WORD_SIZE 22#ifndef WPM_ESTIMATED_WORD_SIZE
23# define WPM_ESTIMATED_WORD_SIZE 5 23# define WPM_ESTIMATED_WORD_SIZE 5
24#endif 24#endif
25#ifndef WPM_SMOOTHING 25#ifndef WPM_SAMPLE_SECONDS
26# define WPM_SMOOTHING 0.0487 26# define WPM_SAMPLE_SECONDS 5
27#endif
28#ifndef WPM_SAMPLE_PERIODS
29# define WPM_SAMPLE_PERIODS 50
27#endif 30#endif
28 31
29bool wpm_keycode(uint16_t keycode); 32bool wpm_keycode(uint16_t keycode);