Panning

Nel corso di questo paragrafo ci occuperemo delle principali tecniche di panning ovvero di come posizionare un segnale monofonico in uno spazio bi o tri-dimensionale delimitato in vari modi da due o più altoparlanti.

Facciamo attenzione a non confondere le tecniche di pannning con quelle di spazializzazione in quanto le prime implicano solo lo spostamento del segnale in uno spazio neutro e fondamentalmente si ottengono con fadein e fadeout del segnale sincronizzati tra i diversi altoparlanti mentre le seconde vogliono simulare il movimento di una sorgente sonora virtuale in relazione ad un punto di ascolto preciso e implicano anche parametri come gain (più la sorgente si allontana e più il volume diminuisce), riverberazione dinamica (più la sorgente si allontana maggiore sarà l'ampiezza del riverbero rispetto a quella del suono diretto), assorbimento dell'aria, effetto doppler, etc.

Espansione multicanale

Cominciamo ad esplorare uno spazio sonoro soffermandoci su di una particolarità di SuperCollider ovvero l'espansione multicanale. Abbiamo visto che nella maggior parte dei casi possiamo specificare gli argomenti delle UGens sia come singoli valori che come singoli segnali audio o di controllo.

s.options.numOutputBusChannels_(8);
s.reboot;
s.plotTree;
s.scope;      
s.meter;

{SinOsc.ar(980,0,0.3)}.play                   // singolo valore
{SinOsc.ar(980,0,SinOsc.kr(1).unipolar)}.play // segnale		

Se però includiamo più UGens in un Array oppure sostituiamo il singolo argomento di una sola UGen con un Array (di valori o di segnali), SuperCollider crea tante istanze della UGen che li contiene quante gli items dell'Array e ogni singola istanza (segnale monofonico) viene scritta su un canale audio differente.

// Array di UGens:
(
{[SinOsc.ar(440),
  SinOsc.ar(550),        // possono anche essere diverse UGens
  SinOsc.ar(660),
  SinOsc.ar(880)]}.play; // 4 segnali che escono su 4 canali differenti e contigui
)                        

// Array di argomenti come valori:

{SinOsc.ar(440)}.play;
{SinOsc.ar([440,550,660,880])}.play;

// Array di argomenti come segnali:

{SinOsc.ar(mul: SinOsc.kr(1).unipolar)}.play;
{SinOsc.ar(mul: SinOsc.kr([1,0.5,0.2,0.4]).unipolar)}.play;	

Se non stiamo utilizzando una scheda audio multicanale udiremo solo i primi due oscillatori (nel caso delle freqenze: 440 e 550) in quanto la prima istanza viene scritta di default sul canale sinistro (bus 0) mentre tutte le altre (se presenti) sui canali attigui in ordine crescente.

image not found

image not found

Vediamo ora cosa succede quando utilizziamo Array di lunghezza diversa per più argomenti della stessa UGen:

(
{SinOsc.ar(freq:[440,550,660,880,990,999],
           mul: [0.2,0.6,0.4]
)}.play
)

image not found

In questo caso l'Array che specifica le ampiezze (mul:) ha meno items di quello che specifica le frequenze (freq:) e quando termina ricomincia dal primo item:

440 --> 0.2
550 --> 0.6
660 --> 0.4
880 --> 0.2
990 --> 0.6
999 --> 0.4

Tutte le UGen hanno questa caratteristica, anche gli Env:

(
SynthDef(\multi,
               {arg t_trig = 0;
                var freq, bpf, env, sig;
                    freq = #[200,974,1500,2465];
                    env = Env.perc([0.1,0.2,0.3,0.4],[0.4,0.3,0.2,0.1]).kr(gate:t_trig);
                    sig = SinOsc.ar(freq, mul: env);
				Out.ar(0,sig*0.5)
                }).add;

{~synth = Synth(\multi)}.defer(0.1)
)

~synth.set(\t_trig,1);	

Visualizzazione multicanale

Possiamo visualizzare insiemi di segnali o inviluppi sotto forma di Array facendo sì che ognuno sia posizionato in un proprio piano cartesiano:

image not found

image not found

(
{[SinOsc.ar(440),LFNoise0.ar(443),Saw.ar(447)]}
.plot(name:"Sig", bounds:300@200, minval:-1, maxval:1);

[Env.perc(0.1,2,0.7),Env.perc(0.5,2,0.7),Env.perc(0.7,2,0.7)]
.plot(name:"Env", bounds:300@200, minval:0, maxval:1)
)	

Se utilizziamo la notazione per l'espansione multicanale però inviuppi e segnali si comportano in modo differente:

Implosione multicanale

Possiamo sfruttare la caratteristica appena illustrata in molte situazioni musicali che implicano l'uso di diversi canali audio. Spesso però nel programmare algoritmi di sintesi o elaborazione del suono abbiamo la necessità di effettuare l'operazione opposta, ovvero miscelare tra loro più segnali per ottenerne uno monofonico in uscita come ad esempio nella sintesi additiva. Questo ristultato può essere ottenuto con la Classe Mix.new().

s.options.numOutputBusChannels_(6);
s.reboot;
s.plotTree;
s.scope;      
s.meter;

{SinOsc.ar([600,800,1000,1200,1500], mul:0.1)}.scope;          // multicanale
{Mix.new(SinOsc.ar([600,800,1000,1200,1500], mul:0.1))}.scope; // mono	

Nel codice seguente un esempio riassuntivo con inviluppi differenti applicati a segnali differenti:

(
SynthDef(\envi, {arg t_trig = 0;
                 var freq,env,sig;
                     freq = #[200,974,1500,1800];
                     env  = Env.perc([0.1,0.2,0.3,0.4],[0.4,0.3,0.2,0.1]).kr(gate:t_trig);
                     sig  = SinOsc.ar(freq, mul:env); // multicanale
                     sig  = Mix(sig);                 // mono
                Out.ar(0,sig*0.3)
                }
        ).add;

{~synth = Synth(\envi)}.defer(0.1);

)
~synth.set(\t_trig,1);	

Per generare un Mix monofonico da più istanze di una sola UGen o da più UGens differenti possiamo invocare anche la maggior parte dei metodi invocabili sugli Arrays come ad esempio .fill(). I suoi argomenti sono il numero di istanze da creare e i segnali da includere nell'Array:

(
{var n = rrand(1,85);   // Numero di istanze da creare e mixare
     n.postln;
 Mix.fill(n, {SinOsc.ar(rrand(20,20000),mul:1/n)})
}.play;
)	

Oppure l'abbreviazione sintattica del metodo n.dup():

{Mix({SinOsc.ar(rrand(200,1200),mul:1/8)} ! 8)}.play;

Approfondiremo queste ed altre tecniche nel paragrafo dedicato alla sintesi additiva (METTI LINK)

Tipologie di spazi sonori

Prima di affrontare le diverse tecniche di panning con i loro specifici controlli, credo sia necessario soffermarci sui tre possibili modi di pensare lo spazio nell'utilizzo di sistemi di diffusione stereofonici e multicanale, sopratutto in relazione al pensiero musicale:

  1. gli altoparlanti delimitano uno spazio acustico all'interno del quale posizionare le diverse sorgenti monofoniche. In questo caso devono assumere il più possibile una funzione neutra ovvero l'ascoltatore non dovrebbe percepire la loro presenza, come se fossero le pareti di una sala da concerto. Utilizzeremo principalmente questo tipo di panning quando le nostre esigenze musicali sono quelle di avere diversi personaggi sonori che si muovono virtualmente all'interno dello stesso ambiente percepito come luogo inclusivo/immersivo e non come strumento.

  2. gli altoparlanti sono uno strumento di diffusione del suono caratteristico e riconoscibile e la loro disposizione in sistema (tipicamente ma non necessariamente circolare come l'ottofonia oppure in orchestre di altoparlanti come gli acousimonium) assume una precisa valenza musicale che ai nostri giorni ha ormai sviluppato proprie tecniche strumentali storicizzate e codificate come il panning circolare, randomico, le traiettorie a spirale tipiche della diffusione su acousmonium, etc.) che rispondono a necessità estetiche e musicali molto precise come in passato è avvenuto per l'evoluzione delle tecniche esecutive degli strumenti acustici.

  3. gli altoparlanti assumono la stessa valenza dei singoli strumenti acustici, di personaggi o di sculture sonore. In questo caso la caratterizzazione musicale deve essere massima in quanto anche se inseriti in un sistema di diffusione più ampio, ogni altoparlante deve rappresentare se stesso, ed essere sempre riconoscibile sia musicalmente che come punto di diffusione del suono nello spazio, esattamente come il singolo strumento di un quartetto d'archi o le sezioni di un'orchestra sinfonica.

Ognuno di questi tre modi di pensare lo spazio può essere realizzato con i diversi sistemi di diffusione del suono, dalla monofonia alla multifonia e in SuperCollider ci sono diverse UGens specifiche per ognuno di essi.

Monofonia

Se pensiamo al mondo digitale un segnale monofonico è un un flusso di numeri (numeric stream) compresi tra +/-1.0 scritti su un singolo bus di uscita audio che, dopo essere stati convertiti in segnale analogico (flusso di elettroni liberi che scorrono in una direzione all'interno di un singolo cavo) viene diffuso da un solo altoparlante. Possiamo pensare anche al percorso inverso: quando colleghiamo un microfono ad una scheda audio o a un mixer tramite un cavo analogico il segnale che entra è monofonico e se colleghiamo più microfoni avremo un segnale mono per ognuno di loro.

Bus audio

In Supercollider abbiamo a disposizione 128 canali audio (da 0 a 127) che sono anche chiamati Bus. I primi 16 sono pubblici ovvero utilizzati per i segnali in ingresso e in uscita da Supercollider, gli altri sono privati ovvero utili per routing interni al software.

Possiamo scrivere qalsiasi tipo di segnale su ognuno di questi con la UGen Out.ar() mentre per leggerli dobbiamo utilizzare la UGen In.ar().

Ricordiamo che per utilizzare sistemi multicanale in entrata ed uscita da SuperCollider dobbiamo prima settare il Server che nelle ultime versioni ha solo due canali in uscita di default.

Out.ar()

Gli argomenti di questa UGen sono: Out.ar(numero_del_bus, segnale_da_collegare) dove il segnale può anche essere un Array di segnali.

s.options.numOutputBusChannels_(6);
s.reboot;
s.plotTree;
s.scope;      
s.meter;

(
SynthDef(\out,{arg t_trig = 0, out = 0;
	       var freq,env,senv,sig,mix;
	           freq  = #[200,974,1500,1800];
	           env   = Env.perc([0.1,0.2,0.3,0.4],[0.4,0.3,0.2,0.1]);
	           senv  = EnvGen.kr(env, t_trig);
	           sig   = SinOsc.ar(freq, mul:senv)*0.5;
	           mix   = Mix(sig)*0.5;
	    Out.ar(out,mix)
}).add;

{a = Synth(\out)}.defer(0.2);
)

a.set(\out,0, \t_trig,1);
a.set(\out,1, \t_trig,1);
a.set(\out,rand(8).postln, \t_trig,1);	

Se il segnale che vogliamo scrivere è stereo o multicanale, dobbiamo specificare come argomento solo il primo da sinistra, gli altri sono posizionati automaticamente nei canali attigui:

(
SynthDef(\out,{arg t_trig = 0, out = 0, pos = 0;
	       var freq,env,senv,sig,mix,pan;
	           freq  = #[200,974,1500,1800];
	           env   = Env.perc([0.1,0.2,0.3,0.4],[0.4,0.3,0.2,0.1]);
	           senv  = EnvGen.kr(env, t_trig);
	           sig   = SinOsc.ar(freq, mul:senv)*0.5;
	           mix   = Mix(sig)*0.5;
			   pan   = Pan2.ar(mix,pos,0.5);
	    Out.ar(out,pan)
}).add;

{a = Synth(\out)}.defer(0.2);
)

a.set(\pos,rrand(-1.0,1.0),\out,rand(7),\t_trig,1);

Mono

In SuperCollider la maggior parte delle UGens genera o elabora un segnale monofonico che di default è scritto sul bus audio 0 (in un sistema di diffusione standard corrisponde al canale sinistro). Come abbiamo appena visto può essere anche il risultato del mixaggio di più canali audio:

{SinOsc.ar}.play;
{Mix.new(SinOsc.ar([440,578,897])*0.2)}.play;

image not found

image not found

In una SynthDef ad esempio possiamo stabilire su quale bus audio scrivere un segnale monofonico, specificandolo come primo argomento di Our.ar().

(
SynthDef(\mono,
    	      {arg bus=0;
               var sig;
                   sig = SinOsc.ar;
               Out.ar(bus, sig*0.2)}
         ).add;

{~synth = Synth(\mono)}.defer(0.1);
)

~synth.set(\bus,rand(8));

(
r = Routine.new({
                 inf.do({
                         ~synth.set(\bus,rand(2));
                         0.5.wait
                         })
                 }).reset.play
)
r.stop;		

Possiamo in questo modo inviare contemporaneamente più segnali mono a diversi bus audio che saranno diffusi da più altoparlanti

Dual mono e multi mono

Se duplichiamo un segnale monofonico scrivendolo su due canali attigui e lo diffondiamo tramite due altoparlanti non otteniamo un segnale stereofonico ma dual-mono (due segnali monofonici uguali):

{SinOsc.ar(rrand(700,1200)!2,0,0.1)}.play; // esegui più volte...	

image not found

I sistemi di diffusione multi-monofonici si prestano a interessanti soluzioni artistico espressive nel terzo modo di pensare lo spazio tra quelli illustrati all'inizio di questo Paragrafo. Pensiamo per esempio a un ensemble di strumenti acustici elaborati in tempo reale ognuno da un proprio device con un uscita monofonica e un singolo altoparlante. Se disposti in posizioni fisse non univoche all'interno di uno spazio reale (a uno strumento corrisponde l'altoparlante che diffonde le elaborazioni di un'altro strumento) si possono realizzare giochi di specchi e rimandi sonori spaziali tra strumenti reali e i loro doppi elettroacustici molto accattivanti, facendo assumere di fatto al parametro spazio la stessa importanza degli altri parametri musicali come altezza, dinamica, timbro, etc. sul piano del linguaggio musicale e sonoro.

image not found

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.

Quadrifonia

Oltre a sistemi sistemi di diffusione del suono monofonici o stereofonici utilizzati prevalentemente per la fruizione musicale (e multimediale) di largo consumo (radio, televisione, sistemi Hi-Fi, videogiochi, etc.) oppure per concerti e spettacoli pop, rock e jazz, nel corso del secolo scorso alcuni compositori di musica colta prima e i produttori di sistemi di diffusione per l'audio cinematografico in seguito hanno sviluppato per ragioni differenti numerose tecnologie per la diffusione multicanale ovvero attraverso più altoparlanti disposti in vari modi.

La maggior parte di queste tecnologie erano (e sono in quanto la ricerca in questo ambito è ancora molto attiva) quasi tutte volte alla ricostruzione di uno spazio acustico virtuale atto a fornire un'esperienza d'ascolto di tipo immersivo sviluppata a 360° attorno all'ascoltatore. Anche in questi casi sono validi i diversi modi di pensare lo spazio esposti all'inizio del paragrafo precedente che si differenziano ora anche nell'utilizzo di UGens dedicate.

Così come abbiamo fatto per la stereofonia, nei prossimi sottoparagrafi ci occuperemo solamente delle principali tecniche di panning multicanale realizzabili in SuperCollider, tralasciando diverse tecniche di spazializzazione che abbiamo a disposizione ai nostri giorni come gli acousmonium, la registrazione e diffusione bineaurale, la wave field synthesis, vbap, i sistemi ambisonic e gli array o cupole di altoparlanti. Chi volesse approfondire queste tecniche può trovare un valido supporto introduttivo nel Capitolo dedicato scritto da Marije A. J. Baalman e Scott Wilson del "The SuperCollider book".

Il più tradizionale tra i sistemi di diffusione multicanale è la quadrifonia ovvero il delimitare la spazio acustico all'interno del quale si trova il pubblico con quattro altoparlanti posti idealmente agli angoli di un quadrato immaginario come illustrato nell'immagine seguente:

image not found

Possiamo notare come la numerazione degli altoparlanti (L1 - L4) può essere effettuata in due modi: per coppie stereofoniche (a sinistra) oppure circolare (a destra). Notiamo anche come in questo caso i bus di uscita da SupercCollider (da 0 a 3) rimangono invariati in entrambe le numerazioni.

Esattamente come abbiamo fatto per il fronte stereofonico, dobbiamo suddividere l'ampiezza di ogni sorgente (segnale monofonico) non su due ma su quattro altoparlanti e per farlo possiamo considerare il quadrato delimitato dagli altoparlanti come un piano cartesiano sul quale andiamo a specificare ogni punto come coordinate x/y comprese tra 0.0 e 1.0

Il valore di x rappresenterà la posizione sul fronte stereofonico mentre il valore di y la profondità:

image not found

Il calcolo delle ampiezze di ogni altoparlante relative alla posizione della sorgente è illustrato nell'immagine seguente e può tornarci utile nell'impiego di softwares differenti da SuperCollider:

image not found

Pan4.ar()

Nel caso volessimo pensare lo spazio come nella tipologia descritta al punto 1 all'inizio del paragrafo precedente potremmo utilizzare una UGen dedicata: Pan4.ar() che ha come argomenti gli stessi parametri appena esposti ed effettua automaticamente il calcolo delle ampiezze relative alla posizione:

Pan4.ar(sig, x, y, level)

Come per Pan2.ar() i valori di x e y devono essere espressi in un ambito compreso tra +/- 1.0 mentre l'argomento level anche se presente in questo caso non è utilizzato per simulare la distanza ma per fare degli eventuali bilanciamenti o compensazioni di ampiezza tra sorgenti diverse presenti nello stesso spazio acustico virtuale.

image not found

s.options.numOutputBusChannels_(4);
s.reboot;
s.plotTree;
s.scope;      
s.meter;

(
SynthDef(\quadri,
              {arg x=0,y=0,smt=0.2;
               var sig,env,pan;
                   sig = SinOsc.ar(Rand(400,2000));
		   env = EnvGen.kr(Env.perc(0.01,0.19),Impulse.kr(5));
                   pan = Pan4.ar(sig*env,x.lag(smt),y.lag(smt));
               Out.ar(0,pan)
               }
        ).add;

{~synth = Synth(\quadri)}.defer(0.1);
)

~synth.set(\x,0,\y,0);   // centro
~synth.set(\x,-1,\y,1);  // L1 - bus 0
~synth.set(\x,0, \y,1);  // centro al fondo
~synth.set(\x,1, \y,1);  // L2 - bus 1
~synth.set(\x,1, \y,0);  // centro a destra
~synth.set(\x,1,\y,-1);  // L4 - bus 3
~synth.set(\x, 0, \y,-1);// centro di fronte
~synth.set(\x,-1,\y,-1); // L3 - bus 2
~synth.set(\x, -1, \y,0);// centro a sinistra

~synth.set(\x,0,\y,0);   // centro
~synth.set(\x,rand2(1.0),\y,rand2(1.0));	

Se invece vogliamo pensare lo spazio come al punto 2 tra quelli descritti all'inizio del paragrafo precedente ovvero alla quadrifonia come specifico sistema di diffusione vale tutto quanto diremo nel paragrafo seguente riguardo pentafonia, esafonia e ottofonia anche se quattro diffusori soli non si prestano particolarmente a tecniche di panning circolare o dedicato.

GUI per la quadrifonia

Per quanto riguarda la quadrifonia realizzata con la UGen Pan4.ar() dove come abbiamo appena visto le posizioni della sorgente sono specificate sotto forma di coordinate cartesiane (x/y) entro i limiti di +/-1.0:

image not found

image not found

per il controllo e la visualizzazione possiamo utilizzare Slider2D.ar()) mappando le coordinate in modo diretto (x --> x e y --> y) riscalando in modo corretto i valori.

(
// ------------------------------ SynthDef e Synth

SynthDef(\quadri_gui,
              {arg x=0.0,y=0.0,dur=0.2,dist=1.0;
               var sig,xpos,ypos,sdist,env,pan;
                   sig = SinOsc.ar(Rand(400,2000));
	           xpos  = VarLag.kr(x,dur);
                   ypos  = VarLag.kr(y,dur);
                   sdist = Lag.kr(dist,dur);
                   env   = EnvGen.kr(Env.perc(0.01,0.19),Impulse.kr(5));
                   pan = Pan4.ar(sig*env,xpos,ypos);
				   
               SendReply.kr(Impulse.kr(50), '/pos', [xpos,ypos]);
               Out.ar(0,pan)
               }
        ).add;
	
{~synth = Synth(\quadri_gui)}.defer(0.1);

// ------------------------------ GUI

w = Window.new("Pan", 200@220);
w.alwaysOnTop;
w.front;
w.onClose_({~synth.free;w.free;e.free;c.free});

b = NumberBox.new(w, Rect(40,195,50,20)).value_(0);  // NumberBox X
c = NumberBox.new(w, Rect(120,195,50,20)).value_(0); // NumberBox Y
d = StaticText.new(w,Rect(25,198,180,15))            // Static text
 .string_("X:                  Y:");
e = Slider2D.new(w,  Rect(10, 10, 180, 180))         // Slider2D
 .x_(0.5).y_(0.5);

OSCdef.new(\vedi, {arg msg;
	         {b.value_(msg[3]);              // al Number Box X (+/-1)
		  c.value_(msg[4]);              // al Number Box Y (+/-1)
		  e.x_(msg[3].linlin(-1,1,0,1)); // alla x dello Slider2D
		  e.y_(msg[4].linlin(-1,1,0,1))  // alla y dello Slider2D
	          }.defer(0)
             },'/pos', s.addr);
		 
// ------------------------------ Operazioni di basso livello
	
e.action_({arg i;
           ~synth.set(\x,i.x.linlin(0,1,-1,1),            //  al Synth (+/-1)
                      \y,i.y.linlin(0,1,-1,1)); 
           });
	
MIDIIn.connectAll;                                
MIDIdef.cc(\knob, {arg val;                  
                   ~synth.set(\x,val.linlin(0,127,-1,1)); // al Synth (+/-1)
                   }, 16);                                // dal cc 16
MIDIdef.cc(\slid, {arg val;                  
                   ~synth.set(\y,val.linlin(0,127,-1,1)); // al Synth
                   }, 0);                                 // dal cc 0
)			   
// ------------------------------ Sequencing

~synth.set(\x,rand2(1.0),\y,rand2(1.0),\dur,rand(2.0));

Movimenti dinamici

Client side

Il codice appena illustrato 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. Per quanto riguarda l'invio di singoli valori vale quanto detto nel paragrafo corrispondente riguardante il panning stereo, ovvero possiamo specificare movimenti in qualsiasi punto nello spazio attraverso tre valori (x, y, durata):

(x:tra +/- 1.0,y:tra +/- 1.0, dur:tempo)

~synth.set(\x,0,\y,0,\dur,2);  // centro
~synth.set(\x,-1,\y,1,\dur,2); // L1 - bus 0
~synth.set(\x,0,\y,1,\dur,2);  // centro al fondo
~synth.set(\x,1,\y,1,\dur,2);  // L2 - bus 1
~synth.set(\x,1,\y,0,\dur,2);  // centro a destra
~synth.set(\x,1,\y,-1,\dur,2); // L4 - bus 3
~synth.set(\x,0,\y,-1,\dur,2); // centro di fronte
~synth.set(\x,-1,\y,-1,\dur,2);// L3 - bus 2
~synth.set(\x,-1,\y,0,\dur,2); // centro a sinistra

~synth.set(\x,0,\y,0,\dur,0);  // centro
~synth.set(\x,rand2(1.0),\y,rand2(1.0),\dur,rand(2.0));

Traiettorie.

(
var pos;
    pos = [[0,1,1],[-0.4,0.4,0.3],[0.6,-0.3,2],[0,0.4,1],[1,0,3],[-1,1,5]]; // [x,y,time]

h = Routine.new({
                 pos.do({arg i;
                         ~synth.set(\x,i[0],\y,i[1],\dur,i[2]);
                         i[2].wait
                         })
                 }).reset.play;
)

h.stop;~synth.set(\x,0,\y,0,\dur,0);  

Random.

(
var x,y,dur;

h = Routine.new({
                 inf.do({x = rand2(1.0);  
                         y = rand2(1.0);
                         dur = rrand(0.01,3);
                         ~synth.value(\x,x,\y,y,\dur,dur);
                         dur.wait                
                         })
                  }).reset.play
)

h.stop;~synth.set(\x,0,\y,0,\dur,0);  

Rota. In questo caso rappresenta una rotazione sequenziale tra gli altoparlanti che può avvenire sia in senso orario che antiorario e rappresenta una delle prime tecniche di spazializzazione utilizzate nella musica elettronica:

// Senso orario
(
var ciclo;
    ciclo = 4; // tempo di giro in secondi

h = Routine.new({

                 inf.do({var dur = ciclo*0.25;  // pos
                         ~synth.set(\x,-1,\y,1,\dur,dur);
                         dur.wait;
                         ~synth.set(\x,1,\y,1,\dur,dur);
                         dur.wait;
		         ~synth.set(\x,1,\y,-1,\dur,dur);
		         dur.wait;
		         ~synth.set(\x,-1,\y,-1,\dur,dur);
		         dur.wait;
                         })

                 }).reset.play
)

h.stop;~synth.set(\x,0,\y,0,\dur,0);

// Senso antiorario
(
var ciclo;
    ciclo = 4; // tempo di giro in secondi

h = Routine.new({

                 inf.do({var dur = ciclo*0.25;  // pos
                         ~synth.set(\x,-1,\y,1,\dur,dur);
                         dur.wait;
                         ~synth.set(\x,-1,\y,-1,\dur,dur);
                         dur.wait;
		         ~synth.set(\x,1,\y,-1,\dur,dur);
		         dur.wait;
			~synth.set(\x,1,\y,1,\dur,dur);
		         dur.wait;
                         })

                 }).reset.play
)

h.stop;~synth.set(\x,0,\y,0,\dur,0);

Spreading. Anche in questo caso basta aggiungere la coppia di valori che definisce l'asse delle x.

(
var rx,ry,dur;
    rx = [-1,0];
    ry = [1,0];
    dur = 0.1;

~synth.set(\dur,dur);

h = Routine.new({
                 inf.do({var x,y;
                         x = rrand(rx[0],rx[1]);
                         y = rrand(ry[0],ry[1]);
                         ~synth.set(\x,x,\y,y,\dur,dur);
                         dur.wait
                         })
                 }).reset.play
)

h.stop;~synth.set(\x,0,\y,0,\dur,0);

Devices OSC. Possiamo controllare facilmente la quadrifonia con un devices esterno o un altro software attraverso il protocollo OSC.

In questo caso scarichiamo e lanciamo il patch di Max dal quale contolleremo la quadrifonia in SuperCollider.

(
// ------------------------------ SynthDef e Synth

SynthDef(\quadri_gui,
              {arg x=0.0,y=0.0,dur=0.2,dist=1.0;
               var sig,xpos,ypos,sdist,env,pan;
                   sig = SinOsc.ar(Rand(400,2000));
	           xpos  = VarLag.kr(x,dur);
                   ypos  = VarLag.kr(y,dur);
                   sdist = Lag.kr(dist,dur);
                   env   = EnvGen.kr(Env.perc(0.01,0.19),Impulse.kr(5));
                   pan = Pan4.ar(sig*env,xpos,ypos);
				   
               SendReply.kr(Impulse.kr(50), '/posi', [xpos,ypos]);
               Out.ar(0,pan)
               }
        ).add;
	
{~synth = Synth(\quadri_gui)}.defer(0.1);

// ------------------------------ GUI

w = Window.new("Pan", 200@220);
w.alwaysOnTop;
w.front;
w.onClose_({~synth.free;w.free;b.free;c.free;d.free;e.free});

b = NumberBox.new(w, Rect(40,195,50,20)).value_(0);  // NumberBox X
c = NumberBox.new(w, Rect(120,195,50,20)).value_(0); // NumberBox Y
d = StaticText.new(w,Rect(25,198,180,15))            // Static text
 .string_("X:                  Y:");
e = Slider2D.new(w,  Rect(10, 10, 180, 180))         // Slider2D
 .x_(0.5).y_(0.5);

OSCdef.new(\vedi, {arg msg;
	         {b.value_(msg[3]);              // al Number Box X (+/-1)
		  c.value_(msg[4]);              // al Number Box Y (+/-1)
		  e.x_(msg[3].linlin(-1,1,0,1)); // alla x dello Slider2D
		  e.y_(msg[4].linlin(-1,1,0,1))  // alla y dello Slider2D
	          }.defer(0)
             },'/posi', s.addr);
			 
OSCdef.new(\daMax, {arg msg;
                     ~synth.set(\x, msg[1].linlin(0,127,-1,1),  // al Synth
                                \y, msg[2].linlin(0,127,-1,1));},
					'/pos', recvPort:57120);
		 
// ------------------------------ Operazioni di basso livello
	
e.action_({arg i;
           ~synth.set(\x,i.x.linlin(0,1,-1,1),            //  al Synth (+/-1)
                      \y,i.y.linlin(0,1,-1,1)); 
           });
)

Server side

Segnali

Se vogliamo utilizzare segnali di controllo possiamo modificare il codice realizzato per il panning stereofonico, aggiungendo un secondo segnale di controllo per il parametro y con i propri argomenti specifici e sostituendo la GUI per la visualizzazione con uno Slider2D:

(
// ------------------------------ SynthDef e Synth

SynthDef(\quad_sig,
              {arg tipoX=1,velX=0.5,sprdX=1,initposX= 0,
                   tipoY=1,velY=0.5,sprdY=1,initposY= 0.5pi,
                   smt=0.2;
               var sig,env,ksigs,x,y,pan;
                   sig = SinOsc.ar(Rand(400,2000));
		   env = EnvGen.kr(Env.perc(0.01,0.19),Impulse.kr(5));
                   x   = Select.kr(tipoX,[LFTri.kr(velX,initposX,sprdX),
                                          SinOsc.kr(velX,initposX,sprdX),
                                          LFNoise0.kr(velX,sprdX),
                                          LFNoise1.kr(velX,sprdX),
                                          LFNoise2.kr(velX,sprdX),
			                              ]);
                   y   = Select.kr(tipoY,[LFTri.kr(velY,initposY,sprdY),
                                          SinOsc.kr(velY,initposY,sprdY),
                                          LFNoise0.kr(velY,sprdY),
                                          LFNoise1.kr(velY,sprdY),
                                          LFNoise2.kr(velY,sprdY),
			                              ]);
                   SendReply.kr(Impulse.kr(50), '/pos', [x,y]); // Invia Slider2D
                   pan = Pan4.ar(sig*env,x,y);
               Out.ar(0,pan)
               }
        ).add;

{~synth = Synth(\quad_sig)}.defer(0.1);

// ------------------------------ GUI

w = Window.new("Pan", 210@210);
w.alwaysOnTop;
w.front;
w.onClose_({~synth.free;w.free;d.free;a.free});

d = Slider2D.new(w, Rect(5, 5, 200, 200));          // Slider2D

OSCdef.new(\vedi, {arg msg;
             {d.x_(msg[3].linlin(-1,1,0,1));     // solo all'interfaccia
              d.y_(msg[4].linlin(-1,1,0,1))}.defer(0);
              },'/pos', s.addr);
)

~synth.run(false);
~synth.run(true);

Nel codice sottostante alcune combinazioni tipiche della musica elettronica:

~synth = Synth(\quad_sig,[\tipoX, 3, \tipoY, 3]).run;        // Random
~synth.free;
~synth = Synth(\quad_sig,[\initposX,0,\initposY,0.5pi]).run; // Senso orario
~synth.run(false);
~synth = Synth(\quad_sig,[\initposX,0.5pi,\initposY,0]).run; // Senso anti-orario
~synth.run(false);
~synth.set(\tipoX,rand(4),\tipoY,rand(4)).run;               // Combinazioni di segnali
~synth.run(false);
~synth.set(\tipoX,1,\tipoY,1,\velX,rrand(0.2,1),\velY,rrand(0.2,1)).run; // Combinazioni di velocità

Infine un controllo misto con una GUI dove un Knob controlla la velocità di rotazione mentre uno slider la distanza dal centro:

(
// ------------------------------ SynthDef e Synth
	
SynthDef(\quad_sig,
              {arg tipoX=1,velX=0.5,sprdX=1,initposX= 0,
                   tipoY=1,velY=0.5,sprdY=1,initposY= 0.5pi,
                   smt=0.2;
               var sig,env,ksigs,x,y,pan;
                   sig = SinOsc.ar(Rand(400,2000));
		   env = EnvGen.kr(Env.perc(0.01,0.19),Impulse.kr(5));
                   x   = Select.kr(tipoX,[LFTri.kr(velX,initposX,sprdX),
                                          SinOsc.kr(velX,initposX,sprdX),
                                          LFNoise0.kr(velX,sprdX),
                                          LFNoise1.kr(velX,sprdX),
                                          LFNoise2.kr(velX,sprdX),
			                              ]);
                   y   = Select.kr(tipoY,[LFTri.kr(velY,initposY,sprdY),
                                          SinOsc.kr(velY,initposY,sprdY),
                                          LFNoise0.kr(velY,sprdY),
                                          LFNoise1.kr(velY,sprdY),
                                          LFNoise2.kr(velY,sprdY),
			                              ]);
                   SendReply.kr(Impulse.kr(50), '/pos', [x,y]); // Invia Slider2D
                   pan = Pan4.ar(sig*env,x,y);
               Out.ar(0,pan)
               }
        ).add;

{~synth = Synth(\quad_sig)}.defer(0.1);

// ------------------------------ GUI
		
w = Window.new("Pan", 270@210);
w.alwaysOnTop;
w.front;
w.onClose_({~synth.free;w.free;b.free;c.free;d.free});

b = Knob.new(w,     Rect(210, 5, 50, 50));   // Knob
c = Slider.new(w,   Rect(210, 55, 50, 150)); // Slider
d = Slider2D.new(w, Rect(5, 5, 200, 200));   // Slider2D
	
// ------------------------------ Operazioni di basso livello

b.action_({arg i; ~synth.set(\velX,i.value.linlin(0,1,0.1,5),  // solo al Synth
                             \velY,i.value.linlin(0,1,0.1,5))});
c.action_({arg i; ~synth.set(\sprdX,i.value,\sprdY,i.value)});
	
OSCdef.new(\vedi, {arg msg;
                    {d.x_(msg[3].linlin(-1,1,0,1));            // solo all'interfaccia
                     d.y_(msg[4].linlin(-1,1,0,1))}.defer(0);
                     },'/pos', s.addr);
MIDIIn.disconnectAll; 	
MIDIIn.connectAll;                                
MIDIdef.cc(\knob, {arg val;                  
                   ~synth.set(\velX,val.linlin(0,127,0.1,5),  // al Synth
                              \velY,val.linlin(0,127,0.1,5)); 
                   {b.value_(val.linlin(0,127,0,1))}.defer(0) // al Knob                 
                   }, 16);                                    // dal cc 16
MIDIdef.cc(\slid, {arg val;                  
                   ~synth.set(\sprdX,val.linlin(0,127,0,1),   // al Synth
                              \sprdY,val.linlin(0,127,0,1));
                   {c.value_(val.linlin(0,127,0,1))}.defer(0) // allo Slider
                   }, 0);                                     // dal cc 0
)

Inviluppi

Anche per quanto riguarda gli inviluppi possiamo modificare il codice già illustrato per il panning stereofonico:

(
// ------------------------------ SynthDef e Synth

SynthDef(\penvi,
              {arg t_gate=0;
               var sig,env,x,xbpf,xsenv,y,ybpf,ysenv,pan;
                   sig = SinOsc.ar;
		   env = EnvGen.kr(Env.perc(0.01,0.19),Impulse.kr(5));
				   
                   x   = Env.newClear(4);          // Crea un inviluppo vuoto di 4 nodi
                   xbpf= \x.kr(x.asArray);         // Crea un controllo dell'inviluppo
                   xsenv= EnvGen.kr(xbpf, t_gate); // Genera l'inviluppo

                   y   = Env.newClear(4);
                   ybpf= \y.kr(y.asArray);
                   ysenv= EnvGen.kr(ybpf, t_gate);

                   pan = Pan4.ar(sig*env,xsenv,ysenv);
			       SendReply.kr(Impulse.kr(50), '/pos', [xsenv,ysenv]);
               Out.ar(0,pan)
               }
          ).add;


{~synth = Synth(\penvi)}.defer(0.1);

// ------------------------------ GUI

w = Window.new("Pan", 210@210);
w.alwaysOnTop;
w.front;
w.onClose_({~synth.free;w.free;d.free});

d = Slider2D.new(w, Rect(5, 5, 200, 200)); // Slider2D

OSCdef.new(\vedi, {arg msg;
                    {d.x_(msg[3].linlin(-1,1,0,1));            
                     d.y_(msg[4].linlin(-1,1,0,1))}.defer(0);
                     },'/pos', s.addr);
)

// ------------------------------ Sequencing

(
h = 2;                                     // Definiamo una durata
u = {[{rand2(1.0)}!4, {rand(0.5)}!3]}!2;   // generiamo i valori di x e y
u.postln;                                  // stampa i valori
x = Env.new(u[0][0],u[0][1]).duration_(h); 
y = Env.new(u[1][0],u[1][1]).duration_(h); 
[x,y].plot;                               // Plotter
~synth.set(\x, x, \y, y, \t_gate,1);      // li inviamo al Synth
)

Mouse

Il codice per il controllo del pan quadrifonico con il mouse è molto semplice:

(
SynthDef(\pmouse,
              {var sig,env,x,y,pan;
                   sig = SinOsc.ar;
	           env = EnvGen.kr(Env.perc(0.01,0.19),Impulse.kr(5));
                   x   = MouseX.kr(-1,1);
                   y   = MouseY.kr(-1,1); // vicino in basso
                   pan = Pan4.ar(sig*env,x,y);
               Out.ar(0,pan)
               }
          ).add;

{a = Synth(\pmouse)}.defer(0.1);
)

Anche in questo caso non è necessaria una GUI in quanto lo spazio è delimitato dallo schermo del computer.

Sistemi circolari

Un sistema di diffusione del suono che rappresenta ormai uno degli standard multicanale è quello composto da un numero variabile di altoparlanti disposti in modo circolare attorno al pubblico. Può essere un particolare tipo di quadrifonia oppure se gli altoparlanti sono cinque pentafonia, sei esafonia, sette eptafonia, otto ottofonia e via dicendo fino a a 128 canali.

Questi tipi di sistemi possono essere controllati in SuperCollider attraverso una sola UGen: PanAz.ar() indipendentemente dal numero di altoparlanti impiegato e sottointende principalmente il modo di pensare lo spazio descritto nel punto 2 tra quelli esposti all'inizio del paragrafo precedente ovvero pensando il sistema di diffusione sonora come un vero e proprio strumento musicale con le sue caratteristiche e le sue tecniche strumentali. In seguito esamineremo le tipologie di diffusione più utilizzate, ma i principi espressi valgono anche per tutte le altre configurazioni da 3 a 128 canali.

Pentafonia, esafonia e ottofonia

Utilizziamo queste tre configurazioni ampiamente diffuse per descrivere nel dettaglio i parametri espressi come argomenti di PanAz.ar().

In questo caso la posizione della sorgente non è specificata attraverso coordinate cartesiane (x/y) come per Pan4.ar() ma segue un sistema derivato dalle coordinate polari dove lo spazio non è delimitato da un quadrato ma da un cerchio e sia gli altoparlanti che i bus audio non seguono una disposizione a coppie stereofoniche come nel caso della quadrifonia appena descritta ma circolare:

image not found

Nel dettaglio gli argomenti sono:

PanAz.ar(n_chans, sig, pos, level, width, orientation)

Per quanto riguarda l'ottofonia che è diventato ormai il sistema di diffusione standard per la musica elettroacustica vale tutto quanto riportato per pentafonia ed esafonia, basterà solo cambiare il primo argomento di PanAz.ar() definendo correttamente il numero di altoparlanti utilizzato.

Nelle immagini seguenti alcune configurazioni tipiche con i codici più propri per controllare lo spazio:

image not found

s.options.numOutputBusChannels_(68;
s.reboot;
s.plotTree;
s.scope;      
s.meter;

(
SynthDef(\ottof,
              {arg pos= 0,lev=1,dist=1,orient=0.5,dur=0.2;
               var nchans=8,sig,env,spos,slev,sdist,pan;
                   sig = SinOsc.ar(Rand(400,2000));
		   env = EnvGen.kr(Env.perc(0.01,0.19),Impulse.kr(5));
		   spos= VarLag.kr(pos,dur);
		   slev= Lag.kr(lev,dur);
		   sdist=VarLag.kr(dist.linexp(0,1,nchans,2),dur);
                   pan = PanAz.ar(nchans, sig*env, spos, slev, sdist, orient);
				   
               Out.ar(0,pan)
               }
        ).add;
{~synth = Synth(\ottof,[\orient,0.5])}.defer(0.1);
)

~synth.set(\pos,0);   // fondo centro (bus 0/1)
~synth.set(\pos,0.25);// fondo destra (bus 1/2)
~synth.set(\pos,0.5); // laterale destra (bus 2/3)
~synth.set(\pos,0.75);// frontale destra (bus 3/4)
~synth.set(\pos,1);   // frontale centro (bus 4/5)
~synth.set(\pos,1.25);// frontale sinistra (bus 5/6)
~synth.set(\pos,1.5); // laterale sinistra (bus 6/7)
~synth.set(\pos,1.75);// fondo sinistra (bus 7/0)
~synth.set(\pos,2);   // fondo centro (bus 0/1)	

image not found

~synth = Synth(\ottof,[\orient,0.0]);

~synth.set(\pos,0.00, \dist, 1); // bus 0
~synth.set(\pos,0.25, \dist, 1); // bus 1
~synth.set(\pos,0.50 ,\dist, 1); // bus 2 
~synth.set(\pos,0.75, \dist, 1); // bus 3	
~synth.set(\pos,1.00, \dist, 1); // bus 4
~synth.set(\pos,1.25, \dist, 1); // bus 5
~synth.set(\pos,1.50, \dist, 1); // bus 6
~synth.set(\pos,1.75, \dist, 1); // bus 7
~synth.set(\pos,2.00, \dist, 1); // bus 0

GUI per sistemi circolari

Per visualizzare la sorgente nei sistemi di diffusione circolare come quelli appena esposti possiamo utilizzare Slider2D.ar(). Partiamo da alcune considerazioni:

  1. Abbiamo visto che PanAz.ar() accetta posizioni espresse in un sistema simile a quello delle coordinate polari dove un valore indica il raggio di un ipotetico cerchio (ρ - rho) mentre un altro valore l'angolo in gradi tra 0° e 360° (θ - theta) e che in un sistema esafonico preso come esempio può essere rappresentato nel modo seguente:

    image not found

  2. Slider2D.ar() invece accetta valori espressi in coordinate cartesiane (x/y) e la differenza tra i due sistemi ci costringe a:

    • Specificare le posizioni in valori compresi tra 0 e 2 per quanto riguarda il θ (theta) e tra 0 e 1 per quanto riguarda il ρ (rho) come stabilito in precedenza.
    • Inviare questi valori a PanAz.ar() come illustrato in precedenza.
    • Convertire i valori che ritornano via OSC da coordinate polari a cartesiane.
    • Inviare a Slider2D.ar() i valori convertiti.
  3. Per convertire le coordinate da polari a cartesiane dobbiamo esprimere le posizioni (θ - theta) di un punto sull'angolo giro (la circonferenza nell'immagine precedente) in valori compresi tra 0π e 2π:

    image not found

    In SuperCollider dobbiamo allora:

    • aggiungere il π ai valori di θ (theta) che già utilizziamo per PanAz.ar()
    • traslare il punto 0π di -90° sottraendo 0.5 in quanto abbiamo stabilito che la posizione 0.0 deve coincidere con il punto centrale al fondo (a metà tra bus 0 e bus 1 o coincidente con bus 0 a seconda dell'argomento orientation di PanAz.ar())

      image not found

    • rendere negativo il π per invertire il verso dell'incremento dei valori da antiorario a orario.
      (
      var theta,radianti;
      theta    = 0;   // testare con valori compresi tra 0 e 2
      radianti = theta - 0.5 * -pi
      )
      

      image not found

    • applicare le formule di conversione da coordinate polari a cartesiane:

      x = ρ*cos(θ)
      y = ρ*sin(θ)

      (
      a = {arg theta=0,rho=1;
           var x,y;
      
           x = rho * cos(theta -0.5* -pi); // tra +/- 1
           y = rho * sin(theta -0.5* -pi); // tra +/- 1
      
      ["x: "++x.round(0.01),"y: "++y.round(0.01)]     // stampa
      }
      )
      a.value(0,1);
      

      ottenendo i valori x/y che possiamo inviare a Slider2D.ar().

      image not found

Il codice per generare l'interfaccia è lo stesso usato in precedenza per la quadrifonia, l'unica differenza consiste nel fatto che in questo caso a causa delle peculiarità del tipo di panning non è possibile interagire con il mouse sullo Slider2D ma solo visualizzare la posizione della sorgente. Possiamo invece utilizzare una combinazione di 2 GUI: Knob (per il theta) e Slider (per il rho) sia per l'interazione con il mouse che per la visualizzazione dei valori provenienti da controller MIDI o OSC esterni:

image not found

(
// ------------------------------ SynthDef e Synth

SynthDef(\ottof,
              {arg pos= 0,lev=1,dist=1,orient=0.5,dur=0.2;
               var nchans=8,sig,env,spos,slev,sdist,pan;
                   sig = SinOsc.ar(Rand(400,2000));
		   env = EnvGen.kr(Env.perc(0.01,0.19),Impulse.kr(5));
		   spos= VarLag.kr(pos,dur);
		   slev= Lag.kr(lev,dur);
		   sdist=VarLag.kr(dist.linexp(0,1,nchans,2),dur);
                   pan = PanAz.ar(nchans, sig*env, spos, slev, sdist, orient);
				   
	       SendReply.kr(Impulse.kr(50), '/pos', [spos,1-sdist.linlin(2,nchans,0,1)]); // theta (0-2) e rho (0-1)
               Out.ar(0,pan)
               }
        ).add;

{~synth = Synth(\ottof)}.defer(0.1);

// ------------------------------ GUI

w = Window.new("Pan", 250@220);
w.alwaysOnTop;
w.front;
w.onClose_({~synth.free;w.free;b.free;c.free;d.free;e.free;f.free;g.free});

b = NumberBox.new(w, Rect(48, 195, 50, 20)).value_(0);   // NumberBox theta
c = NumberBox.new(w, Rect(140, 195, 50, 20)).value_(0);  // NumberBox rho
d = StaticText.new(w, Rect(10, 198, 180, 15))            // Static text
 .string_("theta:                  rho:");
e = Slider2D.new(w, Rect(10, 10, 180, 180))              // Slider2D
 .x_(0.5).y_(1);
f = Knob.new(w, Rect.new(195,10,50,50));
g = Slider.new(w, Rect.new(195,90,50,100));

OSCdef.new(\vedi, {arg msg;
             var theta,rho,x,y;
	         theta = msg[3];
	         rho = msg[4];
	         x = rho * cos(theta -0.5* -pi); // tra +/- 1
	         y = rho * sin(theta -0.5* -pi); // tra +/- 1
				 
             {b.value_(theta);                          // alle diverse GUI
              c.value_(rho);                  

              e.x_((rho * cos(theta -0.5* -pi)).linlin(-1,1,0,1)); 
              e.y_((rho * sin(theta -0.5* -pi)).linlin(-1,1,0,1));
              f.value_(theta.linlin(-1,1,0,1));
              g.value_(rho);
              }.defer(0);			 
				 
             },'/pos', s.addr);

// ------------------------------ Operazioni di basso livello

f.action_({arg val; ~synth.set(\pos, val.value * 2 - 1, \dur, 0.02)});
g.action_({arg val; ~synth.set(\dist, val.value, \dur, 0.02)});

MIDIIn.disconnectAll;
MIDIIn.connectAll;

MIDIdef.cc(\knob, {arg val; ~synth.set(\pos, val/127 * 2 - 1, \dur, 0.02)}, 16);                                 
MIDIdef.cc(\slid, {arg val; ~synth.set(\dist, val/127, \dur, 0.02)}, 0);                    
)

// ------------------------------ Sequencing

a = SystemClock.sched(0, {~synth.set(\pos,rand2(1.0),\dist,rand(1.0),\dur,0.3); 0.3});
a.clear;

Movimenti dinamici

Client Side

Il codice appena illustrato 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. Per quanto riguarda l'invio di singoli valori vale quanto detto nel paragrafo corrispondente riguardante il panning stereo, ovvero possiamo specificare movimenti in qualsiasi punto nello spazio attraverso tre valori (theta, rho, durata):

(theta:tra +/- 1.0,rho:tra 0 e 1, dur:tempo)

~synth.set(\pos,0.00,\dist,1,\dur,1);
~synth.set(\pos,0.25,\dist,1,\dur,1);
~synth.set(\pos,0.50,\dist,1,\dur,1);
~synth.set(\pos,0.75,\dist,1,\dur,1);
~synth.set(\pos,1.00,\dist,1,\dur,1);
~synth.set(\pos,1.25,\dist,1,\dur,1);
~synth.set(\pos,1.50,\dist,1,\dur,1);
~synth.set(\pos,1.75,\dist,1,\dur,1);
~synth.set(\pos,2.00,\dist,1,\dur,1);

~synth.set(\pos,0,\dist,0,\dur,1);
~synth.set(\pos,rand2(1.0),\dist,rand(1.0),\dur,rrand(0.1,2))

Traiettorie.

(
var pos;
    pos = [[0,1,1],[-0.4,0.4,0.3],[-0.6,0.3,2],[0,0.4,1],[1,0,3],[-1,1,5]]; // [theta,rho,time]

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;

Random.

(
var theta,rho,dur;

h = Routine.new({
                 inf.do({theta = rand2(1.0);  
                         rho = rand(1.0);
                         dur = rrand(0.01,3);
                         ~synth.set(\pos,theta,\dist,rho,\dur,dur);
                         dur.wait                
                         })
                  }).reset.play
)

h.stop;

Rota. In questo caso rappresenta una rotazione sequenziale tra gli altoparlanti che può avvenire sia in senso orario che antiorario e rappresenta una delle prime tecniche di spazializzazione utilizzate nella musica elettronica:

// Senso orario
(
var dur, valO;
    dur  = 1;   // tempo di giro in secondi
    valO = 0;   // posizione iniziale   
h = Routine.new({
                 inf.do({
		         valO = valO+2;
		         ~synth.set(\pos,valO,\dist,1,\dur,dur);
		         dur.wait;
                         })

                 }).reset.play;
)

h.stop;
h.reset.play;

// Senso antiorario
(
var dur, valO;
    dur  = 1;   // tempo di giro in secondi
    valO = 0;   // posizione iniziale   
h = Routine.new({
                 inf.do({
		         valO = valO-2;
		         ~synth.set(\pos,valO,\dist,1,\dur,dur);
		         dur.wait;
                         })

                 }).reset.play;
)

h.stop;
h.reset.play;

Spreading. Anche in questo caso basta aggiungere la coppia di valori che definisce l'asse delle x.

(
var rth,rrh,dur,smt;
    rth = [0,0.5];
    rrh = [1,0.5];
    dur = 0.1;
    smt = 0.1;  // prova a cambiare

~ksynth.value(smt:smt);

h = Routine.new({
                 inf.do({var theta,rho;
                         theta = rrand(rth[0],rth[1]);
                         rho = rrand(rrh[0],rrh[1]);
                         ~synth.set(\pos,theta,\dist,rho,\dur,dur);
                         dur.wait
                         })
                 }).reset.play
)

h.stop;

Server side

Segnali

Se vogliamo utilizzare segnali di controllo possiamo anche in questo caso modificare il codice realizzato per il panning stereofonico e quadrifonico, specificando un segnale di controllo bipolare (+/- 1.0) per il parametro pos e valori fissi o un secondo segnale di controllo riscalato in un ambito compreso tra il numero di canali e 2 per il parametro dist:

(
// ------------------------------ SynthDef e Synth

SynthDef(\circ_sig,
              {arg tipoS=0,velS=1,sprdS=1,initpoS= 0,
                   lev = 1,
                   tipoD=0,velD=1,sprdD=1,initpoD=1,
                   orient=0.5,
                   smt=0.2;
               var nchans=8,sig,env,pos,dist,pan;
                   sig = SinOsc.ar(Rand(400,2000));
		   env = EnvGen.kr(Env.perc(0.01,0.19),Impulse.kr(5));
                   pos   = Select.kr(tipoS,[LFSaw.kr(velS,initpoS,sprdS),    // orario
                                            LFSaw.kr(velS,initpoS,sprdS)* -1,// antiorario
                                            SinOsc.kr(velS,initpoS,sprdS),
                                            LFNoise1.kr(velS,sprdS),
                                            LFNoise2.kr(velS,sprdS)
                                           ]);
                   dist = Select.kr(tipoD,[initpoD.lag(smt),              // fissa
                                           LFTri.kr(velD,initpoD,sprdD),
                                           SinOsc.kr(velD,initpoD,sprdD),
                                           LFNoise1.kr(velD,sprdD),
                                           LFNoise2.kr(velD,sprdD),
			                              ]);
                   SendReply.kr(Impulse.kr(50), '/pos', [pos,dist]);
                   pan = PanAz.ar(nchans, sig*env, pos, lev.lag(smt),
                                  dist.linexp(0,1,nchans,2),             // riscalato
                                  orient);
               Out.ar(0,pan)
               }
        ).add;

{~synth = Synth(\circ_sig)}.defer(0.1);

// ------------------------------ GUI

w = Window.new("Pan", 270@210);
w.alwaysOnTop;
w.front;
w.onClose_({~synth.free;w.free;b.free;c.free;d.free});

b = Knob.new(w,     Rect(210, 5, 50, 50));   // Knob
c = Slider.new(w,   Rect(210, 55, 50, 150)); // Slider
d = Slider2D.new(w, Rect(5, 5, 200, 200));   // Slider2D

// ------------------------------ Operazioni di basso livello

b.action_({arg i; ~synth.set(\velS,i.value.linlin(0,1,0.1,5),  // solo al Synth
                             \velD,i.value.linlin(0,1,0.1,5))});
c.action_({arg i; ~synth.set(\tipoD,0,\initpoS,i.value,\initpoD,i.value)});

OSCdef.new(\vedi, {arg msg;

           {b.value_(msg[3]);                                 // alle diverse GUI
            c.value_(msg[4]);
            d.x_((msg[4] * cos(msg[3] -0.5* -pi)).linlin(-1,1,0,1));
            d.y_((msg[4] * sin(msg[3] -0.5* -pi)).linlin(-1,1,0,1))
            }.defer(0)

            },'/pos', s.addr);
MIDIIn.disconnectAll;
MIDIIn.connectAll;
MIDIdef.cc(\knob, {arg val;
                   ~synth.set(\velS,val.linlin(0,127,0.1,5),
                              \velD,val.linlin(0,127,0.1,5));
                   }, 16);                                    // dal cc 16
MIDIdef.cc(\slid, {arg val;
                   ~synth.set(\tipoD,0,                       // al Synth
                              \initpoS,val.linlin(0,127,0,1),
                              \initpoD,val.linlin(0,127,0,1));
                   }, 0);                                     // dal cc 0
)

~synth.set(\tipoS,3,\tipoD,3);

Da notare che quando interagiamo sullo slider dedicato alla distanza sia con il mouse che con un controller esterno, automaticamente viene selezionato il tipo 0 (\tipoD o singolo valore).

Inviluppi

Se vogliamo utilizzare un qualsiasi tipo di inviluppo, basterà sostituire Select.kr() dei parametri pos e dist con un'istanza di inviluppo così come già fatto per il panning stereofonico e quadrifonico:

(
// ------------------------------ SynthDef e Synth

SynthDef(\circ_sig,
              {arg t_gate,
                   lev = 1,
                   orient=0.5,
                   smt=0.2;
               var nchans=8,sig, env,
                   pos,posbpf,posEnv,
                   dist,distbpf,distEnv,
                   pan;

                   sig = SinOsc.ar(Rand(400,2000));
		   env = EnvGen.kr(Env.perc(0.01,0.19),Impulse.kr(5));
                   pos   = Env.newClear(4);           // Crea un inviluppo vuoto di 4 nodi
                   posbpf= \pos.kr(pos.asArray);      // Crea un controllo dell'inviluppo
                   posEnv= EnvGen.kr(posbpf, t_gate); // Genera l'inviluppo
  
                   dist = Env.newClear(4);
                   distbpf= \dist.kr(dist.asArray);
                   distEnv= EnvGen.kr(distbpf, t_gate);

                   SendReply.kr(Impulse.kr(50), '/pos', [posEnv,distEnv]);
                   pan = PanAz.ar(nchans,
                                  sig*env,
                                  posEnv,
                                  lev.lag(smt),
                                  distEnv.linexp(0,1,nchans,2),
                                  orient);
               Out.ar(0,pan)
               }
        ).add;

{~synth = Synth(\circ_sig)}.defer(0.1);

// ------------------------------ GUI

w = Window.new("Pan", 210@210);
w.alwaysOnTop;
w.front;
w.onClose_({~synth.free;w.free;d.free;h.free;p.free;u.free});

d = Slider2D.new(w, Rect(5, 5, 200, 200));   // Slider2D

// ------------------------------ Operazioni di basso livello

OSCdef.new(\vedi, {arg msg;
             {d.x_((msg[4] * cos(msg[3] -0.5* -pi)).linlin(-1,1,0,1));
              d.y_((msg[4] * sin(msg[3] -0.5* -pi)).linlin(-1,1,0,1))}.defer(0)
             },'/pos', s.addr)
)

// ------------------------------ Sequencing

(
var pos,dist;
h = 2;                                         // Definiamo una durata
p = [{rand2(1.0)}!4, {rand(0.5)}!3];           // generiamo i valori di pos
u = [{rand2(1.0)}!4, {rand(0.5)}!3];           // generiamo i valori di dist
pos  = Env.new(p[0],p[1]).duration_(h);
dist = Env.new(u[0],u[1]).duration_(h);
[pos,dist].plot;                               // Plotter
~synth.set(\pos, pos, \dist, dist, \t_gate,1); // li inviamo al Synth
)

Mouse

Se infine vogliamo utilizzare il mouse per controllare i movimenti, possiamo mappare l'asse delle x sulle rotazioni di pos definendo un numero di giri per range e l'asse delle y sulla distanza, riscalata come sempre tra il numero di canali e 2:

(
// ------------------------------ SynthDef e Synth
SynthDef(\pmouse,
              {var nchans=8,sig,env,pos,dist,pan;
                   sig = SinOsc.ar;
		   env = EnvGen.kr(Env.perc(0.01,0.19),Impulse.kr(5));
                   pos  = MouseX.kr(-5,5);    // numero di giri sull'asse x
                   dist = MouseY.kr(0,1);
                   SendReply.kr(Impulse.kr(50), '/pos', [pos,dist]);
                   pan = PanAz.ar(nchans,sig*env,pos,1,dist.linexp(0,1,nchans,2));
               Out.ar(0,pan)
               }
          ).add;

{a = Synth(\pmouse)}.defer(0.1);

// ------------------------------ GUI

w = Window.new("Pan", 210@210);
w.alwaysOnTop;
w.front;
w.onClose_({~synth.free;w.free;d.free});

d = Slider2D.new(w, Rect(5, 5, 200, 200));   // Slider2D

// ------------------------------ Operazioni di basso livello

OSCdef.new(\vedi, {arg msg;
             {d.x_((msg[4] * cos(msg[3] -0.5* -pi)).linlin(-1,1,0,1));
              d.y_((msg[4] * sin(msg[3] -0.5* -pi)).linlin(-1,1,0,1))}.defer(0)
             },'/pos', s.addr)
)

SplayAz

Infne possiamo utilizzare la SplayAz.kr() per posizionare un'Array di segnali in punti equidistanti all'interno di un sistema di diffusione multicanale. I suoi argomenti sono simili al "fratello minore" Splay e può essere utilizzato efficacemente per aprire o chiudere dinamicamente la disposizione di segnali nel sistema (suono diffuso/suono direzionale):

(
SynthDef(\stereo,
              {arg sprd,ctr=0;
               var nchans=8,sig,env,width,lev=1,orient=0.5,pan;
		           sig = [SinOsc.ar(800),Saw.ar(1000),WhiteNoise.ar];
		           env = EnvGen.kr(Env.perc(0.01,0.19),Impulse.kr(5));
		           width = MouseX.kr(2,6);
		           sprd = MouseY.kr(0,1);
                   pan = SplayAz.ar(nchans, // numero di canali del sistema
                                    sig*env,    // Array di canali mono
                                    sprd,   // spread 0 = tutti al centro
                                    lev,    // ampiezza globale (0. / 1.)
                                    width,  // su quanti canali i segnali sono distribuiti
                                    ctr,    // shift dal centro
                                    orient, // orientazione
                                    true);  // limiter...
               Out.ar(0,pan)
               }
        ).add;

{~synth = Synth(\stereo)}.defer(0.1);
)

Sistemi dedicati

Oltre ai sistemi di diffusione multicanale "neutri" appena esposti e ai loro simili possiamo anche progettare uno spazio acustico dedicato alle caratteristiche musicali o drammaturgiche del brano che vogliamo diffondere posizionando altoparlanti dedicati in posizioni specifiche come già illustrato per i sistemi multi-monofonici:

image not found

In una configurazione di questo tipo ad esempio potremmo avere tutti segnali monofonici indipendenti (uno per bus) oppure diffondere e muovere una o più sorgenti con Pan4.ar() su gruppi di altopalranti come L1, L2, L3 e L4 mentre il loro riverbero o altre elaborazioni degli stessi segnali su L5, L6 e L7 muovendoli con PanAz.ar() specificando tre canali e che il primo bus di uscita è il numero 4. L'imporante è che questo tipo di diffusione deve essere strettamente correlato alle esigenze espressive e musicali del brano e non un semplice artifizio applicato a qualsiasi tipo di musica solo per generare effetti speciali.