Lua for GrandMA3 Episode 10 - Miscellaneous Important Info

Hey lighting folks! The following is the transcript from my YouTube video, Lua for MA3 Ep 10 - Miscellaneous Important Info

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, 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

Lua for GrandMA3 YouTube Crash Course

Intro to Lua for GrandMA3

How to Make a Reminder Plugin for GrandMA3 Using Lua