Home › Forums › General › Using Ctrlr › Global Persistent Variables?
Tagged: stateData
- This topic has 12 replies, 3 voices, and was last updated 3 years, 11 months ago by computerchemist.
-
AuthorPosts
-
May 10, 2020 at 1:09 pm #118291
Hopefully the title explains it.. I’m looking for a way to save certain global variables prior to exit and restore them again during execution.
This would use the panel functions “Called before any modulators..” for load, but I’m not sure what to use for exiting.
My bigger problem is what method I must use to store/retrieve the variables. I’d like to use the same state files that must be used when Ctrlr stores MIDI channel, etc.
I could solve this by making a standalone ini file but then I would need to store it in the profiles section which I don’t know for each operating system.
So my question is:
– How can we preserve certain variable states when the program is not being used, so they are there again at startup? Is there a special method for this?May 10, 2020 at 3:39 pm #118293You can achieve this with stateData. StateData is kept in the Ctrlr cache.
Create a method SaveStateData to store the data you want to keep. Example:
-- -- Called when data needs saving -- StateData is saved when the user press Ctrl+Alt+S (Save Ctrlr StateData in the menu) -- or when quitting Ctrlr while leaving the panel open. -- At that time all modulators indicated as Dynamic are saved but by using this method -- it is possible to also save values of variables that can then be read when loading the panel -- SaveStateData = function(--[[ ValueTree --]]stateData) -- Saving the last file loaded/saved so it can be reloaded at panel load -- This is needed to have all uiLabels restored -- If a uiLabel is set to Dynamic instead of Static then it is its value - 0 - and not its uiLabelText property that is saved -- Therefore, obliged to keep all uiLabels as Static and to reload the last file stateData:setProperty ("LastFileFullPath", sLastFileFullPath, nil) stateData:setProperty ("LastFileName", sLastFileName, nil) stateData:setProperty ("RootFolder", currentRootFolder, nil) end
Create a method to read your stateData and put the values in variables. Example:
-- -- Called when data is restored -- ReadStateData = function(--[[ ValueTree --]] stateData) -- Variables to be read sLastFileFullPath = stateData:getProperty("LastFileFullPath") sLastFileName = stateData:getProperty("LastFileName") currentRootFolder = stateData:getProperty("RootFolder") end
In panel properties, set
Called when Ctrlr state is saved: SaveStateData
Called when Ctrlr is loaded: ReadStateDateIn your PanelLoaded method, you need to check if the StateDate has been restored or not. Yep, it is sometimes failing… Example:
-- Check restored statedata and, if found, inform timer to reload file to secure all uiLabels are correctly set if sLastFileFullPath == nil or sLastFileFullPath == "" then sLastFileFullPath = "" sLastFileName = "" bLastFile = false else -- if stateData is working then sLastFileFullPath, sLastFileName should have been restored correctly -- Check if file has not been moved or been deleted f = File(sLastFileFullPath) if f:existsAsFile() then bLastFile = true else sLastFileFullPath = "" sLastFileName = "" bLastFile = false end end
Last info: please be aware that StateData is only saved when the user quit Ctrlr by keeping the panel open. Thus not when simply closing a panel. A bit tricky and for sure not perfect.
Good luck!May 10, 2020 at 5:17 pm #118299Thanks! That is exactly what I’ve been looking for (and could not find!) 🙂
May 10, 2020 at 5:52 pm #118302Glad I could help 🙂
May 10, 2020 at 6:54 pm #118304I have a follow on query that’s still within this context (sort of) and I hope you can help with too.
I have a 3 dimensional array (table?), ProgramPatch[][] where the first element is the program number, the second element is the patch number in the program. I effectively want to store this into the State Data too.
In other languages I’d serialise the array into a string, or use a JSON library to do the same. I just can’t seem to figure out the best way with LUA. What’s the best way in CTRLR of storing the array and retrieving it?May 10, 2020 at 8:17 pm #118307I have never tried but I think you can store what you want in StateData.
Just make a test 😉
I suppose this is for user patches. For factory patches, just declare your table in some TablesInit() method then call that method at panel load to initialize it.
If you load user patchs in some component, then they stay there even if you close and re-open the panel because in the cache of the panel.
Therefore, I’m not sure you really need to keep that in state data.
You need to experiment different ways and choose the one you like the best. This is Ctrlr 😉May 10, 2020 at 8:21 pm #118308Thanks for the pointers (again) 🙂 Yes, it will store the current sliders but I can only do bulk sysex dumps to this synth so I need to store all 64 patches locally, not just the displayed one. I’ve probably take the long road doing that with arrays but I’ll give it a go.
May 10, 2020 at 8:59 pm #118309That just crashes and burns trying to reference the array directly – it doesn’t crash while saving state, but it doesn’t seem to write the arrays to the state table either. So when reading back in, I’m just getting a nil array.
May 10, 2020 at 9:35 pm #118310OK. which synth is it?
You didn’t tell me if it was factory or user patches.
Now that you gave me more info about what you want to do I can redirect you a bit.
Please look at my Pro 2 panel.I would use Memoryblocks (look in Juce API doc and also in another post I answered recently.
One MB would be a bank of 64 patches. Each patch has a fixed length. Starts by F0 and finishes by F7.
When starting the panel, you load a MB of 64 patches (file on disk) and from there you can send each patch to the synth as you want.
You also treat each patch as a MB.May 10, 2020 at 9:44 pm #118311Sorry – it was for the Korg Poly-800. I think I’ve taken the wrong approach then completely – because of all the bit slicing and shifting I decode the sysex in one block (it doesn’t handle CC or individual patches) into a 64×99 array which I then map onto the controllers. A prog up/down just refreshes the controllers from the appropriate row in the array. When saving, I read the controllers in a loop and update the appropriate row in the array. The first element is the program and the second is the controller. So the complete program patch state is stored in a 3 dimensional array from when it leaves the sysex (file or dump) to when it need to go back to be sysex (file or dump).
Hold up – I think I answered my own question. Why not store the sysex dump which is a MemoryBlock prior to writing, I could treat the save state as a thrid way of storing sysex. Duh. On it now… 😀May 10, 2020 at 11:04 pm #118313Hi computerchemist,
You can store a lua table as a JSON like string:
This is in my JD-990 panel:
ccMapArray is an table of modulator names as values with CC numbers as keys. The user can build a set of pairings for controlling the panel -> synth from a keyboard.
So I have for example a modulator I named TVFCUTFR, which is the JD-990 TVF cutoff. I want to save a mapped cc number, for example 41, to that modulator on reload, along with any other pairing.
I can save this using
stateLData:addChild
instead of Goodweather’sstateData:setProperty
by creating a new ValueTree object called ccMapping. Here I use the modulator name as a hash/key, but you could just as easily store an index too, I guess.First I check that the table is not empty otherwise bad things happen.
SAVE CC MAPPING ARRAY TO stateData:child if not rawequal(next(ccMapArray), nil) then local aoc=ValueTree("ccMapping") for k,v in pairs (ccMapArray) do aoc:setProperty (tostring(v),tostring(k), nil) end stateData:addChild (aoc, -1, nil) end -- table not empty
To read that ValueTree table back into Ctrlr on reload:
mi=stateData:getChildWithName("ccMapping") for i=0,mi:getNumProperties()-1 do local k=mi:getPropertyName(i) -- k is the name of the control local v=mi:getProperty(k) -- v is the cc number ccMapArray[tonumber(v)]=k -- re-populate ccMapArray with data end
Note: In my case I am reading the value back in as the key to my table. You probably would keep it the same as how it was saved, i.e.
local k=mi:getPropertyName(k) -- k is the name of the control local v=mi:getProperty(i) -- v is the cc number ccMapArray[tonumber(k)]=v -- re-populate ccMapArray with data
I don’t seem to be checking whether
mi
exists . Maybe don’t have to? Andmi
should probably be declared local.What Goodweather suggested is stored as
<panelCustomData></panelCustomData>
and<ccmapping />
will be stored within that node as XML Attributes. (I looked this all up on XML, so I may have the terminology wrong)<panelCustomData graphlinecolour="4282400832" envbackgroundbrightness="0" <ccMapping WGBENDSW="0" WGPICHRN="10" TVFCUTFR="41"/></panelCustomData></panel>
I would leave it to you to work out how to do this with a three dimensional array or table in lua speak, but it must be doable!
- This reply was modified 3 years, 11 months ago by dnaldoog.
May 10, 2020 at 11:18 pm #118315Why not store the sysex dump which is a MemoryBlock prior to writing, I could treat the save state as a thrid way of storing sysex. Duh. On it now… ?
Or just do it that way!!!
x=function() str="F0 43 12 03 00 00 57 f7" m=MemoryBlock() m:loadFromHexString(str) end
save = function(--[[ ValueTree --]]stateData) stateData:setProperty ("mylongsysexdump", m:toHexString(1), nil) end
P.S. unless you need those hash key or value names recorded …
- This reply was modified 3 years, 11 months ago by dnaldoog.
May 11, 2020 at 7:02 pm #118325Big shout out to both of you super helpful guys 🙂
I got it all working in the end with a mixture of both approaches. -
AuthorPosts
- The forum ‘Using Ctrlr’ is closed to new topics and replies.