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.