Skip to content

Instantly share code, notes, and snippets.

@trentgill
Created March 29, 2024 23:12
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save trentgill/43d1ad05c416e96dbafafb6e76294cd5 to your computer and use it in GitHub Desktop.
Save trentgill/43d1ad05c416e96dbafafb6e76294cd5 to your computer and use it in GitHub Desktop.
--- 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