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:
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user