SuperCollider

De Pontão Nós Digitais
Ir para navegaçãoIr para pesquisar

Tutorial de Introdução à SC

Instalando e configurando

Atualmente SC possui pacotes para a versão 3.4. É recomendado que você comece por usar a versão para Ubuntu/Debian e depois se aventure na versão instável disponível no repositório Git (para tal, veja o Anexo A no final desse tutorial).

Primeiro adicione o PPA seguindo as instruções em https://launchpad.net/~supercollider/+archive/ppa:

   sudo add-apt-repository ppa:supercollider/ppa
   sudo aptitude update

e depois:

   sudo aptitude install supercollider supercollider-doc supercollider-quarks supercollider-gedit

Agora abra o Gedit e vá em Editar -> Preferências -> Plugins e habilite o SCED (SuperCollider EDitor). Assim você poderá usar o Gedit para controlar o SuperCollider, enviando para ele comandos na linguagem do SuperCollider.

Para começar a editar código em SC, vá em Ferramentas e depois Supercollider Mode. Pronto, o Supercollider estará executando em um painel do Gedit. É no painel de cima que começaremos a escrever o código.

Antes de começar a programar... entendendo a arquitetura de SuperCollider

SC é baseado em uma arquitetura cliente-servidor. O servidor é o responsável por acessar o dispositivo de áudio e fazer todo o trabalho de processamento de áudio, e é chamado de scsynth. Como cliente podemos usar qualquer aplicativo que suporte o protocolo OSC. O SC vem com um cliente padrão, chamado sclang. É com ele que iremos programar o scsynth, usando uma linguagem específica, a linguagem SuperCollider.

Portanto, temos o seguinte cenário:

   scsynth  <----OSC----> sclang
      ^---.
          `------OSC----> qualquer programa que suporte OSC (Python, Processing, PD, ...)

A Linguagem SuperCollider

Antes de tudo, é necessário sempre iniciar o servidor de SC (SC Server). Para isso:

// Iniciando o servidor
s = Server.local;
s.boot;

Com isso fizemos o SC Lang conectar no SC Server e inicializamos ele. É importante lembrar que você terá que iniciar o servidor JACK de áudio. Para isso, recomendo instalar o qjackctl (sudo aptitude install qjackctl), executá-lo e clicar em Start.

A sintaxe de SC será familiar para quem já programou em C, Java e Smalltalk. SC Lang é uma linguagem orientada a objetos, fortemente baseada em Smalltalk e com algumas noções de Scheme.

Para não perder o costume, vamos a um "Hello World" em SC:

"Hello, world!".postln;

Para avaliar o comando acima, digite CTRL + E enquanto seu cursor estiver na linha da expressão. Grave esse comando, pois usaremos ele para executar (avaliar) qualquer comando em SC.

Vamos tirar um primeiro som de SC, apenas para testar. Novamente, copie o código abaixo no seu Gedit e aperte CTRL+E:

{[SinOsc.ar(660, 0, 0.2), SinOsc.ar(662, 0, 0.2)]}.play(s);

Para para de tocar todos os sons, aperte ESC.

Esse é o básico do uso de SC: iniciar o servidor, avaliar as expressões (comandos) com CTRL+E e parar todos os sons com ESC.

= Objetos

// Tudo em SC é um objeto. Todo objeto tem um valor.
2.value;
(2.5).value;
{ "foo".postln; }.value;
"foo".value;

//
// Funções
//

f = { "foo bar".postln; };
f;
f.value;

// Uma função anônima sendo avaliada:
{ "foo bar".post; }.value;

// Avaliar uma função, é, como o nome sugere, saber seu valor

// Funções podem ter argumentos

f = { arg foo;
      foo.value * 2;
};

f.value(3);

// Podemos definir argumentos de modo alternativo, usando | var |

f = { | a, b |
      a + b;
};

f.value(2, 3);

// Keywords
g = { arg a, b, c;
      a * b + c;
};

g.value(b: 4, a: 3, c: 2);

// Valores de argumentos padrão
h = { arg foo, bar = 1;
      foo / bar;
};

h.value(2);
h.value(2, 1);

// Variáveis dentro de funções

f = { arg foo;
      var bar = 2;
      foo + bar;
};

f.value(2);

//
// Variáveis padrões
//

// Perceba que todas as variáveis que usamos até agora (f, g, h, s, ...)
// não foram previamente declaradas por nós (usando var). Porquê?!
// Quando SC é inicializado, ele declara as variáveis de a-z para testes.
// Existem variáveis definidas também por motivo especial, como o caso de s,
// que aponta sempre para o servidor local de scsynth.

// Tente avaliar:
a;
b;
c;
s;
foo; // somente essa não é declarada ainda...

//
// Blocos de código
//

// Para facilitar a avaliação (sem ter que ficar selecionando blocos de código)
// é uma prática comum em SC delimitar blocos que iremos avaliar durante
// a execução do programa com parênteses

// É muito mais prático avaliar isso:

( // <- sempre deixe o cursor no início do bloco para avaliar
f = { | a, b |
      (a * b) / 10;
};
)

f.value(2,3)

// ... do que isso:

f = { |a, b|
      (a * b) / 10;
}

//
// Arrays
//

a = [2, 3.3, "foo"];
a[0];
a[1] = 3.4;
a[2];
a.at(2);
a.choose;

// 
// Fazendo Barulho
//

{ SinOsc.ar(222, 0, 0.5); }.play;

(
{  var ampOsc;
   ampOsc = SinOsc.kr(0.5, 1.5pi, 0.5, 0.5);
   SinOsc.ar(440, 0, ampOsc);
}.play;
)

// Podemos usar arrays para especificar múltiplos canais de áudio

{ [SinOsc.ar(440, 0, 0.2), SinOsc.ar(441, 0, 0.2)] }.play;

// ou...

{ SinOsc.ar([440, 442], 0, 0.2) }.play;

// Alguns UGens se aproveitam disso

{ Pan2.ar(PinkNoise.ar(0.2), SinOsc.kr(0.5)) }.play;

// Podemos também somar ondas (mixing)

{ SinOsc.ar(440, 0, 0.3) + SinOsc.ar(441, 0, 0.3) }.play;

// o que é equivalente a mixá-as, ou usar Mix:

{ Mix([SinOsc.ar(440, 0, 0.3), SinOsc.ar(441, 0, 0.3)]) }.play;

// É interessante plotar as ondas para ter uma ideia de sua forma

{ Mix([SinOsc.ar(60, 0, 0.2), Saw.ar(80, 0.2)]) }.plot;

// Usando MouseX

(

        { var input, kernel;
                
        input=AudioIn.ar(1);    
        kernel= Mix.ar(LFSaw.ar([300,500,800,1000]*MouseX.kr(1.0,2.0),0,1.0));

        //must have power of two framesize
        Out.ar(0,Convolution.ar(input,kernel, 1024, 0.5));
         }.play;

)

(
{ var freq;

freq = [[660, 880], [440, 660], 1320, 880].choose;

SinOsc.ar(freq, 0, 0.2) * SinOsc.ar(20, 0, 0.2); 

}.plot;

)

{ Pan2.ar(PinkNoise.ar(0.2), MouseX.kr(-1.0, 1.0)) }.play;
a = SynthDef(\sinetest, {arg out = 1, freq = 440; Out.ar(out, SinOsc.ar(freq))}) ;
z = SynthDefAutogui(\sinetest, scopeOn:false) ; 

//
// Classes que abstraem o server (scsynth)
//

{ SinOsc.ar(440, 0, 0.5) }.play;

SynthDef("meuSinOsc", { Out.ar(0, SinOsc.ar(440, 0, 0.5)) }).play;

SynthDef("meuSinOsc", { Out.ar(0, SinOsc.ar(440, 0, 0.5)) }).send(s);
x = Synth("meuSinOsc");
y = Synth("meuSinOsc");
x.free;
y.free;

(
SynthDef.new("meuSinOscStereo", { var outArray;
      outArray = [SinOsc.ar(440, 0, 0.5), SinOsc.ar(440, 0, 0.5)];
      Out.ar(0, outArray);
}).play;
)

// Quando trabalhamos com SynthDef, é importante definir o segundo argumento,
// o 'grafo de UGens' como sendo ele todo composto por UGens
// Veja que até a geração de números aleatórios é dada por um UGen: Rand

SynthDef("meuSinOscRandomico", { Out.ar(0, SinOsc.ar(Rand(440, 660), 0, 0.5)) }).send;
x = Synth("meuSinOscRandomico")
x.free

// Outro exemplo de SynthDef, agora com argumentos (muito mais flexível!)

(
SynthDef("sin-param", { | freq=440, out=0 |
    Out.ar(out, SinOsc.ar(freq, 0, 0.5))
}).send;
)

x = Synth("sin-param"); // pega os argumentos padrões
x = Synth("sin-param", ["freq", 660, "out", 0]); // especificando parâmetros
x.free

// Podemos também usar o método set() para alterar argumentos
// Muito interessante para livecoding!

x.set("freq", 1000);
x.set("out", 1)
x.set("out", 0)
x.set("freq", 660);

//
// Símbolos versus Strings
//

// Podemos usar símbolos no lugar de strings

// Repare no símbolo usado: 'meu-sin' ao invés da string "meu-sin"

(
SynthDef('meu-sin', { Out.ar(0, SinOsc.ar(440, 0, 0.5)) }).send;
)

// ou... (repare no símbolo usado: \meuSin)

(
SynthDef(\meuSin, { Out.ar(0, SinOsc.ar(440, 0, 0.5)) }).send;
)

// Símbolos portanto são representados como 'meu simbolo' ou \meuSimbolo

//
// Busses
// 

// Pense em um bus como uma forma de acessar o IO de áudio e rotear
// o áudio entre bus e synths

// Bus de saída
Out.ar(0, SinOsc.ar(440, 0, 0.5));
// Bus de entrada
In.ar(0, 1);

// Bus genérico
Bus.control(s, 2) // cria dois busses de controle
Bus.audio(s)      // cria um bus de áudio (1 é padrão)

// Exemplo mais completo de uso de Busses

(
// the arg direct will control the proportion of direct to processed signal
SynthDef("tutorial-DecayPink", { arg outBus = 0, effectBus, direct = 0.5;
    var source;
    // Decaying pulses of PinkNoise. We'll add reverb later.
    source = Decay2.ar(Impulse.ar(1, 0.25), 0.01, 0.2, PinkNoise.ar);
    // this will be our main output
    Out.ar(outBus, source * direct);
    // this will be our effects output
    Out.ar(effectBus, source * (1 - direct));
}).send(s);

SynthDef("tutorial-DecaySin", { arg outBus = 0, effectBus, direct = 0.5;
    var source;
    // Decaying pulses of a modulating Sine wave. We'll add reverb later.
    source = Decay2.ar(Impulse.ar(0.3, 0.25), 0.3, 1, SinOsc.ar(SinOsc.kr(0.2, 0, 110, 440)));
    // this will be our main output
    Out.ar(outBus, source * direct);
    // this will be our effects output
    Out.ar(effectBus, source * (1 - direct));
}).send(s);

SynthDef("tutorial-Reverb", { arg outBus = 0, inBus;
    var input;
    input = In.ar(inBus, 1);
    // a low rent reverb
    // aNumber.do will evaluate it's function argument a corresponding number of times
    // {}.dup(n) will evaluate the function n times, and return an Array of the results
    // The default for n is 2, so this makes a stereo reverb
    16.do({ input = AllpassC.ar(input, 0.04, { Rand(0.001,0.04) }.dup, 3)});
    Out.ar(outBus, input);
}).send(s);

b = Bus.audio(s,1); // this will be our effects bus
)

(
x = Synth.new("tutorial-Reverb", [\inBus, b]);
y = Synth.before(x, "tutorial-DecayPink", [\effectBus, b]);
z = Synth.before(x, "tutorial-DecaySin", [\effectBus, b, \outBus, 1]);
)

// Change the balance of wet to dry

y.set(\direct, 1); // only direct PinkNoise
z.set(\direct, 1); // only direct Sine wave
y.set(\direct, 0); // only reverberated PinkNoise
z.set(\direct, 0); // only reverberated Sine wave
x.free; y.free; z.free; b.free;

//
// Groups
//

//
// Buffers
//

// Criamos um buffer de 100 frames e 2 canais
b = Buffer.alloc(s, 100, 2);
// Liberamos memória
b.free;

// Podemos ler um arquivo para um Buffer
b = Buffer.read(s, "/home/vilson/Samples/drums1.wav");

// E tocá-lo
(
x = SynthDef("player", { | out=0, bufnum |
        Out.ar(out,
               PlayBuf.ar(1, bufnum, BufRateScale.kr(bufnum))
        )
}).play(s, [\bufnum, b]);
)
x.free; b.free;

// ou simplesmente:
b.play;
b.play(true); // toca em loop
b.free;
// e também visualizá-lo:
b.plot;

// Podemos usar buffer com qualquer tipo de dado (útil para waveshapping):

b = Buffer.alloc(s, 512, 1);

b.cheby([1,0,1,1,0,1]);

(
x = play({ Shaper.ar(b, SinOsc.ar(300, 0, Line.kr(0,1,6)), 0.5) });
)

x.free; b.free; 

//
// Agendando eventos
//

// A música só acontece no tempo. Em SC, controlamos o "quando" agendando
// coisas em relógios.

// Existem 2 relógios importantes em SC: TempoClock, SystemClock
// TempoClock é mais interessante para eventos musicais (respeitam compasso)
// SystemClock é mais interessante para eventos mundanos (em segundos)

// Agendamos para mostrar Hello daqui a 5 segundos
SystemClock.sched(5, { "Hello".postln })
// Quantos segundos até agora?
SystemClock.seconds;
// Quantas batidas até agora? (do TempoClock padrão, podem haver quantos TempoClocks desejarmos)
TempoClock.default.beats;
// Podemos usar schedAbs() para especificar quando exatamente (absoluto) queremos que algo seja executado
(
TempoClock.default.tempo = 2;
var timeNow = TempoClock.default.beats;
TempoClock.default.schedAbs(timeNow + 5, { "foo".postln; });
)

//
// Agengando eventos com Routines e Tasks
//

//
// Sequenciando com Patterns
//


p = Pseq([60, 72, 71, 67, 69, 71, 72, 60, 69, 67], 1);
r = p.asStream;
while { (m = r.next).notNil } { m.postln };

(
var midi, dur;
midi = Pseq([60, 72, 71, 67, 69, 71, 72, 60, 69, 67], 1).asStream;
dur = Pseq([2, 2, 1, 0.5, 0.5, 1, 1, 2, 2, 3], 1).asStream;

SynthDef(\smooth, { |freq = 440, sustain = 1, amp = 0.5|
    var sig;
    sig = SinOsc.ar(freq, 0, amp) * EnvGen.kr(Env.linen(0.05, sustain, 0.1), doneAction: 2);
    Out.ar(0, sig ! 2)
}).send(s);

r = Task({
    var delta;
    while {
        delta = dur.next;
        delta.notNil
    } {
        Synth(\smooth, [freq: midi.next.midicps, sustain: delta]);
        delta.yield;
    }
}).play(quant: TempoClock.default.beats + 1.0);
)

(

SynthDef(\bass, { |freq = 440, gate = 1, amp = 0.5, slideTime = 0.17, ffreq = 1100, width = 0.15,

detune = 1.005, preamp = 4|

var sig,

env = Env.adsr(0.01, 0.3, 0.4, 0.1);

freq = Lag.kr(freq, slideTime);

sig = Mix(VarSaw.ar([freq, freq * detune], 0, width, preamp)).distort * amp

* EnvGen.kr(env, gate, doneAction: 2);

sig = LPF.ar(sig, ffreq);

Out.ar(0, sig ! 2)

}).add;


TempoClock.default.tempo = 132/60;


p = Pxrand([

Pbind(

\instrument, \bass,

\midinote, 36,

\dur, Pseq([0.75, 0.25, 0.25, 0.25, 0.5], 1),

\legato, Pseq([0.9, 0.3, 0.3, 0.3, 0.3], 1),

\amp, 0.5, \detune, 1.005

),

Pmono(\bass,

\midinote, Pseq([36, 48, 36], 1),

\dur, Pseq([0.25, 0.25, 0.5], 1),

\amp, 0.5, \detune, 1.005

),

Pmono(\bass,

\midinote, Pseq([36, 42, 41, 33], 1),

\dur, Pseq([0.25, 0.25, 0.25, 0.75], 1),

\amp, 0.5, \detune, 1.005

),

Pmono(\bass,

\midinote, Pseq([36, 39, 36, 42], 1),

\dur, Pseq([0.25, 0.5, 0.25, 0.5], 1),

\amp, 0.5, \detune, 1.005

)

], inf).play(quant: 1);

)


// totally cheesy, but who could resist?

(

SynthDef(\kik, { |preamp = 1, amp = 1|

var freq = EnvGen.kr(Env([400, 66], [0.08], -3)),

sig = SinOsc.ar(freq, 0.5pi, preamp).distort * amp

* EnvGen.kr(Env([0, 1, 0.8, 0], [0.01, 0.1, 0.2]), doneAction: 2);

Out.ar(0, sig ! 2);

}).add;


// before you play:

// what do you anticipate '\delta, 1' will do?

k = Pbind(\instrument, \kik, \delta, 1, \preamp, 4.5, \amp, 0.32).play(quant: 1);

)


p.stop;

k.stop;

//
// Usando MIDI
//
MIDIClient.init;
MIDIClient.sources.at(3);
MIDIClient.destinations;
MIDIIn.connect(0, MIDIClient.sources.at(3));
MIDIIn.noteOn = { |port, chan, note, vel| [port, chan, note, vel].postln }; 

(
SynthDef("sik-goo", { arg freq=440,formfreq=100,gate=0.0,bwfreq=800;
        var x;
        x = Formant.ar(
                SinOsc.kr(0.2, 0, 1, freq),
                formfreq,
                bwfreq
        );
        x = EnvGen.kr(Env.adsr, gate,Latch.kr(gate,gate)) * x;
        Out.ar(0, x);
}).send(s);
)

x = Synth("sik-goo");

//set the action:
(
MIDIIn.noteOn = {arg src, chan, num, vel;
        x.set(\freq, num.midicps / 4.0);
        x.set(\gate, vel / 200 );
        x.set(\formfreq, vel / 127 * 1000);
};
MIDIIn.noteOff = { arg src,chan,num,vel;
        x.set(\gate, 0.0);
};
MIDIIn.bend = { arg src,chan,val;
        //(val * 0.048828125).postln;
        x.set(\bwfreq, val * 0.048828125 );
};
)

(

{Pluck.ar(WhiteNoise.ar(0.1), Impulse.kr(1), 440.reciprocal, 440.reciprocal, 10, 

coef:MouseX.kr(-0.999, 0.999))

}.play(s)

)

//
// Outros exemplos interessantes
//

{o=0; 15.do{|i| o=o+LFTri.ar(138.59*(1.0595**(3*i)), 0, LFPulse.kr((51+i)/60, 0, 0.05, 0.1))}; o;}.play;

fork{{var t=0.2,x=t.rand;play{PMOsc.ar(100+400.rand,400+400.rand,1+4.0.rand,0,0.2)*EnvGen.kr(Env.linen(0,x,0,1),1,1,0,1,2)};(t-x).wait}!200};

(

{var n;

n=34;


Resonz.ar(
    
Mix.arFill(n,{

var freq, numcps;


freq= rrand(50,560.3);

numcps= rrand(2,20);

Pan2.ar(Gendy1.ar(6.rand,6.rand,1.0.rand,1.0.rand,freq ,freq, 1.0.rand, 1.0.rand, numcps, SinOsc.kr(exprand(0.02,0.2), 0, numcps/2, numcps/2), 0.5/(n.sqrt)), 1.0.rand2)

})

,MouseX.kr(100,2000), MouseY.kr(0.01,1.0))

;

}.play

)


(

 

{

var freq,time, ex, delay, filter, local;

 

freq= 440;

time= freq.reciprocal;


ex= WhiteNoise.ar(EnvGen.kr(Env([1.0,1.0,0.0,0.0], [time,0,100])));


freq= SinOsc.ar(6, 0, 10, freq);

time= freq.reciprocal;  


local= LocalIn.ar(1);


filter= LPZ1.ar(ex+local); //apply filter


//maximum delay time is 440-10

delay= DelayN.ar(filter, 430.reciprocal, time-ControlDur.ir);  


LocalOut.ar(delay*0.99); 


Out.ar(0, Pan2.ar(filter,0.0))

}.play


)

 

Anexo A: Instalando a partir do GIT

Se quiser as últimas atualizações é recomendável usar a versão de desenvolvimento:

   git clone --recursive git://supercollider.git.sourceforge.net/gitroot/supercollider/supercollider
   cd supercollider
   mkdir build
   cd build
   cmake ..
   make
   sudo make install 

E mantenha-se atualizado:

   git pull
   git submodule update


Referências