Tipologie di controllo

La tastiera 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).

A differenza del mouse se utilizziamo questo strumento possiamo solamente ottenere un controllo discreto (trigger) di eventi anche se questo può avvenire in due modalità esecutive differenti:

Nello specifico ambiente di SuperCollider abbiamo due diverse possibilità per recuperare diverse informazioni sulle azioni compiute sulla tastiera per poi eventualmente mappare i valori ottenuti su di un qualche parametro di sintesi o di elaborazione del suono:

Negli esempi che seguono per favorire la comprensione controlleremo dinamicamente di volta in volta parametri elementari del suono come ampiezza e frequenza di onde sonore semplici, ricordiamo però che queste tipologie di controllo valgono per qualsiasi parametro di un qualsiasi algoritmo di sintesi.

Client side

Se vogliamo interagire con la tastiera 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 premendo sui tasti si scrivano involontariamente frasi senza senso nel codice di SuperCollider o in altre applicazioni eventualmente aperte nel corso di una performance aprendo così la possibilità a spiacevoli imprevisti.

Ad ogni metodo corrisponde un'azione differente:

.keyDownAction_({})

Valuta la funzione specificata come argomento ogni volta che premiamo un tasto:

(
w = Window.new("key");
w.view.keyDownAction_({arg ...args;
	                      args.postln;
	                      args[1].postln
                       });
w.front;
w.alwaysOnTop_(true);
w.onClose_({w.free})
)

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

In questo tipo di controllo interattivo sono in genere utilizzate le più comuni operazioni condizionali per assegnare un preciso evento a un determinato tasto (mapping). Osserviamo le diverse possibilità:

if(test, {vero}, {falso})

Se utilizziamo esclusivamente due tasti o due condizioni per triggerare due eventi possiamo utilizzare l'operatore condizionale if:

switch(valore,valore_test1, {func_1})

Se vogliamo mappare più di due tasti assegnando a ciascuno il trigger di un evento differente invece che assegnare un if ad ogni tasto possiamo utilizzare l'operatore condizionale switch():

// --------- switch(valore da comparare,
//                  valore_selettore, {func},
//                  valore_selettore, {func},
//                  valore_selettore, {func},
//                  ...,              {...});

(
s.boot;
s.meter(2);
s.plotTree;

w = Window.new("key");
w.view.keyDownAction_({arg ...args;
	               switch(args[1],           
		                    $q, {b.play;        "SoundFile".postln},
		                    $w, {Synth(\noise); "Noise".postln},
		                    $e, {Synth(\blip);  "Blip".postln});
                       });
w.front;
w.alwaysOnTop_(true);
w.onClose_({w.free});

b = Buffer.read(s, Platform.resourceDir +/+ "sounds/a11wlk01.wav");
SynthDef(\noise, {Out.ar(0,ClipNoise.ar*EnvGen.ar(Env.perc,doneAction:2))}).add;
SynthDef(\blip, {Out.ar(0,Blip.ar*EnvGen.ar(Env.triangle,doneAction:2))}).add;
)		

Counter

L'operatore switch() è utile anche nel programmare un contatore che è spesso usato per far avanzare e resettare i preset in alcune tecniche classiche di controllo del Live Electronics nelle quali se si preme la barra spaziatrice si passa in sequenza al preset successivo mentre il tasto enter riporta il preset 0 (di inizializzazione).

(
~count = 0;               // inizializza la variabile
w = Window.new("key");
w.view.keyDownAction_({arg ...args;
	               switch(args[3],
		                    32, {~count = ~count + 1}, // tasto 'spazio' + 1
		                    13, {~count = 0});         // tasto 'enter' resetta
		       ~count.postln;
                       });
w.front;
w.alwaysOnTop_(true);
w.onClose_({w.free})
)		

Ci sono tecniche che utilizzano counters più complessi ma saranno affrontate in un Paragrafo dedicato.

.keyUpAction_({})

Esattamente come .keyDownAction_({}) ma riporta le informazioni al rilascio del tasto:

(
w = Window.new("key");
w.view.keyUpAction_({arg ...args;
	                      args.postln;
	                      args[1].postln
                       });
w.front;
w.alwaysOnTop_(true);
w.onClose_({w.free})
)

Filtro valori On/Off

Se volessimo utilizzare i tasti come se fossero tasti del pianoforte (note On/off) ovvero far partire un evento quando si preme un tasto (.keyDownAction_({})), tenerlo premuto per una durata a piacere e far terminare l'evento al suo rilascio (.keyUpAction_({})) incorreremmo in un problema: mentre il tasto è premuto gli argomenti non sono restituiti una sola volta ma continuano ad arrivare a un tempo di scheduling diventando nella maggior parte dei casi dati ridondanti.

(
s.boot;
s.meter(2);
s.plotTree;

w = Window.new("key");

w.view.keyDownAction_({arg ...args;
	                   if(args[1]==$q, {a.set(\t_gate,1)}); // 'q' = Note On
                       });

w.view.keyUpAction_({arg ...args;
	                 if(args[1]==$q,{a.set(\t_gate,0)});    // Note Off
                     });
w.front;
w.alwaysOnTop_(true);
w.onClose_({w.free;a.free});

SynthDef(\if_1, {arg t_gate=0;
	         var sig, env;
	             sig = Saw.ar;
	             env = EnvGen.ar(Env.perc,t_gate);
	         Out.ar(0,sig*env);
}).add;

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

Per ovviare a questo inconveniente possiamo programmare un semplice filtro di dati effettuando un test condizionale su due valori consecutivi nel tempo: valore_corrente e valore_precedente:

Se il valore corrente è diverso dal valore precedente esegui la funzione, altrimenti non fare nulla:

(
var ora, prec = 0;                           // inizializza la variabile

w = Window.new("key");
w.view.keyDownAction_({arg ...args;
                       ora = args[3];
		       if((ora != prec),     // se diverso dal precedente
		           {"noteOn".postln; // esegue
			    prec = ora });   // riassegna 'prec' all'ultimo
                       });                   // valore ricevuto
					   
w.view.keyUpAction_({arg ...args;
                      prec = 0;         // reset Array (altrimenti se si rischiaccia
                                        // lo stesso tasto non funziona...)
                     "noteOff".postln;  // Azione
                     });            
w.front;
w.alwaysOnTop_(true);
w.onClose_({w.free})
)	

Ovviamente questo filtro può essere applicato a tutte le strutture di controllo illustrate poco sopra.

(
s.boot;
s.meter(2);
s.plotTree;

~ora;
~prec = 0;

w = Window.new("key");

w.view.keyDownAction_({arg ...args;                        // 'q' = Note On
	               ~ora = args[1];
	               if(
		          (~ora == $q).and(~ora != ~prec), // se uguale a 'q' AND diverso dal precedente
		          {a.set(\gate,1);                 
			   ~prec = ~ora})
                       });

w.view.keyUpAction_({arg ...args;        // Note Off
	             if(~ora==$q,
		        {a.set(\gate,0);
			 ~prec = 0});    // Reset
                     });
w.front;
w.alwaysOnTop_(true);
w.onClose_({w.free;a.free});

SynthDef(\if_1, {arg gate=0;
	         var sig, env;
	             sig = Saw.ar;
	             env = EnvGen.ar(Env.adsr,gate);
	         Out.ar(0,sig*env);
}).add;

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

Server side

Nel caso volessimo interagire con la tastiera del computer per generare direttamente un segnale di controllo possiamo utilizzare la UGen KeyState.kr():

KeyState.kr(keycode, minval, maxval, tempo_di_Lag)

Anche se priva di utilità diretta è meglio creare una GUI per evitare di scrivere nell'Interprete di SuperCollider utilizzando i tasti come controllo, osserviamo inoltre come sia necessario conoscere il keycode per poter scegliere il tasto o i tasti con i quali interagire.

(
SynthDef(\key,
            {var amp,sig;
	         gate = KeyState.kr(35,0,1,2);   // tasto 'p'
	         sig = SinOsc.ar;
             Out.ar(0,sig*amp)
}).add;

{Synth(\key)}.defer(0.1)
)