summaryrefslogtreecommitdiffstats
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)
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 cad4cefd5d..925e2c416e 100644
--- a/quantum/wpm.c
+++ b/quantum/wpm.c
@@ -21,13 +21,37 @@
// WPM Stuff
static uint8_t current_wpm = 0;
-static uint16_t wpm_timer = 0;
+static uint32_t wpm_timer = 0;
+#ifndef WPM_UNFILTERED
+static uint32_t smoothing_timer = 0;
+#endif
-// This smoothing is 40 keystrokes
-static const float wpm_smoothing = WPM_SMOOTHING;
+/* The WPM calculation works by specifying a certain number of 'periods' inside
+ * a ring buffer, and we count the number of keypresses which occur in each of
+ * those periods. Then to calculate WPM, we add up all of the keypresses in
+ * the whole ring buffer, divide by the number of keypresses in a 'word', and
+ * then adjust for how much time is captured by our ring buffer. Right now
+ * the ring buffer is hardcoded below to be six half-second periods, accounting
+ * for a total WPM sampling period of up to three seconds of typing.
+ *
+ * Whenever our WPM drops to absolute zero due to no typing occurring within
+ * any contiguous three seconds, we reset and start measuring fresh,
+ * which lets our WPM immediately reach the correct value even before a full
+ * three second sampling buffer has been filled.
+ */
+#define MAX_PERIODS (WPM_SAMPLE_PERIODS)
+#define PERIOD_DURATION (1000 * WPM_SAMPLE_SECONDS / MAX_PERIODS)
+#define LATENCY (100)
+static int8_t period_presses[MAX_PERIODS] = {0};
+static uint8_t current_period = 0;
+static uint8_t periods = 1;
-void set_current_wpm(uint8_t new_wpm) { current_wpm = new_wpm; }
+#if !defined(WPM_UNFILTERED)
+static uint8_t prev_wpm = 0;
+static uint8_t next_wpm = 0;
+#endif
+void set_current_wpm(uint8_t new_wpm) { current_wpm = new_wpm; }
uint8_t get_current_wpm(void) { return current_wpm; }
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) {
}
#endif
+// Outside 'raw' mode we smooth results over time.
+
void update_wpm(uint16_t keycode) {
if (wpm_keycode(keycode)) {
- if (wpm_timer > 0) {
- uint16_t latest_wpm = 60000 / timer_elapsed(wpm_timer) / WPM_ESTIMATED_WORD_SIZE;
- if (latest_wpm > UINT8_MAX) {
- latest_wpm = UINT8_MAX;
- }
- current_wpm += ceilf((latest_wpm - current_wpm) * wpm_smoothing);
- }
- wpm_timer = timer_read();
+ period_presses[current_period]++;
}
#ifdef WPM_ALLOW_COUNT_REGRESSION
uint8_t regress = wpm_regress_count(keycode);
if (regress) {
- if (current_wpm < regress) {
- current_wpm = 0;
- } else {
- current_wpm -= regress;
- }
- wpm_timer = timer_read();
+ period_presses[current_period]--;
}
#endif
}
void decay_wpm(void) {
- if (timer_elapsed(wpm_timer) > 1000) {
- current_wpm += (-current_wpm) * wpm_smoothing;
- wpm_timer = timer_read();
+ int32_t presses = period_presses[0];
+ for (int i = 1; i <= periods; i++) {
+ presses += period_presses[i];
+ }
+ if (presses < 0) {
+ presses = 0;
}
+ int32_t elapsed = timer_elapsed32(wpm_timer);
+ uint32_t duration = (((periods)*PERIOD_DURATION) + elapsed);
+ uint32_t wpm_now = (60000 * presses) / (duration * WPM_ESTIMATED_WORD_SIZE);
+ wpm_now = (wpm_now > 240) ? 240 : wpm_now;
+
+ if (elapsed > PERIOD_DURATION) {
+ current_period = (current_period + 1) % MAX_PERIODS;
+ period_presses[current_period] = 0;
+ periods = (periods < MAX_PERIODS - 1) ? periods + 1 : MAX_PERIODS - 1;
+ elapsed = 0;
+ /* if (wpm_timer == 0) { */
+ wpm_timer = timer_read32();
+ /* } else { */
+ /* wpm_timer += PERIOD_DURATION; */
+ /* } */
+ }
+ if (presses < 2) // don't guess high WPM based on a single keypress.
+ wpm_now = 0;
+
+#if defined WPM_LAUNCH_CONTROL
+ if (presses == 0) {
+ current_period = 0;
+ periods = 0;
+ wpm_now = 0;
+ }
+#endif // WPM_LAUNCH_CONTROL
+
+#ifndef WPM_UNFILTERED
+ int32_t latency = timer_elapsed32(smoothing_timer);
+ if (latency > LATENCY) {
+ smoothing_timer = timer_read32();
+ prev_wpm = current_wpm;
+ next_wpm = wpm_now;
+ }
+
+ current_wpm = prev_wpm + (latency * ((int)next_wpm - (int)prev_wpm) / LATENCY);
+#else
+ current_wpm = wpm_now;
+#endif
}
diff --git a/quantum/wpm.h b/quantum/wpm.h
index 4af52d2b98..c8e7d26684 100644
--- a/quantum/wpm.h
+++ b/quantum/wpm.h
@@ -22,8 +22,11 @@
#ifndef WPM_ESTIMATED_WORD_SIZE
# define WPM_ESTIMATED_WORD_SIZE 5
#endif
-#ifndef WPM_SMOOTHING
-# define WPM_SMOOTHING 0.0487
+#ifndef WPM_SAMPLE_SECONDS
+# define WPM_SAMPLE_SECONDS 5
+#endif
+#ifndef WPM_SAMPLE_PERIODS
+# define WPM_SAMPLE_PERIODS 50
#endif
bool wpm_keycode(uint16_t keycode);