Skip to content

Commit aa6cbd2

Browse files
authored
Merge pull request #660 from cv-on-hub/cv_meter_set_numeric
Update meter_set_numeric.lua
2 parents 83b9392 + b3b3204 commit aa6cbd2

File tree

1 file changed

+150
-81
lines changed

1 file changed

+150
-81
lines changed

src/meter_set_numeric.lua

+150-81
Original file line numberDiff line numberDiff line change
@@ -3,32 +3,36 @@ function plugindef()
33
finaleplugin.Author = "Carl Vine"
44
finaleplugin.AuthorURL = "https://carlvine.com/lua/"
55
finaleplugin.Copyright = "https://creativecommons.org/licenses/by/4.0/"
6-
finaleplugin.Version = "0.70"
7-
finaleplugin.Date = "2023/07/25"
6+
finaleplugin.Version = "0.83"
7+
finaleplugin.Date = "2024/01/27"
88
finaleplugin.MinJWLuaVersion = 0.60
9-
finaleplugin.Notes = [[
10-
This script provides rapid entry of simple or complex
9+
finaleplugin.Notes = [[
10+
This script allows rapid creation of simple or complex
1111
time signatures with a few keystrokes.
1212
It supports composite numerators like [3+2+3/16] and can join
13-
with further composites (e.g. [3+2+3/16]+[1/4]+[5+4/8]).
14-
"Display only" time signatures can be equally complex and set without using a mouse.
13+
easily with extra composites (e.g. [3+2+3/16]+[1/4]+[5+4/8]).
14+
"Display only" time signatures can be equally complex and set without using a mouse.
15+
1516
At startup the time signature of the first selected measure is shown.
16-
Click the "Clear All" button to revert to a simple 4/4 with no other options.
17+
To revert to a simple 4/4 with no other options click the "Clear All" button or type [x].
18+
To read these script notes click the [?] button or type [q].
19+
To respace notes on completion click the "Respace" button or type [r].
1720
1821
All measures in the current selection will be assigned the new time signature.
22+
Use this feature to quickly copy the initial meter throughout the selection.
1923
If just one measure is selected only it will be changed.
2024
21-
"Bottom" numbers (denominators) are the usual "note" numbers: 2, 4, 8, 16, 32, 64.
25+
"Bottom" numbers (denominators) are the usual "note" numbers: 2, 4, 8, 16, 32, or 64.
2226
"Top" numbers (numerators) are integers, optionally joined by '+' signs for composite meters.
23-
Multiples of 3 automatically convert to compound signatures so [9/16] will
24-
convert to three groups of dotted 8ths.
25-
To prevent automatic compounding, instead of the bottom 'note' number enter its EDU value
26-
(quarter note = 1024; eighth note = 512 etc).
27+
Numerators that are multiples of 3 automatically convert to compound signatures
28+
so [9/16] will convert to three groups of dotted 8ths.
29+
To prevent automatic compounding, instead of the bottom 'note' number enter its
30+
EDU value (quarter note = 1024; eighth note = 512; sixteenth = 256 etc).
2731
2832
Empty and zero "Top" numbers will be ignored.
29-
If "Secondary" numbers are zero then "Tertiary" values are ignored.
33+
"Tertiary" values will be ignored if "Secondary" numbers are blank or zero.
3034
]]
31-
return "Meter Set Numeric", "Meter Set Numeric", "Set the Meter Numerically"
35+
return "Meter Set Numeric...", "Meter Set Numeric", "Set the Meter Numerically"
3236
end
3337

3438
--[[
@@ -45,130 +49,196 @@ end
4549
}
4650
]]
4751

52+
local info_notes = [[
53+
This script allows rapid creation of simple or complex
54+
time signatures with a few keystrokes.
55+
It supports composite numerators like [3+2+3/16] and can join
56+
easily with extra composites (e.g. [3+2+3/16]+[1/4]+[5+4/8]).
57+
"Display only" time signatures can be equally complex and set without using a mouse.
58+
**
59+
At startup the time signature of the first selected measure is shown.
60+
To revert to a simple 4/4 with no other options click the "Clear All" button or type [x].
61+
To read these script notes click the [?] button or type [q].
62+
To respace notes on completion click the "Respace" button or type [r].
63+
**
64+
All measures in the current selection will be assigned the new time signature.
65+
Use this feature to quickly copy the initial meter throughout the selection.
66+
If just one measure is selected only it will be changed.
67+
**
68+
"Bottom" numbers (denominators) are the usual "note" numbers: 2, 4, 8, 16, 32, or 64.
69+
"Top" numbers (numerators) are integers, optionally joined by '+' signs for composite meters.
70+
Numerators that are multiples of 3 automatically convert to compound signatures
71+
so [9/16] will convert to three groups of dotted 8ths.
72+
To prevent automatic compounding, instead of the bottom 'note' number enter its
73+
EDU value (quarter note = 1024; eighth note = 512; sixteenth = 256 etc).
74+
**
75+
Empty and zero "Top" numbers will be ignored.
76+
"Tertiary" values will be ignored if "Secondary" numbers are blank or zero.
77+
]]
78+
info_notes = info_notes:gsub("\n%s*", " "):gsub("*", "\n")
79+
.. "\n(v" .. finaleplugin.Version .. ")"
80+
4881
local mixin = require("library.mixin")
4982
local configuration = require("library.configuration")
83+
local library = require("library.general_library")
84+
local script_name = library.calc_script_name()
85+
5086
local config = {
5187
note_spacing = true,
5288
window_pos_x = false,
5389
window_pos_y = false,
5490
}
55-
local script_name = "meter_set_numeric"
5691
configuration.get_user_settings(script_name, config, true)
5792

58-
function blank_meter()
93+
local function blank_meter()
5994
return { -- empty composite meter record
60-
main = { top = {}, bottom = {} },
61-
display = { top = {}, bottom = {} }
95+
main = { top = {}, bottom = {} },
96+
display = { top = {}, bottom = {} }
6297
}
6398
end
6499

65-
function dialog_set_position(dialog)
100+
local function dialog_set_position(dialog)
66101
if config.window_pos_x and config.window_pos_y then
67102
dialog:StorePosition()
68103
dialog:SetRestorePositionOnlyData(config.window_pos_x, config.window_pos_y)
69104
dialog:RestorePosition()
70105
end
71106
end
72107

73-
function dialog_save_position(dialog)
108+
local function dialog_save_position(dialog)
74109
dialog:StorePosition()
75110
config.window_pos_x = dialog.StoredX
76111
config.window_pos_y = dialog.StoredY
77112
configuration.save_user_settings(script_name, config)
78113
end
79114

80-
function user_chooses_meter(meter, rgn)
115+
local function user_chooses_meter(meter, rgn)
81116
local x = { 0, 70, 130, 210, 280, 290 } -- horizontal grid
82-
local label = {"PRIMARY", "(+ SECONDARY)", "(+ TERTIARY)"}
83-
local label_off = { 0, -37, -21 } -- horizontal offset for these names (ranged right)
117+
local label = { -- data type descriptors and (range right) horizontal offset
118+
{ "PRIMARY", 0}, -- name, horiz (left) offset
119+
{ "(+ SECONDARY)", -37 },
120+
{ "(+ TERTIARY)", -21 }
121+
}
84122
local y_middle = 118 -- window vertical mid-point
85123
local offset = finenv.UI():IsOnMac() and 3 or 0
124+
local name = plugindef():gsub("%.%.%.", "") -- remove trailing dots
125+
local function show_info() finenv.UI():AlertInfo(info_notes, "About " .. name) end
126+
local box, save_text = {}, {} -- user's edit-box entry responses
86127
local message = "m. " .. rgn.StartMeasure -- measure selection information
87128
if rgn.StartMeasure ~= rgn.EndMeasure then -- multiple measures
88129
message = "m" .. message .. "-" .. rgn.EndMeasure
89130
end
131+
local respace_check -- pre-declare the "respace" checkbox
132+
90133
local y = 0
91-
local dialog = mixin.FCXCustomLuaWindow():SetTitle(plugindef())
92-
-- STATIC TEXT ELEMENTS
93-
local function cstat(nx, ny, nwide, ntext)
94-
local dx = (type(nx) == "number") and x[nx] or tonumber(nx)
95-
return dialog:CreateStatic(dx, ny):SetWidth(nwide):SetText(ntext)
96-
end
97-
local function join(values) -- join integers from the meter.top array with '+' signs
98-
if not values or #values == 0 then return "0" end
99-
return table.concat(values, "+")
100-
end
101-
local shadow = cstat("1", y + 1, x[3], "TIME SIGNATURE")
102-
if shadow.SetTextColor then shadow:SetTextColor(153, 153, 153) end
103-
cstat(1, y, x[3], "TIME SIGNATURE")
134+
local dialog = mixin.FCXCustomLuaWindow():SetTitle(name)
135+
local function cstat(nx, ny, nwide, ntext, id)
136+
local dx = (type(nx) == "number") and x[nx] or tonumber(nx)
137+
return dialog:CreateStatic(dx, ny, id):SetWidth(nwide):SetText(ntext)
138+
end
139+
local function join(values) -- join integers from the meter.top array with '+' signs
140+
if not values or #values == 0 then return "0" end
141+
return table.concat(values, "+")
142+
end
143+
local function clear_entries()
144+
for j = 0, 6, 6 do
145+
for i = 1, 3 do
146+
local n = (j == 0 and i == 1) and 4 or 0
147+
box[j + i]:SetText(n)
148+
box[j + i + 3]:SetInteger(n)
149+
save_text[j + i] = n
150+
save_text[j + i + 3] = n
151+
end
152+
end
153+
box[1]:SetKeyboardFocus()
154+
end
155+
local function key_check(id)
156+
local s = box[id]:GetText():lower()
157+
if s:find("[^0-9+]") then
158+
if s:find("x") then
159+
clear_entries()
160+
else
161+
if s:find("[?q]") then
162+
show_info()
163+
elseif s:find("r") then
164+
local n = respace_check:GetCheck()
165+
respace_check:SetCheck((n + 1) % 2)
166+
end
167+
box[id]:SetText(save_text[id])
168+
end
169+
elseif s ~= "" then
170+
save_text[id] = s -- save newly entered text
171+
end
172+
end
173+
174+
cstat(1, y + 1, x[3], "TIME SIGNATURE", "main")
104175
cstat(3, y, x[3], "TOP")
105176
cstat(4, y, x[3], "BOTTOM")
106177
cstat(6, 25, 80, "Selection:")
107178
cstat(6, 40, 80, message)
179+
dialog:CreateButton(x[5], y):SetWidth(80):SetText("Clear All (x)")
180+
:AddHandleCommand(function() clear_entries() end)
108181
y = y_middle
109182
cstat(x[2] - 30, y - 30, 330, "'TOP' entries can include integers joined by '+' signs")
110183
dialog:CreateHorizontalLine(x[1], y - 9, x[5] + 50)
111184
dialog:CreateHorizontalLine(x[1], y - 8, x[5] + 50)
112-
shadow = cstat("1", y + 1, x[3], "DISPLAY SIGNATURE")
113-
if shadow.SetTextColor then shadow:SetTextColor(153, 153, 153) end
114-
cstat(1, y, x[3], "DISPLAY SIGNATURE")
185+
cstat(1, y + 1, x[3], "DISPLAY SIGNATURE", "second")
115186
cstat(3, y, 150, "(set to '0' for none)")
116-
dialog:CreateButton(x[5] + 60, y):SetText("?"):SetWidth(20)
117-
:AddHandleCommand(function()
118-
finenv.UI():AlertInfo(finaleplugin.Notes:gsub(" %s+", " "), "About " .. plugindef())
119-
end)
120-
-- USER EDIT BOXES
187+
188+
-- COMPOSITE TIME SIG BOXES
121189
y = 0
122-
local box = {} -- user's edit-box entry responses
123190
for jump = 0, 6, 6 do
124191
local t_sig = (jump == 0) and meter.main or meter.display
125192
for group = 1, 3 do
126193
y = y + 20
127194
local id = group + jump
128-
box[id] = dialog:CreateEdit(x[3], y - offset, tostring(id))
195+
box[id] = dialog:CreateEdit(x[3], y - offset)
129196
:SetText(join(t_sig.top[group])):SetWidth(65)
130-
box[id + 3] = dialog:CreateEdit(x[4], y - offset, tostring(id + 3))
197+
:AddHandleCommand(function() key_check(id) end)
198+
box[id + 3] = dialog:CreateEdit(x[4], y - offset)
131199
:SetInteger(t_sig.bottom[group] or 0):SetWidth(65)
132-
cstat(x[2] + label_off[group], y, 56 - label_off[group], label[group])
200+
:AddHandleCommand(function() key_check(id + 3) end)
201+
cstat(x[2] + label[group][2], y, 56 - label[group][2], label[group][1])
202+
save_text[id] = box[id]:GetText()
203+
save_text[id + 3] = box[id + 3]:GetText()
133204
end
134205
y = y_middle
135206
end
136-
dialog:CreateCheckbox(x[3], y_middle + 85, "spacing"):SetText("Respace notes on completion")
137-
:SetCheck(config.note_spacing and 1 or 0):SetWidth(170)
138-
local clear_button = dialog:CreateButton(x[5], 0):SetWidth(80):SetText("Clear All")
139-
clear_button:AddHandleCommand(function()
140-
box[1]:SetText("4")
141-
box[4]:SetInteger(4)
142-
for _, i in ipairs({2, 3, 7, 8, 9}) do -- "clear" the other edit boxes
143-
box[i]:SetText("0")
144-
box[i + 3]:SetInteger(0)
145-
end
146-
box[1]:SetKeyboardFocus()
147-
end
148-
)
207+
y = y + 60
208+
dialog:CreateButton(x[5] + 60, y, "q"):SetText("?"):SetWidth(20)
209+
:AddHandleCommand(function() show_info() end)
210+
y = y + 25
211+
respace_check = dialog:CreateCheckbox(x[3], y):SetText("Respace notes on completion (r)")
212+
:SetCheck(config.note_spacing and 1 or 0):SetWidth(185)
149213
dialog:CreateOkButton()
150214
dialog:CreateCancelButton()
151215
local choices = {} -- convert edit box values to 12-element table
152-
dialog:RegisterInitWindow(function() box[1]:SetKeyboardFocus() end)
153-
-- "TOP" values are strings, "BOTTOMS" are integers
154-
dialog:RegisterHandleOkButtonPressed(function(self)
155-
for count = 0, 6, 6 do -- "main" then "display" t_sig values
216+
dialog:RegisterInitWindow(function(self)
217+
box[1]:SetKeyboardFocus()
218+
local q = self:GetControl("q")
219+
local bold = q:CreateFontInfo():SetBold(true)
220+
q:SetFont(bold)
221+
self:GetControl("main"):SetFont(bold)
222+
self:GetControl("second"):SetFont(bold)
223+
end)
224+
dialog:RegisterHandleOkButtonPressed(function()
225+
for count = 0, 6, 6 do -- "main" then "display" time_sig values
156226
for group = 1, 3 do
157227
local id = count + group
158-
choices[id] = self:GetControl(tostring(id)):GetText() or "0"
228+
choices[id] = box[id]:GetText() or "0"
159229
if choices[id] == "" then choices[id] = "0" end
160-
choices[id + 3] = math.abs(self:GetControl(tostring(id + 3)):GetInteger()) or 0
230+
choices[id + 3] = box[id + 3]:GetInteger() or 0
161231
end
162232
end
163-
config.note_spacing = (self:GetControl("spacing"):GetCheck() == 1)
164-
dialog_save_position(self) -- save window position and config choices
233+
config.note_spacing = (respace_check:GetCheck() == 1)
165234
end)
235+
dialog:RegisterCloseWindow(function(self) dialog_save_position(self) end)
166236
dialog_set_position(dialog)
167237
local ok = dialog:ExecuteModal(nil) -- run the dialog
168238
return ok, choices
169239
end
170240

171-
function encode_current_meter(time_sig, sub_meter)
241+
local function encode_current_meter(time_sig, sub_meter)
172242
local function numerators_treble(top_table)
173243
for j = 1, #top_table do
174244
top_table[j] = top_table[j] * 3
@@ -211,7 +281,7 @@ function encode_current_meter(time_sig, sub_meter)
211281
end
212282
end
213283

214-
function copy_meter_from_score(measure_number)
284+
local function copy_meter_from_score(measure_number)
215285
local measure = finale.FCMeasure()
216286
measure:Load(measure_number)
217287
local meter = blank_meter()
@@ -222,16 +292,16 @@ function copy_meter_from_score(measure_number)
222292
return meter
223293
end
224294

225-
function is_power_of_two(num)
295+
local function is_power_of_two(num)
226296
local current = 1
227297
while current < num do
228298
current = current * 2
229299
end
230300
return current == num
231301
end
232302

233-
function convert_choices_to_meter(choices, meter)
234-
if choices[1] == "0" or choices[4] == 0 then
303+
local function convert_choices_to_meter(choices, meter)
304+
if choices[1] == "0" or choices[1] == "" or choices[4] == 0 then
235305
return "Primary time signature cannot be zero"
236306
end
237307
for jump = 0, 6, 6 do -- 'main' meter then 'display' meter
@@ -273,7 +343,7 @@ function convert_choices_to_meter(choices, meter)
273343
return "" -- no error
274344
end
275345

276-
function new_composite_top(sub_meter)
346+
local function new_composite_top(sub_meter)
277347
local composite_top = finale.FCCompositeTimeSigTop()
278348
for count = 1, 3 do
279349
if not sub_meter[count] or sub_meter[count][1] == 0 then break end
@@ -286,7 +356,7 @@ function new_composite_top(sub_meter)
286356
return composite_top
287357
end
288358

289-
function new_composite_bottom(sub_meter)
359+
local function new_composite_bottom(sub_meter)
290360
local composite_bottom = finale.FCCompositeTimeSigBottom()
291361
for count = 1, 3 do
292362
if not sub_meter[count] or sub_meter[count] == 0 then break end
@@ -297,8 +367,7 @@ function new_composite_bottom(sub_meter)
297367
return composite_bottom
298368
end
299369

300-
301-
function fix_new_top(composite_top, time_sig, numerator)
370+
local function fix_new_top(composite_top, time_sig, numerator)
302371
if composite_top ~= nil then -- COMPOSITE top
303372
time_sig:SaveNewCompositeTop(composite_top)
304373
else
@@ -310,7 +379,7 @@ function fix_new_top(composite_top, time_sig, numerator)
310379
end
311380
end
312381

313-
function fix_new_bottom(composite_bottom, time_sig, denominator)
382+
local function fix_new_bottom(composite_bottom, time_sig, denominator)
314383
if composite_bottom ~= nil then -- COMPOSITE bottom
315384
time_sig:SaveNewCompositeBottom(composite_bottom)
316385
else
@@ -322,7 +391,7 @@ function fix_new_bottom(composite_bottom, time_sig, denominator)
322391
end
323392
end
324393

325-
function create_new_meter()
394+
local function create_new_meter()
326395
local region = mixin.FCMMusicRegion()
327396
region:SetRegion(finenv.Region()):SetStartMeasurePosLeft():SetEndMeasurePosRight()
328397

@@ -338,7 +407,7 @@ function create_new_meter()
338407
main = { top = nil, bottom = nil},
339408
display = { top = nil, bottom = nil}
340409
}
341-
for _, kind in ipairs({"main", "display"}) do
410+
for _, kind in ipairs{"main", "display"} do
342411
if meter[kind].top[1] and (#meter[kind].top > 1 or #meter[kind].top[1] > 1) then
343412
composites[kind].top = new_composite_top(meter[kind].top)
344413
if #meter[kind].bottom > 1 then

0 commit comments

Comments
 (0)