Un esempio di codice relativo alla creazione di un semplice elemento grafi- co permette di introdurre i fondamenti della sintassi di SC. Il codice presentato si propone di creare un elemento grafico a rotativo, la cui rotazione controlli in forma parametrica il colore dello sfondo.
SuperCollider è dotato di una ricca tavolozza di elementi grafici che per- metto di costruire interfacce grafiche (GUI) sofisticate. Dunque, se è vero che l’interfaccia utente di SC è testuale in fase di implementazione dei propri pro- getti, non lo è necessariamente in fase di utilizzo del software, ad esempio nel
3 L’utilità del metodo sta nel fatto che i segnali audio multicananle, rappresenta-
bili attraverso un array, sono memorizzati in questa forma, “interallacciata”. Se il segnale è stereo, plot con numChannels:2 disegna la forma d’onda dei segnali sui due canali.
va programmata. E il caso delle GUI è un classico della programmazione a og- getti, perché a ben vedere un elemento GUI si presta in maniera semplice ad essere definito come oggetto (è una entità dai chiari confini, con proprietà e comportamenti). La gestione GUI in SuperCollider avviene attraverso una li- breria molto diffusa, Qt, che viene compilata insieme all’IDE. Molto semplice- mente, è subito disponibile per essere utilizzata. Si noterà però che quando si fa uso di un GUI si utilizza non l’applicazione SuperCollider (IDE), ma invece sclang, a cui fanno riferimento (anche in termini di focus) le GUI. In termini di meccanismo di comunicazione/rappresentazione, gli oggetti GUI in Qt sono rappresentati in SC da classi, ad esempio Window è la classe SC che rappresenta un oggetto finestra in Qt. Se si ricorda che si tratta di un linguaggio, è come dire che Finestra è la rappresentazione concettuale e linguistica (un segno dotato di caratteristiche di espressione e di contenuto) di una categoria di oggetti del mondo (un tipo di serramento). Passando al programma:
1 (
2 /* accoppiamento di view e controller */
4 var window, knob, screen ; // dichiarazione delle variabili usate
6 // una finestra contenitore
7 window = Window.new("A knob", Rect.new(300,300, 150, 100)) ;
8 window.background = Color.black ;
10 // una manopola nella finestra, range: [0,1]
11 knob = Knob.new(window, Rect(50, 25, 50, 50)) ;
12 knob.value = 0.5 ;
14 // azione associata a knob
15 knob.action_({ arg me;
16 var red, blue, green ;
17 red = me.value ;
18 green = red*0.5 ;
19 blue = 0.25+(red*0.75) ;
20 ["red, green, blue", red, green, blue].postln ;
21 window.background = Color(red, green, blue);
22 });
24 // non dimenticarmi
25 window.front ;
• 1: il blocco di codice è racchiuso tra parentesi tonde (1 e 26);
• 3: un commento multilinea è utilizzato a mo’ di titolo (2) e sono presenti altri commenti che forniscono alcune informazioni sulle diverse parti del codice (ad esempio, 6). Commentare il codice è ovviamente opzionale ma è notoriamente buona prassi: permette di fornire informazioni generali sulla struttura e eventualmente dettagli sull’implementazione;
• 4: a parte i commenti, il codice inizia con la dichiarazione delle tre variabili utilizzate. Sono appunto in testa al blocco di codice valutato;
• 7-8: la prima cosa da fare è creare una finestra contenitore, cioè un oggetto di riferimento per tutti gli altri elementi grafici che verranno creati in seguito. È un approccio tipico nei sistemi di creazione di GUI. Alla variabile window viene assegnato un oggetto Window, generato attraverso il metodo costrutto- re new. Al metodo new vengono passati due argomenti (nuova finestra, con questi attributi): una stringa che indica il titolo visualizzato dalla finestra ("A knob" e un oggetto di tipo Rect. In altri termini, la dimensione della fi- nestra, invece di essere descritta un insieme di parametri, è descritta da un oggetto rettangolo, cioè da un’istanza della classe Rect, dotato di suoi attri- buti, cioè , cioè left, top width, height. La posizione del rettangolo è data dall’angolo in alto a sinistra (argomenti left e top), mentre width e height specificano le dimensioni. Il rettangolo stabilisce allora che la finestra sarà di 150x100 pixel, il cui angolo superiore sinistro è nel pixel (300,300). Si noti che Rect.new restituisce un oggetto (new è un costruttore) ma senza assegnarlo a variabile. Infatti, da un lato non serve che l’oggetto abbia una identifica- bilità al di fuori della GUI finestra, dall’altro in realtà rimane accessibile a future manipolazioni, poiché viene memorizzato attraverso una proprietà della finestra, bounds. Se si valuta window.bounds si può notare come venga restituito proprio quel oggetto Rect. Attraverso lo stesso sistema è possibi- le modificare la stessa proprietà, ad esempio con questo codice: w.bounds = Rect(100, 100, 1000, 600), che assegna a window un nuovo oggetto Rect (con dimensioni e posizione diverse). A parte il rettangolo dei confini (per così dire), tra gli attributi della finestra c’è il colore dello sfondo, accessibile attraverso l’invocazione di background. Il valore di background viene speci- ficato un colore attraverso un oggetto Color. Anche i colori sono oggetti in SC e la classe Color prevede alcuni metodi predefiniti, che permettono di avere a disposizione i nomi più comuni dei colori: ad esempio il nero con
stituisce una istanza con certe particolarità;
• 10-12: la costruzione di un elemento grafico a manopola segue un procedi- mento analogo a quanto avvenuto per la finestra-contenitore. Alla variabile knob viene assegnato un oggetto Knob (11). Il costruttore sembra uguale a quello di Window: senonché questa volta è necessario specificare a quale fine- stra-contenitore vada riferito l’oggetto Knob: la finestra-contenitore è window, e il rettangolo che segue prende come punto di riferimento non lo schermo, ma la finestra window. Dunque un rettangolo 50x50, la cui origine è nel pixel (30,30) della finestra window. Si noti anche che il gestore geometrico di Knob (sul modello di Window) è ottenuto con questo costrutto: Rect(50, 25, 50, 50). Ad una classe segue una coppia di parentesi con argomenti. Dov’è fini- to il metodo costruttore? È un esempio di syntax sugar. Se dopo una classe si apre una parentesi con i valori degli argomenti, allora l’interprete sottinten- de che l’utente abbia omesso new, che è cioè il metodo predefinito. Quando SC vede una classe seguita da una coppia di parentesi contenenti dati assu- me che si sia invocato Class.new(argomenti). Il punto di partenza della ma- nopola è 0.5 (12). Per default l’escursione di valori di un oggetto Knob varia tra 0.0 e 1.0 (escursione normalizzata): dunque attraverso l’attributo knob.va- lue = 0.5 si indica la metà. La ragione dell’escursione normalizzata sta nel fatto che in fase di costruzione non si può sapere a cosa servirà il rotati- vo (controllerà la frequenza di un segnale? 20-20.000 Hz; o una nota MIDI? 0-127). Si noti che la proprietà è impostata attraverso l’uso del = che è l’ope- ratore di assegnazione. Nel caso di attributi di un oggetto è anche possibile un’altra sintassi. Infatti, il = è sinonimo del metodo setter rappresentato dal simbolo _, che esplicitamente assegna alla proprietà in questione il valore desiderato. In altri termini le due sintassi successive sono equivalenti.
1 knob.value = 0.5 ;
2 knob.value_(0.5) ;
La sintassi setter è perciò del tipo oggetto.proprietà_(valore). La parte successiva del codice definisce l’interazione con l’utente.
• 15-22: un rotativo è evidentemente un “controller”, un oggetto usato per controllare qualche altra cosa. All’oggetto knob è perciò possibile associare
un azione: è previsto per definizione che l’azione venga portata a termi- ne tutte le volte che l’utente cambia il valore di knob, cioè muove la ma- nopola. Una funzione rappresenta opportunamente questo tipo di situazio- ne, poiché come si è visto è un oggetto che definisce un comportamento richiamabile di volta in volta e parametrizzato da un argomento. Il metodo knob.action chiede di attribuire a knob l’azione seguente, descritta attraver- so una funzione: la funzione è tutto quanto è compreso tra parentesi graffe, 15-22. Quanto avviene è che, dietro le quinte, quando si muove la manopola alla funzione viene spedito un messaggio value. Il messaggio value chie- de di calcolare la funzione per il valore della manopola, che è l’input della funzione: dietro le quinte cioè viene inviato alla funzione il messaggio va- lue(knob.value). In altre parole, la funzione risponde alla domanda “cosa bisogna fare quando la manopola knob si muove”. Nella funzione l’input è descritto dall’argomento me (15): l’argomento, il cui nome è del tutto arbi- trario (e scelto da chi scrive) serve per poter rappresentare in forma riflessa l’oggetto stesso all’interno della funzione. A cosa può servire questo riferi- mento interno? Ad esempio, a dire ad un oggetto grafico di modificare se stesso come risultato della propria azione.
• 16-21: nell’esempio però, il comportamento previsto richiede di cambiare il colore di sfondo di window. Vengono dichiarate tre variabili (red, green, blue) (16). Esse identificano i tre componenti RGB del colore dello sfondo di
window, che SC definisce nell’intervallo[0, 1]. A red viene assegnato il valore
in entrata di me (17). A green e blue due valori proporzionali (ma in modo diverso) a me, in modo da definire un cambiamento continuo in funzione del valore di me nelle tre componenti cromatiche. Si tratta appunto di un’opera- zione di mapping: a un dominio di valori ([0, 1]) se ne fanno corrispondere altri tre, uno per ogni componente ([[0, 1], [0, 0.5], [0.25, 1]]). Quindi si dice di stampare su schermo un array composto da una stringa e dei tre valori (20): in questo caso SC stampa gli elementi dell’array sulla stessa riga op- portunamente formattati. La stampa su schermo permette di capire come vengono calcolati i valori (ed è utile senz’altro in fase di debugging). Infine, all’attributo background di window viene assegnato un oggetto Color, a cui sono passate le tre componenti. Il costruttore di Color accetta cioè le tre com- ponenti RGB in escursione [0,1] come definizione del colore da generare. È una delle molte abbreviazioni possibili in SC. Dunque, Color(red, green, blue) è equivalente in tutto e per tutto a Color.new(red, green, blue).
è creare gli oggetti GUI, un conto è dire che debbano essere visibili: que- sta distinzione permette di fare apparire/sparire elementi GUI sullo scher- mo senza necessariamente costruire e distruggere nuovi oggetti. Il metodo front rende window e gli elementi che da essa dipendono visibili: in caso d’omissione tutto funzionerebbe uguale, ma nulla sarebbe visualizzato sul- lo schermo.