From dee0bb7b4e243db7a4a2a933fa6a546858832853 Mon Sep 17 00:00:00 2001 From: cv-on-hub <105578039+cv-on-hub@users.noreply.github.com> Date: Fri, 26 Jan 2024 08:08:27 +1100 Subject: [PATCH 1/2] Update selection_refiner.lua simpler staff selection using popup menus; simpler key command mapping; some bolding in dialog --- src/selection_refiner.lua | 257 ++++++++++++++++++++------------------ 1 file changed, 137 insertions(+), 120 deletions(-) diff --git a/src/selection_refiner.lua b/src/selection_refiner.lua index 7f9004d1..b2c803c0 100644 --- a/src/selection_refiner.lua +++ b/src/selection_refiner.lua @@ -3,8 +3,8 @@ function plugindef() finaleplugin.Author = "Carl Vine" finaleplugin.AuthorURL = "https://carlvine.com/lua/" finaleplugin.Copyright = "CC0 https://creativecommons.org/publicdomain/zero/1.0/" - finaleplugin.Version = "0.53" - finaleplugin.Date = "2023/12/11" + finaleplugin.Version = "0.57" + finaleplugin.Date = "2024/01/25" finaleplugin.CategoryTags = "Measures, Region, Selection" finaleplugin.MinJWLuaVersion = 0.67 finaleplugin.Notes = [[ @@ -15,7 +15,7 @@ function plugindef() continuously updating the score highlighting as the selection changes. Note that when one slider overlaps the other in the same - measure, it will be pushed out of the way to create a "null" + measure, it will push the other out of the way creating a "null" selection (start = end). This doesn't break anything but the selection contains no notes. @@ -28,11 +28,19 @@ function plugindef() relative to the beat, as happens when entering beat numbers on the inbuilt "Select Region" option. - == Key Commands == - - START point: [w][s][a][d] (up-down-left-right) increments [e][r] - - END point: [f][v][c][b] (up-down-left-right) increments [g][h] - - [z] toggle the "follow selection" checkbox. - - [q] show these script notes + == Key Commands == + - (w)(s) @tStart Staff up/down + - (d)(f) @tStart Measure left/right + - (g)(h) @tStart increments -/+ + - (j)(k) / (-)(+) @tStart -/+ one EDU + - • • + - (a)(z) @tEnd Staff up/down + - (x)(c) @tEnd Measure left/right + - (v)(b) @tEnd increments -/+ + - (n)(m) / ([)(]) @tEnd -/+ one EDU + - • • + - (e) toggle the "follow selection" checkbox + - (q) show these script notes ]] return "Selection Refiner...", "Selection Refiner", "Refine the selected music area with visual feedback" end @@ -45,7 +53,7 @@ slider controls to change the beat and EDU position in each measure, continuously updating the score highlighting as the selection changes. ** Note that when one slider overlaps the other in the same -measure, it will be pushed out of the way to create a "null" +measure, it will push the other out of the way creating a "null" selection (start = end). This doesn't break anything but the selection contains no notes. ** @@ -60,22 +68,32 @@ relative to the beat, as happens when entering beat numbers on the inbuilt "Select Region" option. ** == Key Commands == -*• (w)(s)(a)(d) @tSTART (up-down-left-right) -*• (e/r) @t@tSTART increments -*• (+/-) (t/y) @tSTART +/- one EDU -*• (f)(v)(c)(b) @tEND (up-down-left-right) -*• (g/h) @t@tEND increments -*• ( [/] ) (j/k) @tEND +/- one EDU -*• (z) toggle the "follow selection" checkbox +*• (w)(s) @tStart Staff up/down +*• (d)(f) @tStart Measure left/right +*• (g)(h) @tStart increments -/+ +*• (j)(k) / (-)(+) @tStart -/+ one EDU +*• • • +*• (a)(z) @tEnd Staff up/down +*• (x)(c) @tEnd Measure left/right +*• (v)(b) @tEnd increments -/+ +*• (m)(n) / ([)(]) @tEnd -/+ one EDU +*• • • +*• (e) toggle the "follow selection" checkbox *• (q) show these script notes ]] info_notes = info_notes:gsub("\n%s*", " "):gsub("*", "\n"):gsub("@t", "\t") + .. "\n(v" .. finaleplugin.Version .. ")" + +local config = { + follow_measure = 0, -- follow selection beyond the visible screen? (== 1) + window_pos_x = false, + window_pos_y = false +} -local config = { window_pos_x = false, window_pos_y = false, follow_measure = 1 } local mixin = require("library.mixin") local library = require("library.general_library") local configuration = require("library.configuration") -local script_name = "selection_refiner" +local script_name = library.calc_script_name() local function dialog_set_position(dialog) if config.window_pos_x and config.window_pos_y then @@ -92,27 +110,33 @@ local function dialog_save_position(dialog) configuration.save_user_settings(script_name, config) end -local function power_of_2(duration) - local test_rest = finale.NOTE_128TH / 2 -- smallest duration = 256th note +local function power_of_two(duration) + local smallest = finale.NOTE_128TH / 2 -- smallest duration = 256th note local power = 1 - while test_rest < duration and power < 10 do - test_rest = test_rest * 2 + while smallest < duration and power < 10 do + smallest = smallest * 2 power = power + 1 end return power -- 256th note = 1 ... breve = 10 end local function score_limits(rgn) + local staff = finale.FCStaff() + local staff_list = {} local stack = mixin.FCMMusicRegion() - stack:SetRegion(rgn):SetFullMeasureStack() + stack:SetRegion(rgn):SetFullDocument() + for staff_number in eachstaff(stack) do + staff:Load(staff_number) + -- {staff_list} index number matches SLOT number + table.insert(staff_list, staff:CreateDisplayFullNameString()) + end local max_slot = stack.EndSlot - stack:SetFullDocument() - return stack.EndMeasure, max_slot + return stack.EndMeasure, max_slot, staff_list end local function compile_rest_strings(power) power = math.min(math.max(power, 1), 10) -- maximum exponent of 2 as "beat" rhythm - -- non-SMuFL font characters first + -- non-SMuFL font REST characters first local rests = { "…", "Â", "Ù", "®", "≈", "‰", "Œ", "Ó", "∑", "„" } -- 256th to breve local array = { dot = "k", space = " ", vert = 18 } if library.is_font_smufl_font() then -- SMuFL @@ -170,7 +194,7 @@ local function get_measure_details(region, is_start_sector) md.mark = md.beatdur / 2 -- first-division marker = half of beat md.steps = 8 -- divisions per beat end - local power = power_of_2(md.mark * 2) -- 2 ^ power exponent to index notehead durations + local power = power_of_two(md.mark * 2) -- 2 ^ power exponent to index notehead durations md.div_dur = md.beatdur / md.steps -- duration of each division md.divisions = md.beats * md.steps -- total number of divisions if md.composite then @@ -188,13 +212,6 @@ local function get_measure_details(region, is_start_sector) return md end -local function get_staff_name(region, slot) - local staff_number = region:CalcStaffNumber(slot) - local staff = finale.FCStaff() - staff:Load(staff_number) - return staff:CreateDisplayFullNameString() -end - local function convert_edu_to_rest_string(index, md, backwards) if backwards then index = md.divisions - index end local beat = md.rests.beat @@ -228,20 +245,21 @@ end local function user_chooses(rgn) local y, rest_wide, x_wide = 40, 130, 236 local x_offset = finenv.UI():IsOnMac() and 0 or 3 + local name = plugindef():gsub("%.%.%.", "") local function yd(diff) y = diff and y + diff or y + 16 end local function show_info() - finenv.UI():AlertInfo(info_notes, "About " .. plugindef()) + finenv.UI():AlertInfo(info_notes, "About " .. name) end -- indicator and control arrays for "start" [1] and "end" [2]: - local measure, sliders, offset, save_off = {}, {}, {}, {} - local rest, buttons, index, staff, actions = {}, {}, {}, {}, {} + local measure, sliders, offset, save_off, header = {}, {}, {}, {}, {} + local rest, buttons, index, staff_sel, actions = {}, {}, {}, {}, {} local follow - local max_measure, max_slot = score_limits(rgn) + local max_measure, max_slot, staff_list = score_limits(rgn) - -- MD :: MEASURE DETAILS - local md = { get_measure_details(rgn, true), get_measure_details(rgn, false) } -- { start, end } + -- MD :: MEASURE DETAILS md = .{ {start}, {end} } + local md = { get_measure_details(rgn, true), get_measure_details(rgn, false) } local function pos_to_index(side) -- convert selection position (edu) to thumb index return math.floor(md[side].pos * md[side].divisions / md[side].dur) end @@ -249,7 +267,7 @@ local function user_chooses(rgn) index[2] = pos_to_index(2) -- start dialog - local dialog = mixin.FCXCustomLuaWindow():SetTitle(plugindef()) + local dialog = mixin.FCXCustomLuaWindow():SetTitle(name) -- local functions local function set_measure_pos(side) if side == 1 then rgn.StartMeasurePos = md[side].pos @@ -302,15 +320,6 @@ local function user_chooses(rgn) rgn:SetInDocument() rgn:Redraw() end - local function staff_button_visibility() - for side = 1, 2 do - buttons[side].up:SetEnable(md[side].slot > 1) - buttons[side].down:SetEnable(md[side].slot < max_slot) - staff[side]:SetText(get_staff_name(rgn, md[side].slot)) - end - rgn:SetInDocument() - rgn:Redraw() - end local function position_increment(side, add) if (add > 0 and md[side].pos < md[side].dur) or (add < 0 and md[side].pos > 0) then @@ -324,39 +333,39 @@ local function user_chooses(rgn) ---- "action" routines for buttons and keystrokes actions = { - up = function(a_side) - if md[a_side].slot > 1 then - md[a_side].slot = md[a_side].slot - 1 - if a_side == 1 then - rgn.StartSlot = md[1].slot - else -- side 2 - rgn.EndSlot = md[2].slot - if md[1].slot > md[2].slot then - md[1].slot = md[2].slot - rgn.StartSlot = md[2].slot - end + staff = function(a_side) -- a staff popup has changed + local new_slot = staff_sel[a_side]:GetSelectedItem() + 1 + if new_slot == md[a_side].slot then return end -- no change + if a_side == 1 then -- "start" staff popup + rgn.StartSlot = new_slot + if new_slot > (staff_sel[2]:GetSelectedItem() + 1) then + staff_sel[2]:SetSelectedItem(new_slot - 1) + md[2].slot = new_slot + rgn.EndSlot = new_slot + end + else -- a_side == 2 / "end" staff popup + rgn.EndSlot = new_slot + if new_slot < (staff_sel[1]:GetSelectedItem() + 1) then + staff_sel[1]:SetSelectedItem(new_slot - 1) + md[1].slot = new_slot + rgn.StartSlot = new_slot end - staff_button_visibility() end + staff_sel[a_side]:SetSelectedItem(new_slot - 1) + md[a_side].slot = new_slot + rgn:SetInDocument() + rgn:Redraw() end, ------- - down = function(a_side) - if md[a_side].slot < max_slot then - md[a_side].slot = md[a_side].slot + 1 - if a_side == 1 then - rgn.StartSlot = md[1].slot - if md[2].slot < md[1].slot then - md[2].slot = md[1].slot - rgn.EndSlot = md[1].slot - end - else - rgn.EndSlot = md[2].slot - end - staff_button_visibility() + change_staff = function(a_side, diff) -- change index of staff popup + local slot = staff_sel[a_side]:GetSelectedItem() + 1 + if (slot > 1 and diff < 0) or (slot < max_slot and diff > 0) then + staff_sel[a_side]:SetSelectedItem(slot + diff - 1) end + actions.staff(a_side) -- register this index change end, ------- - left = function(a_side) + left = function(a_side) -- move measure to left local other_side = (a_side % 2) + 1 if md[a_side].measure > 1 then md[a_side].measure = md[a_side].measure - 1 @@ -380,7 +389,7 @@ local function user_chooses(rgn) end end, ------- - right = function(a_side) + right = function(a_side) -- move measure to right local other_side = (a_side % 2) + 1 if md[a_side].measure < max_measure then md[a_side].measure = md[a_side].measure + 1 @@ -404,7 +413,7 @@ local function user_chooses(rgn) end end, ------- - slide = function(i) + slide = function(i) -- a slider has changed local thumb = sliders[i]:GetThumbPosition() local other_side = (i % 2) + 1 set_rest_and_offset(i, thumb) @@ -428,7 +437,7 @@ local function user_chooses(rgn) rgn:Redraw() end, ------ - thumb = function(i, dir) + thumb = function(i, dir) -- move slider thumb by discrete amount local n = sliders[i]:GetThumbPosition() sliders[i]:SetThumbPosition(n + dir) actions.slide(i) @@ -438,23 +447,23 @@ local function user_chooses(rgn) local s = offset[i]:GetText():lower() if s:find("[^0-9]") then if s:find("[?q]") then show_info() - elseif s:find("w") then actions.up(1) - elseif s:find("s") then actions.down(1) - elseif s:find("a") then actions.left(1) - elseif s:find("d") then actions.right(1) - elseif s:find("e") then actions.thumb(1, -1) - elseif s:find("r") then actions.thumb(1, 1) - elseif s:find("f") then actions.up(2) - elseif s:find("v") then actions.down(2) - elseif s:find("c") then actions.left(2) - elseif s:find("b") then actions.right(2) - elseif s:find("g") then actions.thumb(2, -1) - elseif s:find("h") then actions.thumb(2, 1) - elseif s:find("[-_t]") then position_increment(1, -1) - elseif s:find("[+=y]") then position_increment(1, 1) - elseif s:find("[%[j]") then position_increment(2, -1) - elseif s:find("[%]k]") then position_increment(2, 1) - elseif s:find("z") then + elseif s:find("w") then actions.change_staff(1, -1) + elseif s:find("s") then actions.change_staff(1, 1) + elseif s:find("d") then actions.left(1) + elseif s:find("f") then actions.right(1) + elseif s:find("g") then actions.thumb(1, -1) + elseif s:find("h") then actions.thumb(1, 1) + elseif s:find("a") then actions.change_staff(2, -1) + elseif s:find("z") then actions.change_staff(2, 1) + elseif s:find("x") then actions.left(2) + elseif s:find("c") then actions.right(2) + elseif s:find("b") then actions.thumb(2, 1) + elseif s:find("v") then actions.thumb(2, -1) + elseif s:find("[-_j]") then position_increment(1, -1) + elseif s:find("[+=k]") then position_increment(1, 1) + elseif s:find("[%[n]") then position_increment(2, -1) + elseif s:find("[%]m]") then position_increment(2, 1) + elseif s:find("e") then follow:SetCheck((follow:GetCheck() + 1) % 2) end offset[i]:SetText(save_off[i]) @@ -475,18 +484,14 @@ local function user_chooses(rgn) local default_font = finale.FCFontInfo() default_font:LoadFontPrefs(finale.FONTPREF_MUSIC) - local button_x = (x_wide + rest_wide + 14) / 4 -- space buttons evenly - local bx12 = button_x + 12 + local button_x = (x_wide + rest_wide + 14) / 5 -- space buttons evenly by fifths local function make_rest_text(i, y_off) rest[i] = dialog:CreateStatic(x_wide + 65, y_off + md[i].rests.vert) :SetWidth(rest_wide):SetHeight(80):SetFont(default_font) :SetText(convert_edu_to_rest_string(index[i], md[i], false)) end - local function make_staff_name(i) - staff[i] = dialog:CreateStatic(bx12 + 25, y):SetWidth(rest_wide) - :SetText(get_staff_name(rgn, md[i].slot)) - end + local function make_slider_and_offset(i) sliders[i] = dialog:CreateSlider(0, y):SetMinValue(0) :SetWidth(x_wide):SetMaxValue(md[i].divisions) @@ -495,47 +500,59 @@ local function user_chooses(rgn) offset[i] = dialog:CreateEdit(x_wide + 7, y - x_offset):SetInteger(md[i].pos) :AddHandleCommand(function() actions.offset(i) end):SetWidth(50) end + local function make_buttons(i) + -- first the staff-name popup menu + staff_sel[i] = dialog:CreatePopup(0, y) + :AddStrings(table.unpack(staff_list)):SetWidth(button_x * 2) -- 2 fifths + :SetSelectedItem(md[i].slot - 1) + :AddHandleCommand(function() actions.staff(i) end) + -- then the two "measure" buttons buttons[i] = {} - for k, v in pairs({ - up = { bx12, y, "Staff ↑" }, down = { bx12, y + 20, "Staff ↓" }, - left = { 0, y + 10, "← Measure" }, right = { bx12 * 2, y + 10, "Measure →" } - }) do - buttons[i][k] = dialog:CreateButton(v[1], v[2]):SetWidth(button_x) - :AddHandleCommand(function() actions[k](i) end):SetText(v[3]) + for k, v in pairs{ + left = { button_x * 2 + 5, "← Measure" }, right = { button_x * 3 + 5, "Measure →" } + } do + buttons[i][k] = dialog:CreateButton(v[1], y):SetWidth(button_x - 5) + :AddHandleCommand(function() actions[k](i) end):SetText(v[2]) end - measure[i] = dialog:CreateStatic(bx12 * 3, y + 10):SetWidth(60) + measure[i] = dialog:CreateStatic(button_x * 4 + 5, y):SetWidth(button_x - 5) :SetText("m. " .. md[2].measure) end -- "rest" static texts go first so high MUSIC font height doesn't overlap buttons make_rest_text(1, 0) - make_rest_text(2, 83) - dialog:CreateStatic(0, y):SetText("START of Selection:"):SetWidth(x_wide) - make_staff_name(1) - dialog:CreateButton(x_wide + rest_wide + 30, y):SetText("?"):SetWidth(20) + make_rest_text(2, 78) + -- "start" components + dialog:CreateStatic(0, y, "head_1"):SetText("START of Selection:"):SetWidth(x_wide) + dialog:CreateButton(x_wide + rest_wide + 30, y, "q"):SetText("?"):SetWidth(20) :AddHandleCommand(function() show_info() end) yd(14) make_slider_and_offset(1) - yd(22) + yd(30) make_buttons(1) - yd(42) - dialog:CreateHorizontalLine(0, y, button_x * 4) + yd(30) + dialog:CreateHorizontalLine(0, y, button_x * 5) yd(5) -- "end" components - dialog:CreateStatic(0, y):SetText("END of Selection:"):SetWidth(x_wide) - make_staff_name(2) + dialog:CreateStatic(0, y, "head_2"):SetText("END of Selection:"):SetWidth(x_wide) yd(14) make_slider_and_offset(2) - yd(22) + yd(30) make_buttons(2) - yd(42) + yd(30) follow = dialog:CreateCheckbox(0, y, "follow_measure"):SetWidth(x_wide) :SetText("Follow selection to off-screen measures"):SetCheck(config.follow_measure) dialog:CreateOkButton() dialog:CreateCancelButton() dialog_set_position(dialog) dialog:RegisterHandleOkButtonPressed(function() config.follow_measure = follow:GetCheck() end) + dialog:RegisterInitWindow(function(self) + local h = self:GetControl("head_1") + local bold = h:CreateFontInfo():SetBold(true) + h:SetFont(bold) + self:GetControl("head_2"):SetFont(bold) + self:GetControl("q"):SetFont(bold) + end) dialog:RegisterCloseWindow(function(self) dialog_save_position(self) end) return (dialog:ExecuteModal(nil) == finale.EXECMODAL_OK) end From 9c3c4a985c6cafda4b0c00de897a1b9a2787569d Mon Sep 17 00:00:00 2001 From: cv-on-hub <105578039+cv-on-hub@users.noreply.github.com> Date: Wed, 31 Jan 2024 14:40:14 +1100 Subject: [PATCH 2/2] Update selection_refiner.lua Fixed the lint warning on line 256. --- src/selection_refiner.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/selection_refiner.lua b/src/selection_refiner.lua index b2c803c0..a3767667 100644 --- a/src/selection_refiner.lua +++ b/src/selection_refiner.lua @@ -253,7 +253,7 @@ local function user_chooses(rgn) finenv.UI():AlertInfo(info_notes, "About " .. name) end -- indicator and control arrays for "start" [1] and "end" [2]: - local measure, sliders, offset, save_off, header = {}, {}, {}, {}, {} + local measure, sliders, offset, save_off = {}, {}, {}, {} local rest, buttons, index, staff_sel, actions = {}, {}, {}, {}, {} local follow local max_measure, max_slot, staff_list = score_limits(rgn)