SuperCollider On Organelle

with OS 4.0 C&G just reorganised the folder structure slightly, you can find it here:

thanks so much for the quick answer !

Hi @thetechnobear , I have some questions about the mother.scd patch, if you like to exchange about this topic…
As there is a lot of good sounding SC patches out there, I was planning to adapt some for my organelle! (and share it with the community)
But I’m not sure to understand what is missing, in order to use all the functionalities of the organelle (like aux button, expression pedal, midi messages, output volume ?)

Also, I couldn’t play a sound file using some buffer manipulation examples (that I can adapted, following the example of simple synth) and I didn’t understand what was the reason (I tried to put the sound file in the same folder as the main.scd patch or using a sound that should be installed with supercollider). I wanted to check error messages in the dialog window of supercollider, using VNC viewer on a laptop but apparently the patch window is not opening with the sc app on organelle desktop (unlike PD which is possible to program directly in the organelle, with this method). Is it something doable by any way ?
I don’t have a long experience with sc but I have a friend who is kind of sc guru. He may help if needed, but for that I must know a bit more about how the mother patch works.

If you can give some help to start, it would be really nice !

have you taken a look at my example patch?

Ive also explained this patch in this post

you should make sure you understand how this works and implement a few patches before you considering ‘extending it’

how to extend past the functionality I’ve implemented this should be fairly straightforward using what I have already implemented as a basis.

bare in mind that all communication with the screen and hardware is done via OSC message to/from the mother host - you can see how these messages are handled in PureData, and simply implement these in the mother.scd in the same way I did the current set of messages.

note: re vumeter
so \OrganelleAudioLevel in mother.scd is now wrong, the reason is because OS 4.0 changed the call to /oled/vumeter to include a peak level - you can see this in mother.pd.
so if you wish this to work, you will need to override this OSCdef to correct it.

note:
its important to recognise, you do not to have use what I have done in mother.scd at all! ,
really these are just ‘helper’ functions, that I thought would be be useful to have - but your supercollider patch, could ignore them entirely, and simply use things like OSCdef (as i do in mother.scd) directly… which you may find simpler?!

This is standard supercollider stuff, try using a fixed path.
What I would recommend is getting the patches working on your desktop first, and then learn to adapt them to the Organelle environment (i.e. adding the ‘UI’) - really there is not much different.
obviously, the Organelle has not where near the cpu resources or memory that a desktop/laptop has, so keep this in mind during the development phase.

on a more general note, Id highly recommend Eli Fieldsteel’s Supercollider videos, they are absolutely amazing - a gem of youtube, and will really help you get started in Supercollider.

1 Like

Hi @thetechnobear !

Thanks again, for all your help :wink: So nice !
I finally succeeded in doing a first Supercollider patch for the Organelle.
It’s a simple metal percussion rhythm generator based on a physical model, that I took from SC examples.
Simple but it already sounds quite good. Still, there is some mysteries about some Organelle functions I’d like to solve before sharing it in Patchstorage website.
As I’m a new forum user, I can’t upload a zip file, so I send you the code directly, at the end of this message.

There is 3 things I’d like to do with it:

  • make the output vumeters work. (I took the code from the mother patch that I thought could do the job, but I don’t know how to do, as there is no documentation about it)
  • use the AUX button as a sustain control. (same pb as above)
  • in a later version I would like to synchronise to a midi clock, to generate rhythms accordingly. (any explanations in how the midi is handled by this version of Organelle/Supercollider would be really great)

If I can do that, I’ll start soon to share some patches, cause there is a lot of interesting things out there, just waiting to be shrunk into this cute little box :smiley: !

Here is my code (sorry it’s many not very convenient, and tell me if there a way to send directly the zip file):

//////
// initialise
~voices = Dictionary.new;
~bandwith=8000.0;
~partials=8;
~impulse=0.01;
~ringtime=2.0;
~oled.screen(1,format(“Bandwith % Hz”,~bandwith.asInteger));
~oled.screen(2,format(“Partials %”,~partials.asInteger));
~oled.screen(3,format(“Impulse %”,~impulse.asStringPrec(3)));
~oled.screen(4,format(“RingTime % sec”,~ringtime.asStringPrec(3)));
~oled.screen(5,“Sustain OFF”);

// output for vumeter and volume control
~mainVolume = 0.8;
~mainOut = nil;
~outL = Bus.audio(s,1);
~outR = Bus.audio(s,1);
~mainOutDef = SynthDef(\mainOutput,
{
arg amp = 0.8;
var insig,sig, peaksig;
insig = SoundIn.ar([0,1]);
sig = In.ar(~outL,2);
sig = sig * amp;
Out.ar(0,sig);
peaksig=[insig[0],insig[1],sig[0],sig[1]];
SendPeakRMS.kr(peaksig, 5, 3, “/audioLevel”);
}
);

// create knob callback
~knobfunc = {
arg func, msg, knob, value;
if(knob==0, {
~bandwith = value.linexp(0,1,20,8000);
~oled.screen(1,format(“Bandwith % Hz”,~bandwith.asInteger));

});
if(knob==1, {
	~partials = value.linlin(0,1,2,16).asInteger;
	~oled.screen(2,format("Partials %",~partials));

});
if(knob==2, {
	~impulse = value.linexp(0,1,0.003,3.0);
	~oled.screen(3,format("Impulse %",~impulse.asStringPrec(3)));

});
if(knob==3, {
	~ringtime = value.linexp(0,1,0.1,10.0);
	~oled.screen(4,format("RingTime % s",~ringtime.asStringPrec(3)));

});

};
// register knob callback
~knobs.addDependant(~knobfunc);

// aux callback

~aux[\key_hit] = {
arg self,key,vel;
self.changed(\aux,vel>0);
if(~aux>0,
{~oled.screen(5,“Sustain ON”);
~led.value(7);
},
{~oled.screen(5,“Sustain OFF”);
~led.value(0);
});
};

// create notes callback
~notesfunc = {
arg func, msg, note, vel;
if(vel>0 , {
if(~voices[note]!=nil,{~voices[note].set(\gate,0);});
~voices[note] = Synth.new(“KlankSynth_”++~partials.asSymbol,[
\freq, (note).linexp(60,83,20,8000),
\bandwith, ~bandwith,
\impulse, ~impulse,
\ringtime, ~ringtime,
\pan, 1.0.rand2,
\rhythm, #[0.125,0.25,0.375,0.5,0.75,1,1.5,2].choose
// \amp, (vel / 3.0) .clip(0,0.3)
]
);
~led.value(1);
} , {
if(~voices[note]!=nil,{~voices[note].set(\gate,0);});
~voices[note] = nil;
~led.value(0);
}; )
};
// register key callback
~notes.addDependant(~notesfunc);

//other callbacks
//~encoder_turn
//~encoder_button
//~footswitch

// metallic synths

SynthDef(\KlankSynth_2, {
arg freq=20, bandwith=8000.0, impulse=0.5, ringtime=2.0, gate=1, pan=0.0, rhythm=1;
var trig, p, exc, x, sig, env, partials=2;
exc = BrownNoise.ar(Decay2.kr(Impulse.kr(rhythm,0,0.04), 0.1, impulse));
env = EnvGen.ar(Env.adsr(),gate,doneAction:2);

sig = (Klank.ar(`[Array.fill(partials, {linrand(bandwith)+freq}),nil,Array.fill(partials, {rrand(0.1,ringtime)})], exc) * 0.35).softclip;
sig = sig * env;
sig = Pan2.ar(sig,pan);
Out.ar(~outL,sig);

}
).add;

SynthDef(\KlankSynth_3, {
arg freq=20, bandwith=8000.0, impulse=0.5, ringtime=2.0, gate=1, pan=0.0, rhythm=1;
var trig, p, exc, x, sig, env, partials=3;
exc = BrownNoise.ar(Decay2.kr(Impulse.kr(rhythm,0,0.04), 0.1, impulse));
env = EnvGen.ar(Env.adsr(),gate,doneAction:2);

sig = (Klank.ar(`[Array.fill(partials, {linrand(bandwith)+freq}),nil,Array.fill(partials, {rrand(0.1,ringtime)})], exc) * 0.35).softclip;
sig = sig * env;
sig = Pan2.ar(sig,pan);
Out.ar(~outL,sig);

}
).add;

SynthDef(\KlankSynth_4, {
arg freq=20, bandwith=8000.0, impulse=0.5, ringtime=2.0, gate=1, pan=0.0, rhythm=1;
var trig, p, exc, x, sig, env, partials=4;
exc = BrownNoise.ar(Decay2.kr(Impulse.kr(rhythm,0,0.04), 0.1, impulse));
env = EnvGen.ar(Env.adsr(),gate,doneAction:2);

sig = (Klank.ar(`[Array.fill(partials, {linrand(bandwith)+freq}),nil,Array.fill(partials, {rrand(0.1,ringtime)})], exc) * 0.35).softclip;
sig = sig * env;
sig = Pan2.ar(sig,pan);
Out.ar(~outL,sig);

}
).add;

SynthDef(\KlankSynth_5, {
arg freq=20, bandwith=8000.0, impulse=0.5, ringtime=2.0, gate=1, pan=0.0, rhythm=1;
var trig, p, exc, x, sig, env, partials=5;
exc = BrownNoise.ar(Decay2.kr(Impulse.kr(rhythm,0,0.04), 0.1, impulse));
env = EnvGen.ar(Env.adsr(),gate,doneAction:2);

sig = (Klank.ar(`[Array.fill(partials, {linrand(bandwith)+freq}),nil,Array.fill(partials, {rrand(0.1,ringtime)})], exc) * 0.35).softclip;
sig = sig * env;
sig = Pan2.ar(sig,pan);
Out.ar(~outL,sig);

}
).add;

SynthDef(\KlankSynth_6, {
arg freq=20, bandwith=8000.0, impulse=0.5, ringtime=2.0, gate=1, pan=0.0, rhythm=1;
var trig, p, exc, x, sig, env, partials=6;
exc = BrownNoise.ar(Decay2.kr(Impulse.kr(rhythm,0,0.04), 0.1, impulse));
env = EnvGen.ar(Env.adsr(),gate,doneAction:2);

sig = (Klank.ar(`[Array.fill(partials, {linrand(bandwith)+freq}),nil,Array.fill(partials, {rrand(0.1,ringtime)})], exc) * 0.35).softclip;
sig = sig * env;
sig = Pan2.ar(sig,pan);
Out.ar(~outL,sig);

}
).add;

SynthDef(\KlankSynth_7, {
arg freq=20, bandwith=8000.0, impulse=0.5, ringtime=2.0, gate=1, pan=0.0, rhythm=1;
var trig, p, exc, x, sig, env, partials=7;
exc = BrownNoise.ar(Decay2.kr(Impulse.kr(rhythm,0,0.04), 0.1, impulse));
env = EnvGen.ar(Env.adsr(),gate,doneAction:2);

sig = (Klank.ar(`[Array.fill(partials, {linrand(bandwith)+freq}),nil,Array.fill(partials, {rrand(0.1,ringtime)})], exc) * 0.35).softclip;
sig = sig * env;
sig = Pan2.ar(sig,pan);
Out.ar(~outL,sig);

}
).add;

SynthDef(\KlankSynth_8, {
arg freq=20, bandwith=8000.0, impulse=0.5, ringtime=2.0, gate=1, pan=0.0, rhythm=1;
var trig, p, exc, x, sig, env, partials=8;
exc = BrownNoise.ar(Decay2.kr(Impulse.kr(rhythm,0,0.04), 0.1, impulse));
env = EnvGen.ar(Env.adsr(),gate,doneAction:2);

sig = (Klank.ar(`[Array.fill(partials, {linrand(bandwith)+freq}),nil,Array.fill(partials, {rrand(0.1,ringtime)})], exc) * 0.35).softclip;
sig = sig * env;
sig = Pan2.ar(sig,pan);
Out.ar(~outL,sig);

}
).add;

SynthDef(\KlankSynth_9, {
arg freq=20, bandwith=8000.0, impulse=0.5, ringtime=2.0, gate=1, pan=0.0, rhythm=1;
var trig, p, exc, x, sig, env, partials=9;
exc = BrownNoise.ar(Decay2.kr(Impulse.kr(rhythm,0,0.04), 0.1, impulse));
env = EnvGen.ar(Env.adsr(),gate,doneAction:2);

sig = (Klank.ar(`[Array.fill(partials, {linrand(bandwith)+freq}),nil,Array.fill(partials, {rrand(0.1,ringtime)})], exc) * 0.35).softclip;
sig = sig * env;
sig = Pan2.ar(sig,pan);
Out.ar(~outL,sig);

}
).add;

SynthDef(\KlankSynth_10, {
arg freq=20, bandwith=8000.0, impulse=0.5, ringtime=2.0, gate=1, pan=0.0, rhythm=1;
var trig, p, exc, x, sig, env, partials=10;
exc = BrownNoise.ar(Decay2.kr(Impulse.kr(rhythm,0,0.04), 0.1, impulse));
env = EnvGen.ar(Env.adsr(),gate,doneAction:2);

sig = (Klank.ar(`[Array.fill(partials, {linrand(bandwith)+freq}),nil,Array.fill(partials, {rrand(0.1,ringtime)})], exc) * 0.35).softclip;
sig = sig * env;
sig = Pan2.ar(sig,pan);
Out.ar(~outL,sig);

}
).add;

SynthDef(\KlankSynth_11, {
arg freq=20, bandwith=8000.0, impulse=0.5, ringtime=2.0, gate=1, pan=0.0, rhythm=1;
var trig, p, exc, x, sig, env, partials=11;
exc = BrownNoise.ar(Decay2.kr(Impulse.kr(rhythm,0,0.04), 0.1, impulse));
env = EnvGen.ar(Env.adsr(),gate,doneAction:2);

sig = (Klank.ar(`[Array.fill(partials, {linrand(bandwith)+freq}),nil,Array.fill(partials, {rrand(0.1,ringtime)})], exc) * 0.35).softclip;
sig = sig * env;
sig = Pan2.ar(sig,pan);
Out.ar(~outL,sig);

}
).add;

SynthDef(\KlankSynth_12, {
arg freq=20, bandwith=8000.0, impulse=0.5, ringtime=2.0, gate=1, pan=0.0, rhythm=1;
var trig, p, exc, x, sig, env, partials=12;
exc = BrownNoise.ar(Decay2.kr(Impulse.kr(rhythm,0,0.04), 0.1, impulse));
env = EnvGen.ar(Env.adsr(),gate,doneAction:2);

sig = (Klank.ar(`[Array.fill(partials, {linrand(bandwith)+freq}),nil,Array.fill(partials, {rrand(0.1,ringtime)})], exc) * 0.35).softclip;
sig = sig * env;
sig = Pan2.ar(sig,pan);
Out.ar(~outL,sig);

}
).add;

SynthDef(\KlankSynth_13, {
arg freq=20, bandwith=8000.0, impulse=0.5, ringtime=2.0, gate=1, pan=0.0, rhythm=1;
var trig, p, exc, x, sig, env, partials=13;
exc = BrownNoise.ar(Decay2.kr(Impulse.kr(rhythm,0,0.04), 0.1, impulse));
env = EnvGen.ar(Env.adsr(),gate,doneAction:2);

sig = (Klank.ar(`[Array.fill(partials, {linrand(bandwith)+freq}),nil,Array.fill(partials, {rrand(0.1,ringtime)})], exc) * 0.35).softclip;
sig = sig * env;
sig = Pan2.ar(sig,pan);
Out.ar(~outL,sig);

}
).add;

SynthDef(\KlankSynth_14, {
arg freq=20, bandwith=8000.0, impulse=0.5, ringtime=2.0, gate=1, pan=0.0, rhythm=1;
var trig, p, exc, x, sig, env, partials=14;
exc = BrownNoise.ar(Decay2.kr(Impulse.kr(rhythm,0,0.04), 0.1, impulse));
env = EnvGen.ar(Env.adsr(),gate,doneAction:2);

sig = (Klank.ar(`[Array.fill(partials, {linrand(bandwith)+freq}),nil,Array.fill(partials, {rrand(0.1,ringtime)})], exc) * 0.35).softclip;
sig = sig * env;
sig = Pan2.ar(sig,pan);
Out.ar(~outL,sig);

}
).add;

SynthDef(\KlankSynth_15, {
arg freq=20, bandwith=8000.0, impulse=0.5, ringtime=2.0, gate=1, pan=0.0, rhythm=1;
var trig, p, exc, x, sig, env, partials=15;
exc = BrownNoise.ar(Decay2.kr(Impulse.kr(rhythm,0,0.04), 0.1, impulse));
env = EnvGen.ar(Env.adsr(),gate,doneAction:2);

sig = (Klank.ar(`[Array.fill(partials, {linrand(bandwith)+freq}),nil,Array.fill(partials, {rrand(0.1,ringtime)})], exc) * 0.35).softclip;
sig = sig * env;
sig = Pan2.ar(sig,pan);
Out.ar(~outL,sig);

}
).add;

SynthDef(\KlankSynth_16, {
arg freq=20, bandwith=8000.0, impulse=0.5, ringtime=2.0, gate=1, pan=0.0, rhythm=1;
var trig, p, exc, x, sig, env, partials=16;
exc = BrownNoise.ar(Decay2.kr(Impulse.kr(rhythm,0,0.04), 0.1, impulse));
env = EnvGen.ar(Env.adsr(),gate,doneAction:2);

sig = (Klank.ar(`[Array.fill(partials, {linrand(bandwith)+freq}),nil,Array.fill(partials, {rrand(0.1,ringtime)})], exc) * 0.35).softclip;
sig = sig * env;
sig = Pan2.ar(sig,pan);
Out.ar(~outL,sig);

}
).add;

OSCdef( \OrganelleAudioLevel,
{
arg msg, time, addr, recvPort;
var peakInL,peakInR,peakOutL,peakOutR;
peakInL = (msg[3] * 11).asInteger;
peakInR = (msg[5] * 11).asInteger;
peakOutL = (msg[7] * 11).asInteger;
peakOutR = (msg[9] * 11).asInteger;

	~motherHost.sendMsg("/oled/vumeter", peakInL,peakInR,peakOutL,peakOutR);
},
"/audioLevel"

);

tl;dr use this snippet. Read on for details.

OSCdef(\OrganelleAudioLevel, { arg msg, time, addr, recvPort;
		var peakInL, peakInR, peakOutL, peakOutR;
		peakInL = (msg[3] * 11).asInteger;
		peakInR = (msg[5] * 11).asInteger;
		peakOutL = (msg[7] * 11).asInteger;
		peakOutR = (msg[9] * 11).asInteger;

		~motherHost.sendMsg("/oled/vumeter", peakInL, peakInR, peakOutL, peakOutR, 1);
	},
	"/audioLevel"
);

I’m a total n00b when it comes to Supercollider, but the snippet above adjusts the OSCdef(\OrganelleAudioLevel with the tip @thetechnobear mentioned farther above.

The key is the 5th argument required by /oled/vumeter. From what I can tell this argument allows the meter to track what the last peak was, I’m cheating and just statically assigning 1 so that the meter at least works. Perhaps in the future I’ll not be so lazy and adjust so that it works as intended and of course post back here.

In general just crack open mother.pd to see the full functionality available for OSC messages.

Not sure if I am alone here but on my vanilla Organelle M I would get a black screen running @thetechnobear’s test SC patch… running OS 4.2

I think I may have found the culprit? It looks like MainMenu.cpp is looking for sclang in a different place than the default install that started to be built in with newer Organelle_OS versions.

I submitted a PR to correct this and there is more detail there as well as a workaround if folks are interested. Fix SC on Organelle M by dessertplanet · Pull Request #47 · critterandguitari/Organelle_OS · GitHub

I am super interested in messing around with SC capabilities on Organelle so looking forward to getting into this!

Also caught one more bit of weirdness. SuperCollider needs X to be running. This means on Organelle you have to do one of the following before any SC patches will work:

  1. connect a monitor and run startx from cmdline (you can then unplug your monitor!?)
  2. ssh into organelle and then add the environment variable DISPLAY=:0.0 before launching any SC patches
  3. Just enable VNC in settings! This appears to do what is necessary. You don’t even need to connect Wi-if or AP.

I found details here: https://github.com/supercollider/supercollider/blob/develop/README_RASPBERRY_PI.md

I was only not hitting this before because I was always hooked up to VNC and therefore X was running. Good to know!

Back to this finally :slight_smile:

I’ve successfully built SuperCollider 3.11 from source on my Organelle M, with Supernova enabled (for multi-threaded shenanigans) and QT disabled, following the “gui-less” build approach from the SC GitHub. After installing jackd supercollider patches load seamlessly and without any of the display-related errors that are observed on the in-built version.

The new build put sclang in /usr/local/bin (where the MainMenu.cpp pointed to before my recent PR) so unpatched Organelle OS 4.2 automatically points to the new installation without incorporating my change.

Scide is still installed and runs on the old version which is handy (No_QT version doesn’t include the IDE).

Here are the steps I took to get this far:

remount as rw!

Install dependencies for non-GUI Build from the instructions here

Upgrade cmake to 12.0 using bootstrap/make/make install from source tar.gz available in “older releases” here

Build and install SC 3.11 from source following instructions linked above (need to switch to the 3.11 branch, I think they broke something on RPi in later releases, I had compile issues)

When it came time to pass cmake parameters these are the incantations that finally worked:
cmake -DCMAKE_BUILD_TYPE=Release -DSUPERNOVA=ON -DSC_ED=OFF -DSC_EL=OFF DSC_VIM=ON -DNATIVE=ON -DSC_QT=OFF ..

After install I ran the ldconfig command as described and didn’t touch jack since I know MainMenu.cpp runs start-jack.sh which appears to have similar functionality.

Tested with the SimplePoly.sc patch and created a blank SC patch as well for starting mother.scd and then sending messages remotely. My ultimate goal is to get that darn DX7 patch working (I know Faust’s DX7 is out there but this one is different right?).

I’ve grabbed an .img file that you can put on your own sdcard if you are familiar with etcher and understand the risk of data loss etc. but it is too big to upload here. If anybody is interested in kicking the tires, let me know.

Hi dessertplanet, I got the DX7 patch working in a simple way quite a while ago. (see this thread Status of SuperCollider? - #21 by DaveBowman)

1 Like

If i remember rightly the UI was a bit rough but it worked! also looking at your instructions, I pretty sure my firmware I uploaded in that thread is pretty much the same method you used to get supercollider working

1 Like

Hi @teapot !

Cool I will have to give your image a try. I got a version of it working as well that I never shared because I couldn’t quite get the presets stable. Might revisit it soon. I wanted to set it up so you could do “car radio” style preset favourites. Polyphony was also a challenge because the original DX7 patch leverages multi-threading via supernova. I could get it going for most presets up to three note polyphony. After that things got weird.

If you are still interested in SC on Organelle you should check out my Icarus patch Icarus for Organelle

I wrote a custom mother.scd for that patch that handles VU meter, MIDI, Audio etc. and also the install script for that patch allows for “living off the land” on an existing install- it installs jackd and sets things up so that only the VNC/display hack is necessary for things to work. The mother.scd and install script should in theory be useful for any SC patches on Organelle.

1 Like

for mine I just did the easiest thing which was to multiply the value of knob1 by knob2 to set the preset… so maybe your’s was a bit further along anyway!

Here is what I had- though I haven’t touched it in ages and probably didn’t really understand supercollider at that time :slight_smile: