SuperCollider Tutorial

142
SuperCollider by Paolo Olocco Personali traduzione dei tutorial Gettin-Started ed altri presenti nella documentazione di SuperCollider con approfondimenti e riferimenti. Il materiale presentato può essere un valido aiuto per iniziare a lavorare con Supercollider. Per approfondimenti e chiarimenti si rimanda il lettore sia ai file di help originali (presenti nella directory /help/Tutorial/Gettin-Started della cartella di SuperCollider), sia al forum. Per errori o commenti: [email protected]

description

SuperCollider Tutorial

Transcript of SuperCollider Tutorial

Page 1: SuperCollider  Tutorial

SuperColliderby Paolo Olocco

Personali traduzione dei tutorial Gettin-Started ed altri presenti nelladocumentazione di SuperCollider con approfondimenti e riferimenti. Ilmateriale presentato può essere un valido aiuto per iniziare a lavorarecon Supercollider.Per approfondimenti e chiarimenti si rimanda il lettore sia ai file dihelp originali (presenti nella directory /help/Tutorial/Gettin-Starteddella cartella di SuperCollider), sia al forum.Per errori o commenti: [email protected]

Page 2: SuperCollider  Tutorial

1

Indice1 Primi Passi 41.1 Hello World, sono SuperCollider 41.2 Il mondo dentro SuperCollider 6

2 Accendiamo i motori 8

3 Funzioni e Funzionalità 10

4 Funzioni e Suoni 164.1 Divertiamoci con Funzioni e UGens 19

5 Living Stereo 21

6 Creiamo un Mix 24

7 Scoping and Plots 267.1 Scoping su richiesta 277.2 Local vs. Internal 28

8 Help di Supercollider 298.1 Classi and Metodi 298.2 Shortcut sintattici 308.3 Snooping, etc. 30

9 SuperCollider 3 Synth Server: architettura 319.1 Introduzione 319.2 Principali componenti 31

10 SynthDefs e Synths 3510.1 SynthDef 3510.2 SynthDefs vs. Functions 3610.3 SynthDefs ‘variabili’ 3910.4 Synth 4010.5 Alcune nozioni su Symbols, Strings, SynthDef e Arg Names 40

11 I Buss 4211.1 Buss: scrittura e lettura 4311.2 Creiamo un Bus Object 4411.3 Buss in azione! 4611.4 Divertiamoci con i Control Buss 4911.5 L’ordine è una cosa importante! 50

Page 3: SuperCollider  Tutorial

2

12 I Gruppi 5312.1 Gruppi come tool per l’ordinamento 5312.2 Tutte le addAction 5512.3 ’queryAllNodes’e node IDs 5512.4 Il Root Node 5612.5 Il default Group 5712.6 Gruppi come tool per l’invio di insiemi di messaggi 5912.7 Gruppi: ereditarietà e non solo 59

13 Buffer 6113.1 Creazione di un Buffer Object e allocazione di memoria 6113.2 Uso dei Buffers con Sound Files 6213.3 Streaming di File da e su Disco 6313.4 OOP: Variabili di istanza e Funzioni azione 6413.5 Registrare nel Buffer 6513.6 Accesso ai dati 6613.7 Plotting e Playing 6813.8 Altri usi dei Buffers 68

14 La comunicazione 7014.1 Impacchettamento automatico dei messaggi 72

15 Ordine di esecuzione 7415.1 Richiami su Server e Target 7415.2 Controllo dell’ordine di esecuzione 7515.3 Utilizzare l’ordine di esecuzione a proprio vantaggio 7715.4 Stile Messaggio 8015.5 Un caso particolare: Feedback 8015.6 Espansione multicanale 8215.7 Protezione di array dall’espansione 8415.8 Ridurre l’espansione multicanale con Mix 8515.9 Uso di flop per l’espansione multicanale 86

16 Just In Time Programming 8816.1 Overview delle differenti classi e tecniche 8816.2 Placeholder in supercollider 8916.3 Proxy Space: concetti base 9316.4 Struttura interna di un node Proxy 10016.4.1 NodeProxy slots 10016.4.2 fade time 10116.4.3 play/stop, send/free, pause/resume 10216.4.4 Il parametro di contesto 105

Page 4: SuperCollider  Tutorial

3

16.5 Timing in NodeProxy 106

17 Esecuzione sincrona e asincrona 11017.1 Scheduler 11717.2 OSCSched 12017.3 Task 12417.4 Tempo 12517.5 Routine 12717.5.1 Variabili di istanza accessibili 129

18 Scrivere classi in SC 13218.1 Oggetti e Messaggi 13218.2 Classi, Variabili di instanza e metodi 13318.2.1 Classi 13318.2.2 Ereditarietà 13318.2.3 Variabili di istanza 13418.2.4 Variabili di Classe 13618.2.5 Metodi di istanza 13618.2.6 Metodi di classe 13718.3 Creazione di una nuova istanza 13818.4 Overriding dei metodi (Overloading) 13918.5 Metodi definiti in file esterni 13918.6 Tricks and Traps 140

19 Glossario 141

Page 5: SuperCollider  Tutorial

Primi Passi

4

Capitolo 1 Primi Passi

1.1 Hello World, sono SuperCollider

È tradizione quando ci si appresta ad imparare un nuovo linguaggio di programma-zione partire con un semplice programma chiamato “Hello World”; ciò che fa questoprogramma è stampare il testo “Hello World!” nella finestra di output chiamata, inSuperCollider, post window che si apre all’avvio del programma, e in cui compaionouna serie di righe di testo del tipo:

1 init_OSC2 compiling class library..3 NumPrimitives = 5874 compiling dir: ’/Applications/SC3/SCClassLibrary’5 pass 1 done6 Method Table Size 3764776 bytes7 Number of Method Selectors 31848 Number of Classes 18149 Number of Symbols 7595

10 Byte Code Size 18097311 compiled 296 files in 1.34 seconds12 compile done13 RESULT = 25614 Class tree inited in 0.14 seconds

Non è il caso di preoccuparsi molto ora del significato inerente questo testo, bastatenere a mente che questa finestra è l’interfaccia in cui SC invierà le informazioni. Èanche il posto in cui si otterrà il risultato del nostro programma Hello World listatoqui sotto:

1 "Hello World!".postln;

Per eseguire il programma, occorre semplicemente posizionare il cursore sulla stessariga di codice in cui compare il codice e premere enter (e non return). Il tasto ‘enter’è quello sul blocco dei numeri della tastiera. Sui laptops Mac si preme ‘return’ con‘fn’.Eseguendo l’esempio si potrà vedere nella post window:

1 Hello World!2 Hello World!

Page 6: SuperCollider  Tutorial

Primi Passi

5

Ora guardiamo un più da vicino il codice.La prima parte, “Hello World!”, è un tipo di Object , chiamato Stringa. Un oggettoè semplicemente un modo di rappresentare qualcosa nel computer, per esempio untesto, oppure un oscillatore che permette di controllare e inviare messaggi. Di tuttoquesto ce ne occuperemo più avanti, ma ai fini di questo capitolo introduttivo, èsufficiente capire che una Stringa è un modo di rappresentare una quantità di testo.La seconda parte, .postln;, significa ‘stampami nella post window’. Questo metodopostln, è il miglior amico del programmatore in SC. Può essere applicato a quasitutto in SC e restituisce informazioni rilevanti. Può essere veramente utile quando sicerca un bug nel codice. Osservano il risultato ottenuto viene spostaneo domandarsiperchè viene stampato due volte il risultato.Quando si esegue codice in SC, viene sempre postata l’ultima istruzione eseguita; inquesto caso non occorreva la postln. Ma da qualche parte bisognava pur cominciare,no?! Nell’esempio seguente selezioniamo entrambe le linee di testo cliccandoci soprae premiamo enter.

1 "Hello World!".postln;2 "Hello SC!".postln;

La prima riga, “Hello World” non verrebbe stampata se non avesse la postln espli-citata. Da notare inoltre che ogni linea di codice termina con un punto e virgola.Questo del punto e virgola è il metodo in SC per separare le linee di codice. Se noncomparisse un punto e virgola fra due righe, si potrebbe incorrere in errore.

In generale quando si intende eseguire diverse linee di codice allo stesso tempo, ènecessario racchiudere il codice fra parentesi tonde, come l’esempio seguente. Que-sto approccio è conveniente perchè permette di selezionare l’intero blocco di codicesemplicemente facendo doppio click dentro le parentesi. Si provi con il seguenteesempio:

1 (2 "Call me,".postln;3 "Ishmael.".postln;4 )

Da notare che tutte le linee dentro il blocco di codice terminano con un punto evirgola. La cosa importantissima quando si eseguono più linee di codice, è sapercome SC capisce dove separare i comandi. Senza i punti e virgola, si otterrebbe unerrore.

Page 7: SuperCollider  Tutorial

Primi Passi

6

1 (2 "Call me?".postln3 "Ishmael.".postln;4 )

Eseguendo infatti il codice sopra riportato, si ottiene un Parse Error. Con un erroredi questo tipo, il ě nel messaggio di errore mostra dove SC rileva un errore. Quisuccede appena dopo “Ishmael.”.

1 ě ERROR: Parse error2 in file ’selected text’3 line 3 char 11 :4 "Ishmael."ě.postln;

Usare il punto e virgola permette infine di avere più di una linea di codice nellastessa linea di testo. Questo può essere comodo per l’esecuzione:

1 "Call me ".post; "Ishmael?".postln;

Vediamo ancora un paio di nozioni in più sulla post window.È molto utile essere in grado di averla a disposizione e vederla, ma può esserealtrettanto utile nasconderla dietro le altre finestre. Può essere portata in avanti inogni momento premendo il tasto command e premendo /.Per convenzione questo tipo di sequenza sarà scritto con Cmd-/.Per ripulire la post window occorre premere Cmd-shift-k.

1.2 Il mondo dentro SuperCollider

SuperCollider è sostanzialmente formato da 2 programmi:

- il linguaggio o client app, che è ciò che stiamo vedendo ora

- il server o server app , che computa sintesi e calcoli sull’audio.

Il primo è un’applicazione grafica con menù, document window, divertenti GUI eun sofisticato linguaggio di programmazione e può essere un’applicazione a riga dicomandi UNIX efficiente e agile (se non si utilizzano le divertenti GUI).

Le 2 applicazioni comunicano con un protocollo chiamato Open Sound Control(OSC), e può essere impiegato sia sul protocollo di rete UDP che TCP. Non venga

Page 8: SuperCollider  Tutorial

Primi Passi

7

da pensare che le due applicazioni debbano per forza eseguire su due computer dif-ferenti (è comunque possibile e può portare a numerosi vantaggi di performance) eche essi siano connessi a internet (anche se è possibile avere clients e servers in partidifferenti del mondo!). La maggior parte delle volte gireranno sulla stessa macchina,e la ‘rete’ sarà resa trasparente all’utente finale.

L’unico metodo di comunicazione client-server supportato è quello basato sull’utilizzodi messaggi OSC, e fortunatamente il linguaggio di Supercollider dispone di alcunioggetti potenti che rappresentano diverse entità sul server e permettono di control-larle facilmente ed elegantemente. Capire esattamente come lavorano è cruciale perdominare SC, così ne parleremo a fondo.

Ma prima divertiamoci un po’e creiamo qualche suono.

Per maggiori informazioni:

[How-to-Use-the-Interpreter] [Literals] [String] [ClientVsServer] [Server-Architecture]

Page 9: SuperCollider  Tutorial

Accendiamo i motori

8

Capitolo 2 Accendiamo i motori

Prima di poter creare qualunque suono è necessario far partire o, in maniera più spe-cifica, effettuare il boot dell’applicazione server. Il modo più semplice per effettuarequesta operazione è utilizzare una delle server windows che si creano automatica-mente all’avvio dell’app del client e che vengono visualizzate di solito in basso asinistra nello schermo. La finestra del server localhost si presenta in questo modo:

Fig. 2.1 Server localhost window

Il termine localhost significa semplicemente sul proprio computer, come oppostoall’esecuzione su un altro computer connesso alla rete. Per avviare il server, occorrecliccare sul bottone Boot o cliccare sulla finestra e premere spazio. Dopo un secondoo due dovrebbe comparire qualcosa di simile a questo:

Fig. 2.2 Server localhost window

Da notare che il nome è diventato rosso e che il bottone Boot è cambiato in Quit.Questo indica che il server è passato in stato running. Questa finestra offre inoltrealcune informazioni sul server:

− Avg CPU : carico medio della CPU espresso in percentuale

− Peak CPU : picco di carico della CPU espresso in percentuale

− UGens : numero di UGen correntemente utilizzate

− Synths : numero di synth correntemente istanziati (capitolo 10)

Page 10: SuperCollider  Tutorial

Accendiamo i motori

9

− Goups : numero di group correntemente istanziati. All’avvio sono 2, il root nodee il default group. (capitolo 12)

− SynthDefs : numero di SynthDef conosciute dal server.(capitolo 10)

Osservando la post window in seguito al boot del server, vediamo che SC ha generatoalcune informazioni sull’esito positivo del boot:

1 booting 571102 SC_AudioDriver: numSamples=512, sampleRate=44100.0000003 start UseSeparateIO?: 04 PublishPortToRendezvous 0 571105 SuperCollider 3 server ready..6 notification is on

Nel caso in cui, per qualche motivo, il boot fallisca, potrebbero essere riportarealcune informazioni che ne indicano il fallimento e la motivazione. Di default, cisi può riferire al server localhost usando la lettera s. È possibile quindi mandaremessaggi di start e stop al server come i seguenti:

1 s.quit;2 s.boot;

Proviamo a lasciare il server in esecuzione per ora. Molti esempi nella documenta-zione di SC hanno l’istruzione s.boot all’inizio, ma in generale è bene assicurarsi cheil server sia in stato running prima di utilizzare qualunque esempio che genera audioo che accede a funzioni del server. In generale per gli esempi in questo tutorial siassume che il server sia in stato running. Ci si può anche riferire al server localhostcon il testo Server.local, per esempio:

1 Server.local.boot;

Per maggiori informazioni:

[Server]

Page 11: SuperCollider  Tutorial

Funzioni e Funzionalità

10

Capitolo 3 Funzioni e Funzionalità

Il modo più semplice per generare suoni in SC è utilizzare una Function. Di seguitone viene mostrato un semplice esempio. Si provi ad eseguire il codice (dopo essersiaccertati che il server sia in stato running), e dopo poco si prema Cmd-. ( tenerpremuto il tasto command e premere la barra spaziatrice) per fermare il suono.Questo comando fermerà sempre tutti i suoni in SC. Verrà usato parecchio in futuroper permettere di ricordalo.

1 { [SinOsc.ar(440, 0, 0.2), SinOsc.ar(442, 0, 0.2)] }.play;

Non è troppo ispirato il suono generato da quest’esempio!? Non ci si preoccupi trop-po, siamo all’inizio e questo è solo un semplice esempio per dimostrare le Functionse il suono. Lo riprenderemo dopo con calma.

Prima di tutto impariamo qualche nozione generale sulle Functions.Una Function è soltanto una parte di codice riutilizzabile. Si definisce racchiudendoil codice fra parentesi graffe. Per esempio:

1 f = { "Function evaluated".postln; };

Ciò che viene inserito fra le parentesi graffe è ciò che sarà eseguito ogni volta che laFunction sarà riutilizzata o rivalutata. Da notare che è definita come un’equazione, f=..., anche se non lo è nel vero senso matematico. Questa operazione è ciò che vienedetto essere un assegnamento. Di base un assegnamento permette di nominare laFunction creata salvandola in una variabile f. Una variabile è solo un nome che rap-presenta uno slot in cui possiamo salvare qualcosa, come una Function, un numero,una lista, ecc. Eseguendo le seguenti linee di codice una alla volta e osservando lapost window,

1 f = { "Function evaluated".postln; };2 f;

vedremo che entrambe le volte verrà scritto a Function. Quindi, se vorremo riferircialla Function in futuro dopo che è stata assegnata, è possibile usare la lettera f. Eccocosa s’intende per riutilizzabile!! Se non fosse possibile assegnarla, sarebbe necessarioriscrivere ogni volta la Function stessa.

Page 12: SuperCollider  Tutorial

Funzioni e Funzionalità

11

Come è quindi possibile riusare una Function? Si eseguano le seguenti linee una allavolta e si osservi la post window:

1 f = { "Function evaluated".postln; };2 f.value;3 f.value;4 f.value;

La Function è un oggetto (un’entità che rappresenta qualcosa e ha funzionalità spe-cifiche), che abbiamo definito e salvato nella variabile f. La parte di codice .valuevaluta la funzione in quel momento e ne restituise il valore.Questa operazione è un esempio di invio di un messaggio a un oggetto. Segue lasintassi someObject.someMessage separati da un punto.

Per un dato oggetto, ogni invocazione message significa esecuzione di un particola-re metodo. Tipi differenti di oggetti potrebbero avere metodi con lo stesso nome equindi rispondere allo stesso messaggio in modi differenti. Data l’importanza, evi-denziamo il concetto appena espresso:

Tipi differenti di oggetti potrebbero avere metodi con lo stesso nome,e quindi rispondere allo stesso messaggio in modi differenti.

La cosa interessante è che i metodi potrebbero differire nell’implementazione ef-fettiva, anche se hanno lo stesso nome, rendendo intercambiabile nel codice il loroutilizzo.

Un buon esempio di questo principio è proprio il metodo value. Tutti gli oggettiin SC rispondono a questo messaggio. In generale, quando si richiama un metodo,questo ritornerà sempre qualcosa come un valore o un risultato. Quando si invoca ilmetodo value su una Function, quest’ultima verrà valutata e ritornerà il risultatodell’ultima riga di codice della Function. Si veda l’esempio seguente: ritornerà unvalore uguale a 5.

1 f = { "Evaluating...".postln; 2 + 3; };2 f.value;

Più spesso i metodi restituiscono l’oggetto stesso a cui fanno riferimento (in specialmodo nel caso di molti oggetti con il messaggio value). Si consideri l’esempio se-guente (Tutto ciò che è a destra di due backslash // è un commento che significasemplicemente che SC ignorerà ciò che viene scritto durante il parse e l’escuzione).

Page 13: SuperCollider  Tutorial

Funzioni e Funzionalità

12

1 f = 3; // f viene inizializzata a un numero2 f.value; // nella Post window viene stampato 33 f.value; // viene di nuovo stampato 3

5 f = { 3.0.rand; }; // f viene inizializzata a un numero random6 f.value; //7 f.value; // stampa 3 valori differenti8 f.value; //

L’uso del metodo value di Functions permette agli altri oggetti di essere inter-cambiabili nel codice. Questo è un esempio di polimorfismo, uno dei meccanismipiù potenti del paradigma della programmazione Object Oriented. Il polimorfismo,in breve, permette l’intercambiabilità di oggetti differenti se rispondono allo stessomessaggio. Vediamo un altro breve esempio che mostra questa tecnica in azione:

1 f = { arg a; a.value + 3 };2 f.value(3);3 g = { 3.0.rand; };4 f.value(g);5 f.value(g);

Come potrebbero essere sfruttati questo polimorfismo e questa tipologia di esempio?Le Function possono essere definite con l’utilizzo di argomenti. Questi argomentisono valori che vengono passati alla funzione quando deve essere valutata. L’esempiosotto dimostra quanto detto. Si provi ad indovinare il risultato prima dell’esecuzione.

1 (2 f = { arg a, b;3 a - b;4 };5 f.value(5, 3);6 )

Gli argomenti sono dichiarati all’inizio della Function, usando la parola chiave arg.Ci si può riferire ad essi nel codice come se fossero variabili. Quando si richiama ilmetodo value su una Function, è possibile passare degli argomenti, ordinati, met-tendoli fra parentesi. La sintassi è: someFunc.value(arg1, arg2). Questa metodologiaè lo stessa con qualsiasi metodo che prendere in input argomenti di qualsiasi tipo,siano essi Function, espressioni o anche solo valori.

È possibile specificare un ordine differente usando ciò che viene detto keyword ar-guments:

Page 14: SuperCollider  Tutorial

Funzioni e Funzionalità

13

1 f = { arg a, b; a / b; }; // ’/’significa dividi2 f.value(10, 2); // regular style3 f.value(b: 2, a: 10); // keyword style

È inoltre possibile miscelare i due stili, se necessario, ma gli argomenti regolaridevono essere antecedenti:

1 f = { arg a, b, c, d; (a + b) * c - d };2 f.value(2, c:3, b:4, d: 1); // 2 + 4 * 3 - 1

(Da notare che SC non ha precedenza negli operatori: le operazioni vengono effet-tuate nell’ordine in cui sono scritte)

A volte è utile impostare valori di default per degli argomenti, in questo modo:

1 f = { arg a, b = 2; a + b; };2 f.value(2); // 2 + 2

I valori di default devono essere letterali. I letterali sono di base numeri, stringhe,simboli o collezioni di questi. Di nuovo, non ci si preoccupi troppo se per ora nonha tutto un senso completo, diverrà chiaro in seguito.

Esiste un modo alternativo per specificare argomenti, che è incapsularli dentro duesbarre verticali (su molte tastiere il simbolo è Shift- ). Le seguenti due funzioni sonoequivalenti:

1 f = { arg a, b; a + b; };2 g = { |a, b| a + b; };3 f.value(2, 2);4 g.value(2, 2);

Perchè coesistono due modi differenti per effettuare la stessa operazione!? ... perchèalcune persone preferiscono il secondo stile e lo considerano una scorciatoia e altripreferiscono il primo stile perchè è più esplicito. ( ... il mondo è bello perchè è vario... )

SC ha un certo numero di scorciatoie sintattiche come quella illustrata, che per-mettono di scrivere codice più velocemente. In ogni caso incontreremo questi dueapprocci differenti in entrambe le forme per permettere di impararle entrambe.

Page 15: SuperCollider  Tutorial

Funzioni e Funzionalità

14

È possibile anche avere variabili in una Function; devono essere dichiarate all’iniziodella Function, subito dopo gli argomenti, usando la keyword var.

1 (2 f = { arg a, b;3 var firstResult, finalResult;4 firstResult = a + b;5 finalResult = firstResult * 2;6 finalResult;7 };8 f.value(2, 3); // (2 + 3) * 2 = 109 )

I nomi delle variabili e degli argomenti possono consistere di lettere e numeri, maoccorre che inizino con una lettera minuscola e non contengano spazi vuoti.

Le variabili sono valide solo per quello che è detto essere il loro scope. Lo scope diuna variabile dichiarata in una Function è la Function stessa, in altre parole l’areafra due parentesi graffe. Eseguiamo questo esempio una riga alla volta:

1 f = { var foo; foo = 3; foo; };2 f.value;3 foo; // ERRORE!! foo compare fuori dal proprio scope.

È possibile dichiarare variabili all’inizio di ogni blocco di codice che verrà eseguitoinsieme (selezionandolo tutto insieme). In tal caso il blocco di codice eseguito è loscope della variabile. Eseguiamo il blocco (in parentesi tonde) e in seguito l’ultimalinea.

1 (2 var myFunc;3 myFunc = { |input| input.postln; };4 myFunc.value("foo"); // arg \‘e una String5 myFunc.value("bar");6 )

8 myFunc; // generare un errore

Ci si potrebbe meravigliare del perchè non è stato necessario dichiarare variabili comef, e perchè queste non sembrano aver qualche scope particolare (mantengono i valorianche quando eseguiamo una linea alla volta). Le lettere minuscoli dell’alfabeto sonodette variabili dell’interprete. Queste variabili sono pre-dichiarate quando avviamo

Page 16: SuperCollider  Tutorial

Funzioni e Funzionalità

15

SC e hanno uno scope illimitato, o, detto in un altro modo, sono globali allo scriptdi SC.Per maggiori informazioni:

[Functions] [Function] [Assignment] [Intro-to-Objects] [Literals] [Scope]

Page 17: SuperCollider  Tutorial

Funzioni e Suoni

16

Capitolo 4 Funzioni e SuoniDopo questi dettagli tecnici noiosi, torniamo alla creazione di rumore che è il motivoprincipale per il quale credo si stia leggendo questo manuale. È pur vero però, chetutto quello che abbiamo visto finora, tornerà molto utile in futuro.

Torniamo al nosto esempio sonoro, o almeno ad una versione più semplficata. Dopoaver verificato che il server localhost è in stato running, eseguiamo il codice seguente( ... e si stoppi il tutto con Cmd-. quando si vuole, non è un test di resistenza ... )

1 { SinOsc.ar(440, 0, 0.2) }.play;

In questo caso si è creata una Function racchiudendo del codice fra parentesi graf-fe e quindi richiamando il metodo play sulla Function che semplicemente valuta ilcodice e fa il play del risultato sul server. Se non si specifica un server, ne sarà sele-zionato uno di default che, ricordiamo, è salvato nella variabile s e settata, durantelo startup del programma, al server localhost.

In quest’ esempio la Function non viene salvata in una variabile, così non può essereriusata. Questo approccio è spesso impiegato con Function-play, come modo veloceper il testing. Ci sono altri modi per riutilizzare Functions per suoni, che sono spessomigliori e più efficenti, come vedremo in seguito.

Osserviamo ora il codice fra le parentesi graffe dell’esempio. Si è fatto uso di qualco-sa chiamato SinOsc a cui stiamo inviando il messaggio ar, con qualche argomento.SinOsc è un esempio di ciò che chiamaremo class. Per capire cose è una classe, ènecessario conoscere qualcosa in più sugli oggetti e sull’Object Oriented.

Nella programmazione ad oggetti, un oggetto è formato da un insieme di dati einformazioni, e un insieme di operazione che possono essere effettuate sui dati. Sipotrebbero avere differenti oggetti dello stesso tipo. Essi sono detti istanze. Il tipostesso è la classe dell’oggetto. Per esempio potremo avere una classe detta Studentie diverse istanze della classe, Bob, Dave e Sue. Tutti e tre sono dati dello stessotipo e potrebbero avere una parte di dati detta QI. Il valore di ogni dato potrebbeessere differente comunque. Si vorrebbe che avessero anche tutti gli stessi metodiper operare sui dati. Per esempio potrebbero avere un metodo detto calculateQI oqualcosa di simile.

Una classe di oggetti definisce l’insieme di dati (o variabili di istanza) e i metodi.Inoltre è possibile definire alcuni altri metodi che comunicano all’interno della classe

Page 18: SuperCollider  Tutorial

Funzioni e Suoni

17

stessa e altri dati usati da tutte le istanze. Questi sono detti metodi e variabili diclasse.

Tutte le classi cominciano con una letteta maiuscola, rendendo più semplice identi-ficarle nel codice.

Le classi sono utilizzate, come fossero template, per creare gli oggetti. È possibilecreare oggetti attraverso metodi come new o, in caso della nostra classe SinOsc vistaprima, il metodo ar. Tali metodi restituiscono un oggetto o un’istanza e gli argomentipassati al costruttore influenzano i dati dell’oggetto creato e il suo comportamento.Prendiamo una parte dell’esempio in questione:

1 SinOsc.ar(440, 0, 0.2)

Viene considerata la classe SinOsc e ne viene creata un’istanza. Tutte le SinOsc sonoun esempio di ciò che viene detto unit generator, o UGens, oggetti che produconosegnali audio o di controllo. SinOsc e un oscillatore sinusoidale. Questo significa cheprodurrà un segnale consistente di una sola frequenza. Un grafo della sua formad’onda potrebbe essere il seguente:

Questo ciclo d’onda viene creato dal segnale di output ar (ar crea l’istanza audiorate). SC calcola l’audio in gruppi di campioni, detti blocks. Audio rate significa chela UGen calcolerà un valore per ogni campione nel blocco. Esiste un altro metodo, krche significa control rate. Questo metodo invece calcola un singolo valore per tuttoil blocco di campioni. Questo permette di risparmiare in prestazioni ed è ottimo persegnali che controllano altre UGens, ma non va bene per segnali audio sintetizzatiperchè non è abbastanza dettagliato.

I tre argomenti della Function SinOsc-ar data nell’esempio sono frequenza, fase e unmul.

Page 19: SuperCollider  Tutorial

Funzioni e Suoni

18

− La frequenza è proprio la frequenza dell’oscillatore in Hertz (Hz) o cicli al secondo(cps)

− La fase si riferisce al punto in cui inizierà il ciclo della forma d’onda. Per SinOsc(ma non per tutte le UGens) la fase è data in radianti (compresi tra 0 e pigreco).Se per esempio istanziamo un SinOsc con una fase di (pi * 0.5), o un quarto diciclo, la forma d’onda sarà:

Vediamo una serie di cicli per chiarire l’idea:

− Mul invece è un argomento speciale che quasi tutte le UGens hanno. È così comu-ne che di solito non viene in sostanza mai spiegato nella documentazione. Il valo-re o il segnale espresso da questo parametro sarà moltiplicato per l’output dellaUGen. Nel caso di un segnale audio, questo moltiplicatore influenzerà l’ampiezzadel segnale. Il mul di default di molte UGens è 1, il che significa che il segnalein uscita oscillerà fra -1 e 1. Questo è un buon valore di default, perchè ci metteal sicuro da problemi di clipping o distorsione che segnali più ampi potrebberoaccusare. Un mul che vale 0 porta un segnale ad essere davvero nullo, come se lamanopola del volume fosse a 0.Per rendere chiaro l’effetto del mul, vediamo un grafo di due SinOsc, una con ilmul di default a 1 e una con un mul di 0.25:

Page 20: SuperCollider  Tutorial

Funzioni e Suoni

19

Esiste un altro argomento simile chiamato add (anch’esso di solito non conside-rato nella documentazione), che è qualcosa che viene addizionato al segnale dioutput. Può essere utile per segnali di controllo. In generale ‘add’ ha un valoredi default uguale a 0 così da non essere necessario specificarlo.Tenendo queste cose a mente, rivediamo il nostro esempio con i parametri com-menti:

1 (2 { // Apre la Function3 SinOsc.ar( // Crea un SinOsc audio rate4 440, // frequenza di 440 Hz, or accordato in

la5 0, // fase iniziale a 0, o all’inizio del

ciclo6 0.2) // mul di0.27 }.play; // chiude la Function e invoca il metodo ’play’8 )

4.1 Divertiamoci con Funzioni e UGens

Presentiamo ora un altro esempio di polimorfismo e di come questo strumento siapotente. Quando creiamo una Function composta da diverse UGens, è possibileche per molti argomenti non si abbiano valori fissi, ma altre UGens! Vediamo unesempio che dimostra questo principio:

1 (2 { var ampOsc;3 ampOsc = SinOsc.kr(0.5, 1.5pi, 0.5, 0.5);4 SinOsc.ar(440, 0, ampOsc);5 }.play;6 )

Page 21: SuperCollider  Tutorial

Funzioni e Suoni

20

Si provi a eseguire il codice più volte (ancora, usando Cmd-. per fermare il suono).Ciò che si è fatto in questo caso è stato inserire il primo SinOsc (control rate !!)dentro l’argomento mul del secondo SinOsc. In questo modo l’output del primoSinOsc viene moltiplicato per l’output del secondo. I parametri del primo SinOscsono:

− Frequenza = 0.5 cps completerà un ciclo in 2 secondi (1 / 0.5 = 2)

− Mul e add = 0.5 Se di default SinOsc va fra 1 e -1, allora una moltiplicazioneper 0.5 scalerà il segnale fra 0.5 e -0.5. Aggiungendo 0.5 il segnale vieneportato fra 0 e 1.

− Fase = 1.5pi significa 3/4 di ciclo, che come illustrato nel primo grafico, sipuò vedere il punto più basso, o in questo caso, 0.

Il risultato finale sarà:

Quindi il risultato è un SinOsc su cui viene applicato un leggero fade in/fade out.In pratica si è influenzato l’output di SinOsc usando l’ampOsc cioè un inviluppoin ampiezza. Ci sono altri metodi per fare la stessa cosa, alcuni anche più semplici,ma quest’esempio ne mostra il principale.Collegare UGens insieme in questo modo è la metodologia base per creare suoniin SC. Per una visuale generale dei vari tipi di UGens disponibili in SC, si veda[UGens] o [Tour_of_UGens].Per maggiori informazioni:

[Functions] [Function] [UGens] [Tour_of_UGens]

Page 22: SuperCollider  Tutorial

Living Stereo

21

Capitolo 5 Living Stereo

Fino a questo punto si sono visti concetti importati, ma il primo esempio delcapitolo precedente, che riportiamo:

1 { [SinOsc.ar(440, 0, 0.2), SinOsc.ar(442, 0, 0.2)] }.play;

... cosa significava? Si hanno due SinOsc con argomenti differenti racchiuse fra2 parentesi quadre e con una virgola fra loro. Come le parentesi graffe stannoad indicare una funzione, le parentesi quadre definiscono qualcosa chiamato Ar-ray. Un Array è un tipo di Collection, una collezione di Objects. Le Collectionssono esse stesse Objects e molti tipi di Collections possono gestire ogni tipo dioggetti, mixarli insieme, includere altre Collections! Ci sono inoltre parecchi tipidifferenti di Collections in SC e impareremo come questi siano una delle featurespiù potenti e importanti in SC.

Un Array è un tipo particolare di Collection: una collezione ordinata di dimensio-ne limitata. È possibile crearne uno come abbiamo fatto nel codice dell’esempioappena visto, inserendo oggetti fra due parentesi quadre e separandoli con lavirgola. Per prelevare i differenti elementi di un Array si può usare il metodoat che prende un indice come argomento. Gli indici corrispondono alla posizionenell’ordinamento degli oggetti nell’Array e partono da 0.

1 a = ["foo", "bar"];// "foo" = posizione 0; "bar" = posizione 12 a.at(0);3 a.at(1);4 a.at(2);// restituisce "nil", non ci sono oggetti alla posizione 2

6 // alternativamente, si utilizza7 a[0]; // uguale ad a.at(0);

Inoltre, per il fatto che può contenere collezioni di oggetti, un Array ha ancheun uso speciale in SC: viene utilizzato per implementare l’audio multicanale! Seuna Function restituisce un Array di UGens (si ricordi che una Function ritornail risultato della sua ultima linea di codice), allora l’output sarà un certo numerodi canali. Il numero di canali dipende dalla dimensione dell’Array, e ogni canalecorrisponderà univocamente a un elemento dell’Array. Nel nostro esempio:

1 { [SinOsc.ar(440, 0, 0.2), SinOsc.ar(442, 0, 0.2)] }.play;

Page 23: SuperCollider  Tutorial

Living Stereo

22

Il risultato è un output stereo, con un SinOsc a 440Hz nel canale sinistro eun SinOsc a 442Hz nel canale destro. È possibile avere più canali di outpututilizzando un array di dimensione maggiore.

Ora si osservi il prossimo esempio. Dato che gli argomenti di fase e mul sono glistessi per entrambi i SinOsc, è possibile riscrivere il codice in questo modo:

1 { SinOsc.ar([440, 442], 0, 0.2) }.play;

Si è sostituito l’argomento frequenza con un Array di frequenze. Questo causaquello che viene detto espansione multicanale che non è nient’altro che l’inserimentodi un Array in uno degli argomenti della UGen al posto di un singolo valore.

Consideriamo ora questo codice:

1 (2 { var freq;3 freq = [[660, 880], [440, 660], 1320, 880].choose;4 SinOsc.ar(freq, 0, 0.2);5 }.play;6 )

Provando ad eseguire questo esempio più volte si otterranno risultati diversi; ilmetodo choose seleziona uno degli elementi dell’Array in maniera random. Inquesto caso il risultato potrebbe essere un singolo numero e si avrebbe un outputmonofonico o un altro Array, nel qualcaso si avrebbe un output stereo. Questatipologia di finezze può rendere il nostro codice molto flessibile.

Ma se si volesse effettuare un crossfade fra due canali? Come si gestisce il pan? SCha un certo numero di UGens che risolvono la spazializzazione orizzontale in varimodi, ma per ora ne introduciamo una solo: Pan2. Pan2 prevede due argomenti:un input e una posizione e restituisce un Array di due elementi, il canale sinistroe il canale destro oppurre il primo e il secondo canale. l’argomento posizionevaria fra -1 (sx) a 1 (dx), passando per lo 0 (centrale). Vediamo l’esempio:

1 { Pan2.ar(PinkNoise.ar(0.2), SinOsc.kr(0.5)) }.play;

In questo esempio un SinOsc controlla la posizione (si ricorda che il suo outputva da -1 a 1) ma usa una UGen differente, il rumore rosa, come input nel Pan2.Questo che vediamo, il PinkNoise è solo un tipo di generatore di rumore e

Page 24: SuperCollider  Tutorial

Living Stereo

23

ha un argomento solo: mul. È possibile usare anche valori fissi per l’argomentoposizione.

1 { Pan2.ar(PinkNoise.ar(0.2), -0.3) }.play; // slightly to the left

Per ulteriori informazioni:

[MultiChannel] [Collections] [Pan2]

Page 25: SuperCollider  Tutorial

Creiamo un Mix

24

Capitolo 6 Creiamo un Mix

Si è già visto che, nel contesto di segnali audio, le moltiplicazioni cambiano illivello di qualcosa, ma cosa succede e come si fanno a mixare UGens diverseinsieme? Tutto ciò che serve è l’addizione:

1 { PinkNoise.ar(0.2) + SinOsc.ar(440, 0, 0.2) + Saw.ar(660, 0.2) }.play;

Saw è un tipo di oscillatore, con una forma d’onda che assomiglia a un’onda adente di sega. Da notare che viene usato con un valore basso per l’argomentomul per assicurarci che l’output finale sarà fra -1 e 1 e non si incorra in problemidi clipping.

Esiste una comoda classe,Mix, che mixerà un array di canali in un singolo canaleo un array di array di canali in un singolo array di canali.Si osservi la post window per vedere i risultati di Mix.

1 // un canale2 { Mix.new([SinOsc.ar(440, 0, 0.2), Saw.ar(660, 0.2)]).postln }.play;

4 // combinazione di due array stereo5 (6 {7 var a, b;8 a = [SinOsc.ar(440, 0, 0.2), Saw.ar(662, 0.2)];9 b = [SinOsc.ar(442, 0, 0.2), Saw.ar(660, 0.2)];

10 Mix([a, b]).postln;11 }.play;12 )

Nel primo caso viene passato un BinaryOpUGen (in questo caso un’operazione diaddizione delle due UGens), e nel secondo caso un Array di due BinaryOpUGens.

Da notare che nel primo esempio si usa Mix.new( ... ) e che nel secondo si usaMix( ... ). Il secondo è un’abbreviazione del primo. Il metodo new è quello piùcomune per creare un oggetto nuovo. In alcuni casi gli oggetti hanno più di unmetodo per creare oggetti, come i metodi ar e kr delle UGens. (Mix è una classedi convenienza: non crea un oggetto Mix, ma ritorna soltanto il risultato dellasua somma, o un BinaryOpUGen oppure un Array di questi).

Mix ha inoltre un altro metodo,fill, che prevede due argomenti. Il primo è un

Page 26: SuperCollider  Tutorial

Creiamo un Mix

25

numero, che determina quante volte il secondo argomento, una Function, saràvalutata. Il risultato di questa valutazione verrà poi sommato. Per chiarire questiconcetti consideriamo il prossimo esempio:

1 (2 var n = 8;3 { Mix.fill(n, { SinOsc.ar(500 + 500.0.rand, 0, 1 / n) })

}.play;4 )

La Function sarà valutata n volte, ogni volta creando un SinOsc, con una frequen-za random da 500 a 1000 Hz (500 più un numero random fra 0 e 500). l’argomentomul di ogni SinOsc è settato a 1/n, assicurando quindi che l’ampiezza totale nonandrà mai oltre i limiti di -1 e 1. Semplicemente cambiando il valore di n, sipuò avere un numero variabile di SinOsc! Queto tipo di approccio rende il codiceestremamente flessibile e riusabile.

Ogni volta che viene valutata, alla Function viene passato come argomento unnumero. Così, se n è uguale a 8, la Function considererà valori da 0 a 7, in sequen-za crescente. Dichiarando un argomento dentro la nostra Function, è possibileusare questo valore come parametro. Vediamo:

1 // si osservi la post window per vedere frequenze e indici2 (3 var n = 8;4 {5 Mix.fill(n, { arg index;6 var freq;7 index.postln;8 freq = 440 + index;9 freq.postln;

10 SinOsc.ar(freq , 0, 1 / n)11 })12 }.play;13 )

Dalla combinazione di addizioni e moltiplicazioni ( o qualunque altra possibileoperazione matematica che si può immaginare!) con l’uso di classi come Mix, sihanno gli strumenti necessari per combinare sorgenti multicanali di suono al finedi ottenere mix e submix complessi.Per maggiori informazioni:

[Mix] [BinaryOpUGen] [Operators] [Syntax-Shortcuts]

Page 27: SuperCollider  Tutorial

Scoping and Plots

26

Capitolo 7 Scoping and Plots

Oltre ai metodi visti nei capitoli precedenti, l’oggetto Function dispone di altrimetodi legati all’audio. Il primo che vediamo è Function-plot:

1 { PinkNoise.ar(0.2) + SinOsc.ar(440, 0, 0.2) + Saw.ar(660, 0.2) }.plot;

Questo metodo crea un grafo del segnale prodotto dall’output della Function. Èpossibile specificare alcuni argomenti come la durata. Il valore di default è 0.01secondi, ma è possibile modificarlo a piacere.

1 {2 PinkNoise.ar(0.2) + SinOsc.ar(440, 0, 0.2) + Saw.ar(660, 0.2)3 }.plot(1);

Questo metodo può essere utile per verificare cosa succede in fase di testing, ese si ottiene l’output desiderato.

Il secondo metodo,Function-scope, mostra un display simile ad un oscilloscopiodell’ouput della funzione. Questo però funziona solo con il server interno, quindisi rende necessario che questo server sia in stato running. È possibile avviarlousando la finestra:

oppure via codice:

1 Server.internal.boot;

Cliccando su ->default sulle finestre server localhost o internal, si setta il serverselezionato a default (quello cioè su cui verrà suonato l’audio) salvandone ilriferimento nella variabiles. Dal fatto che Function-scope lavora solo sul serverinternal, comunque, funzionerà sempre e solo su esso.Provando il codice:

Page 28: SuperCollider  Tutorial

Scoping and Plots

27

1 {2 PinkNoise.ar(0.2) + SinOsc.ar(440, 0, 0.2) + Saw.ar(660, 0.2)3 }.scope;

si dovrebbe aprire una finestra di questo tipo:

Questo metodo scope lavora anche per canali multipli:

1 { [SinOsc.ar(440, 0, 0.2), SinOsc.ar(442, 0, 0.2)] }.scope;

Lo Scope ha anche un argomento di zoom. Valori alti corrispondono allo zoomout.

1 { [SinOsc.ar(440, 0, 0.2), SinOsc.ar(442, 0, 0.2)] }.scope(zoom: 10);

Come Function-plot, Function-scope può essere utile per testing e per vedere sedavvero si ottiene quello che si vuole.

7.1 Scoping su richiesta

Si può anche ottenere l’output del server interno in ogni momento, richiamandoil metodo scope su di esso.

Page 29: SuperCollider  Tutorial

Scoping and Plots

28

1 {2 [ SinOsc.ar(440, 0, 0.2), SinOsc.ar(442, 0, 0.2) ]3 }.play(Server.internal);4 Server.internal.scope;5 // si potrebbe usare la variabile s, se fosse settata a internal

Si può fare la stessa cosa cliccando la finestra del server interno premendo la s.

7.2 Local vs. Internal

La differenza fra i due tipi di server, interno e locale, è relativamente semplice: ilserver interno gira come un processo dentro l’app client: un programma dentroun programma. Il principale vantaggio di questo approccio è che permette a dueapplicazioni di condividere memoria, utile per cose come lo scoping in tempo realedell’audio. Lo svantaggio evidente è che le due applicazioni sono interdipendenti,così se il client va in crash, il server lo seguirà a ruota.Per maggiori informazioni:

[Function] [Server] [Stethoscope]

Page 30: SuperCollider  Tutorial

Help di Supercollider

29

Capitolo 8 Help di Supercollider

Siamo giunti ad un buon punto di questo manuale per fermarci ed esplorarealcuni metodi per trovare ulteriori informazioni di SC. Nei file di Help vieneutilizzato il link cliccabile come questo:[Help]Cliccando su questo link si aprirà la finestra principale dell’help, che contieneun certo numero di link ad altri file di help. A questo punto, potrebbe essereuna buona idea familiarizzare con alcuni di questi. Quelli che rispondono ai linkEssential Topics e Language sono di particolare importanza.

8.1 Classi and Metodi

Abbiamo visto finora sufficiente teoria sull’OOP, da conoscere che ci sono leclassi, che sono come template per oggetti, e istanze, oggetti che sono creati daquesti template. Si possono anche avere classi e metodi di istanza, che possonoprendere in input argomenti o meno. Metodi di classe fanno cose come creareistanze (come alcune funzioni convenienti che non richiedono un istanza attuale),e metodi di istanza che controllano e manipolano le istanze stesse. Ci sono anchele variabili d’istanza, che sono i dati specifici di ogni istanza e le variabili diclasse, che sono dati in comune fra tutte le istanze.

Ricordiamo che ogni cosa che comincia nel codice di SC con una lettera maiuscolanel codice è una classe. Molte classi hanno l’help file. In SC, se si seleziona unaclasse facendo doppio click e premendo Cmd-?, verrà aperto l’help file della classe,se esiste, altrimenti si aprià la finestra main dell’help. Si provi con:

1 SinOsc

È possibile avere una finestra con una breve descrizione della classe e di cosa fa,una lista di alcuni metodi e una descrizione dei loro argomenti.Di solito, in fondo, ci sono alcuni esempi della classe in azione. Questi possonoessere molto utili per rendere chiaro esattamente cosa fa la classe, e possonoservire come punto di partenza per il proprio lavoro. È una buona idea copiaree incollare questi esempi in una nuova finestra e provarli, apportando modificheo meno per capire davvero come funzionano.Per accedere all’help file per Function e Array, dato che spesso appaiono nelcodice come {...} e [...], basta selezionarli e premere Cmd-? su:

Page 31: SuperCollider  Tutorial

Help di Supercollider

30

1 Function2 Array

Alcuni metodi hanno anche help file, e alcuni di essi compaiono sui topics gene-rali. Molti di questi sono elencati nella finestra main dell’help.

8.2 Shortcut sintattici

Vi ricordate dell’esempio di Mix(...) vs. Mix.new(...) del capitolo capitolo 6.?SC ha un certo numero di tali forme di scorciatoie o sintassi alternative. Unesempio comune è il seguente: someObject.someMethod(anArg) è equivalente asomeMethod(someObject, anArg).Qui vediamo un esempio concreto. Le due righe di codice seguenti fanno la stessacosa:

1 { SinOsc.ar(440, 0, 0.2) }.play;

3 play({ SinOsc.ar(440, 0, 0.2) });

Vi sono numerosi altri esempi di scorciatoie di sintassi nella documentazione diSC. Se si incontra qualcosa di sconosciuto, spesso un buon posto per cercare è[Syntax-Shortcuts], che fornisce molti esempi di questi shortcut.

8.3 Snooping, etc.

SC ha numerosi altri metodi di tracciare informazioni su classi, metodi, etc.Molti di questi potrebbero non essere troppo di aiuto a questo punto, ma è utileconoscerle per un uso futuro. Li si può trovare nel file [More-On-Getting-Help] e[Internal-Snooping].Per maggiori informazioni:

[More-On-Getting-Help] [Internal-Snooping] [Syntax-Shortcuts]

Page 32: SuperCollider  Tutorial

SuperCollider 3 Synth Server: architettura

31

Capitolo 9 SuperCollider 3 Synth Server: archi-tettura

Arrivati a questo punto possiamo considerare in dettaglio il Server di SuperCol-lider; questo e i prossimi capitoli si occuperanno dell’architettura del Server edelle sue principali componenti.

9.1 Introduzione

Il Server di SuperCollider v3 è un motore di sintesi semplice e potente. Mentrequesto motore è in stato running, possono essere creati nuovi moduli, distruttie/o ricollegati, possono essere creati e/o riallocati buffer; possono inoltre esserecreati e collegati dei processi di effettistica in un flusso di segnali in modo deltutto dinamico a tempo di scheduling.Tutti i moduli in stato running sono ordinati in un albero di nodi che definiscel’ordine di esecuzione. Il patching (collegamento) fra i moduli è costruito attra-verso i bus globali di audio e di controllo.Tutti i comandi sono ricevuti via TCP o UDP usando una versione semplificatadi Open Sound Control (OSC). Il server di sintesi e il suo client (uno o più)potrebbe essere sulla stessa macchina o nella rete. Il server di sintesi non invia oriceve messaggi MIDI. Si aspetta che il client invierà solo comandi di controllo. Sesi rende necessario usare il protocollo e/o i messaggi MIDI, è compito del clientricevere tali messaggi e convertirli in comandi OSC appropriati per il motore disintesi.Le definizioni di synth sono salvate in file generati dall’applicazione linguaggioSC (SCLang). Le definizioni delle UGens sono Mach-O bundles (da non confon-dersi con CFBundles). Le API delle UGen sono un semplice interfaccia in C.

9.2 Principali componenti

Elenchiamo qui brevemente tutti i componenti dell’architettura del server di SCdandone una veloce definizione. Per una trattazione più completa si rimanda illettore ai capitoli relativi.

• NodoUn nodo è un’entità indirizzabile in un albero di nodi eseguito dal motore di

Page 33: SuperCollider  Tutorial

SuperCollider 3 Synth Server: architettura

32

sintesi. Ci sono due tipi di nodi: Synths e Groups. l’albero definisce l’ordinedi esecuzione di tutti i Synths. Tutti i nodi hanno un ID intero.

• SynthUn Synth è una collezione di unit generator (UGen) che vengono eseguiteinsieme. Questi possono essere indirizzati e controllati da comandi inviati almotore di sintesi. In genere si occupano di leggere dati in input e scriveredati in output sui bus globali di audio e di controllo. I Synths possono averei propri controlli locali che sono settati tramite comandi al server.I Synth vengono trattati approfonditamente nel capitolo 10.

• Synth DefinitionI Synths sono creati a partire dalle Synth Definition. I file di Synth Definitionsono creati dal SC language application e vengono caricati nel server di sintesi.Ci si può riferire ad essi tramite il nome.Le Synth Definition vengono trattate approfonditamente nel capitolo 10.

• GroupUn Group è una collezione di nodi rappresentati come una linked list. Unnuovo nodo potrebbe essere aggiunto in testa o in coda al gruppo. I nodidentro un gruppo possono essere controllati insieme. I nodi in un Grouppossono essere sia Synths che altri Group. Allo startup del server si ha untop level Group con un ID=0 che definisce la radice dell’albero. Se il serverviene avviato dentro SCLang (invece di essere avviato da linea di comando)ci sarà anche il default group con un ID=1 e sarà il target di default per tuttii nuovi nodi.I Group vengono trattati approfonditamente nel capitolo 12.

• Bus I Bus vengono trattati approfonditamente nel capitolo 11.

− Audio Buses I Synths inviano segnali audio fra di loro tramite un solo ar-ray globale di bus audio. I Bus Audio sono indicizzati da interi, partendoda 0. l’utilizzo di bus, come la connessione diretta fra synths, permette diconnettere i synths stessi all’insieme di altri synths senza particolari cono-scenze su di essi. I bus con numero più basso scrivono direttamente suglioutput audio dell’hardware. Immediatamente seguenti ai bus di output ci

Page 34: SuperCollider  Tutorial

SuperCollider 3 Synth Server: architettura

33

sono quelli di input, che leggono dagli input audio dell’hardware. Il nu-mero di canali bus definiti come input e output non ha però un riscontrodiretto e fisico con l’hardware.

− Control Buses I Synths possono inviare segnali di controllo fra di loro,tramite un solo array globale di bus di controllo. I Bus sono indicizzati daun numero intero cominciando da zero.

− Shared Control Buses Il server internal (che è in stato running con lostesso spazio di indirizzi dell’app del client) ha un certo numero di bus dicontrollo condivisi con l’app del client con un accesso di lettura/scritturasincrono. Questi bus sono indicizzati da interi, partendo da zero.

• Buffers I Buffer sono array di valori da 32 bit floating point con un picco-lo header descrittivo. I Buffers sono salvati in un array globale indicizzatoda interi partendo da zero. I Buffers potrebbero essere allocati in manieraprotetta, caricati e liberati mentre la sintesi sta eseguendo anche quando leUgen gli stanno usando. I Buffers sono usati per wave tables, sample buffers,linee di ritardo, inviluppi e/o per ogni altra necessità che potrebbe usare unarray di valori in floating point. I file audio potrebbero essere caricati dentroo scritti da buffers.I Buffer vengono trattati approfonditamente nel capitolo 13.

• Unit Generator Definitions Le definizioni di Unit Generator sono caricateautomaticamente all’avvio del programma. Esse sono in librerie binary code evengono usate dai Synths per costruire algoritmi di sintesi. Le definizioni delleUGens hanno un nome che corrisponde al nome della classe del linguaggio diSC usato nei Synth Definitions.

Ora che abbiamo scoperto alcune informazioni base su SC e sul server, andiamoa studiare le astrazioni server, che sono le varie classi nel linguaggio dell’appdel client che rappresentano qualcosa sul server. È importante capire che questioggetti sono solo rappresentazioni client-side di parti dell’architettura del server,e non devono esser confuse con le parti stesse che rappresentano. Questi oggetti-astrazioni sono utilizzati semplicemente per convenienza nel linguaggio.Capire la distinzione fra le due cose può non essere così diretto e può portare aconfusione, così, in generale ci riferiremo alle classi client-side con nomi maiusco-li, e gli aspetti corrispondenti dell’architettura del server con nomi con letteraminuscola, per esempio Synth vs. synth.

Si è già incontrato un tipo di astrazione server, la classe Server stessa. Gli oggetti

Page 35: SuperCollider  Tutorial

SuperCollider 3 Synth Server: architettura

34

riferiti a Server.local e Server.internal (e qualsiasi cosa salvata nella variabile sin ogni momento) sono istanze di Server.

Ora è il momento di prendere familiarità con altre astrazioni. Le prime che ve-diamo sono le classe SynthDef e Synth, che presentiamo nel capitolo successivo.

Page 36: SuperCollider  Tutorial

SynthDefs e Synths

35

Capitolo 10 SynthDefs e Synths

La prima che vediamo è la classe SynthDef, che è un’abbreviazione di synthdefinition.

10.1 SynthDef

Fino ad ora sono state usate Functions per generare audio. Il loro utilizzo è moltoutile per testing veloci, e in casi dove è necessaria la massima flessibilità. Questoperchè ogni volta che si esegue il codice, la Function viene ri-valutata ex-novocon la conseguenza che i risultati possono variare e di molto.

Il server, in generale, non conosce tuttavia Functions, o l’Object Oriented, o ilSC language, ma piuttosto vuole infomazioni su come creare output audio in unaforma speciale detta definizione di synth. Una definizione di synth è formata dauna serie di informazioni sulle UGens da utilizzare e come esse devono essereinterconnesse. Questa serie di informazioni viene inviata al server in una formaspeciale ottimizzata, detta ‘byte code’ con cui il server può lavorare in manieramolto efficiente.

Una volta che il server ha una definizione di synth, può essere molto efficientecreare un certo numero di synth basandosi su questa definizione. I synths sulserver sono, in sostanza, entità che creano o processano il suono, o produconosegnali di controllo per pilotare altri synths.

Questa relazione fra la definizione di synth e i synth è analoga alla relazione cheintercorre fra le classi e le istanze, in cui le primi sono un templare per le seconde.l’analogia è però solo a livello concettuale in quanto le app server non conosconol’ OOP.

Fortunatamente per noi, nel linguaggio ci sono classi come SynthDef, che rendonosemplice creare il byte code necessario e inviarlo al server, e avere a che fare condefinizioni di synth in modalità object oriented.

Quando si usa qualunque metodo di Function per creare audio, ciò che succedeè che viene creata una istanza corrispondente di SynthDef ‘dietro le quinte’, percreare il protocollo di dialogo. Viene generato il byte code necessario e inviato alserver, dove sarà creato un synth per suonare l’audio desiderato. I metodi audiodi Function offrono così un tipo di convenienza per il programmatore, liberandolodal compito di occuparsi di byte code e cose lontane al suo modo di lavorare.

Page 37: SuperCollider  Tutorial

SynthDefs e Synths

36

Vediamo quindi come si crea una SynthDef. Si usa il suo metodo new, allostesso modo delle Function. Inoltre, come per le Functions, SynthDef ha ancheun metodo play. In un certo senso sono proprio equivalenti:

1 //prima la funzione2 { SinOsc.ar(440, 0, 0.2) }.play;

4 // ora una SynthDef equivalente alla funzione5 SynthDef.new("tutorial-SinOsc", { Out.ar(0, SinOsc.ar(440, 0, 0.2))

}).play;

SynthDef-new prevede un certo numero di argomenti. Il primo è il nome, di solitosotto forma di stringa. Il secondo è, di fatto, una Functione viene detto UGenGraph Function, perchè esplicita al server come connettere i vari UGens.

10.2 SynthDefs vs. Functions

La UGen Graph Function, usata nella SynthDef, è simile ad una Function, epresenta una non troppo lieve differenza: ha un UGen extra detta Out. Out sioccupa di scrivere in output un segnale ar o kr sui bus del server, che possonoessere canali di un mixer o degli output veri e propri. I bus verranno discussi indettaglio più avanti; per ora è sufficiente sapere che sono usati per inviare audioin uscita e per leggerlo da sorgenti come microfoni in ingresso alla scheda audio.

La UGen Out prevede 2 argomenti:

− Il primo è l’indice numerico del bus su cui scrivere. La numerazione dei buscomincia da 0, che, su un setup stereo, è il canale sinistro.

− Il secondo argomento è o una UGen o un Array di Ugens. Se si passa un array(per esempio un output multicanale), allora il primo canale sarà suonato sulbus con l’indice indicato, il secondo sul bus con l’indice + 1, e così via.

Vediamo un esempio stereo per far chiarezza su come effettivamente lavora questaUGen.

Page 38: SuperCollider  Tutorial

SynthDefs e Synths

37

1 (2 SynthDef.new("tutorial-SinOsc-stereo", { var outArray;3 outArray = [SinOsc.ar(440, 0, 0.2), SinOsc.ar(442, 0, 0.2)];4 Out.ar(0, outArray)5 }).play;6 )

Il SinOsc con la frequenza di 440 Hz sarà suonato sul bus 0 (il canale sinistro)e il SinOsc con la frequenza di 442 Hz sarà suonato sul bus 1 (il canale destro).Quando usiamo Function-play, viene di fatto creata automaticamente per noiuna UGen Out. Il bus di default per questa UGen è 0.

Sia Function-play che SynthDef-play restituiscono un altro tipo di oggetto, unSynth, che rappresenta un synth sul server. Se salviamo questo oggetto assegnan-dolo in una variabile, è possibile controllarne il comportamento tramite i metodidi cui dispone. Per esempio il metodo free causa lo stop del synth sul server ene vengono liberate le risorse cpu e di memoria che gli erano state riservate.

1 x = { SinOsc.ar(660, 0, 0.2) }.play;2 y = SynthDef.new("tutorial-SinOsc", { Out.ar(0, SinOsc.ar(440, 0, 0.2))

}).play;3 x.free; // libera x4 y.free; // libera y

l’utilizzo del metodo free è più flessibile del comando da tastiera Cmd-., che‘libera’ tutti i synths insieme.

SynthDef ha 3 metodi che inviano il byte code corrispondente all’app server senzacreare immediatamente un synth: send e load e writedef. La differenza fra questi3 è:

− send fa lo stream della definizione sulla rete

− load carica la definizione fra le definizioni correnti del server.

− writedef scrive la definizione sul disco sotto forma di file così da permettereal server di caricarla.

Questi file avranno estensione .scsyndef ( nel nostro esempio tutorial-SinOsc.scsyndef),e saranno salvato nella cartella synthdefs/ presente nella directory principale diSC. Questi file saranno inoltre caricati automaticamente ogni qualvolta si farà il

Page 39: SuperCollider  Tutorial

SynthDefs e Synths

38

boot del server e rimarranno in questa cartella fino a che non saranno specifica-mente cancellati.In generale è possibile usare send senza riusare le definizioni tutte le volte. Sirende tuttavia necessario in alcuni casi usare load, casi in cui si ha a che farecon definizioni molto grandi o complicate, a causa del limite della dimensionedei pacchetti sulla rete.

Usare le SynthDef comporta certi vantaggi e certe limitazioni.

Una volta che si ha una definizione di un synth in un app server, si possono crearemolti synths da questa con un overhead relativamente basso di CPU. È possi-bile farlo con il metodo new, che prende il nome della definizione come primoargomento.

1 SynthDef.new("tutorial-PinkNoise", { Out.ar(0, PinkNoise.ar(0.3))}).send(s);

2 x = Synth.new("tutorial-PinkNoise");3 y = Synth.new("tutorial-PinkNoise");4 x.free; y.free;

È più efficiente che chiamare ripetutamente play sulla stessa Function, perchè sirisparmia lo sforzo di rivalutare la Function, compilare il byte code e inviarlo alserver per ogni valutazione. In molti casi però questo risparmio dell’uso di CPU ècosì piccolo da essere insignificante; in altri casi diventa importante, specialmentese si prevede una produzione massiccia di synths.

Una limitazione nel lavorare con SynthDef direttamente è che la UGen GraphFunction in un SynthDef è valutata una volta e solo una volta. (Si ricordi cheil server non conosce in sostanza niente del linguaggio SC). Questo significa chela definizione del synth è meno flessibile di quanto si pensi. Si comparino questidue esempi:

Page 40: SuperCollider  Tutorial

SynthDefs e Synths

39

1 // prima con una Function. Da notare la frequenza random, differente adogni play

2 f = { SinOsc.ar(440 + 200.rand, 0, 0.2) };3 x = f.play;4 y = f.play;5 z = f.play;6 x.free; y.free; z.free;

8 // Ora con una SynthDef. No random!!9 SynthDef("tutorial-NoRand", { Out.ar(0, SinOsc.ar(440 + 200.rand, 0,

0.2)) }).send(s);10 x = Synth("tutorial-NoRand");11 y = Synth("tutorial-NoRand");12 z = Synth("tutorial-NoRand");13 x.free; y.free; z.free;

Ogni volta che viene creato un nuovo Synth basandosi sulla definizione, la fre-quenza è la stessa. Questo perchè la Function (e quindi 200.rand) viene valutatauna volta sola e cioè al momento della creazione.

10.3 SynthDefs ‘variabili’

Per superare le limitazioni viste in precedenza, esistono diversi modi per ottenerevalori di output non fissi da SynthDefs. Si può usare per esempio [Rand], checalcola un numero random fra i valori min e max passati come parametri quandoalla creazione del synth:

1 // Se si usa l’oggetto Rand, allora si pu\‘o!2 SynthDef("tutorial-Rand", { Out.ar(0, SinOsc.ar(Rand(440, 660), 0,

0.2)) }).send(s);3 x = Synth("tutorial-Rand");4 y = Synth("tutorial-Rand");5 z = Synth("tutorial-Rand");6 x.free; y.free; z.free;

Il modo più comune di avere variabilità nella SynthDef è attraverso il passaggio diparamentri alla Ugen Graph Function. Questo permette di settare differenti valoriquando il synth viene creato. Viene pertanto passato, come secondo argomentonella Synth-new, un array di argomenti formato da coppie nome-valore.

Page 41: SuperCollider  Tutorial

SynthDefs e Synths

40

1 (2 SynthDef("tutorial-args", { arg freq = 440, out = 0;3 Out.ar(out, SinOsc.ar(freq, 0, 0.2));4 }).send(s);5 )6 x = Synth("tutorial-args"); // no args7 y = Synth("tutorial-args", ["freq", 660]); // cambia la freq8 z = Synth("tutorial-args", ["freq", 880, "out", 1]); // cambia freq

e canale di output9 x.free; y.free; z.free;

Questa combinazione di argomenti e UGens permette di avere una singola defi-nizione abbastanza flessibile, ma in qualche caso, dove è richiesta un alto gradodi flessibilità, è ancora necessario usare le Functions, o creare definizioni multipledi synth.

10.4 Synth

La classe Synth prevede alcuni metodi che permettono di variare il valore degliargomenti dopo che un synth è stato creato. Per ora ne vedremo uno, il metodoset. Synth-set prevede come argomenti una o più coppie nome-valore dove nomeè il parametro della synthdef a cui si associa il valore.

1 Server.default = Server.internal;2 s = Server.default;3 s.boot;4 (5 SynthDef.new("tutorial-args", { arg freq = 440, out = 0;6 Out.ar(out, SinOsc.ar(freq, 0, 0.2));7 }).send(s);8 )9 s.scope; // per vedere l’effetto

10 x = Synth.new("tutorial-args");11 x.set("freq", 660);12 x.set("freq", 880, "out", 1);13 x.free;

10.5 Alcune nozioni su Symbols, Strings, SynthDef e Arg Names

I nomi delle SynthDef e degli argomenti possono essere o una String, come vistoin precedenza, o un altro tipo di letterale detto Symbol. È possibile scriveresimboli alternativamente in uno dei due modi seguenti:

Page 42: SuperCollider  Tutorial

SynthDefs e Synths

41

− racchiudendolo fra apici singoli: ’tutorial-SinOsc’

− preceduti da un backslash: " tutorial"-SinOsc.Come le Strings, anche i Symbols sono composti da una sequenza alfa-numerica.La differenza fra Stringhe e Simboli è che tutti i simboli con lo stesso testo sonogarantiti essere identici, ( xes esattamente lo stesso oggetto), mentre le stringhepotrebbero differire. Per testare questa cosa, usiamo ’===’. Eseguiamo il codicee osserviamo la post window

1 "a String" === "a String"; // false2 \aSymbol === ’aSymbol’; // true

In generale nei metodi che comunicano con il server è possibile usare Stringse Symbol in modo intercambiabile, ma potrebbe non essere vero in un codicegenerico.

1 "this" === \this; // false

Per maggiori informazioni:

[SynthDef] [Synth] [String] [Symbol] [Literals] [Randomness] [UGens]

Page 43: SuperCollider  Tutorial

I Buss

42

Capitolo 11 I Buss

Vediamo ora qualche ulteriore informazione sui bus sul server. Esiste un’analogiacon i bus e mandate sui mixer analogici perchè presentano funzionalità simili:definiscono il routing dei segnali fra un punto e un altro di una catena audio. InSuperCollider questo significa da e per l’hardware audio o fra synth differenti.Esistono due tipi di bus:

− Audio Rate

− Control RateCom’è intuibile, il primo indirizza segnali audio mentre il secondo segnali di con-trollo.

I Bus control rate sono abbastanza semplici da comprendere, sono semplici canalidi comunicazione ognuno con un proprio indice, partendo da zero.

I Bus audio rate sono simili ai precedenti, ma richiedono una piccola spiegazio-ne ulteriore. Un’ app Server avrà un certo numero di canali di input e output;questi canali corrispondono a bus audio con indice più basso, con i canali dioutput anteposti nell’ordine ai canali di input. Per esempio, se immaginiamo unserver con 2 canali di output e 2 canali di input (x es. stereo in e stereo out),allora i primi 2 audio bus (indice 0 e 1) saranno gli output e i 2 immediatamen-te seguenti (indice 2 e 3) saranno gli input. Scrivere audio su uno dei 2 bus dioutput provocherà un’emissione sonora dagli altoparlanti e leggere audio dai 2bus di input permetterà di acquisire suoni in SC, per processi di registrazione odi elaborazione.

I bus audio rimanenti saranno privati. Essi sono usati per inviare audio e segnalidi controllo fra i vari synths. Inviare audio ad un bus privato non comporteràemissione sonora negli altoparlanti se non sarà reindirizzerato su uno dei bus dioutput. Questi bus privati sono spesso usati per esempio per funzionalità comeuna mandata effetti, qualcosa cioè che richiede alcuni passi di elaborazione audioprima di generare output.

Il numero di bus audio e di controllo disponibili, come il numero di canali dioutput e di input è settato al momento del boot dell’App server.( Si veda [Ser-verOptions] per informazioni su come settare il numero di canali e bus di inpute output)

Page 44: SuperCollider  Tutorial

I Buss

43

11.1 Buss: scrittura e lettura

Si è già visto Out.ar, che permette di scrivere audio (per esempio play out) suun bus. Ricordiamo che prevede 2 argomenti: un indice e un output, che possonoessere un vettore di UGens (xes un output multicanale) o una singola UGen.

Per leggere da un bus server si utilizza un’altra UGen: In. Il metodo ar di In haanch’esso due argomenti: un indice, e il numero di canali da leggere. Se il numerodi canali è maggiore di 1, allora l’output di In sarà un Array. Si provi il seguenteesempio e si osservi la post window:

1 In.ar(0, 1); // restituir\‘a ’an OutputProxy’2 In.ar(0, 4); // restituit\‘a un Array di 4 OutputProxies

Un OutputProxy (si veda l’esempio precedente) è un tipo speciale di UGenche agisce come segnaposto per qualche segnale che sarà presente quando qual-che synth starà eseguendo. Probabilmente non sarà mai necessario averci a chefare direttamente, così non occorre preoccuparsi troppo di questo costrutto; èsufficiente per i nostri scopi capire cosa sono in generale. Per una trattazionecompleta su OutputProxy e l’interessante utilizzo di un proxyspace si vedaLe UGen In e Out hanno metodi kr, che leggeranno e scriveranno segnali controlrate da e in bus control rate.Da notare che Out.kr convertirà un segnale audio rate in un segnale controlrate (operazione che viene detta sottocampionamento); l’operazione inversa nonè possibile: Out.ar necessita di un segnale audio rate come secondo argomento.

1 // Questo genera un errore: segnale control rate su un bus audio rate2 {Out.ar(0, SinOsc.kr)}.play;

4 // Questo non genera errore: segnale audio rate sottocampionato acontrol rate

5 Server.internal.boot;6 {Out.kr(0, SinOsc.ar)}.scope;

(Questa limitazione non è universale fra le UGens audio rate, e molte accetteranosegnali control rate per alcuni o tutti i loro argomenti. Alcune convertiranno icontrol rate in input in audio rate se necessario, calcolando i valori mancantitramite un processo di interpolazione.)Da notare inoltre che quando più Synth scriveranno nello stesso bus, l’outputsarà sommato, o in altre parole, mixato.

Page 45: SuperCollider  Tutorial

I Buss

44

1 (2 SynthDef("tutorial-args", { arg freq = 440, out = 0;3 Out.ar(out, SinOsc.ar(freq, 0, 0.2));4 }).send(s);5 )6 // sia x che y scrivono sul bus 1. L’output viene mixato7 x = Synth("tutorial-args", ["out", 1, "freq", 660]);8 y = Synth("tutorial-args", ["out", 1, "freq", 770]);

11.2 Creiamo un Bus Object

Esiste un comodo oggetto lato-client che rappresenta i bus server: Bus. Dato chetutto ciò che serve è una UGen di In o di Out e un indice per scrivere su un bus,ci si potrebbe chiedere a cosa dovrebbe servire un Bus Object. Molte volte infattinon li si usa direttamente, specialmente se ciò che si sta facendo è solo far fluireaudio dall’ingresso all’uscita della nostra scheda audio. In generale tuttavia i Busoffrono alcune utili funzionalità. Le vedremo in seguito, prima vediamo come èpossibile creare un’oggetto di questo tipo.Come molte UGens che hanno metodi ar e kr, anche i Bus hanno 2 metodi dicreazione comuni: Bus-audio e Bus-Control. Ognuno prevede 2 argomenti: unoggetto Server e il numero di canali.

1 b = Bus.control(s, 2); // Crea un Bus control a 2 canali2 c = Bus.audio(s); // Crea un Bus audioprivato

Ci si potrebbe chiedere cos’è un bus due-canali, dato che fin’ora non è ancora sta-to menzionato. Occore ricordare pertanto, che quanto Out ha un Array come suosecondo argomento, scriverà i canali dell’Array in bus consecutivi. Richiamiamoun esempio dal capitolo precendete:

1 (2 SynthDef.new("tutorial-SinOsc-stereo", { var outArray;3 outArray = [SinOsc.ar(440, 0, 0.2), SinOsc.ar(442, 0, 0.2)];4 Out.ar(0, outArray); // scrive sui bus 0 e 15 }).play;6 )

La verità è che non ci sono bus multicanali di per se, quindi non esiste un oggettobus due-canali, tuttavia gli oggetti Bus sono in grado di rappresentare una seriedi bus con indici consecutivi. L’incapsulamento di molti bus lato-server adiacenti

Page 46: SuperCollider  Tutorial

I Buss

45

in un singolo oggetto Bus, permette di trattarli come un gruppo, rendendo il loroutilizzo molto più agevole.Quando si lavora con i cosiddetti bus privati (cioè quelli oltre i canali audio di in-put e output e tutti i bus di controllo), generalmente si vorrebbe che quest’ultimivengano usati con un certo ordine; si potrebbe farlo oculatamente se si potessedecidere quale indice usare, come fossero array, ma i Bus gestiscono questi indiciautomaticamente. Ogni oggetto Server ha un bus allocator e quando si crea unoggetto Bus, questo gli riserva un indice privato che non potranno essere resti-tuito fino alla fine dell’utilizzo del Bus. È possibile recuperare l’indice di un Bususando il suo metodo index.

1 // riavviamo il server e quindi azzeriamo il bus allocator2 s.reboot;3 b = Bus.control(s, 2); // un control Bus a 2 canali4 b.index; // dovrebbe essere 05 b.numChannels // Bus ha anche un metodo numChannels6 c = Bus.control(s);7 c.numChannels; // il numero di default dei canali=18 c.index; // =2; b usa sia 0 che 1

Tramite l’utilizzo di oggetti Bus per rappresentare bus adiacenti, si può garantireche non avvenga un conflitto. Dal momento che gli indici sono allocati dinamica-mente, è possibile cambiare il numero di canali di un bus dal codice (per esempioperchè si rende necessario indirizzare un segnale multicanale) e garantire che iltutto sia ancora stabile e sicuro. Se fosse compito del programmatore l’allocazionedegli indici dei bus e se si rendesse necessario ad un certo punto riarrangiare tuttoper un canale adiacente extra, dal momento che gli indici devono essere conse-cutivi, sarebbe davvero molto complicato! Questo è un buon esempio del potereche ha la programmazione a oggetti; incapsulando negli oggetti stessi processicome l’allocazione degli indici dei bus, si ha un certo livello di astrazione e si puòscrivere codice molto flessibile.È possibile liberare e riallocare l’indice di un Bus in uso richiamando il metodofree;

1 b = Bus.control(s, 2);2 b.free; // libera gli indici. b diventa inutilizzabile se non reinstanziato

Da notare che questo metodo non "libera" il bus sul server, non viene cioè disal-loccato; il metodo free semplicemente comunica all’allocatore che si è "liberato"un bus e che può essere liberamente riallocato il suo indice.Vediamo ora un ulteriore vantaggio dell’uso dei bus privati audio rate. Comedetto in precedenza, gli indici dei bus con valore più basso sono i canali di input

Page 47: SuperCollider  Tutorial

I Buss

46

e output. Detto ciò, se vogliamo usare il primo bus privato, quale indice dovremoutilizzare? Consideriamo per esempio l’App Server con 2 canali di output e 2 diinput. Il primo audio bus privato è indicizzato con 4 (0, 1, 2, 3 ... 4!!). Cosìquando scriviamo il nostro codice, daremo il valore 4 all’Ugen Out come indicedel bus.Ma cosa succede se si decide in seguito di cambiare il numero di canali di outpute impostarlo a 6? Tutto ciò che è stato scritto sul nostro bus privato, finirà suuno dei canali di output! In realtà un bus allocator di un Server audio assegneràsolo indici privati, così se si rendesse necessario cambiare il numero di canali diinput o di output, gestirà gli indici considerando il numero di bus di input e dioutput quando verra eseguito il codice. Ancora, questo principio rende il codicepiù flessibile.

11.3 Buss in azione!Vediamo qui due esempi che fanno uso di bus. Il primo è con un bus control rate.

1 (2 SynthDef("tutorial-Infreq", { arg bus, freqOffset = 0;3 // this will add freqOffset to whatever is read in from the bus4 Out.ar(0, SinOsc.ar(In.kr(bus) + freqOffset, 0, 0.5));5 }).send(s);

7 SynthDef("tutorial-Outfreq", { arg freq = 400, bus;8 Out.kr(bus, SinOsc.kr(1, 0, freq/40, freq));9 }).send(s);

11 b = Bus.control(s,1);12 )

14 (15 x = Synth.new("tutorial-Outfreq", [\bus, b.index]);16 y = Synth.after(x, "tutorial-Infreq", [\bus, b.index]);17 z = Synth.after(x, "tutorial-Infreq", [\bus, b.index, \freqOffset,

200]);18 )19 x.free; y.free; z.free; b.free;

Sia y che z fanno riferimento allo stesso bus; il secondo synth modifica la fre-quenza del segnale di controllo aggiungendo un valore costate di 200. Questoapproccio è più efficiente rispetto ad avere 2 oscillatori di controllo separati percontrollare la frequenza. Questo tipo di strategia di connettere insieme synths,ognuno dei quali fa cose differenti in un processo più grande, può essere moltoefficace in SC.

Page 48: SuperCollider  Tutorial

I Buss

47

Ora vediamo un esempio con un bus audio. Questo è l’esempio più complicato diquelli visti fin’ora, ma potrebbe dar qualche idea su come mettere insieme tuttele nozioni fin qui illustrate. Il codice presentato usa 2 Synths come sorgente,uno crea pulsazioni di PinkNoise (un tipo di rumore che ha maggior energia allebasse frequenze rispetto alle alte frequenze), e un altro crea impulsi di un’ondasinusoidale. Gli impulsi sono creati usando la Ugens [Impulse] e [Decay2]. Questisono quindi riviberati usando una catena di [AllpassC], che è un tipo di delay.Da notare inoltre il costrutto16.do( ... ) che crea la catena valutando la funzione16 volte. Si tratta di una tecnica molto potente e flessibile, caratterizzata dalfatto che semplicemente cambiando il numero è possibile modificare il numero divalutazioni. (Si veda [Integer] per ulteriori informazioni su Integer-do.)

Page 49: SuperCollider  Tutorial

I Buss

48

1 (2 // l’arg permette il controllo diretto3 SynthDef("tutorial-DecayPink", { arg outBus = 0, effectBus, direct = 0.5;4 var source;5 // Decayi sul PinkNoise.6 source = Decay2.ar(Impulse.ar(1, 0.25), 0.01, 0.2, PinkNoise.ar);7 // il nostro main output8 Out.ar(outBus, source * direct);9 // il nostro effects output

10 Out.ar(effectBus, source * (1 - direct));11 }).send(s);

13 SynthDef("tutorial-DecaySin", { arg outBus = 0, effectBus, direct = 0.5;14 var source;15 // Decay sull’onda Sinusoidale.16 source = Decay2.ar(Impulse.ar(0.3, 0.25), 0.3, 1, SinOsc.ar(SinOsc.kr(0.2,

0, 110, 440)));17 // il nostro main output18 Out.ar(outBus, source * direct);19 // il nostro effects output20 Out.ar(effectBus, source * (1 - direct));21 }).send(s);

23 SynthDef("tutorial-Reverb", { arg outBus = 0, inBus;24 var input;25 input = In.ar(inBus, 1);26 // Viene valutata aNumber.do in corrispondenza del numero di volte27 // {}.dup(n) valuta la funzione n volte, e restituisce un Array dei

risultati28 // n=2 di default per un riverbero stereo29 16.do({ input = AllpassC.ar(input, 0.04,30 { Rand(0.001,0.04) }.dup, 3)});Out.ar(outBus, input);}).send(s);31 b = Bus.audio(s,1); // this will be our effects bus32 )

34 (35 x = Synth.new("tutorial-Reverb", [\inBus, b.index]);36 y = Synth.before(x, "tutorial-DecayPink", [\effectBus, b.index]);37 z = Synth.before(x, "tutorial-DecaySin", [\effectBus, b.index,

\outBus, 1]);38 )

40 // Istruzioni per variare il balance wet/dry del segnale41 y.set(\direct, 1); // only direct PinkNoise42 z.set(\direct, 1); // only direct Sine wave43 y.set(\direct, 0); // only reverberated PinkNoise44 z.set(\direct, 0); // only reverberated Sine wave45 x.free; y.free; z.free; b.free;

Page 50: SuperCollider  Tutorial

I Buss

49

Da notare infine che potremmo avere molti più synth sorgenti che devono essereprocessati da un singolo synth di riverbero. Se inseriamo il riverbero nella sor-gente ovviamente duplichiamo lo sforzo. Usando un bus privato, siamo in gradodi essere più efficienti.

11.4 Divertiamoci con i Control Buss

Con i bus control rate è possibile creare script molto potenti. Per esempio, èpossibile mappare qualunque argomento di un synth che è in stato running eleggerlo da un control bus. Questo significa che non si rende necessario l’utilizzodi una UGen In. Inoltre è possibile scrivere valori costanti su un bus di controllousando il metodo set, e recuperare i valori usando il metodo get

1 (2 // crea 2 bus control rate e setta il loro valori a 8803 //e 884 rispettivamente4 b = Bus.control(s, 1); b.set(880);5 c = Bus.control(s, 1); c.set(884);6 // crea un synth con 2 frequenze come argomenti7 x = SynthDef("tutorial-map", { arg freq1 = 440, freq2 = 440;8 Out.ar(0, SinOsc.ar([freq1, freq2], 0, 0.1));9 }).play(s);

10 )11 // vengono mappate freq1 e freq2 per leggere dai due bus12 x.map(\freq1, b.index, \freq2, c.index);

14 // viene creato un Synth per scrivere su uno dei bus15 y = {Out.kr(b.index, SinOsc.kr(1, 0, 50, 880))}.play(addAction:

\addToHead);

17 // libera y, e b mantiene il suo ultimo valore18 y.free;

20 // con Bus-get si ottine questo valore e stamparlo nella post window21 b.get({ arg val; val.postln; f = val; });

23 // viene settata la freq2, annullando la mappatura da c24 x.set(\freq2, f / 2);

26 // freq2, non essndo mappata, la modifica su c non ha effetti27 c.set(200);

29 x.free; b.free; c.free;

Page 51: SuperCollider  Tutorial

I Buss

50

Da notare che, diversamente dai bus audio rate, i bus control rate mantengonoi valori in essi contenuti finchè qualcosa di nuovo non viene sovrascritto. Danotare inoltre in questo script che il metodo Bus-get prende una Function (dettafunzione-azione) come argomento, il che comporta una piccola quantità di tempoper il server valutare la Function e restituire il risultato indietro. La funzione,che è passata come argomento può restituire un valore o un Array di valori nelcaso di un bus multicanale. Questo concetto, cioè occupare una piccola quantitàdi tempo per la risposta (usualmente detto latenza) è abbastanza importante dacapire. Ci sono un certo numero di altri metodi in SC che funzionano in questomodo, e possono causare dei problemi se non sono trattati con attenzione. Perillustrare questa cosa eseguiamo l’esempio seguente:

1 // crea un Bus object e setta i suoi valori2 b = Bus.control(s, 1); b.set(880);

4 // eseguire tutto insieme5 (6 f = nil; // just to be sure7 b.get({ arg val; f = val; });8 f.postln;9 )

11 f.postln;

Osservando la post window, perchè f era nil la prima volta e non la seconda?La parte del linguaggio che esegue il codice (detto interprete) lavora in manieraottimale, cioè si può dire fa cosa gli si dice di fare, più veloce che può e quandoglielo si dice. Così nel blocco di codice fra le parentesi, invia il messaggio ’get’alserver, fa lo scheduling della Function per eseguirla quando riceve una risposta,e quindi si muove sull’istruzione postln di f. La prima volta f sarà nil perchè nonsi sarà ancora ricevuta una risposta dal server.

Il server impiega solo una piccolissima quantità di tempo per inviare una risposta,così nel momento in cui stiamo eseguendo l’ultima linea di codice f è stato settatoa 880, come ci aspettavamo. Nell’esempio precedente questa latenza non era unproblema, dal fatto che si eseguiva una sola linea per volta. Ma ci potranno esseresicuramente casi in cui sarà necessario eseguire blocchi interi di istruzioni e latecnica della funzione azione sarà la soluzione.

11.5 L’ordine è una cosa importante!Negli esempi precedenti, si è fatto uso di istruzioni come Synth.after, e addAc-tion: addToHead. Durante ogni ciclo (il periodo in cui viene calcolato un blocco

Page 52: SuperCollider  Tutorial

I Buss

51

di campioni), il server esegue istruzioni in un ordine particolare, seguendo la sualista di synth che sono in stato running. Il server inizia con il primo synth, ecalcola un blocco di campioni per la sua prima UGen. In seguito, a turno, cal-cola un blocco di campioni per ognuno delle UGen rimanenti in base all’ordinein cui sono configurate (ognuna delle quali potrebbe prendere l’output di UGenprecedenti come input). Questo output viene scritto su uno o più bus, Il serverallora si occupa del prossimo synth della lista e il processo prosegue fino a chetutti i synth in stato running hanno calcolato un blocco di campioni. A questopunto il Server ricomincia il ciclo.

La cosa importante da capire è che come regola generale, quando si connettonosynths usando dei bus, è importante che i synth che scrivono segnali sui bus sianoi primi nell’ordinamento seguito dal server rispetto a quelli che leggono segnalida questi bus. Per esempio nel bus audio dell’esempio precedente, era molto im-portante che il synth di riverbero fosse calcolato dopo che il synth di rumore e ilsynth sinusoidale fossero processati dal server.

L’ordinamento è un fatto complicato, e ci sono alcune eccezioni, ma dovrebbe es-sere chiaro che l’ordinamento dei synth diventa cruciale quando i synth vengonoconnessi fra di loro. Il file [Order-of-execution] copre questo argomento in detta-glio. Synth-new presenta fra gli altri, 2 argomenti che permettono di specificaredove aggiungere un synth nell’ordinamento. Il primo è un target, e il secondo èun addAction. che specifica posizione del synth in relazione al target.

1 x = Synth("default", [\freq, 300]);2 // aggiunge un secondo synth immediatamente dopo x3 y = Synth("default", [\freq, 450], x, \addAfter);4 x.free; y.free;

Un target potrebbe essere un altro Synth (o qualche altra cosa ...), e un ad-dAction è un simbolo. Si Veda il file [Synth] per una lista completa dei possibiliaddActions. Metodi come Synth-after sono il modo più semplice e convenientedi fare la stessa cosa di utilizzare un metodo Synth-new con un addAction spe-cificato; la differenza sta nel fatto che prendono un target come il loro primoargomento.

1 // queste due linee di codice sono EQUIVALENTI2 y = Synth.new("default", [\freq, 450], x, \addAfter);3 y = Synth.after(x, "default", [\freq, 450]);

Page 53: SuperCollider  Tutorial

I Buss

52

Per ulteriori informazioni:

[Bus] [In] [OutputProxy] [Order-of-execution] [Synth]

Page 54: SuperCollider  Tutorial

I Gruppi

53

Capitolo 12 I Gruppi

La nostra discussione sui synths sul server ci porta a parlare di gruppi. L’architetturadel server (capitolo 9) è costituita da nodi che corrispondono a synth o a gruppi.I gruppi sono semplicemente collezioni di nodi, e possono contenere sia synths oaltri gruppi. Sono molto utili in 2 contesti:

1. nel processo di controllo dell’ordinamento di esecuzione

2. permettono di raggruppare insieme nodi e di inviare a tutti lo stesso messaggionello stesso istante.

Esiste ovviamente una comodo oggetto che permette l’astrazione Server per rap-presentare gruppi di nodi nell app client: Group.

12.1 Gruppi come tool per l’ordinamento

I Gruppi possono essere abbastanza utili in termini di controllo dell’ordinamentosul server. Come i synths, i gruppi prevedono un targets e un addAction comeargomenti, che rendono facile il loro posizionamento. Vediamo un esempio:

1 g = Group.new;2 h = Group.before(g);3 g.free; h.free;

Il loro utilizzo diventa fondamentale per aggiungere effettistica o processare se-gnali audio in modo separato dalle sorgenti sonore e nel giusto ordine. Si ricon-sideri il riverbero dell’esempio del capitolo precedente.

Page 55: SuperCollider  Tutorial

I Gruppi

54

1 (2 // una versione stereo3 SynthDef("tutorial-DecaySin2", {4 arg outBus = 0, effectBus, direct = 0.5, freq = 440;5 var source;

7 // 1.0.rand2 restituisce un numero random fra -1 e 1, usato qui8 // per avere un pan random9 source = Pan2.ar(Decay2.ar(Impulse.ar(Rand(0.3, 1), 0, 0.125),

0.3, 1,10 SinOsc.ar(SinOsc.kr(0.2, 0, 110, freq))), Rand(-1.0,

1.0));11 Out.ar(outBus, source * direct);12 Out.ar(effectBus, source * (1 - direct));13 }).send(s);

15 SynthDef("tutorial-Reverb2", {16 arg outBus = 0, inBus;17 var input;

19 input = In.ar(inBus, 2);20 16.do({ input = AllpassC.ar(input, 0.04, Rand(0.001,0.04),

3)});21 Out.ar(outBus, input);22 }).send(s);23 )

25 // Si crea un gruppo per i synth e uno per gli effetti26 (27 ~sources = Group.new;28 ~effects = Group.after(~sources); // assicurarsi che sia AFTER29 b = Bus.audio(s, 2); // ecco il nostro bus stereo effettato30 )

32 // I synths nel gruppo. L’addAction di default \‘e \\addToHead33 (34 x = Synth("tutorial-Reverb2", [\inBus, b.index], ~effects);35 y = Synth("tutorial-DecaySin2", [\effectBus, b.index, \outBus, 0],

~sources);36 z = Synth("tutorial-DecaySin2", [\effectBus, b.index, \outBus, 0,

\freq, 660], ~sources);37 )38 ~sources.free; ~effects.free; // queste istruzioni liberano x,y e z39 b.free;

Da notare che non ci si preoccupa con quale ordine le sorgenti e gli effetti sonoraggruppati dentro i gruppi; ciò che importa è che tutti i synth che si occupano dieffetti vengano posposti, nell’ordinamento, alle sorgenti synth che essi processano.

Page 56: SuperCollider  Tutorial

I Gruppi

55

I nomi ~sources e ~effects, che presentano una tilde (~) davanti a una parola,sono variabili d’ambiente. Per il momento, tutto ciò che serve conoscere su questecose è che possono essere usate nello stesso modo delle variabili dell’interprete(non è necessario dichiararle, sono persistenti), e permetto nomi più descrittivi.

12.2 Tutte le addActionA questo punto può tornare utile scoprire i possibili add actions. Oltre a add-Before e addAfter, esite anche addReplace usato raramente, e due addactions applicabili ai gruppi:

− addToHead : aggiunge il ricevente all’inizio del gruppo, così che sia eseguitoper primo

− addToTail: aggiunge alla fine del gruppo, così che sia eseguito per ultimoCome per le altre addAction, anche addToHead e addToTail hanno metodiusati nell’app del client chiamati head e tail.

1 g = Group.new;2 h = Group.head(g); // aggiunge h davanti a g3 x = Synth.tail(h, "default"); // aggiunge x in coda ad h4 s.queryAllNodes; // vediamo la gerarchia dei

nodi nella post window5 x.free; h.free; g.free;

12.3 ’queryAllNodes’e node IDsL’oggetto Server ha un metodo chiamato queryAllNodes che stamperà unarappresentazione dell’albero dei nodi del server nella post window. Eseguendol’esempio precedente si potrebbe ottenere qualcosa come:

1 nodes on localhost:2 a Server3 Group(0) : Root Node4 Group(1) : default group5 Group(1000)6 Group(1001)7 Synth 1002

Da questa rappresentazione, ciò che compare sotto un gruppo e indentato sulladestra è contenuto dentro qualcosa che è indentato in alto sulla sinistra in un

Page 57: SuperCollider  Tutorial

I Gruppi

56

sistema di scatole cinesi. l’ordine dei nodi è dalla cima alla base. I numeri accatoa i nodi sono gli ID, utilizzati dal server per tener traccia dei nodi stessi. Normal-mente quando si lavora con oggetti astrazioni Server non è necessario occuparsidegli ID e come gli oggetti ne tengono traccia, come vengono assegnati e liberati.

l’esempio precedente presentava 4 gruppi anche se ne venivano creati solo 2. Iprimi due, con gli ID 0 e 1, sono gruppi speciali, detti RootNode e il defaultgroup.

12.4 Il Root Node

Al momento del boot del server , viene creato un gruppo speciale con un ID dinodo uguale a 0 e che rappresenta la radice dell’albero dei nodi del server. Esiteun’astrazione server che rappresenta questo oggetto che èRootNode.

Questo Root Node ha alcune caratteristiche:

− viene sempre utilizzato.

− è sempre in stato running.

− non può essere rimosso o spostato all’interno dell’albero.

− viene usato il meccanismo di Cacheing per assicurare che ci sia sempre unnodo radice per l’albero dei nodi.

Per dimostrare che questo esista e sia univoco si provi ad eseguire il seguentecodice:

1 s = Server.local;2 a = RootNode(s);3 b = RootNode(s);

5 a === b; // identical object

Inviando messaggi /s_new al server, il target 0 rappresenta proprio questooggetto.

1 s.sendMsg("/s_new","default", -1, 0, 0);//the last argument is the target id

Importante: In generale non si dovrebbero aggiungere nodi al RootNode senzauna specifica ragione.

Page 58: SuperCollider  Tutorial

I Gruppi

57

12.5 Il default Group

Al boot del Server c’è un gruppo a livello top con un ID di 0 che definisce la rootdell’albero; come detto precedentemente esso è rappresentato da una sottoclassedi Group: RootNode. Se il Server viene avviato da dentro SCLang (da riga dicomando) sarà creato automaticamente un default group con un ID=1. Questoè il target di default per tutti i Nodi. Se non si specifica un target o si passa nil,il target sarà il default group del Server di default.La gerarchia dei gruppi alla base del Server è quindi:

root node (id:0) [default group (id:1)

]

Eseguendo il codice seguente si può vedere che il synth creato fa parte del gruppodi default con id=1.

1 Server.default.boot;2 a = Synth.new(\default);3 // viene creato un synth nel default group del Server di default4 a.group;5 // restituisce un oggetto Group. Da notare l’ID = 1 (il default group)6 //nella post window

Il default group ha uno scopo importante: offrire un albero di nodi così che me-todi come Server-scope, Server-record, etc. possono funzionare senza esserevincolati dall’ordine di esecuzione. Nel seguente esempio, il node scoping è dopoil default group.

1 Server.internal.boot;

3 { SinOsc.ar(mul: 0.2) }.scope(1);

5 // osserva la post window6 Server.internal.queryAllNodes;

8 // Il synth (SinOsc) dentro il default group (ID 1)9 // Il nodo dello scope viene dopo il default group

11 Server.internal.quit;

Da notare che il default group è persistente; viene creato nel metodo initTree delServer (eseguito con ogni codice salvato nelle sue variabili di istanza dell’albero: si

Page 59: SuperCollider  Tutorial

I Gruppi

58

veda [Server] per maggiori dettagli) ed è ricreato nel reboot, dopo la pressione diCmd-. e dopo che tutti i nodi sono stati liberati. Con i messaggi OSC è possibileusare sempre un nodo target di ID 1:

1 s.sendMsg("//s_new", "snd", 1832,0,1); // aggiungi in testa al group 1

In generale è possibile aggiungere nodi al gruppo di default, o gruppi dentro di es-so e non prima o dopo. Quando si aggiunge un synth di effettistica, per esempio,in generale si dovrebbe resistere alla tentazione di aggiungerlo dopo il gruppo didefault, e creare un gruppo sorgente separato dentro il gruppo di default. Questopreverrà problemi con lo scoping e la registrazione. Quanto spiegato è raffiguratodi seguito. Se si procedesse ad un’allocazione del tipo:

1 default group [2 source synth13 source synth24 ]5 recording synth6 effects synth

il syhnth di recording potrebbe non catturare l’output del synth di effetto datoche è prima di quest’ultimo nell’ordinamento. In casi come questo, il metodomigliore è creare un gruppo dentro il default group e mettere un synth di effettidopo tutti i synth sorgenti.

1 default group [2 source group [3 source synth14 source synth25 ]6 effects synth DENTRO IL DEFAULT GROUP7 ]8 recording synth

Infine, è possibile "liberare" il default group, ma in generale non c’è ragione difarlo. Inoltre in generale si potrebbero aggiungere nodi al default group come alRootNode senza averne, anche qui, uno specifico motivo per farlo (per esempioaggiungere alcune nuove funzionalità come recording e scoping che dipendonodall’ordine dei nodi). Rimane comunque possibile farlo, ma solo per un motivodavvero valido.

Page 60: SuperCollider  Tutorial

I Gruppi

59

12.6 Gruppi come tool per l’invio di insiemi di messaggi

Come accennato nelle prime righe di questo capitolo, l’altro maggior utilizzo deigruppi è legato al fatto che permettono di trattare facilmente un certo numero(...gruppo..) di synths come un tutt’uno. Se si invia un messaggioset a un gruppo,questo verrà inviato a tutti i nodi contenuti nel gruppo stesso.

1 g = Group.new;

3 // creiamo 4 synth dentro g4 // 1.0.rand2 restituisce un numero random nel range -1;15 4.do({6 { arg amp = 0.1;7 Pan2.ar(SinOsc.ar(440 + 110.rand, 0, amp), 1.0.rand2) }.play(g);8 });

10 g.set("amp", 0.005); // abbassa il volume di tutti

12 g.free;

12.7 Gruppi: ereditarietà e non solo

Vediamo ancora un po’di teoria Object Oriented. Sia i Gruppi che i Synth son esem-pi di cosa possiamo definire sottoclassi. Possiamo pensare alle sottoclassi come figlidi classi genitori, dette superclassi. Tutte le sottoclassi ereditano i metodi delle lorosuperclassi. Essi potrebbero ridefinire alcuni metodi con la loro implementazione(traendo vantaggio dal polimorfismo), ma in generale le sottoclassi rispondono atutti i metodi della sopraclasse, e ai propri. Alcune classi sono definite astratte, chesignifica che non si possono creare istanze di esse, infatti esse offrono solo un insiemecomune di metodi e variabili intese come interfacce alle loro sottoclassi.

Questo modo di lavorare porta alcuni vantaggi: se si rende necessario cambiare unmetodo ereditato, si può fare in un posto solo, nella superclasse, e tutte le sottoclassiche ereditano questo metodo risentiranno del cambiamento. È possibile anche esten-dere una classe per creare una variante personale o migliorata e/o automaticamenteprendere tutte le funzionalità della superclasse.

l’ereditarietà è un meccanismo che può anche propagarsi all’indietro attraverso mol-ti livelli, che è come dire che una superclasse di una classe potrebbe avere unasuperclasse. (una classe non può, comunque avere più di una sovraclasse al livelloprecendente della gerarchia dell’ereditarietà). Tutti gli oggetti in SC ereditano difatto da una classe detta Object, che definisce un certo insieme di metodi, e che

Page 61: SuperCollider  Tutorial

I Gruppi

60

tutte le sue sottoclassi ereditano o sovrascrivono. Gruppi e Synth sono sottoclassidi classi astratte della classe astratta [Node]. Alcuni dei loro metodi sono definiti inNode, e sono documentati nell’helpfile di Node.

Così, se guardando l’helpfile se non si trova un metodo particolare della classe, po-trebbe essere necessario andare nell’helpfile della superclasse della classe e così viaseguendo la catena. Molte classi hanno elencate all’inizio dell’helpfile le proprie so-vraclassi. Possono anche essere usati i seguenti metodi per ottenere questo tipo diinfomazioni tracciate nella documentazione nella post window:

1 Group.superclass; // restituisce ’Node’2 Group.superclass.openHelpFile;3 Group.findRespondingMethodFor(’set’); // Node-set4 Group.findRespondingMethodFor(’postln’); // Object-postln;5 Group.helpFileForMethod(’postln’); // apre l’help file di Object

Per maggiori informazioni:

[Group] [Node] [default_group] [RootNode] [Intro-to-Objects] [Order-of-execution][Synth] [More-On-Getting-Help] [Internal-Snooping]

Page 62: SuperCollider  Tutorial

Buffer

61

Capitolo 13 Buffer

Gli oggetti Buffers sono astrazioni: rappresentano i buffer sul server, che non sonoaltro che array ordinati di valori float. Float è l’abbrevazione di numero in floatingpoint, ossia un numero decimale; essi differiscono dagli integer, che possono esserepositivi o negativi (o zero) e sono scritti senza punto decimale.Per chiarezza: 1 è uninteger, 1.0 è un float.

I buffers Server possono essere singoli o multicanale, e sono tipicamente il mezzoper salvare dati lato-server. Il loro uso più comune è quello di mantenere file audioin memoria, ma in realtà qualunque tipo di dato che può essere rappresentato davalori float può essere salvato in un buffer.

Come per i Bus, il numero di buffer disponibili è settato prima della fase di bootdel server (si veda [ServerOptions]), ma prima che possano essere usati, è necessarioallocargli memoria, effettuando così un passo asincrono. Inoltre, come per i bus, ibuffer sono numerati partendo da 0. Occorre fare attenzione, usando i buffer, deinumeri allocati e eliminare il rischio di conflitti (non esiste evidentemente un mec-canismo intrinseco per l’allocazione degli indici come per di bus).

Si può pensare ai buffer sul server all’equivalente di un array, ma senza tutte lefunzionalità utili ed eleganti dell’OOP. Per modificare i dati nei buffer, si può ri-correre a (qualunque) operazione lato-client. I buffer lato-server sono globali, chesignifica semplicemente che ogni synth può accedervi anche più di una volta. Infinericordiamo che in generale sui buffer:

− si può scrivere

− si possono leggere e contemporaneamente modificarli in dimensione.

Molti dei metodi dei buffer presentano numerosi argomenti, per informazioni com-plete si faccia riferimento all’help file [Buffer].

13.1 Creazione di un Buffer Object e allocazione di memoria

Costruire un oggetto Buffer e allocargli la memoria necessaria nell’applicazione ser-ver è abbastanza semplice. È possibile farlo in un passaggio solo con un metodo diallocazione del tipo:

Page 63: SuperCollider  Tutorial

Buffer

62

1 s.boot;2 b = Buffer.alloc(s, 100, 2); // alloca 2 canali e 100 frames3 b.free; // libera la memoria

L’esempio mostrato alloca 2 canali buffer da 100 frames ognuno. Il numero attualedei valori salvati è numChannels * numFrames, così in questo caso ci saranno 200valori float. Ogni frame quindi, in questo caso, è una coppia di valori, uno per ognicanale. Se si vuole allocare in termini di secondi e non in numero di frame, si puòscrivere:

1 // un buffer stereo di 8 secondi2 b = Buffer.alloc(s, s.sampleRate * 8.0, 2);3 b.free;

Il metodofree dell’oggetto Buffer libera la memoria sul server, e restituisce il numerodel buffer per una futura riallocazione.

13.2 Uso dei Buffers con Sound Files

I Buffer hanno un metodo read, che permette di leggere un file sonoro residente inmemoria e restituire un oggetto Buffer che lo contiene. Usando la UGen PlayBuf,è possibile fare il play del file.

1 // legge un file audio2 b = Buffer.read(s, "sounds/a11wlk01.wav");

4 // lo suona5 (6 x = SynthDef("tutorial-PlayBuf",{ arg out = 0, bufnum;7 Out.ar( out,8 PlayBuf.ar(1, bufnum, BufRateScale.kr(bufnum))9 )

10 }).play(s,[\bufnum, b.bufnum ]);11 )12 x.free; b.free;

Il metodo PlayBuf-ar ha un certo numero di argomenti che permettono di controllarevari aspetti. Il rifermimento per dettagli su questa UGen è l’help file [PlayBuf].Vediamo un semplice esempio:

Page 64: SuperCollider  Tutorial

Buffer

63

1 PlayBuf.ar(2 1, // numero di canali3 bufnum, // numero di buffer da suonare4 BufRateScale.kr(bufnum) // tasso di playback5 )

• Numero dei canali: quando si lavora con PlayBuf occore specificare quanticanali ogni buffer leggerà. Non può essere un parametro in una Synthdef e veniremodificato in seguito. Perchè? SynthDefs deve avere un numero fisso di canali dioutput. Così un canale PlayBuf è sempre un unico canale PlayBuf. Se si rendononecessarie versioni che possano suonare un numero maggiore di canali, occorronopiù SynthDefs oppure si può ricorrere all’uso di una Function-play.

• Numero del Buffer: Come si è osservato prima, i Buffer sono numerati partendoda zero. È possibile ottenere il numero del Buffer usando il suo metodobufnum.Nell’esempio sopra presentato viene considerato alla fine della SynthDef, a cuiviene passato come argomento dal Synth risultante. (Da notare SynthDef-playpermette di includere un array di argomenti, proprio come Synth-new.)

• Tasso di riproduzione: Un tasso di 1 potrebbe essere la velocità normale, 2il doppio più veloce, ecc. Ma qui viene presentata una particolare UGen dettaBufRateScale che verifica il samplerate del buffer ( che è settato nel corrispon-dente sound file che è caricato nel buffer) e restituisce il tasso che corrispondealla veloctià normale. Questo risulta molto utile, perchè il file sonoro che cari-chiamo (a11wlk01.wav) ha un samplerate di 11025 Hz. Con un rate di 1, PlayBufdovrebbe fare il play al samplerate del server che è tipicamente settato a 44100Hz, oppure quattro volte più veloce! Quindi BufRateScale impone questo tassoa quello normale.

13.3 Streaming di File da e su Disco

In alcuni casi, per esempio quando si lavora con file di dimensioni considerevoli, sipotrebbe non voler caricare un suono completamente in memoria. Si può quindi fareuno stream del suono dal disco ad un certo bit/tempo usando la UGen DiskIn e ilmetodo cueSoundFile dei buffer:

Page 65: SuperCollider  Tutorial

Buffer

64

1 (2 SynthDef("tutorial-Buffer-cue",{ arg out=0,bufnum;3 Out.ar(out,4 DiskIn.ar( 1, bufnum )5 )6 }).send(s);7 )

9 b = Buffer.cueSoundFile(s,"sounds/a11wlk01-44_1.aiff", 0, 1);10 y = Synth.new("tutorial-Buffer-cue", [\bufnum,b.bufnum], s);

12 b.free; y.free;

Note: non è flessibile come PlayBuf (non si ha un controllo sul tasso di playback),mapermette di risparmiare memoria.

13.4 OOP: Variabili di istanza e Funzioni azione

Vediamo a questo punto ancora un po’di teoria sull’OOP. Si ricorda che gli oggettisalvano dati nelle variabili di istanza. Alcune variabili di istanza hanno metodi di gete set che permettono di recuperare o settare il valore relativo. Si è già visto questomeccanismo, con il metodo bufnum dei Buffer, che restituisce il numero del Buffercontenuto nella variabile di istanza.

Il Buffer ha un certo numero di variabili di istanza i cui metodi di get possono re-stituire i valori relativi. Quelli a cui siamo interessati per ora sono numChannels,numFrames, e sampleRate. Questi possono essere particolarmente utili quando lavo-riamo con file sonori di cui potremmo non avere queste informazioni a priori, primacioè del momento in cui il file venga caricato nel buffer.

1 b = Buffer.read(s, "sounds/a11wlk01.wav");2 b.bufnum;3 b.numFrames;4 b.numChannels;5 b.sampleRate;6 b.free;

Da tener presente che a causa della bassa latenza dei messaggi fra client e server,le variabili di istanza potrebbero non essere aggiornate immediatamente quandosi fa un’operazione dispendiosa come la lettura di un file in un buffer. Per questaragione molti metodi dell’oggetto Buffer prevedono come argomenti funzioni-azioni.Si ricorda che una funzione-azione è una funzione che verrà valutata solo dopo che

Page 66: SuperCollider  Tutorial

Buffer

65

il client ha ricevuto una risposta e ha aggiornato le variabili del Buffer. Vediamo unesempio:

1 // con una funzione azione2 // da notare che le variabili non vengono3 //aggiornate immediatamente4 (5 b = Buffer.read(s, "sounds/a11wlk01.wav", action: { arg buffer;6 ("numFrames after update:" + buffer.numFrames).postln;7 x = { PlayBuf.ar(1,8 buffer.bufnum,9 BufRateScale.kr(buffer.bufnum))

10 }.play;11 });

13 // da notare che la linea seguente viene eseguita14 //PRIMA della funziona azione15 ("numFrames before update:" + b.numFrames).postln;16 )17 x.free; b.free;

Nell’esempio, il client invia il comando leggi all’applicazione server con una richiestaper le informazioni necessarie al fine di aggiornare le variabili di istanza del Buffer. Aquesto punto la funzione-azione sarà eseguita quando verrà ricevuta una risposta econtinuerà e verrà eseguito il blocco di codice relativo. Questo perchè la linea Beforeupdate ... viene eseguita prima.

13.5 Registrare nel Buffer

Oltre a PlayBuf, c’è un’altra UGen dettaRecordBuf, che permette di registrare nelBuffer.

Page 67: SuperCollider  Tutorial

Buffer

66

1 // un Buffer da 5 second e 1 channel2 b = Buffer.alloc(s, s.sampleRate * 5, 1);

4 // registra per 4 secondi5 (6 x = SynthDef("tutorial-RecordBuf",{ arg out=0,bufnum=0;7 var noise;8 noise = PinkNoise.ar(0.3); // registra PinkNoise9 RecordBuf.ar(noise, bufnum); // di default questo cicla

10 }).play(s,[\out, 0, \bufnum, b.bufnum]);11 )

13 // libera il synth dopo alcuni secondi14 x.free;

16 // riproduci il buffer17 (18 SynthDef("tutorial-playback",{ arg out=0,bufnum=0;19 var playbuf;20 playbuf = PlayBuf.ar(1,bufnum);21 FreeSelfWhenDone.kr(playbuf);22 // libera il synth quando PlayBuf ha riprodotto una volta23 Out.ar(out, playbuf);24 }).play(s,[\out, 0, \bufnum, b.bufnum]);25 )26 b.free;

Si veda l’help di [RecordBuf] per dettagli e opzioni.

13.6 Accesso ai dati

l’oggetto Buffer ha una serie di metodi che permettono di recuperare e/o settarevalori nel buffer. I metodi Buffer-get and Buffer-set prevedono come argomento unindice, il riferimento al buffer su cui andranno ad operare.I buffer multicanali presentano un interleave fra i loro dati, così per esempio, per unbuffer a due canali si avrà:

index 0 = frame1-chan1,index 1 = frame1-chan2,index 2 = frame2-chan1, e così via..

Page 68: SuperCollider  Tutorial

Buffer

67

1 b = Buffer.alloc(s, 8, 1);2 b.set(7, 0.5); // set the value at 7 to 0.53 b.get(7, {|msg| msg.postln}); // get the value at 7 and post it when

the reply is4

received5 b.free;

Oltre ai semplici metodiget eset, sono implementati anche metodi qualigetn esetnpemettono di recuperare e/o settare un range di valori adiacenti.

− setn prende un indice di partenza e un vettore di valori da impostare

− getn prende un indice, il numero di valori da recuperare e una funzione-azione.

Per esempio:

1 b = Buffer.alloc(s,16);

3 // setta i primi 3 valori4 b.setn(0, [1, 2, 3]);

6 // recupera i 3 valori7 b.getn(0, 3, {|msg| msg.postln});

9 // riempe il buffer con valori random10 b.setn(0, Array.fill(b.numFrames, {1.0.rand}));

12 // li recupera13 b.getn(0, b.numFrames, {|msg| msg.postln});14 b.free;

C’è un limite superiore al numero di valori che si possono recuperare o settare allavolta (tipicamente 1633 usando UDP, protocollo di default). Questo perchè dipendeproprio dal limite della grandezza del pacchetto di rete del protocollo UDP stesso.Per superare questo limite, si sono implementati nell’oggetto Buffer altri 2 meto-di, loadCollection e loadToFloatArray che permettono di settare o recuperaregrandi quantità di dati scrivendoli su disco e quindi caricandoli sul client o sul servera seconda delle necessità.

Page 69: SuperCollider  Tutorial

Buffer

68

1 (2 // Crea un rumore bianco3 v = FloatArray.fill(44100, {1.0.rand2});4 b = Buffer.alloc(s, 44100);5 )

7 (8 // carica il FloatArray dento b, quindi lo riproduce9 b.loadCollection(v, action: {|buf|

10 x = {11 PlayBuf.ar(buf.numChannels,12 buf.bufnum,13 BufRateScale.kr(buf.bufnum),14 loop: 1) * 0.2 }.play;15 });16 )17 x.free;

19 // recupera FloatArray, e lo compara con v;20 // Gli args 0, -1 sono start dall’inizio e carica l’intero buffer21 b.loadToFloatArray(0, -1, {|floatArray| (floatArray == v).postln });22 b.free;

( Un FloatArray è una sottoclasse di Array che può contenere solo valori Float).

13.7 Plotting e Playing

Altri 2 metodi utili dell’oggetto Buffer sonoplot eplay.

1 // si vede la forma d’onda2 b = Buffer.read(s,"sounds/a11wlk01.wav");3 b.plot;

5 // si fa il play del contenuto del file6 // solo un’argomento: loop.7 //Se false (default) il synth viene liberato automaticamente8 b.play; // libera se stesso9 x = b.play(true); // oppure, se cicla, non si libera da solo

10 x.free; b.free;

13.8 Altri usi dei Buffers

Oltre ad essere usati per caricare file sonori, i buffer possono anche essere sfruttati perquelle situazioni in cui si rende necessario gestire insiemi di dati grandi dimensioni

Page 70: SuperCollider  Tutorial

Buffer

69

accessibili globalmente sul server. Un esempio di un altro uso potrebbe essere unalookup table per waveshaping.

1 b = Buffer.alloc(s, 512, 1);2 b.cheby([1,0,1,1,0,1]);3 (4 x = play({5 Shaper.ar(6 b.bufnum,7 SinOsc.ar(300, 0, Line.kr(0,1,6)),8 0.59 )

10 });11 )12 x.free; b.free;

La UGen Shaper effettua waveshaping su una sorgente di input. Il metodo chebyriempe il buffer con una serie di polinomi di chebyshev, necessari proprio per questotipo di sintesi. La classe Buffer ha molti altri metodi simili a questo per riempire ilbuffer con valori di una forma d’onda ricavandoli o meno da una tabella di lookup.Per maggior informazioni:

[Buffer] [PlayBuf] [RecordBuf] [SynthDef] [BufRateScale] [Shaker]

Page 71: SuperCollider  Tutorial

La comunicazione

70

Capitolo 14 La comunicazioneDopo aver visto i vari componenti dell’architettura del server, passiamo ad occuparciin maniera specifica della comunicazione fra i nodi e della comunicazione client-serverin supercollider.

Il modo più diretto e veloce per mandare comandi al server è inviare messaggiall’oggetto Server, se si è dentro sc-lang; se si è in una shell è possibile utilizzaresendOSC.Quando si creano dei nodi sul server, synths o gruppi, le sole cose necessarie per lacomunicazione sono il nodeID e il server (il suo indirizzo per essere precisi).Al fine di poter includere un synth in una comunicazione, s’inviano messaggi al servercon il proprio nodeID. Se invece non s’intende comunicare con un nodo dopo la suacreazione (e il nodo terminerà da solo senza messaggi esterni), il nodeID può esseresettato a -1, che è l’equivalente di nil per il server. Quando si passa il riferimentoa un certo nodo, assumendo che si potrebbe avere non solo un server, può essereutile creare un oggetto Synth o Group. Questi oggetti rispondono anche a messaggie, se necessario, possono essere utilizzati per ottenere lo stato interno del nodo latoserver. Vediamo un esempio per rendere tutto più chiaro.

1 n = s.nextNodeID;2 s.sendMsg("/s_new", "default", n);3 s.sendMsg("/n_free", n);

5 // equivalente6 n = Synth("default");7 n.free;

9 //------------------------------------------------10 // passando gli argomenti:11 n = s.nextNodeID;12 s.sendMsg("/s_new", "default", n, 0, 0, \freq, 850);13 s.sendMsg("/n_set", n, \freq, 500);14 s.sendMsg("/n_free", n);

16 // equivalente:17 n = Synth("default", [\freq, 850]);18 n.set(\freq, 500)19 n.free;

Viene spontaneo da chiedersi quando è meglio utilizzare oggetti node e quando uti-lizzare direttamente i messaggi per la comunicazion. La risposta potrebbe dipendereda qualche limite del contesto in cui si lavora, e il limite sul contesto dipende spesso

Page 72: SuperCollider  Tutorial

La comunicazione

71

dal gusto personale.

L’incapsulamento di oggetti nodo derivante da una certa generalizzazione teorica,implica che altri oggetti composti possano rispondere allo stesso messaggio e quindisfruttare il polimorfismo. Questo meccanismo inoltre offre un certo livello di conve-nienza, tenendo traccia di indici e ID.In certi casi, come per una sintesi granulare mostrata nell’esempio seguente, si rac-comanda di usare direttamente i messaggi, in quanto non si guadagnano beneficidagli oggetti nodo (per esempio non serve inviarli via messaggio) e non si aggiungecarico sulla cpu lato client inutilmente.

1 (2 SynthDef("grain", {3 Out.ar(0,4 Line.kr(0.1, 0, 0.01, doneAction:2) * FSinOsc.ar(12000))5 }).send(s);6 )

8 (9 Routine({

10 20.do({11 s.sendMsg("/s_new", "grain", -1);12 0.01.wait;13 })14 }).play;15 )

Nei casi in cui è necessario tenere traccia dello stato del synth, è buona cosa utilizzaregli oggetti nodo e registrarli con un NodeWatcher. Oltre ai casi particolari visti,è solo questione di gusto del programmatore scegliere se usare la combinazione dimessaggi e una rappresentazione numerica globale o una rappresentazione a oggetti.I due approcci possono essere miscelati, ottenendo certi vantaggi dello stile a oggettiusando lo stile a messaggi. Per esempio Server.nextNodeID permette di usare IDassegnati dinamicamente nello stile di messaggi. È una generalizzazione grossolana,che probabilmente è lontana da dire che lo stile a oggetti è più conveniente, ma lostile con i messaggi è più efficiente a causa della riduzione di carico sulla CPU latoclient.Importante: Se si vuole avere la funzionalità del default group (per esempio per l’usodelle funzionalità di recording e scoping del server senza problemi) occorre trattarel’ID 1 (il default group) come root al pari dell’ID 0 (il root node).Da notare che Function-play e SynthDef-play restituiscono un oggetto synth chepuò essere usato e a cui è possibile inviare messaggi, come mostrato nell’esempioseguente.

Page 73: SuperCollider  Tutorial

La comunicazione

72

1 x = { arg freq=1000; Ringz.ar(Crackle.ar(1.95, 0.1), freq, 0.05)}.play(s);

2 x.set(\freq, 1500);3 x.free;

14.1 Impacchettamento automatico dei messaggi

Quando vengono usati oggetti Synth/Node/Group in sclang, compare spesso la ne-cessità di costruire bundles (pacchetti) per poter inviare messaggi insieme; per esem-pio quando si vuole avviare un synth che dovrebbe essere mappato istantaneamentesu un certo bus, oppure se si ha la necessità di assicurare che due synths venganoavviati sincronizzati con precisione. Il modo più semplice per trattare queste cose èpassare attraverso il supporto di bundling automatico del Server. Questo permettedi aprire un pacchetto in cui saranno collezionati tutti i messaggi osc fino a che nonverrà inviato. Questa funzionalità è abbastanza conveniente nello “stile-oggetto” eassicura un’esecuzione sincrona. Si veda [Server] e [bundledCommands].Può essere interessante vedere la sintassi del metodo che ci permette di creare pac-chetti da inviare al server.

makeBundle( time, func, bundle) - La Function func viene valutata, e tuttii messaggi OSC generati sono differti e aggiunti a un pacchetto che potrà essereutilizzato successivamente se necessario.Se time:

• viene impostato a nil o a un valore numerico, il pacchetto verrà automaticamen-te inviato ed eseguito dopo il corrispondente ritardo in secondi impostato nelparametro.

• viene impostato a false, il pacchetto non sarà inviato.

L’argomento bundle permette di passare un pacchetto esistente e continuare adaggiungervi i messaggi OSC. Se viene riscontrato un errore durante la valutazionedella Function func, questo metodo genererà un Error e fermerà il differimento deimessaggi.

Page 74: SuperCollider  Tutorial

La comunicazione

73

1 s.boot;2 (3 // inviamo una SynthDef al server4 SynthDef("tpulse", { arg out=0,freq=700,sawFreq=440.0;5 Out.ar(out, SyncSaw.ar(freq, sawFreq,0.1) )6 }).send(s);7 )

9 // tutti i comandi OSC generati nella function contenuta sotto saranno10 // aggiunti al pacchetto ed inviati simultaneamente dopo 2 secondi.11 (12 s.makeBundle(2.0, {13 x = Synth.new("tpulse");14 a = Bus.control.set(440);15 x.busMap(\freq, a);16 });17 )18 x.free;

20 // il pacchetto non viene inviato21 (22 b = s.makeBundle(false, {23 x = { PinkNoise.ar(0.1) * In.kr(0, 1); }.play;24 });25 )26 // Si passa b come un pacchetto pre-esistente, e si avviano27 //entrambi i synth in maniera sincrona

29 (30 s.makeBundle(nil, { // nil esegue ASAP31 y = { SinOsc.kr(0.2).abs }.play(x, 0, 0, \addBefore);32 //inviluppo sinusoidale33 }, b);34 )35 x.free; y.free;

37 // Genera un errore38 (39 try {40 s.makeBundle(nil, {41 s.farkermartin;42 });43 } { | error |44 (error.errorString).postln;45 x = { FSinOsc.ar(440, 0, 0.2) }.play; // This works fine46 }47 )48 x.free;

Page 75: SuperCollider  Tutorial

Ordine di esecuzione

74

Capitolo 15 Ordine di esecuzione

lL ordine di esecuzione è uno degli aspetti più critici e apparentemente più difficilidell’uso di SuperCollider.

L’ordine di esecuzione in questo contesto non significa l’ordine in cui gli statementsono eseguiti nel linguaggio (il client). In realtà ci si riferisce all’ordine in cui sonoeseguiti i nodi synth sul server, che corrisponde in pratica all’ordine in cui i relativioutput vengono calcolati ad ogni ciclo di controllo (blockSize). A prescindere dalfatto che si specifichi o meno l’ordine di esecuzione, ogni synth e ogni group vengonoinseriti in una locazione specifica nella catena computazionale del server.

Se si ha sul server:

1 synth 1 ---> synth 2

tutte le Ugen associate con il synth 1 saranno eseguite prima di quelle nel synth 2,durante ogni ciclo di controllo.

Se non si hanno synth che usano In.ar, non occorre preoccuparsi dell’ordine di ese-cuzione. Il problema si verifica solo quando un synth legge l’output prodotto da unaltro synth. La regola è semplice: se si ha un synth sul server (un effetto) che dipendedall’output da un altro synth (la sorgente), allora l’effetto deve apparire dopo, nellacatena di nodi sul server, rispetto alla sorgente. In generale la sequenza è:

1 source ---> effect

Se si utilizzasse la configurazione opposta:

1 effect ---> source

il synth che si occupa dell’effetto non ”ascolterebbe”il synth sorgente, e quindi nonsi otterrebbe il risultato richiesto.

15.1 Richiami su Server e Target

È sempre presente un default Server, a cui si può accedere o a cui settare la variabiles attraverso il metodo Server.default. Allo startup il server di default viene di

Page 76: SuperCollider  Tutorial

Ordine di esecuzione

75

norma settato al Server locale (localhost) ed è anche assegnato automaticamentealla variabile di interprete s.

1 // si esegua il codice seguente osservando i risultati2 //nella post window3 s === Server.default;4 s === Server.local;5 Server.default = Server.internal;6 s === Server.default;7 Server.default = Server.local;

Si ricorda che quando un Server viene avviato, c’è un gruppo al top level con un ID =0 che definisce la radice dell’albero dei nodi. Questa gruppo è RootNode. C’è inoltreun default group con un ID = 1, che è il gruppo di default per tutti i nodi. Comeanticipato nei capitoli precendenti, tutto ciò si ottiene se si considera un Server comeun target. Se non si specifica un target o lo si imposta a nil, verrà considerato ildefault group sul server di default.

15.2 Controllo dell’ordine di esecuzioneCome considerazione generale, possiamo dire che utilizzare i gruppi è molto con-veniente nell’organizzazione dell’ordine di esecuzione perchè semplifica, in specialmodo su progetti grandi, sia in termini concettuali che operativi.

Esistono tre modi per controllare l’ordine di esecuzione:

1. usando addAction nel messaggio di creazione dei synth

2. muovendo nodi

3. inserendo i propri synth in un gruppo.

Vediamone ora uno per volta:

1. Utilizzo delle Add actionsSpecificando un argomento addAction per Synth.new (or SynthDef.play, Func-tion.play, etc.), si specifica il posizionamento del nodo relativamente a un target.Il target potrebbe essere un nodo gruppo, un altro nodo synth o un server.Come detto in precendenza, il target di default è il default group (il nodo connodeID = 1) del Server di default.

I Symbols seguenti sono addActions valide per Synth.new: addToHead, addTo-Tail, addBefore, addAfter, addReplace.

Page 77: SuperCollider  Tutorial

Ordine di esecuzione

76

Vediamo come vengono applicati:Synth.new(defName, args, target, addAction)se target è:

− un Synth i metodi addToHead e addToTail saranno applicati al quel gruppodi synth

− un Server, allora si riferirà default group del Server.

− nil, allora si riferirà al gruppo di defaulf del server di default.

Per ogni addAction c’è un corrispondente metodo detto “di convenienza” lato-client della classe Synth:

− Synth.head(aGroup, defName, args) Aggiunge il nuovo synth in testa al grup-po specificato da aGroupSe aGroup è un nodo synth, il nuovo synth sarà aggiungo in testa al nodo delgruppoSe il target è un Server, verrà risolto con il default group del ServerSe il target è nil, verrà risolto con il default group del Server di default

− Synth.tail(aGroup, defName, args) Aggiunge il nuovo synth in coda del grup-po specificato da aGroupSe aGroup è un nodo synth, il nuovo synth sarà aggiungo in testa al nodo delgruppoSe il target è un Server, verrà risolto con il default group del ServerSe il target è nil, verrà risolto con il default group del Server di default

− Synth.before(aNode, defName, args) Aggiunge il nuovo nodo appena primadel nodo specificato da aNode.

− Synth.after(aNode, defName, args) Aggiunge il nuovo nodo appena dopo delnodo specificato da aNode.

− Synth.replace(synthToReplace, defName, args) Il nuovo nodo rimpiazza ilnodo specificato da synthToReplace.Il nodo target viene ’liberato’.

Page 78: SuperCollider  Tutorial

Ordine di esecuzione

77

Usando Synth.new senza specificare un’addAction, verrà presa in considerazionel’addAction di default. (Si possono verificare i valori di default per gli argomen-ti di ogni metodo controllando dentro il codice sorgente di ogni clsse. Si vedainoltre [Internal-Snooping] per maggiori dettagli.) In situazioni in cui l’ordine diesecuzione è significativa, è importante specificare un addAction, o usare uno deimetodi ’di convenienza’mostrati precedentemente.

2. Muovendo i nodiI metodi per spostare i nodi sono: .moveBefore .moveAfter .moveToHead .move-ToTail

Se si rende necessario cambiare l’ordine di esecuzione dopo che synth e gruppisono stati creati, è possibile farlo usando messaggi del tipo:

1 ~fx = Synth.tail(s, "fx");2 ~src = Synth.tail(s, "src"); // l’effetto non viene udito3 ~src.moveBefore(~fx); // metto la sorgente prima dell’effetto

3. GruppiI gruppi possono essere mossi nello stesso modo dei synth. Quando si sposta ungruppo, tutti i synth in quel gruppo si muoveranno di conseguenza. Se conside-riamo:

1 Group 1 ---> Group 2

tutti i synth nel gruppo 1 saranno eseguiti prima di tutti i synth del gruppo 2. Inquesto modo viene determinato l’ordine di esecuzione. Il consiglio generale percreare ordinamenti funzionali è: determinare prima l’architettura ottimale e poicreare i gruppi che supportano tale architettura.

15.3 Utilizzare l’ordine di esecuzione a proprio vantaggio

Prima di cominciare a scrivere codice, è sempre consigliabile predisporre un piano intermini di cosa si vuol ottenere e capire dove i synth andranno inseriti nella configu-razione del server. Una configurazione comune è avere una routine che si occupi delplay dei nodi, nodi che necessitano di essere processati da un singolo effetto. Inol-tre, si vuole che questo effetto sia separato dal resto e esegua nello stesso istante.Per assicurarsi del corretto funzionamento di tutto ciò, è necessario creare la catenasynth -> effect su un bus audio privato e quindi trasferirlo al main output.

Page 79: SuperCollider  Tutorial

Ordine di esecuzione

78

Schematicamente:

1 [ Lots of synths] ----> effect ----> transfer

Questo esempio è perfetto per usare un gruppo:

1 Group ( [lots of synths] ) ----> effect ----> transfer

Per rendere chiara la struttura nel codice, è anche possibile creare un gruppo perl’effetto (anche se contiene un solo synth), ottenendo:

1 Group ( [lots of synths] ) ----> Group ( [effect] ) ----> transfer

Vediamo un esempio che mette in pratica tutti questi concetti; in particolare modu-leremo un parametro (lunghezza della nota) usando un synth di control rate. Eccol’esempio:

Page 80: SuperCollider  Tutorial

Ordine di esecuzione

79

1 s.boot;2 ( l = Bus.control(s, 1); // un bus per LFO3 //non rilevante l’ordine di esecuzione4 b = Bus.audio(s, 2); // un bus stereo; questo per tenere5 // la catena src->fx separata da altre catene

simili6 ~synthgroup = Group.tail(s);7 ~fxgroup = Group.tail(s);8 // ora abbiamo synthgroup --> fxgroup dentro il default group di s9 // creiamo qualche synthdefs da suonarci dentro with

10 SynthDef("order-of-ex-dist", { arg bus, preGain, postGain;11 var sig;12 sig = In.ar(bus, 2);13 sig = (sig * preGain).distort;14 ReplaceOut.ar(bus, sig * postGain);15 }).send(s);16 SynthDef("order-of-ex-pulse", { arg freq, bus, ffreq, pan, lfobus;17 var sig, noteLen;18 noteLen = In.kr(lfobus, 1);19 sig = RLPF.ar(Pulse.ar(freq, 0.2, 0.5), ffreq, 0.3);20 Out.ar(bus, Pan2.ar(sig, pan)21 EnvGen.kr(Env.perc(0.1, 1),timeScale:noteLen,doneAction: 2));22 }).send(s);23 SynthDef("LFNoise1", { arg freq, mul, add, bus;24 Out.kr(bus, LFNoise1.kr(freq, mul:mul, add:add));25 }).send(s); )26 // Creiamo il synth di LFO e inseriamolo:27 ~lfo = Synth.head(s, "LFNoise1",28 [\freq, 0.3, \mul, 0.68, \add, 0.7, \bus, l.index]);29 // Quindi inseriamo l’effetto:30 ~dist = Synth.tail(~fxgroup, "order-of-ex-dist",31 [\bus, b.index, \preGain, 8, \postGain, 0.6]);32 // trasferiamo i risultati al main out, con il livello scalato33 // suonandolo il coda del default group34 //(da notare che Function-play prende anche l’addActions!35 ~xfer = { Out.ar(0, 0.25 * In.ar(b.index, 2))36 }.play(s, addAction: \addToTail);37 // Avviamo la nostra routine:38 ( r = Routine({39 {40 Synth.tail(~synthgroup, "order-of-ex-pulse",41 [\freq, rrand(200, 800), \ffreq, rrand(1000, 15000),42 \pan, 1.0.rand2,\bus, b.index, \lfobus, l.index]);43 0.07.wait;44 }.loop;45 }).play(SystemClock); )46 ~dist.run(false); // per provare che la distorsione funzioni47 ~dist.run(true);48 // per ripulire il tutto:49 ( r.stop;50 [~synthgroup, ~fxgroup, b, l, ~lfo, ~xfer].do({ arg x; x.free });51 currentEnvironment.clear; // pulisce tutte le variabili d’ambiente

)

Page 81: SuperCollider  Tutorial

Ordine di esecuzione

80

Da notare che nella routine, usando un gruppo per i synths sorgente, si può specifica-re facilmente l’ordinamento relativo fra un synth e l’altro (essi sono aggiunti al grup-po con il metodo tail) senza preoccuparsi del loro ordine rispetto al synth dell’effetto.Da notare inoltre che questo metodo di operare previene errori nell’ordine di esecu-zione, attraverso l’uso di un breve codice per l’organizzazione. Ovviamente aumen-tando la dimensione del progetto, il principio non varia; semplicemente occorreràutilizzare più gruppi e la configurazione potrà essere più vasta.

15.4 Stile Messaggio

Gli esempi precedenti sono in ’stile-oggetto’. Se si preferisce lavorare in ’stile-messaggio’,ci sono messaggi corrispondenti per tutti i metodi mostrati precedentemente.

15.5 Un caso particolare: Feedback

Quando le varie ugens di output (Out, OffsetOut, XOut) scrivono dati su un bus,quest’ultimo si occuperà di mixarli con ogni dato del ciclo corrente, ma sovrascri-vendo i dati del ciclo precedente. (ReplaceOut sovrascrive indifferentemente tuttii dati).Quindi, a seconda della posizione del nodo nell’ordinamento, i dati su un certo buspotrebbero appartenere al ciclo corrente o al ciclo precedente.Il metodo In.ar verifica il timestamp di ogni dato che legge e lo azzera se riscontrache appartiene al ciclo precedente (per quel che riguarda il synth a cui afferisce; idati rimangono comunque sul bus). Questo è ottimo per i dati audio, evitando ilfeedback, ma per i dati di controllo potrebbe invece essere utile poter leggere datida ogni posto nell’ordinamento dei nodi. Per questa ragione In.kr legge anche datiil cui ordine non è legato al ciclo corrente.In qualche caso si potrebbe anche voler leggere audio da un nodo successivo rispettoall’ordine dei nodi. Questo è lo scopo di InFeedback. Il ritardo introdotto da que-sto oggetto è al massimo della dimensione di un blocco, che equivale a circa 0,.0014sec per un blocco e sample rate di default. Il comportamento variabile di mixing eriscrittura delle UGens di output può rendere cruciale l’ordine di esecuzione quandosi usa In.kr o InFeedback.ar.Per esempio con un ordinamento dei nodi come il seguente, la UGen InFeedbacknel Synth 2 riceverà solo dati dal Synth:

1 //Synth1 sovrascrive l’output di Synth3 prima che Synth2 lo utilizzi2 Synth 1 scrive sul busA3 Synth 2 (with InFeedback) legge dal busA4 Synth 3 scrive sul busA

Page 82: SuperCollider  Tutorial

Ordine di esecuzione

81

Se spostiamo il Synth 1 dopo il Synth 2, allora il InFeedback del Synth 2 riceveràun mix degli output del Synth 1 e Synth 3.

1 Synth 2 (with InFeedback) legge dal busA2 Synth 1 scrive sul busA3 Synth 3 scrive sul busA

Questo potrebbe anche essere vero se il Synth 2 venisse dopo Synth 1 e il Synth 3.

1 Synth 1 scrive sul busA2 Synth 3 scrive sul busA3 Synth 2 (with InFeedback) legge dal busA

In entrambi i casi tuttavia, i dati da Synth 1 e Synth 3 potrebbero avere lo stessotimestamp (del ciclo corrente o di quello precedente); in questo modo non vieneeffettuata nessuna sovrascrittura. (Se qualche In.ar avesse scritto sul busA primadel Synth 2, potrebbe aver azzerato il bus prima che il Synth 3 ottenga i dati dalSynth 2. Questo è vero anche se non ci fosse stato nessuno a scrivere sul busA primadel Synth 2).Da tutto ciò si capisce come può essere utile allocare un bus separato per il feedback.Con questo espediente, il Synth 2 riceverà dati dal Synth 3 indifferentemente dallaposizione del Synth 1 nell’ordinamento dei nodi.

1 Synth 1 scrive sul busA2 Synth 2 (with InFeedback) legge dal busB3 Synth 3 scrive su busB + busA

L’esempio seguente dimostra quanto detto con In.kr:

Page 83: SuperCollider  Tutorial

Ordine di esecuzione

82

1 (2 SynthDef("help-Infreq", { arg bus;3 Out.ar(0, FSinOsc.ar(In.kr(bus), 0, 0.5));4 }).send(s);

6 SynthDef("help-Outfreq", { arg freq = 400, bus;7 Out.kr(bus, SinOsc.kr(1, 0, freq/40, freq));8 }).send(s);

10 b = Bus.control(s,1);11 )12 // aggiunge il primo Synth di controllo in coda al default server; no

audio13 yetx = Synth.tail(s, "help-Outfreq", [\bus, b.index]);

15 // aggiunge il Synth che produce suono PRIMA;16 // Questo riceve i dati di x dal ciclo precedente17 y = Synth.before(x, "help-Infreq", [\bus, b.index]);

19 // aggiunge un altro Synth di controllo prima di y, in testa al server20 // Questo ora sovrascrive i dati di x del ciclo precedente prima che21 //il synth y li riceva22 z = Synth.head(s, "help-Outfreq", [\bus, b.index, \freq, 800]);

24 // creiamo un altro bus25 c = Bus.control(s, 1);

27 // ora y riceve i dati di x28 y.set(\bus, c.index); x.set(\bus, c.index);29 x.free; y.free; z.free;

15.6 Espansione multicanale

Si è accennato all’espansione multicanale nel capitolo Per espansione multicanale siintente l’inserimento di un Array in uno degli argomenti della UGen al posto di unsingolo valore; canali audio multipli vengono rappresentati come Array.

1 s.boot;2 // un canale3 { Blip.ar(800,4,0.1) }.play;

5 // due canali6 { [ Blip.ar(800,4,0.1), WhiteNoise.ar(0.1) ] }.play;

Page 84: SuperCollider  Tutorial

Ordine di esecuzione

83

Ogni canale di output viene dirottato verso uno speaker differente; limitiamo qui ildiscorso a due canali, per un’output stereo. Se si dispone di un’interfaccia audio mul-ticanale, allora è possibile gestire tanti output quanti sono supportati dall’interfaccia.Tutte le UGens hanno un output singolo. Questa uniformità facilita l’uso di gruppidi operazioni per manipolare strutture multicanale.Per implementare output multicanali, le UGen creano una UGen separata conosciu-ta come OutputProxy per ogni output. Un OuputProxy è solo un marcatore perla UGen di output multicanale. Queste OutputProxy sono create internamente, nonè il caso pertanto di preoccuparsi di crearli, ma è buono avere la consapevolezza cheesistono così da sapere cosa sono quando ci si imbatte nella documentazione di SC.

1 // si osservino gli output di Pan2:2 Pan2.ar(PinkNoise.ar(0.1), FSinOsc.kr(3)).dump;3 play({ Pan2.ar(PinkNoise.ar(0.1), FSinOsc.kr(1)); });

Quando viene passato un Array come input a una unit generator, esso causerà unaserie di copie multiple della unit generator, ognuna con un valore differente dell’arraypassato in input. Tutto ciò provoca l’espansione multicanale.Importante: solo gli Array sono espansi, non altri tipi di Collection e non sottoclassidi Array.Vediamo un esempio:

1 { Blip.ar(500,8,0.1) }.play // un canale

3 // l’array nell’input delle frequenze causa un Array di 2 Blips :4 {5 Blip.ar([499,600],8,0.1) }.play // due canali6 Blip.ar(500,8,0.1).postln // crea UNA UGen7 Blip.ar([500,601],8,0.1).postln // crea DUE UGen8 }

L’espansione multicanale si propagherà attraverso il grafo di espressioni. Quandoviene chiamato un costruttore di una UGen con un array di input, ritornerà unarray di istanze. Se questo array è l’input di un altro costruttore, allora viene creatoun altro array e così via. Vediamo un esempio:

1 {2 RLPF.ar(Saw.ar([100,250],0.05), XLine.kr(8000,400,5), 0.05) }.play;

4 // L’array [100,250] di frequenze, input a Saw.ar, crea un array di 2Saw;

5 // tale array in input a RLPF.ar crea due RLPF.6 // Entrambi i RLPF condividono una singola instanza di XLine.

Page 85: SuperCollider  Tutorial

Ordine di esecuzione

84

Quando un costruttore è parametrizzato da due o più array, allora il numero dicanali creati è uguale alla dimensione dell’array più lungo, con i parametri inseritida ogni array in parallelo. L’array più corto sarà wrappato.

Per esempio, il seguente:

1 Pulse.ar([400, 500, 600],[0.5, 0.1], 0.2)2 // \‘e equivalente a:3 [ Pulse.ar(400,0.5,0.2), Pulse.ar(500,0.1,0.2), Pulse.ar(600,0.5,0.2) ]

Un esempio più complesso basato sulla Saw viene riportato di seguito; la XLine èespansa da due istanze, una che va da 8000 Hz a 400 Hz e l’altra che va in direzioneopposta da 500 Hz a 700 Hz. Queste due XLine sono ’accoppiatè ai due oscillatoriSaw e usate per parametrizzare due copie di RLPF.Così, sul canale sinistro si avrà una Saw a 100 Hz filtrata da 8000 Hz a 400 Hz e sulcanale sinistro una Saw a 250 Hz filtrata da 500 Hz a 7000 Hz.

1 { RLPF.ar(2 Saw.ar(3 [100,250],0.05), XLine.kr([8000,500],[400,7000],5)4 , 0.05)5 }.play;

15.7 Protezione di array dall’espansione

Alcune UGen come Klank richiedono in input alcuni array di valori. Dal momentoche tutti gli array sono espansi, è necessario proteggerne alcuni tramite un oggettoRef. Un istanza Ref è un oggetto con un singolo slot detto value che serve comecontenitore di un oggetto. Un modo per creare un’istanza di Ref è Ref.new(object),ma esiste una scorciatoia sintattica: l’apostrofo ’è un operatore unario che è equi-valente a chiamare Ref.new(...). Così per proteggere gli array che sono input in unKlank o ad altre UGens simili, si può scrivere:

1 Klank.ar(’[[400,500,600],[1,2,1]], z)

È possibile inoltre creare Klanks multipli dando un array di array di Ref.

Page 86: SuperCollider  Tutorial

Ordine di esecuzione

85

1 Klank.ar([ ’[[400,500,600],[1,2,1]], ’[[700,800,900],[1,2,1]] ], z)

3 //\‘e equivalente a:

5 [6 Klank.ar(’[[400,500,600],[1,2,1]], z),7 Klank.ar(’[[700,800,900],[1,2,1]], z)8 ]

15.8 Ridurre l’espansione multicanale con Mix

L’oggetto Mix ha lo scopo di ridurre array multicanali in un singolo canale.

1 Mix.new([a, b, c]) // array of channels

3 //\‘e equivalente a:

5 a + b + c // mixati in uno

L’utilizzo di Mix è più efficiente rispetto all’utilizzo del solo + perchè permette cosìdi effetturare addizioni multiple contemporaneamente. Ma il principale vantaggio èche può lavorare in situazioni in cui il numero di canali è arbitrario o determinatosolo a tempo di esecuzione.

1 // 3 canali di Pulse sono mixati in un canale2 { Mix.new( Pulse.ar([400, 501, 600], [0.5, 0.1], 0.1) ) }.play

L’espansione multicanale lavora differentemente per Mix. Mix prevede come inputun array (non protetto da un oggetto Ref). Tale array non causerà copie del Mixche sarà effettuato. Tutti gli elementi dell’array saranno invece mixati insieme inun oggetto singolo Mix. Se l’array contiene uno o più array, allora l’espansionemulticanale avverrà un livello sotto. Questo permette di mixare un array di arraystereo (due elementi) in un array a 2 canali. Per esempio:

1 // array di coppie di stereo input2 Mix.new( [ [a, b], [c, d], [e, f] ] )

4 //\‘e equivalente a:5 // mixare una singola coppia stereo6 [ Mix.new( [a, c, e] ), Mix.new( [b, d, f] ) ]

Page 87: SuperCollider  Tutorial

Ordine di esecuzione

86

Importante: non è ricorsivo! Non si può usare Mix su array di array di array. Vediamoun esempio finale che illustra un’espansione multicanale e Mix. Variando il valoredella variabile ”n”, è possibile variare il numero di voci nella patch.

1 (2 {3 var n;4 n = 8; // numero di voci5 // mix down di tutte le coppie stereo6 Mix.new(7 // spazializza la voce in una posizione stereo8 Pan2.ar(9 // un comb filter usato come uno string resonator

10 CombL.ar(11 // impulsi random come una funzione eccitatrice12 Dust.ar(13 // un array che causa l’espansione di14 // Dust in n canali;15 // 1 : impulso per secondo16 Array.fill(n, 1),17 0.3 // ampiezza18 ),19 0.01, // delay max in secondi20 // array di lunghezze random diverse per ogni ’string’21 Array.fill(n, {0.004.rand+0.0003}),22 4 // tempo di decay in secondi23 ),24 // ad ogni ’voce’una differente spazializzazione25 Array.fill(n,{1.0.rand2})26 )27 )28 }.play;29 )

15.9 Uso di flop per l’espansione multicanale

Il metodo flop inverte colonne e righe, permettendo di derivare serie di insiemi diargomenti:

Page 88: SuperCollider  Tutorial

Ordine di esecuzione

87

1 (2 SynthDef("help_multichannel", { |out=0, freq=440, mod=0.1, modrange=20|3 Out.ar(out,4 SinOsc.ar(5 LFPar.kr(mod, 0, modrange) + freq6 ) * EnvGate(0.1)7 )8 }).send(s);9 )

11 (12 var freq, mod, modrange;13 freq = Array.exprand(8, 400, 5000);14 mod = Array.exprand(8, 0.1, 2);15 modrange = Array.rand(8, 0.1, 40);

17 fork {18 [\freq, freq, \mod, mod, \modrange, modrange].flop.do { |args|19 args.postln;20 Synth("help_multichannel", args);21 0.3.wait;22 }23 };24 )

In maniera analoga, una Function-flop restituisce una funzione non valutata cheespanderà i suoi argomenti quando verrà valutata:

1 (2 SynthDef("blip", { |freq| Out.ar(0, Line.ar(0.1, 0, 0.05, 1, 0, 2)3 Pulse.ar(freq * [1, 1.02])) }).send(s);

5 a = { |dur=1, x=1, n=10, freq=400|6 fork { n.do {7 if(x.coin) { Synth("blip", [\freq, freq]) };8 (dur / n).wait;9 } }

10 }.flop;11 )

13 a.value(5, [0.3, 0.3, 0.2], [12, 32, 64], [1000, 710, 700]);

Page 89: SuperCollider  Tutorial

Just In Time Programming

88

Capitolo 16 Just In Time Programming

Just in time programming (or: live coding, on-the fly-programming, interactive pro-gramming ) è un paradigma che include l’attività di programmazione stessa nelleoperazioni del programma. Questo significa un programma che non è considerabilecome un tool preconfezionato, ma un processo di costruzione dinamico di descri-zione e conversazione - scrivere codice diventa una parte integrante della praticamusicale. SC, essendo un linguaggio di programmazione dinamico, offre diverse pos-sibilità di modificare un programma in stato di esecuzione; la libreria JITLib tentadi estendere, sviluppare e semplificare questo processo principalmente offrendo deiplaceholders (proxies) astratti che possono essere modificati e usati nel calcolomentre stanno suonando. Ci sono alcune classi di rete specifiche, atte a semplificarela distribuzione o l’attività di live coding.

Jitlib consiste di un numero di placeholders (sia server side che client side) e unoschema di accesso. Questi due aspetti dello spazio corrispondo a inclusioni e riferi-menti, a seconda del contesto - in quest’ottica i placeholders sono simili a regole chehanno un certo comportamento e possono essere soddisfatte da certi oggeti.

È utile essere consapevole di tre aspetti riguardanti un placeholder:

− la loro sorgente può essere un certo insieme di elementi.

− possono essere usati in un certo insieme di contesti.

− hanno una certa sorgente di default, se nessuna è assegnata.

Questo capitolo si focalizza su alcuni concetti base usati in JITLib. Sono contemplatesvariate possibilità, come la messaggistica con il server o come i pattern proxies cheperò, qui, non verranno considerati in modo specifico

16.1 Overview delle differenti classi e tecnicheEsistono alcuni modalità di accesso:

1. Uno stile di accesso è la classe def (Pdef, Ndef etc.) che collega un symbol a unoggetto in un modo specifico:

− Pdef (name) restituisce il proxy

− Pdef(name, object) setta la sorgente e restituisce il proxy. Il resto del com-portamento dipende dal suo uso.

Page 90: SuperCollider  Tutorial

Just In Time Programming

89

In generale consideriamo:

− client side: Pdef Pdefn, Tdef, Pbindef

− server side: Ndef

2. Un altro modo, per i NodeProxy lato server, è un ambiente che restituisce pla-ceholders su richiesta:

1 ProxySpace.push2 ~out = { ...}

(si veda l’helpfile di ProxySpace)

3. Esiste infine anche un accesso diretto senza usare gli schemi di accesso offerto daNodeProxy, TaskProxy etc. Internamente si usano le seguenti classi:

• client side: PatternProxy, EventPatternProxy, TaskProxy, PbindProxy, Pdict

• server side: NodeProxy, RecNodeProxyIn reti, locali e remote, grazie all’architettura di SC i nodeproxy possono essereusati da qualunque server, purchè venga notificato al client e si ha un nodo didefault correttamente inizializzato.Da notare che il client deve essere settato.Usando le classi di rete, gruppi di partecipanti possono interferire in ogni altracomposizione condividendo un server comune, usando un SharedNodeProxy escambiando codici e commenti. Per il networking si usano le seguenti classi:BroadcastServer e OSCBundle

16.2 Placeholder in supercolliderIn questa sezione vengono descritti alcuni concetti base sulla programmazione inte-rattiva con SC e il proxy space.

a. Cos’è un proxy?

Un proxy è un placeholder che viene spesso usato per operare su qualcosa chenon esiste ancora. Per esempio, un OutputProxy viene utilizzato per rappresen-tare outputs multipli di una UGen, anche se viene creata soltato una UGen.

Ogni oggetto può avere un comportamento da proxy (per esempio potrebbe de-legare chiamate di funzioni a oggetti differenti), in special modo funzioni e riferi-menti possono essere impiegati come operandi mentre mantengono la loro qualità

Page 91: SuperCollider  Tutorial

Just In Time Programming

90

referenziale. (si veda anche: [OutputProxy] [Function] [Ref] )È possibile effettuare riferimenti utilizzando diversi costrutti:

• Un Ref come proxy

Un’istanza dell’oggetto Ref è un oggetto con un singolo slot, value, che servecome contenitore di un oggetto. Un modo per istanziare questo oggetto èRef.new(object), ma esiste uno shortcut sintaattico, l’apostrofo ’infatti èun operatore unario equivalente al costruttore precedentemente illustrato.Vediamo un esempio:

1 x = Ref.new(nil);2 z = obj.method(x); // method mette qualcosa in riferimento3 x.value.doSomething; // recupera il valore e fa qualcosa

Ref viene anche impiegato come un dispositivo di citazione per limitarel’espansione multicanale in certe UGen che richiedono Array come input.

A questo punto vediamo un esempio di utilizzo di Ref come proxy:

Page 92: SuperCollider  Tutorial

Just In Time Programming

91

1 // creiamo un oggetto Ref2 y = ‘(nil);3 // si pu\‘o cominciare a calcolare con y, anche se il suo valore non

\‘e stato dato:4 z = y + 10; // restituisce una funzione5 // settiamo ora la sorgente:6 y.value = 34;7 // la funzione z viene valutata, A QUESTO PUNTO.8 z.value

10 // la stessa cosa senza riferimento non funziona:11 y = nil;12 z = y + 10; // questo fallisce.13 // anche un array non offre questa referenziazione:14 y = [nil];15 z = y + 10; // questo fallisce.

17 // un ambiente senza default sufficienti ha lo stesso problema:18 currentEnvironment.postln; // anEnvironment19 ~x; // accedo all’ambiente; per ora = nil20 ~x = 9; // salvo qualcosa21 ~x; // ora contiene 922 ~x + 100; // posso calcolare23 currentEnvironment.postln; // the value is stored in the environment24 ~y + ~x; // causa un errore: ~y = nil.25 ~y = -90; // settiamo ~y26 ~y + ~x; // adesso funziona.

• Una Function come proxy:

1 // la stesso esempio, ma con una funzione2 y = nil; // y empty3 z = { y } + 10;4 // questo non fallisce, crea una nuova funzione, che5 // non fallisce quando viene valutata dopo che y6 //viene settato a 347 y = 34;8 z.value;

Si vedano, per completezza, altri proxy lato client come [Tdef] [Pdefn] [Pdef]

b. NodeProxy

Per la programmazione interattiva potrebbe essere utile essere in grado di usa-re qualcosa che non si ha ancora e crea un ordine di valutazione più flessibileper permette di posporre decisioni in momenti successivi. Di solito vengono fatti

Page 93: SuperCollider  Tutorial

Just In Time Programming

92

anticipatamente alcuni passi: negli esempi precedenti, è stato creato un riferi-mento come prima cosa. In altre situazioni, questo ordine di preparazione non èabbastanza, per esempio se si vuole fare operazioni matematiche in processi inrunning sul server.

L’output audio sul server ha principalmente due proprietà:

1. un calculation rate (audio o control)

2. un certo numero di canali.Queste sono le proprietà statiche principali di un node proxy, che non possonoessere modificate mentre è in uso. Vediamo un esempio.

1 // boot del server2 s.boot;

4 // due proxies su un server. Il rate di calcolo \‘e audio rate,5 // il numero di canali = 26 y = NodeProxy.audio(s, 2);7 z = NodeProxy.audio(s, 2);

9 // usiamoli nel calcolo10 z.play;11 z.source = y.sin * 0.2;

13 // settiamo la sua sorgente14 y.source = { Saw.ar([300, 301], 4*pi) };15 // la sorgente pu\‘o essere di vario tipo, per esempio un numero:16 y.source = 0.0;

18 // post della sorgente19 y.source;

21 // fine: vengono liberati i bus22 y.clear;23 z.clear;

Un semplice modo per creare node proxy implica che deve essere utilizzato unproxy space, come nell’esempio seguente.

Page 94: SuperCollider  Tutorial

Just In Time Programming

93

1 p = ProxySpace.push(s.boot); // store proxy space in p so it can beaccessed easily.

2 ~z.play;3 ~z = ~y.sin * 0.2;4 ~y = { Saw.ar([300, 301], 4*pi) };

6 // ripulisce lo spazio (tutti i proxies)7 p.clear;

9 // esce dal proxyspace10 p.pop;

further readings: NodeProxy ProxySpace Ndef

16.3 Proxy Space: concetti baseOccorre precisare a questo punto alcuni concetti, dando le definizioni di ProxySpacee Enviroment. Un ProxySpace è un ambiente di referenze sul server. Generalmenteun proxy è un placeholder per qualcosa, che in questo caso è qualcosa che suonasul server e che scrive su un numero limitato di bus, quindi, per capirci, potrebbbeessere l’esempio di un synth.Quando si accede a un ProxySpace, si ottiene un NodeProxy determinato dal primooggetto inserito nell’ambiente. Una volta creato, questo può solo essere settato aduna funzione che restituisce lo stesso oggetto e un numero di canali uguale a quelloiniziale o al più piccolo.

1 // si noti che le due espressioni sono equivalenti:2 ~out = something;3 currentEnvironment.put(\out, something);

È possibile creare un proxyspace quando il server non è in stato running e vieneavviato dopo.

Un Environment, ambiente in SC, è un IdentityDictionary che mappa simbolia valori. Esiste sempre un ambiente corrente, che è salvato nella variabile di classecurrentEnvironment della classe Object.

In questa parte ci occupiamo di descrivere la struttura esterna di un node proxy,referenziata in un proxyspace e/o in ambienti. Esistono diverse configurazioni pos-sibili:

Page 95: SuperCollider  Tutorial

Just In Time Programming

94

a. Ambiente di default

b. Proxyspace come ambiente

c. Usando il proxyspace per cambiare processi al volo

d. Usando ProxySpace insieme ad altri ambienti

Vediamone ora uno per volta, dando le precisazioni del caso.

a. Ambiente di default Siamo nel caso in cui abbiamo a che fare con il classicoambiente di default di SuperCollider. Si ricorda che le variabili d’ambiente sonoprecedute dal simbolo~.

1 currentEnvironment.postln; // anEnvironment

3 // accediamo all’ambiente, per ora = nil4 ~a;5 ~a = 9; // salviamo qualcosa6 ~a; // ora 9 viene salvato7 ~a + 100; // = 109

9 currentEnvironment.postln; // il valore viene salvato nell’ambiente

11 ~b + ~a; // causa un error: ~b is nil.12 ~b = -90; // setta ~b13 ~b + ~a; // adesso funziona.

Da notare che l’accesso ad ambienti ( o ProxySpace) è sempre possibile dall’esternonel seguente modo:

1 x = currentEnvironment;2 x[\a] + x[\b] // = ~b + ~a

oppure, se know è settata a true, in ques’altro modo

1 x.know = true;2 x.a + x.b;

b. Proxyspace come ambienteÈ possibile rimpiazzare l’ambiente corrente con un tipo speciale di ambiente, unproxy space.

Page 96: SuperCollider  Tutorial

Just In Time Programming

95

1 p = ProxySpace.new(s); // crea un nuovo ambiente e lo salva in p2 p.push; // push = diventa l’ambiente corrente3 currentEnvironment.postln;4 currentEnvironment === p; // sono identici

6 ~x;7 // accedendo si crea automaticamente un NodeProxy (non inizializzato)8 ~x + ~y;9 // questo funziona immediatamente, il lookup non ritorna nil,

10 // ma un placeholder (proxy)

12 p.postln; // ci sono due placeholder nello spazio.

c. Usando il proxyspace per cambiare processi al volo

1 s.boot; // boot del server

3 // funzione assegnata ad un proxy4 // viene fatto il play sul proprio bus privato (anche se non ancora

udibile)5 (6 ~x = {7 RLPF.ar(Impulse.ar(4) * 20, [850, 950], 0.2)8 }9 )

11 // il proxy viene inizializzato dal suo primo assegnamento12 // suona ad audio rate avendolo inizializzato con una UGen audio rate13 // e ha due canali (stereo output)

15 ~x.index;16 // viene visualizzato l’indice del bus, prima era .ir(nil), ora viene

inizializzato .ar(2)17 ~x.bus

19 ~x.play;20 // ora diventa udibile. Viene creato un monitor (Monitor) che suona21 // il segnale su un bus pubblico, indipendentemente dal proxy stesso

La funzione può essere cambiata in ogni momento

Page 97: SuperCollider  Tutorial

Just In Time Programming

96

1 (2 ~x = {3 RLPF.ar(Impulse.ar([5, 7]) * 5, [1450, 1234], 0.2)4 }5 )

7 // filtra le alte frequenze:8 ~x = { RLPF.ar(Impulse.ar([5, 7]) * 5, [1800, 2000], 0.2) }

10 // same pulse ratio (5/8), different pulse tempo:11 ~x = { RLPF.ar(Impulse.ar([5, 8] * 3.2) * 5, [1800, 2000], 0.2) }

13 // different filter:14 ~x = { Ringz.ar(Impulse.ar([5, 8] * 3.2), [1800, 2000], 0.05) }

16 // and if you set the proxy’s fadeTime, you can create little17 // textures by hand:

19 ~x.fadeTime = 3;20 // different filter freqs every time:21 ~x = { Ringz.ar(Impulse.ar([5, 8] * rrand(0.5, 1.5)) * 0.5, ({

exprand(200, 4000) } ! 2), 0.05) }

23 // un altro proxy:24 ~y = { Pan2.ar(Dust.ar(20), 0) };

26 ~y.bus;27 // ha due canali, come ~x,ma suona su un altro bus privato.28 // da notare che ~y non \‘e udibile direttamente29 //ma pu\‘o venire usato in un altro proxy:30 (31 ~x = {32 RLPF.ar(~y.ar * 8, [1450, 1234], 0.2)33 }34 )

36 // Modificando il proxy, il risultato cambia dinamicamente:

38 ~y = { Impulse.ar(MouseX.kr(2, 18, 1)) * [1, 1] };39 ~y = { PinkNoise.ar(MouseX.kr(0, 0.2) * [1, 1]) };40 ~y = { Impulse.ar([MouseX.kr(2, 18, 1), MouseY.kr(2, 18, 1)]) };

42 // fine dell’ascolto. i proxies girano in background.43 ~x.stop;

45 // l’ascolto di ~y diretto46 ~y.play;

48 // per rimuovere un input, si usa nil49 ~y = nil;50 // fine dell’ascolto51 ~y.stop;

Page 98: SuperCollider  Tutorial

Just In Time Programming

97

Quando vengono inizializzati i node proxy?L’inizializzazione dei bus di un node proxy avviene non appena viene impiegatola prima volta; gli input successivi verranno ”adeguati”ai bus disponibili.

1 //un proxy control rate a 4 canali2 ~z2 = { LFNoise0.kr([1, 2, 3, 4]) };3 ~z2.bus.postln;

5 ~z100 = 0.5; // un valore costante equivale ad un proxy6 //control rate di un singolo canale7 ~z100.bus.postln;

9 ~z34.ar(3) //il primo accesso alloca il bus10 ~z34.bus.postln; //un proxy audio a 3 canali

12 // queste inizializzazioni possono essere rimosse usando clear:13 ~z34.clear;14 ~z34.bus.postln;

Questa inizializzazione avviene appena il proxy viene utilizzato per la prima vol-ta. In seguito, si può accedere al proxy con un’altra combinazione rate/numChannelsin base alle necessità (rates sono convertiti, numChannels sono estesi tramite ilwrapping).Da notare che questo potrebbe causare inizializzazioni ambigue, nel qual caso ilproxy dovrebbe sempre essere inzializzato prima. Un problema tipico che si puòriscontrare è riportato nell’esempio seguente:

Page 99: SuperCollider  Tutorial

Just In Time Programming

98

1 ~u.play(0, 2); // inizilizzazione di 2 canali audio (default).2 //0 = numero del bus di output3 // se il proxy non \‘e inizializzato4 //, suona di default su 2 canali.5 // abbiamo esplicitato solo per chiarezza.6 ~u = { PinkNoise.ar(0.2) }; // ne usa solo uno7 ~u.numChannels; // dei 2 canali8 ~u.clear;

10 se valutato, sono un canale \‘e utilizzato:

12 ~u = { PinkNoise.ar(0.2) }; // inizializza 1 canale audio13 ~u.play(0, 2); // suona 2 canali: il canale 1 viene espanso in 2.14 // numChannels di .play sono legati ai numChannels del proxy15 // Qui viene esplicitato, cos\‘i espande i canali16 ~u.numChannels; // 1 canale17 ~u.clear;

19 //Quindi potrebbe essere utile inizializzare in modo esplicito20 // dei proxy che usano input variabili:

22 ~b.kr(8);23 ~c.ar; // inizializzazione esplicita24 p.postln; // visualizza l’intero spazio proxy

e) uscire dal proxy space:

Page 100: SuperCollider  Tutorial

Just In Time Programming

99

1 ~x.play; //suona2 ~x = { PinkNoise.ar(0.5) };3 p.postln; //p = proxy space

5 // per terminare tutti i processi in p, si usa end:6 p.end(2) // con 2 secondi di fade out.

8 // per rimuovere tutti gli oggetti busve liberarli dal bus allocator,si usa clear:

9 p.clear;10 currentEnvironment.postln;

12 // ripristinare l’ambiente originale13 p.pop;14 currentEnvironment.postln;

16 ~a + ~b; // i valora vecchi ci sono ancora.

18 p === currentEnvironment; // non sono uguali

20 // rimuoviamo il contenuto, per far rilasciare la memoria dal garbagecollector

21 p.clear;

23 // da notare che se si usa questo schema di accesso, gli oggetti nonsono

24 //considerati dal garbage collectore fino a che le loro chiavi non sonoposte a nil

25 // Questo appare un errore comune quando si usano ambienti normali

27 // ripulire completamente l’ambiente normale:28 currentEnvironment.clear;

d. Usando ProxySpace insieme ad altri ambientiUsando un proxy space come uno schema di accesso per i node proxy, potrebbecreare confusione con l’uso consueto di ambienti come pseudo-variabili.Vediamoalcuni modi per accoppiarli.

Page 101: SuperCollider  Tutorial

Just In Time Programming

100

1 // per restringere lo scope del proxyspace a un documento (solo mac)

3 EnvirDocument(p, "proxyspace");4 // per testarlo,si verifica currentEnvironment5 //e dentro il documento envir.

7 // per accedere indirettamente al proxyspacce:8 p[\x].play;9 p[\x] = { SinOsc.ar(450, 0, 0.1) };

11 // oppure, dopo una push, si usa un ambiente normale indirettamente:12 p.push;13 d = ();14 d[\buffer1] = Buffer.alloc(s, 1024);15 d.use { ~buffer1.postln; ~zz = 81; };

17 // senza creare un nuovo proxyspace,si usa .envir:18 p.envir.postln;19 p.envir[\x].postln;

16.4 Struttura interna di un node Proxy

Vedremo la struttura interna del node proxy, l’ordine dei nodi e il contesto deiparametri.Un NodeProxy ha due contesti interni in cui gli oggetti vengono inseriti: Il grup-po, che è sul server, e il nodeMap che è un contesto di parametri lato client;quindi, come il gruppo sul server può contenere un insieme di synth in un certoordine, esiste una rappresentazione lato client in cui gli oggetti sorgente vengonosalvati.

1 //creo un nuovo spazio2 = ProxySpace.push(s.boot);3 ~z.play; ~y.ar; // inizializzazione esplicita dei nodeproxy

16.4.1 NodeProxy slots

Un node proxy può contenere diversi oggetti in un ordine di esecuzione preciso.L’indice può essere qualunque intero positivo. Lo slot iniziale ha indice 0 e vieneutilizzato con l’assegnazione diretta. Vediamo un esempio:

Page 102: SuperCollider  Tutorial

Just In Time Programming

101

1 // ~y non ancora utilizzato.2 ~z = (~y * pi).sin * 0.1 * { LFSaw.kr(LFNoise1.kr(0.1 ! 3).sum * -

18).max(0.2) };

4 // altri numeri di slot sono accessibili tramite interi positivi:

6 ~y[1] = { Saw.ar([400, 401.3], 0.4) };7 ~y[0] = { Saw.ar([300, 301], 0.4) };

9 // per rimuoverne uno:10 ~y[0] = nil;

12 // cosa troviamo all’indice 1?13 ~y[1] // un interfaccia14 ~y[1].source.postcs // la funzione15 ~y.source; // gli oggetti nello slot

È possibile effettuare assegnamenti multipli:

1 // viene assegnata la faunzione agli slot da 1 ta 42 ~z[1..4] = { SinOsc.ar(3 exprand(300, 600), 0,4 LFTri.kr({exprand(1, 3)} ! 3)5 .sum.max(0)) * 0.1 };

7 // la funzione viene assegnata agli slot 1, 2 e 38 ~z[1..] = [ {SinOsc.ar(440) * 0.1 },9 { SinOsc.ar(870) * 0.08 },

10 { SinOsc.ar(770) * 0.04 }];

12 // se non vengono esplicitati slot, tutti gli slot sono inzializzati13 ~z = { OnePole.ar(Saw.ar([400, 401.3], 0.3), 0.95) };

15 ~z.end;16 ~y.end;

16.4.2 fade time

Settare questo valore, fadeTime, permetterà di creare dei crossfade. Nel casodi un proxy audio rate,il fade è pseudo- guassiano; nel caso di un proxy controlrate, il fade è lineare. Questo parametro può essere settato o modificato in ognimomento. Vediamo e proviamo il seguente codice:

Page 103: SuperCollider  Tutorial

Just In Time Programming

102

1 ~z.play;

3 ~z.fadeTime = 5.0; // 5 secondi4 ~z = { max(SinOsc.ar([300, 301]), Saw.ar([304, 304.3])) * 0.1 };5 ~z = { max(SinOsc.ar(ExpRand(300, 600)), Saw.ar([304, 304.3])) * 0.1

};

7 ~z.fadeTime = 0.2;8 ~z = { max(SinOsc.ar(ExpRand(3, 160)), Saw.ar([304, 304.3])) * 0.1 };

Da notare infine che il fadeTime è anche utilizzato per le operazioni xset andxmap.

16.4.3 play/stop, send/free, pause/resume

Ci sono una serie di coppie di messaggi che un NodeProxy conosce e che sonorelative al play, stop, pausa, ecc.

a. play/stop

Questa coppia di messaggi è legata alla funzione di monitorizzazione del pro-xy. play avvia il monitoring, stop temina il monitoring. Se il proxy group èin stato di play (e questo può essere testato con il metodo .isPlaying), playnon influenzerà il comportament interno del proxy in alcun modo. Solo senon è in stato play (perchè per esempio si ha liberato il gruppo attraverso ilmessaggio cmd-period) esso avvia synth e oggetti nel proxy.Stop invece noninfluisce mai sullo stato interno del proxy.

1 // si prema prima cmd-period.2 ~z = { max(SinOsc.ar(ExpRand(3, 160)), Saw.ar([304, 304.3])) * 0.1 };3 ~z.play; // monitorizza il proxy4 ~z.stop; // da notare che ora il proxy sta ancora suonando, ma

solo in privato.5 ~z.isPlaying; // Il gruppo suona? si.6 ~z.monitor.isPlaying; // il monitor suona? no.

È possibile passare un argomento di volume al play per modificare il volmedel monitor senza influenzare il volume del bus interno. Questa operazione èsempre possibile e può essere effettuata direttamente tramite il metodo .vol

Page 104: SuperCollider  Tutorial

Just In Time Programming

103

1 ~z.play(vol:0.3);2 ~z.vol = 0.8;

b. send / release

Questa coppia di messaggi controlla i synth dentro il proxy. Non influiscesul monitoring. Send avvia un nuovo synth, release rilascia il synth. Send didefault rilascia l’ultimo synth.

1 // si prema prima cmd-period.2 ~z.play; // monitor. questo avvia anche il synth3 //se il gruppo non era in play4 ~z = { SinOsc.ar(ExpRand(20, 660) ! 2)5 * Saw.ar(ExpRand(200, 960) ! 2) * 0.1 };

7 ~z.release; // rilascia il synth.8 //Il fadeTime corrente viene usato per il fade

out9 ~z.send; // invia un nuovo synth.

10 // Il fadeTime corrente viene usato per il fadein

11 ~z.send; // invia un’altro synth, rilasciando quellovecchio

12 ~z.release;13 ~z.stop;14 ~z.play; // monitor. se il gruppo stava ancora suonando,15 //questo non avvia il proxy

Al fine di liberare i synth e il gruppo insieme, si può utilizzare il metodo free:

1 ~z.free; // questo non influenza il monitoring.2 ~z.play; // monitor. se il monitor non era in play, avvia il

gruppo

Se oltre a liberare i synth e il gruppo, si vuole fermare la riproduzione, si puòutilizzare il metodo end:

1 ~z.end(3); // end in 3 sec

Per ricostruire la synthdef sul server, si può riutilizzare rebuild. Questo po-trebbe aver senso quando la synthdef ha un’architettura statica, ma di sicuroutilizzare questo metodo è meno efficiente che utilizzare il metodo send.

Page 105: SuperCollider  Tutorial

Just In Time Programming

104

1 (2 ~z = {3 sum(4 SinOsc.ar(Rand(300,400) + ({exprand(1, 1.3)} ! rrand(1,

9)))5 * LFCub.ar({exprand(30, 900)} ! rrand(1, 9))6 * LFSaw.kr({exprand(1.0, 8.0)} ! rrand(1, 9)).max(0)7 * 0.18 )9 };

10 )

12 ~z.play;13 ~z.rebuild;14 ~z.send; // send crea un nuovo synth15 ~z.rebuild; // ricostruisce la synthdef16 ~z.end;

c. pause / resumeQuando viene messo in pausa, un node proxy rimane ancora attivo, ma tuttii synth che erano stati avviati sono messi in pausa fino alla ricezione di unmessaggio di resume

1 ~z.play;2 ~z.pause; // si mettono in pausa i synth3 ~z = {4 SinOsc.ar({ExpRand(300, 660)} ! 2) * 0.1 };5 // si possono aggiungere nuove funzioni in situazioni di pausa6 ~z.resume; // esce dallo stato di pausa

Da notare che pause/resume possono causare click atonali con proxy audiorare, cosa che non succede quando si mettono in pausa proxy control rate.

d. clearIl metodo clear rimuove tutti i synth, i gruppi, il monitor e rilascia i numerodi bus.

1 ~z.clear;2 ~z.bus; // no bus3 ~z.isNeutral; // non inizializzato.

Da notare che quando altri processi usano il nodeproxy, non sono notificati.Quindi, il metodo clear deve essere fatto considerando questo inconveniente.

Page 106: SuperCollider  Tutorial

Just In Time Programming

105

16.4.4 Il parametro di contesto

Utilizzando i nodeproxy è possibile modificare argomenti di una funzione. Peresempio:

1 ~y.play;2 ~y = { arg freq=500; SinOsc.ar(freq * [1, 1.1]) * 0.1 };

Ora l’argomento freq è un controllo nel synth (proprio come in SynthDef) epuò essere modificato da un messaggio set. Diversamente dall’oggetto Synth,questo valore viene conservato e applicato a tutti i nuovi synth che verrannocreati in seguito.

1 ~y.set(\freq, 440);2 ~y = { arg freq=500; Formant.ar(50, freq * [1, 1.1], 70) * 0.1 };

Con xset, una variante di set, si può effettuare un crossfade durante il cambioutilizzando il fadeTime corrente:

1 ~y.fadeTime = 3;2 ~y.xset(\freq, 600);

4 // lo stesso contesto si applica a tutti gli slot.

6 ~y[2] = { arg freq=500; SinOsc.ar(freq * [1, 1.1]) * LFPulse.kr(Rand(1,3)) * 0.1 };

7 ~y.xset(\freq, 300);

Il parametro di contesto può anche contenere il mapping del bus. Può inoltreessere mappato un controllo ad ogni proxy di controllo.

1 ~c = { MouseX.kr(300, 800, 1) };2 ~y.map(\freq, ~c);

4 // anche qui il contesto viene conservato

6 ~y = { arg freq=500; Formant.ar(4, freq * [1, 1.1], 70) * 0.1 };

xmap è una variante di map per effettuare il crossfade utilizzando il fadeTimecorrente:

Page 107: SuperCollider  Tutorial

Just In Time Programming

106

1 ~y.set(\freq, 440);2 ~y.xmap(\freq, ~c);

Infine, per rimuovere un settaggio o un mapping, si utilizzano i metodi un-map / unset.

1 ~y.unmap;

Per esempio un controllo multicanale potrebbe essere mappato ad un proxymulticanale usando mapn:

1 ~c2 = { [MouseX.kr(300, 800, 1), MouseY.kr(300, 800, 1)] };2 ~y = { arg freq=#[440, 550]; SinOsc.ar(freq) * SinOsc.ar(freq + 3) *

0.05 };3 ~y.mapn(\freq, ~c2);

Oltre ai parametri esplicitamente settati, il parametro di contesto contienel’indice del bus utilizzato e il parametro fadeTime. È possibile esaminare ilsuo contenuto con il metodo nodeMape

1 ~y.nodeMap;2 p.clear(8); // ripulisce l’intero proxy space in 8 sec.

16.5 Timing in NodeProxy

Le variazioni che avvengono in un NodeProxy, fra le quali la più importanteriguarda la sorgente, sono normalmente effettuate appena il metodo put vienerichiamato (o, in ProxySpace, dopo l’operazione di assegnamento = ). Alcunevolte è desiderabile poter temporizzare questi cambiamenti in base a un clock.Esistono 4 possibili approcci per questa temporizzazione:

a. clock

b. quant and offset

c. client and server tempo

d. sample accurate outputVediamone uno per uno, corredando di esempi ogni approccio.

Page 108: SuperCollider  Tutorial

Just In Time Programming

107

a. clock Generalmente, ogni node proxy può avere il proprio tempo base, di solitoun tempo di clock. Il clock è responsabile per la temporizzazione dell’inserimentodi nuove funzioni che avverrà di default al successivo beat del clock.

1 p = ProxySpace.push(s.boot);2 ~x.play; ~y.play;

4 // questi due synths vengono avviati al momento5 //in cui sono inseriti:6 ~x = { Ringz.ar(Impulse.ar(1), 400, 0.05) };7 ~y = { Ringz.ar(Impulse.ar(1), 600, 0.05) };

9 // aggiungiamo un clock:10 ~x.clock = TempoClock.default;11 ~y.clock = TempoClock.default;

13 // ora sono in sync14 ~x = { Ringz.ar(Impulse.ar(1), 400, 0.05) };15 ~y = { Ringz.ar(Impulse.ar(1), 600, 0.05) };

17 // per offrire un clock per l’intero proxy space:

19 p.clock = TempoClock.default;20 y = { Ringz.ar(Impulse.ar(1), 800, 0.05) };

22 ~z.play;23 ~z = { Ringz.ar(Impulse.ar(1), [500, 514], 0.8) };24 ~z = { Ringz.ar(Impulse.ar(1), exprand(300, 400 ! 2), 0.8) };25 ~z = { Ringz.ar(Impulse.ar(2), exprand(300, 3400 ! 2), 0.08) };26 ~z.end;

Sequenze di eventi:Quando si inserisce una nuova funzione dentro il proxy, viene costruita lasynthdef e inviata al server che risponde un messaggio al completamentodell’operazione. Quindi, il proxy aspetta il successivo beat per avviare il synth.Quando si usano i node proxy con i patterns, i patterns sono messi in playusando il clock come scheduler.

b. quant and offsetAl fine di essere in grado di controllare il offset/quant di inserimento, puòessere usata la variabile di istanza quant, che può essere un numero o unarray della forma [quant, offset], proprio come in pattern.play(quant).

1 ~y.quant = [1, 0.3]; // offset of 0.3, quant of 1.02 ~y = { Ringz.ar(Impulse.ar(1), 600, 0.05) };

Page 109: SuperCollider  Tutorial

Just In Time Programming

108

c. connecting client and server tempoUn ProxySpace ha il metodo makeTempoClock, che crea un’istanza diTempoBusClock insieme con un node proxy (tempo) che tiene il sync.

1 // creiamo un nuovo tempoclock con 2 beats/sec2 p.makeTempoClock(2.0);

4 ~y.quant = 1; // settiamo il quant a 1 e l’offset a 0

6 // l’impulso usa tempo7 ~y = { Ringz.ar(Impulse.ar(~tempo.kr), 600, 0.05) };8 // pattern usa tempoclock9 ~x = Pbind(\instrument, \default, \freq, Pseq([300, 400], inf));

11 p.clock.tempo = 1.0; // setta il tempo a 112 p.clock.tempo = 2.2; // setta il tempo a 2.2

14 ~x.free;15 ~y.free;

d. sample accurate outputPer un fattore di efficienza, NodeProxy usa una normale UGen Out per scri-vere sul suo bus. Se è necessario un sample rate di riproduzione accurato(OffsetOut), la variabile di classe sampleAccurate di ProxySynthDef puòessere settata a true. Vediamo un esempio per chiarire:

Page 110: SuperCollider  Tutorial

Just In Time Programming

109

1 ProxySynthDef.sampleAccurate = false;2 ~x.play;

4 // il grain libera se stesso5 ~x = { SinOsc.ar(800) * EnvGen.ar(Env.perc(0.001, 0.03, 0.4), doneAction:2)

};

7 // tono di jitter.8 (9 r = Routine {

10 loop {11 200.do { arg i;12 ~x.spawn;13 (0.005).wait;14 };15 1.wait;16 }17 }.play;18 )

20 ProxySynthDef.sampleAccurate = true;

22 // tono stabile: sample accurata.

24 ~x.rebuild;25 r.stop;26 p.clear; // rimuove il tutto

Page 111: SuperCollider  Tutorial

Esecuzione sincrona e asincrona

110

Capitolo 17 Esecuzione sincrona e asincrona

In un programma come SC che si occupa di real time, vengono introdotti uncerto numero di problemi sulla temporizzazione e l’ordine di esecuzione.La sintesi audio in tempo reale richiede che i campioni siano calcolati e suona-ti ad un certo tasso e in un certo sistema di schedule, per eliminare dropouts,glitches, ecc.; questi task sono detti sincroni in quanto devono essere coor-dinati, sincronizzati, fra loro.Altri tasks, come il caricamento di un campione in memoria, potrebbe impie-gare una quantità arbitraria di tempo, e non necessariamente limitato dentroun timeframe. ; in questo caso si può parlare di task asincroni

Nella gestione dei due tipi di task, si possono presentare problemi quando tasksincroni sono dipendenti dal completamento di uno asincrono. Per testare ciòsi provi a riprodurre un campione che potrebbe o meno essere completamentecaricato.

In SC2 tutto ciò era relativamente semplice da gestire. Si schedulavano tasksincroni durante la sintesi, (cioè dentro lo scope di un Synth.play). I taskasincroni erano eseguiti in ordine, esternamente alla sintesi. Quindi si creavaprima il buffer, vi si caricavano i campioni e la sintesi poteva cominciare.L’interprete assicurava che ogni passo veniva effettuato solo quando il passoprecedente era stato completato.

In SC3 la separazione del linguaggio e l’app server crea un problema: comefa un lato a sapere che l’altro lato ha completato i task necessari, o in altreparole, come fa la mano sinistra a sapere che la destra ha finito?!La flessibilità guadagnata dalla nuova architettura introduce nuovi livelli dicomplessità e uno sforzo maggiore dell’utente.

Un semplice modo per occuparsi di ciò è eseguire il codice in blocchi. Nelcodice seguente, per esempio, ogni blocco o linea di codice è dipendente dalfatto che la precedente sia completata.

Page 112: SuperCollider  Tutorial

Esecuzione sincrona e asincrona

111

1 // Si esegua questo codice a blocchi

3 // Boot del Server locale4 (5 s = Server.local;6 s.boot;7 )

9 // Compiliamo una SynthDef e scriviamola su disco10 (11 SynthDef("Help-SynthDef",12 { arg out=0;13 Out.ar(out, PinkNoise.ar(0.1))14 }).writeDefFile;15 )

17 // carichiamo la Synthdef18 s.loadSynthDef("Help-SynthDef");

20 // Creiamo un synth a partire dalla SynthDef21 x = Synth.new("Help-SynthDef", s);

23 // Liberiamo il nodo sul server24 x.free;

26 // Azzeriamo l’oggetto Synth lato client27 x = nil;

Nell’esempio precedente, era necessario utlizzare le variabili di interprete perpotersi riferire agli oggetti creati in blocchi precedenti, in blocchi o linee dicodice successive. Se si dichiara una variabile dentro ad un blocco di codice(i.e. var mySynth;) essa sarà persistente solo dentro lo scope dichiarato.

Questo stile di lavoro, eseguendo linee di codice o blocchi di codice alla volta,può essere molto dinamico e flessibile, e può essere abbastanza utile in unasituazione di performance dal vivo, specialmente in casi di improvvisazione,ma presenta il problema dello scope e della persistenza. Un altro modo, simile,che permette l’uso di nomi di variabili più descrittive è quello che utilizzale variabili di ambiente. Comunque, in entrambi i metodi visti, ci si deveoccupare di assicurare che oggetti e nodi non siano persistenti quando nonsono più necessari.

Page 113: SuperCollider  Tutorial

Esecuzione sincrona e asincrona

112

1 (2 SynthDef("Help-SynthDef",3 { arg out=0;4 Out.ar(out, PinkNoise.ar(0.1))5 }).send(s);6 )

8 // creiamo un Synth ed assegnamolo ad una9 // variabile d’ambiente

10 ~mysynth = Synth.new("Help-SynthDef", s);

12 // liberaimo il synth13 ~mysynth.free;

15 // ma abbiamo ancora un oggetto Synth16 ~mysynth.postln;

18 // rimuoviamo il Synth dall’ambiente19 currentEnvironment.removeAt(\mysynth);

In generale, quello che si vuole è avere un blocco di codice che contiene uncerto numero di task sincroni e asincroni. Il seguente codice causerà un errore,dal fatto che la SynthDef che serve al server non è stata ancora ricevuta.

1 // Eseguire tutto il codice insieme produce l’errore2 // ’’FAILURE /s_new SynthDef not found’’3 (4 var name;5 // usiamo un nome random per assicurarci che6 //non sia stata precedentemente caricata7 name = "Rand-SynthDef" ++ 400.0.rand;8 SynthDef(name,9 { arg out=0;

10 Out.ar(out, PinkNoise.ar(0.1))11 }).send(s);

13 Synth.new(name, s);14 )

Una prima soluzione drastica a questo problema potrebbe essere schedulare laparte di codice dipendente dall’esecuzione, dopo un ritardo sufficientementesensato usando un clock.

Page 114: SuperCollider  Tutorial

Esecuzione sincrona e asincrona

113

1 // Questo funziona se la definzione viene inviata al server prima.2 (3 var name;4 name = "Rand-SynthDef" ++ 400.0.rand;5 SynthDef(name,6 { arg out=0;7 Out.ar(out, PinkNoise.ar(0.1))8 }).send(s);

10 // creiamo un Synth dopo 0.05 secondi11 SystemClock.sched(0.05, {Synth.new(name, s);});12 )

Anche se questo metodo funziona, non è molto elegante ed efficiente. Ciòche si vorrebbe è che l’istruzione successiva venga eseguita immediatamentedopo che quella precedente sia stata completata. Per esplorare questa strada,vediamo un esempio già implementato.

Si potrebbe osservare che il primo esempio sopra non era necessariamentecomplesso. SynhtDef-play farà tutta questa compilazione, invio e creazionedi Synth in un colpo solo, alla pressione di dell’enter.

1 // Tutto insieme2 (3 SynthDef("Help-SynthDef",4 { arg out=0;5 Out.ar(out, PinkNoise.ar(0.1))6 }).play(s);7 )

Osserviamo la definizione del metodo per SynthDef-play e cosa fa.

Page 115: SuperCollider  Tutorial

Esecuzione sincrona e asincrona

114

1 play { arg target,args,addAction=\\addToTail;2 var synth, msg;3 target = target.asTarget;

5 // crea un Synth, ma non un nodo Synth6 synth = Synth.basicNew(name,target.server);

8 // crea un messaggio che aggiunge un nodo synth9 msg = synth.newMsg(target, addAction, args);

11 // invia la def, e il messaggio come un messaggio12 // di completamento13 this.send(target.server, msg);14 ^synth // restituisce l’oggetto Synth15 }

Potrebbe sembrare complicato se non si usano molto spesso le definizioni diclasse; ai nostri scopi comunque focalizziamo l’attenzione su una sola parte, lapiımportante importante, che è il secondo argomento this.send(target.server,msg);. Questo argomento è un messaggio di completamento ed è un messag-gio che il server eseguirà quando l’azione send è completata. In questo casopossiamo tradurre grossolanamente il codice in questo modo: ”crea un nodosynth sul server che corrisponde all’oggetto Synth”. Molti metodi in SC han-no la possibilità di includere messaggi di completamento. Qui possiamo usareSynthDef-send per realizzare la stessa cosa come SynthDef-play:

1 // Compila, invia, and start2 (3 SynthDef("Help-SynthDef",4 { arg out=0;5 Out.ar(out, PinkNoise.ar(0.1))6 }).send(s, ["s_new", "Help-SynthDef", x = s.nextNodeID]);7 // in ’stile messaggio’8 )9 s.sendMsg("n_free", x);

I messaggi di completamento necessitano di un messaggio OSC, ma può ancheessere una porzione di codice che, quando valutato, ne ritorna uno.

Page 116: SuperCollider  Tutorial

Esecuzione sincrona e asincrona

115

1 // Interpreta il codice e restituisce un messaggio di completamento.2 //Il .value si rende necessario3 // Questo e il precedente esempio sono essenzialmente4 //la stessa cosa di example SynthDef.play5 (6 SynthDef("Help-SynthDef",7 { arg out=0;8 Out.ar(out, PinkNoise.ar(0.1))9 }).send(s, {x = Synth.basicNew("Help-SynthDef"); x.newMsg;

}.value);10 // in ’stile oggetto’11 )12 x.free;

Se si preferisce lavorare in ’stile messaggio’, questo meccanismo è più semplice.Se invece si preferisce di lavorare in ’stile oggetto’, si possono usare comandicome newMsg, setMsg, etc. con oggetti per creare messaggi server appropriati.I due esempi precedenti ne mostrano la differenza. Si veda il file di helpNodeMessaging per maggiori dettagli.Nel caso di oggetti Buffer, può essere utilizzata una funzione come messaggiodi completamento. Verrà valutata e passata all’oggetto Buffer come argomen-to. Questo succederà dopo che l’oggetto buffer viene creato, ma prima cheil messaggio sia inviato al server. Può anche essere restituito un messaggioOSC valido per essere eseguito nel completamento.

1 (2 SynthDef("help-Buffer",{ arg out=0,bufnum;3 Out.ar( out,4 PlayBuf.ar(1,bufnum,BufRateScale.kr(bufnum))5 )6 }).load(s);

8 y = Synth.basicNew("help-Buffer"); // non ancora inviato9 b = Buffer.read(s,"sounds/a11wlk01.wav",

10 completionMessage: { arg buffer;11 // synth aggiunge il proprio msg s_new per proseguire12 // dopo il completamento della lettura del buffer13 y.newMsg(s,\addToTail,[\bufnum,buffer.bufnum])14 }); // .value NON necessario

16 )17 // quando fatto ...18 y.free;19 b.free;

Page 117: SuperCollider  Tutorial

Esecuzione sincrona e asincrona

116

Lo scopo principare dei messaggi di completamento è offrire messaggi OSCda inviare al server da eseguire immediatamente sul completamento. Nel casodi Buffer, non c’è differenza sostanziale fra i seguenti:

1 (2 b = Buffer.alloc(s, 44100,3 completionMessage: { arg buffer;4 ("bufnum:" + buffer.bufnum).postln; }5 );6 )7 // sono equivalenti:8 (9 b = Buffer.alloc;

10 ("bufnum:" + b.bufnum).postln;11 )

Si può anche valutare una funzione in risposta a un messaggio done, o qua-lunque altro messaggio, usando un OSCresponder o un OSCresponder-Node. La differenza principare fra i due è che il primo premette solo unsingolo responder per comando, mentre il secondo permette più responder.Si vedano i rispettivi helpfile.

Page 118: SuperCollider  Tutorial

Esecuzione sincrona e asincrona

117

1 (2 SynthDef("help-SendTrig",{3 SendTrig.kr(Dust.kr(1.0), 0, 0.9);4 }).send(s);

6 // registriamo per ricevere questo messaggio

8 a = OSCresponderNode(s.addr, ’/done’, { arg time, responder, msg;9 ("This is the done message for the SynthDef.send:" + [time,

responder, msg]).postln;10 }).add.removeWhenDone;11 // rimuovimi automaticamente quando fatto

13 b = OSCresponderNode(s.addr, ’/tr’, { arg time, responder, msg;14 [time, responder, msg].postln;15 }).add;

17 c = OSCresponderNode(s.addr, ’/tr’, { arg time, responder, msg;18 "this is another call".postln;19 }).add;20 )

22 x = Synth.new("help-SendTrig");23 b.remove;24 c.remove;25 x.free;

17.1 Scheduler

Vediamo in questo paragrafo la definizione dell’oggetto Scheduler al fine dicomprendere come lavora e come può essere utilizzato. Questo oggetto, cometutti quelli di SC, è sottoclasse di Object. Il suo scopo è semplicementeschedulare funzioni che saranno valutate in futuro.

Elenchiamo ora metodi e costruttori di tale oggetto:*new(clock, drift)clock: un clock, come SystemClock.drift: se true, schedulerà gli eventi relativi a Main.elapsedTime, altrimenti inbase ai secondi dello scheduler.

play(aTask)schedula il task da eseguire, con un deltatime restituito dal task.

sched(delta, aTask)

Page 119: SuperCollider  Tutorial

Esecuzione sincrona e asincrona

118

schedula il task.

advance(bySeconds)permette di avanzare il tempo di n secondi. Ogni task che è schedulatoall’interno del nuovo tempo, viene valutato e, se viene restituito un nuovotempo, rischedulato.

seconds_(newSeconds)permette di avanzare il tempo di n secondi. Ogni task che è schedulatoall’interno del nuovo tempo, viene valutato e, se viene restituito un nuovotempo, rischedulato.

isEmptyrestituisce ture se la coda di scheduling è vuota.

clearsvuota la coda di scheduling.

Vediamo un esempio:

Page 120: SuperCollider  Tutorial

Esecuzione sincrona e asincrona

119

1 a = Scheduler(SystemClock);

3 //schedula i task. Il primo parametro \‘e il delta in secondi4 a.sched(3, { "now it is 3 seconds.".postln; nil });5 .sched(5, { "now it is 5 seconds.".postln; nil });6 a.sched(1, { "now it is 1 second.".postln; nil });

8 a.advance(0.5);9 a.advance(0.5);

10 a.advance(2);11 a.advance(2);

13 // i beats, secondi and clock sono passati nella funzione task:14 a.sched(1, { arg beats, secs, clock; [beats, secs, clock].postln });15 a.advance(1);

17 // lo scheduling \‘e relativo a "ora":18 a.sched(3, { "now it was 3 seconds.".postln });19 a.sched(5, { "now it was 5 seconds.".postln });20 a.sched(1, { "now it was 1 second.".postln });

22 a.advance(0.5);23 a.advance(0.5);24 a.advance(2);25 a.advance(2);

27 // una Routine o un task:28 a.play(29 Routine {30 5.do { arg i; i.postln; 1.yield }31 }32 );33 a.advance(0.9);

35 // scheduling tasks36 (37 x = Scheduler(TempoClock.default);38 Task {39 inf.do { |i|40 ("next " ++ i ++ " in task." + Main.elapsedTime).postln;41 0.5.wait;}42 }.play(x)43 )44 x.advance(0.1);45 x.seconds;46 x.advance(5);47 x.seconds;48 (49 Routine {loop { x.advance(0.1); 0.1.wait }}.play;50 )51 (52 Task { 5.do {53 x.advance(1);54 2.0.rand.wait;}}.play;55 )

57 x.advance(8.1);

59 Pbind(\degree, Pseries(0, 2, 8), \dur, 0.25).play(x);60 (61 Task { 5.do {62 x.advance(0.20);63 1.0.wait;64 }65 }.play;66 )

Page 121: SuperCollider  Tutorial

Esecuzione sincrona e asincrona

120

17.2 OSCSched

Vediamo ora un oggetto molto utile e potente che permette di schedularel’invio di pacchetti OSC al server.Il pacchetto è tenuto sul client fino all’ultimo istante possibile, e quindi in-viato al server corredato di timestamp, appena prima che venga eseguito.I Bundles possono essere schedulati per esecuzioni precise usando una tem-porizzazione relativa o assoluta in secondi o beats. I pacchetti possono essereschedulati su più server ed essere eseguiti simultaneamente.

I Bundles possono essere facilmente creati e modificati fino al momentoin cui verranno inviati al server. Essi sono inviati al server proprio primadell’esecuzione.

La classe Tempo viene impiegata per specificare il timing e viene usata pertutti i calcoli di beats <-> secondi. Inoltre viene usato un oggetto globaleTempo di default, ma è possibile creare un’istanza specifica di Tempo per ipropri scopi.

C’è un OSCSched globale di default che può essere indirizzato attraverso imetodi delle classi; questa classe usa il SystemClock e il Tempo globale didefault. È comunque possibile crearne istanze che gestiscono le proprie codedi scheduling, tempistiche, ecc.

Il clock di default è il SystemClock, ma è possibile anche utilizzare l’AppClock.

Se specificata, una clientSideFunction opzionale, verrà valutata sul clientall’istante esatto in cui avviene la schedulazione dell’OSC bundle; potreb-be essere usata per mostrare un cambiamento nella GUI o per aggiornarequalche setting su oggetti client side.

Tutti questi metodi esistono in entrambi i modi:

1 class (lo scheduler di default globale)2 OSCSched.tsched(seconds,server,bundle,clientSideFunction)

4 metodi di istanza (uno schedule specifico).5 oscSched = OSCSched.new;6 oscSched.tsched(seconds,server,bundle,clientSideFunction)

Page 122: SuperCollider  Tutorial

Esecuzione sincrona e asincrona

121

I metodi di istanza che possono essere utilizzati sono:

tsched(seconds,server,bundle,clientSideFunction)time based schedulingdelta specified in seconds

xtsched( seconds,server,bundle,clientSideFunction)exclusive time based scheduleQualunque bundle precedentemente schedulato usando xtsched, xsched o xq-sched sarà cancellato. Si rende utile in situazioni in cui potrebbero essereinviati diversi bundle, ma si vuole che solo l’ultimo bundle sia usato comerisposta finale. Per esempio: una scimmia preme molti bottini, modificandola sua mente relativamente a quale suono suona successivamente. Questo po-trebbe risultare in molti bundle, essendo ammucchiati allo stesso temo, e ilserver potrebbe ingolfarsi provando a eseguirli tutti. Così questo forza a unaspecie di monofonia dei bundle. Un’altro esempio: un sequencer suona notein successione, schedulando ognuna quando suona la precedente. Si vorrebbepassarea a una sequenza differente senza che le note precedentemente schedu-late siano prese in considerazione. Usando uno degli x-method, ciò è possibilesenza preoccuparsi di come viene gestito il tutto.

sched(beats,server,bundle,clientSideFunction)delta specified in beats

xsched(beats,server,bundle,clientSideFunction)exclusive beat based scheduling

qsched(quantize,server,bundle,clientSideFunction)viene schedualto alla successiva division (4.0 significa il battere di un 4/4), oimmediatamente se si è precisamente sulla division.

xqsched(quantize,server,bundle,clientSideFunction)exclusive quantized beat based scheduling

tschedAbs(time,server,bundle,clientSideFunction)viene schedulato al time specificato in secondi.

schedAbs(beat,server,bundle,clientSideFunction)viene schedulato al beat specificato. Questo metodo utilizza il TempoClockper lo scheduling.

Page 123: SuperCollider  Tutorial

Esecuzione sincrona e asincrona

122

xblockblocca qualunque bundle x-schdeulato pendente.

Esistono altri metodi per ottenere valori significativi relativi al tempo di sche-duling. timerestituisce il time di scheduler.

time_(secondi)setta il time di scheduler.

beatrestituisce il current beat dello scheduler.

beat_(beat)setta il beat corrente dello scheduler. Viene utilizzato per avviare una “song”: azzera il beat e tutti i time assoluti precedentemente schedulatideltaTillNext(quantizeDivision)restituisce il numero di secondi fino alla prossima quantizeDivision.4.0 significa la prossima battuta16.0 significa le prossima 4 battute0.25 significa il 16-esimoQuesto valore è corretto solo se non viene modificato il tempo.

clearelimina tutti gli eventi schedulati.

Tutti gli x-metodi stabiliscono un blocco tale che un uno schedale seguenteche usa un altro x-metodo causerà la cancellazione del precedente. Questè èparticolarmente utile quando si controlla qualcosa da una gui o da un proces-so, e cambia la mente prima che l’evento triggerato avvenga. Un altro esempioè un pattern che ritorna valori di delat beat Schedulando ripetutamente laprossima nota all’istante di esecuzione della nota corrente. Per switchare ilpattern con un altro o avviarlo brutalmente sopra, semplicemente si facciacosì: se si usa xsched, allora l’evento schedulato precedentemente verrà cancel-lato. In molti casi, occorrerà creare un’istanza privata di OSCSched quandosi usa questa tecnica.Warning: sono esempi vecchi, non testati di recente.Caricare tutti questi per usarli nel prossimo esempio

Page 124: SuperCollider  Tutorial

Esecuzione sincrona e asincrona

123

1 s = Server.local;2 s.boot;3 (4 SynthDef("bubbles", {5 var f, zout;6 f = LFSaw.kr(0.4, 0, 24,7 LFSaw.kr([8,7.23], 0, 3, 80)8 ).midicps;9 zout = CombN.ar(SinOsc.ar(f, 0, 0.04), 0.2, 0.2, 4);

10 Out.ar(0, zout);11 }).send(s);

13 i = [ ’/s_new’, "bubbles", 1002, 1, 0 ];14 o = [ ’/n_free’, 1002 ];15 c = OSCSched.new;16 )

18 // il time dello scheduler = numero di secondi dall’avvio di SC19 c.time.postln;

21 // di defautl 1.0 beats per second22 Tempo.tempo.postln;

24 // numero di beat dall’avvio di SC25 c.beat.postln;

27 // settiamo il default global Tempo28 Tempo.bpm = 96;

30 c.beat.postln;31 c.beat = 0.0;32 c.beat.postln;

34 // "starting" in 2.0 beats35 c.sched(2.0,s,i,{36 "starting".postln;37 });

39 //libera il synth nella prossima battuta40 c.qsched(4.0,s,o,{41 c.beat.postln;42 });

44 // avvia in 4.0 secondi45 c.tsched(4.0,s,i,{46 "starting".postln;47 });

Page 125: SuperCollider  Tutorial

Esecuzione sincrona e asincrona

124

Absolute Beat / Time scheduling

1 c.clear;

3 (4 c.beat = 32.0; // we are starting at beat 325 c.schedAbs(36.0,s,i); // in6 c.schedAbs(39.0,s,o); // out7 c.schedAbs(41.0,s,o); // out8 c.schedAbs(40.0,s,i); // but first in9 c.schedAbs(43.0,s,i,{

10 c.schedAbs(42.0,s,o,{11 "this will never happen, its in the past".postln;12 });13 c.schedAbs(46.0,s,o);14 });15 )16 \stocode17 Exclusive18 \startcode19 (20 c.xsched(4.0,s,i,{21 "4.0".postln;22 });23 c.sched(8.0,s,o); // not affected24 // changed my mind...25 c.xsched(3.0,s,i,{ // the x-methods are exclusive26 "3.0".postln;27 });28 )

17.3 Task

superclass: PauseStream Il Task di Supercollider è un processo che può esse-re sospeso; è implementato dal wrapping di un PauseStream in una Routine.Molti dei suoi metodi (start, stop, reset) sono quindi ereditati da Pause-Stream.

Task.new(func, clock)func - una Function da valutare.clock - Un Clock in cui eseguire la Routine. Se non si passa un Clock, vieneconsiderato di default un’istanza di TempoClock. I metodi che richiamano leprimitive di Cocoa (per esempio funzioni di GUI) devono essere eseguite inAppClock.

Page 126: SuperCollider  Tutorial

Esecuzione sincrona e asincrona

125

1 t = Task({2 50.do({ arg i;3 i.squared.postln;4 0.5.wait5 });6 });

8 t.start;9 t.pause;

10 t.resume;11 t.reset;12 t.stop;

17.4 TempoQuesta classe rappresenta il concetto di tempo. Può essere impiegata per tra-sformare il tempo in secondi, beats e bars. Questa classe detiene un’istanzadi TempoClock che viene settato al proprio tempo ogni volta che cambia.

Può essere impiegato un TempoBus avviato sul server, e conterrà il tem-po dell’oggetto Tempo inteso come un valore floats su un Bus sul server. LeUGens possono usarlo per scalare la loro frequenza per basi ritmiche, ecc.

Può essere anche impiegato per convertire beats <-> secondi, ma questovalore è accurato solo nel momento della computazione. Se il tempo varia, ilvalore non è più valido. TempoBus aggiunge se stesso come un dipendentedell’oggetto Tempo, così quando il tempo cambia, è informato e aggiorna ilvalore coerentemente sul bus.

1 Tempo.bpm = 170;2 Tempo.tempo = 2.3; // in beats per second

4 Tempo.gui; // there is a gui class

6 Tempo.bpm = 170;7 Tempo.beats2secs(4.0).postln;

9 Tempo.bpm = 10;10 Tempo.beats2secs(4.0).postln;

Tutte i metodi della classe si riferiscono al tempo globale di default. È possi-bile creare un’istanza di Tempo se si rendono necessari tempi separati indi-viduali.

Page 127: SuperCollider  Tutorial

Esecuzione sincrona e asincrona

126

1 t = Tempo.new;2 u = Tempo.new;

4 t.bpm = 170;5 u.tempo = 1.3; // in beats per second6 t.gui;

Tutti i metodi seguenti esistono come metodi di classe (il tempo di default)e come metodi di istanze. bpm

bpm_(beatsPerMinute)Tempo.bpm = 96; o Tempo.bpm_(96);

tempoin beats per second

tempo_(beatsPerSecond)Tempo.tempo = 2.0; o Tempo.tempo_(2.0);

beats2secs(beats)

secs2beats(seconds)

bars2secs(bars)È possibile cambiare il beats per bar: Tempo.beatsPerBar = 7.0;

secs2bars(seconds)

sched(delta,function)

Schedula una funzione che viene valuta delta beats da ora.Se si cambia il tempo dopo lo scheduling, la funzione sarà ancora valutata altempo originalmente calcolato. Una soluzione più sofisticata verrà presentatain seguito.schedAbs(beat,function)Schedula una funzione che viene valutata ad un beat assoluto, come misuratoprima dell’istante in cui avviene il primo boot di SC. Se si utilizzano OSC-Sched per controlli più sofisticati (in grado di resettare il beat).Se si cambia il

Page 128: SuperCollider  Tutorial

Esecuzione sincrona e asincrona

127

tempo dopo lo scheduling, la funzione verrà ancora valutata al tempo calco-lato originariamente. Una soluzione più sofisticata sarà presentata in seguito.

17.5 Routine

Superclass: ThreadLe Routine sono funzioni che possono ritornare nel mezzo dell’esecuzione equindi essere riesumate dove sono state lasciate quando vengono richiamatenuovamente. Le Routine possono essere usate per implementare co-routines,tipiche di moltri linguaggi.Le Routine sono inoltre utilizzate per scrivere cose che si comportano comeStreams.Infine, essere ereditano comportamenti per operazioni matematiche e filtraggida Stream.

*new(func, stackSize, seed)

Crea un’istanza di Routine con la data funzione passata come argomento.Gli argomenti stackSize e random seed possono essere sovrascritti se lo sidesidera.

1 (2 a = Routine.new({ 1.yield; 2.yield; });3 a.next.postln;4 a.next.postln;5 a.next.postln;6 )

value(inval)resume(inval)next(inval)Questi sono tutti sinonimi per lo stesso metodo.

La funzione Routine è sia avviata se non è ancora stata chiamata, sia se vieneremutata da dove era stata lasciata. L’argomento inval viene passato comeargomento alla Routine se è stata avviata, o come risultato del yeld metodose è stata resumata da un yeld.Ci sono di base 2 condizioni per una Routine:

Page 129: SuperCollider  Tutorial

Esecuzione sincrona e asincrona

128

1. quando la Routine parte.

2. quando la Routine continua dopo che ha yeldatoQuando la Routine parte (richiamando i metodi precedente), viene passatoun primo inval. Questo inval è accessibile come argomento della funzioneRoutine:

1 (2 Routine { arg inval;3 inval.postln;4 }.value("hello routine");5 )

Quando c’è un yield nella routine, la volta successiva si richiama next (oun sinonimo) e la routine continua da lì, e si considera la possiblità chevenga restituito un valore all’esterno della routine. Per accedere ai quei valoridentro la continuazione della routine, si assegna il result of the yield call auna variabile:

1 (2 r = Routine { arg inval;3 var valuePassedInbyYield;4 inval.postln;5 valuePassedInbyYield = 123.yield;6 valuePassedInbyYield.postln;

8 }9 )

11 r.value("hello routine");12 r.value("goodbye world");

Tipicamente il nome inval (o inevent) è riutilizzato, invece di dichiarare unavariabile come “valuePassedInbyYield” come si può vedere dall’esempio suc-cessivo:

1 (2 r = Routine { arg inval;3 inval.postln;4 inval = 123.yield;5 inval.postln;6 }7 )8 r.value("hello routine");9 r.value("goodbye world");

Page 130: SuperCollider  Tutorial

Esecuzione sincrona e asincrona

129

Tipicamente una routine usa un yeld multiplo, in cui l’inval viene riassegnatoripetutamente:

1 (2 r = Routine { arg inval;3 inval.postln;4 5.do { arg i;5 inval = (i + 10).yield;6 inval.postln;7 }8 }9 )

10 (11 5.do {12 r.value("hello routine").postln;13 }14 )

reset

Forza il restart dall’inizio la prossima volta che viene richiamata la Routine.Una Routine non può resettare se stessa se non tramite il metodo yieldAn-dReset.play(clock, quant)clock: un Clock, di default TempoClockquant: un numero n (quantizzazione a n beats) oppure un array [n, m] (quan-tizzazione a n beats, con offset m)

Nelle applicazioni SuperCollider, una Routine può essere utilizzata insieme aun Clock, come si fa con gli Stream. Tutte le volte che la routine opera unayeld, dovrà farlo con un float; usualmente verrà messa in pausa per i secondistabiliti, e quindi verrà resumata la routine, passando il tempo di clock defi-nito.

17.5.1 Variabili di istanza accessibili

Routine eredita da Thread, e permette accesso ad alcuni dei suoi stati:

Page 131: SuperCollider  Tutorial

Esecuzione sincrona e asincrona

130

− beats restituisce i beat trascorsi della routine. Se la routine è ferma questovalore non viene aggiornato.

− seconds restituisce i secondi trascorsi della routine. Se la routine è fermaquesto valore non viene aggiornato.

− clock restituisce il clock del thread. Se non sta girando, il valore è quellodel SystemClock.

1 (2 r = Routine { arg inval;3 loop {4 // thisThread refers to the routine.5 postf("beats: % seconds: % time: % \n",6 thisThread.beats, thisThread.seconds, Main.elapsedTime7 );8 1.0.yield;

10 }11 }.play;12 )

14 r.stop;15 r.beats;16 r.seconds;17 r.clock;

Esempio routine:

Page 132: SuperCollider  Tutorial

Esecuzione sincrona e asincrona

131

1 (2 var r, outval;3 r = Routine.new({ arg inval;4 ("->inval was " ++ inval).postln;5 inval = 1.yield;6 ("->inval was " ++ inval).postln;7 inval = 2.yield;8 ("->inval was " ++ inval).postln;9 inval = 99.yield;

10 });

12 outval = r.next(’a’);13 ("<-outval was " ++ outval).postln;14 outval = r.next(’b’);15 ("<-outval was " ++ outval).postln;16 r.reset; "reset".postln;17 outval = r.next(’c’);18 ("<-outval was " ++ outval).postln;19 outval = r.next(’d’);20 ("<-outval was " ++ outval).postln;21 outval = r.next(’e’);22 ("<-outval was " ++ outval).postln;23 outval = r.next(’f’);24 ("<-outval was " ++ outval).postln;25 )

27 (28 var r;29 r = Routine.new({30 10.do({ arg a;31 a.postln;32 // Often you might see Wait being used to pause a routine33 // This waits for one second between each number34 1.wait;35 });36 // Wait half second before saying we’re done37 0.5.wait;38 "done".postln;39 });

41 SystemClock.play(r);42 )

Page 133: SuperCollider  Tutorial

Scrivere classi in SC

132

Capitolo 18 Scrivere classi in SC

18.1 Oggetti e Messaggi

Il linguaggio di SuperCollider è un linguaggio a oggetti. Tutte le entità nellinguaggio sono oggetti. Un oggetto è qualcosa che contiene dei dati che rap-presentano lo stato dell’oggetto e un insieme di operazioni che possono esserecalcolate sull’oggetto. Tutti gli oggetti sono instanze di qualche classe chedescrive la struttura dell’oggetto e le sue operaioni. In SC sono anche oggettii numeri, stringhe di caratteri, collezioni di oggetti, ugens, campioni wavempunti, rettangoli, finestre grafiche, bottoni, spider e molte altre cose.

Le operazioni sugli oggetti possono essere invocate tramite messaggi. Un mes-sage è un richiesta per un oggetto, detto receiver, di effettuare una delle sueoperazioni. Questo significa che quale operazione calcolare viene determina-ta dalla classe dell’oggetto. Oggetti di differenti classi possono implemen-tare lo stesso messaggio in differenti modi, ognuno appropriato alla classedell’oggetto. Per esempio tutti gli oggetti capiscono il messaggio value. Moltioggetti semplicemente ritornano se stessi in risposta a value, ma altri oggetti,come fossero funzioni, prima valutano se stessi e poi rispondono il risultatodi questa valutazione. L’abilità di oggetti differenti di reagire differentementeallo stesso messaggio è conosciuta come polymorphism ed è probabilmente ilconcetto più importante in OOP dal fatto che permette di astrarre il com-portamento degli oggetti dal punto di vista del user dell’oggetto (il client).

L’insieme di messagi che un oggetto conosce e a cui risponde è detto esserela sua interface. Un insieme di messaggi che implementano uno speciale tipodi comportamento è conosciuto come protocol. Un’interfaccia oggetto po-trebbe includere diversi protocolli che permettono allìoggetto di interagire indiversi contesti. Per esempio tutti gli oggetti implementano il protocollo ’de-pendancy’che permette all’oggetto di notificare agli oggetti dipendendti cheha subito cambiamenti e che i suoi dipendenti devono assolutamente aggior-narsi. Uno stato interno di un oggetto potrebbe sono essere variato dall’inviodi messaggi. Questo permette che l’implementazione dell’oggetto sia nasco-sta agli occhi del client. Il vantaggio di tutto ciò è che il client non dipendedall’implementazione dell’oggetto e che l’implementazione può essere modifi-cata senza dover modificare il client.

Page 134: SuperCollider  Tutorial

Scrivere classi in SC

133

18.2 Classi, Variabili di instanza e metodiUn class di oggetti contiene la descrizione dei dati dell’oggetto e le operazio-ni. Una calle descrive inoltre come creare un oggetto che è un’istanza dellaclasse.

Un dato di un oggetto è contenuto nella sua instance variables. Queste sonovariabili che descrivono lo stato dell’oggetto. I valori delle variabili di istanzasono essistessi oggetti. Per esempio, le istanze della classe Point hanno varia-bili di istanza x e y che contengono le coordinate di Point. Una variabile diistanza è accessibile solo da dentro la classe stessa.ò L’autore di una classepotrebbe decide di permettere l’accesso per il client alle sue variabili di istan-za aggiungendo messaggi di getter e/o setter.

Un metodo è una descrizione delle operazioni necessarie per implementare unmessaggio di una particolare classe. I metodi nella classe dicono come imple-mentare i mesaggi inviati alle istanze. Una classe contiene una definizione dimetodo per ogni messaggio a cui la sua istanza risponde. I metodi general-mente possono ricadere in diverse categorie. Alcuni metodi rispondono conalcune proprietà del ricevitore. Altri richiedono al ricevitore di modificare lostato interno. Altre ancora potrebbero richiedere al ricevitore di restituire ilvalore di qualche computazione.

18.2.1 ClassiTutti gli oggetti in SC sono membri di una classe che descrive dati e inter-faccia degli oggetti stessi. Il nome di una classe deve iniziare con una letteramaiuscola. I nomi delle classi sono gli unici valori globali in SCLang. Dalmomento che le classi sono esse stesse oggetti, il valore di un indentificatoredi nome di classe è l’oggetto che rappresenta la classe stessa.

18.2.2 EreditarietàPer specificare l’ereditarietà di una classe da un’altra, si segue la sintassi:

1 NewClass : SomeSuperclass {

3 }

Se non si specifica una superclasse, viene assunta di default la superclasseObject.

Page 135: SuperCollider  Tutorial

Scrivere classi in SC

134

1 NewClass { // : Object \‘e implicito

3 }

18.2.3 Variabili di istanzaI dati di un oggetto sono contenuti nelle sue variabili di istanza; possono es-sere di due tipi, named e indexed. Ogni oggetto contiene una copia separatadelle proprie variabili di istanza.

Alcune istanze non hanno variabili di istanza, ma hanno un valore atomico.Classi di questo tipo sono Integer, Float, Symbol, True, False, Nil, Infinitum,Char, Color.

La lista delle dichiarazioni delle variabili d’istanza appare dopo la parentesigraffa aperta della definizione della classe; esse sono precedute dalla parolariservata var. Le variabili di istanza in questa lista possono essere inizializzatea un letterale di default usando il segno dell’uguale. Le variabili di istanza chenon sono esplicitamente inizializzate, saranno inizializzate automaticamentea nil.

Le variabili di istanza possono essere lette e cambiate direttamente dai metodipropri della classe, ma ugualmente dai client è possibile accedervi aggiungen-do, nella classe, messaggi detti di getter e setter nella classe.

− Un messaggio getter è un messaggio con lo stesso nome della variabile erestituisce il valore della variabile stessa quando inviata al ricevente.

− Un messaggio setter è un messaggio con lo stesso nome della variabileseguita da un carattere di underscore _ e imposta il valore della variabiledi istanza al valore passato come argomento del messaggio stesso.

− I metodi getter e setter possono essere definiti nella dichiarazione dellavariabile di istanza.

Inoltre:

− Per una variabile di istanza, anteponendo semplicemente un simbolo diminore < al nome della variabile, viene creato automaticamente un mes-saggio getter del tipo someObject.getMe;

− Per una variabile di istanza, anteponendo semplicemente un simbolo dimaggiore > al nome della variabile, viene creato automaticamente unmessaggio setter del tipo someObject.setMe_(value);

Page 136: SuperCollider  Tutorial

Scrivere classi in SC

135

In generale nulla vieta di utilizzare il meccanismo dell’override dei metodigetter e setter in una sottoclasse scrivendo manualmente i metodi; l’unicacondizione da tenere in considerazione è che il metodo setter abbia un soloargomento, del tipo:

1 variable_ { arg newValue;2 variable = newValue.clip(minval,maxval);3 }

Nel caso compaiano entrambi i simboli, l’ordine corretta da seguire è <>

1 var <getMe, >setMe, <>getMeOrSetMe;

3 In generale:

5 var a, <b, >c, <>d;6 // a non ha metodi getter o setter.7 // b ha un getter, ma non un setter.8 // c ha un setter, ma non un getter.9 // d ha entrambi i metodi getter e setter.

Vediamo un esempio:

Page 137: SuperCollider  Tutorial

Scrivere classi in SC

136

1 Point {2 // x e y sono variabili di instanza che hanno entrambi metodi getter e

setter.3 var <>x = 0, <>y = 0;4 ...5 }

7 p = Point.new;8 p.x_(5); // invia un messaggio setter per settare x a 59 p.y_(7); // invia un messaggio setter per settare y a 7

10 p.x = 5; // invia un messaggio setter usando l’assegnamento ([03Assignement]

11 p.y = 7; // invia un messaggio setter usando l’assegnamento ([03Assignement]

12 a = p.x; // invia un messaggio getter per x13 b = p.y; // invia un messaggio getter per y

15 Quindi, in una classe:

17 NewClass : Superclass {

19 var myVariable;

21 variable { //22 ^variable // ? <variabile23 } //

25 variable_ { arg newValue; //26 variable = newValue; //? >variabile27 } //28 }

18.2.4 Variabili di ClasseLe variabili di classe sono valori che vengono condivisi fra tutti gli oggettinella classe. La lista di dichiarazione delle variabili di classe è preceduta dallaparola chiave classvar e potrebbe essere interavallata da liste di dichiarazionidi variabili di istanza. Per le variabili di classe, come per le variabili di istanza,si potrebbe avere accesso solo tramite metodi della classe, ma è possibile avereanche metodi getter e setter dello stesso tipo definito precedentemente per levariabili di istanza.

18.2.5 Metodi di istanzaI messaggi di un’interfaccia di una classe sono implementati nei metodi dellaclasse. Quando viene inviato un messaggio a un oggetto, viene eseguito il

Page 138: SuperCollider  Tutorial

Scrivere classi in SC

137

metodo il cui nome corrisponde al messaggio. La definizione dei metodi segue,nell’ordine, la dichiarazione delle variabili di classe e le variabili di istanza.

Le definizioni dei metodi sono simili per sintassi alle FunctionDefs: inizianocon il selettore del messaggio, che deve essere un operatore binario o unidentificatore. I metodi possono avere argomenti e dichiarazioni di variabilial loro intermo come per le FunctionDefs. I metodi, comunque hanno unprimo argomento implicito, this, che è il ricevente del messaggio. La variabilethis potrebbe essere riferita a qualunque altro metodo variabile del metodo.E’possibile anche non assegnare un valore a this. In generale dentro il metododi istanza, la parola chiave this si riferisce all’istanza.

In generale questi metodi di istanza sono specificati nel seguento modo:

1 instanceMethod { arg argument;

3 }

Per avere un valore di ritorno dal metodi si usa il carattere ^.

1 someMethod {2 ^returnObject3 }

Sono possibili più punti di uscita:

1 someMethod { arg aBoolean;2 if(aBoolean,{3 ^someObject4 },{5 ^someOtherObject6 })7 }

Se non viene specificato , il metodo restituirà l’istanza (e nel caso dei metodidi classe, restituirà la classe).

18.2.6 Metodi di classeI metodi della classe sono metodi che implementano messaggio che vengonoinviati a un oggetto della classe. Un esempio comune è il messaggio new che

Page 139: SuperCollider  Tutorial

Scrivere classi in SC

138

viene inviato all’oggetto per creare una nuova istanza della classe. I nomi deimetodi di classe sono preceduti, nella definizione, da un asterisco:

1 *classMethod { arg argument;

3 }

In generale dentro il metodo, la parola chiave this si riferisce alla classe.

18.3 Creazione di una nuova istanza

Il costrutto Object.new restituisce, com’è intuibile un nuovo Oggero. Quandosi utilizza l’override, il metodo di classe .new richiama la superclasse, chea sua volta richiama la sua superclasse, risalendo l’albero fino ad Object; aquesto punto viene creato un oggetto e allocata la memoria necessaria.

1 // questo esempio non aggiunge funzionalit\‘a2 *new {3 ^super.new4 }

6 // questo \‘e un normale costruttore7 *new { arg arga,argb,argc;8 ^super.new.init(arga,argb,argc)9 }

10 init { arg arga,argb,argc;11 // do initiation here12 }

In questi casi, si noti che super.new richiama il metodo new della superclassee restituisce un oggetto nuovo. In sequenza viene richiamato il metodo diistanza .init sull’oggetto appena creato.

Attenzione: Se la superclasse richiama a sua volta super.new.init, ci si aspettache venga richiamato il metodo .init della superclasse; invece il messaggio .inittroverà l’implementazione della classe dell’oggetto appena creato che è ora lanuova sottoclasse. Così occore usare un nome di metodo unico, come peresempio “myclassinit” se potrebbe essere un problema tutto questo.

Page 140: SuperCollider  Tutorial

Scrivere classi in SC

139

18.4 Overriding dei metodi (Overloading)

Per modificare il comportamento della superclasse, vengono spesso sovrascrit-ti i suoi metodi. Da notare che un oggetto verifica prima la propria imple-mentazione del metodo e, successivamente l’implementazione nell superclasse.Qui NewClass.value(2) restituisce 6, non 4:

1 Superclass {

3 calculate { arg in; in * 2 }4 value { arg in; ^this.calculate(in) }5 }

7 NewClass : Superclass {

9 calculate { arg in; in * 3 }10 }

Nel caso sia necessario il metodo della superclasse e la sua implementazione,può essere invocato utilizzato la parola chiava super.

1 Superclass {2 var x;

4 init {5 x = 5;6 }7 }

9 NewClass : Superclass {10 var y;11 init {12 super.init;13 y = 6;14 }15 }

18.5 Metodi definiti in file esterni

I metodi possono essere aggiunti in classi in file separati. Per convenzione, ilnome del file comincia con una lettera minuscola; il nome del metodo o dellafeature che i metodi supportano.

Page 141: SuperCollider  Tutorial

Scrivere classi in SC

140

1 Syntax:

3 + Class {4 newMethod {

6 }7 *newClassMethod {

9 }10 }

18.6 Tricks and Traps

"Superclass not found..."

In un dato file di codice, è possibile solo inserire classi che ereditano daogni Object, ogni altro e una classe esterna. In altre parole, non è possibileereditare da due classi separate che sono definite in file separati. Se si vuo-le dichiarare una variabile in una subclasse, e usare lo stesso nome di unavariabile dichiarata in una superclasse, occorrerà verificare che entrambe levariabili esistano, ma solo una nella classe attuale è accessibile. E’meglio nonfarlo! Porterà da qualche parte ad avere qualche errore di compilazione.

Page 142: SuperCollider  Tutorial

Glossario

141

Capitolo 19 Glossario

• buffer - un header e un array di sample espressi in floating poinf. I buffersono usati per i file sonoi, le linee di ritaro, array di controlli globali.

• group - una linked list di nodi. I Gruppi forniscono modi per controlla-re l’esecuzione di molti node in un colpo solo. Un gruppo è un tipo di nodo.

• MIDI - un protocollo per inviare dati di controllo fra synth.

• node - un oggetto in un albero di oggetti eseguito dal synth engine delserver eseguito in una DFT. Ci sono due tipi di nodi: synths e gruppi.

• Open Sound Control - un protocollo definito da CNMAT a UCBerkeleyper il controllo dei sintetizzatori. Si veda: http://cnmat.cnmat.berkeley.edu/OSC/.

• OSC - Open Sound Control.

• synth - modulo sonoro. Simile a "voice" in altri sistemi. I Synths sonoindicati da un numero.

• synth definition - una definizione per creare nuovi synths, simile a "in-strument" in altri sistemi.

• TCP - un protocollo di rete per lo streaming dei dati.

• UDP - un protocol di rete per l’invio di datagrammi.