Lua for GrandMA3 YouTube Crash Course

Hey lighting folks! The following is the transcript from my YouTube video, Lua for GrandMA3 Crash Course

This video is the "concatenation" ;) of my Lua for GrandMA3 Tutorial series. Please check out my channel, From Dark To Light, on YouTube, and you can find the code to go along with my tutorials here on GitHub.

Hello lighting people! Welcome to my Lua for GrandMA3 Tutorial series. I made about 10 episodes of this series and there's a few other videos that are outside of this series and I'm about to be making some more that are going to be more advanced. But this series of 10 episodes kind of takes you from the very most beginning part to some pretty big stuff and I just wanted to assemble it as one video and basically remove the outros just to make it smoother.

And if you are looking for a way to quickly just go over everything you need to know about Lua for GrandMA3, then please watch this video, it is absolutely for you. However, I do know that it's not possible to answer all of your questions in one go without knowing that they are in advance. So if you have questions, we have a Discord server you can join. There's a link in the description and a little on the channel page and you can ask your questions of the Discord server. And I mean, I answer questions but there are people in there who know more about Lua than I do and they are there to answer your questions as well. And so we would love to help you if you're struggling. So I hope you enjoy these videos and they help you and I will see you later in the more advanced ones.


MA3 is a powerful software and one of the greatest things about it is the ability to run Lua scripts. Before diving into Lua coding I wanted to make a quick intro video to show the plugin interface in GrandMA3 and introduce some other applications and workflows. Also, just get this out of the way, Lua is a word, not an acronym. Don't write it in all caps. It's Portuguese for moon, so it's not an acronym.

Before I start going into what I do, I'm going to share the resources that I used to learn so that if I miss something or you want to go deeper into something I didn't go into or get ahead of me you can go do your own research as well.

I used mainly lua.org which has the online version of a book called Programming in Lua.

There are newer editions that you can buy but they're pretty expensive and the online version covers most things. This is only Lua; it does not have anything to do with MA3, but it is useful for learning how Lua works outside of MA3.

The other resource I used is the MA3 user manual; it has a section on plugins. It was not very extensive when I first started doing this, but in recent versions it's added more information so you can read about plugins, about how the plugin editor works, and then you can actually see how some different functions work. I will be getting into what these actually are later but if you want to refer to any specific one, I do that very frequently and it's a great resource. It does not however go into how Lua works, only how the specific function works, so if you don't understand Lua it's going to be a little bit confusing, which is why I use both.

I wish I could recommend other YouTube channels or something but none of the ones I found actually were any good. There were none that had to do with Lua for MA3 specifically and when it comes to Lua for the world in general honestly I didn't find any that I liked, but you're welcome to look and maybe there's something new or something that works for you.


I'm going to go into MA3 now and show you the plugin editor and this, this is my view that I use for working with plugins in MA3. I have the command line history on half of the screen, the system monitor on the other half, and then the plugins down here. Basically both of these are going to provide information that you need to see, and obviously you have to be able to get to your plugins. So to create a plugin, like anything else, just right click or edit and then you get these lines and kind of like macros you can insert a new component and then to edit this you can't just click on it or right click on it; that will only edit the individual column. You have to hit this button down here which says edit and that opens up the editor and there's some template codes in here and some different descriptions, like, short descriptions of different API functions, but I don't refer to that very often. If it helps you that's great.

And then whenever you're closing out of this just make sure to hit save if you've made any changes you want to keep although if you hit the “X” I think it will prompt you, “do you want to save these changes or discard them?”

This editor looks pretty similar to other things in MA3, especially the macro editor, but I'm going to go into a few things that are different. So the first thing that's going to be different from macros is you can have multiple Lua components but it's not going to run all of them. So each component is just code; you put code in here and save that and whenever you call the plugin it's going to run that code, but if you have multiple Lua components only the first one is ever going to get run; the second one will never be run, the third one will never be run, unless you specifically call “Plugin 1.2,” then it specifically calls the second component so you can kind of hide code to only use it if you want to call it specifically. It can be useful but just remember only the first component is actually going to do anything under normal circumstances.

The other things that are different are the file name and file path, file size, source, all these things. You can actually point the plugin editor to a specific file within this file path in your showfile and it will take that code and import it into the plugin and you have the file name and file path for that. When you do that the file will not change in MA3 unless you run a keyword to reload it and that only works if you have this set to installed. If you do not have this set to installed then even changes you make will not translate in MA3.

That is one workflow that you can use if you are using a PC. I don't actually do that; it's easier for my brain to just have the plugin stored in a separate location outside of the showfile, edit them separately, save those files separately, and then copy and paste my code into MA3, but there are multiple ways you can do that. Another thing I will get into is just syntax error. This will turn to yes and everything will turn red if your code has an error in it, like a syntax error that it can recognize; that it'll tell you, this code won't work, and it won't let you run it. Also the file size right here is non-editable, it will just show you in bytes what size the file is wherever you have it, whether it's linked to a file in the system or you just have code in here. If I put code in here, once it gets large enough for it to tell that there's some size to it it will show you the size in bytes here.


So now that I've been over that I'm going to go into Visual Studio Code. This is the application I use for editing code and it's really useful; you'll learn how to use it easily, it's very intuitive, but in here I actually have a folder for Lua for GrandMA3 and then, this I will get into later what it actually is but that is a file you will see on my GitHub page if you go there, which again, I'll get into that in a minute but whenever you need to create a new file you can hit this button which says new file and type in a name. Whatever name you type, make sure you end it with “.lua” because that tells it what language it is and then you can edit it here and it will give you different colors for different types of keywords that you use in there, so it makes it really super intuitive to tell where things are easily, see where you might have an error, how things fit together; it even autofills things for you it's really nice and it will tell you if there are errors.


So I use Visual Studio code together with Git and GitHub which integrate easily with Visual Studio code and I'm not going to really get into that but basically Git will let you save different versions of your whole file structure and so anytime you want to save a change you save it, it's called committing, you write a message to show what changes you made and you can compare old and new versions, you can revert back to old versions anytime you need to, so if you mess something up you can go back in time and see what what an older version was and see, okay so this must be what broke it, cuz this is what I changed.

GitHub is part of Git, or it's connected to Git and GitHub is actually a website but it connects to your desktop so I have a GitHub page for my YouTube channel right here and this is that ReadME file that I saw in VSCode. It has some information about my YouTube channel, a link to my YouTube channel, and what I'm going to do is all of my files that I make to go along with these videos will be available on GitHub for you guys to see and play with and test out and just change however you want and see what you can do with them to use kind of as a starting point to refer back to what I've talked about, so when I save code it's going to be saved in GitHub as well, and that allows also multiple users to edit things, you can edit things from different devices… I do use that capability quite a bit; it's really nice but it's not necessary to get started. I'm not going to go into how to set that up but if that is a video that is desired let me know and I might think about doing it in the future.


Hello lighting people, welcome back to my Lua for GrandMA3 tutorial series! In this episode we're going to be talking about Lua functions because functions are the backbone of code; you cannot have readable code without a function, so let's dive in.


I'm going to open up Visual Studio Code and make sure I'm in my folder, create a new file. This file is going to be called Ep 1 - Functions and Data Types, because that's what we're talking about today, and always put “.lua” at the end of your file name. I'm going to start by making a main function; so the way you make a function is, type “function” and give it a name. I'm just going to call it “main,” and then you put parentheses. VS Code automatically completes your parentheses so you can type inside of them, but just type another one at the end to end it and then I'm going to hit enter twice and type “end;” that's how you end the function and then “return main,” so what this does is when the code interpreter goes through and reads the code it finds this return and goes, “oh, okay so we have to return ‘main.’” Main is a function, so it calls the function and whatever is in the function gets executed at that point.

Now, as I said this code gets read whenever it is returned. There is another way that you can call a function and that is by, well calling it rather than returning it, so let's say now… I have to have a main function to start with, but within this I can have another function, so let's say I have another function called “example,” and this one ends and then down here I want to call the function. I'm going to do so by typing “example()” and that calls it, but you might wonder what the parentheses are actually for, so what the parentheses are for is to specify an argument. Whenever you create a function you can put arguments in the parentheses that then are used in executing whatever is inside the function. That will make more sense later, but for now with these kinds of functions you can just put empty parentheses and you don't really need to be making functions at the moment anyway so we'll get into that at the right time. Until then I'm going to talk about data types.


You have to understand variables and data types in order to get started in Lua. So a variable is a container that holds data. When you declare a variable you can determine what data it holds. This will stay the same until you overwrite it. A variable's name can be anything but cannot contain special characters and cannot start with a number, but can contain a number. Variable names are case sensitive, so to show you an example of that I can name a variable “myVar;” if I call this I have to capitalize the V and nothing else because it that's how it was set and so that is the name of the variable.

I cannot name a variable “1my…” - that doesn't work, but I can name it My1Var; so it can contain a number, it just can't start with a number.

Now there are three important data types in Lua: strings, numbers, and booleans. I'm going to create variables with a different data type so that you can see how they work; so I'm going to create a string and a string is absolutely anything you can type, just put it in quotes. So single or double quotes will work but for neatness I prefer to use single quotes. You cannot use the same type of quotes inside one another, so let's say I'm going to set this string to ‘My string says ‘ - I can't put another set of single quotes to use quotes, but I can use double quotes. So, ‘My string says “hi”’ so that works. It will interpret it as text but you can't use multiple sets of single quotes inside of single quotes. There is another option; if you absolutely have to, you can use the backslash as an escape symbol so then that escapes it and it gets viewed as text, and then go “hi,” back slash again another single quote, that works, it's just not very easy, so it's easier to use whatever type of quotes you don't want to use inside your string.

A string always stays exactly the same as you typed it and when strings are compared they are case sensitive so when I look at the string it's always going to have a capital M and it's also going to have a space at the end. If a string contains only a number it will still be read as a string, however, unless you convert it to an integer. So what is an integer? Well, a number can be any number and it can include a decimal point, mathematical operators, and so on. Trailing zeros after a decimal are cut off though and that's very important to our work with MA3 as you'll see. So I can set my number to be 1.31 but if I want to set it to 1.310 when I call it it's only going to be read as 1.31. That is critical to understand when working in MA3 because we have executor numbers that are labeled 301 through 315; actually, and beyond that, but the main ones that we use are 301 through 315, and so if I try to do 310 and I try to plug that into my console, my console is going to go, “Page 1.31? What? I don't know what that is,” and it's just going to throw an error, so in that case you would have to use a string because if you put, and I'm going to create another one, “string2,” if you put it in quotes it's not going to cut off that zero, it's going to look like this.

So the other type of value is a boolean. A boolean is either true or false, so my boolean can be true or it can be false. When you compare two things to another the returned answer that you get will be a boolean but you can also set a variable to a boolean if you need to, and you'll understand more about that later when we start using it.

The one other thing I do need to talk about is nil. So nil is nothing. myVar equals nil, nil is absolutely nothing. If you try to compare something to nil you'll always get an error. It will just be like, “I can't con- I can't concatenate a nil value,” or whatever. Instead, if you have a variable that could be nil you'll have to first say, “if the value is valid then do this.”


So now that you understand data types we're going to go back to functions. With this main function that I have I'm going to call an MA3 function, Printf, which requires a string as an argument. So Printf is the name of the function and then I get to put a string in these parentheses and whatever string I put in here will be used to execute the Printf function. In this particular case the Printf function takes my string and prints it in the system monitor and the command line history. So I'm going to type “Hello World” and I'm going to take this and copy it and paste it into this plugin right here, save, and when I run it it says “Hello World” in both of these places.

Now I can also put a variable in place of the string as long as I first declare the variable with a string. So I can turn this string into “Hello World” and then instead of this string here I can put “string” the variable, no quotes, and when I plug that into MA3 it is going to do the same thing. Now the reason for this is because my variable “string” is equal to “Hello World” so whenever it sees variable “string” it just replaces it with “Hello World,” now just to show you how this is working behind the scenes I'm going to do an example of making my own function with an argument built in.

This is going to be a redundant example because Printf already does this, but I'm going to make a print function with a variable “myString,” as an argument, so this is a local variable, local to this function; it's not going to exist outside of this, it's just whatever's in that parentheses, so myString, and then we're going to call another function, Printf, to print myString so we end it, we go down here, we call it, print and then we can put whatever we want here; we could put a variable again or we could put words, we can put a string, whatever it says, so now what's happening is it's taking this right here, here, and whatever the first thing is in this parentheses is going to be matched to the first thing in this parentheses, so it goes, okay, I have a variable “myString,” this must be in place of the variable “myString,” so anywhere in here that I see “myString” I'm actually going to use this. You can put multiple arguments, you would just separate them by a comma; so you can do this, and then another one. I don't have any use for any other ones but basically that's how it works. Hopefully that gives you a good visual to understand what's really happening when you put arguments into functions and of course I can run this and it's going to – what?

And it printed hello world but it gave me an error. I wonder why that might be. Well, at least I get to troubleshoot so you can see what that looks like. So it says “Lua API syntax error at 11,” that means line 11. It says Printf string returns nothing. Okay, let's see what's at line 11. It says Printf, ahah, so I used incorrect capitalization here and it doesn't match the one up there, so now I have done that; that should work now. Control A, control C, try that again, and there we go! It works this time.

So that's kind of what your error messages might look like. Syntax errors are relatively easy; there are harder errors to deal with that you will run into, hopefully not too often.

Now before I finish I'm going to make an example of a more complex MA3 function. This one is called Confirm. So the confirm function will display a popup with several customizable elements. It takes two strings. Let me type this out actually so we're going to go, Confirm, two strings for the title and main text; I'm going to go, “Title,” here's the main text, “Hello, this is my text,” there we go. And an optional integer which should be set to nil that determines, otherwise, which screen it goes onto, and then lastly an optional boolean which determines whether or not there will be a cancel button in the popup. True means there will be, false means there will not. The default value is true, so the Confirm function can be called with only two strings and it will work perfectly, but to specify that there should not be a cancel button you have to write it out as follows. So put a nil and then a false that will prevent there from being a cancel button. I'm going to run this and show it to you. So it gives me this popup and I can hit okay; there is no cancel button. If I go back and remove this, or it would be the same if I changed this to true, then what happens is it has an okay and a cancel button.


Hello lighting people! Welcome back to my Lua for GrandMA3 tutorial series. In this episode we are going to talk about if statements and value comparison, and I'm also going to tell you about local variables while we're at it. Let's jump in!


I'm going to start by making a new file here in Visual Studio Code. Always name your file “.lua” at the end… And I'm going to create a main function as a starting point. I usually start by making this sort of code, just function and return, just to start off with, and then I click back into it to continue making whatever I'm making.

So I'm going to start by creating example if statements and kind of show you how comparisons work as we go. So before creating my if statement let's say I set this variable “myVar” equal to 35. Now I'm going to create an if statement “if myVar is equal to (it's two equal signs) 35 (close the parentheses), then Printf(‘It is so’)” cuz what else would we say, I don't know, and then “end.”

So the basic things you need to remember about making if statements are: if whatever's in the parentheses is true, then we'll do this, end. So this is a complete if statement that checks the variable and does an action based on what the variable is. Other ways I could compare this number include less than, less than or equal to, greater than, of course, greater than or equal to, and then the last one is, not equal to.

You can also use “not” to convert a comparison to the opposite, so if I put this as “myVar equal to” and then I put this here, “if not myVar equal to 35” then it basically flips this.

If you want to check for multiple conditions before deciding whether or not to execute the code, you could use “and” or “or.” I'm going to remove this “not” here and go like this. I can change “myVar is equal to 35” to “if myVar is less than 36 or myVar is greater than 34.” So this is going to literally do exactly the same thing as the other one and I'll actually show you basically what this is doing, so myVar is equal to 35, we know that, and then when it checks it it's going to find that it is less than 36 and actually it's both less than 36 and greater than 34, because I should have made this an “and.”

So if it is both less than 36 and greater than 34 of course we know that's the same thing as being equal to 35, but I will show you how it finds that true and prints the desired result… There you go.

Or maybe instead of this I want to exclude 35 from the possibilities. I could of course do “myVar...” Uh, let me take this out… “myVar is not equal to 35” or I could do “if not myVar equal to 35” but another option would be to say “if myVar is less than 35 or myVar is greater than 35,” so that will just print this if it's less than or greater than, but not if it's equal to. Where it starts to make more sense is when you have multiple variables that you're checking; maybe “if var1 is equal to two or var2 is equal to two” – there's so many options.


But let's say I need more options than just whether or not myVar is 35. You can also use an optional “else” or “elseif” to check for multiple conditions at different times. Note that when you do this the first if is checked and then if it's not true the second one is checked, and so on. I'll show you how that works.

So let's change this back to “myVar equal to 35 then ‘it is so,’” then we can go down here and go, “elseif myVar is less than 35 then Printf myVar is less than 35” – I'll just change this to”myVar is 35” just cuz that's easy to understand; anyway then we could add another one that says, let's see, “elseif myVar is less than, no, greater than 35 and myVar is less than 40 then Printf… is more than 35.”

Now what if it's more than 40 though? I actually just want to do a blanket, like, catch all, if it's anything greater than 40, so instead of doing “elseif myVar is greater than 40” – which I could do – I'm actually going to do “else” and then “Printf(‘number is too large’);” why not?

So in this case what it's going to do is, it's going to check, “is it equal to 35” and if it finds that the answer is yes then it executes this and the rest of this just gets ignored, none of it happens.

If it finds that myVar is not equal to 35; let's say this is 34 instead; myVar is not equal to 35, then it goes, okay, well then is it – or “else,” is it less than 35? Oh, yes, it's less than 35, so it does this and the rest gets skipped.

Otherwise if we change this to 36 then it goes, “is this…? Nope. Well then, is this accurate? Nope, well then, is this accurate? Oh yes, 36 is greater than 35 and less than 40,” so it does this and this part gets skipped, but let's say we change this to 40, then it goes through all of these and it gets here and it goes, “well none of these are valid so we get here, this is the only option left, if none of these are valid then we do this.” And then the statement's over.

So while “else” can be very useful, it can allow for errors in some contexts. Let's say myVar wasn't set prior to running this; we'll just take this out entirely… It doesn't exist, we just run this. In this case “else” lets it slip by and print “number is too large” when in fact the number doesn't exist at all, so sometimes you need to specify every possibility you want to see used.


Alright, so this all applies to comparing numbers, but what about the other data types? Can you compare strings and booleans? Of course you can! Strings are always compared alphabetically if you compare them using less than or greater than symbols, and boolean and nil values can only be equal to or not equal to.

The only things to be aware of with strings, however, is that they are case sensitive so capital “A” is not going to ever be equal to lowercase “a.” For some reason lowercase letters are considered greater than uppercase letters so that may be relevant.


Now if you're quicker than me you may have already figured out how if statements can be useful to you in MA, but just to make sure we're all on the same page I'm going to go ahead and show you how I most frequently use them. I'm going to make a confirmation popup like the one I showed you in the last video, but this time I'm actually going to do things based on which button the user hits when they get the pop up. Now one way to do this is to put the confirm function in the if statement, and I'm going to start with this because it's a great visual. Remember that the if statements look for truth – you can specify “is equal to true” but it's not necessary with a variable that will be a boolean so in this case I go “if Confirm(‘Hey!’, ‘Confirm me’)” cuz I can, right?

I can specify “is equal to true” but that's not necessary because this will already be true by itself, because this is already returning a boolean, so the contents of the yellow parentheses will be whatever is returned from the confirmation popup, which will be either true or false, depending. So going to finish this if statement, then, let's do “Printf(‘User confirmed the popup’)” because we want information, right? Alright, “else,” we'll do, ‘user canceled the popup,’ and of course you can put whatever you want in here because it's just information for you; as long as you know what it means then that's great, but it's nice to have readable stuff.

So I'm going to select all of this and go over to MA3 to run it. So I run it, it says “Hey! Confirm me.” If I hit “Okay” then it says “User confirmed the popup,” if I hit “Cancel” then it says “User canceled the popup,” so now we have information. That's very useful, but there's one problem with this.

So let's go back over here and I'll show you, if I just need to know, “User canceled” or no, then this works, because they either did or they didn't. But let's just say I want to specify the false answer.

If I try to type “elseif” and put this whole confirm function in again it's actually going to run the confirm function a second time. So if the user cancels this one so this isn't true, then it will run it again to get an answer for this, and that's not good because then you have like multiple popups for when you only needed one.

So there is in fact a solution to this! Instead of putting the function inside of the if statement I'm going to remove this and set a variable to the answer. The way this works is, “answer” – that's my variable, remember, “equals” – paste that in there… So then the code gets to here, it runs this and sets the result to this variable, and then we'll put this here, “if answer then… elseif answer equals false then…” and the reason why this works is because, obviously this will either be true or false. So if it's true then this is true, this executes; if the statement that “answer is equal to false” is true then that makes this true, does that make sense?

So we'll copy this and you'll see the same result as before. It's going to look exactly the same when you run it, but what's happening behind the scenes is different. So confirm it, “User confirmed...” Cancel, “User canceled...”


Now just as a little nugget of a thing that needs to be explained and is kind of related, I'm going to explain what a local variable is. When you set a variable the way I've been doing, like this, “answer equals...” whatever, that variable is a global variable, meaning it now exists in the system until the system is reset through a reboot or the variable is changed, and it can be accessed using Lua absolutely anywhere. I could go create another plugin here, and call that variable, and it's going to be able to access it. Now that's fine if that's what I want, but sometimes you actually don't want to find that variable name somewhere else; in fact sometimes you might even want to use the same variable name in a different plugin to reference a different thing.

So to make sure that this cannot be accessed outside of this plugin I can type the word “local” in front of it and that sets the variable as a local variable, which means that it cannot be accessed outside of the containing function or statement. So if I set this in my main function, which is where it is currently, then anywhere inside of this main function after it it can be accessed. It can be accessed here; it can be accessed in here, it can be accessed in here… If I set a local variable in here it can only be accessed within the same if statement; it cannot be accessed in the whole main function. So basically whatever function or statement you set it in, it's local to that, but everything else that's also contained in that can also access it. If you try to call such a variable outside of the function where it is allowed to be accessed you'll get a “nil” because it doesn't exist.


Hello lighting people! Welcome back to my Lua for GrandMA3 tutorial series! While I do need to continue explaining how Lua works, I decided for today to take a break from that and show you an important, simple MA3 function and just explain some things about the MA3 system and how it uses code. I may or may not use this information a ton in examples in the next few videos, but I do know that having this knowledge will help you start putting things together to build your own plugins as soon as I give you the concepts in the next few videos.

So what are we actually talking about today? We're actually talking about how to do major normal functions of MA3 using Lua. Now of course there's a lot more in-depth stuff that you can do with Lua that you cannot do without it, but a lot of times when you're using Lua, maybe in place of a macro or something, some parts of what you need to do either must be done in the command line or would be best done through the command line, but you're in the middle of a Lua script rather than a macro, so how are you going to do that? Well, don't worry, because MA3 has several built-in Lua functions that can be used to interact with the regular command line. I'm going to introduce two of these and then show you the difference.


Now I'm going to write out the names of both of these basic functions that I mentioned. One of them is called Cmd and the other one is CmdIndirect. So both of these are going to take one argument: the string you want to put in the command. For example I'll just do one now, ‘Label Sequence 1 “Test.”’

If you're familiar with how the regular command line interaction works and writing commands then basically it just needs to be a valid command for MA3. I'm actually going to name this one “Test 2” and put the same thing in here without the 2.

Alright, now I have these two functions; they're very similar, I'm just going to run this plugin in MA3 and as you can see it executed both commands just as it would if you used the regular command without any Lua involved.


Now at first glance you would think that both of these functions are doing the exact same thing, but look over here at the system monitor. One says “Lua” before the command and the other one says “MainTask.” What exactly does this mean? Well, I'll explain. So the system has a couple of different tasks that can be used simultaneously and are not accessed the same way. The main task is for all of your normal user interaction and the normal command line of macros and all that. The Lua task is for, well, running Lua functions, but the system task designated for Lua can totally interact with the regular command line as well, it's just only capable of doing tasks given through Lua. Now you might be wondering why it matters whether you're running a command in the main task or in the Lua task, and actually it took me a while to really understand this, but it's simple, so I'm just going to tell you.

If you run a command in the Lua task and it fails, let's say, because of a syntax error or an addressed object not existing, the command will actually cause the Lua script to halt and not be able to proceed. You'll get an error and the entire remainder of your code will simply not be executed.

So if you run the command in the main task everything will be great, right? The command is sent to the main task, your Lua script keeps running, errors won't hurt you…

So actually there's a problem with using the main task as well and that is that Lua runs a lot faster than regular console operation, so let's say you have a command for the console in line 2 of your code and something else that is supposed to happen in line 4 that doesn't go through the command line but cannot be done properly unless line 2 has already been executed. Maybe line 2 stores a sequence and then in line 4 you're labeling that sequence using the system rather than than the command line (yeah, I'll show you how that works at some point, but you can actually change the name of an object without going through the command line to label it). Let's say you're doing that and you haven't yet stored the sequence, then trying to change the name of it isn't going to work and then your plugin will fail because it can't find the object. And of course you're probably thinking, “Well, yeah, line 2 and line 4 are pretty close to each other; of course it's possible that it might not quite be finished,” but that's actually not how it works. Basically even if you have 300 lines of code in between, Lua is that much faster. The regular command line system is way slower. Of course if you did execute the command in the Lua task it still takes just as long, the difference is that the rest of the code can't run until the command is finished again, though if the command is bad it will break the plugin.


So what's the solution? Do we just have to pick which problem to deal with? Nope! Remember I said there were three functions for executing commands? Well the third one is called CmdIndirectWait and this one is actually kind of like a combination of the other two. It executes in the main task, but Lua just waits for it to finish before moving on, so it's executed separately, meaning even if the command is bad it will not break the rest of the plugin, but if it is not finished with running then the rest of the code will not go through until it's done.

The only downside to this would be if you're running a bunch of commands and none of your code actually relies on them being completed to continue because it will significantly slow down your code while you're just waiting for the commands to finish. In that case I would definitely use CmdIndirect. I personally haven't found a great use case for Cmd but it's there if you need it.


Now before I end the video I just want to show you one of my favorite real-life, simple use cases for the command functions. This is also going to incorporate several of the concepts talked about in the last two videos, so let's do it together.

Jjust to set this up, I work for a church and we have services at the same time every week. On Sunday mornings I wanted haze to run automatically for rehearsal so my volunteers and I didn't have to remember to turn it on ourselves. The problem is, the worship and tech teams always circle up on stage together right before rehearsal to talk and pray beforehand. These circle ups don't always start or end exactly on time, meaning there's no exact time when the rehearsal will start on any given day. But I really wanted haze going by the time rehearsal starts.

I tried an agenda cue for several different times but they kept getting started while we were still on stage and it was loud enough to be quite a nuisance.

Finally I came up with a really easy solution. I was like, “why on earth didn't I think of this before?”

So I ended up using a function called MessageBox because it's more customizable, but for this video I'm just going to show you the Confirm function since I showed it to you in the last video and it's simple.

To set this up I'm going to create a function called ConfirmHaze. I'm going to set a local variable called UserOK equal to the result of the confirm function and the confirm function is going to have a title that says “Hey” and a message that says, “It's time to run haze for rehearsal! OK to proceed?”

Alright, and then I'm going to create an if statement, so “if UserOK then,” we'll do, “CmdIndirectWait(‘Go+...’)” I'm going to do “Page 1.115” cuz that's where I actually have it on the console and “end,” end the function and now I'm going to call that function.

There's no arguments in it. I don't need any. Time to run it.
“Hey, it's time to run haze for rehearsal. OK to proceed?” Now if I hit cancel nothing happens. If I hit OK then in this case it actually doesn't run because I don't have an object on page 1.115.


Now if you were thinking that it doesn't actually matter if I use CmdIndirect or CmdIndirectWait or Cmd you would actually be right because although I used CmdIndirectWait from habit, it actually doesn't matter. If I were to use Cmd in this case, as you could see it gave me an illegal object because the object didn't exist; that qualifies as a bad command and it would stop the rest of the code from running. However in this case there's no more code following it so it literally would not matter.

Since there's only one command it does not noticeably slow it down to use CmdIndirectWait and there's nothing else happening that I'm waiting for anyway. Obviously with a longer script it is going to matter and there will be reasons to choose different ones but in this case it did not matter at all.


So the way this actually works in our context is that I have an agenda cue, instead of triggering haze, trigger this plugin. And then when the plugin runs if the user is on stage already and not present at the console then they won't be there to hit Confirm, so it just sits on the screen with the confirm box up there and waits and it does not continue until someone presses OK or Cancel, and so whenever the person gets back to the console after the circle-up they can hit OK and then the haze runs. So we don't have to remember and it's super easy, never have to think about it again.


Hello lighting people! Welcome back to my Lua for GrandMA3 tutorial series. Time to make a trip to the hardware store ‘cuz we're going to be building tables today! Wait – whoops. Wrong kind of table. These ones are not made with a hammer and nails, and you can't eat, do crafts, or set your lighting console on them either.

Instead we're going to be talking about a Lua tool called a table that is one of the most powerful tools in Lua and it can be used to sort, organize, label, and retrieve data really easily. If you've used other programming languages you may think of them similarly to arrays; but the concept is more similar to the arrays of Java and Scheme than C or Pascal – apparently. To be honest, I don't know any of those languages, but that's what the book Programming in Lua says, so I'll take their word for it.

Now for our use of tables in Lua there's not actually a lot that you can do in Lua without tables, so I used them for a long time without actually understanding how they worked, and that resulted in me often programming things in the hardest way possible. Obviously that's not very ideal, so I'm going to help you skip that part of the journey and start off strong with a clear understanding of tables before we start using them. Let's get to it.


So what exactly is a table? First off, it's neither a variable nor is it a value. Programming in Lua defines it as an object, so you can think of it as an object, just like your physical table. Or the way I like to think of it is as a container that holds data. That's technically what a variable is, but the difference is that a table can hold compartmentalized data – labeled, separated, and neatly stored.

A table contains keys and values in pairs; so for every key there is a value. A key is like the identifier for the value, or the name that refers to the value.

A table is actually anonymous by default, unlike a variable, which can only exist if it has a name, a table can be used, for example, in an argument, without actually defining a name for it.

Most of the time though, you are going to use a variable to reference the table; so I'm going to go into Visual Studio Code and make a table to show you how it works.


I'm going to make a table; I'm going to call it “myTable” and my table is going to be looking like this. That is a table. That's it. Of course, this is an empty table.

You can put values into the braces straight away, or you can create it with empty braces and insert values later, or you can add values when you create it and still add more later. There are lots of options.

Like I said, you arrange the table in keys and values; so every value has to have a key. A key can be either a string or a number, and if you use a variable it will interpret it as the string or number it references.


There are several ways you can add data to a table. One is when you create it, so I'll add a key/value pair to the table I just created, right where I created it. So I have “myTable is equal to” – I'm going to put “a = 1.”

Now my table contains a value called “a;” that is, the key is “a” and the value is 1. Now this “a” looks like a variable, but believe it or not it's actually read as a string, and there's no way to use a variable or a number as a key when you add values to a table this way.

Now I've made this table; let's say a little later down the road I want to add another piece of data to my table with the key “b.”

I can do it like this. I'm going to use square brackets and put in quotes “b = 2.”

When you add data this way, however, the string “b” that is being used as a key has to be written in quotes, otherwise it's read as a variable and currently the variable “b” doesn't exist so it would just be nil – you can't set a table key to nil.

Now if I were to try to add data using braces like I did before it would overwrite rather than add to my table, but just like when I used the braces to originally make the table, this method right here sets the value in table “myTable” at key “b” equal to 2. This method works no matter whether the key is a string, a number, or a variable referencing one or the other.

There is another method that again only works if the key is a string, and that looks like this. “myTable.c = 3” so in this case “c” is the same as if I put a c right here. It's the same thing; it's a string, it's not the same as myTable[c] in brackets; that would be using c as a variable again.


Now one thing about table keys or indices that is important to understand is that you don't have to define them like I have so far. If you want to know for sure what the key is it's useful to do so (it's called giving the key a name or creating named table values), but you can also simply insert a value or several at a time, separated by commas, and it will automatically number them starting with 1 and going up.

For example I'll create another table. This one's called “MyNewTable” and it's going to be equal to 1, 2, 3. So in this case I have index 1 has the value 1, index 2 has the value 2, index 3 has the value 3 – maybe a better example would be 10, 20, 25, just to be different. Index 1 has the value 10, index 2 has a value 20, and index 3 has a value 25.

There's also another way that you can add to tables that only works with numeric indices so here's how that one works. “table.insert()” and you're going to put an “x,” a “y,” and a “z” value in here, where “x” is the name of the table that you're inserting into, “y” is the index where you want the value to be inserted, and “z” is the value you wish to insert. y is actually optional so if you leave it blank z will be inserted into the next unused index in the table. And you cannot specify y as an index that doesn't yet exist unless it is 1. Also if you insert a value at index y it will move the old y and those following it up by one index each; so in other words, to actually use this in a way that makes sense, I'm going to go, “table.insert(MyNewTable…)” value “2,” or sorry, index “2,” and I'm going to put the value as “A Value,” just a string here; obviously that value can be anything; it can be a number or it can be a string, by the way even with this one, like these can be strings, they can be variables, they can be whatever you want them to be – they can even be other tables.

So basically in this case this inserts a value at index 2. So then whenever you try to read this table 10 is going to be at index 1, the string “A Value” is going to be at index 2, 20 is going to be at index 3, and 25 is going to be at index 4.


Okay, this is all great for creating tables, but how do we retrieve the information we've so carefully stored away in the table? The good news is, it's exactly the same syntax used to store it, so just use “table.index” or “table[index]” like this, anywhere you wish to use that value.

Do not try to print the table as a whole because a table is an object, not a value, and while values can be printed, objects can't, since obviously we don't have 3D printers in our command line or anything.


I understand there are so many different syntaxes and the rules for setting and calling table values and remembering strings versus variables versus numbers, which ones can be written in which way, can be very confusing, so I've actually made a table info example file you can find on GitHub, and I'll link that as well to refer to at any time. Hopefully that will help. By the way, these double dashes here let you put comments in the code, so just, anything beyond the double dashes is commented out and the program does not read it when it's going through the code; it's just for you to read to see information and you can use these at any time in any Lua program. I use comments pretty heavily and really most people do because it's not necessarily easy to remember what everything means when you write a big program and having comments to remind yourself “oh I did this for this reason” or “this is what this function does” is super helpful.


Okay, I'm going to show you one brief example of storing and retrieving table values just so you can see how it works. I'm going to create a table called “mySequence” and I'm going to give it a value called “name” and make that a string, “Test,” and then I'm going to give it a value called “ID” and set that equal to 1, and now I'm going to create a command, CmdIndirectWait like I showed you in the last video, and this one's going to say, “’Store Sequence ’ .. mySequence.ID ..” – I'll tell you in a minute what these dots mean – “’ /noconfirm; Label Sequence ’ .. mySequence.ID .. ‘ “’ .. mySequence.name .. ‘”’
Alright, now, these dots, like I said, they actually mean to concatenate two things, and what that means is, it just, like, connects them together, so if you concatenate two strings that turns them into one string, and that's basically what's happening here, so we're going to – if you read this command the way it's actually going to appear in the program, it's going to say “Store Sequence mySequence.ID,” so it takes the value in the table “mySequence” at the index “ID,” that would be 1, so “Store Sequence 1 /noconfirm; Label Sequence,” again, “mySequence.ID,” so that's 1, and then we put another single quote, space, double quote, so that MA3 can read it properly, and then concatenate the name “mySequence.name,” which would be “Test,” and then another double quote here so that MA3 can read it as a string as well.

Obviously that doesn't matter in this case cuz it's only one word; if it was multiple words or it had a space MA3 would need those quotes to be able to read it properly, but that's going to interpret it that way, and so that's the way this works. The only thing I will say about concatenation is, anytime you're trying to concatenate a nil variable that, that is, well, anything that's equal to nil, you're going to get an error and your whole plugin will just stop working, so make sure you're never trying to concatenate a variable that you don't know has a value.

Now I'm going to take this and put it in MA3 and run it so that you can see all of that and, as you can see, “Store Sequence 1,” and we labeled sequence 1 “Test,” so that worked properly; we had no issues with that.


Hello lighting people, welcome back to my Lua for GrandMA3 tutorial series. In this episode we're going to talk about what has to be my single favorite thing about Lua – UI elements – and my absolute favorite MA3 function, MessageBox, which quickly and easily makes a UI element. Now in case you don't know, or don't remember, what “UI” means, it just stands for “User Interface;” in other words, a UI element is anything the user can see and interact with rather than something that just happens in the background in 1s and 0s. UI elements allow you to take input from the user and use it to make different things happen based on their choice. This is the basis of all computer programs; think of any game on your phone that gives you multiple choices. Do you want to take this path or that path? Based on your choice, different things happen. In order for that to work you have to have a UI element that gives the user a choice, then your program has to both record their choice and perform predefined actions for whichever choice they made, and we can do that in 3 as well, using Lua. In fact, we've already done it to some extent, using the Confirm function, but there's a lot more intricate stuff still to unlock, so let's talk about it.

Now at some point in the future I will definitely make a video about building custom UI elements, but it's complex and requires a lot of code, so for now we're going to focus on MA3's built-in MessageBox function, which already gives a lot of options with relatively few lines of code. After all, you're just calling an existing function and throwing an argument in there to tell it how to execute. The thing that makes the MessageBox function unique compared to those we've looked at so far is the fact that it actually takes a table as an argument. This table will contain lots of carefully defined data rather than just one or two variables. Good thing you understand how tables work, right? And if you don't understand them too well, don't worry, because it'll make more sense once we actually use them in this episode.


Now in a minute I'm going to show you a fully built out MessageBox with all the possible customization, but first I'm going to start with the bare minimum. You're not going to like it, but here goes. We're going to type out MessageBox like this, and now I'm going to put table constrictors, or braces, in here and hit enter, just for visibility. The only table element that is 100% required to prevent the function from causing an error, which is what would happen if you called it with empty parentheses, is the title, so that looks like this. “title = ...” I'm just going to put “Title.”

I'm going to go run this… As I said, it's not pretty. There isn't even a button to close it! Now don't worry, because you can always hit escape on your keyboard and that will close it, but this isn't very ideal, is it?

Instead, let's flesh it out. So I'm going to go back in here and show you this completely fleshed out, full of all of the information, MessageBox function that I have made. And this one has lots of comments, lots of specifically defined stuff. You'll be able to access this file right here if you're following along in GitHub, and you can refer back to it at any time, but for now since I don't want to actually change this file I'm just going to copy it. I'm going to not copy the main function, I'm just going to copy the rest of it, and I'm going to put it in my file for this episode so that I can change it freely and it won't impact the other one.

Now I'm sure you quickly noticed all the green text on my screen, and I touched on this in the last video, but just in case you missed it I want to quickly explain how comments work. These two dashes at the beginning of a line define it as a comment, so that makes it something that the system does not read. It just ignores it when it's going through the code.

You can also create multiple-line comments like this: “--[[” and then it ends whenever the closing square brackets happen.

You can use comments to either comment out code for troubleshooting purposes, or to just give yourself information like I did here. This MessageBox function is complete with literally all of the information I could think of to add, including every possible value you can customize as well as comments on all of it. Please don't hesitate to also refer to the manual, but I think in most cases my comments are better written and better illustrated, since they're in line with the code, than what you'll find in the manual. I also added extra information that isn't in the manual; feel free to just read through this whole function on GitHub Hub at your leisure and refer back to it or copy pieces as often as you need. My hope is that it will contain all the information you'll need as a reference, but some of these things are better explained visually so I'm going to show you a few things.


As you can see, this comment up here shows a list of the main elements included in the MessageBox function; you have a title, you have a message, you have inputs, radio selectors, swipe selectors, states, or checkboxes, and commands – and I'll show you what all of those terms mean shortly.

For now I'm going to go down here and show you, we have a title, we have title text color and back color and an icon; so what do these numbers for the text color and the back color actually mean? Well, it's actually a reference to something in your MA3 showfile. So this one right here says “1.7.” I'm going to go into MA3 and show you exactly what that is in MA3. If we go to “Desk Lights and Color Theme” and “Edit Color Theme;” you can find these color definitions right here. So you have a 1, 2, 3, and a 4 over here, and you have 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, whatever; a whole bunch of them here, and you're actually going to be referencing the ones on the right hand side of the screen, so you're going to want to look for the number 1, then number 7; this would be the information referenced by our first table value over there that says 1.7. It is not referencing this information here, which would be 1.10, it is 1.7.

The other way you can write that would be “Global.Selected” but put that in quotes, and if you do that, that works the same way, and again you do not want to put the name of it as it appears over here, so like I have one that says “Global.AlertText;” the name of the Color Def Ref is Global.Alert but that's not what you want. You have to put this right here, which is “AlertText.” Or it would be 1.31 here as opposed to 1.59, and it all works, it just references the color theme in your show file, and that is the only way to set text colors; you can't actually just set- you can't actually set an RGB value or any other type of color mixing value. You would have to go into your show file and, you can actually create your own color theme; you can go down here to New Color Def and add a new one and make it whatever you want. You can reference that, totally, but that's what you would have to do in order to make a custom color.


The next thing I'm going to mention is obviously we have this icon here, “object_smart.” Okay, what is that? Well, we have a comment here which says “an icon for the left side of the title bar” and that is, just to go back and show you, that's this right here; that's called “object_smart.”

So where does that come from? The options can be found by using “List” in the GraphicsRoot/TextureCollect/Textures CMD line destination. What does that mean? Well, this one took me a bit to figure out so I'll make sure you understand it. It is crazy but you actually have to come in here and go “CD” or “ChangeDestination” “Root” and, what was it we said, “GraphicsRoot,” okay, so we have to type that, and now we're in here. It says “Admin@GraphicsRoot,” now we have to do that again to, what's the next one? Yeah, I'm not going to remember. You do have to do these one at a time, believe me or not. “TextureCollect,” okay, “CD Root TextureCollect” – it's all one word. So, my bad, you only use Root the first time, so you're actually going to just type “CD TextureCollect” and then let's see what's the next one is. “Textures,” okay, “CD Textures” and here we are! Now this matches what it said: GraphicsRoot/TextureCollect/Textures. Now the options can be found using “List,” right, so, now we just type the keyword “List” and, oh my goodness, what a list!

Yeah, this is a really long list. I have not read through it and I have not even gone through it to find different icons. This is a list of all the icons and you can totally go through and try them all. I have actually only used icons that I've already found in example code in different places; I have not gone and searched out my own, but those are all options that you can use.


Okay back to this. The next section here shows the values that can define what can close the MessageBox, so autocloseOnInput is false, this determines whether the MessageBox will close when you press please or enter or if it will wait for you to close it. Timeout is 10,000 milliseconds; that would be 10 seconds. timeoutResultCancel equals true; we'll talk about that in just a minute, timeoutResultID, 36, alright, I'll also wait a minute on that. Timeout – if you don't set a timeout value the default is nil, and it will actually then not do what it is doing here, which is giving you this bar and timing out after the bar hits zero, which is not nice. Or, maybe it it is nice; it depends on what you want, but yeah, whenever this hits zero it just... Closes. Great. I will come back to this information in a bit when I get to what your results actually are.


Commands are the buttons that are right here, and yes, this froze because I went away from the application and it's all confused now, but these are the command buttons. We have “Yay!” and we have “OK Whatever” and in this case they're not tied to anything, they don't do anything; but the these are commands and the way you set them is, you give them a value and you give them a name, and the name is displayed on the button and the value is returned if that is the button that is pressed, and so you'll just make commands equal to a table that's in the blue braces, and then a table within that for each of the individual buttons that you want to make.


The next type of thing that you can put in a MessageBox are inputs. So inputs are… Let's just go back and run this again. These are inputs; “Input 1,” “Input 2,” “Input 3,” and you can put things in them that are then returned whenever the MessageBox is closed. That is fantastic so we have, again you have to put the information in table constructors and then set another table for each individual input. You can give them values, such as the name which is displayed, the value; you can enter a default value, as you can see there is a default value here, and then when you type it takes that away. You can add a blackFilter. A blackFilter defines characters which cannot be entered, so it will not let you enter a black filtered character. VKPlugin has a specific list of different types of values that you can put in here. The options are in the user manual; I'll let you look those up. TextInput is definitely the one I use the most. I honestly don't know if I've ever used one other than TextInput. And then the maxTextLength down here defines how many characters you're allowed to put, so I cannot put more than 10 characters in here. Then in number two… In this one, you can put the default value as characters that are not allowed, but the whiteFilter determines which characters are allowed, so if you use a whiteFilter then anything not included cannot be entered, so in this one we whiteFiltered all the numbers. Only numbers can be entered and the plugin value is set to “NumericInput,” and I will show you in a second what that difference actually does.

And then in input three I put a Cue ID, I put numbers and a dot here because those are what we would use for making a cue, and then the VKPlugin is “CueNumberInput” and I will again show you what that does, so let's go back to MA3, and in this one the onscreen keyboard opens up like this, in this one, the onscreen keyboard opens up only with numbers, and in this one it opens up specific to entering values that have to do with a cue number. Obviously you can still attempt to type numbers if you're using a regular keyboard, or you can attempt to type numbers here, you can attempt to type letters here, and whatever, but they're not going to work.


And then we'll talk about these next, so let's go back to VSCode, and what is a state? A state is obviously written just like these others, it has a name and a state for values, so the name is displayed on it and then the state is true or false; this is really simple honestly, so I'm not actually going to explain it in any more detail; I think you can figure it out: name; state is basically the status, checked or unchecked.


And then down here we have selectors. So there's two types of selectors, swipe buttons and radio buttons, and obviously that information is all included here so I'm not really going to go into it a lot cuz I'm trying to keep the video as short as possible, but we're going to have the name, selected value, that's the value that will be selected by default, we have the type; 0 or 1 for the swipe button or radio button, and then we have the values that are options, and you're probably familiar with these types of buttons because they exist in MA3, but this is a radio selector, this is the swipe selector; you know what they are.


And we'll go back to MA3 and down here I'm going to talk about the values that are returned by the MessageBox function, so as you can see the whole MessageBox function is a table and you have to put, uh you have to, you have to separate table values by a comma, so we have table value, title, comma, next table value, title text color, comma; whenever you have a table in a table it's the same thing, that's just how you write a table, and you can just leave out as many of these things as you don't want to use, and I very, very, very, rarely use all of them. Actually, I've never used all of them except to make this example, but the results that you have here are going to be also organized in a table, so to access them you have to know the table structure so the success returns, it's a boolean; true if the table's closed by timeout or command button unless timeoutResultCancel set to true, in which case it is false, so in other words this is where this comes in. So this is going to determine what kind of result you get for a timeout, and this is an optional value. You can set the timeoutResult to be any value you want; if you want it to be… I just I set it to a number because I like the number 36, but you can set it to be, “oh no the box closed on its own!” or you know, whatever you want it to be; it's just information for you if you need that information for something. I've actually never used a timeout except for this example, but if you need it then you have that option.

And then this result value right here returns the value of the command button that was pressed or the timeoutResultID if that was defined, so the timeout- so the result here, like I said, that's the value of the command button, so that's only going to refer to the command button and it's only going to be the value, which is why you have to know what the value is. You're going to always- there's always going to be a number. You're going to be like, “if myReturnTable.result == 1 then do XYZ,” you know.

And then the inputs returns a table, so like this table, do table and then whatever value you have under that, and I will show you in another video how we can make a short function to actually retrieve those key/value pairs, but it's not too difficult, and remember the inputs are the values that you've input here, so the value will be the value this had when it closed, whether you replaced it or it's a default value. And by the way you don't have to specify a default value, just so you know.

And then for the states, those will be the states’ names and boolean values, so whether the state was checked or unchecked when you closed it, and the selectors are same thing, key/value pairs with the selector names and boolean values, so basically if you understand tables you can make really awesome use of the MessageBox function, and it is so awesome, so simple to just throw into anything at any time; I love it.


Hello lighting people! Welcome back to my Lua for GrandMA3 tutorial series. Today we're going to be jumping through all kinds of hoops. Hoops? Wait, loops! Oh and there's no jumping involved actually, we're just talking about loops; by continuing on the topic of tables, which I've been talking about for the last two videos, and specifically I'm going to explain how the generic for loop works. Note that I specified “generic” for loop; the reason is because there are two types of for loops, “numeric” and “generic.” Generic for loops are the only ones that are specific to use with tables, so we're going to focus on these today.

Basically a generic for loop is a tool to go through the keys, or indices, of a table and give you information about each entry, and for some applications it's a lot faster than calling each index individually. Just to make this clear, “indices” and “keys” are basically two words for the same thing and I'll be using them interchangeably.


Let's go ahead and get started. The basic syntax for a generic for loop is going to be “for var, optionalVar” – I'm just using these as placeholders for now – “in pairs(table) do --my actions end”

So that would be basically how a for loop is made. There are two built-in functions in Lua to be used with the generic for loop and “pairs” is one of of them. Basically both of them do the same thing but there's an important difference I'll explain shortly.

To back up a little bit, let's review what we know about tables. I’m going to create a table here. One way you could create a table would be like this: “myTable =” - we'll put “{10, 20, 30, 40, 50}”

This kind of table is also known as an array. I didn't specify the keys for these values, so they get automatically assigned keys 1, 2, 3, 4 and 5 in that order. If I add another value; let's say I go down here and do “table.insert(myTable, 60)” then the new value is added at key 6.

A more familiar and often more useful way for us to make a table would be like this: “myInfo = {name = ‘John Doe’, age = ‘36’, birthplace = ‘United States’}”
In this case these values are specifically assigned to the keys “name” and “age” and “birthplace.”


Now I'm going to plug the first of those two tables into a generic for Loop and explain what it does. In this case I'm going to use the “ipairs” function and it's just written like that: “ipairs” and for right here I'm going to put the name of the table, which is “myTable” and then here we're going to go… I'm actually going to change these from “var, optionalVar” to “key, value.” You'll also see people just do “k, v.” And then right here I'll do “Printf(‘This is my value: ’ .. value)”

As you can see that's the variable I put right here. Basically what this does is it goes through my table starting at index 1 and temporarily stores the index and value in the corresponding variables, “key” and “value;” I could have named these values anything, it'll do the same thing. So it finds index 1, that would be right here, notes that the key is 1 and the value is 10, and uses that information to perform the task inside the loop, which in this case is printing the value. So we go, “for index 1, value 10, do this,” and we're going to print “value” and then it goes on to the next index, 2, and does the same thing, and so on. Once it reaches an index that doesn't exist then the loop just ends.


Just to show you how this works I will run it in MA3 and as you can see, it printed key 1, key 2, key 3, key 4, key 5; those values. And you could totally also do something with the key. You could print the key and the value if you actually wanted to.


So this ipairs function is great; this is basically how it works. You'll use it the same way no matter what you're doing inside of here. The only problem with it is that it only works with arrays like that, that actually have numeric keys. If you tried it with the “myInfo” table it simply wouldn't work. It would look for key 1 and not find it and end immediately. Instead you have to use this version.

I'll leave this for loop here so you can refer back to it and do a separate one. This one is going to say, “for key, value in pairs(myInfo) do Printf” – I'm going to go “(‘Key: ’ .. key .. ‘ Value: ’ .. value)” and let's go run this one.

As we can see we got the same output as before, and then we had this “Key: age Value: 36 Key: name Value: John Doe Key: birthplace Value: United States.”


To make an example of how this can be super helpful I'm going to make a fairly simple MessageBox function that includes inputs. These return tables with key/value pairs, so to list out the values instead of typing out “Printf(returnTable.inputs[1] Printf(returnTable.inputs[2]” etc. like we would otherwise have to, let's use the for loop instead, so I'm going to create a MessageBox, and you do this by setting a variable, I'll use a local variable, called “returnTable” equal to the results of the function, MessageBox, and then we put our table in here with a title and, why not, put a comma and then the next value. I'm not even going to bother putting a message in here, I'm just going to go ahead and put some inputs in here, so I'm going to create inputs equal to – remember you have to have a table – and then a table for each input, and for this input the information I'm going to use is going to be a name, which will be “Input 1,” a value… I'll just put a default value of nothing, and vkPlugin will be TextInput; that's just kind of the default normal one.

And I'm actually going to throw this in here now just so that you know, if you don't put a maxTextLength you might have a bug cause it to have a limited max text, and I don't know why but I did have this for a while where all of my inputs would only let me put in two characters on my lighting console. On my onPC it worked fine, but on the console I could only do a maximum of two characters, and by simply setting a maxTextLength I was able to get around that, so now I just always put a maxTextLength, even if it's 100 characters, it's just really good to have that so that it won't mess up in case it somehow messes up.

Let's do another one: “name = ‘Input 2’, value = ‘Something’” because I like to have fun that way, and then “vkPlugin = ‘TextInput’” and “maxTextLength = 100” and then, just one more, “name = ‘Input 3’, value = ‘3’” because I can, right? “vkPlugin = ‘TextInput’, maxTextLength = 100”

Okay that was a lot! Now we do have to put a command in here just to get it to close, and we'll go, “commands =” - again, same thing, two sets of braces. I'll just go “name = ‘Okay’, value = 1”

Alright, now I've created my MessageBox, and just to give you a visual, what we would normally have to do to get each of these inputs returned individually would be, “Printf(returnTable.inputs)” and then put the index here, so that would be “[1]” and then the same thing with the index “2” and the same thing with the index “3” and that's a lot!

So instead I actually have a better solution, and that is a for loop. Let's try it. “for key, value in pairs(returnTable.inputs)” – remember you do want to make sure you get the table of inputs, not the main return table, because the main return table will give you “inputs” as a key and a table as a value, and you can't print a table. Close that, type “do” and then “Printf(value)” – obviously you could put other information here, but I just want to get the value printed right now, “end” and let's copy and paste that.

Alright, we have three inputs, and if I hit “Okay” right now we're going to see “3,” a blank space, and “Something.” The reason for this is because inputs actually get ordered alphabetically, so I guess it's placing the blank space as after the numeric value and before the S. Now suppose in input 1 I put “abc” and then in input 3 I put “another value,” then it's listing them... Okay, that's actually really curious. I have no idea why it's putting “another value” before “abc” because that's not alphabetically correct.

But again, you put inputs into your table and you get a result, whatever that result may be, and I guess it's just confused about “another value” honestly because, I mean, it's ordering them alphabetically aside from that and that does not make sense to me, so I guess that's interesting.


The point is, it's way easier than actually typing out “Printf(this value)” for every value, especially when you have a whole bunch of inputs; or maybe you don't know how many you have. This will go through all of them, however many there may be.


Hello lighting people! Welcome back to my Lua for GrandMA3 tutorial series. Today the topic of discussion is going to be numeric for loops, and after the last video this one's going to be pretty easy, but we're going to get to some fun examples, so let's get into it.

The numeric for loop looks pretty similar to the generic for loop we talked about in the last lesson, and it's a similar concept except that it doesn't involve a table or even necessarily any pre-existing values at all, so to make one we are going to just type “for i = a, b, c do” I'll do “Printf(‘My number is ’ .. i) end”

So, what are all these letters for? Well, currently nothing, because a, b, and c aren't tied to anything; they're just, currently empty, variables. Before I set them I'm going to explain what they’re for. A is your starting point, B is your ending point, and C is the step in between. C is actually optional; if you don't include a C value Lua will use 1 as the step, but now let me show you how this actually works; and of course I could just set these variables before, but I'm actually going to just type the numbers in because I can in this case.

I'm going to set a equal to 1, b will be 10, and c will be 2, because 1's the default and obviously, since I'm an LD, I just like being different. If I run this code now, it is going to – I'll just do it.

It says “My number is 1, My number is 3, My number is 5, My number is 7, My number is 9;” so basically what's happening is, it is starting with i being equal to 1 – I'll go back to here, to show this.

For i being equal to 1, we do the contents, which is printing “My number is 1,” and then it increments from the number it is currently by number “c” and starts over again. So we did it with 1, we incremented by 2, we have 3, then it goes back through the loop, gets back to the beginning again, increments to 5, and then once it gets to 9 it can't go to 11 because the ending point is set at 10, so it just ends. In the meantime what's happening inside is each time it runs i is used but then i is immediately reset, just like your key and value in the generic for loop, i is reset, so i is a local variable that is only accessible inside of the for loop; even the value it ends on is not accessible outside the for loop, so you have to either do everything you need to do with the value of i while still inside the loop or you have to set another variable equal to i before you end the loop. Now the problem with that is that if you run the loop multiple times it will reset the variable every time unless you combine it with if statements. Looks like it's time to show you one of the ways I frequently use numeric for loops!

The way I usually use these is to find out if I have multiple of a song, for instance, in my sequence pool. How does this work? Well, as you surely know, if you have multiple sequences in MA3 that have exactly the same name, MA3 automatically adds a number to each one.

So let's say I want to make a list of every sequence in my sequence pool that has the name “Praise.” If I have multiple sequences saved with that name, the second one is going to be called “Praise#2” and the next one is going to be “Praise#3,” and so on. To make a list of all the sequences in my show file named “Praise” I can, of course, search for “Praise” – and yes, you can absolutely do that with Lua – but in order to find the extra ones I have to add a number and search again. That's a little tricky because I don't know how many songs I might have; I don't know how many to look for – and it becomes a whole lot of code and a ton of nested if statements to do it individually.

Instead I'll use a for loop. In order to show you how this works, I'm going to use a couple of nested MA3 functions you're probably unfamiliar with. I'll explain them in more detail in a later video, but for now I'll just explain basically what they're doing. Here's what my code's going to look like. “if-” here I'm going to be typing a couple things that are not going to make sense. I'll explain the gist of what they're doing after I finish typing them.

Now in here I'm going to create a table called mySongList. I'm going to add one string to it called “Praise.” Now I'm going to create a for loop for i = 1, 100. I do typically figure I don't likely have more than 100 of something. We're going to do “if (IsObjectValid()” - this is going to be pretty much a similar copy to the first one I have up top – we're going to do “table.insert(mySongList, ‘Praise#’ .. i)” and… No, actually we'll do “else break end end end.” Yeah you have to end all of those things and it often can get confusing if you have a ton of them, so sometimes I'll put a comment right after the end with what it's ending, but to go back to what all of this is, basically “FromAddr” – this right here is addressing the handle of the specific object, and then IsObjectValid returns a boolean for if that object is valid or not, so mySongList is going to include Praise if Sequences.Praise is valid, and then if Sequences.Praise is valid then I know I can look for the others, so I'll go ahead and do “for i = 1, 100 do” – is this one valid? (Sequences.Praise# .. i), and if it is then we'll insert into mySongList this, Praise# .. i, and if it gets to one that is not valid then this “else” happens, and the break keyword basically ends a loop, and this can be absolutely any type of loop; this can be either type of for loop, or another type of loop as well, which I'll talk about in the next video, but break just ends the loop and break has to be the last thing in a section of code. There has to be either an “end” or an “else” or a similar one of these purple keywords coming directly after it. It can't have another command after it before the end. But, break just ends the for loop it is directly inside of, so it ends this for loop and then we're at the end of this statement and continue through the rest of the code.

So now if I were to run this and I have “Praise#” whatever, I will have a table containing all of the sequences with that name.


Speaking of tables, I actually have another example to show you, and this one really shows you how useful tables can be. It took me a long time before I figured out that I could do this, and now I can't believe I was ever able to manage without this use of tables. Let's say I need to assign five sequences to executors for today. I've already mentioned that I work at a church, and since we don't always have exactly the same service flow I keep sequences programmed for various services, and every Sunday, Wednesday, or whenever we're having a service, I go ahead and run a plugin that asks certain questions regarding what type of service it is, and then outlines the default set of sequences that are most likely to be needed for that type of service, and asks the user to confirm that they're the right ones. I won't get into how all that works right now, but just to focus on the little piece of this plugin; if the user says, “no, that's not the right set of sequences” then the user gets prompted with a message box containing five inputs. The user types the names of the sequences they want to use, up to five, and then the plugin checks the values input by the user and matches them to existing data to figure out where the sequences are that it needs to copy, and assigns to executors to fulfill the user's request.

Have you figured out yet that we can do some pretty insane stuff with Lua?? There's a reason why I love it! But enough chatting, let's look at the code. To clarify, this part of the plugin that only deals with five sequences only goes to the part of the service after worship, which will explain the names you'll see. I'm going to copy this from over here – I just wrote it out over here so I didn't have to type it out on the video – I'm going to copy this and put it here.

Sometimes Visual Studio code does this, I hate it, where it just took away my spaces. These should all be starting here, like two spaces over, and I'm mad. The way to fix that is literally just to go through every single one and move them over, and it's very tedious. I'm not going to actually do that because it's only for visibility, it doesn't matter, but it annoys me to no end.


So I'm going to show how the this works. Basically, I have an empty table called PostWorshipSequences, and by the way this is very similar to what I use in my regular setup plugin, but it is not actually the same. I just wrote out something kind of similar to show you, and I did change names and things and slightly change the operations, just so that it will make more sense.

That was red because I was missing a comma here, and I just inserted that and now we're good, so I'm going to start by… These are kind of in the wrong order for the way I want to explain this. I have this right here set PostWorshipSequences equal to an empty table. You have to do this if you're going to be adding in specific indexes to a table; you have to first create an empty table, otherwise it can't find it. Then I have this table right here, called SermonDefaults, that has at index one a “Greet People” – that's a specific part of the service; they'll have a moment to just like, everyone greets each other, then “Video Announcements,” then we have the video sermon, then we have a live pastor come up, and then we have “Dismiss” where everyone walks out.

I need these names here in order to be able to reference them for the user. Now the reason I put these things in a table instead of typing them out in the place where I need them is because I've actually made it a habit to always set everything to variables that there is any chance it might change, because if we change the name of a piece of our service I don't want to have to go through my whole code and change it. The reason why that's particularly important is, obviously if it was just cosmetic it wouldn't matter, but these are actually the names of the sequences that we use, and these names are used to find the sequences, so if I don't change the names properly then the plugin won't work, so having them all at the beginning of the plugin where I can just go change them in one place and then they’re referenced by variables later on makes it a lot easier and more predictable to be able to know that I can just change it and it will all work. I highly recommend making that part of your workflow. Do not put hard values in your plugin that might ever need to be changed. I will save things in variables such as sequence IDs, the location of an appearance in the appearances pool, the name of a sequence, the number of an executor, absolutely anything related to my MA3 showfile. Again, definitely do that. So then we go down here; we've set this empty table for post-worship sequences, we've set this table with sermon defaults, and then I throw up a message box, it says “Confirm, Is this the right service flow for today?”… SermonDefaults index 1, SermonDefaults index 2, SermonDefaults index 3, 4, and 5.

By the way this “\n” creates a new line and \t” like inserts a tab space.

Then I have commands, “Yes” and “No,” so it shows you this text and then the user either selects yes or no. The value is 1, so if “ConfirmServiceFlow.result” – that would be this one, that's the result of the command, if that's equal to 1, that would be “Yes,” then “for i = 1, 5 do PostWorshipSequences” – again, that's this table – “[I] = SermonDefaults[i]”

So it sets PostWorshipSequences index 1 to be the same as SermonDefaults index 1, and then 2 and 2, and 3 and 3, and so on. Obviously I could actually just set table one equal to table two; in this case, set this table equal to that table, and not use the for loop, but this is a way to kind of show you just how that would work, and again this is actually just a way to show off some of my workflow; this is not exactly how mine works. Mine actually has a few more complications that make it necessary to do this, but I don't want to share that file because there's a lot more complex stuff in it that is too much to explain in a single video.

Then, that was if ConfirmServiceFlow.result is equal to 1. If it's equal to 0 then this “else” happens, which is a manual setup message box that says, “these are the names of the sequences which can be used for service” – again, in mine I actually have a longer list; it's not just the same five, but in this case I just put the same five in because that's easier. So we have the same five options and it says “please type the name of each element in the box for its place in the service flow” and we have inputs for sequence 1, sequence 2, sequence 3, through 5, and just one command which says “Confirm,” so then once this box is finished it goes “for name, value in pairs” – again, this is a generic for loop like we looked at in the last video – “(ManualSetup.inputs) do if (value ~= nil)” – so if the user did input something – then “for i = 1, 5 do if (name == ‘Sequence ’ .. i)” so this is a way of making sure they actually get organized properly. If name is equal to, and that would be this name, so the key in the input table, is equal to “Sequence i” – that'll be 1, 2, 3, whatever – then, for that same index take, set PostWorshipSequences[that index] equal to, the value from here. And I do use the function “tostring()” to make sure that it is read as a string, although it's not really necessary, it is a safety measure just to make really sure that it will work properly.

And of course you will find this Lua file in GitHub and you'll be able to copy this, run it on your own and play with it and adjust it and see how it works. I'm not going to to show you me running it just because I'm not actually doing anything with this information. Of course, the way I would use this is, once the post-worship sequences get set, whichever way they get set, I'll have another for loop go through and find the sequence ID for PostWorshipSequences[1] and then copy it and assign it to an executor for my service; and again, like, there's lots of uses of for loops in a lot of my plugins, and they can just do so much so quickly.

And another benefit, by the way, to having something in a for loop as opposed to writing it out each time is that if you make a typo there's less chance of you catching it and there's more chance of making a typo, because you have to type the whole thing multiple times with basically the same thing, you know, and it can also be harder to catch it just because you have more code, so I always recommend getting your code as compressed as possible and using these loops is a great way to do that.


Hello lighting people, welcome back to my Lua for GrandMA3 tutorial series! Today we're going to continue talking about loops, but of a different kind than we've discussed so far. Unlike for loops, which are basically going through various iterations of things and repeating until all the iterations are completed or the loop is broken, conditional loops are more like if statements; that is they only repeat if the specified condition is met – but unlike if statements, they repeat until the condition is no longer met. There are two types of conditional loops that work only slightly differently, so let's talk about them.

The two types of conditional loops we're going to talk about are “While-Do” and “Repeat-Until,” or, as they're properly called, just “While” and “Repeat,” but to be honest I find it easier to remember how they work by thinking of them the first way I mentioned them.

Now, you already know this, but just to review, the way a conditional statement works is, “if this is true then do that, else if this other is true then do that other, end;” but sometimes you need to do things multiple times.

I'll just create an example of trying to do this with “if” to show you how frustrating it can be. I'm going to be incrementing MyNum and doing an action at every number, like this. I've already set MyNum equal to zero; now I'm going to type “if (MyNum < 10) then Printf(‘My number is less than 10’) else Printf(‘My number is 10 now!’)” and this already auto-completed “end” for me, which is really nice.

In case you missed the last video I made, I recently started using an extension that works with MA3 API and Visual Studio Code. You can find it under the extensions in VS Code and it is absolutely amazing. I only just now found out that it also auto-completes if statements; that's really cool. There are so many cool things it does and I haven't even figured out what all they are yet, but to continue this now, I'm going to go on and do “MyNum = MyNum + 1” and then “if (MyNum < 10) then Printf(‘My number is less than 10’) else Printf(‘My number is 10 now!’)” and, okay, you get the point. That does not make any sense to continue doing this. Of course, one way to do this instead would be to place the original if statement and addition; that is to say, this if statement and this addition, inside of a function called CheckNum with the argument MyNum and run it 10 times.

Actually though the problem with that solution as well as this one is that they're both very limiting. They for sure only work if you know exactly how many times you need to compare the variable, and if you already know how many times to do the action, why bother comparing it now? I'm sure you're thinking, “why don't you just use a for loop?” and you wouldn't be wrong, if all I needed was these numbers, however, depending on where my numbers are coming from I may not be able to use a for loop in every situation; for example, if I'm just getting a random number and I know that number is changing and every time it changes I need to do this comparison but I don't know what it's changing from or to, then I would definitely need some way to do an action based on what that number is that's more efficient than this, because honestly, I don't know how I would make this work in this situation.

And I know this likely doesn't make much sense now, but don't worry, because I'll wrap up with a real-world example before the end of the video. For now, let's focus on the concept.


If only there was some way to keep looping this if statement until MyNum reaches 10 and then stop it... Wait! There is! Allow me to introduce the magic of a while loop. I'm going to take away these if statements because honestly I don't think you're going to need them, and so I've set MyNum equal to zero, and now I'm going to type “while (MyNum < 10) do Printf(‘MyNum is less than 10’)” and “MyNum = MyNum + 1” then after the “end” let's do “Printf(‘My number is 10 now!’)”

Alright, let me explain. So the loop checks the condition. MyNum is less than 10; after all, we just set it to zero, so MyNum is less than 10; the while loop executes the contents of the statement – that would be this right here, and then checks the while loop again rather than moving on at the end. It repeats, so you go back through here; again, MyNum is still less than 10, so we execute the contents, repeat again, and so on. As soon as the condition is no longer valid it immediately breaks out of the loop and now whatever comes after it gets executed. So once this gets to 10 it goes “is MyNum less than 10? Nope, it's not, okay. Go to to the end and execute whatever comes after it.” So then it will print “My number is 10 now!” which would be accurate at that point.

As with a for loop you can always use break to get out of a loop early.


Now this is a nice option to have, but while loops always check the condition first and then execute only if it's true. What if you need a loop to run once before checking and then check the solution to see if it needs to repeat or not? There's another option for that. The repeat loop runs the contents of the loop, checks the condition, and if the condition is false it repeats until the condition is true. The repeat loop is written like this. I've already set MyNum equal to zero. I'm just going to go down here and basically use that same variable. We're going to assume that this code doesn't exist right here, and I'm going to type “repeat.” This extension so nicely auto-completed the repeat for me, but I need to place some info inside it, so I'm going to do that. Going to “repeat MyNum = MyNum + 1 if (MyNum == 2 then Printf(‘My number is 2’) else Printf(‘My number is not 2.’)” and “end” and then after this “until” I'm going to put the condition, which is going to say “MyNum > 2”

I'm going to go ahead and actually copy this “MyNum” setting right here. I don't mind using the same variable, but I'm going to have to reset it in order to reuse it so I can show both of these examples. And now let's run it.


And over here in MA3 I do have these right here that I talked about in the last video, as I mentioned already. I'm not going to use the A_Art plugin today because I have to have my USB drive in order to load a new plugin with it and I left it at church today, so I am just going to copy and paste code like I've done before, but I do intend to use the A_Art plugin in future videos because it is going to be more convenient than copying and pasting.

So for now I'll just go ahead and do this and run it, and as we can see we have 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 instances of the sentence “My num is less than 10” and then it says “My num is 10 now!” that would be because MyNum went from 0 to 10 and then it was 10. And then it says “My number is not 2, my number is 2, my number is not 2;” just to review, that would be because right here we have “MyNum is 0,” then we add 1 to it, that makes it 1. 1 is not equal to 2 so my number is not 2. MyNum is not greater than 2 so we're going to repeat this. MyNum + 1 is 2, so MyNum is 2. We print that. MyNum is not greater than 2 so we repeat again; 1 more is added, it's no longer equal to 2, so my number is not 2, and now MyNum is greater than 2, so the repeat loop ends.


Now I'm going to go ahead and show a real world example of this. I am going to paste some code I already have prepped and it looks like this. So I'm just going to go over this and show you basically how it works; I'm not going to run it because by itself it doesn't actually do anything, but as always you're going to have access to this code on GitHub, so you can play with it, tweak it, alter it, and do whatever you need to do to see how it works and see how you can make it work in even cooler ways! This plugin is designed similarly to a small piece of the same plugin I pulled an example from for the last Lua tutorial video.

My plugin, among other things, actually finds out from the user what songs they need sequences for and copies the sequences from a database to a location where they can be used and modified without impacting the master sequence, but sometimes the location that I'm trying to copy them to has sequences in already, so here's how I handle that.

I am going to be reusing the MA3 functions I showed a while back that I haven't fully explained yet to check if a sequence exists. Don't worry, we'll get to that soon.

This loop serves the purpose of finding out if the song's location is occupied and, if so, whether the user would like to use the existing sequences rather than copy new ones, or delete the existing ones so that they can copy new ones, or try a different location, or just cancel the action. So first we check the variable “SongSetup” to see if it is nil, and it is, meaning we have not yet resolved this question.

Now, is the location for the songs occupied? That is what this piece does right here. This function returns a boolean and so, if this is false then that means that there is no object in SequenceLocation, in this case 1, so all of this is skipped because this is false, and we go to the else, which sets SongSetup equal to “Approved.” In this case then, the while loop ends, because with SongSetup not being equal to nil it cannot repeat, and SongSetup being equal to “Approved” means that everything can go on as it should with the rest of the plugin.


Now suppose there is an object here. This returns “true,” then what is inside of here gets executed and in this case that puts a message box on the screen saying “The location for your sequences is currently occupied; would you like to use the existing ones, delete them, try a different location, or cancel the setup?” and if the user answers with option 1 then sequence 1 thru 4 is deleted, SongSetup is set to “Approved,” the rest of this doesn't happen, and the rest of the plugin continues as it should because SongSetup is equal to “Approved.”

If the user chooses to use them then SongSetup is equal to “Assignments Only” and that means that instead of asking the user what songs to copy and then copying new sequences and doing a whole bunch of extra things it only assigns the existing sequences to executors so that they can be used. If the user says “Try a different location” then that's where the while loop really comes into play, because the sequence location is changed to whatever it currently was plus 10, and SongSetup is not changed, meaning it is still equal to nil and this loop repeats, and of course the same cycle happens again.


Now, it was intentional for me to use an “else” right here, so that if either the user presses “Cancel Setup” or something else causes something to happen, like maybe the user pressed escape and didn't choose an option, it's going to set SongSetup to “Canceled” so nothing crazy will happen.


In this use case the loop is sort of more of an if statement that can be easily repeated if necessary but it rarely actually is. It is really important to have that option though! Let me show you why. If the user chooses to try a different location, we add 10 to the sequence location and try that, but then that one also has something in it, then we have to have the option to try again, right? And so then we have, within this, we have to put this whole if statement again, and then within that we have to put this whole if statement again, and again, and again, and where does it end? Then what you have is a plugin that only allows you to try a certain number of locations, and that's kind of limiting, and obviously my show file is limiting in itself because I have other things in my sequence pool and I wouldn't want to put my songs just anywhere (and you could also put a cap on this, of course), but you don't want your plugin to be limiting you; that's why this while loop is helpful, because every single time – you can run this a million times, it's not going to matter. It's going to just keep looping the entire time, and it's the same amount of code. You don't have to write out more code and have more potential for errors and all the stuff that comes with that.


Now in this case I used a While loop, but you can totally use a Repeat loop as well; that would just look like “repeat” and then instead of “end” we'll type “until SongSetup ~= nil” and that would do basically the same thing.

It does not check for anything at the top, it just runs it, but we for sure want it to run once anyway, right? So it would go right through it and then if the object was not valid or the user had chosen an option that allowed the song setup variable to be set then it would not run again, because SongSetup would already be not equal to nil, but if the song setup was still equal to nil by the end of it then it would repeat, and so on, and so it would basically work the same way as a While loop.


In most cases you can pretty much use either one, but a lot of the time one is more intuitive than the other, and in some cases it really is important to use one over the other; so it's just really important to understand both and how either one could be used to your advantage in different scenarios.


Hello lighting people! Welcome back to my Lua for GrandMA3 tutorial series! As you can see in the title, today we are talking about “handle”-ing GrandMA3 objects, such as sequences, groups, and so on. I believe with all of my heart that this is the most important and exciting video in the entire series. Learning how to play with MA3 objects like sequences, groups, presets, worlds, and everything else from within the system, using Lua, will not only give you a lot of power but also change the way you understand how MA3 works and the way the system is built. This is one of those complex subjects no one ever really explained to me, but don't worry because I'll lay it all out for you neatly in this video and if you have any questions you can always hit me up in the comments or the Discord server. Well, what are we waiting for? Let's get started!


The Lua handle for an object is not the same thing as the MA3 handle you can use, for example, in a macro, but it is a unique identifier for an object that can be found given certain information, and you can always find it using either the name or the number ID of an object. Come with me over to the MA3 user manual. I'm looking at version 2.1 because that is the version of Lua I'm currently using. If you look at the plugin section of the user manual, which I already have pulled up here, you can see these topics over here: “What is Lua,” handle, etc. Let's look at the handles one. This also says “light_userdata,” and if you read this you can see that's because it's a custom data type called “light_userdata.” It says the handle is a unique identifier that refers to a GrandMA3 object, for instance, a specific sequence, cue, preset or fixture. The object the handle refers to has some properties; some can be changed and some are read-only. The object might also have child objects. And then there are a bunch of object API functions here, and a lot of them are actually quite similar to each other. I would definitely recommend you go over them yourself but I'm just going to talk about the ones I use regularly.


So first off I'm going to show you FromAddr(). This one takes an address and converts it to a handle, and it's one I use fairly frequently, so let's look at it. It does this, “mySequenceHandle = FromAddr()” and there's just this string of numbers, like what does that actually mean? I'm actually going to go ahead and just copy this line of code right here and put it in my thing over here, and I'm just going to add another line of code that says Printf(mySequenceHandle) because that way we can actually see it, and now I'm going to make use of that A_Art RunLua plugin that I've talked about in the past; I'm going to just save this file real quickly and then go over to MA3 and I have this macros pool here instead of my plugins pool like I have before, and so the way I'm going to run this plugin is just click this and then it already has the name put in here because I was testing it earlier, and so I'm just going to click “independ” and hit “okay.” It created this macro. Now I can click this macro, and it created a plugin for it and it ran the plugin, but it came up with an error, so let's see what that's about.

It says Printf returns nothing. So if I go back here… I'm not 100% sure… I don't think this should be doing that, but I'm going to use the “tostring()” function which you can always used to turn any data type into a string, and now it should for sure be able to run it. And, okay, there we go. It said “Group 1.”

So I guess just because it's not technically a string it didn't want to do that, but this is referring to Group 1, and the way it got that data was- oops, I can't look at the code this way. I'm going to have to go back to VS Code… So it has “13.13.1.5.1” and this is not a sequence handle, so that was an error on the part of whoever wrote the manual because this is clearly a group handle; it came up with Group 1. But basically, what these numbers are is the “13.3.1” refers to the data pool in the show file, and then the 5 is Groups and the 1 is number one, so it's group number one. Another way that you could write this would always be to do it like as a named string instead of a numbered string, so you could rewrite this as a named string. So I'm going to I'm going to start with number 13, and the number 13 can be replaced by the word “ShowData,” so that's what the number 13 I guess, represents, and then “DataPools” for the second one, and then for this one over here we can do “Default.” I only use the default datapool; if you had other data pools then you could specify which one here, but number 1 is the default, and then for number 5 I'm going to type “Groups” and then… Yeah, we can do 1, or Group 1 in my show file right now is called “All Quantums,” so just to show you I can absolutely type that name. It does have to be “Groups” plural rather than “Group” because the object is called “Group 1” but the groups pool is called “Groups.” Same thing with sequences. So it's whatever the object pool is called when you open up that object pool in MA3, that's what it's actually called, not the singular. And now I'm going to save this and go back and run it and it still showed up “Group 1” because that named string refers to the same thing that the previous numbered string does.

And I could change this to “Sequences.Test” and that gives me Sequence 1 because I have a sequence right now in location one called “Test,” so that did that.

I forgot I did not save my view, so now I have to make this say “Macros” again.


Now I just want to go ahead and explain what's really happening with all of these dots. If you did not watch my video on tables, which was episode 4 in this series, definitely go back and watch that because it's going to help you a lot. This is a table structure right here, so we have a table called “ShowData” and within that is a table called “DataPools” and within that is a table called “Default” and within that is a table called “Sequences” and within that is a table called “Test,” and yes, “Test” is a table as well. It has a name, or handle, which is “Sequence 1” but it also contains a whole bunch of other information which we'll talk about later on in this video. The reason why these can also be accessed using numbers is because they are also numbered, and so this is- this is basically just table structure. We're trying to find this table right here, Sequence “Test,” and we can we can find that by putting in the entire path within the tables, and then we get to it and that accesses the information about the name of that item.


Alright, back to the MA3 user manual. We are now going to talk about the “Addr()” or, I'm just going to call it “Address” – the address function. I don't use this one really, but it will be useful for you to understand. It does exactly the opposite of FromAddr(); it converts a handle to an address. So I'll just show you that. And I mean, we can see the example they have here is like – they're using ObjectList to get the handle of an object first, but we already know this variable, mySequenceHandle, contains a handle. This is now a handle to this object, so I'm going to do “Printf(tostring(mySequenceHandle:Addr())” and put that parentheses there, cuz it needs that to figure out that it's a function and then we'll go run that. And it says “Sequence 1,” “13.13.1.6.1.”

So I hope you can see now that you can use different methods of finding what this data is, whether you have the number and you want to put the number in here or, and you can mix and match names and numbers too, um, if you have the name, and you want to put the name in here to find out what number it is, or if you want to find out what name it has by putting the number in; you can do all that if you want to figure out, like, “Wait, what about my Macros pool?” You can change this to “Macros” and you can change this to 1, let’s see, cuz I don't know off the top of my head what my number one macro is called, and yeah, I know this variable is called mySequenceHandle and this is no longer a sequence, but it doesn't matter. So let's go run it. And it says “Macro...” So macro is number 8, in the datapool.


So now that we’ve talked about these different ways to address objects, let's talk about the real usefulness of handles and some of the cool stuff you can do with them. There are a number of MA3 API functions that use handles as arguments and do awesome things with them. First off, remember how I said that addresses are the table path for the object in MA3? Well, the final object you're referencing, let's say, a sequence, has a table full of data; I mentioned that already, but the sequence has a table of data, and this includes things like name, number, and lots of other things.

The quickest way to find out what data an object holds is by using the Dump() function. So you're going to do that basically like this. I'm going to go back in here and I'm going to… I'm going to, a lot of people do this, I'm going to do this; I'm going to do “Print” – a whole bunch of dashes – “Beginning of Dump” and then a whole bunch more dashes. This is just so you can easily see where your data starts and ends. And then we're going to do “Printf(tostring())” again, we'll use the mySequenceHandle object, which is no longer even a sequence, but it works. “mySequenceHandle:Dump()” and don't forget the parentheses. And now I'll go down here and do an “End of Dump” line as well, just so we can see that, and I'll save this and go run it.

We have whole, huge amount of information here. It says the name of macro 1 is “RunLua Pro,” the class is “Macro” the path is this right here, the properties include all these different settings, and some of these say “Read-only” – that is important because those that are not read-only can be edited using Lua.


And I have been using FromAddr() to find this handle. There is another function that I like to use though, called “ObjectList” and there's a reason why I prefer it. Let me show it to you. So I'm going to going to make a new variable. I'm going to call this one just “Handle” and I'm going to use ObjectList, and this really cool extension I'm using put all that information in here. It, like, shows me what places I need to put data. So I'm going to say “ObjectList()” - it's going to be a string “Sequence 1” and then I'm not going to use this argument right here, but just say “Sequence 1” and then outside the parentheses you do have to put an index, “[1]” and the way this works, and the reason why I prefer it, is because first off, you type in the string exactly the same way you would address the object in the command line, and it just looks in the current datapool. And then the other reason is because you can put multiple objects in here, so if I wanted to put “Sequence 1 thru 3” I can get the handles for all three of these at once. It makes a list of them in a table, which is why I had to put this index here, because this function actually returns a table and within that table is the handle for each of the objects that has been referenced, so even if I just put one object, which I do most of the time, “Sequence 1,” it's going to be at index 1, so I have to put that.

So I found my handle, and then I put this like this. I'm going to run that. Obviously, it's going to be a little bit different than before because this was a sequence, and… Oh, because I scrolled up I have to scroll back down before I can see it. And it has a lot of information here. Oh dear. This is- I've heard this is some kind of bug, I actually haven't run into it before. It's overlapping some of the data, and that's really hard to read. Oh, did it just? Oh, oh, that's weird. Okay, it's – yeah, it's overlapping stuff. But it says class is “Sequence;” this is the path, properties… You can read through all the properties… and then it has a list of children here, and those children are the cues that are in here. They have the numbers; it does count the OffCue and CueZero. It has the number, name and class of each object, and it goes all the way through them, and I have a whole bunch of empty cues here and it lists them as well.

So that's how that works and it's really cool, but whenever you have the handle of an object like this you can also just use this command to get this information. Now I'm going to leave this code in here because I want you to be able to reference it, but I'm not going to – I don't want it to keep running every time I run this code, so I'm going to comment it out. And now, let's see how can I play with this.


I'm going to do “Printf(Handle.name)” and show you what that does. Oh my, this is really frustrating. This is ridiculous! It's a little bit better over here. It printed something that I can't really read… “Macro 1...” Printed the string from the numbered address from Macro 1, and then it printed that name that I asked for; printed “Test.” So that's where it's taking this handle and going, “Okay, at this handle (which, as you remember, that's actually written out like this – I'll change this back to “Sequences” so that I can show you this actually matching.) So this is referring to the same thing, so this is this handle… So we have a handle, and then we take this handle and we put it here, and we go do “name” so then it's printing that name. We can also change that name from Lua, like this. Let's go, “Handle” – I can spell – “Handle.name = ‘New Name’” – let's go! And I run that, and let's go to sequences… Voila! It renamed it. Isn't that awesome?! Once again, I need to seriously just save this view. I'm going to… I'm going to do that real quick. And once again, I just did that with the name; you can do that with the note, you can do that with a command in a macro, you can do that with any property that you can find using the Dump() function that isn't labeled read-only; renaming, changing notes, changing commands… I change commands in cues, I change names of cues and sequences, I change the notes as well, and I also read that information sometimes, and I'll, like, read the name of one cue and add something to it, and name another cue that or something. I mean, it's a really fantastic tool, believe me.


Now, with ObjectList() you can also put the name of the sequence rather than the number; you just have to put it in quotes. If it has spaces… and, actually you might have to put in quotes anyway, I don't know, but I, I just did that. I'm going to change the name back to what it was before actually, so I'm going to change it to “Test” and let's go do that. And it says right now that the name is “New Name” but then it changed, so now it's back to “Test.”


There are a couple things I just want to mention about using this, so one thing is, if you're changing or reading properties of a cue you can't just do “Sequence 1 Cue 2” and change that. Actually, I think it works with the name. Uh, there – I always forget, but there are some properties it works with and some that it doesn't. There are some properties, for sure the command, that you can't just do “Sequence 1 Cue 2” because it's tied to the cue part, so you have to put cue – “Sequence 1 Cue 2 Part 0” if you're not using individual cue parts, and as someone who does not use cue parts in programming, this was not like the top of my mind, like “oh, I should throw a part in there,” but it will cause problems for you, so make sure you always, I just make it a habit to just always put the “Part 0” in there if I'm addressing a cue because that makes sure that it will always work.


Another thing I want to mention is that if you're going through cues and, so I've, I've done this before. Let's say, “Sequence 1 Cue 1 thru” and I just want to get a list of all the cues in sequence 1 starting at Cue 1 or whatever. If you're then trying to reference the handle of that cue, keep in mind the index number when you do that is not going to be the number of the cue, it is going to be the number of the cue in the sequence; so if you have cue 0.1, cue 0.2, cue 0.3, cue 0.3 is going to be index 3 because it is the third item. Do not forget that.


Now another very important thing I need to mention is if you try to look for information about an object that doesn't exist, guess what? It will throw an error and break your code. This is actually a table thing; if you try to read table.index and table doesn't exist then it will throw an error because you're trying to access an item in a table that doesn't exist; but if you're trying to if you're just trying to call table and table doesn't exist then it's just going to be like, “oh, that's nil” and it's not going to cause an error, it's just going to, not find anything. So you'll never get an error looking for sequence 1, why? Because the sequences pool always exists; nothing's going to stop that from existing, so since the sequences pool exists, looking for sequence 1 is just going to go, “oh, this doesn't exist;” but if you try to look for sequence 1 cue 2; alright, I'm not 100% sure if that would work or not. Let's actually try. Let's try “Sequence 2 Cue 3.” Now I don't have a sequence 2 in my show file. I'm also going to… I'm just going to comment this out. I don't have a sequence 2 in my show file; let's see what happens. Yeah, it threw an error. It cannot find it.

Now it didn't, it didn't throw an error in this line, so looking for a cue that doesn't exist doesn't cause a problem either, which may be because of the way we wrote this. I think if you tried to do it in here like “Sequences.2.3,” I think that would not work.

What happened was exactly what I was about to tell you, which is that it looks for this object, and this object is nil; Sequence 2 Cue 3 does not exist, so then you try to reference the name within the table for Sequence 2 Cue 3 and it goes, “that doesn't exist!” and it throws an error and your code just stops running.


So what does that mean – do we just have to never reference the name or command or number of an object unless we're 100% sure it exists? No, that's not actually what that means. You just have to do a little bit of a workaround. So we have “Handle” here, and then, let's do a little if statement. “if” – I'm going to use IsObjectValid() and fill this in with “Handle,” then, going to just paste this in here.

If the object here is valid, then that. That returns true, so then this is true. So if this is true then it does this, so if this object exists, then we look for the name, and that is going to prevent it from throwing an error. It just goes, yeah there's, there's nothing; nothing happens, there's no error, the plugin finishes normally, and then again, if we go back and we make this “Sequence 1 Cue 3,” that does exist, so then it will do… Oh, the name is “Brief Stomp,” apparently.

Super easy; so just always, if you're referencing… If, if you're ever going into the green. If you're taking a handle and you're going to try to read or write information for the name, the note or the number, you definitely want to place that inside of an if statement and just check “IsObjectValid(Handle)” first, and then if it is, then do it.


Hello lighting people! Welcome, welcome, welcome back to my Lua for GrandMA3 tutorial series. Hey, if you've made it this far you are about to set to be a Lua expert among GrandMA3 programmers. Now, this is the last video in my Lua for GrandMA3 tutorial series; this kind of wraps up the basics as far as I'm concerned, but no worries because I have lots more to share still, just not exactly basics, and I'm not going to, like, include it as a numbered part of the series. But, for today we're just going to go over some basic, kind of, miscellaneous, final programming tips that you'll need to get the most out of your Lua scripts without getting too deep in the weeds yet.


First up, let's talk about a pretty serious scenario. You've been experimenting with some code and trying all kinds of different things, and now your stuff is just throwing errors that don't make any sense, or you can't remember what variables you set and then deleted, or you just need them to reset so you can see how your code works the first time it's run in a clean system. What do you do? You could always reboot, and while it's not a bad idea, it is a little time-consuming, and if you just need to reset your Lua variables, is all that really necessary? No, of course not. There is a keyword that you can run immediately that just resets your stuff. That keyword is “ReloadUI” and you just type it into the command line and hit enter and life is good. And it always seems to lag a minute for me, I don't know why, but it's so simple. It has saved me literally countless times.


Tip number two! The second thing I have to talk about is the way you can give a main function an argument. You know how when you call a function you put the function name and then in parentheses you put the arguments? Now personally, most of the time when I call a plugin I just tap it in the plugin pool. You can also type out “Plugin x.y” with X being the plugin number and Y being the component if you need to call a particular component. If you do that you can actually add an argument after it like this. You don't want to put them in parentheses, just in quotes, so you would type “Plugin 1,” for instance, and then -space- quotes, and you put your argument and close it. I'm not going to actually run that because that's not going to do anything right now, but as an example I am going to build a quick little plugin that takes a string and prints it and show you that, so I'm going to go into VS Code and have this function right here... I'm going to just do “Printf(arg)” and I'm going to put that “arg” here. Actually though, there is an important thing. You can't make that your one and only argument for your function. It's a weird interesting thing, but whenever you call a plugin, no matter how you call it, the console automatically gives it as an argument the display from which you called it. So if you're going to actually put arguments in here that you have to use, you need to put a dummy argument in first. I like to call it “display,” and then you can put “arg” as your second one. What's going to happen is, when you call it giving it an argument, your console is going to fill in this one and it's not going to be used, and whatever argument you gave it is going to be used as the second argument, so that'll be this one, and then that'll go here. If you were to try to omit this, you would just get an error. If you were to try to give your plugin multiple arguments from the command line when you call it, that's not possible.

So I'm going to just save this and go over here and I'm going to run this, and I just need to put in the name of this video right here, which is the name of my plugin.
I think that's what I called it. I hope so. If not I'm going to get an error. Alright, it did that, and then, yeah didn't find it. Put the wrong name in! Okay, what's it called? “Miscellaneous Important Info.” Okay. And, there we go. I'm going to just delete this… And now it stored and called the plugin, and it threw an error because I called it without giving it an argument and it can't print a string that doesn't contain any information, so I'm going to call this the other way. What plugin is it? It says it is… Oh, it doesn't tell me what plugin it is. That's handy. I mean, you can also technically call it by name, but that's a long name and I don't want to type it, so I'm going to open my plugins pool, and it looks like it is Plugin 5, so try that. “Plugin 5” and then in quotes I'm going to put “My text” and enter, and there we go! Now it ran because it used that as the argument. A thing to be aware of is that your arguments are always read as strings when you input them this way; and as I already mentioned you can't use more than one argument in calling a plugin from the command line.

There is a workaround if you really need to and I looked into this since it's important to some people. I prefer using popups to get info from users, but although I don't like typing out my plugin calls in the command line, I admit it requires significantly less code if you can put your data in that way, so it is a useful thing to be able to do, and I don't know, maybe someday I'll do it. Well, what's the workaround to only being able to put in one argument?

To explain I'm actually going to need to move on to the next topic, so I'll just use this as a starting point. Let's go over some basic pattern matching! There is a string library in Lua containing functions that can be used used to find and match patterns and all that stuff. I'm just going to talk about two of these functions today: string.find() and string.sub(). string.find() enables you to find certain characters within a string, so I'm going to go back into VS Code and… I don't know if I want to leave this code here or not, to be honest with you. I'm going to comment it out; and it won't matter if you call this plugin without these as long as they're not used. So to use the string.find function you're going to just type, “string.find(mainString, findString)” I will explain what those do. This is going to return- whenever you run this, it's going to return two numbers. One will be the position of the first character found within the string, and the second one will be the position of the last character. So if I do “a, b = string.find()”- I'm going to replace this with a string, “This is my string,” and then I'm going to replace this one with -space- “is.” Then what it's going to do is it's going to give me “a” as 5 and “b” as 7, because, as you can see, it's going to going to go “1, 2, 3, 4, - 5!” This is this, so that's 5. “6, 7” - it's going to give you the location of the last character and the first character. Now let's say I have a string that says “A, B, C, D” and I want to find out where all those commas are. I'll actually I'll leave this here I'll make a new one um we'll go a, b = string.find() and I'll make a string that says “A, B, C, D” and I want to find where all those commas are. So when I run this, actually I'm going to, alright, so. Do this, and then I'll do a comma. Cuz I just want to find the commas. And now, when I run this, it will find the first one and then it will stop. That is not going to help me if I want to find where all of them are, is it?

There is a solution. There's an optional third argument that we can add to the function. So check this out! I'm going to go off of the assumption that we're using this argument right here, and I'm going to come down here and I'm going to create a table called “Args” plural. “Args should equal just empty table constrictors for now, and then we'll do a “local i = 0” and “local lastArg = 1” and then “while true do” I'm going to take this line and put it inside of here. I'm going to change this to “x, i” and then I'm going to change this to “arg” and then I'm going to add a space here, and add another argument here, which will be “i + 1,” and it will do this math and use the mathematical combination of those. And then, let's do “if i == nil then table.insert(Args, string.sub(argument), lastArg, -1)” and then “break.” I will explain this in a minute. Let's go, “else table.insert(Args, string.sub(Argument, lastArg, x -1)” and “lastArg = i + 1.”

Okay! Now you can, in your one argument up here, use multiple arguments separated by commas, then immediately use this to separate them into a table. You'll just have to reference them as “Arg[1]” and so on. Of course I'll have to explain how string.sub works. string.sub takes a string to print from; that would be the string right here, and then the first and last character to print, so in our case string.find finds a comma, sets “x” to where the comma is and “i” to where the space is, and then finding that “i” is not equal to nil, we insert in the Args table the value starting at the lastArg and ending before the new comma. So we find; let's say our string is “A, B, C.” So we find comma and the comma is at location “x;” so we want to find everything from the lastArg to where that comma is. So the lastArg starts out at 1, and we take the characters starting at character 1 until – “x -1” is right before the comma, so from 1 to the last character before the comma, and that string gets inserted into key 1 in the Args table and then we reset lastArg to i +1 so that now when we go through this again, “i” will be the place after where that space was, so the next character after that space, and we go through it again and it does the same thing. It goes through until there are no more commas, then whenever there are no more commas we still want to save the last argument, so we do the same table insertion but make the last character -1; that refers to the last character in the string, and then we break out of this otherwise infinite loop. So whenever there are no more commas, this is going to look for this comma and it's not going to find it, so x and i are going to be equal to nil at that point. So then i is equal to nil, then table.insert, we do the same thing as before except that instead of x -1, since we already know x is nil anyway, we just do -1, because -1 is going to be the last character in the entire string, and then we break out of this otherwise infinite loop, because as you can see I did “while true do.” A while loop just checks to see if this is true. Well, if this is true then this will always be true, so the only way to get out of here is to break, and that's what we do here, because we know there's nothing left to do in here after we get to this point.


Okay, we've talked about pattern matching. I hate to go too deep into the details on Lua libraries right now, so I'm just giving you information I think is really important and I'll make more detailed videos later on the intricacies on some of these libraries. I've touched on table.insert in the past, which is a function in the table management library, and now I've talked about two big functions from the string library, but there's one more I just have to hit in this video because I use it in plugins I run every day and I find it really valuable. This actually comes from the operating system library, and it's about time. You can pull time and date info from the operating system using Lua – you just type “Print” – I'm going to print in this case; obviously, you can do anything with this, but I'm going to “Printf(os.date(‘It is now %X’))” and yeah, that's literally it. Just going to run this. It did not work because I didn't call it with the argument, which is now needed, so I'm going to go “Plugin 5 ‘1, 2, 3’” and run it and – what?Says I have a problem in string.sub. Let's go check that out. And, wait, which line did it say? It said 18… Oh, I went and typed out “argument” when it was supposed to say “arg,” guys, this is just part of the process. It happens, I don't know why. You make errors. It happens.

Okay, let's try this again. “Plugin 5 ‘1, 2, 3’” go – yay! Alright, it worked. Again, we didn't see anything coming out of that while loop because I didn't tell it to print the arguments or anything, but it worked, it ran them, and then it said “It is now 17:16:06.” That would be accurate. I have my clock doing 12 hours, but this will run in 24 hours; and if I run it again, do “Plugin 5 ‘1’” and it goes, now it's 15, or, 17:16:37. It does that. You can run it as many times as you want and that will happen.

So that is awesome! One comment right here. This message right here is coming from my MA3 API extension that I'm using, and it is basically telling me that this data type is not a string and Printf wants a string. It's clearly working just fine, but you can fix that by putting it in “tostring()” and then it will not be giving that error.

One thing to be knowing is, it works just as you would expect on OnPC, but if you are using it on a lighting console, in my experience Lua pulls whatever your console has Universal Time set to. Actually, let me just show you what I'm talking about, so in here we have date and time, and mine is pulling all of the necessary information from my operating system on my PC, but what it's basically saying is… And that's actually that's very interesting, because this is wrong, but it's pulling the correct time from my PC. This is not; it's not 18:18, it is 17:18. I'm going to change that, and again, on the PC it works and it gets the right time no matter what. If you're using a console, what ends up happening is, I have my time zone set to UTC -5. That’d be correct; I'm Central Standard Time, so that is UTC -5, and then you can change this field freely. This changes what your console is seeing it as, but when you change this it says, “okay so UTC is this +5,” and that is what your Lua script will read if you're using it on a console. It's going to take whatever this shows and change it based on your time zone so that it's showing you Universal Time. This is really frustrating, and I haven't found a way to change it. I honestly don't understand how it decides “oh, I'm going to get Universal Time;” why would it do that? That makes no sense, but that is what it does, and so what I've had to do is build a workaround that basically does this. I'm going to just type it real quickly. It's a function called “OurTime() if tonumber(UTC)” – oh, my bad, I have to put “UTC” in here. “UTC.”

If our number, UTC, “<5 then UTC = tonumber(UTC) +24” and then outside of the parentheses, “local realtime = UTC -5 return realtime” and, end the function, and then down here instead of going “os.date(‘It is now %X’)” I would actually… I would remove this; I would just do %X by itself, and I would actually, I'm going to just change this “tostring” because I want to say “OurTime()” and then it takes UTC as an argument… I'm going to change this “X” to an “H” and save that and run it and type “Plugin 5” and it said the time is 12, which, 17 - 5 is 12, so that worked as intended. Of course, in this case it's incorrect because this is a PC.

And the way I use this is I keep this function in a plugin by itself, and it just gets referenced by the other plugins that I use, just like this. I just put this in like this whenever I need to and it returns the correct time, and I don't even have to think about it. Of course, with the time change I do have to think about it whenever Daylight Savings Time starts and ends. I wish Daylight Savings Time would just go away, but actually the way I use this is, I use a variable here for the time, and I just change the variable, and, super easy.

There is a lot more you can pull using this function, rather than just the time in this format and, well, I've shown you two formats because the ‘%H’ actually just gets the number of the hour. I will link a page in the Programming in Lua book that talks about it, but you can also just Google “os.date Lua” and you'll find the information you need. It has a table that shows you a list of all the options you can use and what they'll return. Just remember that the percent character always needs to be inside of the quotes, even if you're just calling it by itself and not printing a string like what I'm doing here where I've got a “%H” in quotes.

My favorite way to use this data, and other data like day of the week or month of the year, is to do certain actions based on what time of the day and day of the week it is and change certain options even based on what month it is. It's really cool! Play around with it and tell me how you use it. I'd love to hear your stories.


One more quick thing I'm going to talk about is the MA3 API functions “Printf()” and “Echo().” Actually, while I'm at it, I'll just clarify in case you don't know, that API means “Application Programming Interface” and refers to the functions contained in an application that help make it easier to interact with the application using whatever code language you're working with.

So Printf() I've shown you before; I've been using it just now. It's great because it puts text in your System Monitor and in your Command Line Feedback, but if you ever want to print something only in the System Monitor and not in the Command Line Feedback you can use Echo(). It works the same way as Printf() other than the fact that it doesn't go to the Command Line Feedback and it prints yellow text instead of white. It just looks like this. We'll do ‘Echo(‘Hi’)” and then there are also a couple of other functions that you can use like “ErrEcho(‘Hello!’)” – that's not how you spell hello. “Hello!” with an exclamation mark! And, “ErrPrintf()” is another one. And both of these just print text just like the regular Echo() and Printf() except for the fact that they do red text.


Now a handy tip for programming is that you can put information about what the code is doing in the System Monitor, and the Command Feedback if you want, about what is going on in each part of your plugin. Personally, I don't use such info in my plugins and leave it in, but one thing I definitely do is use it for troubleshooting. If I'm writing a piece of code and it's just not working and I can't figure out why, I might for example print a variable before an if statement that depends on the variable for execution, to see what the variable is at that point, because that can be helpful in tracking down what code actually got executed and where the problem is. It has saved me so many times where I just cannot figure out what's going on, and I troubleshoot using some print commands and I'm able to figure it out, so definitely use that in that way. It's a major help.


Hey, if you're still watching this, you are champ! I'm truly on the last thing now. I just want to briefly mention the fact that MA3 software updates can totally mess up your code. Version 2.0 was definitely a big one. I had to rewrite a whole bunch of things. I don't remember having any issues with 2.1, but there was a slight change to the MessageBox function that I definitely noticed, though it didn't hurt me.

So as a general rule, if you're using plugins, read the version release notes and check all your code as soon as possible after updating. Also, use the MA3 API extension in VS Code and make sure it's on the right version of MA3.

Check this out! You can actually select the version here, and currently it has 2.1 and 2.2. I'm using 2.1, but apparently it's being updated for each version of MA3, and I'm sure it's going to help a lot with making that transition anytime you're trying to update code to a new version.


Y'all, we did it! You have completed the Lua for MA3 tutorial series, and I mean, so have I, as far as making it, which is awesome. I'm having a little bit of a hard time believing I made it here. I hope you've learned a lot; I know I definitely have. Having to research all these things and write out and explain how everything worked increased my understanding a lot. I hope you learned, I know I learned. Don't forget to reach out if you have any questions; I love problem solving and if I don't know the answer to your question, maybe someone else does, or maybe I can help you figure it out and also learn something while we're at it. If you haven't, definitely joined the Discord server, or leave a comment on the video, and stay tuned because I am not done yet! We've done Lua Basics, and you should be able to program lots of things on your own now, but I know you still have questions, and believe me, I still have answers, and I'm still learning more myself, so watch out for the next videos coming soon because we are going to be talking about making custom UI elements, and at some point I'm definitely going to make a video or several going into detail on Lua libraries like we touched on in this video, and lots more fun stuff. If you have any ideas of things you want to see videos on, comment below or post in Discord, because I'm definitely open to suggestions.

If you watched this video all the way to the end, I love you; you're my absolute favorite! Thank you so much for learning with me today. I will see you in the next video, and I hope you have an awesome week! Byeeee!


Comments

Popular posts from this blog

Intro to Lua for GrandMA3

How to Make a Reminder Plugin for GrandMA3 Using Lua