Stereofonia
Un sistema di diffusione stereofonico è invece formato da due altoparlanti frontali posizionati idealmente a un angolo di 60° rispetto al punto di ascolto ottimale:
E' un sistema monodimensionale frontale riconducibile principalmente alla prima tipologia di spazio tra quelle illustrate all'inizio di questo Paragrafo in quanto possiamo posizionare e muovere una o più sorgenti virtuali monofoniche (source) nel fronte stereofonico ovvero nello spazio compreso tra l'altoparlante sinistro e quello destro ma la distanza (la seconda dimensione) può essere simulata solo alle spalle della linea immaginaria che collega i due diffusori.
Stereo
Per ottenere un segnale stereofonico dobbiamo prendere un segnale monofonico e distribuire la sua ampiezza tra due canali attigui. Così facendo la somma delle ampiezze in uscita dai due altoparlanti sarà la stessa del segnale monofonico originale (mentre nel dual-mono ad esempio viene raddoppiata). In base al rapporto tra le due ampiezze in uscita dagli altoparlanti la sorgente risulterà posizionata in un punto preciso del fronte stereofonico:
s.options.numOutputBusChannels_(2); s.reboot; s.plotTree; s.scope; s.meter; ( SynthDef(\stereo, {arg sx=0.5, dx=0.5; var sig; sig = SinOsc.ar(mul:[sx,dx]); Out.ar(0,sig) } ).add; {~synth = Synth(\stereo)}.defer(0.1); ) ~synth.set(\sx,0.9,\dx,0.1); // sinistra ~synth.set(\sx,0.5,\dx,0.5); // centro ~synth.set(\sx,0.1,\dx,0.9); // destra
Come risulta evidente dal codice precedente in un canale dobbiamo moltiplicare l'ampiezza del segnale monofonico per un fattore compreso tra 0.0 e 1.0 e nell'altro per il valore che resta ad arrivare a 1.0 (1.0-x). Se automatizziamo questa operazione riusciamo a ottimizare il controllo della posizione esprimendo un solo numero compreso tra 0.0 (sinistra) e 1.0 (destra):
( SynthDef(\stereo, {arg pan=0.5; var sig; sig = SinOsc.ar(Rand(400,2000),mul:[1-pan,pan]); Out.ar(0,sig*0.2) } ).add; {~synth = Synth(\stereo)}.defer(0.1); ) ~synth.set(\pan,0); // sinistra ~synth.set(\pan,0.5); // centro ~synth.set(\pan,1); // destra // Interattivo muovendo il mouse... {SinOsc.ar(mul:[1-MouseX.kr,MouseX.kr])}.play; // Più sorgenti virtuali... Synth(\stereo,[\pan,rand(1.0)]); // esegui più volte
Ascoltando attentamente però, possiamo notare che la percezione dell'intensità del suono di ogni singola sorgente alle posizioni specificate nell'esempio (sinistra, centro, destra) non è sempre uguale: quando è al centro risulta più debole rispetto a quando è ai lati. Per ovviare a questo problema dato dall'imperfetta percezione umana una delle soluzioni trovate dagli studiosi nel corso del tempo è stata quella di calcolare la radice quadrata del fattore di moltiplicazione delle ampiezze divise tra i canali. Così facendo la somma dei segnali quando è posizionata al centro eccede 1.0 (0.5.sqrt = 0.707*2 = 1.141) ma all'orecchio umano risulta più bilanciato.
( SynthDef(\stereo, {arg pan=0.5; var sig; sig = SinOsc.ar(Rand(400,2000),mul:[(1-pan).sqrt,pan.sqrt]); Out.ar(0,sig*0.2) } ).add; {~synth = Synth(\stereo)}.defer(0.1); ) ~synth.set(\pan,0); // sinistra ~synth.set(\pan,0.5); // centro ~synth.set(\pan,1); // destra {SinOsc.ar(mul:[(1-MouseX.kr).sqrt,MouseX.kr.sqrt])}.play
LinPan2.ar e Pan2.ar
In SuperCollider possiamo realizzare il panning stereofonico appena illustrato con due UGens dedicate. La prima è LinPan2 che corrisponde al primo esempio (con il suono che risulta più flebile al centro):
LinPan2.ar(sig, pos, level)
( SynthDef(\stereo, {var sig,pos,lev,pan; sig = SinOsc.ar(Rand(400,2000)); pos = MouseX.kr(-1,1).poll(10); // -1=sx, 0=centro,1=dx lev = MouseY.kr(0.001,1).sqrt; // 1=vicino,0.001=lontano pan = LinPan2.ar(sig,pos,lev); Out.ar(0,pan) } ).add; {~synth = Synth(\stereo)}.defer(0.1); )
I suoi argomenti sono:
- sig: il segnale monofonico sul quale applicare il panning,
- pos: la posizione della sorgente in un'ambito compreso tra +/-1.0 dove -1.0 = sinistra, 0.0 = centro e
1.0 = destra (invece che tra 0.0 e 1.0, utilizzeremo questo ambito (valori compresi tra +/-1.0) come standard per tutti
i parametri legati alla posizione della sorgente in quanto risulta quello più compatibile con tutti i tipi di controllo
(anche con segnali).
- level: un fattore di moltiplicazione dell'ampiezza compreso tra 0.0 e 1.0. Possiamo utilizzarlo per simulare la
vicinanza o lontananza della sorgente rispetto alla posizione del fronte stereofonico (0.001 = lontano, 1.0 = vicino).
Nell'esempio seguente abbiamo inoltre calcolato la radice quadrata del livello per meglio simulare la
legge della
distanza inversa e rendere più "umano" il controllo interattivo della posizione con il mouse.
La seconda UGen è Pan2.kr() ed è identica a quella appena illustrata eccezione fatta per la correzione percettiva dell'ampiezza in posizione centrale. E' la UGen utilizzata comunemente per il panning stereofonico semplice.
( SynthDef(\stereo, {var sig,pos,lev,pan; sig = SinOsc.ar(Rand(400,2000)); pos = MouseX.kr(-1,1).poll(10); // -1=sx, 0=centro,1=dx lev = MouseY.kr(0.001,1).sqrt; // 1=vicino,0.01=lontano pan = Pan2.ar(sig,pos,lev); Out.ar(0,pan) } ).add; {~synth = Synth(\stereo)}.defer(0.1); )
Splay
In SuperCollider c'è anche una UGen particolare (Splay) che si comporta come Mix ovvero miscela tra loro Array di segnali monofonici, ma invece che produrre un segnale monofonico, posiziona in modo equidistante i singoli segnali nello spazio stereofonico partendo da sinistra:
( SynthDef(\stereo, {arg sprd=1,ctr=0; var sig,lev=0.4,pan; sig = SinOsc.ar([600,800,1000,1200,1500]); pan = Splay.ar(sig, // Array di canali mono sprd, // spread 0 = tutti al centro lev, // ampiezza globale (0. / 1.) ctr, // shift dal centro true); // limiter... Out.ar(0,pan) } ).add; {~synth = Synth(\stereo)}.defer(0.1); ) ~synth.set(\sprd,0); // tutti al centro ~synth.set(\sprd,0.5); // tra -0.5 e +0.5 ~synth.set(\sprd,1); // su tutto il fronte stereofonico ~synth.set(\ctr,0); // shift ~synth.set(\ctr,0.5); ~synth.set(\ctr,1.5); ~synth.set(\ctr,2); // tutti a destra ~synth.set(\ctr,-2); // tutti a sinistra ~synth.set(\ctr,-1.5);
Possiamo invocare anche un parente stretto del metodo .fill utilizzato con Mix: .arFill e utilizzare le possibilità già illustrate per Mix:
{Splay.arFill(8, {SinOsc.ar(rrand(200,2200),mul:1/8)})}.play; {Splay.ar({SinOsc.ar(rrand(200,4200), mul:1/8)}!8)}.play;
Balance
Se la sorgente invece che essere un segnale monofonico è un segnale stereofonico possiamo favorire la sua posizione verso destra o verso sinistra così come abbiamo fatto in Splay con l'argomento shift impiegando la UGen Balance2.ar():
( SynthDef(\balance, {arg pos=0,lev=1; var sig,pan; sig = SinOsc.ar([440,550],0,[0.7,0.3]); // segnale stereo pan = Balance2.ar(sig[0], // canale sinistro sig[1], // canale destro pos, lev); Out.ar(0,pan) } ).add; {~synth = Synth(\balance)}.defer(0.1); ) ~synth.set(\pos,-1.0); // solo canale sinistro ~synth.set(\pos,-0.5); ~synth.set(\pos, 0.0); ~synth.set(\pos, 0.5); ~synth.set(\pos, 1.0); // solo canale destro
Eseguendo l'esempio precedente risulta abbastanza evidente il compito e i controlli di questa UGen, sottolineiamo solamente l'accesso ai due canali separati del segnale stereofonico originale attraverso la sintassi propria degli items di un Array.
Controllo e visualizzazione
Come abbiamo visto in Paragrafi dedicati possiamo utilizzare diverse modalità di controllo dei parametri e diverse visualizzazioni anche per il panning. Se pensiamo il fronte stereofonico come un modello di spazio monodimensionale frontale possiamo visualizzare i parametri ed eventualemente interagire con tre interfacce grafiche tradizionali che sono anche la rappresentazione virtuale di controlli hardware largamente diffusi:
-
( // ------------------------------ SynthDef e Synth SynthDef(\stereo_gui, {arg pos=0.0, dur=0.2, dist=1; var sig,pan,spos,sdist,env; sig = SinOsc.ar(Rand(400,2000)); spos = VarLag.kr(pos,dur); sdist = Lag.kr(dist,dur); env = EnvGen.kr(Env.perc(0.01,0.19),Impulse.kr(5)); pan = Pan2.ar(sig, spos, sdist); SendReply.kr(Impulse.kr(50), '/pos', spos); Out.ar(0,pan*env) } ).add; {~synth = Synth(\stereo_gui)}.defer(0.1); // ------------------------------ GUI w = Window.new("Pan", 200@100); w.alwaysOnTop; w.front; w.onClose_({~synth.free;w.free;b.free;c.free;d.free}); b = StaticText.new(w, Rect(10, 5, 200, 15)) // Static text .string_("-1 0 1"); c = NumberBox.new( w, Rect(10, 75, 180, 20)).value_(0.5); // NumberBox d = Slider.new( w, Rect(10, 20, 180, 50)).value_(0.5); // Slider orizzontale OSCdef.new(\vedi, {arg msg; {c.value_(msg[3]); // al NumberBox d.value_(msg[3].linlin(-1,1,0,1)) // al Knob }.defer(0) },'/pos', s.addr); // ------------------------------ Interazione d.action_({arg i; ~synth.set(\pos,i.value.linlin(0,1,-1,1), \dur, 0.02); // Se agisce sull'interfaccia solo smooth }); MIDIIn.disconnectAll; MIDIIn.connectAll; MIDIdef.freeAll; MIDIdef.cc(\knob, {arg val; ~synth.set(\pos,val.linlin(0,127,-1,1), \dur, 0.02); // Se agisce sul controller solo smooth }, 16); // dal cc 16 ) // ------------------------------ Sequencing ~synth.set(\pos,rand2(1.0).postln,\dur,rrand(0.1,2).postln);
Notiamo come nella SynthDef i valori relativi alla posizione siano interpolati linearmente con VarLag.kr() e come questo ci permetta di stabilire il tempo impiegato dalla sorgente a compiere il movimento. Per contro il parametro dist utilizza Lag.kr() in quanto agisce sull'ampiezza ed è preferibile una rampa con curva esponenziale.
Per quanto riguarda la visualizzazione utilizziamo due UGens che ci permettono di inviare valori dal Server all'Interprete.
la prima - SendReply.kr() - deve essere specificata nella SynthDef e il suo compito è quello di campionare uno o più segnali audio (o di controllo) per poi inviarli dal Server all'Interprete sotto forma di messaggio OSC. Accetta come primo argomento un segnale impulsivo che stabilisce la rata di campionamento, come secondo argomento un indirizzo e come terzo il segnale (o l'Array di segnali) da campionare.
la seconda - OSCDef.new() - riceve messaggi OSC da un Server (o da un qualsiasi software in grado di trasmettere messaggi OSC) ed è descritta in questo paragrafo. La sola differenza consiste nel fatto che qui specifichiamo l'intero indirizzo IP del Server (s.addr) al posto della sola porta.
Knob (o Dial)
( // ------------------------------ SynthDef e Synth SynthDef(\stereo_gui, {arg pos=0.0, dur=0.2, dist=1; var sig,pan,spos,sdist,env; sig = SinOsc.ar(Rand(400,2000)); spos = VarLag.kr(pos,dur); sdist = Lag.kr(dist,dur); env = EnvGen.kr(Env.perc(0.01,0.19),Impulse.kr(5)); pan = Pan2.ar(sig, spos, sdist); SendReply.kr(Impulse.kr(50), '/pos', spos); Out.ar(0,pan*env) } ).add; {~synth = Synth(\stereo_gui)}.defer(0.1); // ------------------------------ GUI w = Window.new("Pan", 200@100); w.alwaysOnTop; w.front; w.onClose_({~synth.free;w.free;b.free;c.free;d.free}); b = StaticText.new(w, Rect(10, 5, 200, 15)) // Static text .string_(" 0"); c = NumberBox.new( w, Rect(10, 75, 180, 20)).value_(0.5); // NumberBox d = Knob.new( w, Rect(10, 20, 180, 50)).centered_(false).value_(0.5); // Knob OSCdef.new(\vedi, {arg msg; {c.value_(msg[3]); // al NumberBox d.value_(msg[3].linlin(-1,1,0,1)) // al Knob }.defer(0) },'/pos', s.addr); // ------------------------------ Controlli esterni d.action_({arg i; ~synth.set(\pos,i.value.linlin(0,1,-1,1), \dur, 0.02); // Se agisce sull'interfaccia solo smooth }); MIDIIn.disconnectAll; MIDIIn.connectAll; MIDIdef.freeAll; MIDIdef.cc(\knob, {arg val; ~synth.set(\pos,val.linlin(0,127,-1,1), \dur, 0.02); // Se agisce sul controller solo smooth }, 16); // dal cc 16 ) // ------------------------------ Sequencing ~synth.set(\pos,rand2(1.0).postln,\dur,rrand(0.1,2).postln);
Il codice è esattamente come il precedente, sostituiamo il termine Slider con Knob e aggiungiamo il metodo .centered_(false) che posiziona automaticamente lo 0 al centro (e non a sinistra).
-
( MIDIIn.disconnectAll; MIDIIn.connectAll; MIDIdef.freeAll; ~min=0.25; // Limiti alea ~max=0.5; ~dur=0.2; // ------------------------------ SynthDef e Synth SynthDef(\stereo_gui, {arg pos=0.0, dur=0.2, dist=1; var sig,pan,spos,sdist,env; sig = SinOsc.ar(Rand(400,2000)); spos = VarLag.kr(pos,dur); sdist = Lag.kr(dist,dur); env = EnvGen.kr(Env.perc(0.01,0.19),Impulse.kr(5)); pan = Pan2.ar(sig, spos, sdist); SendReply.kr(Impulse.kr(50), '/pos', spos); Out.ar(0,pan*env) } ).add; {~synth = Synth(\stereo_gui)}.defer(0.2); // ------------------------------ GUI w = Window.new("Pan", 200@100); w.alwaysOnTop; w.front; w.onClose_({~synth.free;w.free;e.free;c.free;r.stop}); b = StaticText.new(w, Rect(10, 5, 200, 15)) // Static text .string_(" 0"); c = NumberBox.new( w, Rect(10, 75, 180, 20)).value_(0.5); // NumberBox d = RangeSlider.new(w, Rect(10, 20, 180, 50)); // RangeSlider d.lo_(0.25); d.hi_(0.75); OSCdef.new(\vedi, {arg msg; {c.value_(msg[3])}.defer(0)},'/pos', s.addr); // ------------------------------ Random tra min e max r = Routine.new({var val; inf.do({ val = rrand(~min, ~max); // sceglie il valore nel range ~synth.set(\pos, val,\dur,~dur); // lo invia al Synth ~dur.wait // delta tra i cambi }) }).reset.play; // ------------------------------ Controlli esterni d.action_({arg i; ~min = i.lo.linlin(0,1,-1,1); // recupera il valore min ~max = i.hi.linlin(0,1,-1,1); // recupera il valore max }); MIDIdef.cc(\min, {arg val; ~min = val.linlin(0,127,-1,1); {d.lo_(val.linlin(0,127,0,1))}.defer(0) }, 16); // dal cc 16 MIDIdef.cc(\max, {arg val; ~max = val.linlin(0,127,-1,1); {d.hi_(val.linlin(0,127,0,1))}.defer(0) }, 17); // dal cc 17 ) // ------------------------------ Sequencing ( ~min = rand2(1.0); ~max = rand2(1.0); ~dur = rrand(0.1,0.5); d.hi_(~max.linlin(-1,1,0,1)); d.lo_(~min.linlin(-1,1,0,1)) )
Questo tipo di Slider risulta molto utile in diverse situazioni, nello specifico del panning stereofonico possiamo impegarlo quando vogliamo delimitare un ambito all'interno del quale far compiere a SuperCollider delle scelte randomiche. Il pensiero musicale potrebbe essere riassunto in questo modo:
voglio che la sorgente si muova randomicamente più o meno velocemente nella porzione dello spazio totale che ho a disposizione delimitata dai valori di minimo e massimo.
Movimenti dinamici
Client side
Il codice utilizzato per Silder e knobs può essere utile per tutte le modalità di controllo da Interprete (dal codice, da GUI e da devices), utilizziamolo allora per illustrare alcune tecniche di sequencing:
Singolo valore. Inviamo i parametri nel consueto modo.
~synth.set(\pos,rand2(1.0)); ~synth.set(\pos,0.6,\dur,1); ~synth.set(\pos,rand2(1.0),\dur,rrand(0.1,2));
Ricordiamo che con il Synth che abbiamo programmato possiamo anche interagire sia con il mouse sulla GUI che con un controller MIDI esterno alternando le diverse tipologie di controllo.
Come ben sappiamo, possiamo automatizzare l'invio dei parametri nel tempo attraverso tecniche di sequencing realizzate con Routine, Task o Pattern:
Traiettorie. Specifichiamo in un Array bidimensionale i valori [pos, time] e utilizziamo il metodo Array.do({}) per leggerle sequenzialmente. Separeremo i due parametri con il metodo Array.at(id) o la sua abbreviazione Java Array[id]
( var pos; pos = [[0,1,1],[-0.4,0.8,0.3],[0.6,0.6,2],[0,0.4,0.4],[1,0.1,3],[-1,1,5]]; // [pos,dur] h = Routine.new({ pos.do({arg i; ~synth.set(\pos,i[0],\dist,i[1],\dur,i[2]); i[2].wait }) }).reset.play; ) h.stop; ~synth.set(\pos,0,\dist,1,\dur,0.2);
Random. Possiamo impiegare tutte le tecniche aleatorie che abbiamo affrontato nella prima Sezione. Nel codice seguente un semplice esempio.
( var pos,dist,dur; h = Routine.new({ inf.do({pos = rand2(1.0); dist = rand(1.0); dur = rrand(0.1,3); ~synth.set(\pos,pos,\dist,dist,\dur,dur); dur.wait }) }).reset.play ) h.stop; ~synth.set(\pos,0,\dist,1,\dur,0.2);
Rota. Questa tecnica consiste nel fare oscillare ciclicamente nel tempo la sorgente tra destra e sinistra:
( var ciclo; ciclo = 3; // tempo di un'andata e ritorno in secondi h = Routine.new({ inf.do({var dur = ciclo*0.5; // pos ~synth.set(\pos,1,\dist,1,\dur,dur); dur.wait; ~synth.set(\pos,-1,\dist,1,\dur,dur); dur.wait; }) }).reset.play ) h.stop; ~synth.set(\pos,0,\dist,1,\dur,0.2);
Spreading. Questa tecnica consiste nel delimitare una zona del fronte stereofonico all'interno della quale la sorgente si muove randomicamente secondo una velocità fissa o anch'essa randomica.
( ~rng = [-0.5,0.3]; ~dist = 0.5; ~dur = 0.4; h = Routine.new({ inf.do({var pos; pos = rrand(~rng[0],~rng[1]); ~synth.set(\pos,pos,\dist,~dist,\dur,~dur); ~dur.wait }) }).reset.play ) h.stop; ~synth.set(\pos,0,\dist,1,\dur,0.2);
Server side
Segnali
Possiamo controlllare il panning anche attraverso segnali di controllo e questo è il motivo per il quale in precedenza abbiamo scelto di uniformare tutti i valori in un ambito compreso tra +/-1.0 (l'ampiezza dei segnali audio rientra in questi valori).
Come abbiamo accennato in precedenza i parametri controllabili dinamicamente dall'interprete in questo caso sono differenti da quelli che abbiamo già incontrato e sono:
la forma d'onda del segnale di controllo:
L'immagine è volutamente traslata: sull'asse verticale è rappresentato il tempo mentre sull'asse orizzontale i movimenti della sorgente tra sinistra (-1) e destra (+1).
Per cambiare dinamicamente le diverse forme d'onda, ricordiamo però che non è possibile inviare un segnale a un Synth con il metodo .set(), dobbiamo includere tutti i segnali desiderati nella SynthDef per poi indirizzarne uno o l'altro verso l'output. Questa operazione può essere effettuata dalla UGen Select.kr() che ha due argomenti:
Select.kr(id, [sig0,sig1,sig2,...])
Specificando il primo argomento (l'indice dell'Array) inviamo all'output il segnale corrispondente:
( SynthDef("sel", {arg id = 0; Out.ar(0,Select.ar(id, // id [LFTri.ar, // Array di segnali SinOsc.ar, LFNoise0.ar, LFNoise1.ar, LFNoise2.ar] ) ) } ).add; {~synth = Synth(\sel)}.defer(0.1); ) ~synth.set(\id,rand(4).postln);
In realtà per compiere questa operazione esistono due strategie più dinamiche e meno costose a livello computazionale (in Select.kr() tutti i segnali contenuti nell'Array sono sempre computati contemporaneamente) che implicano l'utilizzo di bus audio oppure attraverso il recupero dei valori da bus di controllo come descritto qui.
la velocità del movimento da un'altoparlante e l'altro per i segnali periodici mentre per i segnali random la frequenza della scelta di una nuova posizione:
( SynthDef("vel", {arg vel = 0; var sig,env,pos,pan; sig = SinOsc.ar(Rand(400,2000)); env = EnvGen.kr(Env.perc(0.01,0.19),Impulse.kr(5)); pos = SinOsc.ar(vel); pan = Pan2.ar(sig*env,pos); Out.ar(0,pan) } ).add; {~synth = Synth(\vel)}.defer(0.1); ) ~synth.set(\vel,rrand(1,4).postln);
lo spread o larghezza del fronte stereofonico controllabili con gli argomenti mul e add del segnale di controllo:
( SynthDef("sprd", {arg sprd=1, add=0; var sig,env,pos,pan; sig = SinOsc.ar(Rand(400,2000)); env = EnvGen.kr(Env.perc(0.01,0.19),Impulse.kr(5)); pos = SinOsc.ar(1,0,sprd,add); pan = Pan2.ar(sig*env,pos,); Out.ar(0,pan) } ).add; {~synth = Synth(\sprd)}.defer(0.1); ) ~synth.set(\sprd,rand(1.0).postln); ~synth.set(\sprd,0.1,\add,0.5); // traslato
la posizione iniziale e il verso del movimento controllabili solo per le UGens che hanno un agomento per la fase (in radianti):
( {[SinOsc.ar(220,0), // 0 = parte dal centro e va verso 1, SinOsc.ar(220,pi), // pi = parte dal centro e va verso -1 SinOsc.ar(220,0.5pi), // 0.5pi = parte da 1 e va verso -1 SinOsc.ar(220,-0.5pi)] // -0.5pi = parte da -1 e va verso 1 }.plot )
la distanza può essere controllata come abbiamo fatto precedentemente, specificando la posizione con un valore compreso tra 1.0 e 0.001 e utilizzando il tempo di lag come durata del movimento.
( SynthDef("dist", {arg dist=1, smt=0.2; var sig,env,pos,pan; sig = SinOsc.ar(Rand(400,2000)); env = EnvGen.kr(Env.perc(0.01,0.19),Impulse.kr(5)); pos = SinOsc.ar(1); pan = Pan2.ar(sig*env,pos,dist.lag(smt)); Out.ar(0,pan) } ).add; {~synth = Synth(\dist)}.defer(0.1); ) ~synth.set(\dist,rand(1.0).postln,\smt,rand(15).postln);
Per una visualizzazione dinamica delle posizioni su un interfaccia grafica possiamo monitorare i segnali con un'oscilloscopio o con i PPM di servizio generati all'inizio dei codici. Di seguito un codice riassuntivo di tutto quanto appena esposto.
( SynthDef(\stereo_sig, {arg tipo=0,vel=1,sprd=1,initpos=0,dist=1,smt=0.2; var sig,env,ksigs,pos,pan; sig = SinOsc.ar(Rand(400,2000)); env = EnvGen.kr(Env.perc(0.01,0.19),Impulse.kr(5)); pos = Select.kr(tipo,[LFTri.kr(vel,initpos,sprd), SinOsc.kr(vel,initpos,sprd), LFNoise0.kr(vel,sprd), LFNoise1.kr(vel,sprd), LFNoise2.kr(vel,sprd)]); pos.scope; // visualizza il segnale di controllo in verde pan = Pan2.ar(sig*env,pos,dist.lag(smt)); Out.ar(0,pan) } ).add; {~synth = Synth(\stereo_sig)}.defer(0.1); ) ~synth.set(\tipo,0); // cambia la forma d'onda ~synth.set(\vel, 1); // cambia la velocità di 1 ciclo in Hz (solo andata o ritorno) ~synth.set(\sprd,1); // cambia la larghezza del fronte stereofonico ~synth.set(\initpos,pi); // cambia la posizione iniziale e il verso ~synth.set(\dist,0.01,\smt,5); // cambia la distanza
Possiamo anche costruire una GUI sia per visualizzare i movimenti della sorgente che per controllare i diversi parametri nei modi che già conosciamo:
( // ------------------------------ SynthDef e Synth SynthDef(\stereo_sig, {arg tipo=0,vel=1,sprd=1,initpos=0,dist=1,smt=0.2; var sig,env,ksigs,pos,pan; sig = SinOsc.ar(Rand(400,2000)); env = EnvGen.kr(Env.perc(0.01,0.19),Impulse.kr(5)); pos = Select.kr(tipo,[LFTri.kr(vel,initpos,sprd), SinOsc.kr(vel,initpos,sprd), LFNoise0.kr(vel,sprd), LFNoise1.kr(vel,sprd), LFNoise2.kr(vel,sprd)]); pos.scope; // visualizza il segnale di controllo in verde SendReply.kr(Impulse.kr(50), '/pos', pos); // Invia al Client pan = Pan2.ar(sig*env,pos,dist.lag(smt)); Out.ar(0,pan) } ).add; {~synth = Synth(\stereo_sig)}.defer(0.1); // ------------------------------ GUI w = Window.new("Pan", 255@130); w.alwaysOnTop; w.front; w.onClose_({~synth.free;w.free;a.free;b.free;c.free;d.free}); a = PopUpMenu(w,Rect.new(10,10,100,20)); a.items = ["Triangolo","Sinusoide","Random 1","Random 2","Random 3"]; b = NumberBox.new(w,Rect.new(115,10,40,20)) .value_(1); c = NumberBox.new(w,Rect.new(180,10,40,20)) .value_(1); StaticText.new(w, Rect.new(158, 12, 20, 20)) .string_("Vel"); StaticText.new(w, Rect.new(222, 12, 20, 20)) .string_("Dist"); StaticText.new(w, Rect(10, 40, 255, 15)) .string_("-1 0 1"); d = Slider.new(w, Rect(10, 55, 230, 50)); // Slider orizzontale // ------------------------------ Operazioni di basso livello a.action_({arg tipo; ~synth.set(\tipo,tipo.value) }); b.action_({arg vel; ~synth.set(\vel, vel.value) }); c.action_({arg dist; ~synth.set(\dist, dist.value) }); OSCdef.new(\vedi, {arg msg; defer{d.value_(\pan.asSpec.unmap(msg[3]))}},'/pos', s.addr) )
Inviluppi
Per controllare posizione e distanza dinamicamente, possiamo utilizzare anche qualsiasi tipo di segnale di controllo generato da inviluppi a condizione che i valori dei livelli siano compresi tra +/-1. In questo modo possiamo disegnare qualsiasi tipo di movimento senza dover programmare le Routines di controllo che abbiamo visto nei controlli da Interprete, avendo un'accuratezza a control rate e non dipendente dallo scheduler. Quando possibile questo metodo è da preferire a quello illustrato in precedenza che può essere comunque utile in svariate situazioni.
( // ------------------------------ SynthDef e Synth SynthDef(\penvi, {arg t_gate=0; var sig,env,envpos,envdist,pan; sig = SinOsc.ar; env = EnvGen.kr(Env.perc(0.01,0.19),Impulse.kr(5)); envpos = Env.new([0,-1,0.7,0],[0.3,0.2,2],\lin).kr(0,t_gate); envdist = Env.new([1,0.01,1],[3,3],\cub).kr(0,t_gate); pan = Pan2.ar(sig*env,envpos,envdist); SendReply.kr(Impulse.kr(50), '/pos', [envpos,envdist]); Out.ar(0,pan) } ).add; {~synth = Synth(\penvi)}.defer(0.1); // ------------------------------ GUI w = Window.new("Pan", 255@130); w.alwaysOnTop; w.front; w.onClose_({~synth.free;w.free;b.free;d.free}); StaticText.new(w, Rect(86, 12, 255, 15)) .string_("Dist"); b = NumberBox.new(w,Rect.new(115,10,40,20)); StaticText.new(w, Rect(10, 40, 255, 15)) .string_("-1 0 1"); d = Slider.new(w, Rect(10, 55, 230, 50)); // Slider orizzontale // ------------------------------ Operazioni di basso livello OSCdef.new(\vedi, {arg msg; defer{d.value_(\pan.asSpec.unmap(msg[3]));b.value_(\pan.asSpec.unmap(msg[4]))}},'/pos', s.addr); ) // ------------------------------ Sequencing ~synth.set(\t_gate,1);
Mouse
Così come abbiamo fatto per l'ampiezza possiamo controllare il panning stereofonico anche con il mouse mappando l'asse x dello schermo sulla posizione e l'asse y sulla distanza:
( SynthDef(\pmouse, {var sig,env,pos,dist,pan; sig = SinOsc.ar; env = EnvGen.kr(Env.perc(0.01,0.19),Impulse.kr(5)); pos = MouseX.kr(-1,1); dist = MouseY.kr(1,0.001); // vicino in basso pan = Pan2.ar(sig*env,pos,dist); Out.ar(0,pan) } ).add; {a = Synth(\pmouse)}.defer(0.1); )
In questo caso non abbiamo generato una GUI in quanto lo spazio di azione è dato dallo schermo del computer ma altre modalità possono essere programmate secondo quanto riportato nel paragrafo dedicato al mouse.