Created
March 29, 2024 23:12
-
-
Save trentgill/43d1ad05c416e96dbafafb6e76294cd5 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
--- abstracted grid key mappings from functionality | |
-- demonstrate a simple string-based grid-layout system where each key is | |
-- addressed with a standard structure. direct handling for row and column | |
-- groups of keys that all call the same underlying function. | |
-- | |
-- concept is to have a generic grid handler function, and wrap all of the | |
-- script specific functionality into a simple table of locations and | |
-- matching functions that will be called when that key is pressed or | |
-- released. | |
-- | |
-- functions can instead be tables where the first element is the function to | |
-- be called, followed by an arguments that should be appended on call. this | |
-- allows for the same function to be shared among many keys and can make | |
-- the mapping explicit when it's not a linear range as per row or col. | |
-- | |
-- main benefits are separating key matching logic from script specific logic. | |
-- this is often intertwined and creates a giant function with endless if/else | |
-- chains that make the grid layout opaque & bugs hard to find. and the second | |
-- benefit is to enforce the idea that every action the grid articulates must | |
-- be wrapped in a function, ideally in a separate table's namespace for ease | |
-- of separation. this is particularly beneficial in a collaborative project | |
-- where the grid layout could be designed & implemented in parallel to the | |
-- underlying dsp/events. all that would need to be agreed upon beforehand is | |
-- the API that grid will call, and dsp/events will implement. | |
-- | |
-- side effect is that it basically stop you from having the grid key parser | |
-- capture any state. the only state you may want is to manage grid "paging" | |
-- where you can switch between different tables of mappings. if you do this | |
-- you still want to wrap that switching in a function! | |
local g = {} | |
local dj = | |
{is_mute = 0} | |
dj.mute = function(z) | |
dj.is_mute = 1 - dj.is_mute | |
print('mute = ' .. dj.is_mute) | |
end | |
dj.nudge = function(delta, z) | |
print('nudge by ' .. delta) | |
end | |
dj.fader = function(channel, y, z) | |
print('fader ' .. channel .. ': ' .. y .. ',' .. z) | |
end | |
-- must use the following formats | |
-- where X and Y are the numbers representing column and row of grid | |
-- origin (0,0) is in top left of grid | |
-- single keys: X,Y eg "0,0" "3,4" "15,2" | |
-- columns: xX eg "x12" "x0" | |
-- rows: yY eg "y3" "y10" | |
g.fns = | |
{["0,0"] = dj.mute | |
,["3,1"] = {dj.nudge, 1} | |
,["x2"] = {dj.fader, 2}} | |
-- helper fn to unpack varargs, call the first arg as fn & pass remainder as args | |
-- use it to call a "fn_table" aka table of a function along with n-args. | |
local function apply(f, ...) | |
return f(...) | |
end | |
local function maybe_fn_table(obj,...) | |
-- false signals the obj doesn't exist | |
if obj == nil then return false end | |
local typ = type(obj) | |
if typ == 'function' then | |
obj(...) | |
elseif typ == 'table' then | |
apply(unpack(obj),...) | |
end | |
-- true signals this mapping was eval'd | |
return true | |
end | |
-- lookup is *very* fast as it's a single hash-table address for each category | |
-- of single, row & column. should be at least as fast as a standard deeply | |
-- nested if/else chain, and likely 2-3x faster. | |
-- benefit is really the simplicity, and fact that the grid mappings are | |
-- declarative & non-code. it also greatly reduces chances of edge-case bugs | |
-- and typos in general, especially when managing deeply nested conditionals. | |
-- | |
-- plus it's all re-usable! copy & paste this whole file and provide g.fns at | |
-- runtime. could be called "autogrid" and has an init function that just | |
-- takes a grid object & a g.fns table. | |
-- later we could add a fancier init function that takes multiple g.fns tables | |
-- and expose an autogrid.x function that swithces the active layer. | |
function g.key(x,y,z) | |
-- TODO wrap multiples of the g.fns table if paging | |
-- must reproduce the switching mappings in both layouts | |
-- but they can both call the same underlying function | |
-- | |
if maybe_fn_table(g.fns[x .. ',' .. y],z) then return -- individual key | |
elseif maybe_fn_table(g.fns['x' .. x],y,z) then return -- column | |
elseif maybe_fn_table(g.fns['y' .. y],x,z) then return -- row | |
end | |
end | |
-- note that this is the input side of grid. i actually really the idea that the | |
-- grid presses & lights are managed by entirely different libraries. they are | |
-- fundamentally disconnected, and it could be nice not to think about the key | |
-- presses at all when programming the lighting |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment