Quantcast
Channel: Rainmeter Forums
Viewing all articles
Browse latest Browse all 346

Community Tips & Tricks • Generating meters and measures (using LUA)

$
0
0
Can you generate meters and measures on demand in a Rainmeter skins internal object list under the hood? Tehnically yes, practically no.

We can make our way around it though, to know how, we first need to just think about how a skin works.

When you load a skin, Rainmeter first loads and reads the .ini file itself, then it reads the .ini/.inc files from the @Include options, places them in the according places, and then it can start intializing meters/measures up to bottom.

Everything that the .ini had to send to Rainmeter is loaded in memory, the files have been accessed, read and used, and now just sit on your system, if you have edited your skins, you would have obviously noticed that there's no live-change like behaivour, when you make a change to the file to edit or add new stuff, you have to save the file and then refresh the skin.

Wait a minute... the skin is loaded... the files are done... any change to the file is not registered until we refresh the skin... hmmmmmmm... WAIT, can't we just change the file using any program, @Include that file and refresh the skin?

You can, that's the magic, you can just write anything to an @Included file and when you refresh the skin, the stuff you wrote to that file will be visible.

The following part can be done using any software you might like to read/write files, I have chosen Lua however because it comes with Rainmeter

Let's use something Rainmeter comes with, let's use the Script Measure's Lua funciton. For this example, I have decided to go simple with something people might want to make, a Visualizer (don't worry it's not that hard).

What we will need is just to know what will be generated, a Visualizer needs it's parent AudioLevel measure, the children AudioLevel measures and the meters that listen to the children, let's write them down in .ini form first, with the places that need to count up replaced by @Include.

Code:

[Variables]; 0 indexed to help the child measures, so this is 10 meters NumberOfBars=9; ==== measures[ParentAudioLevel]Measure=PluginPlugin=AudioLevelFFTSize=1024; we are adding a +1 because the `Bands` option is 1-indexed, but `BandIdx` is 0-index, fun isn't it :)Bands=(#NumberOfBars# + 1)[ChildAudio%d]Measure=PluginPlugin=AudioLevelParent=ParentAudioLevelType=BandBandIdx=%d; ==== meters[MeterBandStyle]X=5RY=RW=10H=100SolidColor=100,100,100,255BarOrientation=Vertical[MeterBand%d]Meter=BarMeterStyle=MeterBandStyle; the %d used on MeasureName has to be the same %d in the section's nameMeasureName=ChildAudio%d
Alright so we've got the building blocks, from what we can (obviously) see, what would need to be duplicated will be just the [ChildAudio%d] and [MeterBand%d] sections.

Let's start with that first, I have decided that to generate the sections, I will want to use a table, since the table library offers us the ability to table.concat(), whose result is the string that's gonna be written to the file we @Include.

Code:

local NumOfBars = 10local TableWithSections = {}for i=0, NumOfBars do-- we will basically use multi-line comments for the sections-- and where we have said we would replace %d, we just concat-- it with the iterator, this could have been done with string.gsub tootable.insert(TableWithSections,[[[ChildAudio]]..i..[[]Measure=PluginPlugin=AudioLevelParent=ParentAudioLevelType=BandBandIdx=]]..i..[[]])table.insert(TableWithSections,[[[MeterBand]]..i..[[]Meter=BarMeterStyle=MeterBandStyleMeasureName=ChildAudio]]..i..[[]])endprint(table.concat(TableWithSections, "\n"))
With the code above, if we are to run it in a Lua sandbox online, it will print exactly what we want [ChildAudio0], [ChildAudio1], [ChildAudio2] etc. and [MeterBand0], [MeterBand1], [MeterBand2] etc.

That's perfect, let's transform this code to Rainmeter. Let's quickly think about how we can do it. The Script Measure's Lua has the Initialize() function, which is the very first thing that happens, we should put our code there. Don't forget, we will need a file to write to, since that file is what we @Include, so let's create one before we put our code in the function, for this example, I just named the file FilesCreated.inc.

Code:

function Initialize()local FILE = io.open("FilesCreated.inc", "w")local NumOfbars = 10local TableWithSections = {}for i=0, NumOfBars do-- ... code from aboveendFILE:write(table.concat(TableWithSections, "\n"))FILE:close()end
That's the script, and let's also make the skin.

Code:

[Rainmeter]Update=16; in case our file ever fails, we made this meter just so the skin loads, even if invisible[Dummy]Meter=String[ParentAudioLevel]Measure=PluginPlugin=AudioLevelFFTSize=1024Bands=11; advantage of using MeterStyles is just that you need to change this and not the meters[MeterBandStyle]X=5RY=rW=10H=100SolidColor=51,200,224BarOrientation=Vertical[ScriptMeasureToGenerateFiles]Measure=ScriptScriptFile=Generator.lua@IncludeGenerated=FilesCreated.inc
If we are to load this skin first time, nothing will happen, we have run the Lua script, it did its stuff in Initialize(), it saved the sections to file, but because @Include was read into memory before the Script measure, we can't see the changes, let's refresh the skin aaaaaand... WOAH IT'S THERE!!!!! We have succesfully generated sections using Lua. At the moment though, as you might have noticed, it's very limited, we are basically offering no one beaside the skin author a way to change how much stuff gets generated.

Let's change that, let's do a bit more work on the Script measure in order to standardize some stuff, let's create a [Variable], which we will use in the Script measure and the Parent AudioLevel measure in order to tell the measures how many sections are necesary. Let's also standardize the file to which it generates correctly too, after the changes, our Rainmeter skin .ini file and .lua file become

Code:

[Rainmeter]Update=16; in case our file ever fails, we made this meter just so the skin loads, even if invisible[Dummy]Meter=String[Variables]; 0 indexed to help the child measures, so this is the number below plus 1 NumberOfBars=9[ParentAudioLevel]Measure=PluginPlugin=AudioLevelFFTSize=1024; we are adding a +1 because the `Bands` option is 1-indexed, but `BandIdx` is 0-index, fun isn't it :)Bands=(#NumberOfBars# + 1); advantage of using MeterStyles is just that you need to change this and not the meters[MeterBandStyle]X=5RY=rW=20H=200SolidColor=51,200,224BarOrientation=Vertical[ScriptMeasureToGenerateFiles]Measure=ScriptScriptFile=Generator.luaBarsToGenerate=#NumberOfBars#FileToWriteTo=FilesCreated.inc@IncludeGenerated=FilesCreated.inc

Code:

function Initialize()local FILE = io.open(SKIN:MakePathAbsolute(SELF:GetOption("FileToWriteTo")), "w")local NumOfBars = SELF:GetNumberOption("BarsToGenerate")local TableWithSections = {}for i=0, NumOfBars dotable.insert(TableWithSections,[[[ChildAudio]]..i..[[]Measure=PluginPlugin=AudioLevelParent=ParentAudioLevelType=BandBandIdx=]]..i..[[]])table.insert(TableWithSections,[[[MeterBand]]..i..[[]Meter=BarMeterStyle=MeterBandStyleMeasureName=ChildAudio]]..i..[[]])endFILE:write(table.concat(TableWithSections, "\n"))FILE:close()end
Basically we just use SKIN:MakePathAbsolute to get the correct/absolute filename, just to be very much sure it's that file. Now if we want to change the number of bars, what we need to do is just change the variable in [Variables] section, save the file and refresh the skin.

Why aren't we just adding SKIN:Bang("!Refresh") at the end of the Initialize() function to overcome this problem though? Well, if you try right now, you will notice Rainmeter freezes, because we have basically created a loop, Rainmeter skin loads, Lua file loads, Refreshes skin, Rainmeter skin loads, Lua file loads, Refreshes skin etc.

What we need to do is just create a variable that tells the script if you have generated once, don't generate again, thankfully it's just a simple if then end function. If the variable is 1, then generate, if not, then don't do anything, I have called this variable ToGenerate. With that our Script measure looks like

Code:

[ScriptMeasureToGenerateFiles]Measure=ScriptScriptFile=Generator.luaToGenerate=0BarsToGenerate=#NumberOfBars#FileToWriteTo=FilesCreated.inc
and our Lua file will get have

Code:

function Initialize()-- the operation is controlled by the Script measure's "ToGenerate"-- option, if it's set to "1", it regenerates the sections-- you can always call the script to regenerate the file by just-- doing `!WriteKeyValue` it to 1 with a bang, and then refreshingif SELF:GetOption("ToGenerate", "1") == "1" then-- ... you know the drill-- we set the option on the script measure to 0 in order to not-- not run this code again, if we need to regenerate the file, we just-- !WriteKeyValue to 1 in rainmeter and refresh the skinSKIN:Bang("!WriteKeyValue", SELF:GetName(), "ToGenerate", "0")SKIN:Bang("!Refresh")else-- here is the code that gets executed if "ToGenerate" is 0endend
And that's basically it, when we load a skin, if ToGenerate is 1, Rainmeter will generate the sections, after we write the stuff to the file, we change it to 0, so the script dosen't generate a thing everytime, and with this we can refresh the skin after we do the changes, without a loop.

With that you're done, you know everything you need to know to generate sections for a skin automatically.

Below is the entire final skin, I also added an InputText measure so you can type a number and it generates how many bars you want

Code:

[Rainmeter]Update=16; in case our file ever fails, we made this meter just so the skin loads, even if invisible[Dummy]Meter=String[Variables]; 0 indexed to help the child measures, so this is the number below plus 1 NumberOfBars=4[InputTextMeasure]Measure=PluginPlugin=InputTextCommand1=[!WriteKeyValue "Variables" "NumberOfBars" "($UserInput$ - 1)"][!WriteKeyValue "ScriptMeasureToGenerateFiles" "ToGenerate" "1"][!Refresh][CalcMeasureForTheText]Measure=CalcFormula=#NumberOfBars# + 1[MeterWithNumOfBars]Meter=StringMeasureName=CalcMeasureForTheTextFontSize=30FontColor=FFFFFFSolidColor=00000001LeftMouseUpAction=!CommandMeasure InputTextMeasure "ExecuteBatch 1"[ParentAudioLevel]Measure=PluginPlugin=AudioLevelFFTSize=1024; we are adding a +1 because the `Bands` option is 1-indexed, but `BandIdx` is 0-index, fun isn't it :)Bands=(#NumberOfBars# + 1); advantage of using MeterStyles is just that you need to change this and not the meters[MeterBandStyle]X=5RY=rW=20H=200SolidColor=51,200,224BarOrientation=Vertical[ScriptMeasureToGenerateFiles]Measure=ScriptScriptFile=Generator.lua; `ToGenerate` is `1` so it generates first time you load the skinToGenerate=1BarsToGenerate=#NumberOfBars#FileToWriteTo=FilesCreated.inc@IncludeGenerated=FilesCreated.inc

Code:

function Initialize()-- the operation is controlled by the Script measure's "ToGenerate"-- option, if it's set to "1", it regenerates the sections-- you can always call the script to regenerate the file by just-- doing `!WriteKeyValue` it to 1 with a bang, and then refreshingif SELF:GetOption("ToGenerate", "1") == "1" then-- here it branches off doing instructions that create the sections-- necesary for a visualizer-- we get the file onto which we will write the result-- io.open is in "write" mode and not "append" in order to-- whatever text might have been in the filelocal FILE = io.open(SKIN:MakePathAbsolute(SELF:GetOption("FileToWriteTo")), "w")-- because we're making a visualizer, we get the number of bars to generate for our measures and meterslocal NumOfBars = SELF:GetNumberOption("BarsToGenerate")local TableWithSections = {}for i=0, NumOfBars do-- there are multiple ways to do this next part, we could write to-- the file each line individually, or we can write everything all-- at once at the end, i have went with the 2nd method-- we will basically use multi-line comments for the sections-- and where we have said we would replace %d, we just concat-- it with the iterator, this could have been done with string.gsub tootable.insert(TableWithSections,[[[ChildAudio]]..i..[[]Measure=PluginPlugin=AudioLevelParent=ParentAudioLevelType=BandBandIdx=]]..i..[[]])table.insert(TableWithSections,[[[MeterBand]]..i..[[]Meter=BarMeterStyle=MeterBandStyleMeasureName=ChildAudio]]..i..[[]])-- there are also different ways to create the following system-- i have decided the easiest way is to just concatenate with a-- newline character at the endend-- `TableWithSections` now has all the sections we made, easy and simple-- we can just use `table.concat(TableWithSections, "\n")` to basically-- transform it into a string, and write that string to the fileFILE:write(table.concat(TableWithSections, "\n"))-- don't forget to close the fileFILE:close()-- we set the option on the script measure to 0 in order to not-- not create a loop, if we need to regenerate the file, we just-- !WriteKeyValue to 1 in rainmeter and refresh the skinSKIN:Bang("!WriteKeyValue", SELF:GetName(), "ToGenerate", "0")SKIN:Bang("!Refresh")else-- here is the code that gets executed if "ToGenerate" is 0endend

That's the end of this guide, as a thing to give kudos or credits or whatev, here's some people employeeing similar concepts in the past (with better)

Statistics: Posted by Jeff — Yesterday, 10:08 pm — Replies 0 — Views 26



Viewing all articles
Browse latest Browse all 346

Trending Articles