Icon representing a recipe

Recipe: Manage Bands v1.0

created by Serca

Profile


Name
Manage Bands v1.0
ID
109218
Shared with
Public
Parent
None
Children
Created on
October 23, 2025 at 20:11 PM UTC
Updated on
October 23, 2025 at 20:56 PM UTC
Description

Best for


Code


-- . -- Recipe to manage bands. It's a bit less friendly than Band Equalizer (https://fold.it/recipes/46757), but has more features. -- ------------------------- -- Utilities ------------------------- local function clamp(v, lo, hi) if v < lo then return lo end if v > hi then return hi end return v end local function fmt(n, prec) prec = prec or 3 if n == nil then return "nil" end return string.format("%." .. tostring(prec) .. "f", n) end local function has_band_api() return band and band.GetCount and band.GetResidueBase and band.GetResidueEnd end local function safe_pcall(f, ...) if type(f) ~= "function" then return false, nil end return pcall(f, ...) end -- Try to get actual band length if available. local function safe_get_actual_length(i) if band and band.GetLength then local ok, val = pcall(band.GetLength, i) if ok and type(val) == "number" then return val end end if band and band.GetGoalLength then local ok, val = pcall(band.GetGoalLength, i) if ok and type(val) == "number" then return val end end -- Fallback for segment-to-segment bands: use residue distance if band and structure and structure.GetDistance then local rb = band.GetResidueBase(i) local re = band.GetResidueEnd(i) if rb and re and rb > 0 and re > 0 then local ok, dist = pcall(structure.GetDistance, rb, re) if ok and type(dist) == "number" then return dist end end end return nil end -- Sidechain detection: a band is considered sidechain if it references atoms -- (non-zero atom indices), except the CA-CA case (atom index == 2 on both ends), -- which should be treated as a segment-to-segment band. local function is_sidechain(i) if not band or not band.GetAtomBase or not band.GetAtomEnd then return false end local ab = band.GetAtomBase(i) or 0 local ae = band.GetAtomEnd(i) or 0 if ab == 2 and ae == 2 then return false end return (ab > 0) or (ae > 0) end local function band_enabled(i) if band and band.IsEnabled then local ok, val = pcall(band.IsEnabled, i) if ok then return val and true or false end end -- Assume enabled if API not available return true end -- Amino-acid mapping and helpers local aa3_map = { g = "Gly", a = "Ala", v = "Val", l = "Leu", i = "Ile", m = "Met", f = "Phe", w = "Trp", p = "Pro", s = "Ser", t = "Thr", c = "Cys", y = "Tyr", n = "Asn", q = "Gln", d = "Asp", e = "Glu", k = "Lys", r = "Arg", h = "His", } local function get_aa3(i) if not structure or not structure.GetAminoAcid or not i or i <= 0 then return "Unk" end local ok, k = pcall(structure.GetAminoAcid, i) if not ok or not k then return "Unk" end k = string.lower(tostring(k)) return aa3_map[k] or "Unk" end local function seg_label(b) local rb, re = b.residueBase or 0, b.residueEnd or 0 local ab, ae = b.atomBase or 0, b.atomEnd or 0 local aaB, aaE = get_aa3(rb), get_aa3(re) -- Avoid '>' symbol to prevent HTML rendering issues in some UIs return string.format("seg %d(%s)[%d] to %d(%s)[%d]", rb, aaB, ab, re, aaE, ae) end local function get_band_info(i) local info = { index = i, residueBase = band.GetResidueBase and band.GetResidueBase(i) or 0, residueEnd = band.GetResidueEnd and band.GetResidueEnd(i) or 0, atomBase = band.GetAtomBase and band.GetAtomBase(i) or 0, atomEnd = band.GetAtomEnd and band.GetAtomEnd(i) or 0, strength = band.GetStrength and band.GetStrength(i) or nil, goalLength = band.GetGoalLength and band.GetGoalLength(i) or nil, actualLength= safe_get_actual_length(i), isSidechain = is_sidechain(i), isBiS = false, isEnabled = band_enabled(i), } -- BiS detection: if any residue index is <= 0, treat as Bands in Space if (info.residueBase or 0) <= 0 or (info.residueEnd or 0) <= 0 then info.isBiS = true info.isSidechain = false end return info end local function gather_all_bands() local count = band.GetCount() local list = {} for i = 1, count do list[#list + 1] = get_band_info(i) end return list end -- Compute global ranges for filters and actions local function compute_ranges(items) local smin, smax = math.huge, -math.huge local lmin, lmax = math.huge, -math.huge for _, b in ipairs(items) do if b.strength ~= nil then if b.strength < smin then smin = b.strength end if b.strength > smax then smax = b.strength end end if b.goalLength ~= nil then if b.goalLength < lmin then lmin = b.goalLength end if b.goalLength > lmax then lmax = b.goalLength end end end if smin == math.huge then smin, smax = 0.0, 0.0 end if lmin == math.huge then lmin, lmax = 0.0, 0.0 end return { smin = smin, smax = smax, lmin = lmin, lmax = lmax } end -- Build groups by (strength, length), both rounded to 3 decimals, sorted by strength then length local function roundfmt(x) if x == nil then return "nil" end return string.format("%.3f", x) end local function compute_groups(items) -- Build groups separately for enabled and disabled bands. -- For each status: -- 1) Build exact-value groups (rounded to 0.001) and keep only those with count >= 2. -- 2) Collect remaining singletons and bin them into up to 10 ranges (equal width). -- Concatenate in order: Enabled S exact -> Enabled S ranges -> Enabled L exact -> Enabled L ranges -> -- Disabled S exact -> Disabled S ranges -> Disabled L exact -> Disabled L ranges local function build_groups_for(arr, status_label) local s_map = {} local l_map = {} local s_list, l_list = {}, {} local s_singles, l_singles = {}, {} local smin, smax = math.huge, -math.huge local lmin, lmax = math.huge, -math.huge for _, b in ipairs(arr) do if b.strength ~= nil then local s_str = roundfmt(b.strength) local s_val = tonumber(s_str) or b.strength if s_val < smin then smin = s_val end if s_val > smax then smax = s_val end local g = s_map[s_str] if not g then g = { kind='S', key=s_str, val=s_val, items={}, status=status_label } s_map[s_str] = g s_list[#s_list+1] = g end g.items[#g.items+1] = b end if b.goalLength ~= nil then local l_str = roundfmt(b.goalLength) local l_val = tonumber(l_str) or b.goalLength if l_val < lmin then lmin = l_val end if l_val > lmax then lmax = l_val end local g2 = l_map[l_str] if not g2 then g2 = { kind='L', key=l_str, val=l_val, items={}, status=status_label } l_map[l_str] = g2 l_list[#l_list+1] = g2 end g2.items[#g2.items+1] = b end end -- Split exact (count>=2) vs singletons (count==1) local s_exact, l_exact = {}, {} for _, g in ipairs(s_list) do if #g.items >= 2 then s_exact[#s_exact+1] = g else for _, it in ipairs(g.items) do s_singles[#s_singles+1] = it end end end for _, g in ipairs(l_list) do if #g.items >= 2 then l_exact[#l_exact+1] = g else for _, it in ipairs(g.items) do l_singles[#l_singles+1] = it end end end table.sort(s_exact, function(a,b) return a.val < b.val end) table.sort(l_exact, function(a,b) return a.val < b.val end) -- Bin singletons into up to 10 ranges local function bin_singletons(kind, arr2, vmin, vmax, get_val) if #arr2 == 0 then return {} end local count = 10 local width = (vmax - vmin) / count if width <= 0 then local g = { kind = kind, isRange = true, lo = vmin, hi = vmax, key = roundfmt(vmin) .. ".." .. roundfmt(vmax), items = {}, status=status_label } for _, it in ipairs(arr2) do g.items[#g.items+1] = it end return { g } end local bins = {} for i=1,count do local lo = vmin + (i-1)*width local hi = (i==count) and vmax or (vmin + i*width) bins[i] = { kind = kind, isRange = true, lo = lo, hi = hi, key = roundfmt(lo) .. ".." .. roundfmt(hi), items = {}, status=status_label } end for _, it in ipairs(arr2) do local val = get_val(it) local idx = math.floor(((val - vmin) / width) + 1) if idx < 1 then idx = 1 end if idx > count then idx = count end table.insert(bins[idx].items, it) end local compact = {} for i=1,count do if #bins[i].items > 0 then compact[#compact+1] = bins[i] end end return compact end -- Compute singles min/max separately to bin only singles span local smin_single, smax_single = math.huge, -math.huge for _, it in ipairs(s_singles) do local v = it.strength or 0 if v < smin_single then smin_single = v end if v > smax_single then smax_single = v end end if smin_single == math.huge then smin_single, smax_single = smin, smax end local lmin_single, lmax_single = math.huge, -math.huge for _, it in ipairs(l_singles) do local v = it.goalLength or 0 if v < lmin_single then lmin_single = v end if v > lmax_single then lmax_single = v end end if lmin_single == math.huge then lmin_single, lmax_single = lmin, lmax end local s_ranges = bin_singletons('S', s_singles, smin_single, smax_single, function(it) return it.strength or 0 end) local l_ranges = bin_singletons('L', l_singles, lmin_single, lmax_single, function(it) return it.goalLength or 0 end) local groups = {} for _, g in ipairs(s_exact) do groups[#groups+1] = g end for _, g in ipairs(s_ranges) do groups[#groups+1] = g end for _, g in ipairs(l_exact) do groups[#groups+1] = g end for _, g in ipairs(l_ranges) do groups[#groups+1] = g end return groups end local enabled_items, disabled_items = {}, {} for _, b in ipairs(items) do if b.isEnabled then enabled_items[#enabled_items+1] = b else disabled_items[#disabled_items+1] = b end end local groups_all = {} local en_groups = build_groups_for(enabled_items, 'enabled') for _, g in ipairs(en_groups) do groups_all[#groups_all+1] = g end local dis_groups = build_groups_for(disabled_items, 'disabled') for _, g in ipairs(dis_groups) do groups_all[#groups_all+1] = g end for i, g in ipairs(groups_all) do g.id = i end return groups_all end local function print_groups(groups) print("Groups by status: exact equals (count>=2) by Strength [S] and Length [L]; singles are binned into ranges:") if not groups or #groups == 0 then print(" (no groups)") return end -- Helper to compute counterpart range label local function counterpart_range(g) local mn, mx if g.kind == 'S' then for _, it in ipairs(g.items) do local v = it.goalLength if v ~= nil then mn = (mn == nil or v < mn) and v or mn mx = (mx == nil or v > mx) and v or mx end end if mn == nil then return "" end return string.format(" | L=%s..%s", roundfmt(mn), roundfmt(mx)) else for _, it in ipairs(g.items) do local v = it.strength if v ~= nil then mn = (mn == nil or v < mn) and v or mn mx = (mx == nil or v > mx) and v or mx end end if mn == nil then return "" end return string.format(" | S=%s..%s", roundfmt(mn), roundfmt(mx)) end end local en_s_lines, en_l_lines, dis_s_lines, dis_l_lines = {}, {}, {}, {} for _, g in ipairs(groups) do local status_tag = (g.status == 'disabled') and "[DIS] " or "[EN] " local prefix = status_tag .. ((g.kind == 'S') and "[S] S=" or "[L] L=") local label = g.key if g.isRange then if #g.items == 1 then local it = g.items[1] label = (g.kind == 'S') and roundfmt(it.strength) or roundfmt(it.goalLength) else -- For range groups with multiple items, show actual min..max across items local mn, mx if g.kind == 'S' then for _, it in ipairs(g.items) do local v = it.strength if v ~= nil then mn = (mn == nil or v < mn) and v or mn mx = (mx == nil or v > mx) and v or mx end end else for _, it in ipairs(g.items) do local v = it.goalLength if v ~= nil then mn = (mn == nil or v < mn) and v or mn mx = (mx == nil or v > mx) and v or mx end end end if mn ~= nil and mx ~= nil then if math.abs(mx - mn) < 1e-12 then label = roundfmt(mn) else label = roundfmt(mn) .. ".." .. roundfmt(mx) end end end end local tail = counterpart_range(g) local line = string.format(" #%d %s%s%s | count=%d", g.id, prefix, label, tail, #g.items) if g.status == 'disabled' then if g.kind == 'S' then dis_s_lines[#dis_s_lines+1] = line else dis_l_lines[#dis_l_lines+1] = line end else if g.kind == 'S' then en_s_lines[#en_s_lines+1] = line else en_l_lines[#en_l_lines+1] = line end end end if #en_s_lines + #en_l_lines > 0 then print("Enabled:") for _, line in ipairs(en_s_lines) do print(line) end if #en_l_lines > 0 then print("-") end for _, line in ipairs(en_l_lines) do print(line) end end if #dis_s_lines + #dis_l_lines > 0 then if #en_s_lines + #en_l_lines > 0 then print("=") end print("Disabled:") for _, line in ipairs(dis_s_lines) do print(line) end if #dis_l_lines > 0 then print("-") end for _, line in ipairs(dis_l_lines) do print(line) end end end local function update_state(cfg) local items = gather_all_bands() cfg.items = items cfg.ranges = compute_ranges(items) cfg.groups = compute_groups(items) -- Initialize default filter ranges to the full existing ranges, once if not cfg.defaults_set then cfg.strength_min = cfg.ranges.smin cfg.strength_max = cfg.ranges.smax cfg.length_min = cfg.ranges.lmin cfg.length_max = cfg.ranges.lmax cfg.defaults_set = true end end ------------------------- -- Reporting ------------------------- local function stats(values) local n = #values if n == 0 then return {n=0, min=nil, max=nil, avg=nil} end local mn, mx = values[1], values[1] local sum = 0 for i=1,n do local v = values[i] if v ~= nil then if v < mn then mn = v end if v > mx then mx = v end sum = sum + v end end return { n = n, min = mn, max = mx, avg = sum / n } end -- Histograms removed per new requirements local function print_group_stats(group_name, arr) local strengths, lengths = {}, {} for _, b in ipairs(arr) do if b.strength ~= nil then strengths[#strengths+1] = b.strength end if b.goalLength ~= nil then lengths[#lengths+1] = b.goalLength end end local sst = stats(strengths) local lst = stats(lengths) print(string.format("%s | count=%d", group_name, #arr)) print(string.format(" Strengths: n=%d, min=%s, max=%s, avg=%s", sst.n, fmt(sst.min), fmt(sst.max), fmt(sst.avg))) print(string.format(" Lengths: n=%d, min=%s, max=%s, avg=%s", lst.n, fmt(lst.min), fmt(lst.max), fmt(lst.avg))) end local function print_report(cfg) local count = band.GetCount() if count == 0 then print("No bands found.") return end local items = gather_all_bands() print(string.format("Bands total: %d", #items)) local seg_enabled, seg_disabled, side_enabled, side_disabled, bis_enabled, bis_disabled = {}, {}, {}, {}, {}, {} for _, b in ipairs(items) do if b.isBiS then if b.isEnabled then bis_enabled[#bis_enabled+1] = b else bis_disabled[#bis_disabled+1] = b end elseif b.isSidechain then if b.isEnabled then side_enabled[#side_enabled+1] = b else side_disabled[#side_disabled+1] = b end else if b.isEnabled then seg_enabled[#seg_enabled+1] = b else seg_disabled[#seg_disabled+1] = b end end end print_group_stats("Segment bands (enabled)", seg_enabled) print_group_stats("Segment bands (disabled)", seg_disabled) print_group_stats("Sidechain bands (enabled)", side_enabled) print_group_stats("Sidechain bands (disabled)", side_disabled) print_group_stats("BiS bands (enabled)", bis_enabled) print_group_stats("BiS bands (disabled)", bis_disabled) -- Histograms removed -- Show top extremes table.sort(items, function(a,b) local la = a.goalLength or math.huge local lb = b.goalLength or math.huge if la == lb then return (a.index or 0) < (b.index or 0) end return la < lb end) print("\nShortest bands (up to 5):") for i=1, math.min(5, #items) do local b = items[i] local t = b.isBiS and "BiS" or (b.isSidechain and "sidechain" or "segment") print(string.format( " #%d %s | goal=%s L=%s | S=%s | %s | %s", b.index, seg_label(b), fmt(b.goalLength), fmt(b.actualLength), fmt(b.strength), t, b.isEnabled and "enabled" or "disabled" )) end table.sort(items, function(a,b) local la = a.goalLength or -math.huge local lb = b.goalLength or -math.huge if la == lb then return (a.index or 0) < (b.index or 0) end return la > lb end) print("\nLongest bands (up to 5):") for i=1, math.min(5, #items) do local b = items[i] local t = b.isBiS and "BiS" or (b.isSidechain and "sidechain" or "segment") print(string.format( " #%d %s | goal=%s L=%s | S=%s | %s | %s", b.index, seg_label(b), fmt(b.goalLength), fmt(b.actualLength), fmt(b.strength), t, b.isEnabled and "enabled" or "disabled" )) end print("\nAll bands (full list):") table.sort(items, function(a,b) return (a.index or 0) < (b.index or 0) end) for _, b in ipairs(items) do local t = b.isBiS and "BiS" or (b.isSidechain and "sidechain" or "segment") print(string.format( " #%d type=%s status=%s | S=%s goal=%s L=%s | %s", b.index, t, b.isEnabled and "enabled" or "disabled", fmt(b.strength), fmt(b.goalLength), fmt(b.actualLength), seg_label(b) )) end end ------------------------- -- Filtering and actions ------------------------- local EPS = 1e-6 local function passes_filters(b, cfg) -- Type filter if b.isBiS and not cfg.include_bis then return false end if (not b.isBiS) then if b.isSidechain and not cfg.include_sidechain then return false end if (not b.isSidechain) and not cfg.include_segment then return false end end -- Enabled filter if b.isEnabled and not cfg.include_enabled then return false end if (not b.isEnabled) and not cfg.include_disabled then return false end -- Strength range (always on; treat missing values as pass-through) local s = b.strength if s ~= nil then if cfg.strength_min and s < cfg.strength_min - EPS then return false end if cfg.strength_max and s > cfg.strength_max + EPS then return false end end -- Length range (uses goal length; always on; treat missing as pass-through) local L = b.goalLength if L ~= nil then if cfg.length_min and L < cfg.length_min - EPS then return false end if cfg.length_max and L > cfg.length_max + EPS then return false end end return true end local function select_bands(cfg) local base = {} if cfg.group_index and cfg.group_index > 0 and cfg.groups and cfg.groups[cfg.group_index] then base = cfg.groups[cfg.group_index].items else base = gather_all_bands() end local out = {} for _, b in ipairs(base) do if passes_filters(b, cfg) then out[#out+1] = b end end return out end -- Action types -- Actions are configured via checkboxes and two sliders now local function apply_action(selection, cfg) local n = #selection if n == 0 then print("No bands match the current filters — nothing to apply.") return end local updated = 0 undo.SetUndo(false) for _, b in ipairs(selection) do local i = b.index -- Set Strength if cfg.act_set_strength and band.SetStrength and cfg.action_strength_value then local newv = clamp(cfg.action_strength_value, 0.0, 10.0) band.SetStrength(i, newv) updated = updated + 1 end -- Set Length (goal) if cfg.act_set_length and band.SetGoalLength and cfg.action_length_value then local L = math.max(0.01, cfg.action_length_value) band.SetGoalLength(i, L) updated = updated + 1 end -- Set Goal Length = Actual length via band.GetLength if cfg.act_len_to_actual and band.SetGoalLength and band.GetLength then local okL, L = pcall(band.GetLength, i) if okL and type(L) == 'number' and L > 0 then band.SetGoalLength(i, L) updated = updated + 1 end end -- Enable/Disable if cfg.act_enable and band.Enable then pcall(band.Enable, i) updated = updated + 1 end if cfg.act_disable and band.Disable then pcall(band.Disable, i) updated = updated + 1 end end undo.SetUndo(true) -- Fallback: only if no actions at all are checked -- If nothing was updated and values changed vs remembered defaults, -- auto-apply those changed values (warn user). local no_actions_checked = not ( (cfg.act_set_strength or false) or (cfg.act_set_length or false) or (cfg.act_len_to_actual or false) or (cfg.act_enable or false) or (cfg.act_disable or false) or (cfg.act_remove or false) ) if updated == 0 and n > 0 and no_actions_checked then local warn_msgs = {} local did_any = false local strength_changed = (cfg.action_strength_value ~= nil and cfg.def_strength_value ~= nil and math.abs(cfg.action_strength_value - cfg.def_strength_value) > EPS) local length_changed = (cfg.action_length_value ~= nil and cfg.def_length_value ~= nil and math.abs(cfg.action_length_value - cfg.def_length_value) > EPS) if (strength_changed or length_changed) then undo.SetUndo(false) for _, b in ipairs(selection) do local i = b.index if strength_changed and band.SetStrength then local newv = clamp(cfg.action_strength_value, 0.0, 10.0) band.SetStrength(i, newv) updated = updated + 1 did_any = true end if length_changed and band.SetGoalLength then local L = math.max(0.01, cfg.action_length_value) band.SetGoalLength(i, L) updated = updated + 1 did_any = true end end undo.SetUndo(true) if strength_changed then warn_msgs[#warn_msgs+1] = string.format("Strength default changed: %s to %s", fmt(cfg.def_strength_value), fmt(cfg.action_strength_value)) end if length_changed then warn_msgs[#warn_msgs+1] = string.format("Length default changed: %s to %s", fmt(cfg.def_length_value), fmt(cfg.action_length_value)) end if did_any then print("Note: No actions were checked. Applied changed defaults to selection:") for _, m in ipairs(warn_msgs) do print(" - " .. m) end end end end local removed, failed = 0, 0 if cfg.act_remove and band.Delete then -- Delete after applying other actions; use descending order table.sort(selection, function(a,b) return a.index > b.index end) undo.SetUndo(false) for _, b in ipairs(selection) do local okd = pcall(band.Delete, b.index) if okd then removed = removed + 1 else failed = failed + 1 end end undo.SetUndo(true) end -- Remember current action values as new defaults cfg.def_strength_value = cfg.action_strength_value or cfg.def_strength_value cfg.def_length_value = cfg.action_length_value or cfg.def_length_value if cfg.act_remove then print(string.format("Applied changes to %d bands (selected=%d). Removed=%d, failed=%d", updated, n, removed, failed)) else print(string.format("Applied changes to %d bands (selected=%d)", updated, n)) end end local function preview_selection(selection) print(string.format("Preview: %d bands selected.", #selection)) for _, b in ipairs(selection) do local t = b.isBiS and "BiS" or (b.isSidechain and "sidechain" or "segment") print(string.format( " #%d %s | L=%s | S=%s | %s | %s", b.index, seg_label(b), fmt(b.actualLength), fmt(b.strength), t, b.isEnabled and "enabled" or "disabled" )) end end -- Export selected bands to compact string (for copy/paste) and pretty list local function serialize_compact(tbl) if not tbl or #tbl == 0 then return "" end local out = {"{"} for i, b in ipairs(tbl) do local piece = string.format( "{rb=%d,re=%d,ab=%d,ae=%d,s=%s,gl=%s,al=%s,en=%s}", tonumber(b.residueBase or 0) or 0, tonumber(b.residueEnd or 0) or 0, tonumber(b.atomBase or 0) or 0, tonumber(b.atomEnd or 0) or 0, b.strength ~= nil and string.format("%.6f", b.strength) or "nil", b.goalLength ~= nil and string.format("%.6f", b.goalLength) or "nil", b.actualLength ~= nil and string.format("%.6f", b.actualLength) or "nil", tostring(b.isEnabled ~= false) ) out[#out + 1] = piece .. (i < #tbl and "," or "") end out[#out + 1] = "}" return table.concat(out) end local function export_selection(selection) print(string.format("Export: %d bands selected.", #selection)) for _, b in ipairs(selection) do local t = b.isBiS and "BiS" or (b.isSidechain and "sidechain" or "segment") print(string.format( " #%d type=%s status=%s | S=%s goal=%s L=%s | %s", b.index, t, b.isEnabled and "enabled" or "disabled", fmt(b.strength), fmt(b.goalLength), fmt(b.actualLength), seg_label(b) )) end local compact = serialize_compact(selection) if compact ~= "" then print("\nSerialized (compact):\n" .. compact .. "\n") end end ------------------------- -- Help/Instructions ------------------------- local function print_help() print("") end ------------------------- -- Dialog / UI ------------------------- local function build_dialog(cfg) local dlg = dialog.CreateDialog("Manage Bands") -- Group selection local gcount = (cfg.groups and #cfg.groups) or 0 local grp_val = clamp(cfg.group_index or 0, 0, gcount) dlg.lblG = dialog.AddLabel(string.format("Groups available: %d", gcount)) if gcount > 0 then dlg.grp_idx = dialog.AddSlider("Select group (0=all)", grp_val, 0, gcount, 0) else dlg.grp_note = dialog.AddLabel("No groups to select (0 = all)") end -- Type/status filters dlg.f_seg = dialog.AddCheckbox("Include seg-seg bands", cfg.include_segment ~= false) dlg.f_atom = dialog.AddCheckbox("Include sidechain bands", cfg.include_sidechain ~= false) dlg.f_bis = dialog.AddCheckbox("Include bands in space", cfg.include_bis ~= false) dlg.f_en = dialog.AddCheckbox("Include enabled bands", cfg.include_enabled ~= false) dlg.f_dis= dialog.AddCheckbox("Include disabled bands", cfg.include_disabled ~= false) -- Strength range (always active) local smin = cfg.ranges and cfg.ranges.smin or 0.0 local smax = cfg.ranges and cfg.ranges.smax or 10.0 if smax < smin then smax = smin end if not (smax > smin) then local pad = math.max(0.01, math.abs(smin) * 0.1) smin = math.max(0.0, smin - pad) smax = smax + pad if not (smax > smin) then smax = smin + 0.01 end end local s_val_min = clamp(cfg.strength_min or smin, smin, smax) local s_val_max = clamp(cfg.strength_max or smax, smin, smax) dlg.f_s_min = dialog.AddSlider("Min strength", s_val_min, smin, smax, 2) dlg.f_s_max = dialog.AddSlider("Max strength", s_val_max, smin, smax, 2) -- Length range (always active) local lmin = cfg.ranges and cfg.ranges.lmin or 0.0 local lmax = cfg.ranges and cfg.ranges.lmax or 10.0 if lmax < lmin then lmax = lmin end if not (lmax > lmin) then local pad = math.max(0.01, math.abs(lmin) * 0.1) lmin = math.max(0.0, lmin - pad) lmax = lmax + pad if not (lmax > lmin) then lmax = lmin + 0.01 end end local l_val_min = clamp(cfg.length_min or lmin, lmin, lmax) local l_val_max = clamp(cfg.length_max or lmax, lmin, lmax) dlg.f_l_min = dialog.AddSlider("Min length", l_val_min, lmin, lmax, 2) dlg.f_l_max = dialog.AddSlider("Max length", l_val_max, lmin, lmax, 2) -- Action config via checkboxes dlg.lblAct = dialog.AddLabel("Actions:") dlg.act_s_on = dialog.AddCheckbox("Set strength", cfg.act_set_strength or false) local act_s_val = clamp(cfg.action_strength_value or 1.0, 0.0, 100.0) dlg.act_s_val = dialog.AddSlider("Strength value", act_s_val, 0.0, 10.0, 2) local l_upper = math.max((cfg.ranges and cfg.ranges.lmax or 0.0) * 10.0, 10.0) dlg.act_l_on = dialog.AddCheckbox("Set length", cfg.act_set_length or false) local act_l_val = clamp(cfg.action_length_value or 2.0, 0.0, l_upper) dlg.act_l_val = dialog.AddSlider("Length value", act_l_val, 0.0, l_upper, 2) dlg.act_en = dialog.AddCheckbox("Enable", cfg.act_enable or false) dlg.act_dis = dialog.AddCheckbox("Disable", cfg.act_disable or false) dlg.act_rm = dialog.AddCheckbox("Remove", cfg.act_remove or false) dlg.act_l_actual = dialog.AddCheckbox("Len = actual", cfg.act_len_to_actual or false) -- Buttons (order per request) dlg.btn_exit = dialog.AddButton("Exit", 0) dlg.btn_report = dialog.AddButton("Report", 10) dlg.btn_preview = dialog.AddButton("Preview", 11) dlg.btn_apply = dialog.AddButton("Apply", 12) return dlg end local function read_dialog(dlg, cfg) cfg = cfg or {} -- Type/status cfg.include_segment = dlg.f_seg and dlg.f_seg.value or false cfg.include_sidechain = dlg.f_atom and dlg.f_atom.value or false cfg.include_bis = dlg.f_bis and dlg.f_bis.value or false cfg.include_enabled = dlg.f_en and dlg.f_en.value or false cfg.include_disabled = dlg.f_dis and dlg.f_dis.value or false -- Strength cfg.strength_min = dlg.f_s_min and dlg.f_s_min.value or cfg.strength_min cfg.strength_max = dlg.f_s_max and dlg.f_s_max.value or cfg.strength_max if cfg.strength_min and cfg.strength_max and cfg.strength_min > cfg.strength_max then cfg.strength_min, cfg.strength_max = cfg.strength_max, cfg.strength_min end -- Length cfg.length_min = dlg.f_l_min and dlg.f_l_min.value or cfg.length_min cfg.length_max = dlg.f_l_max and dlg.f_l_max.value or cfg.length_max if cfg.length_min and cfg.length_max and cfg.length_min > cfg.length_max then cfg.length_min, cfg.length_max = cfg.length_max, cfg.length_min end -- Group cfg.group_index = math.floor(dlg.grp_idx and dlg.grp_idx.value or 0) -- Actions cfg.act_set_strength = dlg.act_s_on and dlg.act_s_on.value or false cfg.action_strength_value = dlg.act_s_val and dlg.act_s_val.value or cfg.action_strength_value cfg.act_set_length = dlg.act_l_on and dlg.act_l_on.value or false cfg.action_length_value = dlg.act_l_val and dlg.act_l_val.value or cfg.action_length_value cfg.act_len_to_actual = dlg.act_l_actual and dlg.act_l_actual.value or false cfg.act_enable = dlg.act_en and dlg.act_en.value or false cfg.act_disable = dlg.act_dis and dlg.act_dis.value or false cfg.act_remove = dlg.act_rm and dlg.act_rm.value or false return cfg end ------------------------- -- Main loop ------------------------- local function main() if not has_band_api() then print("Band API is not available in this environment.") return end undo.SetUndo(false) local cfg = { -- Filters defaults include_segment = true, include_sidechain = true, include_bis = true, include_enabled = true, include_disabled = true, strength_min = nil, strength_max = nil, length_min = nil, length_max = nil, -- Group & action defaults group_index = 0, action_strength_value = 1.0, action_length_value = 2.0, -- Remember last applied defaults for auto-apply fallback def_strength_value = 1.0, def_length_value = 2.0, } -- Initial state and prints update_state(cfg) print_help() print("") print_groups(cfg.groups) while true do update_state(cfg) local dlg = build_dialog(cfg) local rv = dialog.Show(dlg) cfg = read_dialog(dlg, cfg) if rv == 0 then print("Exit requested by user.") return elseif rv == 10 then print_report(cfg) elseif rv == 11 then local sel = select_bands(cfg) preview_selection(sel) elseif rv == 12 then local sel = select_bands(cfg) apply_action(sel, cfg) break else -- Unknown button or closed: loop again end end undo.SetUndo(true) save.Quicksave(100) save.Quickload(100) end -- Entry main()

Comments