Learning to Code in JW Lua | Part 3: Changing Character Based on Pitch

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:

OperatorDescription
==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.

  1. Change all notes below middle C
  2. Change all notes above middle C including middle C
  3. Change all Cs in every octave, and only Cs, to an x notehead (see footnote: Modulus Operator)
  4. 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…
  • Cool things you can do with if statements and for loops!

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.

3 Replies to “Learning to Code in JW Lua | Part 3: Changing Character Based on Pitch”

  1. 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

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.