Trigger different keystrokes based on rotation direction with TEK2 jog wheel

Hi all,
I am trying to set up the jogwheel of the TEK2 to move the playhead in Logic Pro forward/backwards, pretty much a DAW standard function. In Logic, this is done with the keys ‘.’ for forward movement and ‘,’ for reverse. I played around in the editor to trigger these two buttons depending on the direction of movement of the jogwheel, but am unable to figure out how to assign different keystrokes based on direction.

What I want to program is:
Jogwheel rotated right - send ‘.’ keystroke
Jogwheel rotated left - send ‘,’ keystroke

I found the variable endless_direction in the local variable block, but can’t find any documentation on what it puts out and whether this is the right one to query. Any help would be appreciated!

Heyas! I do not have a TEK2 controller but I do have Grids with endless encoders. The code may or may not be the same as I can’t see what ‘Tabs’ you have in the editor and I can’t find documentation for the endless_direction() function. The info @ likely hasn’t been updated yet.

Just to preface things, I am new with the Grid controllers and I, personally, find it a bit easier to work with straight LUA. I’m new to LUA and coding in general but it’s been pretty easy to pickup.

If you want to see what a particular action block is doing, you can check its box and click the ‘[…]’ ‘Merge to code’ button. I usually start with action blocks then convert all to code and make one monolithic ‘Code’ action block.

To achieve this using an endless encoder:

On the ‘Init’ tab for the encoder, add the ‘EC’ action to set the encoder mode. Set it to ‘1’ for ‘Relative BinOffset’:

Grid - Encoder Mode

Next, on the ‘Encoder’ tab (this might be labelled different as the jogwheel on the TEK2 is a new element in the Grid world), I use the following code in place of all action blocks:

local num, val = self:element_index(), self:encoder_value()
if val == 63 then
	keyboard_send(25, 0, 2, 99)
elseif val == 65 then
	keyboard_send(25, 0, 2, 54)

When using EC mode 1 for Relative BinOffset, the value of self:encoder_value() is 63 when turning left and 65 when turning right (I believe it returns to a value of 64 when not being turned as well). So what the code above is saying is:

IF ‘val’ (which is the value returned from self:encoder_value() which is determined by the direction you’re turning) is equal to 63 then send out the following keyboard command. Or ELSE IF ‘val’ is equal to 65 then send out the following keyboard command. Otherwise, do nothing at all.

The parameters for keyboard_send() are set when you use a ‘Keyboard’ action block:
Grid - Keyboard Action Block

Again, I’m not sure it the jogwheel on the TEK2 is a different element than an encoder (they’re fundamentally the same), so you may need functions that address the jogwheel specifically.

My code above works to send out ‘.’ on left turn and ‘,’ on right turn (tested in Notepad on Windows). I think those are in reverse of what you need.

Let me know if that points you in the right direction. If the jogwheel does have specific functions to address its values/behaviors then, the devs will need to jump in. The docs don’t appear to be updated yet.

1 Like

To add to this, you could determine the value of endless_direction() by printing its value:

local e_dir = endless_direction()

You would see the output in the debug window (Lady Bug icon on the bottom left):

If it is indeed sending one value on the left turn and another value on the right, you can use my code snippet above with the appropriate modifications (which I could assist with if need be).

EDIT: Can’t use ‘dir’ as a variable name as this is the shortform of the endless_direction() function. Updated to a unique variable name.

1 Like


Hey @_d4ydream , many many thanks for the quick reply and the detailed help. While the jogwheels turn out to behave somewhat differently (at least as far as I understand them after a few hours), your post still pushed me in the right direction. Details below. Also if someone from Intech wants to weigh in that would be swell, since I am also just trying to understand things from the debugging console, maybe there is a much easier solution.

For others running into the same issue - I published my unpolished profile for basic control of Logic Pro X with a TEK2 here in case it helps ‘TEK2 Logic Pro X Basic controls’ grid-editor://?config-link=i4A9nBOAx9o1N6mC6RMV

The jogwheels have two meaningful readouts of their current value, that is
endless_value, which goes from 0-16k when uncapped (highest sensitivity), or otherwise to the value determined in endless_max(), which will I think decrease the number of total steps and sensitivity (?). At the upper limit, further wheel turning does not increase the value anymore (same is true for the lower limit and left turns).
endless_direction goes from 0 to 175 (ish) and then rolls over back to 0 when turned further right, and rolls from 0 to 175 when turned left. So in itself it’s also not a direct measurement of direction
endless_velocity exists but is always 0, so likely an unused function

My solution:
I cap endless_max() at say 200, and reset endless_value() to 100 (the midpoint) after each triggering of the script (turn of the jogwheel).
Within the script, I read the current endless_value, determine whether it is above or below 100, and thus get the direction and velocity of the current jogwheel turn. (@_d4ydream setting the Encoder Mode in the Encoder tab as you suggested as with the regular encoders did not change jogwheel outputs)

local cur_val = self:endless_value()
local dir_vel = cur_val - 100;
local abs_dir_vel = math.abs(dir_vel)
print('direction', dir_vel, 'velocity', abs_dir_vel)

Then I trigger keystrokes based on direction of the jogwheel move.

Getting the keystrokes to register correctly (without being too laggy or overwhelming keyboard output) and to make control of the playhead in Logic Pro X smooth was harder than I thought, had to troubleshoot this for quite some time. I should also mention I tried to get fancy and change the speed of the keystroke repeats based on turn velocity, but this was very unreliable and felt wonky.

Anyways, this is my very preliminary solution after spending a few hours with the grid editor as a first time user, I’d be delighted to hear better solutions. And thanks again, @_d4ydream

1 Like

I just realized that I can use the Grid Editor in virtual mode. I can now see and program a TEK2. I just can’t test anything as there’s no way, that I can see, to interact with the elements virtually.

I think this can be overcome, or at least tweaked, using the self:endless_velocity() function on the ‘Init’ tab of the ‘Endless’ element that you are programming. For an encoder, the default velocity is ‘50’.

Try using a ‘code’ block on the ‘Init’ tab with the following:


This should set the velocity to ‘50’. It expects an integer between 0 and 100. I imagine lower values would clamp down on the velocity. There shouldn’t be any need to reference this elsewhere in your element programming. You’d simply be setting the velocity value.

For this, limiting the range is good if you need to constrain the output to what Logic is expecting. This would have no impact on how many events are being generated for a turn. It would just hit the min/max values at the same velocity.

Play with the ‘Init’ self:endless_velocity(50) function parameter value and see if that impacts velocity how you expect it to.

As an aside, I wonder if Intech would setup a similar ‘Init’ action block that let’s you set the endless type similar to how encoders can be set; Absolute, Relative, Relative BinOffset.

Button elements also have this ‘type’ action block.

The Relative BinOffset mode for endless would allow my original snippet to work. It has a parameter for velocity within the block as well.

@kertikristof do you know if there’s plans for an ‘Init’ action block for the ‘endless’ element so that you can set its type? Relative BinOffset would be quite helpful for the above scenario.

I wonder if the endless_direction() function is what was implemented so that a ‘Relative BinOffset’ mode isn’t needed. I imagine it returns one of two values; one for left and one for right.

Using those values you could modify my original snippet to something like this:

local num, e_dir = self:element_index(), self:endless_direction()
if e_dir == 63 then
	keyboard_send(25, 0, 2, 99)
elseif e_dir == 65 then
	keyboard_send(25, 0, 2, 54)

You would replace the 63 and the 65 with the corresponding values generated by endless_direction()

You could determine those values with:


Just look at the debug output to see what it’s sending. You should see it in the top box. Note that the [0,0] isn’t the actual value. That’s just the grid position of the module generating the value.

Hey, no this behavior is actually different. in uncapped mode, the 0-16k range maps to roughly two full turns of the wheel. When I cap to 300, the 0-300 range is roughly two full turns as well. So the capping basically acts as binning, and each tick of the wheel moves the value up/down by less, which makes the whole thing less noisy.

Edit- not super important, but it’s actually one turn, not two to go over the full range

1 Like

This is good to know! I would think that you would need to set velocity so that ‘value’ isn’t updated as frequently.

I should have maybe highlighted this more clearly in my earlier response, but I tried both your recommended encoder settings in init as well as played around with 0% and 100% Encoder Velocity and all different Encoder Modes. None of these settings affected the jogwheel (neither the max values nor the step size, nor the relationship between jogwheel rotation and value increase). It seems like the Encoder Mode code block is not controlling the jogwheels.

1 Like

The encoder functions may not work but I imagine self:endless_velocity() would have an impact as would self:endless_direction().

Though, I’m assuming those relate to the jog wheel is it’s the only new element and function naming convention that I can see. :thinking:

1 Like

Yup. I thought that might be the case. The encoder named functions only apply to actual encoders. It was worth a shot. The logic would still apply but there’s, hopefully, some jog wheel specific functions that can do similar/same.

1 Like

Sorry I misread your post there, confused endless and encoder. I checked and set endless_velocity as well. It’s by default at 0 and changes to 50 or 100 do not change anything that I can detect. But again, there is a good chance that I am just not seeing this right.

Well that was the function I was going for at first as well because of that obvious name, but it’s not a directionality output as I mentioned, or at least I can’t make a connection in my brain to how to use it as such robustly. It is a function that linearly increases with jogwheel rotation to the right, and that resets back to 0 when reaching the value 175. And in my opinion that roll-over is not robust for calculating directionality, because for edge cases of fast jogwheel turning, you can’t tell if you moved fast to the right or the left from the value of endless_direction(). It’s a Nuclear Gandhi scenario :slight_smile:
So it seems much more robust to me to work with the strictly linearly increasing endless_value(), and reset it periodically and calculate the difference at the next trigger. It seems to work fine and is two lines of basic code.

1 Like

@hgo for keyboard triggers the default delay variable can make a ton of difference. It’s a rather large value by default, but based on most our tests on computers no older than 5 years, 5-6ms is doable as well. In that case zoom / nudge / navigate keybindings can work more promptly.

The endless rotaries on TEK2 only use the functions starting with endless_, the encoder related functions do not work.