Dicts/Events/Arrays Or Other Code In External Files

I’m working on a little project that already generated quite a lot of code (particularly as I like to use lots of comments, and split long lines for readability).

I’d like to split some of the setup code out to separate files to reduce clutter in the main script.

This doesn’t seem to be something that’s often done in Supercollider, which surprises me.

I’m aware it’s possible to save any object to a file, and load it back in again.
The downside of this approach seems to be that the saved object isn’t especially readable, or easily editable by hand (though it is just a raw text file).

I have read it’s also possible to read in a text file and execute any SCLang code it contains.

Is this a better approach, or does it create issues with the scope of variables etc. created by the script in the external document?

What I’m really looking for, I think is an equivalent to “include” or “require”, but I understand Supercollider lacks these.

What are other people doing to modularise their code?

Actually it doesn’t. See String.load.

"foo.scd".load;

is a shortcut (actually implemented exactly as)

thisProcess.interpreter.executeFile("foo.scd");

The latter class (i.e. Interpreter) has a bunch of more refined methods.

N.B., you might sometimes want

"foo.scd".loadRelative;

if you’re loading from a directory not added to your sclang_conf.yaml and don’t want to use absolute paths. But loadRelative only works from saved documents (otherwise there’s no notion of current path for the document), which is why I didn’t mention this first…

does it create issues with the scope of variables etc. created by the script in the external document?

I’m not exactly sure what you mean by this, but from the above it should be clear that whatever variables the “external file” sets are visible in the current interpreter after executeFile returns.

As long as code in the “external file” doesn’t actually play sounds or pop guis etc., i.e. if it you just use it to define functions (and of course assign them to variables) and likewise for data (I often use such files to save parameter dicts) that you use in your “main” file, that’s the exactly the same functionality as “#include” in other languages.

For example, if you create

// extfile.scd contains:

~somefun = { "Haha".postln };
~xx = 22;

And from another (saved) file in the same directory you do:

"extfile.scd".loadRelative;
~xx; // -> 22 
~somefun; // -> a Function (unevaluated)
~somefun.() // posts "Haha"

If you want to isolate the environment variables in the external file to what is called a namespace in other languages, you just use (switch to) a different environment before loading the file e.g.

~extnamespace = ().make { "extfile.scd".loadRelative; }

Now you have captured the extfile.scd’s changes into another environment, accesseed e.g. as

~extnamespace[\xx] // -> 22 or equivalently
~extnamespace.use { ~xx }

That’s great, thank you!

Also, check out StoredList from the Jitlib extension quark. It has built-in write support, which works decently with functions too, as it turns out. Before I discovered that I had some “manual” equivalent like

~asInstList = { |env| env keysValuesDo: {|k,v| "~% = %;\n".postf(k, v.cs)} }
~asInstList.(~extnamespace) // looks like a progam now

and writing that to a file. This obviously only works well for simple stuff like settings, but not with intricate dependencies because dicts have no order.

Take a look at the Require quark - Quarks.install("https://github.com/scztt/Require.quark").
It’s javascript-ish require functionality for scd files, with some nice path resolution and error handling stuff.

Require("synths");   // loads ./synths.scd
Require("patterns"); // loads ./patterns.scd
~presets = Require("~/Desktop/presets/*.scd") // load all matching paths, and store result in an envir variable
2 Likes

Thanks very much for all the tips, guys. I will try the various methods suggested.

I’m not 100% sure I’ll be able to use 3rd-party Quarks in the environment I’m developing for (Monome Norns running on RPi).

Will find out, anyway.

Thanks again!

"foo.scd".loadRelative;

Seems to work well for my current use-case (loading dicts containing settings), so I will keep the other methods mentioned in reserve, should I need them.