Sei sulla pagina 1di 142

SuperCollider

by Paolo Olocco

SuperCollider by Paolo Olocco Personali traduzione dei tutorial Gettin-Started ed altri presenti nella documentazione di

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: olocco.paolo@gmail.com

Indice

1

Primi Passi

4

1.1

Hello World, sono SuperCollider

4

1.2

Il mondo dentro SuperCollider

6

2

Accendiamo i motori

8

3

Funzioni e Funzionalità

10

4

Funzioni e Suoni

16

4.1

Divertiamoci con Funzioni e UGens

19

5

Living Stereo

21

6

Creiamo un Mix

24

7

Scoping and Plots

26

7.1

Scoping su richiesta

27

7.2

Local vs. Internal

28

8

Help di Supercollider

29

8.1

Classi and Metodi

29

8.2

Shortcut sintattici

30

8.3

Snooping, etc.

30

9

SuperCollider 3 Synth Server: architettura

31

9.1

Introduzione

31

9.2

Principali componenti

31

10

SynthDefs e Synths

35

10.1

SynthDef

35

10.2

SynthDefs vs. Functions

36

10.3

SynthDefs ‘variabili’

39

10.4

Synth

40

10.5

Alcune nozioni su Symbols, Strings, SynthDef e Arg Names

40

11

I Buss

42

11.1

Buss: scrittura e lettura

43

11.2

Creiamo un Bus Object

44

11.3

Buss in azione!

46

11.4

Divertiamoci con i Control Buss

49

11.5

L’ordine è una cosa importante!

50

1

12

I Gruppi

53

12.1

Gruppi come tool per l’ordinamento

53

12.2

Tutte le addAction

55

12.3

’queryAllNodes’e node IDs

55

12.4

Il Root Node

56

12.5

Il default Group

57

12.6

Gruppi come tool per l’invio di insiemi di messaggi

59

12.7

Gruppi: ereditarietà e non solo

59

13

Buffer

61

13.1

Creazione di un Buffer Object e allocazione di memoria

61

13.2

Uso dei Buffers con Sound Files

62

13.3

Streaming di File da e su Disco

63

13.4

OOP: Variabili di istanza e Funzioni azione

64

13.5

Registrare nel Buffer

65

13.6

Accesso ai dati

66

13.7

Plotting e Playing

68

13.8

Altri usi dei Buffers

68

14

La comunicazione

70

14.1

Impacchettamento automatico dei messaggi

72

15

Ordine di esecuzione

74

15.1

Richiami su Server e Target

74

15.2

Controllo dell’ordine di esecuzione

75

15.3

Utilizzare l’ordine di esecuzione a proprio vantaggio

77

15.4

Stile Messaggio

80

15.5

Un caso particolare: Feedback

80

15.6

Espansione multicanale

82

15.7

Protezione di array dall’espansione

84

15.8

Ridurre l’espansione multicanale con Mix

85

15.9

Uso di flop per l’espansione multicanale

86

16

Just In Time Programming

88

16.1

Overview delle differenti classi e tecniche

88

16.2

Placeholder in supercollider

89

16.3

Proxy Space: concetti base

93

16.4

Struttura interna di un node Proxy

100

16.4.1

NodeProxy slots

100

16.4.2

fade time

101

16.4.3

play/stop, send/free, pause/resume

102

16.4.4

Il parametro di contesto

105

2

16.5

Timing in NodeProxy

106

17

Esecuzione sincrona e asincrona

110

17.1

Scheduler

117

17.2

OSCSched

120

17.3

Task

124

17.4

Tempo

125

17.5

Routine

127

17.5.1

Variabili di istanza accessibili

129

18

Scrivere classi in SC

132

18.1

Oggetti e Messaggi

132

18.2

Classi, Variabili di instanza e metodi

133

18.2.1

Classi

133

18.2.2

Ereditarietà

133

18.2.3

Variabili di istanza

134

18.2.4

Variabili di Classe

136

18.2.5

Metodi di istanza

136

18.2.6

Metodi di classe

137

18.3

Creazione di una nuova istanza

138

18.4

Overriding dei metodi (Overloading)

139

18.5

Metodi definiti in file esterni

139

18.6

Tricks and Traps

140

19

Glossario

141

3

Primi Passi

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 questo programma è stampare il testo “Hello World!” nella finestra di output chiamata, in SuperCollider, post window che si apre all’avvio del programma, e in cui compaiono una serie di righe di testo del tipo:

1

init_OSC

2

compiling class library

3

NumPrimitives = 587

4

compiling dir: ’/Applications/SC3/SCClassLibrary’

5

pass 1 done

6

Method Table Size 3764776 bytes

7

Number of Method Selectors 3184

8

Number of Classes 1814

9

Number of Symbols 7595

10

Byte Code Size 180973

11

compiled 296 files in 1.34 seconds

12

compile done

13

RESULT = 256

14

Class tree inited in 0.14 seconds

Non è il caso di preoccuparsi molto ora del significato inerente questo testo, basta tenere 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 listato qui sotto:

1

"Hello World!".postln;

Per eseguire il programma, occorre semplicemente posizionare il cursore sulla stessa riga 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!

4

Primi Passi

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 un

testo, oppure un oscillatore che permette di controllare e inviare messaggi. Di tutto

questo 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 metodo

postln, è il miglior amico del programmatore in SC. Può essere applicato a quasi tutto in SC e restituisce informazioni rilevanti. Può essere veramente utile quando si cerca un bug nel codice. Osservano il risultato ottenuto viene spostaneo domandarsi perchè viene stampato due volte il risultato. Quando si esegue codice in SC, viene sempre postata l’ultima istruzione eseguita; in questo caso non occorreva la postln. Ma da qualche parte bisognava pur cominciare, no?! Nell’esempio seguente selezioniamo entrambe le linee di testo cliccandoci sopra

e 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 non comparisse 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 codice semplicemente facendo doppio click dentro le parentesi. Si provi con il seguente esempio:

1

(

2

"Call me,".postln;

3

"Ishmael.".postln;

4

)

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

5

Primi Passi

1

 

(

2

"Call me?".postln

 

3

"Ishmael.".postln;

4

)

Eseguendo infatti il codice sopra riportato, si ottiene un Parse Error. Con un errore

di

questo tipo, il ě nel messaggio di errore mostra dove SC rileva un errore. Qui

succede appena dopo “Ishmael.”.

1

ě ERROR: Parse error

 

2

 

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 nella stessa 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ò essere

altrettanto utile nasconderla dietro le altre finestre. Può essere portata in avanti in

ogni 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 e un sofisticato linguaggio di programmazione e può essere un’applicazione a riga di comandi 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

6

Primi Passi

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) e che essi siano connessi a internet (anche se è possibile avere clients e servers in parti differenti 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’utilizzo di messaggi OSC, e fortunatamente il linguaggio di Supercollider dispone di alcuni oggetti potenti che rappresentano diverse entità sul server e permettono di control- larle facilmente ed elegantemente. Capire esattamente come lavorano è cruciale per dominare 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]

7

Accendiamo i motori

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 effettuare questa 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 a sinistra nello schermo. La finestra del server localhost si presenta in questo modo:

finestra del server localhost si presenta in questo modo: Fig. 2.1 Server localhost window Il termine

Fig. 2.1 Server localhost window

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

o due dovrebbe comparire qualcosa di simile a questo: Fig. 2.2 Server localhost window Da notare

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 inoltre alcune 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)

8

Accendiamo i motori

Goups : numero di group correntemente istanziati. All’avvio sono 2, il root node e 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 generato alcune informazioni sull’esito positivo del boot:

UseSeparateIO?: 0

1 booting 57110

2 SC_AudioDriver: numSamples=512, sampleRate=44100.000000

3 start

4 PublishPortToRendezvous 0 57110

5 SuperCollider 3 server ready

6 notification is on

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

mandare messaggi di start e stop al server come i seguenti: 1 s.quit; 2 s.boot; Proviamo

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 che

il

server sia in stato running prima di utilizzare qualunque esempio che genera audio

o

che accede a funzioni del server. In generale per gli esempi in questo tutorial si

assume che il server sia in stato running. Ci si può anche riferire al server localhost con il testo Server.local, per esempio:

al server localhost con il testo Server.local , per esempio: 1 Server.local.boot; Per maggiori informazioni: [Server]

1 Server.local.boot;

Per maggiori informazioni:

[Server]

9

Funzioni e Funzionalità

Capitolo 3 Funzioni e Funzionalità

Il modo più semplice per generare suoni in SC è utilizzare una Function. Di seguito ne viene mostrato un semplice esempio. Si provi ad eseguire il codice (dopo essersi accertati che il server sia in stato running), e dopo poco si prema Cmd-. ( tener premuto 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 futuro per 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 Functions

e

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 racchiudendo

il

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 la Function sarà riutilizzata o rivalutata. Da notare che è definita come un’equazione, f

=

anche se non lo è nel vero senso matematico. Questa operazione è ciò che viene

,

detto essere un assegnamento. Di base un assegnamento permette di nominare la Function 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 la post window,

1

 

f

= { "Function evaluated".postln; };

2

f;

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

10

Funzioni e Funzionalità

Come è quindi possibile riusare una Function? Si eseguano le seguenti linee una alla volta 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 .value valuta la funzione in quel momento e ne restituise il valore. Questa operazione è un esempio di invio di un messaggio a un oggetto. Segue la sintassi 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 e quindi 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 loro utilizzo.

Un buon esempio di questo principio è proprio il metodo value. Tutti gli oggetti in 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 il metodo value su una Function, quest’ultima verrà valutata e ritornerà il risultato dell’ultima riga di codice della Function. Si veda l’esempio seguente: ritornerà un valore 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 special modo 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 significa semplicemente che SC ignorerà ciò che viene scritto durante il parse e l’escuzione).

11

Funzioni e Funzionalità

1

 

f

=

3;

// f viene inizializzata a un numero // nella Post window viene stampato 3 // viene di nuovo stampato 3

2

f.value;

 

3

f.value;

5

f

= { 3.0.rand; };

 

// f viene inizializzata a un numero random //

6

f.value;

 

7

f.value;

//

stampa 3 valori differenti

8

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 meccanismi più potenti del paradigma della programmazione Object Oriented. Il polimorfismo, in breve, permette l’intercambiabilità di oggetti differenti se rispondono allo stesso messaggio. 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 argomenti

sono valori che vengono passati alla funzione quando deve essere valutata. L’esempio sotto 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 il

metodo 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:

12

Funzioni e Funzionalità

1

 

f

=

{

arg

a,

b;

a

/

b;

// ’/’significa dividi // regular style // keyword style

};

2

f.value(10, 2);

 

3

f.value(b: 2, a: 10);

È

inoltre possibile miscelare i due stili, se necessario, ma gli argomenti regolari

devono 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 non ha tutto un senso completo, diverrà chiaro in seguito.

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

1

 

{

f a

=

arg

a,

b;

+

b;

};

2

g +

=

a

{

|a,

b|

b;

};

3

f.value(2, 2);

 

4

g.value(2, 2);

Perchè coesistono due modi differenti per effettuare la stessa operazione!?

alcune persone preferiscono il secondo stile e lo considerano una scorciatoia e altri

preferiscono il primo stile perchè è più esplicito. ( )

il mondo è bello perchè è vario

perchè

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

13

Funzioni e Funzionalità

È possibile anche avere variabili in una Function; devono essere dichiarate all’inizio

della 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

=

10

9

)

I nomi delle variabili e degli argomenti possono consistere di lettere e numeri, ma occorre 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 di

una variabile dichiarata in una Function è la Function stessa, in altre parole l’area

fra 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à eseguito

insieme (selezionandolo tutto insieme). In tal caso il blocco di codice eseguito è lo scope della variabile. Eseguiamo il blocco (in parentesi tonde) e in seguito l’ultima linea.

1

 

(

2

var myFunc;

 

3

myFunc = { |input| input.postln; };

 

4

myFunc.value("foo");

// arg \‘e una String

 

5

myFunc.value("bar");

 

6

)

8

myFunc;

 

// generare un errore

Ci si potrebbe meravigliare del perchè non è stato necessario dichiarare variabili come

f, e perchè queste non sembrano aver qualche scope particolare (mantengono i valori anche quando eseguiamo una linea alla volta). Le lettere minuscoli dell’alfabeto sono dette variabili dell’interprete. Queste variabili sono pre-dichiarate quando avviamo

14

Funzioni e Funzionalità

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

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

15

Funzioni e Suoni

Capitolo 4 Funzioni e Suoni

Dopo questi dettagli tecnici noiosi, torniamo alla creazione di rumore che è il motivo principale per il quale credo si stia leggendo questo manuale. È pur vero però, che tutto quello che abbiamo visto finora, tornerà molto utile in futuro.

1

Torniamo al nosto esempio sonoro, o almeno ad una versione più semplficata. Dopo

aver 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

)

{ 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 il codice 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, durante lo startup del programma, al server localhost.

In quest’ esempio la Function non viene salvata in una variabile, così non può essere riusata. Questo approccio è spesso impiegato con Function-play, come modo veloce per il testing. Ci sono altri modi per riutilizzare Functions per suoni, che sono spesso migliori 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 e informazioni, e un insieme di operazione che possono essere effettuate sui dati. Si potrebbero avere differenti oggetti dello stesso tipo. Essi sono detti istanze. Il tipo stesso è la classe dell’oggetto. Per esempio potremo avere una classe detta Studenti e diverse istanze della classe, Bob, Dave e Sue. Tutti e tre sono dati dello stesso tipo e potrebbero avere una parte di dati detta QI. Il valore di ogni dato potrebbe essere differente comunque. Si vorrebbe che avessero anche tutti gli stessi metodi per operare sui dati. Per esempio potrebbero avere un metodo detto calculateQI o qualcosa 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

16

Funzioni e Suoni

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

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. È possibile creare oggetti attraverso metodi come new o, in caso della nostra classe SinOsc vista prima, il metodo ar. Tali metodi restituiscono un oggetto o un’istanza e gli argomenti passati 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 sono un esempio di ciò che viene detto unit generator, o UGens, oggetti che producono segnali audio o di controllo. SinOsc e un oscillatore sinusoidale. Questo significa che produrrà un segnale consistente di una sola frequenza. Un grafo della sua forma d’onda potrebbe essere il seguente:

grafo della sua forma d’onda potrebbe essere il seguente: Questo ciclo d’onda viene creato dal segnale

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

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

17

Funzioni e Suoni

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 di ciclo, la forma d’onda sarà:

(pi * 0.5), o un quarto di ciclo, la forma d’onda sarà: Vediamo una serie di

Vediamo una serie di cicli per chiarire l’idea:

sarà: Vediamo una serie di cicli per chiarire l’idea: − Mul invece è un argomento speciale

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 della UGen. Nel caso di un segnale audio, questo moltiplicatore influenzerà l’ampiezza del segnale. Il mul di default di molte UGens è 1, il che significa che il segnale in uscita oscillerà fra -1 e 1. Questo è un buon valore di default, perchè ci mette al sicuro da problemi di clipping o distorsione che segnali più ampi potrebbero accusare. Un mul che vale 0 porta un segnale ad essere davvero nullo, come se la manopola del volume fosse a 0. Per rendere chiaro l’effetto del mul, vediamo un grafo di due SinOsc, una con il mul di default a 1 e una con un mul di 0.25:

18

Funzioni e Suoni

Funzioni e Suoni Esiste un altro argomento simile chiamato add (anch’esso di solito non conside- rato

Esiste un altro argomento simile chiamato add (anch’esso di solito non conside- rato nella documentazione), che è qualcosa che viene addizionato al segnale di output. Può essere utile per segnali di controllo. In generale ‘add’ ha un valore di 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 Function // Crea un SinOsc audio rate // frequenza di 440 Hz, or accordato in

3

 

SinOsc.ar(

4

440,

la

5

 

0,

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

ciclo

6

 

0.2)

// mul di0.2 // chiude la Function e invoca il metodo ’play’

7

 

}.play;

8

)

4.1 Divertiamoci con Funzioni e UGens

Presentiamo ora un altro esempio di polimorfismo e di come questo strumento sia potente. Quando creiamo una Function composta da diverse UGens, è possibile che per molti argomenti non si abbiano valori fissi, ma altre UGens! Vediamo un esempio 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

)

19

Funzioni e Suoni

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 primo SinOsc viene moltiplicato per l’output del secondo. I parametri del primo SinOsc sono:

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 moltiplicazione per 0.5 scalerà il segnale fra 0.5 e -0.5. Aggiungendo 0.5 il segnale viene portato fra 0 e 1.

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

Il risultato finale sarà:

più basso, o in questo caso, 0. Il risultato finale sarà: Quindi il risultato è un

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 inviluppo in 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 suoni in 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]

20

Living Stereo

Capitolo 5 Living Stereo

Fino a questo punto si sono visti concetti importati, ma il primo esempio del capitolo 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 fra 2 parentesi quadre e con una virgola fra loro. Come le parentesi graffe stanno ad indicare una funzione, le parentesi quadre definiscono qualcosa chiamato Ar- ray. Un Array è un tipo di Collection, una collezione di Objects. Le Collections sono esse stesse Objects e molti tipi di Collections possono gestire ogni tipo di oggetti, mixarli insieme, includere altre Collections! Ci sono inoltre parecchi tipi differenti di Collections in SC e impareremo come questi siano una delle features più 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’esempio

appena visto, inserendo oggetti fra due parentesi quadre e separandoli con la virgola. Per prelevare i differenti elementi di un Array si può usare il metodo at che prende un indice come argomento. Gli indici corrispondono alla posizione nell’ordinamento degli oggetti nell’Array e partono da 0.

1

 

a = ["foo", "bar"];// "foo" = posizione 0; "bar" = posizione 1

2

 

a.at(0);

3

a.at(1);

4

 

a.at(2);// restituisce "nil", non ci sono oggetti alla posizione 2

6

// alternativamente, si utilizza

7

 

a[0];

// uguale ad a.at(0);

Inoltre, per il fatto che può contenere collezioni di oggetti, un Array ha anche un uso speciale in SC: viene utilizzato per implementare l’audio multicanale! Se una Function restituisce un Array di UGens (si ricordi che una Function ritorna

il risultato della sua ultima linea di codice), allora l’output sarà un certo numero

di

canali. Il numero di canali dipende dalla dimensione dell’Array, e ogni canale

corrisponderà univocamente a un elemento dell’Array. Nel nostro esempio:

1

 

{

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

21

Living Stereo

Il risultato è un output stereo, con un SinOsc a 440Hz nel canale sinistro e

un SinOsc a 442Hz nel canale destro. È possibile avere più canali di output

utilizzando un array di dimensione maggiore.

Ora si osservi il prossimo esempio. Dato che gli argomenti di fase e mul sono gli stessi 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 causa

quello che viene detto espansione multicanale che non è nient’altro che l’inserimento

di

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; il metodo choose seleziona uno degli elementi dell’Array in maniera random. In questo caso il risultato potrebbe essere un singolo numero e si avrebbe un output monofonico o un altro Array, nel qualcaso si avrebbe un output stereo. Questa tipologia di finezze può rendere il nostro codice molto flessibile.

Ma se si volesse effettuare un crossfade fra due canali? Come si gestisce il pan? SC

ha

un certo numero di UGens che risolvono la spazializzazione orizzontale in vari

modi, 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 sinistro

e il canale destro oppurre il primo e il secondo canale. l’argomento posizione varia 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 output

va

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

22

Living Stereo

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

1

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

Per ulteriori informazioni:

[MultiChannel] [Collections] [Pan2]

23

Creiamo un Mix

Capitolo 6 Creiamo un Mix

Si è già visto che, nel contesto di segnali audio, le moltiplicazioni cambiano il

livello di qualcosa, ma cosa succede e come si fanno a mixare UGens diverse

insieme? 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 a

dente di sega. Da notare che viene usato con un valore basso per l’argomento

mul per assicurarci che l’output finale sarà fra -1 e 1 e non si incorra in problemi

di

clipping.

 

Esiste una comoda classe,Mix, che mixerà un array di canali in un singolo canale o 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 canale

 

2

{

Mix.new([SinOsc.ar(440, 0, 0.2), Saw.ar(660, 0.2)]).postln }.play;

4

// combinazione di due array stereo

5

(

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 di addizione delle due UGens), e nel secondo caso un Array di due BinaryOpUGens.

Da notare che nel primo esempio si usa Mix.new(

). 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 un metodo per creare oggetti, come i metodi ar e kr delle UGens. (Mix è una classe di convenienza: non crea un oggetto Mix, ma ritorna soltanto il risultato della sua somma, o un BinaryOpUGen oppure un Array di questi).

) e che nel secondo si usa

Mix(

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

24

Creiamo un Mix

numero, che determina quante volte il secondo argomento, una Function, sarà valutata. Il risultato di questa valutazione verrà poi sommato. Per chiarire questi concetti 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’argomento

mul di ogni SinOsc è settato a 1/n, assicurando quindi che l’ampiezza totale non andrà mai oltre i limiti di -1 e 1. Semplicemente cambiando il valore di n, si può avere un numero variabile di SinOsc! Queto tipo di approccio rende il codice estremamente flessibile e riusabile.

Ogni volta che viene valutata, alla Function viene passato come argomento un numero. 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, è possibile usare questo valore come parametro. Vediamo:

1

 

// si osservi la post window per vedere frequenze e indici

2

(

3

 

var

n

=

8;

4

{

5

 

Mix.fill(n, { arg index; var freq; index.postln; freq = 440 + index; freq.postln;

 

6

7

8

9

10

 

SinOsc.ar(freq

,

0,

1

/

n)

11

 

})

12

 

}.play;

13

 

)

Dalla combinazione di addizioni e moltiplicazioni ( o qualunque altra possibile operazione matematica che si può immaginare!) con l’uso di classi come Mix, si

hanno gli strumenti necessari per combinare sorgenti multicanali di suono al fine

di ottenere mix e submix complessi.

Per maggiori informazioni:

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

25

Scoping and Plots

Capitolo 7 Scoping and Plots

Oltre ai metodi visti nei capitoli precedenti, l’oggetto Function dispone di altri metodi 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.01 secondi, 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, e se si ottiene l’output desiderato.

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

server sia in stato running. È possibile avviarlo usando la finestra: oppure via codice: 1 Server.internal.boot;

oppure via codice:

1

Server.internal.boot;

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

26

Scoping and Plots

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 zoom out.

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 se davvero si ottiene quello che si vuole.

7.1 Scoping su richiesta

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

27

Scoping and Plots

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: il server interno gira come un processo dentro l’app client: un programma dentro un programma. Il principale vantaggio di questo approccio è che permette a due applicazioni di condividere memoria, utile per cose come lo scoping in tempo reale dell’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]

28

Help di Supercollider

Capitolo 8 Help di Supercollider

Siamo giunti ad un buon punto di questo manuale per fermarci ed esplorare alcuni metodi per trovare ulteriori informazioni di SC. Nei file di Help viene

utilizzato il link cliccabile come questo:

[Help] Cliccando su questo link si aprirà la finestra principale dell’help, che contiene

un certo numero di link ad altri file di help. A questo punto, potrebbe essere

una buona idea familiarizzare con alcuni di questi. Quelli che rispondono ai link

Essential Topics e Language sono di particolare importanza.

8.1 Classi and Metodi

Abbiamo visto finora sufficiente teoria sull’OOP, da conoscere che ci sono le

classi, che sono come template per oggetti, e istanze, oggetti che sono creati da questi template. Si possono anche avere classi e metodi di istanza, che possono prendere in input argomenti o meno. Metodi di classe fanno cose come creare istanze (come alcune funzioni convenienti che non richiedono un istanza attuale),

e metodi di istanza che controllano e manipolano le istanze stesse. Ci sono anche

le variabili d’istanza, che sono i dati specifici di ogni istanza e le variabili di classe, che sono dati in comune fra tutte le istanze.

Ricordiamo che ogni cosa che comincia nel codice di SC con una lettera maiuscola nel codice è una classe. Molte classi hanno l’help file. In SC, se si seleziona una classe 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 possono

essere molto utili per rendere chiaro esattamente cosa fa la classe, e possono servire come punto di partenza per il proprio lavoro. È una buona idea copiare

e incollare questi esempi in una nuova finestra e provarli, apportando modifiche

o meno per capire davvero come funzionano. Per accedere all’help file per Function e Array, dato che spesso appaiono nel

codice come {

}

e [

],

basta selezionarli e premere Cmd-? su:

29

Help di Supercollider

Help di Supercollider 1 Function 2 Array Alcuni metodi hanno anche help file, e alcuni di

1 Function

2 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(

SC ha un certo numero di tali forme di scorciatoie o sintassi alternative. Un

esempio comune è il seguente: someObject.someMethod(anArg) è equivalente a someMethod(someObject, anArg).

Qui vediamo un esempio concreto. Le due righe di codice seguenti fanno la stessa

cosa:

)

vs. Mix.new(

)

del capitolo capitolo 6.?

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 di

SC. 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 è utile conoscerle 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]

30

SuperCollider 3 Synth Server: architettura

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 e delle sue principali componenti.

9.1 Introduzione

Il Server di SuperCollider v3 è un motore di sintesi semplice e potente. Mentre

questo motore è in stato running, possono essere creati nuovi moduli, distrutti e/o ricollegati, possono essere creati e/o riallocati buffer; possono inoltre essere creati e collegati dei processi di effettistica in un flusso di segnali in modo del tutto dinamico a tempo di scheduling. Tutti i moduli in stato running sono ordinati in un albero di nodi che definisce l’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 semplificata

di 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 o

riceve messaggi MIDI. Si aspetta che il client invierà solo comandi di controllo. Se si rende necessario usare il protocollo e/o i messaggi MIDI, è compito del client ricevere tali messaggi e convertirli in comandi OSC appropriati per il motore di sintesi.

Le definizioni di synth sono salvate in file generati dall’applicazione linguaggio

SC (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 SC dandone una veloce definizione. Per una trattazione più completa si rimanda il lettore ai capitoli relativi.

Nodo Un nodo è un’entità indirizzabile in un albero di nodi eseguito dal motore di

31

SuperCollider 3 Synth Server: architettura

sintesi. Ci sono due tipi di nodi: Synths e Groups. l’albero definisce l’ordine

di esecuzione di tutti i Synths. Tutti i nodi hanno un ID intero.

Synth Un Synth è una collezione di unit generator (UGen) che vengono eseguite insieme. Questi possono essere indirizzati e controllati da comandi inviati al

motore di sintesi. In genere si occupano di leggere dati in input e scrivere dati in output sui bus globali di audio e di controllo. I Synths possono avere

i propri controlli locali che sono settati tramite comandi al server.

I Synth vengono trattati approfonditamente nel capitolo 10.

Synth Definition

I Synths sono creati a partire dalle Synth Definition. I file di Synth Definition

sono 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.

Group Un Group è una collezione di nodi rappresentati come una linked list. Un nuovo nodo potrebbe essere aggiunto in testa o in coda al gruppo. I nodi dentro un gruppo possono essere controllati insieme. I nodi in un Group

possono essere sia Synths che altri Group. Allo startup del server si ha un top level Group con un ID=0 che definisce la radice dell’albero. Se il server viene 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 tutti

i 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, partendo da 0. l’utilizzo di bus, come la connessione diretta fra synths, permette di connettere i synths stessi all’insieme di altri synths senza particolari cono- scenze su di essi. I bus con numero più basso scrivono direttamente sugli output audio dell’hardware. Immediatamente seguenti ai bus di output ci

32

SuperCollider 3 Synth Server: architettura

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 riscontro diretto 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 da un numero intero cominciando da zero.

Shared Control Buses Il server internal (che è in stato running con lo stesso spazio di indirizzi dell’app del client) ha un certo numero di bus di controllo condivisi con l’app del client con un accesso di lettura/scrittura sincrono. 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 indicizzato da interi partendo da zero. I Buffers potrebbero essere allocati in maniera protetta, caricati e liberati mentre la sintesi sta eseguendo anche quando le Ugen 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 un

array di valori in floating point. I file audio potrebbero essere caricati dentro

o scritti da buffers.

I Buffer vengono trattati approfonditamente nel capitolo 13.

Unit Generator Definitions Le definizioni di Unit Generator sono caricate automaticamente all’avvio del programma. Esse sono in librerie binary code e vengono usate dai Synths per costruire algoritmi di sintesi. Le definizioni delle UGens hanno un nome che corrisponde al nome della classe del linguaggio di SC usato nei Synth Definitions.

Ora che abbiamo scoperto alcune informazioni base su SC e sul server, andiamo

a studiare le astrazioni server, che sono le varie classi nel linguaggio dell’app

del client che rappresentano qualcosa sul server. È importante capire che questi oggetti 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 a confusione, così, in generale ci riferiremo alle classi client-side con nomi maiusco- li, e gli aspetti corrispondenti dell’architettura del server con nomi con lettera minuscola, per esempio Synth vs. synth.

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

33

SuperCollider 3 Synth Server: architettura

riferiti a Server.local e Server.internal (e qualsiasi cosa salvata nella variabile s in 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.

34

SynthDefs e Synths

Capitolo 10 SynthDefs e Synths

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

10.1 SynthDef

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

Il server, in generale, non conosce tuttavia Functions, o l’Object Oriented, o il

SC language, ma piuttosto vuole infomazioni su come creare output audio in una forma speciale detta definizione di synth. Una definizione di synth è formata da una serie di informazioni sulle UGens da utilizzare e come esse devono essere interconnesse. Questa serie di informazioni viene inviata al server in una forma speciale ottimizzata, detta ‘byte code’ con cui il server può lavorare in maniera molto efficiente.

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

Questa relazione fra la definizione di synth e i synth è analoga alla relazione che intercorre 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 conoscono l’ OOP.

Fortunatamente per noi, nel linguaggio ci sono classi come SynthDef, che rendono semplice creare il byte code necessario e inviarlo al server, e avere a che fare con definizioni 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’, per

creare il protocollo di dialogo. Viene generato il byte code necessario e inviato al server, dove sarà creato un synth per suonare l’audio desiderato. I metodi audio

di Function offrono così un tipo di convenienza per il programmatore, liberandolo

dal compito di occuparsi di byte code e cose lontane al suo modo di lavorare.

35

SynthDefs e Synths

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

1

//prima la funzione

2

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

4

// ora una SynthDef equivalente alla funzione

5

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 solito sotto forma di stringa. Il secondo è, di fatto, una Functione viene detto UGen Graph 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, e presenta una non troppo lieve differenza: ha un UGen extra detta Out. Out si occupa di scrivere in output un segnale ar o kr sui bus del server, che possono essere canali di un mixer o degli output veri e propri. I bus verranno discussi in dettaglio più avanti; per ora è sufficiente sapere che sono usati per inviare audio in 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 bus comincia 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 sul bus 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 questa UGen.

36

SynthDefs e Synths

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)

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 noi una UGen Out. Il bus di default per questa UGen è 0.

e

Sia Function-play che SynthDef-play restituiscono un altro tipo di oggetto, un Synth, che rappresenta un synth sul server. Se salviamo questo oggetto assegnan-

dolo in una variabile, è possibile controllarne il comportamento tramite i metodi

di

cui dispone. Per esempio il metodo free causa lo stop del synth sul server e

ne

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 x

4

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 senza creare immediatamente un synth: send e load e writedef. La differenza fra questi 3 è:

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 permettere al server di caricarla.

Questi file avranno estensione .scsyndef ( nel nostro esempio tutorial-SinOsc.scsyndef),

e saranno salvato nella cartella synthdefs/ presente nella directory principale di

SC. Questi file saranno inoltre caricati automaticamente ogni qualvolta si farà il

37

SynthDefs e Synths

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. Si rende tuttavia necessario in alcuni casi usare load, casi in cui si ha a che fare con definizioni molto grandi o complicate, a causa del limite della dimensione dei 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 creare molti synths da questa con un overhead relativamente basso di CPU. È possi- bile farlo con il metodo new, che prende il nome della definizione come primo argomento.

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

2 = Synth.new("tutorial-PinkNoise");

x

3 = Synth.new("tutorial-PinkNoise");

y

4 x.free; y.free;

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

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

38

SynthDefs e Synths

1

// prima con una Function. Da notare la frequenza random, differente ad ogni 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 valutata una volta sola e cioè al momento della creazione.

10.3 SynthDefs ‘variabili’

Per superare le limitazioni viste in precedenza, esistono diversi modi per ottenere valori di output non fissi da SynthDefs. Si può usare per esempio [Rand], che calcola un numero random fra i valori min e max passati come parametri quando alla 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 = Synth("tutorial-Rand");

x

4 = Synth("tutorial-Rand");

y

5 = Synth("tutorial-Rand");

z

6 x.free; y.free; z.free;

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

39

SynthDefs e Synths

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 args

7

y

= Synth("tutorial-args", ["freq", 660]);

// cambia la freq

8

z

= Synth("tutorial-args", ["freq", 880, "out", 1]);

// cambia freq

e canale di output

 

9

 

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 grado

di

flessibilità, è ancora necessario usare le Functions, o creare definizioni multiple

di

synth.

10.4 Synth

La classe Synth prevede alcuni metodi che permettono di variare il valore degli argomenti dopo che un synth è stato creato. Per ora ne vedremo uno, il metodo set. 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 visto

in precedenza, o un altro tipo di letterale detto Symbol. È possibile scrivere

simboli alternativamente in uno dei due modi seguenti:

40

SynthDefs e Synths

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 sono garantiti essere identici, ( xes esattamente lo stesso oggetto), mentre le stringhe potrebbero differire. Per testare questa cosa, usiamo ’===’. Eseguiamo il codice

e osserviamo la post window

// true

// false

1 "a String" === "a String";

2 \aSymbol === ’aSymbol’;

In generale nei metodi che comunicano con il server è possibile usare Strings

e Symbol in modo intercambiabile, ma potrebbe non essere vero in un codice generico.

//

false

1 "this" === \this;

Per maggiori informazioni:

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

41

I Buss

Capitolo 11 I Buss

Vediamo ora qualche ulteriore informazione sui bus sul server. Esiste un’analogia con 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. In SuperCollider questo significa da e per l’hardware audio o fra synth differenti. Esistono due tipi di bus:

Audio Rate

Control Rate

Com’è intuibile, il primo indirizza segnali audio mentre il secondo segnali di con-

trollo.

I Bus control rate sono abbastanza semplici da comprendere, sono semplici canali

di 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 di output anteposti nell’ordine ai canali di input. Per esempio, se immaginiamo un

server 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 di

output provocherà un’emissione sonora dagli altoparlanti e leggere audio dai 2 bus di input permetterà di acquisire suoni in SC, per processi di registrazione o

di elaborazione.

I bus audio rimanenti saranno privati. Essi sono usati per inviare audio e segnali

di 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 di output. Questi bus privati sono spesso usati per esempio per funzionalità come una mandata effetti, qualcosa cioè che richiede alcuni passi di elaborazione audio prima di generare output.

Il numero di bus audio e di controllo disponibili, come il numero di canali di

output 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 input

e output)

42

I Buss

11.1 Buss: scrittura e lettura

Si è già visto Out.ar, che permette di scrivere audio (per esempio play out) su

un bus. Ricordiamo che prevede 2 argomenti: un indice e un output, che possono essere 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 ha

anch’esso due argomenti: un indice, e il numero di canali da leggere. Se il numero

di canali è maggiore di 1, allora l’output di In sarà un Array. Si provi il seguente

esempio e si osservi la post window:

1

2

In.ar(0, 1);

In.ar(0, 4);

// restituir\‘a ’an OutputProxy’

// restituit\‘a un Array di 4 OutputProxies

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

1

2

4

5

6

// Questo genera un errore: segnale control rate su un bus audio rate

{Out.ar(0, SinOsc.kr)}.play;

// Questo non genera errore:

control rate

Server.internal.boot;

{Out.kr(0, SinOsc.ar)}.scope;

segnale audio rate sottocampionato a

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

43

I Buss

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 mixato

7

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 che

tutto 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 infatti

non li si usa direttamente, specialmente se ciò che si sta facendo è solo far fluire audio dall’ingresso all’uscita della nostra scheda audio. In generale tuttavia i Bus offrono 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 di creazione comuni: Bus-audio e Bus-Control. Ognuno prevede 2 argomenti: un oggetto Server e il numero di canali.

1

 

b = Bus.control(s, 2);

// Crea un Bus control a 2 canali

2

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 suo

secondo argomento, scriverà i canali dell’Array in bus consecutivi. Richiamiamo un 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 1

5

}).play;

6

)

La verità è che non ci sono bus multicanali di per se, quindi non esiste un oggetto

bus due-canali, tuttavia gli oggetti Bus sono in grado di rappresentare una serie

di bus con indici consecutivi. L’incapsulamento di molti bus lato-server adiacenti

44

I Buss

in un singolo oggetto Bus, permette di trattarli come un gruppo, rendendo il loro utilizzo 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’ultimi vengano usati con un certo ordine; si potrebbe farlo oculatamente se si potesse decidere quale indice usare, come fossero array, ma i Bus gestiscono questi indici automaticamente. Ogni oggetto Server ha un bus allocator e quando si crea un oggetto 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 Bus usando il suo metodo index.

1

// riavviamo il server e quindi azzeriamo il bus allocator

2

s.reboot;

 

3

b

= Bus.control(s, 2);

//

un control Bus a 2 canali

4

b.index;

// dovrebbe essere 0 // Bus ha anche un metodo numChannels

5

b.numChannels

6

c

= Bus.control(s);

7

c.numChannels;

// il numero di default dei canali=1

8

c.index;

//

=2;

b usa sia

0

che

1

Tramite l’utilizzo di oggetti Bus per rappresentare bus adiacenti, si può garantire che 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 esempio perchè si rende necessario indirizzare un segnale multicanale) e garantire che il tutto sia ancora stabile e sicuro. Se fosse compito del programmatore l’allocazione degli indici dei bus e se si rendesse necessario ad un certo punto riarrangiare tutto per un canale adiacente extra, dal momento che gli indici devono essere conse- cutivi, sarebbe davvero molto complicato! Questo è un buon esempio del potere che ha la programmazione a oggetti; incapsulando negli oggetti stessi processi come 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 metodo free;

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. Come detto in precedenza, gli indici dei bus con valore più basso sono i canali di input

45

I Buss

e output. Detto ciò, se vogliamo usare il primo bus privato, quale indice dovremo

utilizzare? Consideriamo per esempio l’App Server con 2 canali di output e 2 di

input. Il primo audio bus privato è indicizzato con 4 (0, 1, 2, 3

quando scriviamo il nostro codice, daremo il valore 4 all’Ugen Out come indice

del bus.

Ma cosa succede se si decide in seguito di cambiare il numero di canali di output

e impostarlo a 6? Tutto ciò che è stato scritto sul nostro bus privato, finirà su

uno 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 di input o di output, gestirà gli indici considerando il numero di bus di input e di output quando verra eseguito il codice. Ancora, questo principio rende il codice più flessibile.

4!!). Così

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 bus

4

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. Questo approccio è più efficiente rispetto ad avere 2 oscillatori di controllo separati per controllare la frequenza. Questo tipo di strategia di connettere insieme synths, ognuno dei quali fa cose differenti in un processo più grande, può essere molto efficace in SC.

46

I Buss

Ora vediamo un esempio con un bus audio. Questo è l’esempio più complicato di quelli visti fin’ora, ma potrebbe dar qualche idea su come mettere insieme tutte le 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 alle

basse frequenze rispetto alle alte frequenze), e un altro crea impulsi di un’onda sinusoidale. Gli impulsi sono creati usando la Ugens [Impulse] e [Decay2]. Questi sono quindi riviberati usando una catena di [AllpassC], che è un tipo di delay.

) che crea la catena valutando la funzione

16 volte. Si tratta di una tecnica molto potente e flessibile, caratterizzata dal

Da notare inoltre il costrutto16.do(

fatto che semplicemente cambiando il numero è possibile modificare il numero di valutazioni. (Si veda [Integer] per ulteriori informazioni su Integer-do.)

47

I Buss

1

(

2

// l’arg permette il controllo diretto

3

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 output

8

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 output

18

Out.ar(outBus, source * direct);

19

// il nostro effects output

20

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 volte

27

// {}.dup(n) valuta la funzione n volte, e restituisce un Array dei risultati

28

// n=2 di default per un riverbero stereo

29

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 bus

32

)

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 segnale

41

y.set(\direct, 1); // only direct PinkNoise

42

z.set(\direct, 1); // only direct Sine wave

43

y.set(\direct, 0); // only reverberated PinkNoise

44

z.set(\direct, 0); // only reverberated Sine wave

45

x.free; y.free; z.free; b.free;

48

I Buss

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

di 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 e leggerlo da un control bus. Questo significa che non si rende necessario l’utilizzo

di una UGen In. Inoltre è possibile scrivere valori costanti su un bus di controllo

usando il metodo set, e recuperare i valori usando il metodo get

1

(

2

//

crea 2 bus control rate e setta il loro

valori a 880

3

//e 884 rispettivamente

4

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 argomenti

7

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 bus

12

x.map(\freq1, b.index, \freq2, c.index);

 

14

// viene creato un Synth per scrivere su uno dei bus

15

y

= {Out.kr(b.index, SinOsc.kr(1, 0, 50, 880))}.play(addAction:

\addToHead);

17

// libera y, e b mantiene il suo ultimo valore

 

18

y.free;

20

// con Bus-get si ottine questo valore e stamparlo nella post window

21

b.get({ arg val; val.postln; f = val; });

 

23

// viene settata la freq2, annullando la mappatura da c

24

x.set(\freq2, f / 2);

 

26

// freq2, non essndo mappata, la modifica su c non ha effetti

27

c.set(200);

 

29

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

49

I Buss

Da notare che, diversamente dai bus audio rate, i bus control rate mantengono

i valori in essi contenuti finchè qualcosa di nuovo non viene sovrascritto. Da

notare inoltre in questo script che il metodo Bus-get prende una Function (detta funzione-azione) come argomento, il che comporta una piccola quantità di tempo

per 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 nel caso di un bus multicanale. Questo concetto, cioè occupare una piccola quantità

di tempo per la risposta (usualmente detto latenza) è abbastanza importante da

capire. Ci sono un certo numero di altri metodi in SC che funzionano in questo

modo, e possono causare dei problemi se non sono trattati con attenzione. Per illustrare questa cosa eseguiamo l’esempio seguente:

1

 

// crea un Bus object e setta i suoi valori

2

b

= Bus.control(s, 1); b.set(880);

4

// eseguire tutto insieme

 

5

(

6

f

=

nil;

//

just to be sure

7

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 maniera

ottimale, cioè si può dire fa cosa gli si dice di fare, più veloce che può e quando glielo si dice. Così nel blocco di codice fra le parentesi, invia il messaggio ’get’al server, 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è non

si

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 settato

a 880, come ci aspettavamo. Nell’esempio precedente questa latenza non era un

problema, dal fatto che si eseguiva una sola linea per volta. Ma ci potranno essere

sicuramente casi in cui sarà necessario eseguire blocchi interi di istruzioni e la tecnica della funzione azione sarà la soluzione.

11.5 L’ordine è una cosa importante!