Recent articles showed how to use to Renoise Lua scripting to alter a track’s send device. One was driven by OSC, the other by MIDI.
In both of them the code that did the actual send device manipulation was the same. But since these were two different scripts the same code appeared in both places.
Sharing code using copy-and-paste is not the world’s worst programming sin, however much it might make some developers cringe. If you are throwing together a one-off script for a short-lived need, go for it.
In many cases, though, having the same code in multiple places is a poor idea because it breaks one of the golden rules of software development: Be lazy. (Read up on the three great virtues of a programmer.)
Copy-and-paste sure seems like the laziest thing to do at first, but if the code has any amount of complexity or importance there’s a really good chance that sooner or later you will want to change it. Maybe to fix a bug, maybe to make it faster, maybe to add a feature.
With the same code in multiple places you need to keep track of all those places and edit the code in all those places. Oh, bother.
Can Renoise Lua code be shared? Can you create a file that can be loaded and used by multiple scripts?
Indeed you can.
The preferred way to load files in Lua programs is the require
function.
require
looks at a special path that defines where files may be found. This path is actually many paths, and it is not just a set of folder paths but paths that contain some pattern-matching variables. (See that previous link for details.)
This path-of-paths can be altered. The usual warning about messing with program defaults applies here; if you screw up the require
path your program may not run as expected (or at all) and you will be sad.
The path is a string, so here’s an example of changing it so that a tool file can load from a parallel directory:
package.path = "../ng-shared/?.lua;" .. package.path
For example, suppose you have this folder and file layout:
Scripts/Tools/ng-shared/Utils.lua Scripts/Tools/com.neurogami.MidiMapper.xrns/main.lua
If the file main.lua
in com.neurogami.MidiMapper.xrns
has that path alteration then when it does this:
require "Utils"
the program will look in the ng-shared/
folder since it has been added to the path. Since that new path is at the front, the program will look there first whenever require
is called.
Alternatively, if you want your special file locations added at the end, you can do this:
package.path = package.path .. ";../ng-shared/?.lua"
Note the placement of the semicolon in each case.
Does this actually work? Yes. com.neurogami.MididMapper.xrns
has a file called Utils.lua
containing some utility functions (such as one to print out a table).
After creating the new directory and moving that file, the code works fine and can still call those utility when the path is updated.
Great! Except not so great.
In real life when you distribute a tool as an xrnx zip file the installation process will not be able to create that shared code folder. You can’t pull files from a directory outside your a tool unless the directory already exists. If your tool needs prexisting files asking a user to install them by hand is clunky and error-prone.
Suppose, though, one tool wanted to reach into the folders of another tool. And suppose that other tool existed solely to host shared code? Would that work?
Yes it would.
A Renoise tool doesn’t have to do anything. In order to be installed a tool package needs a manifest.lua
file describing the tool, and main.lua
file, but the latter can be empty. Any additional files are just tool files, and these can hold the intended shared code.
If you know the name of the shared code tool (e.g. “com.neurogami.SharedCode.xrnx”) then your other tools will know the path needed to load its files. (It also means that if you know the path to any other tool folder your code can load files from there as well.)
package.path = "../com.neurogami.SharedCode.xrnx/?.lua;" .. package.path
require 'Utils'
Writing this up and looking over the code (so far) a few things come to mind. Sticking a custom location at the front of require.path
puts you at risk of inadvertently having a custom file loaded in place of a standard Lua or Renoise file. Can this be mitigated using crafty file names? “Utils.lua” might just be a bit generic. Maybe “UtilsNG.lua.” Or “NeurogamiUtils.lua.”
If there’s a risk of a name collision then distinct naming should take care of that.
Here’s a worthwhile question: does this solve a real problem? No one seems to be doing this, so there’s no empirical use-case to look at. What plausible problems does it solve? Well, it means all (or a lot of) common code lives in one place. But this can be solved in other ways. For example, during development you can keep common code in unique files in a known location; use symlinks to have those files appear local to tools while writing code; use a build script to copy all required files to the appropriate tool folders for packaging.
One possible value in using the shared-code tool approach is if you’ve released multiple tools that use this common code and later discover a bug in that common code. Users can then update just the shared-code tool and all dependant tools will then use the new improved code. The alternative would be to release new versions of all the tools and have each one reinstalled.
It’s very “what if?” If you have two tools that need updated code, updating one instead of two is a negligible gain.
What’s maybe more useful is the idea of there being a large amount of shared code available to all tool developers. The analogy here is something like a programming language’s standard library. For example with Ruby there is the core language, providing basic data types such as String
and Array
, plus there’s the standard library to give you extra stuff, like Matrix
and Net/HTTP
.
There could be something like a Renoise “standard library” that adds useful code to augment the core API provided by default.
Or maybe this sort of shared code is more like Ruby gems; install shared-code tools for different tasks, and then reference those tools as needed. If this were the case then it would be nice if installing tool automagically installed any referenced tools.
Or maybe not. It all depends on actual usage and empirically-determined needs.
In the meantime it’s an interesting experiment.