How to make a custom function permanently available

Hi everyone,

I’m new to SuperCollider, but I have extensive experience with other sound programming environments (such as Csound and Pure Data).

I’ve created a custom function that I’d like to make permanently available across sessions — without manually redefining it every time. I understand that I can use startup.scd for this purpose.

However, I’m a bit concerned that startup.scd might become messy or hard to manage over time if I keep adding more and more definitions.

What’s the typical or recommended workflow for managing custom functions or code that should persist across sessions? Is there a way to organize or modularize this beyond putting everything directly into startup.scd?

Also, I have a similar question about SynthDefs: if I have a few SynthDefs I use frequently, is it common practice to define them in startup.scd, or is there a more idiomatic way to manage them across sessions?

One more question: when defining reusable functions for personal use, is it common practice to assign them to a global variable with a personal prefix (like ~pn_myFunc) or a short name (like ~sine)? Or are there other best practices for naming and organizing personal utility functions in SuperCollider?

Thanks in advance for any advice!
Philipp

Hi Philipp,

I think, what you say absolutely makes sense. Having every customization in the startup file produces clutter and could become annoying and hard to maintain.

I would suggest to write up your own class that takes the job:

MyStarter {

    *initClass {
        /* ...
        everything in here will be executed at class compilation
        */
        StartUp.add(
            // everything here (every function) will be executed on start up, 
            // i.e., just like it's been put in startup.scd
        )
    }

}

Maybe have a look at these:
https://doc.sccode.org/Guides/WritingClasses.html
https://doc.sccode.org/Classes/StartUp.html

ad “global variables”: It’s merely up to your liking how you want to name them. Often variable names are camel-cased but _ is perfectly valid.
The more important aspect about “global variables” is that these are really Environment variables. An Environment constitutes a namespaced dictionary-like collection of variables.When you declare a variable like ~myVar = 111 you really declare a variable in the interpreter’s currentEnvironment:

currentEnvironment[\myVar] // returns 111

… so, if prefixing matters, you might consider using different Environments or use other dictionary-like objects to keep your variables:
https://doc.sccode.org/Classes/Environment.html#.currentEnvironment
https://doc.sccode.org/Classes/Event.html
https://doc.sccode.org/Classes/IdentityDictionary.html
https://doc.sccode.org/Classes/Dictionary.html

Best, Stefan

2 Likes

In addition to what Stefan recommended, you could also consider loading files with your project-specific data / Functions, see help of String.load.
You could then also put this (load) code into startup.scd. Personally, I mostly prefer working with load to defining extra classes as it’s such an easy workflow. Using Events as project-specific name spaces is also a good practice you can combine with loading from project files.

1 Like

Thanks to you both!

This seems to be the easiest solution for me right now.
What advantages would i get if i would write my Functions as classes?

I also stumbled along the writeDefFile method for SynthDef. Is it common to use it to make my SynthDefs avaible at all my sessions?

So, i will start building up a library of functions and function files in a seperate dir from my ‘startup.scd’ and then load them from my ‘startup.scd’.
I will prefix all my library functions with a ‘~pvn’. Would this be a good practice?

if you write Classes the signatures get hinted in your IDE which is nice… and Classes can share data between methods…

String.load

… there’s nothing that prevents you from loading scd files in a StartUp.add(...) call in *initClass of your class. It works just the same as in startup.scd (well, almost :wink: ):

// code must be wrapped in a function {}
StartUp.add({ /* load your files */})

Regarding writeDefFile: Sure, it’s not uncommon. If you got SynthDefs that you want to have available in all sessions it’s probably the way to go.

In my view: none, but it’s a matter of taste also.

You can build huge and perfectly well-organized setups with Functions. A bit more advanced, you can also use Functions for SynthDef building blocks, chunks of UGens – an alternative to writing pseudo UGens.

On the other hand, I have sometimes seen students experienced in other programming languages, immediately eager to write everything with Classes and Methods (“it’s the clean way”). They then often struggle with the subtleties of writing classes and the uncomfortable SC debugging process (recompiling). Event prototyping is an attempt to circumvent that.
I don’t want to disencourage from writing Classes but IMO it only makes sense for rock-solid tested stuff that one wants to use for a long time (but that, again, can be achieved with Functions also). Distributing things as an extension is also a motivation for writing classes.

As Stefan said, it’s common. It might make sense especially if you have many compiling-intense SynthDefs. There are also some special cases where writeDefFile is needed (non-realtime / NRT …). Personally, I almost never use it.

Sounds good !

The prefix is extra clear, when working on only one project at a time I don’t do so. Consider using an Event / Environment as a namespace, then you could also save the prefix (as a tradeoff, the Event / Environment will need a name).

1 Like

Yes, I second the Environment/Event approach. If you want a short/minimal “prefix” for your personal functions, helpers and variables, one thing I’ve sometimes done is set them in a single-letter variable (which holds an Environment/Event), which will then be available regardless of the current Environment, and also is very short to type. Just access them using z.whatever. (just don’t use s as the variable! ;-))

A compromise between everything suggested could be a new class called Fns that just wraps an identity dictionary. I find using the interpreter variables hard to scale and maintain overtime.

I’m assuming you are using the current release candidate here…

Fn {
   classvar store;
   *addFn { |name, fn| store.put(names fn) }
   *initClass{ store = () }
   *doesNotUnderstand { |name ... args, kwargs|
      ^store.performArgs(name, args, kwargs)
   }
}

Then you add functions in startup, or in a new file like so

Fn.add(\meow, { |a, b| [a, b].debug(\meow) })

And call them from anywhere…

Fn.meow(a: 1, b: 2) // meow: [1, 2]

There are a few limits to what the functions can be called, this is a limit of object prototyping and you should get a warning if you’ve named something bad.

Fyi, I wrote this on mobile, so it might have silly errors :melting_face:

1 Like

…or another option (just to expand on what’s been mentioned with an example) is to organise your code in files, storing everything in environment variables, and loading each new file into it’s own environment…

//File path: util.scd
~speak = { |a, b| [a, b].debug(\meow) }
//File path: main.scd
~util = Environment.make{ "util.scd".load };
~util.speak(a:1, b: 2) // meow: [1, 2]
2 Likes

The built-in class Fdef has similar functionality

https://doc.sccode.org/Classes/Fdef.html