chording engine

saturday, 17 april 2021

and how i have been won over by a 30% keyboard.

The Georgi keyboard for steno is flashed with QMK software but with a divergent approach from most supported keyboards. Namely, it uses a “chording” engine associating actions to a chord of one or more keys versus the traditional serial key processing method whereby each key is tied to a single action or function.

Thus, a chord can replicate any serial key action with a single key chord but the converse cannot be said for multi-key chords – at least without a lot of code wrangling in the process_record_user() section of one’s keymap.c file.

macros

the default layout for the Georgi is defined using g Heavy Industries supported chording engine with essentially a single macro..

uint32_t processQwerty(bool lookup) { P ( chord, action;* ) ... }

where, chord is one or more keys chained together with the Pipe symbol, and action is any number of chained chording engine function calls – most commonly SEND() or SEND_STRING().

A layer can be effectively created with a common chord key prefix. How simple is that!

For a simple layout, this coding approach can be adequate but the chording engine’s behaviour is tailored specifically to satisfy stenography chording needs which the Georgi is primarily designed for. Plus, the chording specification, while powerful, does not lend itself syntactically to visually grasping the intended layout.

Enter Reddit user..

u/Denny Tom’s

chording engine which implements numerous counterparts to the QMK platform such as one shot modifiers and layers, tap hold modifiers (for home row modifiers), external macro definition, etc.

If that wasn’t powerful enough, all this is facilitated with a json file to define the layers and chords in a visual self-explanatory way. This edited file is parsed by a custom python app to generate the necessary keymap.c file.

One’s specification is largely entered as a series of tables or arrays, representing key columns, rows and resultant actions. Judicious formatting creates a self documenting json configuration file. The parser does the rest!

The whole result is brilliant. Changes to one’s json file can be made incrementally and flashed for instant feedback. Did i mention brilliant?

All of this was the impetus for purchasing the Georgi keyboard sooner rather than later – with g Heavy Industries’ endorsement of DennyTom’s work for my intentions taken into account. And what this allowed me to do was rethink..

beakl wi

my 40% layout implementations of BEAKL Wi use my own QMK library extensions for rolling tap hold modifiers, pseudo-chording behaviour, tap dances and one shot layers. DennyTom’s chording engine paradigm does away with all that!

What this promoted is a complete rethink of the layout’s special keys as chords, replacing all tap dance keys as such. Home row modifiers are even supported..

"type": "chord_set", "set": "rows", "keycodes": [ "DF(FNC)" , ":", "Y", "O", "U", "-", "G", "D", "N", "M", "X", "DF(SYMBOL)", "DF(PLOVER)" , "KM(Q, LGUI)", "KK(H, LCTL)", "KK(E, LALT)", "KM(A, LSFT)", "W", "C", "KM(T, RSFT)", "KK(R, RALT)", "KK(S, RCTL)", "KM(Z, RGUI)", "DF(NAV)", "M(hexpad, 0, 3)", "J", ",", ".", "K", "'", "B", "P", "L", "F", "V", "DF(NUM)", " ", " ", " ", " ", " ", " " ]

Georgi BEAKL WI

chord output
: Y “ :: ”
Shift I Space
Shift Tab Enter
Shift Backspace Backspace (autorepeat)
Space Backspace Del (autorepeat)

On the base layer, the Colon double tap becomes chord Colon Y. Other double taps similarly partner with an adjacent key. Easy peasy.

shift chords

function similarly, only these are real chords with no key order timing dependencies..

"type": "visual_array", "keys": ["TOP2", "TOP3", "BOT3", "BOT4", "BOT5", "TOP6", "BOT6", "TOP8", "BOT8"], "dictionary": [ ["X", " ", " ", " ", " ", " ", " ", "X", "X", ";"], ["X", "X", " ", " ", " ", " ", " ", " ", " ", "STR(' :: ')"], [" ", " ", "X", " ", " ", " ", " ", "X", "X", "?"], [" ", " ", " ", "X", " ", " ", " ", "X", "X", "!"], [" ", " ", " ", " ", " ", "X", " ", "X", "X", "_"], [" ", " ", " ", " ", " ", " ", "X", "X", "X", "\""] ]

Georgi BEAKL Wi Shift
Chords

chord output
Shift : ;
Shift , ?
Shift . !
Shift - _
Shift ‘

Some chords may appear redundant but are defined merely for typing convenience :) – this facility is a hugely powerful aspect of chording.

As a result of chording, some small changes have been made to BEAKL Wi, notably with punctuation. The Question and Exclamation marks become shift chords moving from the Symbol Layer (for a smoother implementation of leader capitalization) – and on the Georgi, their former home row positions do not feel sacrificed.

leader capitalization

is facilitated with an external macro call based on DennyTom’s chording engine one_shot_layer() function. A one shot CAPS layer is defined as a layer similar to the chord_set above as all upper case characters, minus the modifiers..

void cap(const struct Chord* self) { switch (*self->state) { case ACTIVATED: tap_key(self->value1); tap_key(self->value2); break; case DEACTIVATED: current_pseudolayer = CAPS; *self->state = IN_ONE_SHOT; break; case FINISHED: case PRESS_FROM_ACTIVE: current_pseudolayer = CAPS; a_key_went_through = false; break; case RESTART: if (a_key_went_through) { current_pseudolayer = self->pseudolayer; } else { *self->state = IN_ONE_SHOT; } default: break; } }

"type": "visual_array", "keys": ["TOP2", "BOT3", "BOT4", "TOP5", "BOT5", "TOP8", "BOT8", "THU4", "THU5"], "dictionary": [ ["X", " ", " ", " ", " ", " ", " ", "X", " ", "M(cap, KC_COLN, KC_ENTER)"], ["X", " ", " ", " ", " ", "X", "X", "X", " ", "M(cap, KC_SCLN, KC_ENTER)"], [" ", "X", " ", " ", " ", " ", " ", "X", " ", "M(cap, KC_COMM, KC_ENTER)"], [" ", "X", " ", " ", " ", "X", "X", "X", " ", "M(cap, KC_QUES, KC_ENTER)"], [" ", " ", "X", " ", " ", " ", " ", "X", " ", "M(cap, KC_DOT, KC_ENTER)"], [" ", " ", "X", " ", " ", "X", "X", "X", " ", "M(cap, KC_EXLM, KC_ENTER)"], [" ", " ", " ", "X", "X", " ", " ", "X", " ", "M(cap, KC_NO, KC_ENTER)"], ["X", " ", " ", " ", " ", " ", " ", " ", "X", "M(cap, KC_COLN, KC_SPC)"], ["X", " ", " ", " ", " ", "X", "X", " ", "X", "M(cap, KC_SCLN, KC_SPC)"], [" ", "X", " ", " ", " ", " ", " ", " ", "X", "M(cap, KC_COMM, KC_SPC)"], [" ", "X", " ", " ", " ", "X", "X", " ", "X", "M(cap, KC_QUES, KC_SPC)"], [" ", " ", "X", " ", " ", " ", " ", " ", "X", "M(cap, KC_DOT, KC_SPC)"], [" ", " ", "X", " ", " ", "X", "X", " ", "X", "M(cap, KC_EXLM, KC_SPC)"], [" ", " ", " ", "X", "X", " ", " ", " ", "X", "M(cap, KC_NO, KC_SPC)"] ]

Georgi BEAKL WI Leader Capitalization

chord output
: Space “: ” + one_shot_shift
, Space “, ” + one_shot_shift
. Space “. ” + one_shot_shift
Shift : Space “; ” + one_shot_shift
Shift , Space “? ” + one_shot_shift
Shift . Space “! ” + one_shot_shift
Shift Space “ ” + one_shot_shift
: Enter : <enter> + one_shot_shift
, Enter , <enter> + one_shot_shift
. Enter . <enter> + one_shot_shift
Shift : Enter ; <enter> + one_shot_shift
Shift , Enter ? <enter> + one_shot_shift
Shift . Enter ! <enter> + one_shot_shift
Shift Enter <enter> + one_shot_shift

Note: Opposite hand shift is used in the chording sequences.

The beauty here, like elsewhere, is that these again are real chords without any key order timing dependencies. The remaining layers contain similarly straight forward chording assignments..

symbols and regex

"type": "visual_array", "keys": ["BOT2", "BOT3", "TOP4", "BOT4", "TOP5", "BOT5"], "dictionary": [ ["X", "X", " ", " ", " ", " ", "STR(' <- ')"], [" ", " ", " ", "X", " ", "X", "STR(' -> ')"], [" ", " ", "X", " ", "X", " ", "STR(~/)"] ]

"type": "visual_array", "keys": ["THU1", "THU2"], "dictionary": [ ["X", "X", "STR(!=)"] ]

Georgi BEAKL Wi Symbols

chord output
J < ” <- “
> % ” -> “
Esc = !=
* & ~/

"type": "visual_array", "keys": ["TOP7", "TOP8"], "dictionary": [ ["X", "X", "STR(.*)"] ]

Georgi BEAKL Wi Regex

chord output
* [ .*

Togglable symbol layer..

Georgi BEAKL Wi Symbols Regex

numbers and fn keys

"type": "visual_array", "keys": ["TOP7", "BOT7", "TOP8", "BOT8"], "dictionary": [ ["X", " ", "X", " ", ":"], [" ", "X", " ", "X", ";"] ]

"type": "visual_array", "keys": ["BOT5", "THU5"], "dictionary": [ ["X", "X", "STR(0x)"] ]

Georgi BEAKL Wi Number Pad

chord output
/ 4 :
, 8 ;
G 0 0x

Georgi BEAKL Wi Fn Keys

shortcuts and cursor / mouse

Georgi BEAKL Wi Cursor
Navigation

chord output
XPaste Private Compile time string
Paste Public Compile time stringString

Georgi BEAKL Wi Mouse

steno

Georgi BEAKL Wi Steno

chording paradigm

when i purchased the Georgi i anticipated that it would take some amount of effort to migrate the bulk of the BEAKL Wi feature set to the Georgi with whichever chording engine i chose to use.

DennyTom’s chording engine facilitated the transition in short order.

Using DennyTom’s Georgi json file a template, the base layer took little time to define after which the remaining layers rapidly followed suit. Only the leader capitalization took a bit of time – the bulk of that exchanging messages with DennyTom for guidance. DennyTom’s reddit presence made for a quick one day turn around, after which, cloning and modifying the chording engine function he pointed me to was rote.

Some of the dynamic configurability of the 40% keyboard BEAKL Wi implementation remains to be done. These may be added via the external macro programming interface (see Georgi Wi).

rolling modifiers

a major difference between the chording engine and my 40% keyboard library is the handling of rolling tap hold modifiers, a problem which exists with the standard QMK library. The rules that should be applied on the activation of a tap hold modifier before the deactivation of the preceding key are complex, my particular use case being as much a function of typing technique.

It is early in the burn in cycle for this chorded BEAKL Wi layout and even earlier still for my finger familiarity with the Georgi keyboard. The significantly shorter travel and activation point of the Georgi’s Kailh Low Profile Choc keyswitches mitigate this issue somewhat – though typing technique can also make it worse due to the sensitivity of the keyswitches. By limiting the more responsive KM() macro** to the Shift (and slower pinkie modifier) and KK() to the remaining modifiers – compromising modifier responsiveness for better rolling home row keypresses – the unwanted rolling modifier can be limited to the Shift keys. By doing so, the remainder of the issue should be addressable with a minor adjustment to my rolling home row shifting technique (timing).

**Currently have reverted to the KK() macro and reducing the “chord_timeout” and “dance_timeout” intervals.

in closing

i like DennyTom’s chording engine. A LOT. And i am certain there are unexplored macros that will inspire some new keyboard tricks.

The chording paradigm is IMO a much more elegant mechanism than tap dance – and there is even a rudimentary tap dance macro available with this engine for those that must. Chording simply avoids the timing issues associated with such traditional QMK macros and allows for a much smoother and rhythmic typing experience IMO – not to mention all the easy to implement shortcuts it facilitates.

There are many macros available for the engine to meet a variety of use cases. Yet i have only needed the most common macros – a testament to the power of this chording engine. It is a very robust platform – and DennyTom is continuing to refine it.

When i finally source some 15g springs for my Splitography, i will implement a BEAKL layer identical to the Georgi, even though it can support a physically separate home row! to further explore chorded home rows.

But this chording engine need not be restricted to steno keyboards. It should be compatible with any QMK supported hardware. i highly recommend it even if only for defining a standard keyboard layout. You don’t need to know C language programming – though you do need to install a QMK environment for compiling and flashing your keyboard. All it takes is a bit of json file editing whose structure is fairly flat and a glance at DennyTom’s lucid documentation.

You’ll end up finding chording use cases creeping into your layout – their being so easy to add. i guarantee it :)

sources

BEAKL Wi

»»  georgi'ous

comment ?