Tipologie di controllo

Il mouse del computer può diventare uno strumento di controllo dinamico di uno o più parametri di un qualsiasi algoritmo di sintesi o di elaborazione del suono (interazione uomo/macchina).

Utilizzando questo strumento abbiamo a disposizione due possibili tipologie di controllo:

Il mouse può diventare sia uno strumento musicale indipendente che un semplice strumento di servizio utile all'interazione con oggetti grafici (GUI).

Nello specifico ambiente di SuperCollider abbiamo tre diverse possibilità per recuperare i valori di alcuni parametri del mouse per poi eventualmente mapparli su di un qualche parametro di sintesi o di elaborazione del suono:

Client side

Se vogliamo interagire con il mouse del computer e recuperare i valori nell'Interprete dobbiamo obbligatoriamente creare una finestra GUI per poi invocare su di essa diversi metodi. Questo è necessario per evitare che cliccando sullo schermo si selezionino involontariamente altre applicazioni nel corso di una performance aprendo così la possibilità a spiacevoli inprevisti.

Ad ogni metodo corrisponde un'azione differente:

.mouseDownAction_({})

Valuta la funzione specificata come argomento ogni volta che clicchiamo sul bottone del mouse (trigger):

(
w = Window.new("mouse");
w.view.mouseDownAction_({arg ...args;
                                args.postln;    // tutte le info
			        args[1].postln; // solo posizione x
                       });
w.front;
w.alwaysOnTop_(true);
w.onClose_({w.free});
)	

In questo caso ogni volta che clicchiamo con un tasto del mouse otteniamo un Array i cui items corrispondono a precise informazioni e che possimo richiamare singolarmente con i metodi [].at(id) oppure Array[id]:

Se recuperiamo i singoli items e li mappiamo su diversi parametri di uno o più algoritmi di sintesi abbiamo a disposizione uno strumento di controllo piuttosto complesso.

(
s.boot;
s.scope(1);
s.plotTree;

w = Window.new("mouse");
w.view.mouseDownAction_({arg ...args;
                         if(args[4] == 0,
                         {Synth(\sine,[\freq, args[1].linlin(0,400,300,2000), // Se clicco tasto sx...
				\amp,1-args[2].linlin(0,400,0,1.0),
				\t_gate, 1])}, 
		         {a.set(\amp, 1-args[2].linlin(0,400,0,1.0))}) // Se clicco tasto dx...                             
                         });
w.front;
w.alwaysOnTop_(true);
w.onClose_({w.free;s.freeAll});

SynthDef(\sine , {arg freq=0, amp=0, t_gate=0;
	          var sig,env;                                      // Con inviluppo d'ampiezza
	              sig = SinOsc.ar(freq); 
	              env = EnvGen.kr(Env.perc,t_gate,doneAction:2);
	          Out.ar(0,sig*env*amp);
                  }).add;
SynthDef(\noise , {arg amp=0;
	               var sig;
	                   sig = PinkNoise.ar;
	               Out.ar(0,sig*amp.lag(3)); // fade dell'ampiezza di 3 secondi
                  }).add;
{a = Synth(\noise)}.defer(0.2);
)	

.mouseUpAction_({})

Esattamente come il precedente ma riporta le informazioni al rilascio del tasto.

(
w = Window.new("mouse");
w.view.mouseUpAction_({arg ...args;
                              args.postln;    // tutte le info
			      args[1].postln; // solo posizione x
                       });
w.front;
w.alwaysOnTop_(true);
w.onClose_({w.free});
)	

Possiamo ad esempio utilizzarlo in combinazione con .mouseDownAction_({}) come note On/note Off del generatore di rumore rosa nell'esempio precedente:

(
s.boot;
s.scope(1);
s.plotTree;

w = Window.new("mouse");
w.view.mouseDownAction_({arg ...args;
                         if(args[4] == 0,
                         {Synth(\sine,[\freq, args[1].linlin(0,400,300,2000),                 
				\amp,1-args[2].linlin(0,400,0,1.0),
				\t_gate, 1])}, 
		         {a.set(\amp, args[2].linlin(0,400,0,1.0))}) // note On (proporzioni invertite)                            
                         });
w.view.mouseUpAction_({arg ...args;
                       a.set(\amp,0) // note Off                             
                      });
w.front;
w.alwaysOnTop_(true);
w.onClose_({w.free;s.freeAll});

SynthDef(\sine , {arg freq=0, amp=0, t_gate=0;
	          var sig,env;                                      
	              sig = SinOsc.ar(freq); 
	              env = EnvGen.kr(Env.perc,t_gate,doneAction:2);
	          Out.ar(0,sig*env*amp);
                  }).add;
SynthDef(\noise , {arg amp=0;
	               var sig;
	                   sig = PinkNoise.ar;
	               Out.ar(0,sig*amp.lag(0.2)); // fade di 0.2 secondi
                  }).add;
{a = Synth(\noise)}.defer(0.2);
)		

.mouseMoveAction_({})

Riporta le informazioni solo quando muoviamo il mouse con il bottone premuto ritornando solo i primi quattro argomenti: [view,x,y,modifiers]. A differenza dei due controlli precedenti che potevano essere utilizzati solo come triggers di valori discreti in questo caso abbiamo anche un flusso continuo di valori limitrofi.

(
w = Window.new("mouse");
w.view.mouseMoveAction_({arg ...args;
                                args.postln;    // tutte le info
			        args[1].postln; // solo posizione x
                       });
w.front;
w.alwaysOnTop_(true);
w.onClose_({w.free});
)	

Nell'esempio seguente viene illustrata una combinazione di controlli discreti (tirgger) e continui (numeric stream) nonchè una gestione corretta della polifonia (voice allocation)

(
s.boot;
s.scope(1);
s.plotTree;

w = Window.new("mouse");
w.view.mouseDownAction_({arg ...args;                  // note On (trigger)
                         a = Synth(\spectra, [\freq, args[1].linlin(0,400,300,2000),
		                              \gate, 1]);
                         });
w.view.mouseMoveAction_({arg ...args;                 // controllo continuo
	                 a.set(\amp,1-args[2].linlin(0,400,0,1.0),
		               \harm, args[1].linlin(0,400,1,20))
                       });
w.view.mouseUpAction_({arg ...args;
                       a.set(\gate,0)                  // note Off (trigger)
                      });
w.front;
w.alwaysOnTop_(true);
w.onClose_({w.free;s.freeAll});

SynthDef(\spectra, {arg freq=0, amp=0, harm=1, gate=0;
	             var sig,env;
	                 sig = Blip.ar(freq,harm);
	                 env = EnvGen.kr(Env.asr(0.3,1,5),gate,doneAction:2);
	             Out.ar(0,sig*env*amp);
                   }).add;
)	

.mouseOverAction_({})

Come il precedente ma riporta le informazioni quando muoviamo il mouse sulla Window anche senza premere il bottone ritornando solo i primi tre argomenti [view,x,y]. In questo caso otteniamo solo un flusso di dati e non un trigger.

Quando creiamo la window dobbiamo invocare su di essa il metodo .acceptsMouseOver_(true);

(
w = Window.new("mouse").acceptsMouseOver_(true);// bisogna specificare;
w.view.mouseOverAction_({arg ...args;
                                args.postln;    // tutte le info
			        args[1].postln; // solo posizione x
                       });
w.front;
w.alwaysOnTop_(true);
w.onClose_({w.free});
)	

Passando sopra la finestra effettueremo uno scratch.

(
s.boot;
s.scope(1);
s.plotTree;

w = Window.new("mouse").acceptsMouseOver_(true); 
w.view.mouseOverAction_({arg ...args;
	                     a.set(\pos, args[1].linlin(0,400,0,1),    // posizione x
		                   \amp,1-args[2].linlin(0,400,0,1.0)) // posizione y
                       });
w.front;
w.alwaysOnTop_(true);
w.onClose_({w.free;a.free;b.free});

b = Buffer.read(s, Platform.resourceDir +/+ "sounds/a11wlk01.wav");

SynthDef(\scratch, {arg pos=0, amp=0, buf=0, smooth=0.2;
	            var sig,punt;
	                punt = BufFrames.ir(buf)*K2A.ar(pos).varlag(smooth);
	                sig = BufRd.ar(1,b,punt);
	            Out.ar(0,sig*amp.lag(0.2))
        }).add;

{a = Synth(\scratch, [\buf,b])}.defer(0.2);
)		

Server side

Se vogliamo recuperare i valori dell'interazione con il mouse direttamente nel Server sotto forma di segnali di controllo abbiamo a disposizione tre diverse UGens e non è necessario creare una GUI:

Tutte e tre hanno gli stessi argomenti ad eccezione della curva che in MouseButton.kr() ovviamente non può essere specificata:

MouseX.kr(valore a sinistra, valore a destra, curva, tempo_di_Lag)

{[MouseX.kr(0,1,0,0.2), MouseX.kr(0.0001,1,1,0.2)]}.scope; // lineare vs esponenziale
{[MouseX.kr(0,1,0,5),   MouseX.kr(0.0001,1,1,5)]}.scope;   // lag time di 5 secondi...	

Ecco un esempio di J.Parmenter che utilizza due delle UGens appena illustrate:

(
s.boot;
s.scope(1);
s.plotTree;

a = Buffer.read(s, "/Applications/SuperCollider/SuperCollider.app/Contents/Resources/sounds/a11wlk01.wav");
SynthDef(\scratch,
	              {arg gate = 1, buffer;
	               var buf, speed, env;
	                   env   = Env.new([0,1,0], [0.1, 0.1], \sin, 1).kr(20,gate);
	                   speed = MouseX.kr(-10, 10);
	                   speed = speed - DelayN.kr(speed, 0.1, 0.1);
	                   speed = MouseButton.kr(1, 0, 0.3) + speed ;
	                   buf = PlayBuf.ar(1, buffer, speed * BufRateScale.kr(buffer), loop: 1);
	               Out.ar(0, (buf * env).dup );
}).add;

{b = Synth(\scratch, [\buffer, a])}.defer(0.3);
)

b.set(\gate, 0); a.free; b.free;	

Server to Client

Se infine vogliamo utilizzare i valori generati delle UGens appena illustrate (ad esempio per visualizzare su una GUI l'interazione) dobbiamo inviare messaggi OSC dal Server all'Interprete attraverso l'utilizzo congiunto della UGen SendReply.kr() e della Classe OSCFunc.new({}):

(
s.boot;
s.meter(2);
s.plotTree;
// ------------------------------ SynthDef

SynthDef(\mouseval,
                   {var x,y,click,sig;
		        x = MouseX.kr(0,1);           // Panpot...
		        y = MouseY.kr(0.0001,1,1);    // Ampiezza...no 0...
		        click = MouseButton.kr.round; // Note on/off

		        SendReply.kr(Impulse.kr(50),  // rata di campionamento
		                     '/pos',          // indirizzo o nome
		                     [x, y, click]);  // segnali da campionare

		        sig = Pan2.ar(SinOsc.ar*y*click,x*2-1); // Audio
		    Out.ar(0,sig)
	            }
         ).add;

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

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

w = Window.new("mouse", Rect(0,0,210,210));
h = Slider2D.new(w, Rect(5,5,200,200));
h.knobColor_(Color.green);
w.front;
w.alwaysOnTop_(true);
w.onClose_({a.free;h.free;w.free});

// ------------------------------ OSC
OSCFunc.new({arg msg;
	         msg.postln;     // stampa il messaggio OSC
			 
	         {h.x_(msg[3]);  // assegna i singoli valori
	          h.y_(msg[4]);
			  
                  if(msg[5] == 1,
	                  {h.knobColor_(Color.red)},
	                  {h.knobColor_(Color.green)})
					  
	         }.defer;        // AppCLock per messaggi dinamici alla GUI
             },
		 '/pos',         // indirizzo o nome
		 s.addr);        // eventuale NetAddr
)