Supercollider tips, Q/A


#1

i got a PM from a lines subscriber recently asking for some basic supercollider tips.

thought i would answer some of the questions in a public thread because i know there are other supercollider beginners on the forum, as well as other experienced users, so yeah. more efficient.

i don’t really have time to show examples for everything but will get things started with a couple.

here’s what i was asked about:

  • a piano roll
  • a step sequencer
  • detecting long key-presses
  • using pbind
  • switching modes on something (not real clear on this one)

and here are a couple caveats:

  • i’m not going to attempt any instruction in good modular software design. these are isolated examples. in SC good design uses Classes, which need to be compiled. Event is useful for interpreter-time OOP, but this gets more verbose. we could introduce that later.

  • i tend not to use super-idiomatic stuff like Patterns and Pbind. they are very useful but i prefer structures that translate more readily into other environments.


Approaching: norns
Approaching: norns
Approaching: norns
#2

detecting long key-presses

this is simply done by setting a callback when the key is pressed, and cancelling it when the key is released. the body of the callback fires off the long-press event. just like other environments.

here’s a super basic example where the ‘a’ key starts the timer and the ‘z’ key cancels it (this is just to avoid dealing with key-repeat.)

it’s limited to a single event though, because it schedules stuff on the singleton AppClock, and clears everything on the clock when the “key is lifted.” for a bunch of events, i would use an array of Schedulers, or just a single Scheduler checking an array of states.

SuperCollider has lots of mechanisms to handle timing. Maybe a useful observation is that in my experience, objects that inherit from Thread (like Routine and Task) are less useful for this kind of fine-grained interactive control, because their responsiveness can be unpredictable esp. across platforms.

w = Window.new("timer", Rect(0, 0, 70, 70));

w.view.keyDownAction_({ arg view, char;

    if(char == $a, {
        w.background_(Color.black);
        // UI events need to be on AppClock not SystemClock
        AppClock.sched(3, {
            w.background_(Color.red);
            postln ("boom");
            nil // returning nil prevents rescheduling
        });

        postln("started timer");
    });

    if(char == $z, {
        AppClock.clear;
        postln("cancelled timer");
    });

});

w.front;

#3

piano roll

here’s a simple piano roll.

again, this is not very modular. to make it more so, one could group associated state variables in an Event, make the “sequencer methods” accept an Event argument, &c.

//! ---- HOWTO: piano roll
//! very straightforward "procedural" style.

(
// number of notes and steps
var notes, steps;
// scale array (as ratios), fundamental in hz
var scale, fundamental;

// sequence state: note flag array, step duration
var seq_note_state, seq_step_period;
// sequencer state: is-playing flag, current position
var seq_play_flag, seq_pos;
// sequencer control functions
var seq_update, seq_advance, seq_set_pos;

// sequencer main loop (a Thread of some kind)
var seq_run_loop;
// GUI elements
var win, pos_but, val_but;
// GUI parameters
var bpx; // size of buttons

notes = 16;
steps = 16; 

// 8-tone scale of natural harmonics
scale = 16.collect({ arg x;
	var y;
	var xm8 = x%8;
	if(x==0, {
		y = 1.0;
	}, {
		y = xm8.nthPrime;
		while( { y > 2.0 }, { y = y / 2.0; });
		y = y * (2 ** ((x/8).floor));
	});
	y
}).sort;

/*
ok ok, you want 12tet instead...
scale = 16.collect({ arg x; x.midiratio });
*/

fundamental = 220;

seq_step_period = 0.2;

// initial position (advance before update)
seq_pos = -1;

// the "score": 2d  array of Booleans for note states
// outer array could be a List for extensibility,
// inner element could be an Event for more complex state, &c
seq_note_state = Array.fill(steps, { Array.fill(notes, { false  }); });

// update step function
seq_update = {
	// play the notes for the current step
	seq_note_state[seq_pos].postln;
	seq_note_state[seq_pos].do({ arg v, i;
		if(v, {
			// test synthdef compiled on the fly from a function...
			// could use e.g.:
			// Synth.new(\mydef, [\freq:fundamental*scale[i]... ], s);
			{
				SinOsc.ar(
					fundamental * scale[i],
					mul:EnvGen.ar(Env.perc, levelScale:0.125, doneAction:2)
				).dup
			}.play(Server.default);
		});
	});
	// update the GUI
	AppClock.sched(0, {
		steps.do({ arg i;
			pos_but[i].value = (i==seq_pos);
		});
		nil
	});
};


// increment the position for next update
seq_advance = {
	seq_pos = (seq_pos +1)%steps;
};

// function to immediately set position
seq_set_pos = { arg i;
	seq_pos  = i;
	seq_update.value;
};

// main update thread
seq_run_loop = Routine { inf.do {
	seq_advance.value;
	seq_update.value;
	seq_step_period.wait;
} };

//--------------------------
//--- ok, do stuff

// boot the server
s = Server.default;
s.waitForBoot({

	///... FIXME: for now, just run the thing forever
	seq_run_loop.play;
	seq_play_flag= true;

}); // waitForBoot


//---------------------------------
//-- GUI (very basic)

// size of buttons
bpx = 32;

// make window
win = Window.new("piano", Rect(0, 0, bpx * (steps+1), bpx * notes));
win.front;

// list of buttons to change state
val_but = Array.fill(steps, { arg i; Array.fill(notes,{ arg j;
	Button(win, Rect(j*bpx, i*bpx, bpx, bpx))
	.states_([
		["", Color.black, Color.black],
		["", Color.white, Color.white] ])
	.action_({arg but; seq_note_state[i][j] = (but.value > 0);
	})
}) });

// list of buttons to show/select position
pos_but = Array.fill(steps,{ arg i;
	Button(win, Rect(notes*bpx, i*bpx, bpx, bpx))
	.states_([
		["", Color.grey, Color.grey],
		["", Color.red, Color.red]
	])
	.action_({arg but; seq_set_pos.value(i); })
});


// toggle play with spacebar... select speed with numbers... &c.
win.view.keyDownAction = { arg view, char, mod, unicode, keycode, key;
    [view, char, mod, unicode, keycode, key].postln;
	///... left as exercise for reader :)
	// if(seq_play_flag, { ... }, { ... });
};

)


#4

Thanks for sharing!

I’d love to see more SuperCollider discussion, tutorials, and coding examples here. I like to use SuperCollider quite a bit myself. In the spirit of sharing, here is the code
http://sccode.org/1-51x
to reproduce this piece of music

if anyone is interested in studying it. BTW, every track on that album uses SuperCollider.


#5

I’m glad this thread exists and will test these examples later

I wonder how others would feel about a whole code related section of the forum? would that be redundant or restrictive since many conversation spill into this arena?


#6

I’d definitely enjoy a code section - I’m currently learning Javascript in Max and would like to be able to share ideas and get answers to any questions.


#7

I think a “coding” or “code & development” section would be great. Go for it.

I also like the idea of “code & algorithms”.


#8

I gave a 30 minutes lightning round intro to SC/Patterns tutorial for the Automatic Music hackathon at monthlymusichackathon.org last year.

It’s perhaps not the best stand-alone tutorial, but it does step through the fundamentals to understanding and using Pbind effectively (events, patterns, then Pbinds).

There’s some other random code in there (round2) for the last SC tutorial I did at MMH, in which I learned my lesson and just got people making noise. It’s not got any explanation in the files, but there’s some fun stuff if you’re interested in short form hacking in SC.

Another good resource, and much more in depth, is James Harkins’ ‘Pattern Guide’ under ‘Stream, Patterns, Events’ in the SC Help Browser.


#9

thinking about my original correspondent’s message, i think what they were really asking was, what kind of strategy in SC might be useful for changing the behavior of a sequencer / UI system on the fly.

in other words, some variation on MVP or whatever. (disregarding the jargon details, i mean decoupling controller/presenter logic from the UI itself.)

its a good question and honestly not something i’ve thought about much in that context. I’ve been using SC for so long now (since version 1, back in 1998 or something!) that i’m not always good at keeping up with new additions to the language. but now that i’m looking, it seems that someone has indeed implemented some MVP/MVC-friendly classes like SimpleController.

in a nutshell, this is an IdentityDictionary of actions that is also capable of monitoring the state of any Object.

it seems to me like you could make a different SimpleController for each sequencer logic module, attached to the same UI/IO elements, and add/remove them as desired. worth a try.


#10

I typically just use regular functions as listeners and rely on the dependancy support all classes inherit from Object to decouple UI from more, uh, core stuff. The changed invocation notifies any listeners without coupling the publishing object to the listener. Not MVC or MVP perhaps, more akin to Publish/Subscribe I guess, but it mostly does the trick.

s.boot;
a=();

(
a[\play_it] = { |self|
	var freq;
	freq = 1.0.rand.linexp(0, 1, 100, 4000).round;
	{
		SinOsc.ar(freq, mul: (-10).dbamp) * EnvGen.ar(Env.perc(releaseTime: 0.25), 1, doneAction: 2) ! 2;
	}.play;
	self.changed(\hey_it_just_played, freq);
};
)

a.play_it // evaluate to play

b = { |thechanged, what, freq| "hey it just played at freq %".format(freq).postln };

a.addDependant(b)

a.play_it // evaluate to play, b function listens and prints info

(
// in a sequence
fork {
	16.do {
		a.play_it;
		0.1.wait;
	}
}
)

a.removeDependant(b) // make b stop listen

#11

By the way, how on earth do you get the syntax coloring to work in this forum?


#12

oh, thanks for that.
so for me, Object.addDependant is the key takeaway, with which you can make many things.
e.g., i think in the original question, two-way binding could be helpful.

re: syntax coloring, i dunno, i just wrapped the code in triple-backticks and it worked.


#13

Thanks, triple-backticks did the trick for syntax colour.

On decoupling: Some years back I did foray into MVC and related patterns in SuperCollider but had a hard time deciding on how to structure certain parts of my application according to the pattern. For example, how does sequencers fit into the MVC pattern. Is a sequencer a controller? Is it part of the model? But you do not want too much logic in the model, right?

I ended up dumbing things down and using publish/subscribe via Object.addDependant which solved my major decoupling issues and also kind of inherently promoted structuring UI code well. So I mostly keep core, non-UI stuff in one place and spit out notification events via Object.changed letting other parts of the program listen and act upon those events however is needed. It works good enough for my small projects. I agree two way databinding would be better. It is probably a must for non-small projects.

On another note: SimpleController actually relies on Object.addDependant.

For publish/subscribe I can also recommend looking into NotificationCenter,


#14

hi. sorry for late response @zebra its been really hectic for me lately.

At that time i was thinking about using pbinds to control grid but now i decided to implement pbind related methods as a function on top of step-sequencer. Thought that getting all tracks/rows in a different routine for the sequence part would be more practical and it pretty much solved all the issues so far.

What i mean is some kind of embedded system rather than the MVP paradigm. Got myself a Kenton Killimax Mini to control both synthdef parameters and sequence changes, hell of a data routing/mapping tho.

Got some kind of polyrhythmic sequencer running right now, pretty primitive. But i believe pbinds are really versatile if can be controlled/augmented by grid.

Happy that this thread exists :slight_smile:


#15

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.


#16

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:


#17

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!


#18

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


#19

Genelec M030 over here


#20

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;
)

Approaching: norns