Table of Contents

Here are some complete scripts that should get you you a quick start to programming with JW Lua. The full scripts page also contains some more extensive, fully-featured scripts.

Resize Measures in a Region

This script will resize the measures in the selected region by the percent (given on the first script line).

resizemeasures.lua
resizepercent = 90
region = finale.FCMusicRegion()
region:SetCurrentSelection()
selectedmeasures = finale.FCMeasures()
selectedmeasures:LoadRegion(region)
for measure in each(selectedmeasures) do
   measure.Width = measure.Width * resizepercent / 100         
end
selectedmeasures:SaveAll()

Detailed explanation:


Assure Page Size Consistency

The script below will assure that the width, height and resize settings are consistent for all pages in each part. It scans through every part in the document (including the score view) and sets all subsequent pages to the first page settings. The script doesn't touch the margin settings, and it will allow for different page sizes for each part.

For this script, please note that the onepart:SwitchTo() call (which sets the internal editing focus to another part) must be followed by a subsequent onepart:SwitchBack() call (to return the editing focus to the visible part).

assurepageconsistency.lua
allparts = finale.FCParts()
allparts:LoadAll()
for onepart in each (allparts) do
    onepart:SwitchTo()
    pageone = finale.FCPage()
    pageone:Load(1)
    allpages = finale.FCPages()
    allpages:LoadAll()
    for apage in each(allpages) do
        if apage.ItemNo > 1 then
            apage.Width = pageone.Width
            apage.Height = pageone.Height
            apage.Percent = pageone.Percent
            apage.HoldMargins = pageone.HoldMargins
            apage:Save()
        end
    end
    onepart:SwitchBack()
end

Find all Double Barlines

This script will report all measures in the document that has a double barline:

reportdoublebarlines.lua
allmeasures = finale.FCMeasures()
allmeasures:LoadAll()
for measure in each(allmeasures) do
    if measure.Barline == finale.BARLINE_DOUBLE then
       print ("Double barline found in measure ", measure.ItemNo)
    end
end

Technical note: in the PDK Framework documentation, you'll find the BARLINE_DOUBLE constant within the FCMeasure scope. However, when using JW Lua, all such constants are in the finale namespace.

A sample output from this script could be found at the bottom of this [very early beta] screen shot:


Single Pitch

This script turns note entries into a single pitch, very much like in Finale's built-in plug-in:

singlepitch.lua
pitchstring = finale.FCString()
pitchstring.LuaString = "A#5"
writtenpitch = true
for e in eachentrysaved(finenv.Region()) do
   if e:IsNote() then
      -- Quick trick to assure single notes at chords
      e:MakeRest()
      e:MakeNote()
      -- Set the pitch of the single note
      e:GetItemAt(0):SetString(pitchstring, nil, writtenpitch)
      e.CheckAccidentals = true   -- Assure proper accidental refresh
   end
end

A couple of notes for this script:


String Harmonics

Here's a script that will create visual string harmonics on all perfect 4ths in the selected region. One difference compared to Finale's built-in solution is that it makes the diamond notehead a little larger to better match the normal notehead.

stringharmonics.lua
for entry in eachentrysaved(finenv.Region()) do
   -- Only process 2-note chords
   if (entry.Count ~= 2) then goto continue end
   -- Only process (enharmonically) perfect 4ths
   local highestnote = entry:CalcHighestNote(nil)
   local lowestnote = entry:CalcLowestNote(nil)
   local mididiff = highestnote:CalcMIDIKey() - lowestnote:CalcMIDIKey()
   if mididiff ~= 5 then goto continue end
   -- Create a notehead modification object
   local notehead = finale.FCNoteheadMod()
   -- Remove any old notehead data for this entry
   notehead:EraseAt(lowestnote)
   notehead:EraseAt(highestnote)
   -- Create a diamond character that is 110% of the default size
   notehead.CustomChar = 79
   notehead.Resize = 110
   notehead:SaveAt(highestnote)
   -- Jump label here:
   ::continue::
end

One specific note about the script above is that it uses eachentrysaved (not eachentry) as the entry iterator. Since the entry contains specific flags about which kind of alterations records that should load for the entry, these flags must be set accordingly. When the modification record is linked back to an entry that is later saved through the iterator, all this will be taken care of automatically.

The script above will play back correctly when using Human Playback on a staff defined for strings. For non-HP documents, you might want to expand the script further. The FCPerformanceMod class provides playback modifications for each notehead, and the FCNote.Playback property can mute single notes (when HP isn't used).


Change Hairpin Opening Size

This script will change the hairpin opening size (for the selected region's hairpins) to a smaller one. The script uses an opening size 24 EVPUs.

hairpinopeningsize.lua
local marks = finale.FCSmartShapeMeasureMarks()
-- Load all smart shape marks within the region and remove duplicate references
marks:LoadAllForRegion(finenv.Region(), true)
for mark in each(marks) do
    -- Make a smart shape object (of the FCSmartShape class) for the mark
    local smartshape = mark:CreateSmartShape()
    if smartshape:IsHairpin() then
        -- Get the FCSmartShapeCtrlPointAdjust sub object
        -- (which is part of the FCSmartShape)
        local adjust = smartshape:GetCtrlPointAdjust()
        -- Set the hairpin opening width to 24 EVPUs
        adjust.ControlPoint1OffsetY = 24
        adjust.CustomShaped = true
        -- Save the smart shape change
        smartshape:Save()
    end
end

Since smart shapes can span multiple measures and cross to other staves, a smart shape occurrence in a measure is marked with a FCSmartShapeMeasureMark object. Each such object points to a “master” smart shape object of the FCSmartShape class. If marks are loaded from multiple measures of a document, it's likely that there will be marks that points to the same smart shape. The methods in FCSmartShapeMeasureMarks can remove any duplicate smart shape references. In this example, removal of duplicate references are made by using true as the second parameter to marks:LoadAllForRegion.

The FCSmartShape class is huge in size and functionality. In JW Lua, parts of the class functionality is accessed by sub object classes. In the example above, smartshape:GetCtrlPointAdjust() is called to access the FCSmartShapeCtrlPointAdjust sub object for control point adjustments.


Text Block Line Spacing

This script will change the line spacing in all “title” page text blocks to 90%.

The line spacing is located in the FCTextBlock class, which is a separate object that is linked to the page text.

textlinespacing.lua
-- Load all page-attached texts
pagetexts = finale.FCPageTexts()
pagetexts:LoadAll()
 
for pagetext in each(pagetexts) do
    -- Create the text string
    local str = pagetext:CreateTextString()
    -- Check if the title text insert exists in the string
    if str:ContainsLuaString("^title()") then
        -- Create (and load) the connected FCTextBlock data
        local textblock = pagetext:CreateTextBlock()
        -- Set the line spacing to 90 % and save
        textblock.LineSpacingIsPercent = true
        textblock.LineSpacing = 90
        textblock:Save()
    end 
end

Set the File Info text

This script sets the composer in the File Info to “My Name”.

setcomposername.lua
-- Create a FCFileInfoText object
local text = finale.FCFileInfoText()
-- Create a FCString object
local str = finale.FCString()
str.LuaString = "My Name"
text:SetText(str)
-- Save the object as a composer text insert
text:SaveAsComposer()

Note Duration Statistics

The following script counts how many occurrences each duration has within the selected region. Rests are not counted.

The script relies heavily on Lua's powerful table feature (used both for counting, presenting the sorted result, and as a lookup table for strings), and the fact that any key that hasn't been defined in a table is nil.

durationstatistics.lua
-- Set up an empty table for duration statistics
local durationstatistics = {}
for e in eachentry(finenv.Region()) do
    if e:IsNote() then
        if durationstatistics[e.Duration] == nil then
            -- No statistics exists for this duration - create a counter
            durationstatistics[e.Duration] = 1        
        else
            -- Increase the counter for this duration
            durationstatistics[e.Duration] = durationstatistics[e.Duration] + 1
        end    
    end
end
 
-- A look-up table for durations.
-- Add other values to the table if necessary (can be added as number values as well).
-- Quarter Note is 1024, Dotted Quarter Note is 1536, etc
local durationtable =
{
    [finale.EIGHTH_NOTE] = "8th Notes",
    [finale.QUARTER_NOTE] = "Quarter Notes",
    [finale.HALF_NOTE] = "Half Notes",
    [finale.WHOLE_NOTE] = "Hole Notes"
}
 
-- Report the statistics sorted by duration
for key, value in pairsbykeys(durationstatistics) do
    if durationtable[key] == nil then
        -- Not a duration with a known "text version" - output EDU value instead
        print("EDU duration", key, ":", value)
    else
        print(durationtable[key], ":", value)
    end   
end

Change Tuplets from Preferences

The following script will set the tuplets in the region to use the number and shape style from the tuplet preferences (in the Document Options).

For “standard” preferences (where there aren't multiple instances of the preference data), load them with 1.

tupletfromprefs.lua
-- Load the tuplet preferences
local tupletprefs = finale.FCTupletPrefs()
tupletprefs:Load(1)
 
for e in eachentry(finenv.Region()) do
    -- Speed/memory optimization - only load tuplets if
    --  the "tuplet start flag" is set for the entry.    
    if e.TupletStartFlag then
        -- Load all tuplets that starts on the note entry
        local tuplets = e:CreateTuplets()
        for tuplet in each(tuplets) do
            -- Change number/shape styles to what's in the preferences:
            tuplet.ShapeStyle = tupletprefs.ShapeStyle
            tuplet.NumberStyle = tupletprefs.NumberStyle
            -- Save tuplet
            tuplet:Save()
        end
    end
end

Append Note Entry

This script appends a note entry of a specific pitch to the first measure and staff of the current selection.

appendnotenentry.lua
-- Load existing notes in the cell
local region = finenv.Region()
notecell = finale.FCNoteEntryCell(region.StartMeasure, region.StartStaff)
notecell:Load()
-- Append to layer 1, add 1 entry
entry = notecell:AppendEntriesInLayer(1, 1)
if entry then
    entry.Duration = finale.QUARTER_NOTE
    entry.Legality = true   -- Must be set for a created note
    entry:MakeNote()
    local note = entry:GetItemAt(0)
    -- Assign a specific pitch to the note
    local pitch = finale.FCString()
    pitch.LuaString = "Bb4"
    -- Set pitch, using the note entry measure's key signature.
    -- Also use sounding pitch.
    note:SetString(pitch, nil, false)
    notecell:Save()
end

Create Page Text in One Part Only

This script creates a page text that's only visible in the part/score where it was created.

To unlink page text data in Finale, the data first needs to be saved. After that, that same object needs to be re-saved again for each part (including the score) - but with modified Visible property when needed.

To save to a specific part, use the SwitchTo and SwitchBack method for a part. These methods must be used in pairs, so the editing focus returns to the original state.

createparttext.lua
-- Create a FCString with the raw Enigma string
local stringobject = finale.FCString()
stringobject.LuaString = "^font(Times New Roman)^size(12)^nfx(0)My Text"
 
-- Create a page text object
-- Set it up to top/center on the first page
local pagetext = finale.FCPageText()
pagetext.HorizontalAlignment = finale.TEXTHORIZALIGN_CENTER
pagetext.VerticalAlignment = finale.TEXTVERTALIGN_TOP
pagetext.HorizontalPos = 0
pagetext.VerticalPos = 0
pagetext.Visible = true
pagetext.FirstPage = 1
pagetext.LastPage = 1
 
-- Save the raw text and text block first
pagetext:SaveNewTextBlock(stringobject)
-- Save the page text to the page 1 storage
pagetext:SaveNew(1)
 
-- Load all parts
local allparts = finale.FCParts()
allparts:LoadAll()
 
-- Parse through all parts and RESAVE the existing page text object
for part in each(allparts) do
    -- Page text will only be visible in the part/score where it was created
    pagetext.Visible = part:IsCurrent()    
    part:SwitchTo()
    pagetext:Save()
    part:SwitchBack()    
end

Search for text expressions

This script searches for a substring among the text expression definitions and displays the expressions to the user (using Finale's own dialog), one by one. As long as the user cancels the dialog box, the script will continue to display the next expression def found in the list. If the user hits Assign, the script will stop.

The script consists of 4 parts:

  1. Display the search dialog
  2. Load the text expression definitions
  3. Build the match table
  4. Display the Expression Definition dialog to the user

The FCUI:DisplayTextExpressionDialog() method displays the ItemNo of a text expression definition.

Please note that when a substring is matched, all Enigma command tags are first removed with FCString:TrimEnigmaTags(). If it wouldn't do that, the script would also match “on” on a “^font()” Enigma tag, for example.

searchtextexpressiondefs.lua
-- Display user dialog to get the search text
local dialog = finenv.UserValueInput()
dialog.Title = "Enter Search String"
dialog:SetTypes("String")
dialog:SetDescriptions("Text expression search string")
local results = dialog:Execute()
if not results then return end
if results[1] == "" then return end
 
-- Load all the text expression defs
local exprdefs = finale.FCTextExpressionDefs()
exprdefs:LoadAll()
 
-- Init a table for all the found expressions
local foundexpressions = {}
 
-- Search all text expression defs 
for exprdef in each(exprdefs) do
    local exprstring = exprdef:CreateTextString()
    exprstring:TrimEnigmaTags()
    if exprstring:ContainsLuaString(results[1]) then 
        -- Add matching text expression def to table
        table.insert(foundexpressions, exprdef)
    end
end
 
-- Display Finale's expression selection dialog box
-- for each found expression.
-- Continue if the user cancels the dialog box.
for k, v in pairs(foundexpressions) do
    if finenv.UI():DisplayTextExpressionDialog(v.ItemNo) ~= 0 then
        -- User confirmed the dialog - exit
        return
    end
end

Swap Staves

This script swaps the contents between the top and bottom staff in the selected region, by using Finale's copy/paste mechanism (using clip files). Finale's Edit filters are not used for these methods.

swapstaves.lua
-- Get the selected region
local region = finenv.Region()
-- Get the top and bottom staff
local topstaff = region.StartStaff
local bottomstaff = region.EndStaff
-- Make sure that the swap can take place
if topstaff < 1 then return end
if bottomstaff < 1 then return end
if topstaff == bottomstaff then return end
 
-- Create a top region
local topregion = finale.FCMusicRegion()
topregion:SetRegion(region)
topregion.EndStaff = topstaff
 
-- Create a bottom region
local bottomregion = finale.FCMusicRegion()
bottomregion:SetRegion(region)
bottomregion.StartStaff = bottomstaff
 
-- Copy the music to clip files
topregion:CopyMusic()
bottomregion:CopyMusic()
 
-- Paste top contents to the bottom staff
topregion.StartStaff = bottomstaff
topregion.EndStaff = bottomstaff
topregion:PasteMusic()
 
-- Paste bottom contents to the top staff
bottomregion.StartStaff = topstaff
bottomregion.EndStaff = topstaff
bottomregion:PasteMusic()
 
-- Release the clip files
topregion:ReleaseMusic()
bottomregion:ReleaseMusic()
 
-- Make sure the original region is visually restored
region:SetInDocument()

Staff Range

This script searches through the selection and presents the highest and lowest note for each staff. It uses 4 different Lua tables (named lowestnotes, highestnotes for the MIDI key numbers and lowestnotestrings, highestnotestrings for the pitch string representations), where each table use the staff number as the index. If the index (staff number) doesn't appear in a table, there is no statistical information about the staff.

The method used to verify the highest/lowest note is by using the enharmonic MIDI key number.

Since pitchstring is one single object through the whole processing (which saves memory and is faster), the tables with pitch strings stores the Lua string versions.

staffrange.lua
-- Use sounding pitch
local written_pitch = false   
 
-- Table with highest/lowest MIDI notes for each staff
local lowestnotes = {}
local highestnotes = {}
 
-- Tables with the pitch strings for highest/lowest notes
local lowestnotestrings = {}
local highestnotestrings = {}
 
local region = finenv.Region()
-- Use a single object for the string pitch result (=faster)
local pitchstring = finale.FCString()
for entry in eachentry(region) do
    if entry:IsNote() then
        -- Get the staff number where the entry is placed
        local staffnumber = entry.Staff
        -- Parse through all notes in the note entry (=chord)
        for note in each(entry) do
            -- Use the enharmonic MIDI note to check the pitch
            local midikeynumber = note:CalcMIDIKey()
            -- Fill the FCString object that represent the pitch string
            note:GetString (pitchstring, nil, false, written_pitch)
            -- See if the note is lowest on the staff
            if not lowestnotes[staffnumber] or lowestnotes[staffnumber] > midikeynumber then
                lowestnotes[staffnumber] = midikeynumber
                lowestnotestrings[staffnumber] = pitchstring.LuaString                
            end
            -- See if the note is the highest on the staff
            if not highestnotes[staffnumber] or highestnotes[staffnumber] < midikeynumber then
                highestnotes[staffnumber] = midikeynumber
                highestnotestrings[staffnumber] = pitchstring.LuaString              
            end
        end
    end
end
 
-- Statistics output
for k, v in pairs(lowestnotes)  do
    -- Construct a staff name to display
    local staff = finale.FCStaff()
    staff:Load(k)
    local namestr = staff:CreateDisplayFullNameString()
    local fullstaffname = namestr.LuaString
    if fullstaffname == "" then
        fullstaffname = "#" .. k
    end
    -- Display the statistics for a staff
    print (fullstaffname, "-   lowest note", lowestnotestrings[k], "   highest note", highestnotestrings[k])
end

Add a Staff Style

This example adds a staff style to the staff ID 1 in the document, between measures 3 and 5. It assumes that the staff ID 1 exists in the document and it also assumes that staff style definition ID 2 exists.

(The current betas can't yet get the staff style definitions and their settings, that will come in a later beta).

addstaffstyle.lua
local ssa = finale.FCStaffStyleAssign()
ssa.StyleID = 2    -- Document-specific staff style definition ID
ssa.StartMeasure = 3
ssa.StartMeasurePos = 0
ssa.EndMeasure = 5
ssa:SetEndMeasurePosRight()
ssa:SaveNew(1) -- Save to staff ID 1

Add a Staff

This script adds a staff to the current document and changes it to “Violin” in the Score Manager. It does not set the playback info.

addstaff.lua
local staffID = finale.FCStaves.Append()
if staffID then
    -- Load the created staff
    local staff = finale.FCStaff()
    staff:Load(staffID)
    -- Set the virtual instrument (in the Score Manager) to violin:
    staff.InstrumentUUID = finale.FFUUID_VIOLIN
    staff:Save()    
end

Hide Articulations

The script below hides all articulations in the selected region. It requires Finale 2014b or later.

hidearticulations.lua
for e in eachentry(finenv.Region()) do
    local artics = e:CreateArticulations()
    for a in each(artics) do
        a.Visible = false
        a:Save()
    end
end

Double Precision Time Signatures

This script changes the time signature to the “double precision” for the whole document. For example, a 2/4 measure becomes 4/8.

halftimesig.lua
local measures = finale.FCMeasures()
measures:LoadAll()
for m in each(measures) do    
    local timesig = m.TimeSignature    
    timesig.Beats = timesig.Beats * 2
    timesig.BeatDuration = timesig.BeatDuration / 2    
    m:Save()
end

Open File Names

This sample script displays the operating system's dialog box for file name selection and prints all file names that were selected by the user.

Please note that the initialization of the FCFileOpenDialog require a FCUI object. Use finenv.UI() to get the global UI object, as in the example.

This script requires beta version 0.22 or later.

openfilenames.lua
-- Create the dialog object
local dialog = finale.FCFileOpenDialog(finenv.UI())
-- Set the window title
local windowtitle = finale.FCString()
windowtitle.LuaString = "Open some files"
dialog:SetWindowTitle(windowtitle)
-- Set the filter
local filter = finale.FCString()
filter.LuaString = "*.txt"
local filterdescr = finale.FCString()
filterdescr.LuaString = "Text Files"
dialog:AddFilter(filter, filterdescr)
-- Allow multiple file selection
dialog.MultiSelect = true
-- Display the dialog to the user 
if dialog:Execute() then
    -- Get the names of the selected files and display the names
    local filenames = finale.FCStrings()
    dialog:GetFileNames(filenames)
    for fname in each(filenames) do
        print (fname.LuaString)
    end
end

Break Secondary Beams

The following script (which requires beta version 0.22 or later) breaks secondary beams. The min_beamed_duration controls the largest note entry duration that should break. The duration_alignment controls how often the secondary beam breaks should appear. The reason the script verifies the note entries against the double min_beamed_duration value, is because dotted durations are also allowed durations.

Please note that the script uses the eachentrysaved iterator (rather than eachentry). This is required since the entries needs to resave to attach correctly to new created data.

In this example, the secondary beams break all the way to the eight note (primary) beam.

beambreaker.lua
local min_beamed_duration = finale.THIRTYSECOND_NOTE
local duration_alignment = finale.EIGHTH_NOTE
 
-- This function returns true if the secondary beams should break
-- before the note entry 
function IsBreakableEntry(entry)    
    -- Check that the current entry is valid for secondary beam breaks:
    if entry.BeamBeat then return false end
    if entry:IsRest() then return false end
    if entry.Duration >= min_beamed_duration * 2 then return false end
    -- Check that the previous note entry is valid for secondary beam breaks:
    local previousentry = entry:Previous()
    if not previousentry then return false end
    if previousentry:IsRest() then return false end
    if previousentry.Duration >= min_beamed_duration * 2 then return false end
    -- Return true if the measure position is on eight note boundary
    return ((entry.MeasurePos % duration_alignment) == 0)
end
 
for noteentry in eachentrysaved(finenv.Region()) do
    local sbbm = finale.FCSecondaryBeamBreakMod()
    sbbm:SetNoteEntry(noteentry)
    if IsBreakableEntry(noteentry) then
        local loaded = sbbm:LoadFirst() 
        sbbm:SetBreakAll(true)
        if loaded then
            -- Save existing data
            sbbm:Save()
        else        
            -- Create new data
            sbbm:SaveNew()
        end
    end
end

Mute Selected Staves

This script mutes the note layers in the selected staves and unmutes the note layers outside the selected staves. It uses the FCStaff object to get the FCInstrumentPlaybackData, where the instrument data for the Score Manager is stored.

The script requires beta 0.22 or later.

muteselectedstaves.lua
-- Set the full document as a region
local fulldocregion = finale.FCMusicRegion()
fulldocregion:SetFullDocument()
-- Get the selected region
local region = finenv.Region()
-- Loop through all staves in the full document
for slot = fulldocregion.StartSlot, fulldocregion.EndSlot do    
    -- Convert from slot number to staff number/ID
    local staffnumber = region:CalcStaffNumber(slot)
    -- Load staff object
    local staff = finale.FCStaff()  
    staff:Load(staffnumber)
    -- Create a FCInstrumentPlaybackData object for the staff
    local playbackdata = staff:CreateInstrumentPlaybackData()
    -- Go through all 4 layers and process the note layer
    for layer = 1, 4 do
        local layerdef = playbackdata:GetNoteLayerData(layer)
        -- Mute in region, play outside region
        layerdef.Play = not region:IsStaffIncluded(staffnumber)
    end
    -- Save the playback settings
    playbackdata:Save()
end

Measurement Units

Sometimes it's convenient to use a specific measurement in a script. However, since Finale only uses EVPUs internally, all other kinds of units need to be converted to EVPUs. The FCString:GetMeasurement() method can convert a string to EVPUs, so one solution could be a wrapper function, like in the example below. The string conversion supports the same measurement suffixes that Finale supports (e for EVPUs, c for centimeters, i for inches, etc).

This code sample would set the page width to 20 centimeters for all pages in the current score/part view.

If the string sent to the ToEvpus() function has no suffix, the current default measurement will be assumed.

measurementunits.lua
function ToEvpus(text)
    local str = finale.FCString()
    str.LuaString = text
    return str:GetMeasurement(finale.MEASUREMENTUNIT_DEFAULT)
end
 
for p in loadall(finale.FCPages()) do
    p.Width = ToEvpus("20c")
    p:Save()
end

Move TAB Numbers

This sample moves the tablature (TAB) numbers from one string to another string (while keeping the fret numbers) in the current selection. A dialog box appears where the user can specify the number of strings to move the numbers. A negative number (such as -1) would result in an upwards movement.

To keep the fret number between strings, two steps are required: transposition of the note and moving the note to another string. The eachentrysaved() iterator is used (rather than eachentry()) to resave the transposed note.

This script requires beta version 0.26 or later.

movetabnumbers.lua
-- Show dialog box
local dialog = finenv.UserValueInput()
dialog.Title = "Move TAB Numbers"
dialog:SetTypes("Number")
dialog:SetDescriptions("Move x strings downwards:")
dialog:SetInitValues(1)
local dlgresult = dialog:Execute()
if not dlgresult then return end
local stringadd = dlgresult[1]
 
-- Browse through the note entries in the selection:
local tabinstrument = finale.FCFretInstrumentDef()
for entry in eachentrysaved(finenv.Region()) do
    local staffspec = finale.FCCurrentStaffSpec()
    if staffspec:LoadForEntry(entry) and staffspec:IsTablature() then
        if tabinstrument:Load(staffspec.FretInstrumentDefID) then
            for note in each(entry) do
                local mod = finale.FCTablatureNoteMod()
                if mod:LoadAt(note) then
                    -- Calculated the MIDI offset difference between the strings:
                    local originalstring = tabinstrument:GetStringTuning(mod.StringNumber)
                    local otherstring = tabinstrument:GetStringTuning(mod.StringNumber + stringadd)
                    local midioffset = originalstring - otherstring  
                    -- Transpose:
                    local newmidipitch = note:CalcMIDIKey() - midioffset
                    note:SetMIDIKey(newmidipitch)
                    -- Change the string:
                    mod.StringNumber = mod.StringNumber + stringadd
                    -- Save the new string number:
                    mod:Save()
                end
            end
        end    
    end
end

Using Unicode

JW Lua supports Unicode for Finale 2012 and above (as long as the font supports it). Umlauts, cyrillic text, Japanese characters, etc can be mixed with traditional western characters.

This sample script sets the composer text insert to include both a cyrillic and western version of the composer's name.

This script requires beta 0.27 or later.

cyrillictext.lua
-- Assure that the script exits directly on pre-2012 Finale versions:
if finenv.FinaleVersion < 2012 then return end
 
--Set composer text to use both cyrillic and western version
-- of the composer's name. Put a new line character between.
local str = finale.FCString()
str.LuaString = "Сергей Прокофьев\r(Sergei Prokofiev)"
 
-- Set an save the text insert:
local fileinfotext = finale.FCFileInfoText()
fileinfotext:SetText(str)
fileinfotext:SaveAsComposer()

Align Repeat Brackets

The following script example aligns the top lines of the repeat brackets within the selected region to the topmost position. It does not look at staves with independently moved bracket positioning.

All ending repeat brackets and backward repeat brackets are first scanned for the highest vertical position. The initial “highest position” is set to a extremely low value, to make sure that a true top position is found. After that, all the brackets are set to the highest vertical position found.

The script requires beta 0.29 or higher.

alignrepeatbrackets.lua
local region = finenv.Region()
if region:IsEmpty() then return end
 
-- Scan to find the highest bracket position in the selected measures:
local highest = -10000
local count = 0
for m = region.StartMeasure, region.EndMeasure do
    local endingrepeat = finale.FCEndingRepeat()
    local backwardrepeat = finale.FCBackwardRepeat()
    if endingrepeat:Load(m) then
        if endingrepeat.VerticalTopBracketPosition > highest then
            highest = endingrepeat.VerticalTopBracketPosition
        end
        count = count + 1
    end
    if backwardrepeat:Load(m) then
        if backwardrepeat.TopBracketPosition > highest then
            highest = backwardrepeat.TopBracketPosition
        end
        count = count + 1
    end
end
 
if count > 1 then
    -- Set the new bracket positions:
    for m = region.StartMeasure, region.EndMeasure do
        local endingrepeat = finale.FCEndingRepeat()
        local backwardrepeat = finale.FCBackwardRepeat()
        if endingrepeat:Load(m) then
            endingrepeat.VerticalTopBracketPosition = highest
            endingrepeat:Save()
        end
        if backwardrepeat:Load(m) then
            backwardrepeat.TopBracketPosition = highest
            backwardrepeat:Save()
        end
    end
end

Swap Metatool Key Assignment

The following script swaps the expression metatool keyboard assignments for the keys '1' and '2'. Metatools are saved to the (ASCII) key number, and the Lua string.byte() function is used to find the key number. Simply resaving to the other key number will do a swap of the metatool keys. Beta version 0.40 or later is required for this sample code.

swapmetatools.lua
-- Load all existing metatool assignments for the Expression Tool
local mas = finale.FCMetatoolAssignments()
mas:LoadAllForMode(finale.MTOOLMODE_EXPRESSION)
-- Find the current expression metatools for keyboard keys '1' and '2'
metatool1 = mas:FindKeystroke(string.byte("1"))
metatool2 = mas:FindKeystroke(string.byte("2"))
-- Swap the keyboard mapping for these 2 objects
if (metatool1 ~= nil) then
    metatool1:SaveAsKeystroke(string.byte("2"))
end
if (metatool2 ~= nil) then
    metatool2:SaveAsKeystroke(string.byte("1"))
end