Hello, my name is CJ and I’m here to run you through some of the nitty-gritty of the preferred syntax I and my colleagues who develop for the JetStream Finale Controller project use. Knowing how to install and write code with JW Lua as well as being familiar with it’s classes is a prerequisite to this style guide and will not be covered. However, feel free to post in our JW Lua Facebook group if you have any questions at all.
First off, and very importantly, I am a music composition major working as an engraver who’s only formal training in programming was three computer science courses in Python at Rice University via Coursera. Everything else has been learned through dissecting existing code and spending too many hours on stackoverflow.com. This document is intended to provide consistency in public JW Lua script development, it is not a reflection of best practices in the language of lua or a way to learn them. I am still learning, and will continue to learn.
Secondly, nobody’s perfect and I am constantly refactoring my own code to make it consistent with the syntax explained in this document. This is a living document and will be changed and updated as we go. I am malleable on the stances taken here, however, I will always weigh the cost of refactoring time. What started off as an interest in saving time and a fun little project between Robert Puff and myself has grown quite a bit with interest in JW Lua. Though the bottom line is if your code works, your code works, however folks who write code generally do like consistency and these will be some explicit instructions for consistency sake when committing your code to the public JW Lua Git Repository set up by Nick Mazuk.
Thirdly, for what it’s worth, I’m writing all of the code in Visual Studio Code with this Lua extension. I am using the Code Blocks chrome extension for this document with “gruvbox-dark” as the theme.
JW Lua is a framework created by Jari Williamson for use in Finale. Since a PDK (Plug-in Developers Kit) is not publicly available for Finale. Jari, being one of the three remaining plug-in developers who owns the PDK, decided to essentially make his own wrapper for the PDK with a much more lightweight and simpler language: lua. JW Lua is a plug-in that creates plug-ins.
All functions should be written in snake_case. This is a personal preference for two reasons: 1) I want to be able to distinguish what code that I or other people have written compared against JW Lua’s built in classes, methods, and properties, all of which use CamelCase and 2) I personally think it’s easier to read.
Though not always necessary in a script, I just want to talk about a little bit of this philosophy if you choose to use Run and Helper functions. The purpose of a Run function is to provide a single execution point of a script. Run functions do not pass any arguments, that is a job reserved for the helper functions. It is also important that run functions be global, they should never be local. Variables and other functions within your scripts should absolutely be local variables, but run functions must remain global.
Naming a run function should follow the structure of Macro -> micro.
Example: dynamics_ffff_start()
We begin by asking “What is the most macro thing about this function?” In most cases, this will be something in the JW Lua Development tab > Plug-in def… > “Category Tags”. In this specific case, it’s “dynamics”. Next we ask “what dynamic am I working with?” In this case it’s the ffff expression . Then we ask “what action is happening with this dynamic?” In this case we are placing it at the start of the region.
So all together this run function name goes from category > what > action and that is how we get to dynamics_ffff_start()
.
Helper functions are more standard: they can pass arguments, more than one can be used inside a run function, and most importantly, they should be written in a way that they could be used in other run functions - the principle of having DRY (Don’t Repeat Yourself) code is very important. Here is an example of the helper functions from the dynamics_ffff_start() run function.
function dynamics_ffff_start()
find_dynamic({235}, first_expression, "fortissississimo (velocity = 127)")
dynamic_region("Start")
end
The helper function naming syntax is less rigorous. Though it should be descriptive, it does not have to follow the Macro -> micro structure. In this example, there are two helper functions: find_dynamic()
and dynamic_region()
. Both of these helper functions are used in most of the dynamic category and pass arguments that make this possible. Below is the find_dynamic()
helper function:
function find_dynamic(glyph_nums, table_name, description_text)
local matching_glyphs = {}
local exp_defs = finale.FCTextExpressionDefs()
local exp_def = finale.FCTextExpressionDef()
exp_defs:LoadAll()
for exp in each(exp_defs) do
local glyph_length = 0
local exp_string = finale.FCString()
exp_string.LuaString = ""
for key, value in pairs(glyph_nums) do
exp_string:AppendCharacter(value)
glyph_length = glyph_length + 1
end
local current_string = exp:CreateTextString()
current_string:TrimEnigmaTags()
if glyph_length > 1 then
if (current_string:GetCharacterAt(-1) == glyph_nums[2]) and
(current_string:GetCharacterAt(0) == glyph_nums[1]) then
table.insert(matching_glyphs, exp:GetItemNo())
end
else
if current_string:GetCharacterAt(0) == glyph_nums[1] then
table.insert(matching_glyphs, exp:GetItemNo())
end
end
end
if matching_glyphs[1] == nil then
create_dynamic(glyph_nums, table_name, description_text)
else
exp_def:Load(matching_glyphs[1])
table.insert(table_name, exp_def:GetItemNo())
end
end
Reading code should be like reading a book in my opinion. Let’s look at a few items within this function and other code that increase readability.
It should go without saying that after every declaration, a line break should occur. And generally, the less whitespace the better. However, I’ve been known to separate my loops and variables with an empty line between the two or between the end of one loop and the beginning of another. However, when loops within loops begin to occur, this can start to look ugly really quick, so I’ve tried to eliminate whitespace within functions. Whitespace should, however, exist between two functions regardless of context.
Though technically unnecessary for lua to execute correctly, I do like to adapt a bit of Python-esque syntax: I use indentation within functions and loops, and that indentation is set to be 4 (four) spaces. In VS Code, you can change the tab amount settings under File > Preferences > Settings and then search for “Tab Size”. The reason for 4 spaces is to match the “Development” tab in JW Lua so that copying and pasting between the two for testing doesn’t require reformatting the code.
Again, reading code is much easier when it is close to reading normal text (such as this). You’ll see spaces in the following examples:
local matching_glyphs = {}
matching_glyphs[1] == nil
glyph_length > 1
if ((current_string:GetCharacterAt(-1) == glyph_nums[2]) and (current_string:GetCharacterAt(0) == glyph_nums[1])) then)
if matching_glyphs[1] == nil then
find_dynamic(glyph_nums, table_name, description_text)
table.insert(matching_glyphs, exp:GetItemNo())
local dyn_char = {150, 175, 184, 185, 112, 80, 70, 102, 196, 236}
if matching_glyphs[1] == nil then
if (current_string:GetCharacterAt(-1) == glyph_nums[2]) and
(current_string:GetCharacterAt(0) == glyph_nums[1]) then
if direction_type == "far" then
While inside a helper function, variables are the most flexible of our syntax. As usual, if they use more than one word, then again, use snake_case and they should generally be local variables.
playback_AS_DB_RE()
. Any reasonable person would and frankly shouldn’t have any idea what that means. Though shorter and cleaner, it is not clear what that functionality is. Instead, the function name should be playback_all_staves_from_document_beginning_to_region_end()
. Though longer, it reads easily and describes explicitly what is going to happen.notehead_mod:GetVerticalPos()
local str = finale.FCString()
for key, value in pairs(first_table) do
for k, v in pairs(value) do
print(v)
end
end
harmonics_touch_4()
should instead be written out as harmonics_touch_four()
If you’d like to take care of many of these style preferences automatically, consider using the VS Code extension lua-linter.
There are three quick steps:
luac
if you haven’t alreadybrew install lua
luaconfig.config
file in this repo is in the same folder you are editing the Lua script in. If you are editing in this repo, the luaconfig.config
file is included automatically.This linter will take care of these things every time you save a file:
It will not correct any variable or function names.
Thanks again for your interest in JW Lua. If you feel as if there’s anything missing in this guide or have any questions, comments, or concerns, feel free to reach out to me (CJ Garcia - CJGarciaMusic@gmail.com) or if you want help with your code please post to the JW Lua Facebook group where many other talented folks will happily help guide your way.