Conditions and Logic
A script that only prints what it was given is a calculator. It becomes useful the moment it can look at a value and decide. A pan control at zero should say Centre rather than 0. A level of zero has no decibel value at all and needs a word instead of a number. A message that a capture played back should not be forwarded a second time. All of these are decisions, and in Lua a decision is an if.
The if statement
The shape is always the same: if, a condition, then, the lines to run when the condition holds, and end:
local velocity = 100
if velocity > 64 then
print("a hard hit")
endIf the condition is true, the lines between then and end run. If it is false, Lua skips to the end and carries on. Change 100 to 40, send again, and the log stays quiet.
The condition is built with a comparison. Lua has six of them, and five look the way you expect: <, <=, >, >= and == for equal to. The odd one out is ~=, which means not equal to. The double == matters: a single = is an assignment, and if x = 5 then is not a decision but an error. Every Lua programmer has typed it at least once.
More than one answer
When there are several possibilities, elseif asks the next question and else catches whatever is left. Here is the panning example, written as a function so that we can try it with several values at once:
function describePan(value)
if value == 0 then
return "Centre"
elseif value < 0 then
return "L " .. -value
else
return "R " .. value
end
end
print(describePan(0))
print(describePan(-32))
print(describePan(17))The log reads Centre, L 32 and R 17. Lua checks the conditions from the top and takes the first one that is true. Note the -value in the middle branch: the number there is negative, and the minus sign turns it back into a distance.
If you give a control the formatter formatPan from the Formatting Values tutorial, this is exactly the function doing the work on the screen. Functions are the subject of a later chapter; for now, read describePan(-32) as run those lines with value set to -32.
Guarding against the impossible
Sometimes a condition is there not to choose between answers, but to step around a case that would otherwise fail. A level in decibels is twenty times the logarithm of the level, and the logarithm of zero does not exist:
function describeLevel(value)
if value == 0 then
return "-inf dB"
end
return string.format("%.1f dB", 20 * math.log(value / 127, 10))
end
print(describeLevel(0))
print(describeLevel(64))
print(describeLevel(127))The log shows -inf dB, -6.0 dB and 0.0 dB. The if at the top has no else. When the value is zero the function answers and is finished; when it is anything else, the if does nothing and the line after it runs. This shape, deal with the awkward case first and then get on with the normal one, is so common that it has a name, the guard clause, and you will see it in nearly every script in the tutorials.
Combining conditions
Three words combine conditions: and, or and not. They read like English, which is a rare gift in a programming language:
local note = 60
local velocity = 0
if velocity == 0 or note > 127 then
print("treat as a note off")
end
if note >= 36 and note <= 96 then
print("within the keyboard")
end
if not timer.isEnabled() then
print("the timer is off")
endAll three lines print. The first one is the rule from the Introduction to MIDI: a Note On with velocity zero means be quiet. The script makes that rule explicit in one readable line.
When you mix and and or in one condition, use parentheses to say what you mean. Lua has rules about which binds tighter, but nobody reading your script should have to remember them.
What counts as true
Here is the one rule in this chapter that surprises people coming from other languages. In Lua, only false and nil are false. Everything else is true, including zero and the empty string:
local lives = 0
if lives then
print("this line runs, because 0 is true")
end
if lives ~= 0 then
print("this line does not")
endThe upside is that checking whether something exists is very short. A control that has no override gives you nil, so if name then reads as if there is a name. The downside is that if value then is not a test for a non-zero value. When you mean zero, write ~= 0.
A condition that does something
So far our conditions have chosen text. A condition can just as well choose an action. This function is the example from the Lua reference, and it belongs in a control's Function field rather than its Formatter: the controller calls it whenever the value changes, and the function colours the control orange when the value is high.
function highlightOnOverload(valueObject, value)
local control = valueObject:getControl()
if value > 100 then
control:setColor(ORANGE)
else
control:setColor(WHITE)
end
endTo try it, put a fader on the page, type highlightOnOverload into its Function field (Expert mode), send to Electra, and turn the knob past 100. We have not talked about valueObject or the colon in control:setColor yet; they belong to the chapter on objects. The if in the middle is the part that matters here, and you can already read it.
What comes next
A decision is made once. The next chapter is about doing something many times: ten bytes of a name, eight operators of a DX7, every note of a chord.