TIL: Nested functions don’t have to be local and can be seen outside of parent functions scope!
I have been looking into privacy for functions and variables for libraries for an OOP approach to use from now on instead of the Renoise provided “Class” object. In Lua everything is public and in those classes i started to name internal states or functions prepended with underscores, like __some_var, class:__some_func(), and it get’s really ugly when your class uses helper functions defined in the class, lot’s of use of self and such. Then i found this approach here using closures…
https://www.lua.org/pil/16.4.html
At the top you basically have a table self, where you add all your private variables in, those can’t be accessed from outside, same with all the functions, unless you declare an interface at the end in the return part, again, as a table. This whole thing is really just a table and in LUA this is one of the few data types passed around by reference.
You are able to nest this approach as well and can simulate something like inheritance inside there by passing a parent state down to a sub table / object, so overall it seems pretty neat.
This whole chapter here was a pretty good read on different methods on how to achieve something like OOP in LUA.
Funnily enough was having a chat w copilot about closures recently and was wondering what the end results might be like with scaling up from the toy examples it was giving. Will have to have take a look.
There is for sure some overhead by LUA having to do some more table traversal, the “scaling up question” is something to be seen though. Since Renoise tools are pretty small and now we got Luajit even, i just hope it does not matter too much performance wise in most tasks. There is also the variant of using metables, but it has no privacy and i suspect the Renoise “Class” data type is just some “syntaktik” (ha) sugar variant of it. For more time critical tasks like processing midi messages we can often pass callbacks to Renoise, where internally it’s very likely handled as a direct pointer to a function and shouldn’t matter, assumed of course. Need some more testing here as well.
I see what you did there… I’m sure there’s a drinking game around taktik puns waiting to be invented! You certainly deserve a taktiquila for that one!
I’ve been trying this design earlier but it’s less readable than normal classes imo, so long term it doesn’t work for me. I think there is also only a small potential benefit performance wise - not necessarily having to create the same metatable/methods that classes do. Yeah, the class “keyword” is just a “simple” LUA function afaik.
Just wanted to give my opinion: classes provide nice syntax ![]()
Consider this function here:
function cLaunchpad:open(midi_input, midi_output)
self.__midi_input = rm.create_input_device(midi_input,
{self, self.__lp_midi_handler}, {self, self.__lp_sysex_handler}
)
if type(self.__midi_input) == "MidiInputDevice" then
self.__midi_output = rm.create_output_device(midi_output)
if type(self.__midi_output) == "MidiOutputDevice" then
self.__lp_debug_print(self, "Using MIDI input", self.__midi_input.name)
self.__lp_debug_print(self, "Using MIDI output", self.__midi_output.name)
self.__lp_debug_print(self, "Identifying device...")
self.__lp_send(self, {SYSEX_ID_REQUEST_MESSAGE})
local time = os.clock()
while not self.__identified do
if os.clock() - time > 5.0 then break end
end
if self.__identified then
self.__lp_debug_print(self,
"... found " .. self.__DEVICE.NAME ..
" with Application verion " .. self.application_version
)
end
else
self.__midi_input:close()
end
end
end
Issues i had was visibility of own class methods __lp_debug_print, __lp_send for example, as they were not defined yet, SYSEX_ID_REQUEST_MESSAGE is already a table but i still had to wrap it in curly braces to pass onto the send function or it would not receive it properly, in trying to findout why it’s not passed properly i went full berserk and used x.blah(self, var) instead of x:blah(var) everywhere, so the issue of having a lot of self references in there is a bit exaggerated here, but overall i find this sort of ugly to read.
Now say i want to write something using this class and autocomplete will give you then a whole bunch of internal state variables and functions starting with __ plus the normal named ones supposed to be accessible, the interface. Using the closures method the interface is really nice and tidy, i can be sure the external usage is not messing with my internal state.
Maybe i’m doing this OOP thing wrong, possible, i come mainly from a C background, but willing to learn. ![]()
Just to make sure… are you using native lua classes (table with metatable) or the class ‘keyword’ provided natively in Renoise?
I seldom explicitly forward declare anything (other than for verbosity)… and if I find I have to, I tend to see it as a sign of having to refactor something. But yeah… something like class properties are ‘forward declarations’, which is their point I guess.
That example is using the Renoise native class keyword, used that in the past as well. At first i did start as a normal tool to get going and figure out my pretty big data structure, basically just a table with named constants, later on felt it made sense to go OOP here, have a class Launchpad which covers general things, then subclasses for the models like X. So i went to rewrite everything but kept running into issues. Self references for properties are fine for me, but own functions / methods not being found and passing on parameter issues which worked fine before made me rethink and read up a lot before the project grows. Had a pause also from Renoise coding, so just getting back in now.
TIL:
Renoise’s provided/official tool prefs can be manually saved early (or loaded if needed). Useful for debugging as you go or defending against renoise crashes.
-- Renoise's provided/official tool prefs can be manually saved early (or loaded if needed)
--Useful for development testing, place options:save_as(FILE_PATH) after update you want to test
--then check preferences.xml in the bundle manually that the changes were saved. This is the same method
--for saving custom pref files, you can just have access to renoise.tool().preferences this way to.
----------------------------------
--create xml
local options = renoise.Document.create {
value_a = false,
value_b = 10,
}
--assign options-object to .preferences so renoise knows to load and update it with the tool
renoise.tool().preferences = options
local XML_NAME = "preferences.xml"
local TOOL_PATH = renoise.tool().bundle_path
local FILE_PATH = TOOL_PATH..XML_NAME
options:save_as(FILE_PATH)
options:load_from(FILE_PATH) --add relevant checks
print(options:to_string())
Isn’t this blocking the whole DAW for 5 secs in worst case? It’s not running in a separate process, but instead in the GUI process.
Not learned today, but I believe that XML is not a practical data structure… Too clunky, too slow to parse and manage… Much more easy is JSON!!! Go JSON now!!! No, for real, do your self a favour and use JSON. It’s the today’s standard in most configs, too, this for a reason.
I built a little JSON configuration manager (kind of ugly code), but it does the job more or less. You simply setup all possible config values a big, easily managable table at beginning of the source or so. Then you instanciate a config object. There is a listener to react on config changes. It will autobuild the config menu. I’ve tried to make it a most simple usage pattern. Surely it can be improved.
A Config looks similar like this:
--------------------------------------------------------------------------------
-- Default Config
--------------------------------------------------------------------------------
DawProject.defaultConfig = {
convertToRedux = true,
exportVST2 = false,
exportVST3 = true,
addTrackDelayToClips = true,
devMode = true
}
DawProject.configDescription = {
convertToRedux = { type = "boolean", txt = "Convert sample instruments to Redux" },
exportVST2 = { type = "boolean", txt = "Export VST2 plugins (not possible due to api limitations)" },
exportVST3 = { type = "boolean", txt = "Export VST3 plugins" },
addTrackDelayToClips = { type = "boolean", txt = "Add the track delay ms to the clip position" },
devMode = { type = "boolean", txt = "Adds some debugging menu entries / functionality" },
}
Initalization like:
toolIdle = function()
self.configurator = LibConfigurator(LibConfigurator.SAVE_MODE.FILE, self.defaultConfig, "config.json")
config = self.configurator:getConfig()
self.configurator:addMenu("ffx.tools.DawProject", self.configDescription,
function(newConfig)
config = newConfig
refreshMenus()
end
)
You also could instanciate multiple configurators, too. It could be much improved or refactored, but I hope that you get the idea.
Configurator class: DawProject-Tool/ffx.tools.DawProject.xrnx/lib/lib-configurator.lua at main · Jurek-Raben/DawProject-Tool · GitHub
Well, yes and no. As is, the code should not be run in a normal tool context. This is part of a midi controller object which needs this midi message to successfully return itself as the correct model to use so it has the right data structures. Creation of that object then should be in an extra process of course. i could create a timer and check an answer periodically, but my little timer object is not written yet. ![]()
Ah yes, in a perfect world, the whole lua API would run asynchronously against either the audio engine, or some kind of song data provider. So then it couldn’t block the GUI at all, or doing a while(true) {} loop, no problem. Or there should be an lua interrupt? Maybe such a DAW structure would be in a perfect world, these processes running in parallel:
Then all packed into an event system… I think Bitwig is similar? Maybe it’s not possible to deal with asynchonous stuff in lua while accessing the api, simply.
If you imagine a big data structure like a Renoise song and you have a whole bunch of threads running which can write to it, think of multiple tools even, you can run into some funny race conditions when threads try to write to the same data at the same time, maybe even corrupting it. There is a way to simulate mutexes in LUA, basically locking a variables access till a write is completed and then hand on work to the next thread, but out of the box Lua does not seem to support it, so either the host has some kind of protection built in or the scripting should provide some functions to deal with it. Using observables you kind of have an event system in Renoise.
Yep ur allowed NLT’s as long as the useful nuggets keep appearing as they have been!
TIL:
Don’t:
Attempt to change your preferences structure/ default values, thinking deleting preferences.xml and ‘reload all tools’ will do the trick and you’ll have a fresh .xml instance after.
Do:
Close renoise first and delete preferences.xml manually in the tools folder via OS, then reboot renoise. It’s the only way to bust the ghost of the object in memory that will do a sneaky rebuild and re-write of the preferences.xml
Much pain can be avoided this way!
--related
renoise.tool().preferences = options
Welcomed with open arms!

related: preferences.xml will not appear on first re-opening renoise after doing this. It will appear if renoise is closed again or if you reload (all) tool/s
hi @Ledger - have you figured out a way to show scripted tool menu entries with shortcuts?? is that what you’re showing?
