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