Uh oh!
There was an error while loading. Please reload this page.
- Notifications
You must be signed in to change notification settings - Fork 14
Expand file tree
/
Copy pathmeter_set_numeric.lua
More file actions
Latest commit
432 lines (403 loc) · 17 KB
/
Copy pathmeter_set_numeric.lua
File metadata and controls
432 lines (403 loc) · 17 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
functionplugindef()
finaleplugin.RequireSelection=true
finaleplugin.Author="Carl Vine"
finaleplugin.AuthorURL="https://carlvine.com/lua/"
finaleplugin.Copyright="https://creativecommons.org/licenses/by/4.0/"
finaleplugin.Version="0.84"
finaleplugin.Date="2024/04/16"
finaleplugin.MinJWLuaVersion=0.70
finaleplugin.Notes=[[
This script allows rapid creation of simple or complex
time signatures with a few keystrokes.
It supports composite numerators like [3+2+3/16] and joins easily
with extra composites (e.g. [3+2+3/16]+[1/4]+[5+4/8]).
__Display Only__ time signatures can be equally complex and set without mouse action.
At startup the time signature of the first selected measure is shown.
To revert to a simple 4/4 with no other options click the _Clear All_ button or type __x__.
To read these script notes click the __?__ button or type __q__.
To respace notes on completion click the _Respace_ button or type __r__.
All measures in the current selection will be assigned the new time signature.
Use this feature to quickly copy the initial meter across the selection.
If just one measure is selected only it will be changed.
__Bottom__ numbers (denominators) are the usual "note" numbers: 2, 4, 8, 16, 32, or 64.
__Top__ numbers (numerators) are integers, optionally joined by __+__ signs for composite meters.
Numerators that are multiples of 3 automatically convert to compound signatures
so [9/16] will register as three groups of dotted 8ths.
To prevent automatic compounding, instead of the bottom "note" number enter its
EDU value (quarter note = 1024; eighth note = 512; sixteenth = 256 etc).
Empty and zero __Top__ numbers will be ignored.
__Tertiary__ values will be ignored if __Secondary__ numbers are blank or zero.
]]
return"Meter Set Numeric...", "Meter Set Numeric", "Set the Meter Numerically"
end
--[[
Data structure for composite Time Signatures:
local meter = {
main = {
top = { {element_1a, element_1b, element_1c}, {element_2a, etc. ...}, {... element_3c} },
bottom = {group_1, group_2, group_3}
},
display = {
top = { {element_1a, element_1b, element_1c}, etc. ... },
bottom = {group_1, group_2, group_3}
},
}
]]
localmixin=require("library.mixin")
localconfiguration=require("library.configuration")
localutils=require("library.utils")
locallibrary=require("library.general_library")
localscript_name=library.calc_script_name()
localrefocus_document=false-- set to true if utils.show_notes_dialog is used
localfunctionrefocus() -- utils.show_notes_dialog has been used?
ifrefocus_documentthenfinenv.UI():ActivateDocumentWindow() end
end
localconfig= {
note_spacing=true,
window_pos_x=false,
window_pos_y=false,
}
configuration.get_user_settings(script_name, config, true)
localfunctionblank_meter()
return { -- empty composite meter record
main= { top= {}, bottom= {} },
display= { top= {}, bottom= {} }
}
end
localfunctiondialog_set_position(dialog)
ifconfig.window_pos_xandconfig.window_pos_ythen
dialog:StorePosition()
dialog:SetRestorePositionOnlyData(config.window_pos_x, config.window_pos_y)
dialog:RestorePosition()
end
end
localfunctiondialog_save_position(dialog)
dialog:StorePosition()
config.window_pos_x=dialog.StoredX
config.window_pos_y=dialog.StoredY
configuration.save_user_settings(script_name, config)
end
localfunctionencode_current_meter(time_sig, sub_meter)
localfunctionnumerators_treble(top_table)
forj=1, #top_tabledo
top_table[j] =top_table[j] *3
end
end
iftime_sig.CompositeTopthen
localcomp_top=time_sig:CreateCompositeTop()
localgroup_count=comp_top:GetGroupCount()
ifgroup_count>3thengroup_count=3end-- 3 groups max
fori=1, group_countdo-- add each group
sub_meter.top[i] = {}
forelement=1, comp_top:GetGroupElementCount(i-1) do
sub_meter.top[i][element] =comp_top:GetGroupElementBeats(i-1, element-1)
end
end
else-- NON-Composite Top
sub_meter.top[1] = { time_sig.Beats }
end
iftime_sig.CompositeBottomthen
localcomp_bottom=time_sig:CreateCompositeBottom()
localgroup_count=comp_bottom:GetGroupCount()
ifgroup_count>3thengroup_count=3end-- 3 groups max
fori=1, group_countdo
localbeat_duration=comp_bottom:GetGroupElementBeatDuration(i-1, 0)
ifbeat_duration%3==0then
beat_duration=beat_duration/3
numerators_treble(sub_meter.top[i])
end
sub_meter.bottom[i] =math.floor(4096/beat_duration) -- round to integer in case
end
else-- NON-Composite Bottom
localbeat_duration=time_sig.BeatDuration
ifbeat_duration%3==0then
beat_duration=beat_duration/3
numerators_treble(sub_meter.top[1])
end
sub_meter.bottom[1] =math.floor(4096/beat_duration)
end
end
localfunctioncopy_meter_from_score(measure_number)
localmeasure=finale.FCMeasure()
measure:Load(measure_number)
localmeter=blank_meter()
encode_current_meter(measure:GetTimeSignature(), meter.main)
ifmeasure.UseTimeSigForDisplaythen
encode_current_meter(measure.TimeSignatureForDisplay, meter.display)
end
returnmeter
end
localfunctionis_power_of_two(num)
localcurrent=1
whilecurrent<numdo
current=current*2
end
returncurrent==num
end
localfunctionconvert_choices_to_meter(choices, meter)
ifchoices[1] =="0" orchoices[1] =="" orchoices[4] ==0then
return"Primary time signature cannot be zero"
end
forjump=0, 6, 6do-- 'main' meter then 'display' meter
localdata= (jump==0) andmeter.mainormeter.display
forcount=1, 3do
-- 'TOP' NUMBERS
localc_count=count+jump
localvalue=choices[c_count]
ifvalue=="0" orchoices[c_count+3] ==0then
break-- no valid meter values
end
ifstring.find(value, "^%d") then-- string contains non-numeric characters
data.top[count] = {}
forsplitinstring.gmatch(value, "%d+") do
table.insert(data.top[count], tonumber(split))
end
else
data.top[count] = { tonumber(choices[c_count]) } -- single integer value
end
-- 'BOTTOM' NUMBERS
localbottom=choices[c_count+3]
ifbottom<=64andbottom>0then-- must be a NOTE VALUE (64th note or smaller) not EDU
ifnotis_power_of_two(bottom) then
return"DENOMINATORS must be powers of 2\n(not " ..bottom..")"
end
bottom=4096/bottom-- convert to EDU
if#data.top[count] ==1then-- not composite so check for compound
localn=data.top[count][1]
ifn%3==0andjump==0andn>3then
data.top[count][1] =n/3-- COMPOUND METER: so numerator divide by 3
bottom=bottom*3-- and denominator multiply by 3
end
end
end
data.bottom[count] =bottom
end
end
return"" -- no error
end
localfunctionuser_chooses_meter(rgn)
localx= { 0, 70, 130, 210, 280, 290 } -- horizontal grid
locallabel= { -- data type descriptors and (range right) horizontal offset
{ "PRIMARY", 0}, -- name, horiz (left) offset
{ "(+ SECONDARY)", -37 },
{ "(+ TERTIARY)", -21 }
}
localy_middle=118-- window vertical mid-point
localoffset=finenv.UI():IsOnMac() and3or0
localname=plugindef():gsub("%.%.%.", "") -- remove trailing dots
localbox, save_text= {}, {} -- user's edit-box entry responses
localmessage="m. " ..rgn.StartMeasure-- measure selection information
ifrgn.StartMeasure~=rgn.EndMeasurethen-- multiple measures
message="m" ..message.."-" ..rgn.EndMeasure
end
localrespace_check-- pre-declare the "respace" checkbox
localy=0
localdialog=mixin.FCXCustomLuaWindow():SetTitle(name)
-- local functions
localfunctionshow_info()
utils.show_notes_dialog(dialog, "About " ..name, 500, 350)
refocus_document=true
end
localfunctioncstat(nx, ny, nwide, ntext, id)
localdx= (type(nx) =="number") andx[nx] ortonumber(nx)
returndialog:CreateStatic(dx, ny, id):SetWidth(nwide):SetText(ntext)
end
localfunctionjoin(values) -- join integers from the meter.top array with '+' signs
ifnotvaluesor#values==0thenreturn"0" end
returntable.concat(values, "+")
end
localfunctionclear_entries()
forj=0, 6, 6do
fori=1, 3do
localn= (j==0andi==1) and4or0
box[j+i]:SetText(n)
box[j+i+3]:SetInteger(n)
save_text[j+i] =n
save_text[j+i+3] =n
end
end
box[1]:SetKeyboardFocus()
end
localfunctionkey_check(id)
locals=box[id]:GetText():lower()
ifs:find("[^0-9+]") then
ifs:find("x") then
clear_entries()
else
ifs:find("[?q]") then
show_info()
elseifs:find("r") then
localn=respace_check:GetCheck()
respace_check:SetCheck((n+1) %2)
end
box[id]:SetText(save_text[id])
end
elseifs~="" then
save_text[id] =s-- save newly entered text
end
end
cstat(1, y+1, x[3], "TIME SIGNATURE", "main")
cstat(3, y, x[3], "TOP")
cstat(4, y, x[3], "BOTTOM")
cstat(6, 25, 80, "Selection:")
cstat(6, 40, 80, message)
dialog:CreateButton(x[5], y):SetWidth(80):SetText("Clear All (x)")
:AddHandleCommand(function() clear_entries() end)
y=y_middle
cstat(x[2] -30, y-30, 330, "'TOP' entries can include integers joined by '+' signs")
dialog:CreateHorizontalLine(x[1], y-9, x[5] +50)
dialog:CreateHorizontalLine(x[1], y-8, x[5] +50)
cstat(1, y+1, x[3], "DISPLAY SIGNATURE", "second")
cstat(3, y, 150, "(set to '0' for none)")
-- COMPOSITE TIME SIG BOXES
localmeter=copy_meter_from_score(rgn.StartMeasure)
y=0
forjump=0, 6, 6do
localt_sig= (jump==0) andmeter.mainormeter.display
forgroup=1, 3do
y=y+20
localid=group+jump
box[id] =dialog:CreateEdit(x[3], y-offset) -- numerator
:SetText(join(t_sig.top[group])):SetWidth(65)
:AddHandleCommand(function() key_check(id) end)
save_text[id] =box[id]:GetText()
box[id+3] =dialog:CreateEdit(x[4], y-offset) -- matching denominator
:SetInteger(t_sig.bottom[group] or0):SetWidth(65)
:AddHandleCommand(function() key_check(id+3) end)
cstat(x[2] +label[group][2], y, 56-label[group][2], label[group][1])
save_text[id+3] =box[id+3]:GetText()
end
y=y_middle
end
y=y+60
dialog:CreateButton(x[5] +60, y, "q"):SetText("?"):SetWidth(20)
:AddHandleCommand(function() show_info() end)
y=y+25
respace_check=dialog:CreateCheckbox(x[3], y):SetText("Respace notes on completion (r)")
:SetCheck(config.note_spacingand1or0):SetWidth(185)
dialog:CreateOkButton()
dialog:CreateCancelButton()
localchoices= {} -- convert edit box values to 12-element table
dialog:RegisterInitWindow(function(self)
box[1]:SetKeyboardFocus()
localq=self:GetControl("q")
localbold=q:CreateFontInfo():SetBold(true)
q:SetFont(bold)
self:GetControl("main"):SetFont(bold)
self:GetControl("second"):SetFont(bold)
end)
localuser_error=false
dialog_set_position(dialog)
dialog:RegisterHandleOkButtonPressed(function()
forcount=0, 6, 6do-- "main" then "display" time_sig values
forgroup=1, 3do
localid=count+group
choices[id] =box[id]:GetText() or"0"
ifchoices[id] =="" thenchoices[id] ="0" end
choices[id+3] =box[id+3]:GetInteger() or0
end
end
config.note_spacing= (respace_check:GetCheck() ==1)
meter=blank_meter() -- start over with an empty meter
localmsg=convert_choices_to_meter(choices, meter)
ifmsg~="" then-- entry error
user_error=true
finenv.UI():AlertInfo(msg, plugindef())
end
end)
dialog:RegisterCloseWindow(function(self) dialog_save_position(self) end)
localok= (dialog:ExecuteModal(nil) ==finale.EXECMODAL_OK)
returnok, user_error, meter
end
localfunctionnew_composite_top(sub_meter)
localcomposite_top=finale.FCCompositeTimeSigTop()
forcount=1, 3do
ifnotsub_meter[count] orsub_meter[count][1] ==0thenbreakend
localgroup=composite_top:AddGroup(#sub_meter[count])
fori=1, #sub_meter[count] do
composite_top:SetGroupElementBeats(group, i-1, sub_meter[count][i])
end
end
composite_top:SaveAll()
returncomposite_top
end
localfunctionnew_composite_bottom(sub_meter)
localcomposite_bottom=finale.FCCompositeTimeSigBottom()
forcount=1, 3do
ifnotsub_meter[count] orsub_meter[count] ==0thenbreakend
localgroup=composite_bottom:AddGroup(1)
composite_bottom:SetGroupElementBeatDuration(group, 0, sub_meter[count])
end
composite_bottom:SaveAll()
returncomposite_bottom
end
localfunctionfix_new_top(composite_top, time_sig, numerator)
ifcomposite_top~=nilthen-- COMPOSITE top
time_sig:SaveNewCompositeTop(composite_top)
else
iftime_sig:GetCompositeTop() then
time_sig:RemoveCompositeTop(numerator)
else
time_sig.Beats=numerator-- simple time sig
end
end
end
localfunctionfix_new_bottom(composite_bottom, time_sig, denominator)
ifcomposite_bottom~=nilthen-- COMPOSITE bottom
time_sig:SaveNewCompositeBottom(composite_bottom)
else
iftime_sig:GetCompositeBottom() then
time_sig:RemoveCompositeBottom(denominator)
else
time_sig.BeatDuration=denominator
end
end
end
localfunctioncreate_new_meter()
localregion=mixin.FCMMusicRegion()
region:SetRegion(finenv.Region()):SetStartMeasurePosLeft():SetEndMeasurePosRight()
localok, error, meter=true, true, {}
while (okanderror) do-- re-entrant until no error
ok, error, meter=user_chooses_meter(region)
end
ifnotokthenrefocus() returnend-- user cancelled
localcomposites= {
main= { top=nil, bottom=nil},
display= { top=nil, bottom=nil}
}
for_, kindinipairs{"main", "display"} do
ifmeter[kind].top[1] and (#meter[kind].top>1or#meter[kind].top[1] >1) then
composites[kind].top=new_composite_top(meter[kind].top)
if#meter[kind].bottom>1then
composites[kind].bottom=new_composite_bottom(meter[kind].bottom)
end
end
end
localmeasures=finale.FCMeasures()
measures:LoadRegion(region)
formeasureineach(measures) do
localtime_sig=measure:GetTimeSignature()
fix_new_top(composites.main.top, time_sig, meter.main.top[1][1])
fix_new_bottom(composites.main.bottom, time_sig, meter.main.bottom[1])
if#meter.display.top>0then-- new DISPLAY meter
measure.UseTimeSigForDisplay=true
localdisplay_sig=measure.TimeSignatureForDisplay
ifdisplay_sigthen
fix_new_top(composites.display.top, display_sig, meter.display.top[1][1])
fix_new_bottom(composites.display.bottom, display_sig, meter.display.bottom[1])
end
else
measure.UseTimeSigForDisplay=false-- suppress display time_sig
end
measure:Save()
end
ifconfig.note_spacingthen
localspace_rgn=mixin.FCMMusicRegion()
space_rgn:SetRegion(region):SetFullMeasureStack()
space_rgn:SetInDocument()
finenv.UI():MenuCommand(finale.MENUCMD_NOTESPACING)
region:SetInDocument()
end
refocus()
end
create_new_meter()