Close
0%
0%

AirBerries and SpaceExplorer

I should start using my £80 split keyboard and £40 spacemouse more than my £32 keyboard and £16 mouse.

Similar projects worth following
This is a log of my process learning how to use my "Let's Split" keyboard with either Taipo Posh or my ZEV-XS layout (instead of QWERTY) and the SpaceExplorer as a pointing device (like a mouse / trackball).

Navigation

Prior Reading

Contents

The title tag system is explained here, and the table is updated when a change occurs. Notable logs have bold L# text.

L1
[T] Reasoning
- Essentially the preface for this project
L2
[X] Setting up Taipo-enabled firmware
L3
[X] Set-up success! Starting to learn Taipo.
L4
[T] 2D Spacemouse strategy
L5
[A] Introducing 3DxPoint: The 3D Trackpoint
L6
[C] 3DxPoint: Sounds and Shortcuts
L7
[C] 3DxPoint: ButtonRing improvements and BounceBack fail
L8
[C] 3DxPoint: Absolute Movement Zone
L9
[A] 3DxPoint now on Github
L10
[C] Made ButtonRing diagonals consistent with cardinal directions
L11
[C] Improved mouse smoothness

  • [E1][C] Improved mouse smoothness

    kelvinA11/28/2023 at 00:06 0 comments

    This suprisingly took even more effort than the ButtonRing and I got a lot more help from BingChat this time around. Essentially, I wanted to replace the simple method with one that waited until it was sure both the real and imaginary components were accounted for. This is the plan I wrote just to have an idea on what to do:

    I then asked BingChat many C++ questions, such as 

    and also learned things that I didn't even know existed, such as

    I then started deriving equations in Desmos:

    This one is for velocity speed limits. There also needed to be a "slow movement zone".

    To get that slow movement zone, I needed to store the fractional part of the number I sent to SendMouse so that it could be carried forward.

    Eventually, I had to create a massive wall of logs to debug:

    This is one of the BingChat functions, and there wasn't any checks for m == 0.

    I still had the issue with overshooting, so I had the idea to implement a bell curve that would multiply the output by considering the %-changed of the magnitude. Didn't help:

    I went in, fixed a bug with the %-change calculation, and tried many different curves, namely those that actually went negative when the %-change was close to -100% as it would actually cause the mouse to bounce back slightly, mirroring the actual feeling on the spacemouse.

    Red = Bell, Green = Linear, Purple = Cos and Blue = Exponent.

    I also tried a negative bell curve on positive %-change, but since I was often getting things like +1400% when starting off, it meant that the cursor started going backwards before going forwards. For medium-large moves, this was beneficial as it gave me just a bit more runway, but was counter-intuitive when I was moving inside the slow-zone.

    The finishing touch I've just added now are some lines so that the bounceback feature actually bounces back in the same direction it came in:


    Before this change, I'd often have situations like this:

    This is because when the spacemouse closer to the centre, it's more likely to magnetise onto a cardinal direction. Here's a gif that tries to show what it looks like now:
    The slight ghosting of the GIF is probably a feature and not a bug in this case.

    [9 Dec] I've mostly been using the below displacement multiplier curves (before admitting defeat and trying a Logitech G300s mouse):

    Desmos: https://www.desmos.com/calculator/fr7bwyuxqr

    For the multiplier when decelerating (-1 <= x <= 0), I just tried the 4 combinations of the 2 curves and got the most points in the accuracy test with the above configuration. 

    For accelerating, my reasoning was that I'd either be coasting (so near 0) or wanting to accelerate (usually over 14), thus values in the middle should be supressed as I'm probably just transitioning from one to the other. In practice, looking at the logs, it seems that change-in-magnitude readings are something like 20 -> 8 -> 2 -> 1.5 -> 0.8 -> 0.2..., so don't seem to be many values around in the suppression region anyway.

    If I could just get the ergonomics of the SpaceExplorer with the speed and precision of the G300s, that would be great. Like the opposite version of the Wing mouse: 

  • [C] Made ButtonRing diagonals consistent with cardinal directions

    kelvinA11/19/2023 at 19:59 0 comments

    Something I noticed when using the ButtonRing is that it seemed that less force was needed to press. This ment that I often activated the Diagonal Edge button instead of the Outer Diagonal button and it would take 4 - 5 attempts to get it right. 

    The reason is because diagonal magnitudes are able to be larger than cardinals.

    When I first encountered the issue, I considered doing something inside the angle-checking if-blocks themselves, but I thought that would just result in a cliff-edge response between the main cardinal directions and the diagonal directions.

    A few hours ago, I got the idea to have a magnitude multiplier that would scale back these larger values, and it didn't take long to find (via image search) a polar equation that did what I wanted to do (with a few tweaks):

    I went into desmos to experiment. I plugged in real values so that I could work to a more general calculation.

    This is the equation I want. The clover-like shape is the magnitude multiplier and the circle is the minimum of this multiplier which should be exactly on the diagonals. I tweaked the values so that the maximum for this function was 1.

    I was able to discover a general equation to make sure that the function was less than or equal to 1, and then I rearanged the inner circle equation to make the minimum value of the function the subject.

    I feel like the implementation is a bit crude since I have to remember to multiply currentMagnitude, prevMagnitude and SpacePoint.ButtonEvent, but if I try and do this multiplier filtering on the input values as they come in, I belive SpacePoint.ButtonRing would decay to 0. Remember, 3DxPoint just gets handed a new axis value and, at that point in time, doesn't know what angle the spacemouse is at.

    Anyway, after once again being thrown under the bus temporarily because C++ divided 2 ints and probably injected a 0 into my double variable (see below for correction), I got the fix to work.

    I've only tried it for a couple minutes but it already feels like the ButtonRing is more true to my intent. I'm now also considering if I need to use this for cursor translation too.

  • [E1][A] 3DxPoint now on Github

    kelvinA11/10/2023 at 18:52 0 comments

    https://github.com/Glodigit/3DxPoint

    Decided to just upload what I've got, since it's probably 80-85% the way there anyway. As the saying goes, 80% of the work is in that last 20%, and I'd rather not delay #Coaxial Hotend [gd0144] and #Tetrescent [gd0150] much longer.

    As an ergonomic pointing device to lightly browse the web, it's an OK solution (mostly because scrolling is excellent), but as I don't have even optical-square levels of ability and the ButtonRing still isn't as reliable as I'd like:

    The solution... 
    fails.

    [18 Nov

    The good news (or bad news, depending how you look at it) is that the SpaceExplorer feels more comfortable to me than my almost-4-year-old mouse, and when I went to use said mouse (to get more speed/accuracy data), it felt somewhat alien, as if I haven't touched a mouse in months or something. My first thought was "I tolerated this?" (namely things like how it's only designed for my right hand or the surface it moves on needs to be a certain way).

    Thus, I'm considering if I should continue to work on 3DxPoint to increase speed and accuracy moving the cursor and more reliabilty with the ButtonRing, or just get a "Used, Like New" Logitech G300S mouse and move on.

  • [C] 3DxPoint: Absolute Movement Zone

    kelvinA11/10/2023 at 16:15 0 comments

    2 days ago, whilst thinking of possible ways to obtain better precision and accuracy, I got the idea to have a small part of the range used for "absolute movement". So, instead of a trackpoint, it'll act more like an optical sensor seen on small netbooks like the OneGX1. 

    As I expected, I wasn't using anywhere near the full range of motion for moving the cursor. The first thing to try was to just send the 1 : 1 position of the spacemouse to the cursor, which is just taking away the previous mouse position from the current one, thus only the difference is sent. This produces a cursor that looks like it's trapped in a gravity well, and is rather jittery:

    To turn this into something useful, I don't send the moments when the spacemouse moves closer to the center. Additionally, I send the usual movements when outside the "absolute zone". It looks like the mouse is running at stop-motion speeds, but it does work to increase accuracy and precison when the cursor is near a target. This was one of the major issues with the traditional method.

    Unfortunately, things weren't as glamarous when I tested it out. 

    For starters, my best speed for White TIle, Don't Touch It was 50.499 seconds (my best with the traditional movement was 45.07). I can certainly feel that getting the cursor from place to place is slower than before.

    My accuracy and precision have certainly improved, looking at these results:

    However, I decided to also compare with the small optical sensor that's on Q3ti, my OEM 8" N100 Laptop from AliExpress. I believe it's the same thing as the one on the OneGX1. My results show that I'm still a long way off from a mouse replacement, though it also gave me the impression that I'm better on this claimed "7.2 * 7.2mm square" than on a trackpad.

    Right now, I'm wondering if I should implement a look-ahead buffer and positional averaging to make the cursor appear smoother and reduce the chance of overshoot, or to just cut my losses, upload what I've already got and then buy a traditional mouse. I have just recently split the monolithic .cpp up a bit, so it may be a good time to publish:

    Right now, 3DxPoint would be around about the "media center control" level of usability.

  • [C] 3DxPoint: ButtonRing improvements and BounceBack fail

    kelvinA11/07/2023 at 23:53 0 comments

    Something that I noticed was that I was overshooting or undershooting almost all the time. Thus, I got some quantatative measurements:

    Me on SpaceExplorer
    Me on touchscreen

    I found the game White Tile, Don't Tap It and it was a more forgiving way to test things out. The top 3 results were from touch, the next 3 were from my half-a-heart-durability mouse and the bottom two were from my SpaceExplorer. The "score" is actually the time, in seconds, it took to clear all the black tiles. Lower is better.

    I will admit that the SpaceExplorer does feel more comfortable than my "gaming" mouse.

    I quickly wrote some code to implement a "bounce back" feature, where if it was detected that I was slowing down, it'll invert the values. While it did do what I said it should do, it was like a mime hitting an invisible wall; cool to see, but I couldn't get anywhere. I found out that trackpoints have a feature called "negative inertia" that I'll have to look into.

    I was also getting a few missclicks, and I suspected it had something to do with the of-chance the SpaceExplorer sends values like 90 -> 100 -> 96 -> 110 -> 115..., meaning that it triggered earlier than it should. The solution was to take the max from the previous 2 values, and this is one of the results:

    I also took this time to fine-tweak the thresholds. The SpaceExplorer is somewhat non-ideal, since it's not stiff enough to make the force->value a linear thing. It's displacement->value, which sometimes means that the force reqired to displace the spacemouse is higher or lower (depending on things like grip, angle of the SpaceExplorer, etc).

    I also need to add some kind of legacy scroll mode, as there are a small handful of applications that don't behave well with ultra-precision scroll.

  • [C] 3DxPoint: Sounds and Shortcuts

    kelvinA11/07/2023 at 23:31 0 comments

    [C] - Coding

    I mainly worked on improving the ButtonRing and getting it to do things outside just mouse click events.

    Sounds for click events

    I was hoping that I could just piggy-back off a Windows sound, but I couldn't find anything suitable in windows/media/. Thus, I looked to see if there was anything that could be generated, and there's a function called Beep().

    So I found this tone generator and started trying out some notes, and decided to go with

    • C4 = Button Up
    • D4 = Button Down
    • E4 = Outer Button Down
    • F4 = Button Edge Down

    Yes. I had added a 3rd ring of buttons, which I'm calling the ButtonRing edge.

    At first, I implemented the 100ms beeps to play on the onEntry event, but I wondered if it'll be better to get more of a live feedback and instead play the note when a threshold has been passed:

    I also found a convenient place that doesn't require elevated access to use:

    I've moved the log.txt to this location. This is also where I'd store the audio files.

    Audio files?

    Well, unfortunately, Beep() will freeze the mouse until it's made it's note. I tried a few async methods I found between BingChat and StackOverflow, but it didn't seem like the beep was being executed from a new thread. Additionally, my speakers made some kind of pop sound after Beep().

    The easiest and simplest way I knew was to just go into AIR Ignite 1.3.1, pick a good (and somewhat luxurious) instrument (to complement the SpaceExplorer), and these were the 2 that I initially chose:

    Spiccato sounds quite grand and posh, wheras Pizzicato sounds sharp-starting (which is the kind of sound I had in my mind for something that's supposed to represent a click event). I then made some test runs so that I could hear how it would ideally be like in practice:

    Welllllll unfortunately, the PlaySound() command I used, while is asynchronous and doesn't freeze the cursor, cannot play more than one sound at a time. Research suggests I'd apparently need to be doing some complex stuff for that. Thus, Pizzicato sounded much less annoying, as it didn't sound like it was being cut off. 

    Along with that, I reduced the volume of the output notes so that they were less distracting / drone-sounding, as well as turning the "brightness" of the instrument down to 0 for the C3 note (as for some reason, it's C/D/E/G3 in Ignite, not C/D/E/G4) which removes that sharp start to the note. I think it makes sense since it represents a return to 0, not a click event. G is louder than the other ones as I thought it should sound like more power went into the click.

    I've set up PlaySound() so that it doesn't play anything if the file can't be found, so a user could only use D/E/G and not put C into the folder (as, without headphones, it does sound like those notes just cut off midway). Later on, I also added a boolean so that the "play note on trigger" functionality can be selected (which is what I'm using now, again due to the PlaySound() limitation).

    I've been using it for a few days, and the sound effects remind me of some kind of media-player interface, like Windows Media Center or the Xbox 360. Considering I've got as much control as an airmouse, it sounds accurate, at least.

    Shortcuts

    So I looked at the documentation for INPUT_KEYBOARD from Microsoft, and they actually had a sample that implements a keyboard shortcut:

    It took less than 50 seconds to decide that there was too much boilerplate present, and I elegantly put everything into macros:

    I'd then find out a few things. Firstly, the [ ` ~ ] key on US keyboards is the [ ` ¬ ] key on UK keyboards. Even though the [ ` ] is the same on both, Windows treats them differently, so they've got to be different virtual keys. Secondly, only the ASCII alphanumerics map directly to their virtual key code counterparts. 

    Thirdly, that for-each loop strat I tried doesn't work. I have to do the old fashoned 

    for (int i = 0; i < ARRAYSIZE(k);...
    Read more »

  • [A] Introducing 3DxPoint: The 3D Trackpoint

    kelvinA11/03/2023 at 23:58 2 comments

    I'll be honest. I knew this was going to take a while to do, and so I avoided doing it until my £16 mouse finally actually stopped working. Like, it technically works, but the cable had to be within a specific angle or else it'll disconnect. Well that angle has been getting smaller and smaller over the passing months and, a week ago, this angle was too small to do anything meaningful. I can't model #Coaxial Hotend [gd0144] with the Windows 10 touchscreen trackpad, so I really had to do something and do it now.

    Since then, I've spent about 36 combined hours to develop the 3DxPoint.dll that implements all the features I mentioned in the previous log, and other than some refinements that I'll mention at the end, I've got a working minimum viable product.

    Getting a new Visual Studio project set up

    All the way back in April, i got an email from jwick (mod on the 3Dconnexion fourms) which included the source code for a modified 3DxSample.dll that was mainly to get mouse button presses on an Axis instead of a Button. He mentioned that useful information about the true capabilities of the 3DxWare driver is in a beta forum, but it seems pedestrian users like myself don't have access to that.

    So, the first order of business was to rename the project to a more fitting name, and I thought of 3DxPoint, short for "3DxWare X Trackpoint", since the control should be very similar to a massively enlarged trackpoint.

    Now, it seems that just renaming a few things in Visual Studio to "3DxPoint" wasn't going to work, so I created a brand new DLL project and deleted the automatically created files to bring in the new ones. And then I got this complaint:

    Well, as I soon found out, those files I deleted were more or less the same thing as the 3DxSample files but with a new name (I guess thing's have changed since 2015, which was the datestamp of those files), so I backtracked.

    Obviously, stuff was still broken, mainly to do with pch.cpp. This answer solved it:

    I got things to compile, but I had warnings:

    I was looking at the code and it didn't look like that was possible for fp == 0, but I didn't want to assume things that could be the cause of bugs. Well then I found this, showing that it is indeed possible:

    I just tacked "&& fp != NULL" inside all the if statements.

    Getting 3DxWare 10.6.4 to run a DLL

    A hurdle I hit very quickly was that, like 3DxSample.dll, 3DxPoint.dll didn't seem to be doing anything. There's an init function that writes to a fill every time it's called, and nothing was being written. 

    After doing some trial and error, I decided to reread the instructions slowly and carefully:

    1) Save it into %ProgramFiles%/3Dconnexion/3DxWare/3DxWinCore/Win64/DLLs/.  

    I kind-of just assumed that the DLL went in the same directory as the XML that used it. I only have a "3DxWinCore64" folder, so I used that instead.

    2) Add these AxisActions to an app-specific cfg, or Global.xml

    Now, I thought that Desktop.xml counted, but it actually doesn't. This is what happened.

    I added a ButtonAction in Global.xml.

    I restarted the driver and looked at 3DxPoint.txt:

    I audiably ghasped:

    The AxisAction that I defined in Desktop.xml but not Global.xml magically started working:

    Initial testing and understanding

    One of the frist things I found out was how the deadband affected the readings. This is a deadband of 40 compared to a deadband of 0:

    This also tells me that the absolute max value produced is most likely 350. 

    I also found out that the ButtonAction set in Global.xml doesn't actually need to be used for anything in the same xml for the 3DxPoint.dll to still work for Desktop.xml.

    Code cleanup and consolidation

    All these block-if's to deal with logging were making things a bit cluttered and it was all boilerplate anyway, so I turned some parts into macros:

    I did some research and changed the first...

    Read more »

  • [T] 2D Spacemouse strategy

    kelvinA10/27/2023 at 00:38 1 comment

    What I'm going to try to implement is as follows:


    • Left / right / forward / backwards translations controls the cursor position.
    • Height controls a cursor speed multiplier. 
      • When translated, the cursor moves faster or slower dependent on how much the cylinder is lifted / lowered respectively.
    • Rotate (like a dial) to scroll. Specifically, "ultra-high precision scroll" such as the scrolling from a Windows precision touchpad, not the dedent-like default scroll.
      • Looking at the Windows documentation, I believe RAWMOUSE would achieve this.
    • Tilt backwards = left click and tilt forward = right click. 
      • Ideally, this would be implemented as an angle and magnitude so that more mouse/keyboard events can be added in the future. For example:
        • Right-button-down event when -20 < Tilt_Angle <= 20 AND Tilt_Magnitude > 200 AND another virtual tilt button isn’t already active
        • Right-button-up event when Tilt_Magnitude <= 200

  • [X] Set-up success! Starting to learn Taipo.

    kelvinA10/20/2023 at 10:24 0 comments

    I wanted to copy-paste all of dilp's work, since it seems that he's stopped making commits specifically for Taipo. After looking around to see if I could just download a single folder in a repo, I somehow found my way into opening a PR for my own repo:

    It actually worked to pull in what I wanted.

    Unfortunately, RGBLIGHT_ENABLE = yes still doesn't work, but I found out that reducing Taipo_tap_timeout from the default 150 to 50 made things MUCH more responsive:

    I started looking in the only parts of code that would likely conflict with something RGB related:
    From my understanding:
    1. TP_TLP's numerical keycode is the smallest numerical keycode, so var key is a number between 0 and 19
    2. The state to work on is then determined, as key / 10 would truncate to int 0 if key is less than 10 and truncate to int 1 if over.
    3. In dilp's actual layouts, the only things on _TAIPO are TP_ keys and KC_NO, so line 1181 probably deals with that.
    4. Not sure exactly what the rest of the first function is doing
    5. the second function seems to check if both the state timer is non-0 and passed the time allocated...

    and that's about the extent of my understanding. I'm sure the code is dealing with things like holding the ' y ' combo being able to result in "yyyyyyyyyyy" sent to the host. I'm just trying to understand what could be the issue with RGBs and if I could implement something like TAIPO_PREFER_LEFT and TAIPO_PREFER_RIGHT so that it's possible to choose between a Keyboard Cat--esque typing style and a stenography-esque style.

    I also tried to add unicode characters to my layout, but I got unexpected behaviour:

    This was also the time when I realised that there are keys in the layout that expect both thumb buttons to be pressed, for which I can't hit reliably (likely due to my keycap shape):
    Also, it seems that ' , ' and ' . ' are swapped from what the docs say.
    The solution was to comment out the "both" define and redefine it to map to one of the "pinky" buttons.

    Finally, I could now start practicing in Ngram Type:

    The Average WPM is wrong. It's supposed to be sub 50.

    I started trying to alternate all the time, but I found it was easier to understand the finger movements if I just did one round with only my left and one round with only my right hand. Then I wanted to look ahead and see if there'd be any issues with my current setup before I hardcoded my neural network into a corner.

    And there was. There are also parts where the fingers need to hit both keys. I figured out how to angle my fingers in the gap to get both keys to reliably depress, and it's probably a good thing I've got those light Gateron Clears installed. However, I had to increase the tap timeout. I was also having issues with things like ctrl + s, and while timeout = 300 solved it, it caused keys to merge with the OuterThumb, which will capitalize the letter instead of add a space:
    I got the problem to mostly go away at Timeout = 108 whilst still being able to CTRL + S with 1 hand:
    In terms of practice, I'd've spent around 3 hours. All this happened yesterday+day before and I'm typing the log today.

  • [X] Setting up Taipo-enabled firmware

    kelvinA09/16/2023 at 14:25 0 comments

    [16 Sep]

    I was thinking that I was going to set this up and then make a log about it, but it's taking a bit of time so I thought a live-running log format would be better.

    So far, I've tried to implement Taipo.c from a QMK source instead of a QMK->Vial fork and made everything barebones and turn things on, one step at a time. 

    It seems that Taipo and my keyboard works when RGBLIGHT_ENABLE = no in my rules.mk, and fails when set to yes, even without anything RGB related in the keymap.c. Unfortunately, turning rgblight off in my vial config doesn't fix the unresponsiveness issue.

    Most recent commit.

View all 11 project logs

Enjoy this project?

Share

Discussions

Similar Projects

Does this project spark your interest?

Become a member to follow this project and never miss any updates