User Tools

Site Tools


jwlua:development

This page lists JW Lua programming topics.

Your First Script

Every programming reference seems to require a “Hello, world!” example, so here it is in JW Lua:

print ("Hello, world!")

A Quick Start

The fastest way to get a feel for how the JW Lua script language works is probably to read and test existing source code material, such as the scripts on the example page. When the basics of JW Lua has been covered, there are also some larger scripts on the page with fully-featured scripts.

Make small changes to the script code and try and see what happens when you run it.

PDK Framework

JW Lua maps the Lua script language to the PDK Framework (created by Jari Williamsson). For more info about how to interpret the PDK Framework documentation for scripting in JW Lua, please refer to this page.

All accessible PDK Framework classes starts with the FC prefix, such as FCMeasure, FCMusicRegion, FCNoteEntry, etc.

The methods names (and as a result, the Lua properties) in the PDK Framework are in CamelCase. The constants are all-uppercase.

The Lua Language

JW Lua is based on Lua 5.2. More information about the Lua computer language (and books about Lua) is available at the Lua home page, including the Lua 5.2 Reference Manual and the On-line version of "Programming in Lua", First Edition (this book covers Lua 5.0, but for JW Lua programming purposes, it should cover what you need.)

Lua is case sensitive. The basic Lua syntax is very similar to other computer languages and Lua is an easy language to learn. To be able to write plug-in scripts there are just a few concepts in Lua you need to be aware of, such as:

  • Variables
  • Tables
  • Functions
  • Namespaces
  • for … do … end loops
  • if … then … end conditional statements

However, to really take advantage of the full power of Lua, there are other very powerful tools (such as iterators, closures and coroutines) to explore.

JW Lua includes all the standard Lua modules (string, math, file, etc), so they can be used in any JW Lua script, such as :

print (math.random(1, 10))

The 'finale' namespace

All functionality that accesses Finale through the Lua/PDK Framework resides within the finale namespace (namespaces use the dot separator).

For example:

page = finale.FCPage()

The 'finenv' namespace

The finenv namespace has been created to provide “programming shortcuts” to some objects that are often needed for a Finale scripts. finenv currently contains these functions:

Member Description
finenv.Region() Returns an object with the currently selected region (in the document/part currently in editing scope), without the need for any other method calls.
finenv.UI() Returns the global “user interface” object (of the FCUI class). The FCUI class contains Finale and system-global tasks, such as displaying alert boxes, sounding a system beep, or getting the width of the screen, etc.
finenv.UserValueInput() Creates and returns a dialog object to be used for simple user input.
finenv.StartNewUndoBlock() Ends the currently active Undo/Redo block in Finale and starts a new one with a new undo text. First parameter (a Lua string) is the name of the new Undo/Redo block. Second parameter (optional, default is true) is a boolean, indicating if the edits in the previous Undo/Redo block should be stored (=true) or cancelled (=false). Finale will only store Undo/Redo blocks that contains edit changes to the documents. Available in beta 0.36 and above.
finenv.FinaleVersion A read-only property with the running Finale “year” version, such as 2011, 2012, etc.
finenv.RawFinaleVersion A read-only property with the full Finale version number. It's constructed as 4 bytes with different version info. The highest byte is the major version, the next is subversion, etc. Use this only if you need the revision number of a specific major Finale version.
finenv.MajorVersion A read-only property with the major version number of JW Lua. Beta versions return 0, version 1.xx gives 1, etc
finenv.MinorVersion A read-only property with the minor version number of JW Lua. A version 1.07 would give 7, etc.
finenv.StringVersion A read-only property with the full “printed” version number of JW Lua, as a string.
finenv.ConsoleIsAvailable A read-only property that will return true if there's a console available for print() statements. Scripts that runs from the Finale menu don't have a console. Available in beta v0.28 and above.

Dialog Boxes

JW Lua supports dialog boxes to the user through the finenv.UserValueInput() call. Programming of the dialog boxes is explained in full detail on this page.

Finale Concepts

When you access Finale data in JW Lua, the data is internally handled in a way that sometimes is very different from how a Finale user experience the data. This section lists some internal concepts in Finale that a JW Lua programmer should be aware of.

Enigma Database

Internally, Finale stores its database, historically called Enigma data which is loaded/saved from/to the database. Different data record types have different access parameters. However, the PDK Framework “hides” all calls to the database through its classes and methods.

Some data (such as the “page spec” record, which describes a page with its width, height, etc) is really simple and has no linked connections to other data in the database. Other types of data demands more, and might consist of linked data records across the database (for example an expression added to the score in Finale, which contains a number of different data records).

TGF Entry Frame

Note entries however, are not accessed through Finale's usual database calls. They are accessed through a concept called the TGF entry frame, where all note entries in a single measure+staff+layer are accessed through one variable-sized data record. The TGF frame can actually be browsed directly in Finale, using Finale's Edit Frame dialog box (in the Speedy Entry Tool).

A note entry can contain multiple notes (which are the different pitches in a chord), but the notes are just sub-data in the note entry.

Optimization Flags

Finale makes extensive use of bit flags (access through properties in JW Lua) to signal that the data should behave in a certain way.

However, when connecting some data types to measures and note entries, Finale also uses some internal flags to mark if the database should be accessed for the specific measure/entry. These flags are optimization flags, to speed up the performance in Finale. In JW Lua and the PDK Framework, all optimization flags ends with Flag.

Example: if a note entry has articulations, there's a specific optimization flag for that is accessed through the FCNoteEntry.ArticulationFlag property.

Editing Scope

Finale will edit the document and part that is currently has the editing focus. Technically, the editing focus might be different from what the user sees on the screen (the visual focus), so a script can edit data in a document/part that's not currently visible. However, as a plug-in script programmer, it's extremely important that you make sure that the editing focus and the visual focus is identical when the plug-in script ends.

Class Concepts

Collections

An important concept in the PDK Framework is the collection. Usually, collection classes ends with a plural 's' version of the “single-object” version. For example:

measure = finale.FCMeasure()      -- A single measure
measures = finale.FCMeasures()    -- A collection of multiple measures 

Collections are not compatible with Lua tables, but they can be converted to Lua tables with the coll2table() function (see below).

Cells

A cell in the PDK Framework termonology is a reference to a single measure on a single specific staff. The concept is used with the FCCell and FCNoteEntryCell classes, but affects other many classes as well. The coordinate system for cells (at object creation, for example) always is x, y, which in the cell concept means measure_number, staff_number.

Connect to Finale/JW Lua

To really integrate a plug-in script with Finale (and JW Lua) - so it behaves like an independent plug-in - the script can describe the script to JW Lua through the plugindef() function.

Please note that JW Lua can handle everything in the plugindef() function automatically, by using the Plug-in Def dialog box.

The 'plugindef()' function

The plugindef() function is an optional function that only should do a maximum of 2 things:

  • Return the plug-in name, undo string and brief description to be used by Finale and JW Lua.
  • Define the finaleplugin namespace environment to further describe the plug-in (see below).

A simple plugindef() implementation might look like this:

function plugindef()
    finaleplugin.RequireSelection = true
    finaleplugin.CategoryTags = "Rest, Region"
    return "Hide Rests", "Hide Rests", "Hides all rests in the selected region."
end

plugindef() is considered to be a reserved name in the global namespace. If the script has a function named plugindef(), it might be called at any time (not only during script execution) by JW Lua to gather information about the plug-in. The plugindef() function can NOT have dependencies outside the function itself.

All aspects regarding the plugindef() execution is optional, but for a plug-in script that's going to be used repeatedly, the minimum should be to return a plug-in name, undo string and short description.

The plugindef() function can return a maximum of 3 return values (all of them should be strings):

  • The first return value is the name of the plug-in. On Windows, the & character can be used before a letter to specify that letter as a mnemonic character keystroke when the script appears in Finale's plug-in menu. (Example: “My &Plug-in” would make p the shortcut mnemonic key.)
  • The second return value is the undo text for the plug-in. The undo string will get a “ [JW Lua]” text appended after it when Finale gets the undo record.
  • The third return value is a brief description text (for the status/message bar in Finale and the JW Lua user interface).

Again, all these return values are optional.

The 'finaleplugin' namespace

The finaleplugin namespace is a reserved and defined namespace for the plugindef() execution. It can both contain properties that affect how the script will run and properties that further describes the script to the outside world.

Please note that since the execution of plugindef() is completely silent (no errors are displayed on failure), make absolute sure that all spellings are correct (including correct upper/lower case).

The finaleplugin properties should only be set in the plugindef() function.

The properties are discussed in details on the finaleplugin properties page.

The output of the standard Lua print function has been redirected to the JW Lua window, so all output from the print function will show up there:

Additional JW Lua Functions

JW Lua adds some some functions to the global namespace that are specially designed for plug-in script programming. They are listed below:

coll2table()

coll2table() transforms a PDK Framework collection object into a Lua-style table, so the items can be used with the Lua standard functions for tables (such as the standard table. library). The resulting table is 1-based (not 0-based as the PDK Framework collections). For example:

-- Load all measure objects
allmeasures = finale.FCMeasures()
allmeasures:LoadAll()
-- Convert to table
measuretable = coll2table(allmeasures)
-- Sort the measure objects according to their width (widest measure first)
table.sort(measuretable, function (a,b) return (a.Width > b.Width) end)
-- Print the sorted result
for i, v in ipairs(measuretable) do
   print ("Item", i, "is measure number", v.ItemNo, "with the width", v.Width)
end

dumpproperties()

dumpproperties() creates a table consisting of all available properties for an object and their values. The keys in the table is the property names; the values in the table are the property values. Use the pairsbykeys() iterator (see below) to get the properties sorted in alphabetical order.

page = finale.FCPage()
page:Load(1)
properties = dumpproperties(page)
for k, v in pairsbykeys(properties) do
   print (k, "=", v)
end

each()

each() is the general iterator “factory” for PDK Framework collection objects. It feeds the for loop with all the elements of the collection:

-- Print the widths for all the pages
allpages = finale.FCPages()
allpages:LoadAll()
for v in each(allpages) do
     print ("Page", v.ItemNo, "has the width", v.Width)
end

eachbackwards()

eachbackwards() does the same as the each() iterator, but parses the elements backwards starting from the end of the collection. It feeds the for loop with all the elements of the collection. This iterator is available in beta version 0.31 and later.

-- Print the widths for all the pages, starting from the last page
allpages = finale.FCPages()
allpages:LoadAll()
for v in eachbackwards(allpages) do
     print ("Page", v.ItemNo, "has the width", v.Width)
end

eachentry()

eachentry() feeds a for loop with all the note entry objects in a region, without saving them back. Mirror entries are processed with eachentry().

First parameter to this function is the region to process, where you could use finenv.Region() to get the current selection.

The second parameter is optional, but can be used to indicate the note entry layer(s) to load in Finale. The default is to load all visible layers. These values are available:

Value Description
-3 Don't load any entries.
-2 The non-visible layers(s).
-1 All layers, regardless if they're visible or not.
0 All visible layer(s). This is the default.
1 through 4 Load only the one-based layer number. The layer is loaded regardless of the layer visibility.
A bit mask combination of hex values 0x100, 0x200, 0x400, 0x800 The layers 1-4 in any combination.

Example:

counter = 0
for e in eachentry(finenv.Region()) do
   if e:IsRest() then
      counter = counter + 1
   end
end
print ("The region contains", counter, "rest entries.")

eachentrysaved()

eachentrysaved() feeds a for loop with all the note entry objects in a region and automatically saves the entries back to Finale after processing. Only use this function when the entries actually needs to be saved. It requires the same parameter(s) as eachentry() (see above). Mirror entries are not processed with eachentrysaved().

One other task that can be automatically done with eachentrysaved() is to delete entries. Just set the duration to 0, and eachentrysaved() will automatically delete the entry prior to saving it.

-- Let eachentrysaved() delete all rests in the selected region
for e in eachentrysaved(finenv.Region()) do
   if e:IsRest() then e.Duration = 0  end
end

Due to the way the TGF frame works, the note entry is not saved directly after each loop turn, but only when a frame has been fully processed.

eachcell()

eachcell() feeds a for loop with all the cell coordinates for a region. Partially selected measures are treated as being selected. The first coordinate is the measure, the second is the staff ID. eachcell() requires a region as the parameter, the easiest way is to refer to finenv.Region() to get the currently selected region.

Example:

for m, s in eachcell(finenv.Region()) do
   print ("Measure: ", m, "Staff: ", s)
end

loadall()

loadall() feeds a for loop with the elements from a collection, typically where the data hasn't been loaded. The supplied collection must support the LoadAll() method. This is a useful shortcut when the collection itself isn't important, but rather all the items in the collection.

Example:

for m in loadall(finale.FCMeasures()) do
   print ("Measure width: ", m.Width)
end

loadallforregion()

loadallforregion() feeds a for loop with the elements from a collection, based on a region. The first argument is a created collection, typically without any loaded objects. The second argument is the region.

The supplied collection must support the LoadAllForRegion() method. This is a useful shortcut when the collection itself isn't important, but rather all the items in the collection.

Example:

local region = finenv.Region()
for e in loadallforregion(finale.FCExpressions(), region) do
   print ("Expression's measure:", e.Measure)
end

pairsbykeys()

pairsbykeys() feeds a for loop with the table elements, where the elements appear sorted by key. It returns the key/value pair just like Lua's own pairs() iterator.

Example:

t = {}
t["test"] = 12
t["a test"] = 100
for k, v in pairsbykeys(t) do
   print (k, "=", v)
end

Memory Management

The Lua language handles memory through a garbage collection: the memory are handled automatically by Lua. However, there are a couple of points that a plug-in programmer should be aware of.

  • Define variables as local as often as possible. This becomes particularly important when group scripts are used (global variables survives to the subsequent scripts in the group as well), or when using libraries.
  • The memory allocated by JW Lua's Create methods are handled automatically by JW Lua (and not by the Lua interpreter) and those memory objects are released after the full script has been run.
  • Since the Create methods could sometimes result in a huge number of object waiting for release (for example in a tight loop), Lookup classes (classes that ends with Lookup) are available as a faster and more memory-efficient approach.
  • When using the FCCellMetrics or FCNoteEntryMetrics class, make sure to call the FreeMetrics() method separately for the loaded object as soon as the data is no longer is needed. Finale allocate loaded metrics data internally, and metrics with a garbage collector can otherwise lead to problems in scripts where lots of metrics data are loaded without release.

Tips

  • If you don't need a collection object anymore, you can set it to nil. That might benefit the performance in large scripts with huge collections of data (since it signals to the garbage collector that it can free the allocated memory).
  • There's virtually no performance penalty to put lots of comments in the script code. Once the script has been loaded into the Lua engine, the code resides in (quite efficient) bytecode where only the actual execution code appears.
  • An alternative syntax for properties is to use the property name as a string table index with the object as a table. For example, the syntax mymeasure.Width is equivalent to mymeasure[“Width”]. This alternative syntax might be useful when referencing properties dynamically through the code.
jwlua/development.txt · Last modified: 2015/04/17 09:59 by jariw