Modalità

In SuperCollider possiamo generare interfacce grafiche (GUI) all'interno delle quali posizionere una o più icone come sliders, knobs, number boxes, etc. Queste interfacce possono essere utilizzate in due diversi modi:

Se le utilizziamo solamente come monitor visivi è sempre meglio programmarle in modo che siano indipendenti e rimovibili in qualsiasi momento sia rispetto ai segnali audio che alle diverse modalità di controllo (sequencing, devices esterni, mouse, tastiera, etc.).

Window

Per creare una GUI la prima cosa dobbiamo fare consiste nel generare un'istanza della Classe Window. Questa sarà il contenitore all'interno del quale posizionare tutti gli oggetti grafici desiderati. I primi due argomenti sono un nome sotto forma di stringa e un'istanza di Rect() che specifica il posizionamento sullo schermo del computer e le dimensioni della finestra in pixels:

Rect.new(sinistra: 0, alto: 0, larghezza:0, altezza:0)

L'argomento sinistra:0 corrisponde al lato sinistro dello schermo mentre alto:0 al margine inferiore dello schermo facendo coincidere in questo caso l'angolo inferiore sinistro della finestra con quello del computer.

(
w = Window.new("CiaoCiao",         // Nome che compare sulla barra in alto
               Rect(0,0,300,110)); // Posizione e size in pixels	
)

Assegnamo sempre la finestra a una variabile sia per poter invocare su di essa alcuni metodi molto utili le cui funzioni si possono desumere facilmente dal codice sia per poter in seguito inviare e ricevere valori dall'Interprete in modo dinamico:

w.front;                           // la fa comparire
w.alwaysOnTop_(true);              // la fa sempre stare davanti alle altre finestre sullo schermo
w.onClose_( {w.free} );            // questo metodo esegue la funzione che è suo argomento quando
                                   // chiudiamo la finestra dal pallino rosso

image not found

Possiamo inoltre modificare alcuni parametri grafici:

w.name_("urca");          // Cambiare il titolo
w.background_(Color.red); // Vedere l'help file di Color
w.alpha_(rand(1.0));      // Indice di trasparenza tra 0.0 e 1.0

Oggetti grafici di controllo

All'interno della Window possiamo inserire tutti i principali elementi grafici utili al controllo e alla visualizzazione di parametri (Client side):

image not found

(
w = Window.new("CiaoCiao", Rect(0,0,300,110)); 
w.front;                          
w.alwaysOnTop_(true);              
w.onClose_({w.free;h.free;n.free;k.free;b.free;p.free;d.free;t.free;z.free});            

h = Slider.new(w, Rect(5,5,50,100));            // Crea uno slider x = 0 = sinistra / y = 0 = alto (in pixels)
n = NumberBox.new(w, Rect(65,5, 50,20));        // Crea un number box
k = Knob.new(w, Rect(65,30, 75,75));            // Crea un knob
b = Button.new(w, Rect(120,5, 50,20));          // Crea un number box
p = PopUpMenu.new(w, Rect(175,5, 100,20));      // Crea un pop up menu
d = Slider2D.new(w, Rect(145,30, 75,75));       // Crea un interfaccia XY
t = StaticText.new(w, Rect(225,30, 75,75));     // Crea un testo
z = MultiSliderView.new(w,Rect(5,110, 290,75)); // Crea un multislider
)

Anche in questo caso dobbiamo assegnare ogni elemento grafico a una variabile per poter successivamente inviare o recuperare valori da esso.

I primi due argomenti sono comuni a tutti: la Window all'interno della quale vogliamo posizionare l'elemento e un'istanza di Rect().

Prestiamo attenzione che il valore 0 del punto all'interno della Window che definisce la posizione y non corrisponde al basso ma all'alto. Rect(0,0) definisce quindi l'angolo in alto a sinistra della Window.

Ogni oggetto grafico può sia inviare (nel caso di un'interazione con esso) che ricevere (nel caso di visualizzazione) dati dall'Interprete. Questi poi saranno eventualmente inviati al Server attraverso le consuete modalità:

image not found

Le sintassi di questi percorsi sono simili per tutti gli oggetti. Vediamole nel dettaglio

Recuperare valori dalle GUI

Interagendo con il mouse su un oggetto grafico possiamo modificarne i valori in uscita. Questi possono essere recuperati nell'Interprete invocando il metodo .action_({}). La funzione argomento di questo metodo sarà valutata ad ogni azione compiuta sullo slider mentre il valore in uscita sarà recuperato attraverso il mentodo .value specificato al suo interno:

(
w = Window.new("Slider", Rect.new(0,0,265,210));
h = Slider.new(w, Rect(5,5,50,200));
w.front;                          
w.alwaysOnTop_(true);              
w.onClose_({w.free;h.free});

h.action_({h.value.postln});  // Recupera valore
)

Possiamo assegnare a una variabile il valore recuperato per poterlo riutilizzare in qualsiasi modo all'interno del codice, tipicamente per inviarlo a un Synth:

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

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

w = Window.new("Slider", Rect.new(0,0,60,210));
h = Slider.new(w, Rect(5,5,50,200));   

w.front;                          
w.alwaysOnTop_(true);              
w.onClose_({w.free;h.free;u.free});

h.action_({var val;           // Assegna il valore a una variabile
               val = h.value; // per poterlo riutilizzare nel codice
	       val.postln;
	   u.set(\amp, val)   // Invia il valore al Server
           });

{u = Synth(\slider)}.defer(0.2);
)	

Inviare valori alle GUI

Possiamo modificare dinamicamente lo stato dello slider inviando valori dall'Interprete all'oggetto con il metodo .value_(). Se inviamo un valore alla volta non c'è alcun problema, ma se utilizziamo tecniche di sequencing dobbiamo obbligatoriamente lavorare con AppClock oppure includere il comando in {n.value(val)}.defer. Il range di default è compreso tra 0.0 e 1.0 per quasi tutti gli elementi grafici.

h.value_(rand(1.0)); // Singolo valore

(
r = Routine.new({    // Sequencing
	         inf.do({
		         h.value_(rand(1.0)); 
		         0.1.wait
                         })
                 }).reset.play(AppClock); 
)

(
r = Routine.new({
	         inf.do({
		         {h.value_(rand(1.0))}.defer; 
		         0.1.wait
                         })
                 }).reset.play; 
)

r.stop;	

Il metodo .value_() invia il valore dall'Interprete alla GUI ma non genera alcuna azione (come avviene quando interagiamo con il mouse). Se necessitiamo di entrambe le cose dobbiamo sostituirlo con .valueAction_()

h.action_({h.value.postln}); // Recupera il valore

h.valueAction_(rand(1.0));   // Invia il valore

(                            // Sequencing
r = Routine.new({
	         inf.do({
		         {h.value_(rand(1.0))}.defer; 
		         0.1.wait
                         })
                 }).reset.play; 
)

r.stop;

// Provare ad interagire con il mouse sullo Slider...

In questo modo potremo scegliere indifferentemente la modalità di controllo dei parametri senza dover riscrivere il codice a seconda delle diverse situazioni.

Visualizzazione valori MIDI/OSC

Possiamo visualizzare i valori in ingresso generati dall'interazione con devices esterni che comunicano col computer attraverso il protocollo MIDI o l'OSC. In questo tipo di visualizzazione possiamo adottare due differenti strategie di programmazione:

Visualizzazione elementi di controllo

Slider.new()

Questo oggetto raffigura un classico slider (o fader) e può essere sia verticale che orizzontale. In entrambi i casi i valori sono compresi in un range di default che va da 0.0 a 1.0.

image not found

(
w = Window.new("Slider", Rect.new(0,0,265,210));
h = Slider.new(w, Rect(5,5,50,200));   
v = Slider.new(w, Rect(60,155,200,50));

w.front;                          
w.alwaysOnTop_(true);              
w.onClose_({w.free;h.free});
)	

Per ulteriori info ed esempi possiamo consultare l'Help file.

Knob.new()

Questo oggetto raffigura un classico knob. I valori sono compresi in un range di default che va da 0.0 a 1.0.

image not found

(
w = Window.new("Knob", Rect.new(0,0,120,120));
h = Knob.new(w, Rect(5,5,100,100));   

w.front;                          
w.alwaysOnTop_(true);              
w.onClose_({w.free;h.free});
)	

Per ulteriori info ed esempi possiamo consultare l'Help file.

NumberBox.new()

Questo oggetto visualizza valori numerici sia interi (int) che decimali (float).

image not found

(
w = Window.new("Num", Rect.new(0,0,110,30));
h = NumberBox.new(w, Rect(5,5,100,20));   

w.front;                          
w.alwaysOnTop_(true);              
w.onClose_({w.free;h.free});
)	

Per ulteriori info ed esempi possiamo consultare l'Help file.

Button.new()

Questo oggetto visualizza un pulsante che può contenere del testo e avere più "stati". Ogni "stato" può contenere un testo differente e avere colori differenti. Per impostarli dobbiamo invocare il metodo .states_([[0],[1],[2]]) ed utilizzare la sintassi illustrata.

image not found

(
w = Window.new("Num", Rect.new(0,0,160,60));
h = Button.new(w, Rect(5,5,150,50));

h.states_([["stato 0",Color.black, Color.red],
           ["stato 1",Color.white, Color.black],
	   ["stato 2",Color.red, Color.white]]
         );

w.front;
w.alwaysOnTop_(true);
w.onClose_({w.free;h.free});
)

Per ulteriori info ed esempi possiamo consultare l'Help file.

PopUpMenu.new()

Questo oggetto visualizza un menù a comparsa che contiene diversi items testuali. Cliccando sull'oggetto compare un menu dal quale li possiamo selezionare singolarmente. Per impostarli dobbiamo invocare il metodo .items_(["","",""]) ed utilizzare la sintassi illustrata.

image not found

(
w = Window.new("Menu", Rect.new(0,0,160,60));
h = PopUpMenu.new(w, Rect(5,5,150,50));

h.items_(["ciao",\miao,"uao",\bau,\cau]); // Array di simboli o stringhe
h.clear; // Rimuove tutti gli items

w.front;
w.alwaysOnTop_(true);
w.onClose_({w.free;h.free});
)

Per ulteriori info ed esempi possiamo consultare l'Help file.

StaticText.new()

Questo oggetto visualizza un testo all'interno di una cornice posizionata nella Window. Per scrivere il testo dobbiamo invocare il metodo .string_("...") ed utilizzare la sintassi illustrata.

image not found

(
w = Window.new("Text", Rect.new(0,0,500,300));
h = StaticText.new(w, Rect(20,20,460,260));

h.string_(" questo è cio che scrivo ");

w.front;
w.alwaysOnTop_(true);
w.onClose_({w.free;h.free});
)	

Slider2D.new()

Questo oggetto visualizza un cursore posizionato all'interno di un piano cartesiano. Sull'asse delle ascisse (x) il valore 0.0 = sinistra mentre 1.0 = destra mentre sull'asse delle ordinate (y) 0.0 = basso e 1.0 = alto.

image not found

(
w = Window.new("Slider 2D", Rect.new(0,0,300,300));
h = Slider2D.new(w, Rect(5,5,290,290));

w.front;
w.alwaysOnTop_(true);
w.onClose_({w.free;h.free;r.free});
)

Per ulteriori info ed esempi possiamo consultare l'Help file.

MultiSliderView.new()

Questo oggetto visualizza un insieme di Sliders. Il range di ogni Slider sull'asse delle ordinate (y) è compreso tra 0.0 e 1.0.

image not found

A differenza degli altri oggetti grafici in questo caso dobbiamo assegnare la larghezza a una variabile per poter calcolare in modo dinamico la larghezza del singolo slider in base al numero di slider che desideriamo visualizzare. Per visualizzare gli sliders dobbiamo invocare il metodo .value_([]) ed utilizzare la sintassi illustrata.

(
~larg = 300;                    // Larghezza in Pixels
w = Window.new("Multi", Rect.new(0,0,~larg + 10,110));
h = MultiSliderView.new(w, Rect(5,5,~larg,100));

w.front;
w.alwaysOnTop_(true);
w.onClose_({w.free;h.free;n.free;v.free});
)

(
n = 20;                           // Numero di sliders
h.elasticMode_(1);                // 1 = distribuisce sull'asse delle ascisse
h.indexThumbSize_(~larg / n - 2); // Calcola automaticamente la larghezza del singolo slider - 2 pixels

v = Array.rand(n,0.0,1.0);        // genera un Array di n valori
h.value_(v);                      // Invia i valori alla GUI
)

Per ulteriori info ed esempi possiamo consultare l'Help file.

FlowLayout

Se l'interfaccia che vogliamo costruire non è complessa oppure se deve contenere molti diplicati dello stesso oggetto possiamo utilizzare la Classe FlowLayout.new() o la sua abbreviazione sintattica illustrata nel codice seguente.

// Oggetti diversi
(
w = Window.new("Multi", Rect.new(0,0,220,250));
w.addFlowLayout( 5@20,     // Margini dalla Window x@y
	         5@5 );    // Spazio tra gli oggetti x@y

h = Slider.new(w, 50@100); // Specificare solo le dimensioni
n = Slider.new(w, 50@100);
d = Slider2D.new(w, 100@100);
p = PopUpMenu.new(w, 210@20);
z = MultiSliderView.new(w, 210@75);

w.front;
w.alwaysOnTop_(true);
w.onClose_({w.free;h.free;n.free;d.free;p.free;z.free});
)

//=========================================================
// Copie multiple dello stesso oggetto

(
w = Window.new("Multi", Rect.new(0,0,220,220));
w.addFlowLayout( 20@20,  // Margini dalla Window x@y
	             5@5 ); // Distanze tra gli oggetti x@y

16.do{Slider2D(w.view,40@40).background_(Color.rand)};

w.front;
w.alwaysOnTop_(true);
w.onClose_({w.free});
)

Per ulteriori info ed esempi possiamo consultare l'Help file.

Visualizzazione segnali

Abbiamo anche la possibilità di generare interfacce grafiche per visualizzare e in alcuni casi interagire con segnali audio sia memorizzati su supporto che generati o elaborati in tempo reale.

SoundFileView.new()

Questo oggetto visualizza un sound file memorizzato su supporto.

Quando utilizziamo questa Classe non visualizziamo il contenuto di un Buffer ma un Sound file che possiamo eventualmente caricare in un buffer.

image not found

(
f = SoundFile.openRead(Platform.resourceDir +/+ "sounds/a11wlk01.wav");  // Crea un'istanza di Soundfile

w = Window.new("SoundFileView", Rect.new(0,0,550,210)); 
a = SoundFileView.new(w, Rect(5,5, 540, 200));  

a.soundfile_(f);                                // Assegna il Sounfile alla visualizzazione
a.read(0, f.numFrames);                         // Legge il file dal frame 0 all'ultimo,
                                                // N.B. Per file molto lunghi è meglio usare: a.readWithTask;
a.refresh;                                      // Ripulisce e visualizza

w.front;                          
w.alwaysOnTop_(true);              
w.onClose_({w.free;a.free});
)	

EnvelopeView.new()

Questo oggetto visualizza dinamicamente gli inviluppi. Se visualizziamo inviluppi con il metodo .plot() SuperCollider crea automaticamente una nuova Window per ogni inviluppo (o per ogni Array di inviluppi) mentre se utilizziamo questa Classe creiamo una sola Window alla quale inviare i parametri dell'inviluppo.

image not found

(
w = Window.new("EnvelopeView", 500@400);     
e = EnvelopeView.new(w, Rect(10,10,480,380));

h = Env.new([0,1,0.4,0],[0.2,0.5,1],[3,-2.3,1.5]);
~dur = h.duration; // recupera la durata dell'inviluppo (può servire)
e.setEnv(h);                           

w.front;  
w.alwaysOnTop_(true);                                                                  
w.onClose_({e.free;w.free;h.free;r.stop});                
)