Welcome to the third installation of learning to code with JW Lua for Finale. So far, we’ve learned the basics of the Lua language and have written one small script in JW Lua. Today, we’re going to expand on the script we wrote last time to help us really get more comfortable, while introducing another really important aspect of coding: the if statement.
And by now, you might start to see why JW Lua can be incredibly powerful in Finale. Though you haven’t really written much, you can already start to see that the ability to create loops and edit the music on the page can really speed up your workflow.
But we’re still missing one key ingredient: logic.
And that’s why we use if statements in programming. It allows the code to start making intelligent choices, which will allow you to code up many of your repetitive tasks instead of doing them all by hand.
The If Statement
The structure of an if statement is actually quite simple: if this, then do that.
if condition then -- code that runs end
Now what qualifies as a condition? Pretty much any expression that evaluates to a boolean (true or false) is a valid condition. The most simple if statement is:
if true then print('hello') end -- alternatively local my_bool = true if my_bool then print('hello') end
If the condition evaluates to true, the code will run. If it evaluates too false, the code will not run.
if false then print('hello') -- this line does not run end
Let’s now look at different ways we can create valid conditions in Lua.
Conditional Statements
There are many ways to use conditional statements but here’s the three that we’re going to use today:
- Booleans
- Items Exist
- Relational Operators
We’ve already encountered how to use booleans as a condition. You just write true or false as we did above. Now, let’s look at the other options.
Items Exist
Now I’m sure there’s a technical name for this, but I don’t’ know it. But you can use if statements to see if an item exists. For instance:
if my_num then -- my_num is not defined, so this is false print('my_num is not defined yet') end local my_num = 21 if my_num then -- my_num is defined, so this is true print('my_num is defined!') end
For a more technical reason why this works, it’s because if a variable is not defined, it returns nil
. We saw that the very first week when we attempted to print(Pizza)
as a string without quotes. And a nil
evaluates to false. In other words,
if nil then print('this will not print') end
That means, by using a variable as the conditional, we can tell if the variable is defined or not.
Relational Operators
These are items like greater than, equal to, or less than. Here are some examples.
3 > 1 -- true 4 < 15 -- true 2 == 6 -- false
Here’s a table for all the standard operators:
Operator | Description |
---|---|
== | Checks if the two values are equal to one another |
~= | Checks if the two values are not equal to one another |
> | Checks if the first value is greater than the second |
< | Checks if the second value is greater than the first |
>= | Checks if the first value is greater than or equal to the second |
<= | Checks if the second value is greater than or equal to the first |
So with that, let’s look at some JW Lua code.
Detecting Pitch
Ok, so last time we ended up with this piece of code:
for entry in eachentrysaved(finenv.Region()) do local highestnote = entry:CalcHighestNote(nil) local notehead = finale.FCNoteheadMod() notehead:EraseAt(lowestnote) notehead:EraseAt(highestnote) notehead.CustomChar = 192 notehead.Resize = 80 notehead:SaveAt(highestnote) end
And today, we’re going to modify it so only notes above middle C will get an x notehead.
But first, let’s actually figure out how to detect the note of a given note entry. To make sure we’re on the same page, go to Finale and create out the following music.
I know that’s not the traditional way this melody is written, but bear with me.
Let’s just take our simple loop that loops over each note entry, and have it print each pitch.
for entry in eachentrysaved(finenv.Region()) do local highestnote = entry:CalcHighestNote(nil) local midi = highestnote:CalcMIDIKey() print(midi) end
Select the music region, and run it in JW Lua.
Error?
It says that on line 9, it’s trying to calculate the MIDI key, but there’s no “highestnote” (i.e., it’s a nil
value).
Yet the code seems to be working because it’s printing out the MIDI key for a bunch of other notes. So what’s going on?
Well, you may remember last time that the loop note only runs through all note entries, but also all rest entries. Therefore, it’s also calculating the highest notes of the rests. But rests don’t have highest notes!
So, what can we do if we have some values of highestnote that we want to loop through, and others we don’t? We can use an if statement!
And we already know how to filter out when a variable isn’t defined. Just use it as the conditional for an if statement.
for entry in eachentrysaved(finenv.Region()) do local highestnote = entry:CalcHighestNote(nil) if highestnote then -- is nil when it isn't defined local midi = highestnote:CalcMIDIKey() print(midi) end end
There, we’ve now printed out the MIDI key for every note in the selection.
Finding All Notes Above Middle C
Now that we have the pitches, what do you notice about what’s being printed?
They are all numbers.
That means we if we know what note middle C is, we can filter out all notes that are below middle C to leave us with only the notes above middle C!
Middle C is 60 in MIDI, so let’s now only print out the MIDI key for notes above 60.
for entry in eachentrysaved(finenv.Region()) do local highestnote = entry:CalcHighestNote(nil) if highestnote then local midi = highestnote:CalcMIDIKey() if midi > 60 then print(midi) end end end
And there we go. We just used an if statement again to filter out the data we don’t want.
For good measure, let’s go through line by line to ensure we know what everything is doing.
-- loops through all entries in the selected region for entry in eachentrysaved(finenv.Region()) do -- gets the highest note in an entry -- if it's a single note, highestnote becomes the single note -- if it's an interval or chord, highestnote becomes the highest note -- if the entry is a rest, highestnote becomes nil local highestnote = entry:CalcHighestNote(nil) -- if the highestnote isn't nil (isn't a rest) if highestnote then -- get the MIDI key for the note local midi = highestnote:CalcMIDIKey() -- if the MIDI key is above 60 (above middle C) if midi > 60 then -- print out the MIDI key print(midi) -- ends if statement end -- ends if statement end -- end of loop end
Changing All Noteheads Above Middle C
Great! Now we have everything we need to change all noteheads above middle C. We have loops and if statements that isolate the note above middle C, and we already have the code to change noteheads.
So let’s put them together!
for entry in eachentrysaved(finenv.Region()) do local highestnote = entry:CalcHighestNote(nil) if highestnote then local midi = highestnote:CalcMIDIKey() if midi > 60 then -- code from changing noteheads to "x" local notehead = finale.FCNoteheadMod() notehead:EraseAt(lowestnote) notehead:EraseAt(highestnote) notehead.CustomChar = 192 notehead.Resize = 80 notehead:SaveAt(highestnote) end end end
And there we go. We can now change any notehead above middle C.
Next Steps
Homework
As always, here’s some homework to make sure you actually know what’s going on.
- Change all notes below middle C
- Change all notes above middle C including middle C
- Change all Cs in every octave, and only Cs, to an x notehead (see footnote: Modulus Operator)
- Change every other note to an x notehead (advanced)
Footnotes
- What is debugging? How do you do it?
- Introduction to the Modulus Operator
- What does “–” mean?
- If, then, else…
Next Time
You’re learning nicely so far, but you probably have one question in your mind that’s just bugging you…
“Hey, Nick! How do you know what commands to type in JW Lua?”
Like how did I know to use finale.FCNoteheadMod() or :CalcMIDIKey(), or similar things. Well, we’re going to find that out really soon.
But first is an important step to understanding the answer to that question. You first have to understand functions and classes. So that’s what we’re going to look at next week. Make sure you stay on top of the homework and footnotes, and put your answers in the comments below. I look forward to reading them!
Nick
Nick Mazuk is a composer and trombonist in Los Angeles. He mainly freelances by creating arrangements and original compositions for orchestra. In Finale, he specializes in automating tasks so he can achieve great results fast. Nick’s YouTube channel provides more tips and tricks for speeding up your Finale workflow.
OK Grazie.
homework 1
function plugindef()
— This function and the ‘finaleplugin’ namespace
— are both reserved for the plug-in definition.
finaleplugin.RequireSelection = true
return “”, “”, “”
end
for entry in eachentrysaved(finenv.Region()) do
local lowestnote = entry:CalcLowestNote(nil)
if lowestnote then
local midi = lowestnote:CalcMIDIKey()
if midi < 64 then
local notehead = finale.FCNoteheadMod()
notehead:EraseAt(lowestnote)
— OPTIONAL notehead:EraseAt(highestnote)
notehead.CustomChar = 79 — Harmonics ;-)
print "Harmonics" — on Lua "Development" displays the modified heads type ;-)
notehead.Resize = 80
notehead:SaveAt(lowestnote)
print(midi) — on Lua "Development" displays the modified note number
end
end
end
hello Nick, unfortunately there are no links to Footnotes