Technical Accounting
Spent the last couple of weeks tending to "technical debt" in the D-Lev software, particularly the user interface sections that service the encoders and push buttons. Software features that are the last to go in tend to be "hackier" than the rest of the code base because they don't have the virtue of being reexamined as many times and thus better integrated into the whole. The user interface is on thread 7, which is particularly tricky to work on because it has real-time interrupt service routine (ISR) code running at the 48kHz audio rate (or some fraction of it) as well as non-real-time duties, and these form separate "clocking domains" as it were, so transferring data between them requires extra thought and care in the form of atomic reads / writes and other techniques. Since the interface itself is being operated on, bugs tend to derail the very mechanisms that convey error information and other health indicators.
A scary proportion of the activity boils down renaming variables and subroutines to bring clarity and uniformity to the nomenclature. Changes here tend to bring new insight into the operation, so it's not just a cosmetic effort but a back and forth with real implications. The saying goes "when code is written only the coder and god understand it; six months later only god" so unambiguously naming things is a gift to future you.
Anyway, one section that's been slowly evolving is the parallelism of the encoder section. It initially started life as fully parallel and able to track the user twisting all of the knobs at once - which as it "turns" out never actually happens. Because we only have two hands only two encoders at most would be changing simultaneously - and even that never really happens in practice, though there are mitigations we can implement to mask the fact that all of the encoders now share a single velocity speed-up. The old code processed each encoder change sequentially at a 48kHz / 10 rate, which is entirely adequate to catch single detent changes even if the encoder is equipped with a small diameter knob and spun as fast as possible, where the switching rate can approach 1kHz. The new code only addresses changes to the most significant encoder at a 48kHz / 8 rate, which might seem like a single point of failure, but the FPGA firmware contains a state machine for each encoder, so the only changes that make it to the processor register are full detent events.
Sharing the velocity code among all of the encoders is only potentially problematic when quickly switching from one encoder to another - you don't want a velocity "hangover" transferring from one to the other, so we zero out the velocity when a switch is detected. The velocity should be similarly zeroed out when changing the direction of a single encoder. As before, the encoder twist delta is transferred from the ISR domain to the non-real-time domain via modulo subtraction - delta equals the old value subtracted from the new.
The push buttons integrated into the encoders are similarly addressed in a most significant precedence manner, because the user will likely never press more than one at a time. And even if they do, single point of failure is avoided by only tracking press events of the push buttons, with the tracking for all refreshed each time the code implements the specific button press event. So only in the extremely unlikely scenarios where two or more buttons are pressed within 167 microseconds (8 / 48kHz) of each other could a button press event be missed.
The GPIO port is dealt with in a parallel fashion, so nothing can be missed there. I took the opportunity to implement two new inputs here, grounding one input increments the preset, grounding another decrements it. So the four inputs are now: ACAL, Mute, Pre++, and Pre--, which provides for a fairly full-featured footswitch arrangement. For more advanced remote control, one could implement a footswitch that talks to the serial port, where the sky is the limit as to what is possible.
Speaking of which, the serial command "wk" that writes to the knobs has been reworked so that only the knob being written to is updated system-wide. Previously all of the knobs were updated every time a single knob was written to, which introduced a noticeable lag when performing editor commands that write to multiple knobs, such as the "morph" feature. To further facilitate this, the "wk" command doesn't update the knob values on the LCD, a new "lcd" command has been provided to do this explicitly, and would normally be invoked at the end of a string of one or more "wk" commands in order to speed things up.
All of the knob and push button code now pretty much run a single gauntlet of actions, which has simplified and clarified the functionality, and as a side benefit has freed-up some memory, so I might be able to experiment with my "DC scanning" technique proposed a while back, where the encoder wiring is condensed and all of the debouncing and decoding and stuff could happen purely in software, and ideally by a real processor rather than a soft processor instantiated in an FPGA.