Server
SuperCollider è formato da due applicazioni distinte che operano in parallelo: Client (o Interpreter o SClang) e Server (o SC synth). Il Client utilizza processi di scheduling (o controllo) dove, se non compiamo alcuna azione (come la valutazione del codice) o generiamo un qualche tipo di automazione (come nelle Routine e nei Task) il software non produce alcun valore nel tempo, mentre il Server è il motore di sintesi del suono di SuperCollider, dove possiamo leggere, produrre e modificare segnali audio digitali lavorando a una determinata rata di campionamento. In questa Parte approfondiremo le sue caratteristiche ed esploreremo le sue potenzialità. Azzardiamo infine un paragone musicale per chiarire ulteriormente la differenza tra Client e Server pensando a uno strumento ad arco come il violino. Il corpo dello strumento (che produce suono) può essere paragonato al Server mentre il Client può assumere al tempo stesso tre differenti funzioni in una: la partitura, l'archetto e il braccio dello strumentista che interpretando la prima muove il secondo. Da questo punto in poi per evitare errori di programmazione diventa fondamentale tenere sempre a mente questa separazione che è presente in tutti i software audio ma che in SuperCollider è informaticamente strutturale.
Boot info
Quando accendiamo (boot) il Server, SuperCollider riporta nella nella Post window alcune informazioni che ci possono tornare utili:
Un numero che definisce (indicizza) il Server che stiamo "accendendo" in quanto sullo stesso computer possono convivere più Servers contemporaneamente (ad esempio localhost e internal) e ognuno può essere "acceso" o "spento". Attraverso questo numero (indice o etichetta) inoltre possiamo inviare messaggi a quel singolo Server e non a tutti gli altri.
booting 57110
Il numero e i nomi dei devices (sia reali che virtuali) che abbiamo a disposizione per fare comunicare questo Server con il mondo esterno. Come vedremo in seguito dobbiamo necessariamente sceglierne uno (o più) tra quelli compresi in questo elenco.
Number of Devices: 5 0 : "Built-in Microph" 1 : "Built-in Input" 2 : "Built-in Output" 3 : "Soundflower (2ch)" 4 : "Soundflower (64ch)"
Come possimo notare anch'essi sono indicizzati con un numero al quale corrisponde il nome sotto forma di stringaIl device collegato a questo Server di default ovvero al momento del booting e quanti canali audio supporta. Nell'esempio seguente sono indicati i drivers interni di un computer Mac sia per quanto riguarda i segnali in entrata (ADC) che per quelli in uscita (DAC)
"Built-in Microph" Input Device Streams: 1 0 channels 2 "Built-in Output" Output Device Streams: 1 0 channels 2
L'indicazioneStreams: 1
significa che le informazioni di tutte e due i canali audio sono rappresentate con un solo flusso numerico.La Sample rate e il block size alla quale sta lavorando il Server in questione.
SC_AudioDriver: sample rate = 44100.000000, driver's block size = 512
Sappiamo già cosa è la sample rate ma cosa significa block size?Detta in breve è il ritardo in campioni che intercorre tra il segnale in entrata e il segnale in uscita dal device utilizzato dal Server e a volte è chiamato buffer size o vector size. Ma perchè c'è questo ritardo? Approfondiamo. Abbiamo stabilito che un segnale audio digitale non è altro che un flusso o sequenza di numeri (numeric stream). Per modificare i parametri del suono (come ampiezza, frequenza, timbro, etc.) di un segnale rappresentato in questo modo dobbiamo dunque effettuare operazioni matematiche su questi numeri e queste devono obbligatoriamente precedere il valore che viene inviato all'output audio essendo quest'ultimo il risultato dell'operazione. Per questo motivo nella computazione di un segnale audio digitale abbiamo bisogno di un tempo parallelo al tempo reale chiamato tempo logico (logical time) che precederà sempre di almeno un campione (il tempo necessario alla computazione di eventuali operazioni matematiche) l'invio del valore di ampiezza istantanea al convertirore D/A.
Per aumentare l'efficienza computazionale del tempo logico molti software dedicati all'audio effettuano queste operazioni non su un campione per volta ma su blocchi (o pacchetti) di campioni memorizzati di volta in volta in buffer (memoria temporanea). Il numero di campioni contenuti in un pacchetto corrisponde al block size e generalmente anche al buffer size.
Alcuni messaggi di controllo che indicano lo stato delle comunicazioni tra Server e Client:
SuperCollider 3 server ready. Receiving notification messages from server localhost Shared memory server interface initialized
Settaggi
In SuperCollider ci possono essere diversi Servers su uno stesso computer che possono essere controllati da un solo interprete, così come un interprete che abita un computer può controllare in rete uno o più Servers che sono su altri computers. Quando lanciamo l'applicazione SuperCollider crea automaticamente due Servers: localhost e internal. Successivamente possiamo selezionare uno o l'altro dal codice invocando i metodi omonimi sulla Classe Server:
s = Server.internal; s = Server.local;
La Classe Server è la rappresentazione nel Client dell'applicazione Server in quanto quest'ultima essendo indipendente, non necessita della prima e può essere programmata direttamente dal terminale del computer attraverso linee di comando UNIX. Il Client infatti assume la semplice funzione di tradurre il codice da noi programmato in comandi UNIX inviati all'applicazione Server attraverso il protocollo OSC.
Server.default
Quando lanciamo l'applicazione si aprono automaticamente i due Server appena illustrati ma se effettuiamo il boot senza specificare nulla, ad esempio con
cmd + . questa azione sarà effettuata su quello dei due che è di default, usualmente il localhost. Possiamo cambiare il Server di
default invocando il metodo .default
Server.default = Server.internal; Server.default = Server.local; // oppure... s = Server.local; Server.default_(s);
Server.new
Oltre ai due Server che abbiamo appena illustrato possiamo crearne quanti ne vogliamo invocando il metodo .new()
:
y = Server.new(\local2, NetAddr("127.0.0.1", 57111));
Questo metodo accetta quattro argomenti, ma i più importanti sono i primi due:
- un nome (etichetta) specificato sotto forma di simbolo che identifica il Server (come 'local' o 'internal')
- un'istanza della Classe
NetAddr.new()
che specifica:- un indirizzo IP (hostname o indirizzo MAC). Affronteremo i protocolli di rete nel Capitolo dedicato al protocollo OSC, per ora pensiamolo come a una stringa di numeri che identifica un computer (inteso come oggetto). L'indirizzo specificato nell'esempio ("127.0.0.1") è un indirizzo riservato e rappresenta il computer sul quale stiamo operando.
- una porta alla quale inviare i messaggi OSC che specifica un indirizzo preciso sul computer definito dall'indirizzo IP al quale
recapitare i messaggi. Le porte sono specificate da un numero intero. I numeri da 0 a 65.535 sono dedicati a porte riservate
e non possono essere usati, possiamo dunque scegliere qualsiasi numero superiore. Abbiamo visto come quando eseguiamo il boot appare
il messaggio
booting 57110
che riporta il numero della porta del Server di default alla quale possiamo inviare eventuali messaggi OSC.
Messaggi al Server
A qualsiasi server possiamo inviare una serie di messaggi. Di seguito i principali.
s = Server.local; s.boot; // Boot del Server y.boot; // Boot di un secondo Server... s.reboot; // Chiude e riapre il Server s.waitForBoot({...}) // Accende il Server e terminato il booting esegue il codice successivo tra le parentesi s.freeAll; // Libera tutti i nodi nel Server s.quit; // Chiude il Server s.volume = 0; // Setta il voume in uscita (in dB) s.mute; // Mute del Server s.unmute; // Unmute
Schede audio
Di default SuperCollider legge i valori in ingresso e scrive quelli in uscita sul driver utilizzato in quel momento dal sistema operativo del computer e se colleghiamo una scheda audio esterna il miglior modo di far comunicare SuperCollider con la scheda consiste nel selezionarla nelle preferenze audio di sistema. Per chi ha un computer Mac:
- Andare in menù mela e selezionare Preferenze di Sistema, si apre una finestra:
- Selezionare Suono, si apre una nuova finestra:
- Selezionare il device desiderato dalla lista sia per l'Entrata che per l'Uscita audio (possono anche essere differenti, l'importante è che abbiano la stessa rata di campionamento altrimenti i Server di SuperCollider non eseguono il boot.
Possiamo selezionare la scheda audio in uso anche dal codice invocando il metodo .options
sul Server desiderato ma lo sconsiglio
in quanto a volte può dare dei problemi:
s = Server.local; o = s.options;
Dopodichè possiamo specificare tutti i parametri che abbiamo visto descritti nella post window al momento del boot:
o.device; // riporta il device in uso o.device = "Soundflower (2ch)"; // seleziona il device specificando il nome o.device_("Soundflower (2ch)"); // altra scrittura... s.reboot; o.device = nil; // 'nil' specifica i driver del computer s.reboot;
Se vogliamo utilizzare devices differenti per i segnali inEntrata e quelli in Uscita:
o.inDevice = "Built-in Microph"; o.outDevice = "Soundflower (2ch)"; s.reboot; o.device = nil; s.reboot;
I Server di SuperCollider supportano di default 8 canali (Bus) in Uscita i cui indici vanno da 0 a 7. Se vogliamo verificare o
modificare questo parametro possiamo farlo invocando il metodo .numOutputBusChannels
. Lo stesso dicasi per i canali (Bus) in Entrata
i cui indici vanno da 8 a 15 (cambia solo il metodo):
o.numOutputBusChannels.postln; // riporta l'informazione o.numOutputBusChannels = 8; // cambia i settings o.numInputBusChannels.postln; // riporta o.numInputBusChannels = 8; // cambia
Per verificare possiamo visualizzare i meter:
( o.numOutputBusChannels = 2; // 2 canali o.numInputBusChannels = 2; // 2 canali s.meter; ) ( o.numOutputBusChannels = 8; // 8 canali o.numInputBusChannels = 8; // 8 canali s.meter; )
Possiamo specificare la Sample Rate facendo attenzione che quella scelta sia supportata dal device specificato:
o.sampleRate; // riporta o.sampleRate = 44100; // cambia
Lo stesso dicasi per il Block size che di default è di 64 samples:
o.blockSize = 64; // cambia o.blockSize; // riporta il block size corrente
Torniamo infine a quella scritta che abbiamo letto in precedenza nella Post window: a ServerOptions
. Ogni Server possiede al suo interno
un'istanza di questo oggetto che rappresenta un pacchetto di opzioni. Con questo oggetto possiamo dunque creare un pacchetto di opzioni
personalizzato da applicare a più Servers differenti. Per farlo dobbiamo:
h = ServerOptions.new; // creare una nuova istanza e assegnarla a una variabile h.sampleRate_(44100); // specificare le opzioni desiderate h.numOutputBusChannels_(2); y = Server.new(\Local2, NetAddr("127.0.0.1", 57111), h).meter; // assegnarle ad un Server come terzo argomento
Questo metodo è utile anche quando vogliamo ottenere nuovamente una lista dei devices a disposizione:
ServerOptions.devices; ServerOptions.inDevices; ServerOptions.outDevices;
Monitors
A chiosa di questo Capitolo dedicato al Server vediamo quali sono le principali possibilità che abbiamo a disposizione per monitorare le attività di un Server. Ci sono tre possibilità:
Stampare le informazioni nella Post window.
s.status; // Riporta il nome del Server in uso s.avgCPU; // Riporta l'utilizzo medio della CPU s.peakCPU; // Riporta l'utilizzo di picco della CPU s.numUGens; // Riporta il numero di UGens s.numSynths; // Riporta il numero di Synths s.numGroups; // Riporta il numero di Gruppi s.numSynthDefs; // Riporta il numero di SynthDefs s.volume; // Riporta il volume massimo in uscita (dB) s.mute; // Muting s.unmute; // Toglie il Mute
Al momento non sappiamo ancora nulla riguardo ad alcune informazioni appena esposte, ma ho preferito metterle in questa parte dello scritto per uniformare gli argomenti ed eventualmente ritornare in seguito a questo punto come reference.Una delle ragioni di poter ottenere queste informazioni dal codice sta nel fatto che potremmo utilizzarle come valori di controllo di una qualche tecnica di sintesi o di elaborazione del segnale oppure riportarli su un'interfaccia grafica (GUI) per un monitoraggio visivo personalizzato (custom). Ad esempio vediamo come monitorare costantemente l'utilizzo medio della CPU con una
Routine
:( a = {inf.do({ ["AVG: "++s.avgCPU.round(0.001), "PEAK: "++s.peakCPU.round(0.001)].postln; 0.1.wait }) }.fork; ) a.stop;
Leggere le informazioni nell'interfaccia grafica IDE (in basso a destra).
Sono le stesse informazioni che abbiamo appena richiamato dal codice, da sinistra a destra:
- 0.00%: utilizzo medio della CPU.
- 0.00%: utilizzo di picco della CPU.
- 0u: numero di UGens attive.
- 0s: numero di Synths attivi.
- 2g: numero di Groups attivi.
- 94g: numero di SynthDef attive.
- 0.0dB: Master gain del Server.
- M: Mute (o meno).
Possiamo notare come il numero di Groups e di SynthDefs non sia 0 già dal booting de Server, senza aver fatto nulla. Questo perchè alcuni di questi dati agiscono "dietro le quinte" e sono caricati automaticamente. Se clicchiamo sui numeri appare la seguente finestra dove possiamo attivare o disattivare diversi comandi:
Creare interfacce grafiche (GUI). Possiamo infine creare alcune interfacce grafiche dedicate alla visualizzazione di alcuni parametri riguardanti i segnali audio processati nel Server come le ampiezze in input e output visualizzate attraverso PPMeters, un oscilloscopio e uno spettroscopio:
s.meter; // Visualizza segnali in input e ouput s.scope(2); // Oscilloscopio del master out (il numero di canali si accorda // con quello specificato nelle ServerOption oppure possiamo spe- // cificarlo come argomento) s.freqscope; // Spettroscopio del master out (monofonico, possiamo scegliere // quale canale (Bus) visualizzare specificandone l'ID nel box // a destra. Possiamo inoltre specificare se la visualizzazione // deve essere lineare o logaritmica (di default) e anche il li- // mite inferiore in dB -(96 di default) {Pan2.ar(Mix(SinOsc.ar(Array.rand(20,200,5000),0,0.1)),0.3)}.play // Test
Possiamo infine aprire una finestra con una rappresentazione grafica del Server così come appariva fino alla versione 3.4 di SuperCollider, con la quale è possibile interagire utilizzando il mouse e dove sono riportate le stesse informazioni presenti nell'interfaccia IDE che abbiamo affrontato in precedenza.:
s.makeWindow;