Refactor x-touch script, basic AgX support

Refactored x-touch script to improve structure and maintainability. Mainly by adding helper functions for handling different module types.
the added support for AgX module
(credit goes to claude.ai which helped a lot )
This commit is contained in:
Martin Straeten
2025-11-06 19:19:46 +01:00
committed by GitHub
parent 447980ddc4
commit 056f616283

View File

@@ -59,150 +59,266 @@ local du = require "lib/dtutils"
du.check_min_api_version("9.2.0", "x-touch")
local gettext = dt.gettext.gettext
local function _(msgid)
return gettext(msgid)
local function _(msgid)
return gettext(msgid)
end
-- return data structure for script_manager
-- Constants
local NUM_KNOBS = 8
local NAN = 0/0
local script_data = {}
script_data.metadata = {
name = _("x-touch"),
purpose = _("example of how to control an x-touch midi device"),
author = "Diederik ter Rahe",
help = "https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/examples/x-touch"
-- Configuration tables
local MASK_PROPERTIES = {
"opacity", "size", "feather", "hardness",
"rotation", "curvature", "compression"
}
script_data.destroy = nil -- function to destory the script
script_data.destroy_method = nil -- set to hide for libs since we can't destroy them commpletely yet
script_data.restart = nil -- how to restart the (lib) script after it's been hidden - i.e. make it visible again
script_data.show = nil -- only required for libs since the destroy_method only hides them
local COLOR_ELEMENTS = {
colorzones = {
"red", "orange", "yellow", "green",
"aqua", "blue", "purple", "magenta"
},
colorequal = {
"red", "orange", "yellow", "green",
"cyan", "blue", "lavender", "magenta"
}
}
-- set up 8 mimic sliders with the same function
for k = 1,8 do
dt.gui.mimic("slider", "knob ".. k,
function(action, element, effect, size)
-- take the number from the mimic name
local k = tonumber(action:sub(-1))
local MODULE_CONFIGS = {
sigmoid = {
"red attenuation", "red rotation",
"green attenuation", "green rotation",
"blue attenuation", "blue rotation",
"recover purity"
},
primaries = {
"red hue", "red purity", "green hue", "green purity",
"blue hue", "blue purity", "tint hue", "tint purity"
},
agx = {
"white relative exposure",
"black relative exposure",
"curve/pivot input shift",
"curve/pivot target output",
"curve/contrast",
"curve/toe power",
"curve/shoulder power",
"curve/curve y gamma"
}
}
-- only operate in darkroom; return NAN otherwise
if dt.gui.current_view() ~= dt.gui.views.darkroom then
return 0/0
end
local DEFAULT_MAPPINGS = {
"iop/exposure/exposure",
"iop/filmicrgb/white relative exposure",
"iop/filmicrgb/black relative exposure",
"iop/filmicrgb/contrast",
"iop/crop/left",
"iop/crop/right",
"iop/crop/top",
"iop/crop/bottom"
}
local maskval = 0/0
if k < 8 then
-- first try if the mask slider at that position is active
local s = { "opacity",
"size",
"feather",
"hardness",
"rotation",
"curvature",
"compression" }
maskval = dt.gui.action("lib/masks/properties/" .. s[k],
element, effect, size)
end
-- if a value different from NAN is returned, the slider was active
if maskval == maskval then
return maskval
-- try if colorzones module is focused; if so select element of graph
elseif dt.gui.action("iop/colorzones", "focus") ~= 0 then
which = "iop/colorzones/graph"
local e = { "red",
"orange",
"yellow",
"green",
"aqua",
"blue",
"purple",
"magenta" }
element = e[k]
-- try if colorequalizer module is focused; if so select element of graph
elseif dt.gui.action("iop/colorequal", "focus") ~= 0 then
local e = { "red",
"orange",
"yellow",
"green",
"cyan",
"blue",
"lavender",
"magenta" }
which = "iop/colorequal/graph"
element = e[k]
-- if the sigmoid rgb primaries is focused,
-- check sliders
elseif dt.gui.action("iop/sigmoid", "focus") ~= 0 and k <8 then
local e = { "red attenuation", "red rotation", "green attenuation", "green rotation", "blue attenuation", "blue rotation", "recover purity" }
which = "iop/sigmoid/primaries/"..e[k]
-- if the rgb primaries is focused,
-- check sliders
elseif dt.gui.action("iop/primaries", "focus") ~= 0 and k >=1 then
local e = { "red hue", "red purity", "green hue", "green purity", "blue hue", "blue purity", "tint hue", "tint purity" }
which = "iop/primaries/" ..e[k]
-- if the tone equalizer is focused,
-- select one of the sliders in the "simple" tab
elseif dt.gui.action("iop/toneequal", "focus") ~= 0 then
which ="iop/toneequal/simple/"..(k-9).." EV"
-- if color calibration is focused,
-- the last 4 knobs are sent there
elseif dt.gui.action("iop/channelmixerrgb", "focus") ~= 0
and k >= 5 then
-- knob 5 selects the active tab; pressing it resets to CAT
if k == 5 then
which = "iop/channelmixerrgb/page"
element = "CAT"
-- since the tab action is not a slider,
-- the effects need to be translated
if effect == "up" then effect = "next"
elseif effect == "down" then effect = "previous"
else effect = "activate"
end
else
-- knobs 6, 7 and 8 are for the three color sliders on each tab
which = "iop/focus/sliders"
local e = { "1st",
"2nd",
"3rd" }
element = e[k - 5]
end
-- the 4th knob is contrast;
-- either colorbalance if it is focused, or filmic
elseif dt.gui.action("iop/colorbalancergb", "focus") ~= 0
and k == 4 then
which = "iop/colorbalancergb/contrast"
-- in all other cases use a default selection
else
local s = { "iop/exposure/exposure",
"iop/filmicrgb/white relative exposure",
"iop/filmicrgb/black relative exposure",
"iop/filmicrgb/contrast",
"iop/crop/left",
"iop/crop/right",
"iop/crop/top",
"iop/crop/bottom" }
which = s[k]
end
-- now pass the element/effect/size to the selected slider
return dt.gui.action(which, element, effect, size)
end)
-- Helper functions
local function is_in_darkroom()
return dt.gui.current_view() == dt.gui.views.darkroom
end
local function destroy()
-- nothing to destroy
local function is_valid(value)
return value == value -- NaN check
end
script_data.destroy = destroy
local function is_module_focused(module_name)
return dt.gui.action("iop/" .. module_name, "focus") ~= 0
end
local function translate_tab_effect(effect)
if effect == "up" then
return "next"
elseif effect == "down" then
return "previous"
else
return "activate"
end
end
-- Handler functions for different module types
local function handle_mask_properties(knob_idx, element, effect, size)
if knob_idx >= NUM_KNOBS then
return NAN
end
local property = MASK_PROPERTIES[knob_idx]
local value = dt.gui.action("lib/masks/properties/" .. property,
element, effect, size)
return value
end
local function handle_color_module(module_name, knob_idx, element, effect, size)
local elements = COLOR_ELEMENTS[module_name]
if not elements then
return nil
end
return dt.gui.action("iop/" .. module_name .. "/graph",
elements[knob_idx], effect, size)
end
local function handle_sigmoid(knob_idx, element, effect, size)
if knob_idx >= NUM_KNOBS then
return nil
end
local slider = MODULE_CONFIGS.sigmoid[knob_idx]
return dt.gui.action("iop/sigmoid/primaries/" .. slider,
element, effect, size)
end
local function handle_primaries(knob_idx, element, effect, size)
local slider = MODULE_CONFIGS.primaries[knob_idx]
return dt.gui.action("iop/primaries/" .. slider,
element, effect, size)
end
local function handle_agx(knob_idx, element, effect, size)
local slider = MODULE_CONFIGS.agx[knob_idx]
return dt.gui.action("iop/agx/" .. slider,
element, effect, size)
end
local function handle_tone_equalizer(knob_idx, element, effect, size)
return dt.gui.action("iop/toneequal/simple/" .. (knob_idx - 9) .. " EV",
element, effect, size)
end
local function handle_channel_mixer(knob_idx, element, effect, size)
if knob_idx < 5 then
return nil
end
if knob_idx == 5 then
-- Tab selection
return dt.gui.action("iop/channelmixerrgb/page",
"CAT",
translate_tab_effect(effect),
size)
else
-- Color sliders
local positions = {"1st", "2nd", "3rd"}
return dt.gui.action("iop/focus/sliders",
positions[knob_idx - 5],
effect, size)
end
end
local function handle_color_balance_contrast(knob_idx, element, effect, size)
if knob_idx ~= 4 then
return nil
end
return dt.gui.action("iop/colorbalancergb/contrast",
element, effect, size)
end
local function handle_default_mapping(knob_idx, element, effect, size)
local mapping = DEFAULT_MAPPINGS[knob_idx]
return dt.gui.action(mapping, element, effect, size)
end
-- Main knob handler
local function handle_knob_action(knob_idx, element, effect, size)
if not is_in_darkroom() then
return NAN
end
-- Try mask properties first
local mask_value = handle_mask_properties(knob_idx, element, effect, size)
if is_valid(mask_value) then
return mask_value
end
-- Try color modules
if is_module_focused("colorzones") then
return handle_color_module("colorzones", knob_idx, element, effect, size)
end
if is_module_focused("colorequal") then
return handle_color_module("colorequal", knob_idx, element, effect, size)
end
-- Try sigmoid module
if is_module_focused("sigmoid") then
local result = handle_sigmoid(knob_idx, element, effect, size)
if result then
return result
end
end
-- Try primaries module
if is_module_focused("primaries") then
local result = handle_primaries(knob_idx, element, effect, size)
if result then
return result
end
end
-- Try agx module
if is_module_focused("agx") then
local result = handle_agx(knob_idx, element, effect, size)
if result then
return result
end
end
-- Try tone equalizer
if is_module_focused("toneequal") then
return handle_tone_equalizer(knob_idx, element, effect, size)
end
-- Try channel mixer (color calibration)
if is_module_focused("channelmixerrgb") then
local result = handle_channel_mixer(knob_idx, element, effect, size)
if result then
return result
end
end
-- Try color balance contrast
if is_module_focused("colorbalancergb") then
local result = handle_color_balance_contrast(knob_idx, element, effect, size)
if result then
return result
end
end
-- Fall back to default mapping
return handle_default_mapping(knob_idx, element, effect, size)
end
-- Setup mimic sliders
local function setup_knobs()
for k = 1, NUM_KNOBS do
dt.gui.mimic("slider", "knob " .. k,
function(action, element, effect, size)
local knob_idx = tonumber(action:sub(-1))
return handle_knob_action(knob_idx, element, effect, size)
end)
end
end
-- Script metadata
local script_data = {
metadata = {
name = _("x-touch"),
purpose = _("example of how to control an x-touch midi device"),
author = "Diederik ter Rahe",
help = "https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/examples/x-touch"
},
destroy = function() end,
destroy_method = nil,
restart = nil,
show = nil
}
-- Initialize
setup_knobs()
return script_data