Hi,

I got something you might be interested in… the classic monome “step” app using pbinds, with grid and arc control. I just want to release a serialosc lib for SuperCollider I’ve been working on… sooon.

1 Like

Here’s a Pbind-based step sequencer with SerialOSCClient UI (grid and arc).

The SerialOSCClient UI code is crude. The core stuff is all right.

Dependancy support - as described in this thread - is used to decouple the UI from the core stuff.

I wanted to make a drag n drop UI for sample loading before releasing but I’ve been so busy I could not find time yet. For now you need to use ~load_sample.(samplenum, path) to load your samples.

:slight_smile:

1 Like

lovely track, lovely album! I remember listening to it a few weeks back being super excited about it. Now even more so seeing the code.
Thanks for sharing!

Agreed. Amazing stuff. I did not listen to it until now.

2 Likes

Genelec M030 over here

I posted this in a Muff’s SC thread. This is an interactive homework assignment that I turned in a few years back. This isn’t the most in-depth SC tutorial, but it shows a learning process in a build-up manner. Just copy-paste the entire thing into the SC editor.

Anything labelled “AC” is a comment from my professor, Andres Cabrera:

//240A Theory Paper
//SuperCollider Composition by Michael Hetrick
//Started 12.03.12
//Finished 12.xx.12

//In this interactive paper, I will be exploring generative strategies in Supercollider. I am writing this paper simultaneously with my learning process,
//so all code you see is written as I learn it.

//Recently, I've been working a lot with my hardware modular synth. One of my favorite modules is Maths by Make Noise.
//The main concept of Maths is a looping AD envelope with many other tricks. I have found it to be invaluable in making music.
//I'm starting off with some example code for the Decay UGen:

play({ Decay.ar(Impulse.ar(XLine.kr(1,50,20), 0.25), 0.2, PinkNoise.ar, 0) });

//Similarly, the Decay2 documentation contains a much more pleasant sine implementation.
{ Decay.ar(Impulse.ar(XLine.kr(1,50,20), 0.25), 0.2, FSinOsc.ar(600), 0)  }.play;

//Both of these examples use an XLine object to take the AD envelope from subsonic frequencies, through morphosis into AM synthesis.
//Annoylingly, both of these examples only use the left channel.
//While the morphosis is a very cool effect, I want to do something more rhythmic in stereo. I am starting with the following line:

{ Decay.ar(Impulse.ar([1.50, 1.25], 0.25), 0.3, FSinOsc.ar(600), 0)*0.3  }.play;

//This creates a short percussive loop in stereo. To achieve the stereo effect, I have used the [ , ] notation inside of the Impulse arguments.
//Next, I want to set up stereo notes:

{ Decay.ar(Impulse.ar([1.50, 1.25], 0.25), 0.3, FSinOsc.ar([600,300]), 0)*0.3  }.play;

//This is more interesting, but still quite boring. To offset this, I have set up three envelopes to be played simultaneously.

{ Decay.ar(Impulse.ar([1.50, 1.25], 0.25), 0.3, FSinOsc.ar(600), 0)*0.3  }.play;
{ Decay.ar(Impulse.ar([0.75, 1.75], 0.25), 0.3, FSinOsc.ar(300), 0)*0.3  }.play;
{ Decay.ar(Impulse.ar([2.50, 1.00], 0.25), 0.3, FSinOsc.ar(400), 0)*0.3  }.play;

//Triggering these simultaneously creates all sorts of rhythms as the patterns go in and out of sync.
//Okay, so now we need changing notes. It seems the best way to do this would be to create a SynthDef, and trigger each impulse manually:

SynthDef.new("blip", { |leftPitch = 440, rightPitch = 440, amp = 0.3, decay = 0.25, leftFreq=0.0, rightFreq=0.0|
    var sig;
	sig = Decay.ar(Impulse.ar([leftFreq, rightFreq], 0.0), decay, FSinOsc.ar([leftPitch, rightPitch]), 0)*amp;
    Out.ar(0, sig ! 2);    // sig ! 2 is the same as [sig, sig]
}).play;

x = Synth.new("blip");

// AC: Remember to free the synth! You can use the doneAction in envelope generators, but it can't be done here! (OK I see you've solved it below)

//That took a lot of trial and error! If Impulse's frequency is set to 0, it only releases one impulse. This now gives me the flexibility
//to sequence individual blips or full pulse streams. For example:

x =  Synth.new("blip",["leftPitch", 600, "rightPitch", 400]);   //blip chord
x =  Synth.new("blip",["leftPitch", 600, "rightPitch", 400, "leftFreq", 200, "rightFreq", 20, "amp", 0.1]); //Stereo AM synth

// AC: I'm trying to figure out why this sounds buzzy. Do you know? I suspect there is a sudden jump in amplitude when the Decay is retriggered.

//We learned about Routines in class, which are something I haven't tried before. Let's attempt to create a simple stereo sequence.

r = Routine({
	a =  Synth.new("blip",["leftPitch", 600, "rightPitch", 400]).yield;
	b =  Synth.new("blip",["leftPitch", 300, "rightPitch", 300]).yield;
	c =  Synth.new("blip",["leftPitch", 900, "rightPitch", 200]).yield;
	d =  Synth.new("blip",["leftPitch", 800, "rightPitch", 400]).yield;
	a.free; b.free; c.free; d.free;
});

r.next;
r.reset;
//Cool! We have two melodies (one on each channel). Alternatively, we can use a task:

(
t = Task({
    loop {
        [600, 300, 900, 800].do({ |seqFreq|
            Synth("blip", [ amp: 0.2, leftPitch:seqFreq]);
            0.125.wait;
        });
    }
}).play;
)

t.stop;
t.play;

//I like that! I'm wondering what will happen if I do a double do loop:
(
t = Task({
    loop {
		[400, 300, 200, 300].do({ |rightSeqFreq|
			[600, 300, 900, 800].do({ |leftSeqFreq|
				x = Synth("blip", [ amp: 0.2, leftPitch:leftSeqFreq, rightPitch:rightSeqFreq]);
				0.125.wait;
				x.free;
			});
		});
    }
}).play;
)

t.stop;
t.play;

//I turned the Synth part into "x" so that I could free it after every loop. I did this because the RAM and CPU usage were
//growing with every repeat. This is now an efficient, looping sequence!

//The SC sequencing tutorial is now trying to convince me to use patterns. This seems like it would be a cleaner way of sequencing
//longer patterns with more variation.
(
var leftNoteSequence, rightNoteSequence;
leftNoteSequence = Pseq([600, 300, 900, 800, 400, 600],inf).asStream;
rightNoteSequence = Pseq([600, 400, 300, 200, 400],inf).asStream;
t = Task({
    loop {
				x = Synth("blip", [ amp: 0.2, leftPitch:leftNoteSequence.next, rightPitch:rightNoteSequence.next]);
				0.125.wait;
				x.free;
    }
}).play;
)

//Now we're talking! Because of the uneven PSeq size, I now have a 30-event sequence. Let's see if we can do something cool
//with changing the frequency of the Impulse generator.
//First, let's redefine the SynthDef to include stereo amplitude control:
SynthDef.new("blip", { |leftPitch = 440, rightPitch = 440, ampL = 0.3, ampR = 0.3, decay = 0.25, leftFreq=0.0, rightFreq=0.0|
    var sig;
	sig = Decay.ar(Impulse.ar([leftFreq, rightFreq], 0.0), decay, FSinOsc.ar([leftPitch, rightPitch]), 0)*[ampL, ampR];
    Out.ar(0, sig ! 2);    // sig ! 2 is the same as [sig, sig]
}).play;

//Now, let's make a new sequence with pauses

(
var leftNoteSequence, rightNoteSequence;
var leftAmpSequence, rightAmpSequence;
leftNoteSequence = Pseq([600, 300, 900, 800, 400, 600],inf).asStream;
rightNoteSequence = Pseq([600, 400, 300, 200, 400],inf).asStream;
leftAmpSequence = Pseq([0.3, 0.0, 0.0, 0.3, 0.2, 0.1],inf).asStream;
rightAmpSequence = Pseq([0.3, 0.0, 0.3, 0.0, 0.0, 0.3, 0.3, 0.1, 0.3],inf).asStream;
t = Task({
    loop {
				x = Synth("blip", [ ampL: leftAmpSequence.next, ampR: rightAmpSequence.next, leftPitch:leftNoteSequence.next, rightPitch:rightNoteSequence.next]);
				0.125.wait;
				x.free;
    }
}).play;
)

//Cool! Here it is with different Impulse frequencies:
(
var leftNoteSequence, rightNoteSequence;
var leftAmpSequence, rightAmpSequence;
var leftImpSequence, rightImpSequence;
leftNoteSequence = Pseq([600, 300, 900, 800, 400, 600],inf).asStream;
rightNoteSequence = Pseq([600, 400, 300, 200, 400],inf).asStream;
leftAmpSequence = Pseq([0.3, 0.0, 0.0, 0.3, 0.2, 0.1],inf).asStream;
rightAmpSequence = Pseq([0.3, 0.0, 0.3, 0.0, 0.0, 0.3, 0.3, 0.1, 0.3],inf).asStream;
leftImpSequence = Pseq([0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 4.0, 0.0, 0.0, 0.0, 6.0, 0.0, 0.0, 0.0, 12.0],inf).asStream;
rightImpSequence = Pseq([0.0, 0.0, 0.0, 6.0, 0.0, 0.0, 14.0, 0.0, 0.0, 0.0, 0.0],inf).asStream;
t = Task({
    loop {
				x = Synth("blip", [ ampL: leftAmpSequence.next, ampR: rightAmpSequence.next, leftPitch:leftNoteSequence.next, rightPitch:rightNoteSequence.next, leftFreq: leftImpSequence.next, rightFreq: rightImpSequence.next]);
				0.125.wait;
				x.free;
    }
}).play;
)

//A bit clicky. Let's redefine our Synth to use Decay2, which has an attack time.
// AC: This is the solution...
(
SynthDef.new("blip", { |leftPitch = 440, rightPitch = 440, ampL = 0.3, ampR = 0.3, decay = 0.25, leftFreq=0.0, rightFreq=0.0|
    var sig;
	sig = Decay2.ar(Impulse.ar([leftFreq, rightFreq], 0.0),  0.01, decay, FSinOsc.ar([leftPitch, rightPitch]), 0)*[ampL, ampR];
    Out.ar(0, sig ! 2);    // sig ! 2 is the same as [sig, sig]
}).play;
)
//And now, let's use a shorter decay time, and let's change the length of time between loops

(
var leftNoteSequence, rightNoteSequence;
var leftAmpSequence, rightAmpSequence;
var leftImpSequence, rightImpSequence;
var loopLengthSequence;
leftNoteSequence = Pseq([600, 300, 900, 800, 400, 600],inf).asStream;
rightNoteSequence = Pseq([600, 400, 300, 200, 400],inf).asStream;
leftAmpSequence = Pseq([0.3, 0.0, 0.0, 0.3, 0.2, 0.1],inf).asStream;
rightAmpSequence = Pseq([0.3, 0.0, 0.3, 0.0, 0.0, 0.3, 0.3, 0.1, 0.3],inf).asStream;
leftImpSequence = Pseq([0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 4.0, 0.0, 0.0, 0.0, 6.0, 0.0, 0.0, 0.0, 12.0],inf).asStream;
rightImpSequence = Pseq([0.0, 0.0, 0.0, 6.0, 0.0, 0.0, 14.0, 0.0, 0.0, 0.0, 0.0],inf).asStream;
loopLengthSequence = Pseq([0.125, 0.125, 0.125, 0.25, 0.25, 0.125],inf).asStream;
t = Task({
    loop {
				x = Synth("blip", [ ampL: leftAmpSequence.next, ampR: rightAmpSequence.next, leftPitch:leftNoteSequence.next, rightPitch:rightNoteSequence.next, leftFreq: leftImpSequence.next, rightFreq: rightImpSequence.next, decay: 0.15]);
				loopLengthSequence.next.wait;
				x.free;
    }
}).play;
)

//Now that I have a cool melodic sequence, I should put together a hi-hat pattern
(
SynthDef.new("hihat", { |ampL = 0.3, ampR = 0.3, decay = 0.10, leftFreq=0.0, rightFreq=0.0|
    var sig;
	sig = Decay2.ar(Impulse.ar([leftFreq, rightFreq], 0.0),  0.01, decay, WhiteNoise.ar, 0)*[ampL, ampR];
    Out.ar(0, sig ! 2);
}).play;
)

//Adding it to the melodic sequence:
(
var hiHatSequence;
var leftNoteSequence, rightNoteSequence;
var leftAmpSequence, rightAmpSequence;
var leftImpSequence, rightImpSequence;
var loopLengthSequence;
leftNoteSequence = Pseq([600, 300, 900, 800, 400, 600],inf).asStream;
rightNoteSequence = Pseq([600, 400, 300, 200, 400],inf).asStream;
leftAmpSequence = Pseq([0.3, 0.0, 0.0, 0.3, 0.2, 0.1],inf).asStream;
rightAmpSequence = Pseq([0.3, 0.0, 0.3, 0.0, 0.0, 0.3, 0.3, 0.1, 0.3],inf).asStream;
leftImpSequence = Pseq([0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 4.0, 0.0, 0.0, 0.0, 6.0, 0.0, 0.0, 0.0, 12.0],inf).asStream;
rightImpSequence = Pseq([0.0, 0.0, 0.0, 6.0, 0.0, 0.0, 14.0, 0.0, 0.0, 0.0, 0.0],inf).asStream;
loopLengthSequence = Pseq([0.125, 0.125, 0.125, 0.25, 0.25, 0.125],inf).asStream;
hiHatSequence = Pseq([0.3, 0.1, 0.3, 0.0, 0.3, 0.0, 0.3, 0.0, 0.0, 0.1, 0.1, 0.0, 0.3],inf).asStream;
t = Task({
    loop {
				x = Synth("blip", [ ampL: leftAmpSequence.next, ampR: rightAmpSequence.next, leftPitch:leftNoteSequence.next, rightPitch:rightNoteSequence.next, leftFreq: leftImpSequence.next, rightFreq: rightImpSequence.next, decay: 0.15]);
		        y = Synth("hihat", [ ampL: hiHatSequence.next, ampR: hiHatSequence.next]);
				loopLengthSequence.next.wait;
				x.free; y.free;
    }
}).play;
)
16 Likes

Thanks, I appreciate that :slight_smile:

this is super awesome and I discovered it during a SC research spree two months ago

thanks for re-posting for lines peeps

Ok, stupid question time. Just going through some tutorials, and I ended up with this code:

(
var tempoclock = TempoClock(120/60);

{
inf.do {
Synth(\event, [\freq, 60.midicps]);
tempoclock.beatDur.wait;
};
}.fork(tempoclock)
)

Why does the synth trigger at what seems to be 240 bpm instead of 120 bpm? If I change the TempoClock argument to 60/60 it’s 1/4 of the speed of this.

Yes, I know there’s other / better ways to sequence stuff but I’m just scratching my head trying to figure out why this is working the way it is…

TL/DR:
if you want to specify durations in absolute time, you don’t need a Clock.
if you’re using a Clock, you probably want to specify durations in musical time.


i think it’s going something like this.

the fork method, with a Clock as argument, wraps the function in a new Routine and plays it on the Clock

fork { arg clock, quant, stackSize;
        ^Routine(this, stackSize).play(clock, quant);
    }

the value given to wait or yield is interpreted by the Clock as the number of beats to wait before rescheduling.

so i think what you want is either this (wait one beat and modify the tempo of the clock):


(
var tempoclock = TempoClock(120/60);
{
	inf.do {
		Synth(\event, [\freq, 60.midicps]);
		1.0.wait;  // <<<<----- 
	};
}.fork(tempoclock)
)

or this (use the default TempoClock and modulate the wait time):

(
var beatDur = 0.5;
{
	inf.do {
		Synth(\event, [\freq, 60.midicps]);
		beatDur.wait;
	};
}.fork; // <<<---
)

Ok, I see now.

So if my understanding is correct, when you schedule a task against TempoClock the time is expressed in beats. When you schedule against a SystemClock, it’s expressed in seconds.

Correct.

The problem is that beatDur returns the duration in seconds of a current whole beat, and as zebra wrote you typically specify your wait durations in beats for functions forked to TempoClocks.

super pedantic clarification:

when you use Function.fork with no clock argument, (or Routine.play) it still uses a (default) TempoClock. you can change the default TempoClock. you can use static methods to access the default TempoClock.

one reason this is significant is that TempoClock has meter and other stuff that SystemClock doesn’t:

s = Server.default;
s.waitForBoot({

    t = TempoClock(5);
    TempoClock.default = t;

    {
        SynthDef.new(\event, { arg freq;
            Out.ar(0, SinOsc.ar(freq) * EnvGen.ar(Env.perc, doneAction:2) * 0.1);
        }).send(s);
        s.sync;
        
        TempoClock.beatsPerBar = 5;
        /// these do the same thing:
        // TempoClock.default.beatsPerBar = 5;
        // t.beatsPerBar = 5;

        inf.do {
            Synth(\event, [\freq, (60 + (3 * TempoClock.beatInBar)).midicps]);
            1.0.wait;
        };

    }.fork;
});

and you can, say, modulate everything on the default clock at once:

s = Server.default;
s.waitForBoot({

    t = TempoClock(5);
    TempoClock.default = t;

    {
        SynthDef.new(\event, { arg freq, pan=0;
            var snd = SinOsc.ar(freq) * EnvGen.ar(Env.perc, doneAction:2) * 0.1;
            Out.ar(0, Pan2.ar(snd, pan));
        }).send(s);
        s.sync;

        TempoClock.beatsPerBar = 5;

        {
            inf.do {
                Synth(\event, [\pan, -0.5, \freq, (60 + (3 * TempoClock.beatInBar)).midicps]);
                1.0.wait;
            };
        }.fork;

        {
            inf.do {
                Synth(\event, [\pan, 0.5, \freq, (69 + (5 * TempoClock.beatInBar)).midicps]);
                0.75.wait;
            };
        }.fork;


    }.fork;
});

w = Window.new("tempo", Rect(0, 0, 100, 300));
Slider.new(w, w.bounds).action_( { |sl|
    TempoClock.default.tempo_(sl.value * 20.0)
});
w.front;

i’m trying to think of a scenario where it’s better to explicitly schedule something on the SystemClock. in theory:

  • if you’re calling stuff that isn’t thread safe.
  • if you want to make sure its timing can never be messed with by something like the 2nd example above.

This is very typical of SuperCollider. It relies very (too?) much on implicit defaults. That was tricky for me as a newcomer to SuperCollider.

For instance, for Events you can do this and get a sound (of course assuming server is booted).

().play;

Adding some other supported property to the event will override the default for that property, but use defaults for other stuff.

(degree: 3).play;

The same applies to Pbinds. Without arguments you still get a sequence - it uses the default synthdef, note, duration:

Pbind().play // press Cmd-. to stop the sequence

Override one argument, otherwise use defaults:

Pbind(*[degree: Pseq([0, 3, 7, 10], inf)]).play

Override two arguments, otherwise use defaults:

Pbind(*[
	degree: Pseq([0, 3, 7, 10], inf),
	dur: Pseq([0.15, 0.1], inf)
]).play
)```

Another default: All Pbinds are by default also played using the default TempoClock for tempo, so changing its tempo will affect the pattern:

```TempoClock.default.tempo*60; // post current tempo in bpm
TempoClock.default.tempo = 80/60; // change pattern tempo to 80 bpm
TempoClock.default.tempo = 100/60; // ... 100 bpm
TempoClock.default.tempo = 120/60; // ... 120 bpm```

[quote="zebra, post:27, topic:3185"]
i'm trying to think of a scenario where it's better to explicitly schedule something on the SystemClock
[/quote]

SystemClocks and TempoClocks have the same priority so I believe it's mostly a matter of whether you want to be able to determine / change tempo dynamically (use a TempoClock) or just schedule by regular time.

AppClock, a third option, is of lower priority and typically used for UI updates. That's why you as a newcomer also run into errors that mean having to ```{}.defer``` stuff that's scheduled on a SystemClock or TempoClock that is supposed to update GUI widgets.

Since SystemClock has no variable tempo I believe only one exists and is used but with TempoClocks you can create as many as you want if you need multiple apps with differing tempos.

How nice we got SuperCollider discussions going on here...

SystemClocks and TempoClocks have the same priority

this is getting deeper into the dark waters of the SC scheduler implementation, which i’m not very familiar with.

but, i am pretty sure that creating a TempoClock always attempts to create a new thread as well:

whereas SystemClock (which is singleton) always runs on the interpreter’s main process (calls schedRunFunc() directly in that same source, instead of passing the function to a pthread.)

i am 90% sure i’ve run into situations where things failed to work as expected in a TempoClock. (stuff like filesystem, network, pipes, serial drivers…) and needed to be scheduled explicitly on the SystemClock. presumably because of pthreads.

but i could be wrong about this. there is probably better information to be had on this in the sc-users, sc-devel mailing lists.


It relies very (too?) much on implicit defaults

totally agreed. can be very confusing. i spent many years in ignorance of what i was actually doing in common SC patterns. ctl-D is always informative.

You’re more experienced in low level stuff and probably right in that they’re not of the same OS priority. Thanks for correcting me.

Let me rephrase. What I meant was that SystemClock and TempoClock are for timing critical stuff, whereas AppClock is not.

can’t recall if I shared this already
https://twitter.com/search?q=supercollider+play

interesting challenge that I’ve tried learning bits from

140 char SC compositions

3 Likes

Is anybody using sclang without scide? I’m running Supercollider on raspbian stretch lite and therefore do not have X windows. The sclang REPL works fine, but I’d like to use vi or emacs. Been having a little trouble getting scvim to work, and while I’m really more comfortable in vi than emacs, I’d be willing to switch if it made working with Supercollider easier. But before I did that I thought I’d find out what others are doing, if anything.

yeah i’ve been doing that on raspbian.

only real gotcha that comes to mind is this issue; a bug in AppClock with the terminal client that interferes with server .statusWatcher:

(check top, you may be seeing 100% cpu usage on sclang.)

here’s what i do to work around it… (Crone is a singleton class that always keeps a running server and a bunch of “boilerplate” busses, synths and defs…)

Crone {
	// the audio server
	classvar <>server;
	// current CroneEngine subclass instance
	classvar <>engine;
	// available OSC functions
	classvar <>oscfunc;
	// address of remote client
	classvar <>remoteAddr;
	// port to send OSC on
	classvar <>txPort = 8888;
	// an AudioContext
	classvar <>ctx;

	*initClass {
		StartUp.add { // defer until after sclang init

			postln("\n-------------------------------------------------");
			postln(" Crone startup");
			postln("");
			postln(" \OSC rx port: " ++ NetAddr.langPort);
			postln(" \OSC tx port: " ++ txPort);
			postln("--------------------------------------------------\n");

			server = Server.local;
			server.waitForBoot ({
				Routine {
					// this is necessary due to a bug in sclang terminal timer!
					// GH issue 2144 on upstream supercollider
					// hoping for fix in 3.9 release...
					server.statusWatcher.stopAliveThread;
					server.initTree;
					server.sync;
					CroneDefs.sendDefs(server);
					server.sync;
					// create the audio context
					// sets up boilerplate routing and analysis
					ctx = AudioContext.new(server);
				}.play;
			});
...
        }
...
}

as for vim / emacs, i use scel all the time (including on pi) but have never used scvim…

OK, cool, I’ll give scel a whirl. About time I learned emacs anyway.

Thanks for the Crone tip!