Monday, November 30, 2009

'Harmoniser' with Tartini

A little patch I made today following on from the auto chip tune stuff from before. This time a 'harmoniser' type effect made with the same Tartini type resynthesising, using a live input. I've got 'harmoniser' in inverted commas because frankly it isn't very harmonic, but it's a start I suppose. Here's the code.

(
SynthDef("Harm", {|offSet1, offSet2, output, osc1, osc2, mix, pitch1, pitch2, fader|

var sound = SoundIn.ar;
var pitch = Tartini.kr(sound)[0];
var amp = Amplitude.ar(sound);
pitch = Median.kr(5, pitch); // smooth
pitch = pitch.min(10000).max(10); // limit
pitch1 = pitch.cpsmidi.round + 40;
pitch2 = pitch.cpsmidi.round + 60;

pitch1 = pitch1.midicps;
pitch2 = pitch2.midicps;

osc1 = Saw.ar(pitch1, amp * 2); // resynthesise
osc2 = Pulse.ar(pitch2, 0.2, amp * 2);
mix = Mix.ar([osc1, osc2]);
fader = XFade2.ar(sound, mix, -0.5);
output = FreeVerb.ar(fader, 0.3, 0.1, 0.9); // bit of reverb just to taste
Out.ar(0, output);

}).memStore;


)

x = Synth("Harm");



Here Im using Tartini to determine the pitch of the input, then using fairly roundabout method converting it to two lots of midi notes, adding to it to increase the pitch then sending it to some oscillators and finally mixing it back with the original input. If you change the numbers that I've added to the pitches here,

pitch1 = pitch.cpsmidi.round + 40;
pitch2 = pitch.cpsmidi.round + 60;



you should be able to change the 'harmony'.

There may be a better method to shift the synthesised pitches, I'm not sure but this does seem to work.
It sort of sound okay with some guitar or vocal input, but really bad with anything more complex.

Friday, October 30, 2009

Installing bbcut2 in OS X

I've just upgraded to OS X Snow Leopard and apart from it giving me the ability to take up serpents, drink any deadly thing and not be hurt etc, I also found myself reinstalling SuperCollider from scratch, including all the extensions, quarks and what have you that I had in my previous install.
Everything went smoothly with the exception of bbcut, which took a bit of work. I had the same problem when I installed it the first time round, but I ended up taking such as circuitous route to getting it working that I couldn't remember what I'd done by the time I'd finished. This time I took a more systematic approach and I've isolated the problem.
The latest version of the extension pack provided with the standard SC install includes some, but not all of the Ugens and classes that come with bbcut. If you follow the install instructions that come with bbcut you'll end up with conflicts and the class library won't compile.
To get it to work you'll need to leave out the three ugens in the bbcut2 ugens folder and the machinelistening classes from the bbcut2 classes folder when moving the bbcut folders to your SC directory. These seem to be the source of the conflicts and if you copy over the rest of the files as described in the bbcut2 help file it should all be okay.
If you've already copied these into your SC folder you'll need to make sure you delete the version of the files that come with bbcut and replace the them with the versions that come with the standard SC download, they are different and only the ones from the standard SC download will work. And that's jazz.

I've no idea how this goes on other platforms but I imagine there might be similar problems.

à bientôt.

Sunday, October 18, 2009

Wicki - not wiki

Another soupçon of useful code from the SC-Users list, a Wicki keybord, as used by the Wicki system.
What's the Wicki system? I'm not entirely sure, but it seems to be a way of learning to play music with a type writer style keyboard. There's some background info available here and here.
This bit of code is a class that creates a Wicki keyboard, it is a bit long so I won't reproduce it on this blog, but you can download it here, along with a bit of test code. You can use the keyboard to record and playback sequences of notes, which are stored in an array, probably easier than typing frequencies or midi values directly into patterns and do things like transpose the entire sequence.
As with all SC classes you'll need to put it in the extensions folder and recompile the language before it'll work. The wickiTest file in the zip shows of some of the class methods, but it doesn't mention the startRecording and stopRecording methods. If you want to record an a array of notes you'll need to set it up by doing something like
m.startRecording
and then
m.stopRecording
when you're done.

Saturday, October 3, 2009

From Den Haag

Some great code from tn8, via the SC-Users list, the source code from a piece that she performed at the SC symposium. You can download it here.
It's a fantastic track and it's pretty rare to see the complete source for a song written entirely in SC, so it's worth a look.

Thursday, September 17, 2009

Comb Filter Effect

Another handy snippet of code pulled from the SC-Users list once again. Credit for this goes to Kernal, kernel@audiospillage.com.
The gui should be mostly self explanatory I think, but if you want to use this with other inputs besides the test sound you'll need to set the input bus and send your sound through that.


// parrallel comb filters implemented as effect return

// send the synth defs(
(
SynthDef("ImpulseTest", {|bus = 3|

Out.ar(bus, [Impulse.ar(SinOsc.kr(0.23).range(0.5,23), 0.5)]);

}).send(s);
);

(
SynthDef("CombUnit",{| d1 = 0.001, d2 = 0.001, d3 = 0.001, d4 = 0.001, d5 = 0.001,
t1 = 1, t2 = 1, t3 = 1, t4 = 1, t5 = 1,
f1 = 20000, f2 = 20000, f3 = 20000, f4 = 20000, f5 = 20000,
vol = 1, inBus = 3, outBus = 0|

var in, out, c1, c2, c3, c4, c5;

in = In.ar(inBus, 1);

c1 = LPF.ar(CombC.ar(in, 1, d1, t1), f1);
c2 = LPF.ar(CombC.ar(in, 1, d2, t2), f2);
c3 = LPF.ar(CombC.ar(in, 1, d3, t3), f3);
c4 = LPF.ar(CombC.ar(in, 1, d4, t4), f4);
c5 = LPF.ar(CombC.ar(in, 1, d5, t5), f5);

out = (c1 + c2 + c3 + c4 + c5) * 0.2;

Out.ar([outBus, outBus + 1], out * vol);

}).send(s);
);
)



// create the synth
(
var grp, node, testNode, wComb, testRunning = 0, inputBus = 3;

s.sendMsg("s_new", "CombUnit", node = s.nextNodeID, 1, 1);
// s.sendMsg("g_new",grp = s.nextNodeID,1,1); // create group @ tail of default node

wComb = SCWindow("C O M A", Rect(100, 300, 720, 220))
.onClose_({
if(testRunning == 1, s.sendMsg("n_free", testNode));
s.sendMsg("n_free", node);
})
.front;

// input bus
SCNumberBox(wComb, Rect(10, 10, 30, 20))
.value_(3)
.action_({|v|
inputBus = v.value;
s.sendMsg("n_set", node, "inBus", inputBus);
if(testRunning == 0, s.sendMsg("n_set", testNode, "bus", inputBus));
});

SCStaticText(wComb, Rect(45, 10, 100, 20))
.string_("Input Bus");

// output bus
SCNumberBox(wComb, Rect(150, 10, 30, 20))
.value_(0)
.action_({|v| s.sendMsg("n_set", node, "outBus", v.value)});

SCStaticText(wComb, Rect(185, 10, 100, 20))
.string_("Output Bus");

// audio test
SCButton(wComb, Rect(300, 10, 100, 20))
.states_([
["Start Test", Color.green, Color.black],
["Stop Test", Color.red, Color.black]
])
.action_({
if(testRunning == 0, {
s.sendMsg("s_new", "ImpulseTest", testNode = s.nextNodeID, 0, 1, "bus", inputBus);
testRunning = 1;
},{
s.sendMsg("n_free", testNode);
testRunning = 0;
});
});

// comb frequencies
SCStaticText(wComb, Rect(10, 40, 100, 20)).string_("Comb Frequencies");
5.do{|i|
var box;

box = SCNumberBox(wComb, Rect(10, 60 + (i * 25), 40, 20))
.value_(950);

SCStaticText(wComb, Rect(55, 60 + (i * 25), 15, 20)).string_("Hz");

SCSlider(wComb, Rect(80, 60 + (i * 25), 130, 20))
.value_(0.5)
.action_({|v| var time, freq;
freq = [1, 20000, 6].asSpec.map(v.value);
time = 1.0 / freq;
s.sendMsg("n_set", node, i, time);
box.value_(freq.asInteger);
})
};

// resonances
SCStaticText(wComb, Rect(250, 40, 100, 20)).string_("Resonances");
5.do{|i|
var box;

box = SCNumberBox(wComb, Rect(250, 60 + (i * 25), 30, 20))
.value_(50);

SCStaticText(wComb, Rect(285, 60 + (i * 25), 15, 20)).string_("%");

SCSlider(wComb, Rect(300, 60 + (i * 25), 150, 20))
.value_(0.5)
.action_({|v|
s.sendMsg("n_set", node, i+5, [0, 9, 4].asSpec.map(v.value));
box.value_(v.value * 100);
});
};

// low pass frequencies
SCStaticText(wComb, Rect(555, 40, 100, 20)).string_("Low Pass Frequencies");
5.do{|i|
var box;

box = SCNumberBox(wComb, Rect(490, 60 + (i * 25), 40, 20)).value_(20000);
SCStaticText(wComb, Rect(535, 60 + (i * 25), 15, 20)).string_("Hz");

SCSlider(wComb, Rect(555, 60 + (i * 25), 150, 20))
.value_(1)
.action_({ |v| var freq;
freq = [20, 20000, \exp].asSpec.map(v.value);
s.sendMsg("n_set", node, i+10, freq);
box.value_(freq);
});
};

// volume control
SCStaticText(wComb, Rect(10, 190, 70, 20)).string_("Volume");
SCSlider(wComb, Rect(80, 190, 625, 20))
.value_(1)
.action_({|v| s.sendMsg("n_set", node, "vol", [0, 1, 4].asSpec.map(v.value))});
)

Friday, August 21, 2009

More 140 character SuperCollider Tweets

Even SuperCollidists aren't immune to the memetic allure of Twitter, there's been a whole load of 140 character SuperCollider programmes appearing both on Twitter and on the SC Users list. Thankfully someone has been kind enough to collect them all up and put them on this page here, at the SC Wiki. Smashing.

Here's a couple of my favorites,

from Fredrik Olofsson

{RHPF.ar(GbmanN.ar([2300,1150]),LFSaw.ar(Pulse.ar(4,[1,2]/8,1,LFPulse.ar(1/8)/5+1))+2)}.play

And from the Venerable Dan

{LocalOut.ar(a=DynKlank.ar(`[LocalIn.ar.clip2(LFPulse.kr([1,2,1/8]).sum/2)**100*100],Impulse.ar(10)));HPF.ar(a).clip2!2}.play//#supercollider

Sunday, August 9, 2009

Recreating the THX sound

In a similar vein to the recreation of the rave hoover here's another reverse engineer and recreation of a famous sound, the tooth shattering TXH Deep Note. You can read the full story on EarSlap, but here's the final code.

//inverting init sort, louder bass, final volume envelope, some little tweaks

(

{

var numVoices = 30;

var fundamentals = ({rrand(200.0, 400.0)}!numVoices).sort.reverse;

var finalPitches = (numVoices.collect({|nv| (nv/(numVoices/6)).round * 12; }) + 14.5).midicps;

var outerEnv = EnvGen.kr(Env([0, 0.1, 1], [8, 4], [2, 4]));

var ampEnvelope = EnvGen.kr(Env([0, 1, 1, 0], [3, 21, 3], [2, 0, -4]), doneAction: 2);



var snd = Mix

({|numTone|



var initRandomFreq = fundamentals[numTone] + LFNoise2.kr(0.5, 6 * (numVoices - (numTone + 1)));

var destinationFreq = finalPitches[numTone] + LFNoise2.kr(0.1, (numTone / 3));

var sweepEnv =

EnvGen.kr(

Env([0, rrand(0.1, 0.2), 1], [rrand(5.5, 6), rrand(8.5, 9)],

[rrand(2.0, 3.0), rrand(4.0, 5.0)]));

var freq = ((1 - sweepEnv) * initRandomFreq) + (sweepEnv * destinationFreq);



Pan2.ar

(

BLowPass.ar(Saw.ar(freq), freq * 6, 0.6),

rrand(-0.5, 0.5),

(1 - (1/(numTone + 1))) * 1.5

) / numVoices

}!numVoices);



Limiter.ar(BLowPass.ar(snd, 2000 + (outerEnv * 18000), 0.5, (2 + outerEnv) * ampEnvelope));

}.play;

)


Apparently the original took 20,000 lines of C code! This is a tad more efficient and sounds very close to the original.

Saturday, July 25, 2009

Auto chiptunes with Moog

Wow, it's been a while since I've posted, but It's been a busy month.
Here's an update to Dan's auto chiptune thing that I modified in my last post. This time I decided to mate it with my Moog synthdef in an attempt to create a pitch tracking interface for my synth. Surprisingly it works pretty well.

(
SynthDef("chiptune", { |oscType =0, oscType2 = 1, pan = 0, level = 0.5, cutoff = 500, gain = 3.3, attack = 0.1, decay = 0.1, sust = 0.7, rel = 0.2, attackf = 0.1, decayf = 0.1, sustf = 0.9, relf = 0.2, gate = 1, freq =440, lagLev = 0.2|
var son, pitch, amp, wibble, oscArray, oscArray2, ampEnv, filterEnv, osc1, osc2, fade, filter;
son = SoundIn.ar;
pitch = Tartini.kr(son)[0];
amp = Amplitude.ar(son);
pitch = Median.kr(5, pitch); // smooth
pitch = pitch.min(10000).max(10); // limit
pitch = pitch.cpsmidi.round.midicps; // coerce

oscArray = [Saw.ar(Lag.kr(pitch, lagLev)), SinOsc.ar(Lag.kr(pitch, lagLev)), Pulse.ar(Lag.kr(pitch, lagLev))];
oscArray2 = [Saw.ar(Lag.kr(pitch, lagLev)), SinOsc.ar(Lag.kr(pitch, lagLev)), Pulse.ar(Lag.kr(pitch, lagLev))];
ampEnv = EnvGen.ar(Env.adsr(attack, decay, sust, rel), gate, doneAction:2);
filterEnv = EnvGen.ar(Env.adsr(attackf, decayf, sustf, relf), gate, doneAction:2);
osc1 = Select.ar(oscType, oscArray);
osc2 = Select.ar(oscType2, oscArray2);
fade = Pan2.ar(XFade2.ar(osc1, osc2, pan , level * ampEnv, 0));
filter = MoogFF.ar(fade, cutoff * filterEnv, gain);
wibble = FreeVerb.ar(filter, 0.3, 0.1, 0.9); // bit of reverb just to taste
Out.ar(0, wibble.dup);
}).memStore;
)

Sunday, July 5, 2009

Auto Chiptune

A while ago now Dan posted some auto chip tune generating code on his blog, I've been meaning to write about this for ages, but I've only just got round to it. It takes any MP3 file and converts it to chiptune like sounds by using Tartini to follow the pitch and then re synthesising with a pulse wave. Here's the original code as taken from his blog.
s.boot;
(
SynthDef("help_mp3_01", { |bufnum = 0|
var son, pitch, amp, wibble;
son = DiskIn.ar(2, bufnum).mean;
pitch = Tartini.kr(son)[0];
amp = Amplitude.ar(son);
pitch = Median.kr(5, pitch); // smooth
pitch = pitch.min(10000).max(10); // limit
pitch = pitch.cpsmidi.round.midicps; // coerce
wibble = Pulse.ar(pitch, 0.2, amp * 2); // resynthesise
wibble = FreeVerb.ar(wibble, 0.3, 0.1, 0.9); // bit of reverb just to taste
Out.ar(0, wibble.dup);
}).memStore;
)

// Now let's create the MP3 object and cue it into a Buffer.
m = MP3("../mp3s/Gimme A Pig Foot And A Bottle Of Beer.mp3");
m.start;
b = Buffer.cueSoundFile(s, m.fifo, 0, 2);
// Off we go:
x = Synth("help_mp3_01", [\bufnum, b.bufnum], addAction:\addToTail);

// Please remember to tidy up after yourself:
x.free;
b.close; b.free;
m.finish;




Just for the hell of it I decided to adapt it to use live audio input and give it three different oscillators to choose from. Here's my version, you can change the oscillator with the \oscType parameter.

(
SynthDef("chiptune", { |oscType = 0|
var son, pitch, amp, wibble, oscArray;
son = SoundIn.ar;
pitch = Tartini.kr(son)[0];
amp = Amplitude.ar(son);
pitch = Median.kr(5, pitch); // smooth
pitch = pitch.min(10000).max(10); // limit
pitch = pitch.cpsmidi.round.midicps; // coerce
oscArray = [Pulse.ar(pitch, 0.2, amp * 2), SinOsc.ar(pitch, 0, amp * 2), Saw.ar(pitch, amp * 2)];
wibble = Select.ar(oscType, oscArray); // resynthesise
wibble = FreeVerb.ar(wibble, 0.3, 0.1, 0.9); // bit of reverb just to taste
Out.ar(0, wibble.dup);
}).memStore;
)


// Off we go:
x = Synth("chiptune", [\oscType, 1], addAction:\addToTail);

// Please remember to tidy up after yourself:
x.free;


sounds pretty good with guitar.

Thursday, June 25, 2009

SuperCollider 3.3.1 is out - Safari 4 fix.

SuperCollider 3.3.1 is out now and it works with Safari 4! I don't know about you but that's a weight off my mind.

Thursday, June 18, 2009

A Generative Looper.

What's a generative looper? It's a type of cartilaginous fish, but it's also a interesting new project from Arthur Carabot, via the SC Users list. Here's the source and here's an intructional video.
I can't get it to work at the moment but maybe it's just me.

Tuesday, June 16, 2009

More Dominator Deconstruction.

Another attempt at the rave hoover sound, this time from Wouter Snoei on the SC-Users list, probably the best so far and based on further research into the Alpha Juno 2. Here's the code,

(
SynthDef( "hoover", { |freq = 220, amp = 0.1, lgu = 0.1, lgd = 1, gate = 1|
var pwm, mix, env;

freq = freq.cpsmidi.lag(lgu,lgd).midicps;
freq = SinOsc.kr( { 2.9 rrand: 3.1 }!3, {2pi.rand}!3 ).exprange( 0.995, 1.005 ) * freq;
pwm = SinOsc.kr( {2.0 rrand: 4.0}!3 ).range(0.125,0.875);

// the saw/pulses
mix = (LFSaw.ar( freq * [0.25,0.5,1], 1 ).range(0,1)
* (1 - LFPulse.ar(freq * [0.5,1,2], 0, pwm))).sum * 0.1;

// the bass
mix = mix + LFPar.ar( freq * 0.25, 0, 0.1 );

// eq for extra sharpness
mix = BPeakEQ.ar( mix, 6000, 1, 3 );
mix = BPeakEQ.ar( mix, 3500, 1, 6 );

// kind of chorus
mix = mix + CombC.ar( mix.dup, 1/200,
SinOsc.kr( 3, [0.5pi, 1.5pi] ).range(1/300,1/200),
0.0 ) * 0.5;

env = EnvGen.kr( Env.asr, gate );

Out.ar( 0, mix * env * amp );
}).store;
)

(
p = Pmono(\hoover,
\dur, Pseq([0.25,0.5,7, 0.25]* 0.24, inf),
\lgu, 0.15,
\lgd, Pseq([ 0.1, 0.1, 1.5, 0.25], inf ),
\midinote, Pseq([20, 67, 62, 20] , inf)).play;
)
p.stop;

(
p = Pmono(\hoover, \dur, 0.24,
\lgu, 0.2,
\lgd, Pseq([1,1,2,0.5,2,2,2,2], inf ),
\midinote, Pseq([55, 40, 67, 55, 40, 55, 53, 52], inf)).play;
)
p.stop;


Wouter's come up with a more accurate version of the pulse width modulation, certainly more accurate than my complete guess work and it sounds great.

Here's a little line of code taken from Wouter's post that may offer a bit of an explaination of what's going on with the modulation.

{ LFSaw.ar( 200, 1 ).range(0,1) * (1-LFPulse.ar( 400, 0, 2/3 )) }.plot;

If you run it you should get this plot,



You can see the waveform is a sort of flattened saw wave, this might explain why Dan had trouble identifying what it was.

Sunday, June 14, 2009

Deconstructing the Dominator

Dan's got a post over on his blog about reverse engineering the "hoover sound' found in the old school rave classic Dominator by Human Resource. He took a pretty technical approach and did various forms of analysis to come up with something that sound pretty good.
I thought I'd have a go myself, but I decided to take a more direct approach and look it up. I found this wikipedia article about it, it turns out that Human Resource were using a Roland Alpha Juno 2 to create that sound, a mid 80's digital/analogue hybrid synth. There's a good explanation of it's features here, importantly though, it does have a chorus unit built in, which Dan did identify as being the key feature of the sound.

Here's my re-construction attempt, I did start out to try and recreate the functions of an Alpha Juno 2, but in the end I mostly just improvised.

(
SynthDef(\aj2, {
arg freq = 440, gate = 1, lagLev = 0.01, predelay=0.01, speed=0.05, depth=0.01, ph_diff=0.5;
var width1, width2, width3, osc1, osc2, osc3, filterOut, mix, sig, modulators, numDelays = 8, lfo;
width1 = SinOsc.ar(4, 0, 0.8, 0.2).abs;
width2 = SinOsc.ar(6, 0, 0.8, 0.2).abs;
width3 = SinOsc.ar(2, 0, 0.8, 0.2).abs;
lfo = SinOsc.kr(5, 0, 5);
osc1 = Pulse.ar(Lag.kr(freq + lfo), width1);
osc2 = Pulse.ar(Lag.kr((freq/2) + lfo), 0.5);
osc3 = Pulse.ar(Lag.kr((freq*2) + lfo), width3);
mix = Mix.new([osc1, osc2, osc3]);
modulators = Array.fill(numDelays, {arg i;
       SinOsc.kr(speed * rrand(0.94, 1.06), ph_diff * i, depth, predelay);}); 
sig = DelayC.ar(mix, 0.5, modulators);  
sig = sig.sum;
Out.ar(0, sig.dup)
}).store
)

p = Pmono(\aj2, \dur, 0.24, \midinote, Pseq([40, 67, 64, 62, 62, 62, 62, 62], inf)).play;



I've got three pulse width modulated square wave oscillators here, modulated with low frequency SinOscs and some vibrato added with another low frequency SinOsc. These are put through a chorus efect taken from the ixi-audio.com tutorial, featured here before.

My patch is mostly based on random guesswork and a little bit of research, but it sounds okay, though probably not as good as Dan's scientific attempt.

Monday, June 8, 2009

2 channel bitcrusher

A small update to my bitcrusher plugin, duplicating the bitchrushed output for 'stereo'. The old version had only 1 channel output, which was to be expected as the input was only 1 channel, but this meant that in Garageband at least the signal was just one channel of a 2 channel track. In effect that the signal was panned all the way to the left with no way of centering it, duplicating the output works round this.




( var name, func, specs, componentType, componentSubtype, builder;

name = "Decimator"; // name of your plugin
func = {
| sampleRate, bitRate|

var decOut, in;

in = AudioIn.ar([1]); //Input from AU host

decOut = Decimator.ar(in, sampleRate , bitRate).dup;


Out.ar([0,1], decOut);//Output to AU host
};

specs = #[
[0, 20000 , \Linear, 10000,\Hertz ] ,
[0, 16 , \Linear, 8,\Indexed ]
];




componentType = \aufx;



componentSubtype = \DECI;


builder = AudioUnitBuilder.new(name, componentSubtype,func, specs, componentType);




builder.makeInstall;

)







Sunday, May 31, 2009

Hadron

Anonther intersting new quark,Hadron is a graphical patching environment for use in SC. Looks a bit like it may cover some of the same ground as TeaTracks, featured here previously, but I haven't used it yet. There is a very handy instructional video, which is allways nice.

Tuesday, May 19, 2009

Simple Phaser effect.

Another guitar effect, but also suitable for other things, a simple phaser. This example was adapted from the truly fantastic ixi-audio SuperCollider tutorial.

(
SynthDef(\phaser, { arg out=0, in=0;

var input,dsig, mixed;
input = SoundIn.ar(in, 1);

dsig = AllpassL.ar(input, 4, SinOsc.ar(2, 0, 0.005, 0.005), 0);
mixed = input + dsig;
Out.ar([out, out+1], mixed);
}).load(s);
)

a = Synth(\phaser, addAction:\addToTail)


Very similar to flange, but with a shorter delay time and no feedback, it's got a more otherworldly sound.

Tuesday, May 12, 2009

Creating plugins with SuperCollider AU

SuperColliderAU is an AudioUnit wrapper that allows you to embed a SuperCollider server in a AU host and either control it through SClang or package it up as a stand alone plugin.
It is of course OS X only being based around Apples AU API and there are some limitations to what you can do with it. According to the SourceForge page activity on this project seems to have stalled a bit, but it's pretty usuable in it's current state. It is possible to create effects units, but instruments with midi support don't seem to work very well. I've strugled to get anything to happen with Garage Band as a host at least, but that could just be me.
To install it you'll need to follow the instructions provided on the readme, it's very simple, just adding the main class and the server to the relevant directories, then recompile the lang and you're ready to go.
I decided to use it to create a bitcrushing plugin, making use of the Decimator Ugen, featured here before. The code is really simple, here it is.




( var name, func, specs, componentType, componentSubtype, builder;

name = "Decimator"; // name of your plugin
func = {
| sampleRate, bitRate|

var decOut, in;

in = AudioIn.ar([1]); //Input from AU host

decOut = Decimator.ar(in, sampleRate , bitRate);


Out.ar(0, decOut);//Output to AU host
};

specs = #[
[0, 20000 , \Linear, 10000,\Hertz ] ,
[0, 16 , \Linear, 8,\Indexed ]
];




componentType = \aufx;



componentSubtype = \DECI;


builder = AudioUnitBuilder.new(name, componentSubtype,func, specs, componentType);




builder.makeInstall;

)





There's a function, this is the code that the server will execute when it's embeded in the AU host, there's an array of specs that described the units, range and presets for the GUI control and a builder command that takes the relevant arguments and packages up the plugin.
The function is a very simple effect patch that takes the audio in from the AU host, decimates it and sends it to the output. The help file is a bit vague about this, but I assume that the relevant audio bus used for input from the AU host is 1, and that it needs to be sent out through 0, this is what seems to work anyway.
After that there's the control spec array, this is slightly different from the standard SC spec, but it is explained in the help file fairly well. It contains the range of values, the type and the default setting. In this case I've st up two suitable sliders to control the bit rate and sample rate, I selected index as the type for the bit rate as this gives an integer.
The builder command takes the name of the plugin, a component subtype, basically a unique identifier for the plugin, the function to be used, the spec and the component type, in this case an audio effect unit.
All being well, when this could runs it should create a plugin in the SuperCollider_f/scaudk folder, which can then be loaded into your AU host.

Here's the final product ready rolled.
It you want to gove it a try it should be fairly self explanatory, but I can't guarantee compatabilty with all AU hosts.

Saturday, May 9, 2009

Guitar effects

I've concentrated mostly on synthesis so far with my SuperCollider experiments, but today for a change I've been trying out some audio prosessing with a live input, my guitar.

To get a Mic or line input in SC you have several options, but using a SoundIn Ugen seems to be the easiest. SoundIn is a wrapper for the standard In Ugen, but it makes thigs easier by offsetting the bus index so that the first audio input is always 0. Setting up audio in in SuperCollider can be a bit of a chore, so check the help file if you can't fathom how input works.

Let's start with distortion, there's loads of ways of doing this,


{SoundIn.ar(0).fold2(0.3)}.play

using fold2 works well, as does clip

{SoundIn.ar(0).clip2(0.3)}.play

and there's also the obviously titled distort

{SoundIn.ar(0).distort}.play

The effect of this sems to be fairly subtle though. Making better use of it is this SynthDef, which comes from the Pfx help file, last used with my drum machine.

SynthDef(\distort, { arg out=0, pregain=40, amp=0.2, gate=1;

var env;

env = Linen.kr(gate, 0.05, 1, 0.1, 2);

XOut.ar(out, env, (SoundIn.ar(out, 2) * pregain).distort * amp);

}, [\ir, 0.1, 0.1, 0]).store;



You'll need to use a line like this to set the synth up.

a = Synth(\distort, addAction:\addToTail)

and if you're feeling like Alec Empire you can't beat a bit of bitcrushing for uttlerly insane fuzz.

{Decimator.ar(SoundIn.ar(0), 10000, 1)}.play



FreeVerb is an excellent choice for reverb,

{FreeVerb.ar(SoundIn.ar(0).distort)}.play


but something like flange is more difficult. This example is taken from the ixi-audio tutorial from ixi-audio.com, very slightly adapted to fit my audio input busses.

A flange effect adds a continually varying delayed signal to the original creating a phasing effect. The AllpassL in this case is the delay, with the LFpar UGen controlling the delay time, which is then mixed with the original input.



*/



(
SynthDef(\flanger, { arg out=0, in=0, delay=0.1, depth=0.08, rate=0.06, fdbk=0.0, decay=0.0;

var input, maxdelay, maxrate, dsig, mixed, local;
maxdelay = 0.013;
maxrate = 10.0;
input = SoundIn.ar(in, 1);
local = LocalIn.ar(1);
dsig = AllpassL.ar( // the delay (you could use AllpassC (put 0 in decay))
input + (local * fdbk),
maxdelay * 2,
LFPar.kr( // very similar to SinOsc (try to replace it) - Even use LFTri
rate * maxrate,
0,
depth * maxdelay,
delay * maxdelay),
decay);
mixed = input + dsig;
LocalOut.ar(mixed);
Out.ar([out, out+1], mixed);
}).load(s);
)




After evaluating that SynthDef you'll need to set up an instance of the synth and set the audio in like this,

a = Synth(\flanger, [\in, 0], addAction:\addToTail)

you can then set the various parameters of the effect like this.


a.set(\delay, 0.04)
a.set(\depth, 0.04)
a.set(\rate, 0.01)
a.set(\fdbk, 0.08)
a.set(\decay, 0.01)

.

Saturday, May 2, 2009

Crushing the Bits for Fun and Profit

Bitcrushing, reducing the sample and bit rate of audio for effect was invented by audio pioneer Bit Crusher in the early 80's and is pretty easy to implement in SuperCollider.
There are a few ways of doing it, but probably the easiest is to use the ladspa plugins that are part of the Standard SC plugin pack.
There don't seem to be any help docs with these plugins, but they aren't too hard to work out.
For now I'm using Decimator, which looks a bit like this,

Decimator.ar(in, rate, bits, mul, add )

It takes audio in, and the new sample rate and bit depths you want to use, as well as the standard mul and add args.

Here's my faithful drum machine yet again, this time with some bitcrushing.

(
SynthDef(\drums, {|out = 0, bassLevel = 0 , snareLevel = 0, hatLevel = 0, tomLevel = 0, pan1 = -1, pan2 = -1, pan3 = 1, pan4 = 1|

var env1, env2, env3, env4, bass, snare, hat, tom, bassOut, snareOut, hatOut, tomOut, mixer;
env1 = EnvGen.kr(Env.perc(0.001, 0.2, 1, -4), 1, doneAction:2);
env2 = EnvGen.kr(Env.perc(0.001, 0.5, 1, -1), 1, doneAction:2);
env3 = EnvGen.kr(Env.perc(0.002, 0.3, 1, -2), 1, doneAction:2);
env4 = EnvGen.kr(Env.perc(0.001, 0.1, 1, -5), 1, doneAction:2);
bass = SinOsc.ar(80) + Crackle.ar(1, 0.5);
bassOut = Pan2.ar(bass*env1, pan1, bassLevel);

snare = SinOsc.ar(120) - WhiteNoise.ar(0.5, 0.5);
snareOut = Pan2.ar(snare*env4, pan2, snareLevel);

hat = Klank.ar(`[ [ 6563, 9875 ],
[ 0.6, 0.5 ],
[ 0.002, 0.003] ], PinkNoise.ar(1));
hatOut = Pan2.ar(hat*env3, pan2, hatLevel);

tom = SinOsc.ar(440);
tomOut = Pan2.ar(tom*env4, pan4, tomLevel);

mixer = Mix.new([bassOut, snareOut, hatOut, tomOut]);


Out.ar(out, mixer);

}).store

)



(
SynthDef("bitcrush", { arg out;
var audio, efx, f;

audio = In.ar(20,2);
efx= Decimator.ar(audio, SinOsc.ar(0.05, 0, 9000, 1000).abs, 1);
Out.ar(out, efx);
}).memStore;
)

(
a = Synth.after(1, "bitcrush");
)


(




a = Pseq ([1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0]);
b = Pseq ([0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0]);
c = Pseq ([0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 1, 0]);
d = Pseq ([0, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 0, 1]);
e = Pwhite (0.14, 0.16, inf);

p = Pbind(
\instrument, \drums,
\out, [20],
\dur, e,
\bassLevel, Pseq ([a], inf),
\snareLevel, Pseq ([b], inf),
\hatLevel, Pseq ([c], inf),
\tomLevel, Pseq ([d], inf)

).play;


)



This uses the same method that I used previously to add an effect to a pattern, sending the output of the pattern to the bitcrusher through a bus, and making sure the order of execution is correct the with Synth.after method.
I'm using a sin oscillator to modulate the sample rate, varying it between 1000 and 10000 and using a .abs method to make sure it doesn't go negative, because negative sample rates don't work.
At lower bit rates and depths it's got a very 8-bit sound not unlike the ZX Spectrum beeper.
Buzzy.

Thursday, April 30, 2009

Daily Coding

In interesting experiment from Fredrik Olofsson, a daily coding challenge. He's been creating new SuperCollider code every day in April, all based around the same synthdef, battling Marcus Fjellström who has been doing the same thing on his blog here.


Dedication.

Wednesday, April 29, 2009

Tea Tracks

TeaTracks is a multitrack sequencer for Supercollider designed by Jan Trutzschler. If like me you're wondering how a sequencer actually slots into SuperCollider, fear not, because Dan has displayed the will of the warrior and created this intro video hosted here on archive.org .
Smashing.

Friday, April 24, 2009

DX-7 and Optimisation

A couple of things -

Discovered via Fredrik Olofsson's blog, stefan kersten's FM7 plugin. Presumably inspired by the DX-7, it's a Ugen with a bank of 6 phase modulated oscillators. Looks like fun, and if I can fathom the help file I think I'm going to do someting with it.

Also, discovered via SC Users, this handy page about optimisation.
Speedy.

Thursday, April 16, 2009

Pattern FX with Pfx

It's time to bring out my drum machine again. I've done effects with it before, but this is a new method, slightly more intuitive as you don't need to mess about with busses, it's all handled by the patterns.

(
SynthDef(\drums, {|out = 0, bassLevel = 0 , snareLevel = 0, hatLevel = 0, tomLevel = 0, pan1 = -1, pan2 = -1, pan3 = 1, pan4 = 1|

var env1, env2, env3, env4, bass, snare, hat, tom, bassOut, snareOut, hatOut, tomOut, mixer;
env1 = EnvGen.kr(Env.perc(0.001, 0.2, 1, -4), 1, doneAction:2);
env2 = EnvGen.kr(Env.perc(0.001, 0.5, 1, -1), 1, doneAction:2);
env3 = EnvGen.kr(Env.perc(0.002, 0.3, 1, -2), 1, doneAction:2);
env4 = EnvGen.kr(Env.perc(0.001, 0.1, 1, -5), 1, doneAction:2);
bass = SinOsc.ar(80) + Crackle.ar(1, 0.5);
bassOut = Pan2.ar(bass*env1, pan1, bassLevel);

snare = SinOsc.ar(120) - WhiteNoise.ar(0.5, 0.5);
snareOut = Pan2.ar(snare*env4, pan2, snareLevel);

hat = Klank.ar(`[ [ 6563, 9875 ],
[ 0.6, 0.5 ],
[ 0.002, 0.003] ], PinkNoise.ar(1));
hatOut = Pan2.ar(hat*env3, pan2, hatLevel);

tom = SinOsc.ar(440);
tomOut = Pan2.ar(tom*env4, pan4, tomLevel);

mixer = Mix.new([bassOut, snareOut, hatOut, tomOut]);


Out.ar(out, mixer);

}).store

)




Pfx, or at least it's near relation Pfxb, is currently being debated on the SC Users list so some people might be having problems with it. It seems to work fine for me with OS X, but I think it's a pretty new feature so you might need the latest SuperCollider release candidate if you want to give it a try.

This code is very closely adapted from the Pfx help file, all I've really done is bodge my drum machine sythdef onto it, but it works very well.

(
SynthDef(\echo, { arg out=0, maxdtime=0.2, dtime=0.2, decay=2, gate=1;
var env, in;
env = Linen.kr(gate, 0.05, 1, 0.1, 2);
in = In.ar(out, 2);
XOut.ar(out, env, CombL.ar(in * env, maxdtime, dtime, decay, 1, in));
}, [\ir, \ir, 0.1, 0.1, 0]).store;

SynthDef(\distort, { arg out=0, pregain=40, amp=0.2, gate=1;
var env;
env = Linen.kr(gate, 0.05, 1, 0.1, 2);
XOut.ar(out, env, (In.ar(out, 2) * pregain).distort * amp);
}, [\ir, 0.1, 0.1, 0]).store;

SynthDef(\wah, { arg out=0, gate=1;
var env, in;
env = Linen.kr(gate, 0.05, 1, 0.4, 2);
in = In.ar(out, 2);
XOut.ar(out, env, RLPF.ar(in, LinExp.kr(LFNoise1.kr(0.3), -1, 1, 200,
8000), 0.1).softclip * 0.8);
}, [\ir, 0]).store;
)


(




a = Pseq ([1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0]);
b = Pseq ([0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0]);
c = Pseq ([0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 1, 0]);
d = Pseq ([0, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 0, 1]);
e = Pwhite (0.14, 0.16, inf);

p = Pbind(
\instrument, \drums,
\dur, e,
\bassLevel, Pseq ([a], inf),
\snareLevel, Pseq ([b], inf),
\hatLevel, Pseq ([c], inf),
\tomLevel, Pseq ([d], inf)

);


q = Pfx(p, \echo, \dtime, 0.02, \decay, 0.1);

r = Pfx(q, \distort, \pregain, 20, \amp, 0.25);

o = Pfx(r, \wah).play;
)






Pfx takes a pattern and the name of the effect synthdef as arguemnts, then the names and the arguments you'd usually pass to the synthdef to control it. Pfx then takes the patern you've specified and plays it through the effect you've given it.

As shown in the above code, you can easily nest Pfx patterns to layer on more effects. I've got my original drum machine pattern defined as normal then I've passed it to a Pfx named q, then passed that new pattern on to r and then o, so I end up with my initially defined pattern passed through three effects.

Sounds dubesque.

Wednesday, April 8, 2009

Moog miditest

More work on my Moogy synth, nothing too spectacular, once again I've made use of .miditest from Dewdrop_lib. I've just invoked the miditest method and added control specs for all the parameters. This is the 'lfo' amplitude modulation version of my synth, the oscType sliders let you select from the 12 possible combinations of saw, sin and pulse waves with trinagle, sin or pulse wave amplitude modulation.

( 
SynthDef("Moog",{

arg oscType =0, oscType2 = 0, pan = 0, level = 0.5, cutoff = 500, gain = 3.3, attack = 0.1, decay = 0.1, sust = 0.7, rel = 0.2, attackf = 0.1, decayf = 0.1, sustf = 0.9, relf = 0.2, gate = 1, freq =440, lfo1Rate = 12, lfo2Rate =4, lfo3Rate = 6;

var lfo1 = Lag2.kr(LFSaw.kr(lfo1Rate), 0.1);
var lfo2 = Lag2.kr(LFTri.kr(lfo2Rate), 0.1);
var lfo3 = SinOsc.kr(lfo3Rate);

var oscArray = [Saw.ar(freq), SinOsc.ar(freq), Pulse.ar(freq),Saw.ar(freq, lfo1), SinOsc.ar(freq,0, lfo1), Pulse.ar(freq, 0.5,lfo1), Saw.ar(freq,lfo2), SinOsc.ar(freq, 0,lfo2), Pulse.ar(freq, 0.5,lfo2), Saw.ar(freq, lfo3), SinOsc.ar(freq,0, lfo3), Pulse.ar(freq, 0.5, lfo3)];
var oscArray2 = [Saw.ar(freq), SinOsc.ar(freq), Pulse.ar(freq),Saw.ar(freq, lfo1), SinOsc.ar(freq,0, lfo1), Pulse.ar(freq, 0.5,lfo1), Saw.ar(freq,lfo2), SinOsc.ar(freq, 0,lfo2), Pulse.ar(freq,0.5,lfo2), Saw.ar(freq, lfo3), SinOsc.ar(freq,0, lfo3), Pulse.ar(freq, 0.5, lfo3)];

var ampEnv = EnvGen.ar(Env.adsr(attack, decay, sust, rel), gate, doneAction:2);
var filterEnv = EnvGen.ar(Env.adsr(attackf, decayf, sustf, relf), gate, doneAction:2);
var osc1 = Select.ar(oscType, oscArray);
var osc2 = Select.ar(oscType2, oscArray2);
var fade = Pan2.ar(XFade2.ar(osc1, osc2, pan , level * ampEnv, 0));
var filter = MoogFF.ar(fade, cutoff * filterEnv, gain);
Out.ar(0,filter)

}).miditest(nil, [[0, 11, \linear, 1, 0, 1], [0, 11, \linear, 1, 0, 1], [-1, 1, \linear, 0.1, 0, 0.1], [0, 4, \linear, 0.1, 1, 0.1], [0, 10000, \linear, 0.1, 5000, 0.1],[0, 4, \linear, 0.1, 1, 0.1],[0, 4, \linear, 0.1, 1, 0.1],[0, 4, \linear, 0.1, 1, 0.1],[0, 4, \linear, 0.1, 1, 0.1],[0, 4, \linear, 0.1, 1, 0.1],[0, 4, \linear, 0.1, 1, 0.1],[0, 4, \linear, 0.1, 1, 0.1],[0, 4, \linear, 0.1, 1, 0.1],[0, 4, \linear, 0.1, 1, 0.1],nil, nil, [0, 50, \linear, 0.1, 1, 0.1],[0, 50, \linear, 0.1, 1, 0.1], [0, 50, \linear, 0.1, 1, 0.1]])
)


By the way, I use MidiKeyswith OS X to play midi notes with an ordinary alphanumeric keyboard. Very handy and SuperCollider recognises it without any trouble.
This appears to be a Windows substitute but I've no idea what it's like and it might be a pain in the arse to get working. If you're running linux you're on your own.

Friday, April 3, 2009

Complex envelopes.

I posted a phase modulation synth a while ago, but here's another one, this time using the PMOsc ugen and some custom defined envelopes.
(
SynthDef("phase", { |freq = 440, modfreq = 600,
index = 3, modphase = 2, gate = 1|
var env1 = EnvGen.kr(Env.new([0.2,0.6,0.6,0.8,0.01, 0.2, 0.1, 0.01],[0.1,0.3,0.1,0.2], 'linear', 3, 6));

var pmosc = PMOsc.ar(freq,modfreq, index , modphase);
var out = MoogFF.ar(pmosc, 5000 * env1, 0.1);
Out.ar(0, Pan2.ar(out * EnvGen.kr(Env.new([0.1,0.5,0.3,0.6,0.7,0.8,0.7,0],[0.02,0.03,0.04,0.02,0.04,0.1,0.1],'linear', 2, 1), gate, doneAction:2)))
}).miditest(nil, [nil, [0, 100, \linear, 0, 2], [0, 20, \linear, 0, 2],
[0, 4, \linear, 0, 2], nil]);

)



Defining new envelopes is reasonably easy, the help file explains most of it very well, but the release node and looping function took me a while to work out.

When you create a new envelope with Env.new you can give it a release node, this is the value of the envelope that will be held until the note is released. If you've got a envelope handyly defined like this (the first array is the list if output values, the second is the time it takes to transition to the next one)

Env.new([1,2,3,4,5] , [1,2,3,4]. 'linear', 5]
the note will be held at the first value on the list, '5' in this case, until it's released.
If you define a loop node like this

Env.new([1,2,3,4,5] , [1,2,3,4]. 'linear', 5, 1]

When the note is held it will loop through the nodes starting at the loop node and ending at the node before the release node untill the note is realsed. So in this case when the note is held, the values put out by the envelope will loop through the values '1, 2, 3, 4' as long as the note is held and then transition to '5'when it's released. I think.


And that's jazz.

Wednesday, April 1, 2009

Using BBCut and a Simple Drum Machine

I've had this code for ages but I forgot about it. 
It's my simple drum machine again, but this time put through BBCut, the beat slicing library. I've got to admit, it doesn't sound too good yet, but the principle's there.

Also it's yet another experiment with formatting, I'll find a something that works one day.
s.boot

(
SynthDef(\drums, {|out = 0, bassLevel = 0,
snareLevel = 0, hatLevel = 0, tomLevel = 0,
pan1 = 0, pan2 = 0, pan3 = 0, pan4 = 0|

var env1, env2, env3, bass, snare, hat, tom, bassOut, snareOut,
hatOut, tomOut, mixer;
env1 = EnvGen.kr(Env.perc(0.001, 0.2, 1, -4), 1, doneAction:2);
env2 = EnvGen.kr(Env.perc(0.002, 0.3, 1, -2), 1, doneAction:2);
env3 = EnvGen.kr(Env.perc(0.001, 0.1, 1, -5), 1, doneAction:2);
bass = SinOsc.ar(80) + Crackle.ar(1, 0.5);
bassOut = Pan2.ar(bass*env1, pan1, bassLevel);

snare = SinOsc.ar(40) - WhiteNoise.ar(0.5, 0.5);
snareOut = Pan2.ar(snare*env1, pan2, snareLevel);

hat = Klank.ar(`[ [ 6563, 9875 ],
[ 0.61, 0.55046827363918 ],
[ 0.0024, 0.0036 ] ], PinkNoise.ar(1));
hatOut = Pan2.ar(hat*env2, pan2, hatLevel);

tom = SinOsc.ar(440);
tomOut = Pan2.ar(tom*env3, pan4, tomLevel);

mixer = (bassOut + snareOut) + (hatOut + tomOut);


Out.ar(out, mixer);

}).store

)

TempoClock.default.tempo_(3);

(
var buf, clock, bbcutgroup;
~synthbus = Bus.audio(s,2);
~synthgroup= Group.head(Node.basicNew(s,1));

a = Pseq ([1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0]);
b = Pseq ([0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0]);
c = Pseq ([0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 1, 0]);
d = Pseq ([0, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 0, 1]);

p = Pbind(
\instrument, \drums,
\dur, 0.2,
\bassLevel, Pseq ([a], inf),
\snareLevel, Pseq ([b], inf),
\hatLevel, Pseq ([c], inf),
\tomLevel, Pseq ([d], inf),
\group, ~synthgroup,
\out, ~synthbus
).play;

clock= ExternalClock(TempoClock.default);

bbcutgroup= Group.after(~synthgroup);

Routine.run({
buf= BBCutBuffer.alloc(s,44100,2);
s.sync; //this forces a wait for the Buffer to be allocated
BBCut2(CutGroup(CutStream1(~synthbus.index, buf, 0.75, 0.01, 0.5,
-4),bbcutgroup, numChannels:2), SQPusher1.new).play(clock);
});
clock.play;
)
~synthbus.free;






The code is not to hard to follow I hope, it's adapted from the BBcut help file with help from Dan once again. The only tough bits are the groups and nodes stuff, which to be honest I don't fully understand myself. Maybe I'll post about them in more detail if I ever fathom it.

Monday, March 30, 2009

A one liner from the SC users list.

Lifted directly from the SC Users List a lovely one liner. 


// server samplerate should be 44100 

{Splay.ar(Ringz.ar(Impulse.ar([2, 1, 4], [0.1, 0.11, 0.12]), [0.1,  
0.1, 0.5])) * EnvGen.kr(Env([1, 1, 0], [120, 10]), doneAction: 2)}.play 



-- 
This work is licensed under the Creative Commons Attribution-Share  
Alike 3.0 Germany License. To view a copy of this license, visit http://creativecommons.org/licenses/by-sa/3.0/de/  
  or send a letter to Creative Commons, 171 Second Street, Suite 300,  
San Francisco, California, 94105, USA.

Tasty. 

Friday, March 27, 2009

Stolen From MCLD

Dan Stowel AKA MCLD, legendary SuperCollidist, has brought honour to his family and started twittering about SuperCollider,

Posting very short snippets of code such as this.

{t=HPZ1.kr(LFNoise0.kr(4));{Pulse.ar((t*10000+0.0001).lag(0, 0.1))}.dup+(SinOsc.ar([220, 330])*Integrator.kr(t))*0.1}.play;//

Nice,

He also drank a jar of coffee and took some E, now he's totally Wired! i.e. he's in Wired Magazine.

Nicer.

Saturday, March 21, 2009

The heterodyne is a lie

I knew there was something not quite right with my 'more accurate Theremin' code. 

It does work, but not for the reason that I thought it did. Here's the original code, 

(
{
var mouse = MouseX.kr(100000,110000,1);

BPF.ar((SinOsc.ar(100000) * SinOsc.ar(mouse)), mouse - 100000, 0.2);


}.scope

)

I should have known, 100Khz is waaaay above the maximum frequency that can be produced by a standard sampling rate of 40-some thousand hz,  according to Nyquist-Shannon sampling theorem.

Simply put, aliasing causes the code to output the sound, the rest is erm , complicated. 

A better explanation can be found on the SC user list where I asked about it.  

Qapla!

Friday, March 20, 2009

Midievalism - Midi control

James Harkins' Dewdrop_lib , has an armada of wonderful surprises for the budding SuperCollidist, including several classes for dealing with midi stuff. 

This is part of the official extensions package, but you'll still need to install the quark and recompile the language for it to work. 

A useful method is .miditest, which you can append to any synthdef and automatically create a simple midi interface and a gui to control it. It's very simple to use and the help file is easy to understand. 

Here's an example of a simple phase modulating synth I made. The synth itself is pretty simple, I'm just using sin oscillators to modulate the phase of another SinOsc. 


(
SynthDef("phase", { |freq = 440, pm1f = 2,
pm2f = 2, pm3f = 2, gate = 1|

var out =
SinOsc.ar(freq, SinOsc.ar(pm1f, SinOsc.ar(2, SinOsc.ar(pm2f,
SinOsc.ar(pm3f))))*2pi);
Out.ar(0, Pan2.ar(out * EnvGen.kr(Env.adsr, gate, doneAction:2)))
}).miditest(nil, [nil, [0, 50, \linear, 0, 2], [0, 50, \linear, 0, 2],
[0, 50, \linear, 0, 2], nil]);

)



To make miditest work correctly you'll need to supply it control specs for each of the parameters of your synth in the second argument. If any of your paremeters don't need to be controlled by the gui, putting nil as the argument will cause it to be left out.

Thursday, March 19, 2009

Some Birthday Code

It's was my birthday recently and I was lucky enough to  get this birthday code from my old friend and Finchley's premier SuperCollidist, Dan. 

I was going to post the code on here, but it's pretty long and I can't get it to format nicely at the moment, so you'll have to make do with the .scd file in the link above. 

Happy Birthday to me.  

Thursday, March 12, 2009

Lag in the Time of Cholera.

I updated my Moog synth to use low frequency oscillators to modulate the amplitude a while ago , finding a new use for the Lag Ugen in the process. 

Here's the relevant bit, 

var lfo1 = Lag2.kr(LFSaw.kr(lfo1Rate), 0.1);
var lfo2 = Lag2.kr(LFTri.kr(lfo2Rate), 0.1);
var lfo3 = Lag2.kr(SinOsc.kr(lfo3Rate), 0.1);

When I put it onto my oscillator array the lfo modulates the multipler of the Ugens. 

Why the Lag?

Without it it sounds pretty bad, the sudden changes in amplitude cause a nasty clicking sound. Lag smoothes out the waveform so that the change is more gradual and sounds much better. 

Tuesday, March 10, 2009

The Joy of Frequecy Modulation

Here's a simple frequency modulating synth def I came up with, 


(
SynthDef(\efemer, {|freq = 240, modlev1 = 100, modlev2 = 100,
carlev = 1, pulsewidth = 0.5, mod1osctype = 1,
mod2osctype = 2 , carosctype = 2, sinphase = 0|

var mod1array = [Saw.ar(freq, modlev1), Pulse.ar(freq, pulsewidth, modlev1),
SinOsc.ar(freq, sinphase, modlev1)];

var mod1 = Select.ar(mod1osctype, mod1array);

var mod2array = [Saw.ar(freq + mod1, modlev2),
Pulse.ar(freq + mod1, pulsewidth, modlev2),
SinOsc.ar(freq + mod1, sinphase, modlev2)];

var mod2 = Select.ar(mod2osctype, mod2array);

var cararray = [Saw.ar(freq + mod2, carlev),
Pulse.ar(freq + mod2, pulsewidth, carlev),
SinOsc.ar(freq = mod2, 1, carlev)];

var car = Select.ar(carosctype, cararray);
var env = EnvGen.ar( Env.perc(0.11, 1), doneAction: 2);

Out.ar(0, Pan2.ar(car * env))
}).store
)


(
Pbind(\instrument, "efemer",
\mod1osctype, Pseq([0,1,2], inf),
\freq, Pseq([220,330,440,550], inf),
\dur, 0.3
).play
)



I think it need's some work.

Saturday, March 7, 2009

A More Accurate Theremin (possibly)

One of the weird things about the Theremin is the way it creates sound. Rather than generating sound directly from a voltage controlled oscillator like later analogue synths, it creates sound via the heterodyne principle. Why I have no idea, but it does.  Before I go any further I sould point out that  I learned everything I know about signal processing from Wikipedia, so my explanations here might be a bit garbled. 

In a Theremin there are two high frequency oscilators, one at a fixed frequency and one at a frequency controlled by the movement of the players hand.  These two signals are multiplied by each other, which gives two new frequecies, one at the sum of the original two and one at the diffrence between them.  The difference of these two is the audio signal that is output, so 

120000hz * 115000 = 235000hz and 5000hz. 

I think. 

So just for the hell of it I decided to try doing this in SuperCollider and surprise, it works!

(
{
var mouse = MouseX.kr(100000,110000,1);

BPF.ar((SinOsc.ar(100000) * SinOsc.ar(mouse)), mouse - 100000, 0.2);


}.scope

)

I've put it through a band pass filter that follows the audio frequency in an attempt to filter out what I can only assume are sidebands or aliasing or something. It doesn't quite work completely, but it's better than nothing.

The Ondes Martenot used heterodyning too so I did the same thing with my simple version of it. 

(
{



var mouse = MouseX.kr(100000,110000,1);

w = SCWindow.new("I catch keystrokes");
w.front; 



FreeVerb.ar(BPF.ar((SinOsc.ar(100000, 0, KeyState.kr(36, 0, MouseY.kr(1,0)) ) * SinOsc.ar(mouse)), mouse - 100000, 0.2), 0.5, 0.3);


}.freqscope

)

And that's Jazz. 

Wednesday, March 4, 2009

Adding LFO modulation

Another addition to my Moog style synth, LFO amplitude modulation.

Here's the code,

(
SynthDef("Moog",{

arg oscType =0, oscType2 = 0, pan = 0, level = 0.5, cutoff = 500, gain = 3.3,
attack = 0.1, decay = 0.1, sust = 0.7, rel = 0.2, attackf = 0.1, decayf = 0.1,
sustf = 0.9, relf = 0.2, gate = 1, freq =440, lagLev = 0.2, lfo1Rate = 12,
lfo2Rate =4, lfo3Rate = 6;

var lfo1 = Lag2.kr(LFSaw.kr(lfo1Rate), 0.1);
var lfo2 = Lag2.kr(LFTri.kr(lfo2Rate), 0.1);
var lfo3 = Lag2.kr(SinOsc.kr(lfo3Rate), 0.1);

var oscArray = [Saw.ar(Lag.kr(freq, lagLev)), SinOsc.ar(Lag.kr(freq, lagLev)),
Pulse.ar(Lag.kr(freq, lagLev)),Saw.ar(Lag.kr(freq, lagLev), lfo1),
SinOsc.ar(Lag.kr(freq, lagLev),0, lfo1), Pulse.ar(Lag.kr(freq, lagLev), lfo1),
Saw.ar(Lag.kr(freq, lagLev),lfo2), SinOsc.ar(Lag.kr(freq, lagLev), 0,lfo2),
Pulse.ar(Lag.kr(freq, lagLev), lfo2), Saw.ar(Lag.kr(freq, lagLev), lfo3),
SinOsc.ar(Lag.kr(freq, lagLev), 0, lfo3), Pulse.ar(Lag.kr(freq, lagLev), 0, lfo3)];

var oscArray2 = [Saw.ar(Lag.kr(freq, lagLev)), SinOsc.ar(Lag.kr(freq, lagLev)),
Pulse.ar(Lag.kr(freq, lagLev)),Saw.ar(Lag.kr(freq, lagLev), lfo1),
SinOsc.ar(Lag.kr(freq, lagLev),0, lfo1), Pulse.ar(Lag.kr(freq, lagLev), lfo1),
Saw.ar(Lag.kr(freq, lagLev),lfo2), SinOsc.ar(Lag.kr(freq, lagLev), 0,lfo2),
Pulse.ar(Lag.kr(freq, lagLev), lfo2), Saw.ar(Lag.kr(freq, lagLev), lfo3),
SinOsc.ar(Lag.kr(freq, lagLev), 0, lfo3), Pulse.ar(Lag.kr(freq, lagLev), 0, lfo3)];

var ampEnv = EnvGen.ar(Env.adsr(attack, decay, sust, rel), gate, doneAction:2);
var filterEnv = EnvGen.ar(Env.adsr(attackf, decayf, sustf, relf), gate, doneAction:2);
var osc1 = Select.ar(oscType, oscArray);
var osc2 = Select.ar(oscType2, oscArray2);
var fade = Pan2.ar(XFade2.ar(osc1, osc2, pan , level * ampEnv, 0));
var filter = MoogFF.ar(fade, cutoff * filterEnv, gain);
Out.ar(0,filter)

}).store
)







And here's a download link

I'll talk a bit more about this later.

Hail Atlantis.

Monday, March 2, 2009

A Simple Ondes Martenot

The Ondes Martenot is a very early electronic synthesiser created in the late 1920's by Maurice Martenot. It's generates sound in a similar way to the Theremin, but is much more sophisticated, having several waveforms and filters.

The unique thing about the Ondes Martenot is it's control method. Like many later monophonic synths it's got a piano style keyboard,  but it also has a unique finger ring controller. This is a ring connected to a pice of wire worn on the players right index finger, as the ring moves back and forth it alters the pitch of the synth. 

Whether the player is using the keyboard or the ring controller, the sounds themselves are triggered by a an experession key, a small glass button to the left of the keyboard. This is an analogue control, the more the button is pressed down the greater the amplitude. 

Here's a YouTube Video of one being played, which should make things clearer. 


You've probably seen a simple Theremin implemented in SuperCollider several times, it's a classic bit of code, the audio equivalent of 'Hello World',  here it is again in all it's glory. 

{SinOsc.ar(MouseX.kr(20,20000))}.play

To extend this a little bit I decided to create a simple Ondes Martenot style synth, using a key on the keyboard as an expression key. Sadly this is only a binary control, it's either on or off, unlike the orginal, instead I've used the mouse y position to control the level, with the key just acting as a switch. 

Here's the code. 


( 
w = SCWindow.new("I catch keystrokes");w.front;
{ FreeVerb.ar(SinOsc.ar(MouseX.kr(20, 20000, 1), 0,
KeyState.kr(36, 0, MouseY.kr(1,0))),
0.5, 0.3, 0.1) }.play;
)

This firstly creates a standard window, as the title suggests, to catch keystrokes. I got this idea from the help file on keyboard input, it serves no purpose other than as a safe place to direct keyboard input to.

A keyState ugen provides the extra control input to the SinOsc. A KeyState takes 3 arguments, the key number it is to respond to, the output when the key is not pressed and the output when it is. As you can see Im using this to control the amplitude of a SinOsc, when key 36 (the enter key) is pressed it outputs the mouse y value as the multiplier for the osc. 

Using the a key to trigger the sound gives a bit more expressive power than the standard Theremin, plus it's great for doing impressions of the Clangers.

 



Sunday, March 1, 2009

Another try with the 303 code.

It's been pointed out that blogger's formatting has mangled some of the code here. This is an attempt to rectify that. 

If anyone has any trouble copy and pasting this please let me know. 

(


s = Server.internal;


SynthDef("sc303", { arg out=0, freq=440, wave=0, ctf=100, res=0.2,


sus=0, dec=1.0, env=1000, gate=0, vol=0.2;


var filEnv, volEnv, waves;


// can't use adsr with exp curve???


//volEnv = EnvGen.ar(Env.adsr(1, 0, 1, dec, vol, 'exp'), In.kr(bus));


volEnv = EnvGen.ar(Env.new([10e-10, 1, 1, 10e-10], [0.01, sus, dec], 'exp'), gate);


filEnv = EnvGen.ar(Env.new([10e-10, 1, 10e-10], [0.01, dec], 'exp'), gate);



waves = [Saw.ar(freq, volEnv), Pulse.ar(freq, 0.5, volEnv)];



Out.ar(out, RLPF.ar( Select.ar(wave, waves), ctf + (filEnv * env), res).dup * vol);


}).send(s);


)




/*


s.sendMsg("s_new", "sc303", 3000, 0, 0);


s.sendMsg("n_set", 3000, 'gate', 1, 'freq', 220);


s.sendMsg("n_set", 3000, 'gate', 0);


s.sendMsg("n_set", 3000, 'dec', 1);




s.sendMsg("n_free", ~node)


*/




// make GUI!


(


~bpm = 140;


~step = 0;


// set default note ons


~defaultNoteOns = Array.fill(16,1);


// this is the note on array that will be used in the sequencer routine


~noteOns = ~defaultNoteOns;




// set root


~root = 36;




// set default pitches


~defaultPitches = [ 0, 12, 0, 0, -12, 0, 0, 0, -12, 0, 12, 0, 3, -5, 2, 0 ];


// this is the pitch array that will be used in the sequencer routine


~pitches = ~defaultPitches;




~tStepLED = Array.new; // the step indicator LEDs as an array


~sNoteOn = Array.new; // the note-on sliders as an array


~tNoteOn = Array.new; // the note-on value boxes as an array


~sPitch = Array.new; // the pitch sliders as an array


~tPitch = Array.new; // the pitch value boxes as an array




w = SCWindow("SC-303", Rect(128, 64, 510, 320));


w.view.background = Color.white;


w.front;




~layoutLeft = SCCompositeView(w, Rect(0, 0, 400, 320)); // make the left pane of the window


~layoutLeft.decorator = FlowLayout(~layoutLeft.bounds); // auto-arrange the elements


~layoutRight = SCCompositeView(w, Rect(400, 0, 400, 120));


~layoutRight.decorator = FlowLayout(~layoutRight.bounds);




// make transport controls




// Tempo


SCStaticText(~layoutRight, Rect(0,0, 45, 14)).string_("BPM: ");


~tBpm = SCNumberBox(~layoutRight, Rect(0,0, 45, 14))


.string_(~bpm)


.action_({ arg item;


~bpm = item.value;


~sBpm.value_((~bpm-40)/200);


});




~layoutRight.decorator.nextLine;


~sBpm = SCSlider(~layoutRight, Rect(0,0, 100, 20))


.background_(Color.new(0.8,0.8,0.8))


.value_((~bpm-40)/200)


.action_({ arg sldr;


~bpm = ((sldr.value)*200) + 40;


~tBpm.string_(~bpm);


});




~layoutRight.decorator.nextLine;




// Reset


SCButton(~layoutRight, Rect(0,0,45,20))


.states_([["|<"]])


.action_({ arg item;


~tStepLED[~step].background_(Color.new(0.6,0,0));


~step = 0;


~tStepLED[0].background_(Color.new(1,0,0));


});




// Play/Pause


SCButton(~layoutRight, Rect(0,0,45,20))


.states_([[">"],["||"]])


.action_({ arg item;


if( item.value == 0, {


~seqr.stop;


},{


~seqr.reset;


~seqr.play;


});


});




~layoutRight.decorator.nextLine;




// Root note


SCStaticText(~layoutRight, Rect(0,0, 45, 14)).string_("Root: ");


~tRoot = SCStaticText(~layoutRight, Rect(0,0, 45, 14)).string_(~root);


~layoutRight.decorator.nextLine;


SCSlider(~layoutRight, Rect(0,0, 100, 20))


.background_(Color.new(0.8,0.8,0.8))


.value_((~root-24)/36)


.step_(1/48)


.action_({ arg sldr;


~root = ((sldr.value)*36) + 24;


~tRoot.string_(~root);


});





// make synth control labels


SCStaticText(~layoutLeft, Rect(0,0, 45, 14)).string_("Wave");


SCStaticText(~layoutLeft, Rect(0,0, 80, 14)).string_("Cutoff");


SCStaticText(~layoutLeft, Rect(0,0, 80, 14)).string_("Resonance");


SCStaticText(~layoutLeft, Rect(0,0, 80, 14)).string_("Decay");


SCStaticText(~layoutLeft, Rect(0,0, 80, 14)).string_("Envelope");


~layoutLeft.decorator.nextLine;




// make the synth controls


// wave-type


SCButton(~layoutLeft, Rect(0,0,45,20))


.states_([["Saw"],["Square"]])


.action_({ arg item;


s.sendMsg("n_set", ~node, 'wave', item.value);


});


// cut-off frequency


SCSlider(~layoutLeft, Rect(0,0, 80, 20))


.background_(Color.new(0.8,0.8,0.8))


.action_({ arg sldr;


s.sendMsg("n_set", ~node, 'ctf', ((sldr.value)*(10000-100))+100); // modify synth param


});



// resonance amt


SCSlider(~layoutLeft, Rect(0,0, 80, 20))


.background_(Color.new(0.8,0.8,0.8))


.action_({ arg sldr;


s.sendMsg("n_set", ~node, 'res', (1-sldr.value)*(0.97)+0.03); // modify synth param


});



// decay amt


SCSlider(~layoutLeft, Rect(0,0, 80, 20))


.background_(Color.new(0.8,0.8,0.8))


.action_({ arg sldr;


s.sendMsg("n_set", ~node, 'dec', (sldr.value)*2); // modify synth param


});



// envelope amt


SCSlider(~layoutLeft, Rect(0,0, 80, 20))


.background_(Color.new(0.8,0.8,0.8))


.action_({ arg sldr;


s.sendMsg("n_set", ~node, 'env', (sldr.value)*10000); // modify synth param


});




~layoutLeft.decorator.nextLine;




// make step LEDs


16.do({ arg i;


~tStepLED = ~tStepLED.add(


SCStaticText(~layoutLeft, Rect(0,0,20,12))


.background_(Color.new(0.6,0,0))


.stringColor_(Color.white)


.string_(i+1)


);


});


~tStepLED[~step].background_(Color.new(1,0,0));


~layoutLeft.decorator.nextLine;




// make the note-on sliders


16.do({ arg i;


~sNoteOn = ~sNoteOn.add(


SCSlider(~layoutLeft, Rect(0,0,20,60)) // position doesn't matter; width, height do


.background_(Color.new(0,0,0.7)) // color the slider


.step_(0.1) // values are stepped by 0.1


.value_(~noteOns[i]) // assign it its value from the note on array


.action_({ arg sldr; // when the slider is moved...


~noteOns[i] = sldr.value; // ... update note-on array


~tNoteOn[i].string = sldr.value; // ... update its value box


// ...change color of value box


~tNoteOn[i].background = Color.new((1-~noteOns[i]),1,(1-~noteOns[i]));


})


);


});


~layoutLeft.decorator.nextLine;




// make the note-on value boxes


16.do({ arg i;


~tNoteOn = ~tNoteOn.add(


SCStaticText(~layoutLeft, Rect(0,0,20,20))


.background_(Color.new((1-~noteOns[i]),1,(1-~noteOns[i])))


.string_(~sNoteOn[i].value)


.align_(0)


.action_({ arg text;


text.string.postln;


})


);


});


~layoutLeft.decorator.nextLine;




// make the pitch sliders


16.do({ arg i;


~sPitch = ~sPitch.add(


SCSlider(~layoutLeft, Rect(0,0,20,120))


.value_((~pitches[i]+12)/24)


.step_(1/24)


.background_(Color.new(0.8,0.8,0.8))


.action_({ arg item;


~pitches[i] = ((item.value * 24)-12);


~tPitch[i].string = ((item.value * 24)-12);


})


);


});


~layoutLeft.decorator.nextLine;




// make the pitch value boxes


16.do({ arg i;


~tPitch = ~tPitch.add(


SCStaticText(~layoutLeft, Rect(0,0,20,20))


.background_(Color.white)


.string_(~pitches[i].value)


.align_(0)


);


});


/*~tempButt = SCButton(w, Rect(0,0,60,60))


.states_([


["0", Color.red, Color.black]


]);*/




)




// do other stuff




(


~node = s.nextNodeID;




// start the synth


s.sendMsg("s_new", "sc303", ~node, 1, 1);




// make the step sequencer


~seqr = Routine.new({


loop({



// turn on LED


{~tStepLED[~step].background_(Color.new(1,0,0)); /*(~step.asString + "on").postln;*/}.defer;



if(~noteOns[~step].coin, {


// play note!


// this is out of sync


// s.sendMsg("n_set", ~node, 'gate', 1, 'freq', (~pitches[~step]+~root).midicps);


// (7.5/~bpm).wait;


// s.sendMsg("n_set", ~node, 'gate', 0);


// (7.5/~bpm).wait;



// buffer playback on server (adds start/stop delay, but better inter-note timing)


s.sendBundle(s.latency, ["n_set", ~node, 'gate', 1, 'freq', (~pitches[~step]+~root).midicps]);


(7.5/~bpm).wait;


s.sendBundle(s.latency, ["n_set", ~node, 'gate', 0]);


(7.5/~bpm).wait;


},{


// it's a rest


(15/~bpm).wait;


});



// turn off LED


{~tStepLED[(~step-1)%16].background_(Color.new(0.6,0,0)); /*(((~step-1)%16).asString + "off").postln;*/}.defer;



~step = (~step + 1)%16;



});


});


)




~seqr.play;


~seqr.stop;


~seqr.reset;


~pitches