From 056f616283e8900aa0ff8999b586c6dc5b3a565d Mon Sep 17 00:00:00 2001 From: Martin Straeten <39386816+MStraeten@users.noreply.github.com> Date: Thu, 6 Nov 2025 19:19:46 +0100 Subject: [PATCH 1/3] 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 ) --- examples/x-touch.lua | 382 ++++++++++++++++++++++++++++--------------- 1 file changed, 249 insertions(+), 133 deletions(-) diff --git a/examples/x-touch.lua b/examples/x-touch.lua index 7da3d54..4675d85 100644 --- a/examples/x-touch.lua +++ b/examples/x-touch.lua @@ -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 From ffa125b134b18a09152d16aab5e16ce819affe7b Mon Sep 17 00:00:00 2001 From: Martin Straeten <39386816+MStraeten@users.noreply.github.com> Date: Sun, 9 Nov 2025 20:15:34 +0100 Subject: [PATCH 2/3] further improved customizability to add a new module, simply add an entry to MODULE_PROFILES below. direct usage of action paths from darktable's shortcut interface. --- examples/x-touch.lua | 369 +++++++++++++++++++++++-------------------- 1 file changed, 195 insertions(+), 174 deletions(-) diff --git a/examples/x-touch.lua b/examples/x-touch.lua index 4675d85..9e354be 100644 --- a/examples/x-touch.lua +++ b/examples/x-touch.lua @@ -51,6 +51,10 @@ midi:C0=iop/channelmixerrgb;focus midi:C#0=iop/colorequal;focus midi:D0=iop/colorequal/page;previous midi:D#0=iop/colorequal/page;next + +USAGE: +To add a new module, simply add an entry to MODULE_PROFILES below. +Get action paths from darktable's shortcut interface. ]] local dt = require "darktable" @@ -66,71 +70,160 @@ end -- Constants local NUM_KNOBS = 8 local NAN = 0/0 +local darkroom_view = dt.gui.views.darkroom --- Configuration tables -local MASK_PROPERTIES = { - "opacity", "size", "feather", "hardness", - "rotation", "curvature", "compression" -} +-- ============================================================================ +-- USER CONFIGURATION +-- ============================================================================ -local COLOR_ELEMENTS = { +-- Module Profiles: Define behavior when each module has focus +-- Structure: [module_name] = { knob1_action, knob2_action, ... } +local MODULE_PROFILES = { + + -- Color Zones: Control individual color channels colorzones = { - "red", "orange", "yellow", "green", - "aqua", "blue", "purple", "magenta" + {"iop/colorzones/graph", "red"}, + {"iop/colorzones/graph", "orange"}, + {"iop/colorzones/graph", "yellow"}, + {"iop/colorzones/graph", "green"}, + {"iop/colorzones/graph", "aqua"}, + {"iop/colorzones/graph", "blue"}, + {"iop/colorzones/graph", "purple"}, + {"iop/colorzones/graph", "magenta"} }, + + -- Color Equalizer: Control color channels colorequal = { - "red", "orange", "yellow", "green", - "cyan", "blue", "lavender", "magenta" - } -} - -local MODULE_CONFIGS = { + {"iop/colorequal/graph", "red"}, + {"iop/colorequal/graph", "orange"}, + {"iop/colorequal/graph", "yellow"}, + {"iop/colorequal/graph", "green"}, + {"iop/colorequal/graph", "cyan"}, + {"iop/colorequal/graph", "blue"}, + {"iop/colorequal/graph", "lavender"}, + {"iop/colorequal/graph", "magenta"} + }, + + -- Sigmoid: Control primary color adjustments sigmoid = { - "red attenuation", "red rotation", - "green attenuation", "green rotation", - "blue attenuation", "blue rotation", - "recover purity" + {"iop/sigmoid/primaries/red attenuation"}, + {"iop/sigmoid/primaries/red rotation"}, + {"iop/sigmoid/primaries/green attenuation"}, + {"iop/sigmoid/primaries/green rotation"}, + {"iop/sigmoid/primaries/blue attenuation"}, + {"iop/sigmoid/primaries/blue rotation"}, + {"iop/sigmoid/primaries/recover purity"} }, + + -- Color Calibration (Primaries) primaries = { - "red hue", "red purity", "green hue", "green purity", - "blue hue", "blue purity", "tint hue", "tint purity" + {"iop/primaries/red hue"}, + {"iop/primaries/red purity"}, + {"iop/primaries/green hue"}, + {"iop/primaries/green purity"}, + {"iop/primaries/blue hue"}, + {"iop/primaries/blue purity"}, + {"iop/primaries/tint hue"}, + {"iop/primaries/tint purity"} }, + + -- AgX 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" - } + {"iop/agx/white relative exposure"}, + {"iop/agx/black relative exposure"}, + {"iop/agx/curve/pivot input shift"}, + {"iop/agx/curve/pivot target output"}, + {"iop/agx/curve/contrast"}, + {"iop/agx/curve/toe power"}, + {"iop/agx/curve/shoulder power"}, + {"iop/agx/curve/curve y gamma"} + }, + + -- Tone Equalizer: Control EV bands + -- For 8 knobs, using offsets -8 to get bands + toneequal = { + {"iop/toneequal/simple/-8 EV"}, + {"iop/toneequal/simple/-7 EV"}, + {"iop/toneequal/simple/-6 EV"}, + {"iop/toneequal/simple/-5 EV"}, + {"iop/toneequal/simple/-4 EV"}, + {"iop/toneequal/simple/-3 EV"}, + {"iop/toneequal/simple/-2 EV"}, + {"iop/toneequal/simple/-1 EV"} + }, + + -- Color Balance RGB: Selected controls + colorbalancergb = { + nil, -- Knob 1: unused --> default + {"iop/colorbalancergb/global vibrance"}, + {"iop/colorbalancergb/contrast"}, + {"iop/colorbalancergb/global chroma"}, + {"iop/colorbalancergb/global saturation"}, + {"iop/colorbalancergb/global brilliance"}, + nil, -- Knob 7: --> default + nil -- Knob 8: --> default + }, + + -- Channel Mixer RGB (Color Calibration): Tab control and sliders + channelmixerrgb = { + nil, -- Knob 1-4: --> default + nil, + nil, + nil, + {"iop/channelmixerrgb/page", "CAT", "tab"}, -- Knob 5: Tab selector + {"iop/focus/sliders", "1st"}, -- Knob 6: First slider + {"iop/focus/sliders", "2nd"}, -- Knob 7: Second slider + {"iop/focus/sliders", "3rd"} -- Knob 8: Third slider + }, + } 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" + {"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"} } +-- Mask properties: Always checked first, regardless of module focus +local MASK_PROPERTIES = { + {"lib/masks/properties/opacity"}, + {"lib/masks/properties/size"}, + {"lib/masks/properties/feather"}, + {"lib/masks/properties/hardness"}, + {"lib/masks/properties/rotation"}, + {"lib/masks/properties/curvature"}, + {"lib/masks/properties/compression"} +} + +-- Priority order for checking module focus (most commonly used first) +local MODULE_CHECK_ORDER = { + "toneequal", + "colorequal", + "colorbalancergb", + "agx", + "sigmoid", + "primaries", + "channelmixerrgb", + "colorzones" +} + +-- ============================================================================ +-- CORE LOGIC - No need to edit below unless changing architecture +-- ============================================================================ -- Helper functions local function is_in_darkroom() - return dt.gui.current_view() == dt.gui.views.darkroom + return dt.gui.current_view() == darkroom_view end local function is_valid(value) return value == value -- NaN check end -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" @@ -141,156 +234,84 @@ local function translate_tab_effect(effect) end end --- Handler functions for different module types -local function handle_mask_properties(knob_idx, element, effect, size) +-- Execute an action configuration +local function execute_action(action_config, element, effect, size) + if not action_config then + return nil + end + + local path = action_config[1] + local elem = action_config[2] or element + local action_type = action_config[3] + + -- Handle special action types + if action_type == "tab" then + effect = translate_tab_effect(effect) + end + + return dt.gui.action(path, elem, effect, size) +end + +-- Try mask properties first +local function try_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) + local mask_action = MASK_PROPERTIES[knob_idx] + if not mask_action then + return NAN + end + + local value = execute_action(mask_action, 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) + -- Early exit for non-darkroom view if not is_in_darkroom() then return NAN end - -- Try mask properties first - local mask_value = handle_mask_properties(knob_idx, element, effect, size) + -- Try mask properties first (no focus check needed) + local mask_value = try_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 + -- Check focused modules in priority order + for _, module_name in ipairs(MODULE_CHECK_ORDER) do + local focus_status = dt.gui.action("iop/" .. module_name, "focus") + + if focus_status ~= 0 then + -- Module has focus, get its profile + local profile = MODULE_PROFILES[module_name] + + if profile then + local action_config = profile[knob_idx] + + -- If profile has explicit mapping for this knob, use it + if action_config then + local result = execute_action(action_config, element, effect, size) + if result then + return result + end + else + -- Profile exists but this knob is nil -> use default + -- This avoids checking other modules when we know the focused one + local default_action = DEFAULT_MAPPINGS[knob_idx] + return execute_action(default_action, element, effect, size) or NAN + end + end + + -- If no profile exists for this module, continue checking others 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) + -- No module has focus or no profile matched, fall back to default + local default_action = DEFAULT_MAPPINGS[knob_idx] + return execute_action(default_action, element, effect, size) or NAN end -- Setup mimic sliders @@ -308,8 +329,8 @@ end local script_data = { metadata = { name = _("x-touch"), - purpose = _("example of how to control an x-touch midi device"), - author = "Diederik ter Rahe", + purpose = _("data-driven x-touch midi controller configuration"), + author = "Diederik ter Rahe (modified)", help = "https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/examples/x-touch" }, destroy = function() end, From 3c131c41d1073291234a3d4c153afe5463163462 Mon Sep 17 00:00:00 2001 From: Martin Straeten <39386816+MStraeten@users.noreply.github.com> Date: Tue, 11 Nov 2025 22:00:25 +0100 Subject: [PATCH 3/3] adaption to recent agx update renamed action and reordered sliders --- examples/x-touch.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/x-touch.lua b/examples/x-touch.lua index 9e354be..0e2500d 100644 --- a/examples/x-touch.lua +++ b/examples/x-touch.lua @@ -131,11 +131,11 @@ local MODULE_PROFILES = { agx = { {"iop/agx/white relative exposure"}, {"iop/agx/black relative exposure"}, - {"iop/agx/curve/pivot input shift"}, + {"iop/agx/curve/pivot relative exposure"}, {"iop/agx/curve/pivot target output"}, {"iop/agx/curve/contrast"}, - {"iop/agx/curve/toe power"}, {"iop/agx/curve/shoulder power"}, + {"iop/agx/curve/toe power"}, {"iop/agx/curve/curve y gamma"} },