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:

image not found

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):

image not found

(
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:

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);	

image not found

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:

  1. Slider orizzontale.

    image not found

    (
    // ------------------------------ 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.

  2. Knob (o Dial)

    image not found

    (
    // ------------------------------ 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).

  3. RangeSlider

    image not found

    (
    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:

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:

image not found

(
// ------------------------------ 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.

image not found

(
// ------------------------------ 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.