Question more related to development but I posted it under questions…
Is there any way to do information hiding in SCLang? The best I can think of is “make a class” and that sort of handles it even if there’s really not much to stop someone from modifying the class. I guess information is basically hidden in Ugens but outside of that? Certainly it’s not really in the spirit of the SuperCollider’s design but there have been times I’ve thought it would be useful.
Basically, no.
There’s no saved object code format for sclang, like there is for Java. Everything goes back to the source code. There’s no facility to encrypt sclang source code, and because of GPL, if you added encryption, you’d have to release the source for that (so, encryption would be reverse-engineerable).
hjh
What exactly do you mean, @jamshark70 has interpreted your question to be similar to compiled objects, but are you after a public/private distinction instead?
Or are you talking about abstraction in general?
I’ve interpreted the question as, “is there a way to distribute a sclang program without revealing the source code to the end user?” where compiled objects or encryption would be the most obvious ways to do so.
hjh
So I don’t actually think you want this, @mjsyts … but nevertheless this would do want @jamshark70 has described …
Assuming...
… you have linux (or mac) and lz4
or gpg
installed.
~f = { |a| format("Hello %", a) };
~compress = {
|code, file|
format("echo % | lz4 - -c > %", code.shellQuote, file.shellQuote).unixCmd
}
~decompress = {
|file|
format("lz4 % -d -c", file).unixCmdGetStdOut.compile.()
}
~compress.(~f.def.sourceCode, "/tmp/myfile")
~r = ~decompress.("/tmp/myfile");
~r.("mum") // Hello mum
~r.("world") // Hello world
I’m using lz4
which is a compression tool (like zip
) to store the code as a string, then uncompressed it. Which would mean you would need to distribute the compressed file… its really stupid, please don’t do this.
Alternatively, you could do encryption like this using gpg
which will promt you for a password
~f = { |a| format("Hello %", a) };
~compress = {
|code, file|
format("echo % | gpg -c > %", code.shellQuote, file.shellQuote).unixCmd
}
~decompress = {
|file|
format("gpg -d -q %", file).unixCmdGetStdOut.compile.()
}
~compress.(~f.def.sourceCode, "/tmp/myfile")
~decompress.("/tmp/myfile").("mum")
Again, this is really dumb. I’m only posting it because I think its quite funny to have password encrypted supercollider code…
Perhaps you could give an example of what you were trying to achieve?
Sorry for the late reply! I was actually thinking more of access in C++. So for example, one could make a class with public or private members/functions to limit access. It was a discussion topic in a C++ class I’m taking and honestly, I’m more interested in the idea than the implementation. I don’t really have a particular outcome I’m looking for, so I guess this is more of just a general discussion thread on to what extent this might be available in SC or gets translated between the C++ source code and what’s happening with the interpreter on evaluation.
Well in C++ hiding implementation details is not really what private functions should be used for as you can just move all the private functions into the source - or into the function itself as lambdas - and take refs, thereby keeping the public header completely clean. Public/private/protected interfaces is quite a traditional object oriented way of thinking, and C++ isn’t really about that (anymore). Private methods are only really necessary in C++ when you are overriding some virtual implementation that is used by the base class.
SCLang has no such concept of public and private despite being an object oriented language - this had lead to the convention that ‘private’ methods are prefix with the letters ‘pr’. I think this is smalltalk’s fault? But there is nothing to stop you from calling one.
Many good programming practices you will learning studying C++ are outright flaunted in SCLang, most notably, prefer composition over inheritance (which perhaps should be, avoid inheritance and virtual where possible). In SCLang all objects inherent from Object
something you would never want to do in C++ as it leads to sprawling interfaces - just take a look at the Object.sc
file, its big and other files may decide to randomly add stuff to it like … +Object { ... }
; I suppose this is a type of implementation hiding, but not through a language feature, but through obscurity. In C++ you should avoid these large complex hierarchies and prefer aggregate types. Its also impossible to add functionality outside of the class’ curlies.
Further, in C++ you should only really use virtual methods when you need the user to specify some implementation that might change at run time - and even then, its normally better to use std::function
. As an example I’d suggest GitHub - jcelerier/libremidi: A modern C++ MIDI real-time & file I/O library. Supports Windows, macOS, Linux and WebMIDI. which uses one virtual function, and it isn’t even public facing. However in SCLang, essentially every method is virtual - this way, most features of your class will not be directly known to you, but you will have to hunt through the hierarchy to find them.
If you are learning C++ I think it would be best to forget about SCLang, the two are completely different. It might be different if you were talking about Java or C# though. That isn’t to say supercollider is pedagogically useless, far from it (particularity if you have been keep vars immutable and thinking in a more function way, thereby getting closer to value semantics which is far more popular today!), just that the two are ultimately incomparable when discussing these types of differences.
I don’t quite know what you mean by the following?
From what I understand SC was written in C++ right? Or the bulk of the DSP was at the very least if not maybe everything else. That I don’t really know but I’m also not really all that clear on how the interpreter works/when, where, and how the C++ source comes into play in the evaluation of SC code. I’m new to C++ so I’m mostly just curious about how things work and why.
When ever you evaluate some code c++ reads it, evaluates the code, prints the results and loops (REPL). You can find all this on GitHub in the lang folder. Sclang can explicitly call c++ code, but it must be exposed and these functions begin with an underscore.
DSP code is written in c++, basically you declare some memory and a function of a specific signature - usually you use a class for this, but it’s technically optional. The sclang ugen class then requires it’s name to match the name that you registered the memory and functions with in c++.
To add a little to the response above - there is very little DSP done in sclang, and nothing in sclang is rendered to the sound card.
When a SynthDef is created and sent to scsynth, the UGen class writes the name of the UGen into the descirption:
… snip from UGen …
name {
^this.class.name.asString;
}
writeDef { arg file;
try {
file.putPascalString(this.name);
… end snip …
So, building the SynthDef relies on the name of the UGen subclass you are using (e.g. SinOsc) to match a UGen that is loaded to scsynth from a plugin file. This code (DSP or UGen code) is written in C++. This doesn’t have anything to do with the language primitives though.
I’ll add one detail: SC has no private methods, but you can have a member variable without a getter method (and the variable is then effectively private).
hjh
Another thing to add, which often gets over looked is the function - particular when you consider functions as values in their own right (called first class functions).
To relate C++ and sclang here, functions that capture their value is a powerful trick in both languages, and allows you to construct functions based on some input, that then return another specialised with the input.
The example below is constructing an array, but in practice this would be a longer process that you need to evaluate at some point in the future.
f = { | a, b|
var foo = a * b;
{ [foo, a, b] }
}
g = f.(a, b) // g is now a function
g.() // [50, 10, 5]
// you can call g many times
…or in c++…
#include <array>
template<typename T>
concept CanMultiply = requires(T a, T b){
{a * b} -> std::convertible_to<T>;
};
template<CanMultiply T>
[[nodiscard]] constexpr auto generator(T c, T d) noexcept {
const auto foo{c * d};
return [=]() -> std::array<T, 3> { return {{foo, c, d}}; };
}
static constexpr auto arrayGenerator = generator(3.0, 2.1);
static_assert(arrayGenerator()[0] == 3.0 * 2.1);
static_assert(arrayGenerator()[1] == 3.0);
static_assert(arrayGenerator()[2] == 2.1);
Whats nice about this in C++ is that you can do it all at compile time!
Just wanted to come back to this…
An open source design isn’t related to implementation hiding. Implementation hiding is really about making your code impossible to misuse, its correct by design, a part of this involves making sure the user sees only the bits they are allowed to - not because you want to make it secret - but because users are stupid and will abuse the code if you allow for that possibility. One way of doing this is to embed domain specific knowledge into the code, making things that are illogical in the domain impossible for the user to write. Supercollider, being a child of the late 90 early 2000s, doesn’t quite follow this principle in its modern interpretation. There is absolutely nothing stopping you going into Object.sc
and changing something which will break everything in really weird unexpected ways - the code is quite fragile -, but, as a result of this object oriented framework, specifically the fact you can put any code in any method, the code is incredibly flexible and easy to manipulate. Therefore, its well suited for musicians who will disagree about any musical (domain specific) assumption inscribed into the code.
That’s actually more along the lines of what I meant by “not really in the spirit of SuperCollider’s design.”
Even on a smaller scale than the example you gave of messing with Object.sc
, the flexibility of SC code in general gives the user a lot of freedom to change a piece of code.
I tend to use GUI as misdirection and I’m sure lots of other people are this way. Setting slider ranges helps to keep your code working the way you intended it to, ex. "I want this oscillator {freq | 20 Hz < freq < ( samplerate/2 ) Hz}. If someone changes that, they can, but this is how I want the code to work.
Another example: when making SynthDef Ugen graphs, I try to consider how I’m using arguments since once the SynthDef is added, the arguments are going to be what I’m working with. So recently I’ve changed the way I write resonance arguments for filters:
{|freq, rq = 1.0|
RLPF.ar(WhiteNoise.ar(1.0)!2, freq, rq);
};
//////////
{|freq, q = 1.0|
RLPF.ar(WhiteNoise.ar(1.0)!2, freq, q.reciprocal);
};
So I’ve changed how I access that parameter. There’s nothing to stop me from changing the SynthDef, but I build the SynthDef to support what I will want to do with it (the arguments).
I think you’ve absolutely hit the nail on the head though.
And yes, I’ve found I’m far more successful when I don’t think about SC as I’m writing C++. C++ has been challenging for me.