this repository contains utilities helpful for modularising and importing code in supercollider projects, and for managing / saving projects using those code modules
a module is at its simplest a .scd
file, associated with an Environment stored in memory and indexed by its path.
A module's path uniquely identifies it and as such only one instance of a module is loaded at a time.
resources and values can be declared and instantiated and values may be 'exported' or exposed to consumers of the module using the
~
environment variable syntax shortcut
typically a module is imported by using some variation of Import's constructor or an extension added to Symbol:
var module = Import(
path: "/path/to/module",
// path to the module.scd file - the extension is unnecessary
expose: false,
// whether to expose the returned module in the currentEnvironment
// as ~module (the module's filename)
);
'./relativeModule'.import; // creates the environment variable ~relativeModule
~renamedModule = './otherRelativeModule'.asModule; // doesn't create an extra variable, just returns the module
Import will then try to resolve the supplied path either by looking up an absolute path, resolving it relative to the current file
or searching for it in a configurable list of default module directories
once a module's path has been found an already loaded instance of it is returned, or it must be freshly loaded. when initially loaded, a Module creates an environment, and its supercollider script file is loaded within it.
custom loaders can be specified at import time:
Import("Clap.wav", loader: "soundfile")
will search for a soundfile at the path "Clap.wav", and create an environment bearing that path.
Then a script called "soundfile.scd" is found and its code is executed inside the "Clap.wav" module - see here and here for an example
functions behave in a specific way when saved in an Environment:
var a = (
a: 1,
func: { arg env, extra;
[env.a, extra].postln;
}
);
a.func(2); // -> [1, 2];
prints two values (rather than [2, nil]
) because when called in this way,
the parent environment is passed in as an implicit first parameter, similar to how self
is by convention the first parameter of a python class instance method and refers to the instance itself.
In this way, functions declared and exposed from a module may refer to values within the module by using the first parameter.
because it might be tedious to specify this first parameter all the time, the helper class ModFunc
or M
for short is provided to allow a function to execute within the context of its own module:
// someModule.scd
~moduleVariable = 1;
~exposedFunction = M { arg extra;
[~moduleVariable, extra].postln;
};
// someConsummer.scd
'someModule'.import;
~someModule.exposedFunction(2); // -> [1, 2]
Import stores a list of directories to search for a specified module's path. The first match in the list is used and the rest are ignored. to configure this place the following in your startup.scd file:
Import.defaultModulePath = "/path/to/your/core-modules"; // useful modules you'd like to be accessible from anywhere
Import.defaultSoundfileLibPath = "/path/to/your/soundfile/lib";
Import.search = [
[{ true /* condition for searching this directory*/ }, { "/another/project-specific/directory" }],
] ++ Import.search;