Lua for GrandMA3 Episode 7 - Numeric For Loops
Hey lighting folks! The following is the transcript from my YouTube video, Lua for MA3 Ep 7 - Numeric For Loops
This video is part 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 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.
Okay, that was a lot of ground to cover! At this point we've been over both generic and numeric for loops, which both work through iterations. There are however two other types of loops we've yet to discuss, and they will be the topic of the next video. After that you'll know so much about Lua that we'll be able to really get into how to best integrate it with MA3. Of course you got a taste of the possibilities with this video, but believe me there is a lot more to come! I will see you in the next video, but until then, happy lighting!
Comments
Post a Comment