Lua for GrandMA3 Episode 9 - Handle-ing GrandMA3 Objects

 Hey lighting folks! The following is the transcript from my YouTube video, Lua for MA3 Ep 9 - Handle-ing GrandMA3 Objects

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


Okay, friends, that's all I have to say about object handling today. That was a really fun video! I truly feel like more than anything else I've done in this series, this video is going to help you move forward with Lua and start doing big things in GrandMA3. I would love to see what you're creating if you're willing to share what you're working on in the Discord server or in this video's comments. And speaking of which, if you're not in the Discord server make sure to find the link in the video description and come join us over there. We have a great community and it's a wonderful place to get help if you're struggling with anything or just want to chat.

Now the next video is actually going to be wrapping up the main part of this series with just a few miscellaneous final tips and tricks to help you on your way, but don't worry, I'm not expecting you to know everything yet, and I plan to keep making videos, just not exactly include them as part of the basics, you know?

The first thing probably that I'll do after ending this series is start showing you how to make custom UI elements. I know that is a big topic and there's been people asking about it, and they are complex, but it is so much fun. I really enjoy making my own custom UI elements, and I can promise you your life will never be the same again once you learn that, so stay tuned for that, and I will catch you next week, but until then, happy programming!


Comments

Popular posts from this blog

Lua for GrandMA3 YouTube Crash Course

Intro to Lua for GrandMA3

How to Make a Reminder Plugin for GrandMA3 Using Lua