• Non ci sono risultati.

Generalizzazione degli invilupp

Nel documento Introduzione a SuperCollider (pagine 174-180)

Introduzione a SuperCollider

1. direttamente In altre parole, sclang-interprete è un buon posto per l’utente da dove parlare al server al livello di quest’ultimo (da dove spedire messag-

6.2 Generalizzazione degli invilupp

Sebbene l’uso degli inviluppi sia tipico per l’ampiezza, va comunque ri- cordato che EnvGen è di fatto un lettore di dati in un formato speciale, quello definito dalla classe Env. Un oggetto Env è in effetti del tutto analogo ad una cosiddetta tabella in cui sono tabulati punti che descrivono un profilo (break point). EnvGen è allora un’unità di lettura di tabelle speciali (di tipo Env) il cui contenuto può essere utilizzato per controllare parametri di diverso tipo. Si con- sideri il codice seguente:

1 {

2 var levels, times, env ;

3 levels = Array.fill(50, { arg x ; (x%7)+(x%3)}).normalize ;

4 times = Array.fill(49, 1).normalizeSum ;

5 env = Env.new(levels, times) ;

7 Pulse.ar(EnvGen.kr(env, 1, 100, 200, 20, 2))

8 }.play

Sono allora possibili alcune considerazioni sulla synthDef.

Intanto, l’inviluppo. Le righe 2-5 definiscono un inviluppo env, costituito

da50 punti d’ampiezza (3) intervallati da 49 durate (4). In particolare levels è

un array che contiene una spezzata data da due cicli di iterazioni, poiché usa il

modulo (%)7 e 3. La progressività è data dal moltiplicatore *x, dove x è l’indice

dell’elemento calcolato. L’utilizzo del modulo è interessante per produrre age- volmente curve complesse ma non casuali. L’array risultante è in Figura 4.15.

0 2 4 6 8 10 12 14 16 18 20 22 24 26 28 30 32 34 36 38 40 42 44 46 48 50 0 1 2 3 4 5 6 7 8

Fig. 6.1 Array costruito attraverso l’operatore modulo.

Essendo la periodicità di entrambi pari rispettivamente a7 e 3, i due contatori

andranno in fase nel punto del loro minimo comune multiplo21. Il massimo è

nell’intervallo [0.0, 1.0]. I punti d’ampiezza calcolati per levels sono pensati come equispaziati. È per questo che la definizione di times produce semplice-

mente un array riempito dal valore1. Il metodo normalizeSum restituisce un ar-

ray array/array.sum, dove a sua volta il metodo sum restituisce la somma degli elementi dell’array in questione. L’esempio seguente dimostra interattivamente l’uso di normalize, sum, normalizeSum.

1 [1,2,3,4,5].sum // somma 2 15

4 [1,2,3,4,5].normalize // max e min tra 0 e 1 5 [ 0, 0.25, 0.5, 0.75, 1 ]

7 [1,2,3,4,5].normalizeSum // somma elementi = 1

8 [ 0.066666666666667, 0.13333333333333, 0.2, 0.26666666666667, 9 0.33333333333333 ]

11 [1,2,3,4,5].normalizeSum.sum // somma normalizzata sommata = 1 12 1

I due array levels e times sono allora nella forma normalizzata preferibile. L’idea alla base della synthDef è quella di utilizzare l’inviluppo per controllare

la frequenza di Pulse. Dunque, levelScale è pari a100 mentre levelBias è 200:

la curva descritta si muoverà nell’intervallo[200, 300] Hz, per un totale di 20

secondi (timeScale), quindi il synth si deallocherà (doneAction:2). La curva è una rampa, e intuibilmente produce un glissando. Nel prossimo esempio, viene invece utilizzata un’altra UGen, Latch.

1 {

2 var levels, times, env ;

3 levels = Array.fill(50, { arg x ; (x%7)+(x%3)}).normalize ;

4 times = Array.fill(49, 1).normalizeSum ;

5 env = Env.new(levels, times) ;

7 Pulse.ar(Latch.kr(EnvGen.kr(env, 1, 100, 200, 20, 2), Impulse.kr(6)))

Latch implementa un algoritmo classico, il cosiddetto Sample & Hold. Tut- te le volte che riceve un segnale di trigger, Latch campiona il segnale che ha in ingresso, e genera in uscita quel valore finché un nuovo trigger non innesca una nuova pesca dal segnale in ingresso. Nella synthDef il trigger è Impulse,

un generatore di singoli campioni, con frequenza= 6: ad ogni periodo 𝑇 = 16,

Impulse genera un singolo impulso di ampiezza mul. Si ricordi che la forma del segnale di triggering non è importante se non per quando si attraversa lo ze-

ro dal negativo al positivo. Dunque la curva env di20 secondi è campionata

ogni 16 = 0.16666666666667 secondi. Ne risulta un effetto “notale”. Si potreb-

be espandere ulteriormente quest’effetto. Nell’esempio seguente, levelScale è

pari a24 e levelBias a 60.

1 {

2 var levels, times, env ;

3 levels = Array.fill(50, { arg x ; (x%7)+(x%3)}).normalize ;

4 times = Array.fill(49, 1).normalizeSum ;

5 env = Env.new(levels, times) ;

7 Pulse.ar(Latch.kr(EnvGen.kr(env, 1, 24, 60, 20, 2).poll.midicps.poll, 8 Impulse.kr(6)))

9 }.play

Dunque, l’intervallo in cui si muove l’inviluppo in ampiezza è[60, 84]. L’idea

è di ragionare non in frequenza ma in altezza, come se ci si trovasse sulle due

ottave a partire dal do centrale del piano (codificato in MIDI come60), per poi

convertirlo attraverso midicps. La cosa rilevante in quest’ultimo punto è che midicps è un operatore di conversione: in generale, su tutti i segnali in uscita dalle UGen è possibile applicare operatori matematici, ad esempio, squared o abs, per tornare a casi menzionati. Osservando il codice, si nota anche che è stato inserito due volte il metodo poll. Detto in maniera molto approssimati- va, quest’ultimo è una sorta di analogo a lato server di postln. L’architettura client/server di SC pone un problema di rilievo che spesso risulta nascosto. Il processo di sintesi è controllato dal client, ma è realizzato dal server. Il cliente dice cosa fare al fornitore di servizi il quale svolge la sua azione ottemperan- do alle richieste. Ma come può sapere il client cosa succede dentro il server? Come si fa a sapere se non si sono commessi errori di implementazione? Il solo feedback audio non è sufficiente (il fatto che il risultato sia interessante indipen- dentemente dalla correttezza dell’implementazione è senz’altro positivo, ma la

scope e plot ad esempio) non è analitico, cioè non comunica direttamente cosa succede in termini di campioni audio calcolati. Il metodo poll, definito sulle UGen, dice al server di inviare indietro al client il valore di un campione audio, ad un tasso impostabile come argomento del metodo, e di stamparlo sulla post window. Questo permette di monitorare cosa succede a livello campione nel server in uscita da ogni UGen. In sostanza, il metodo può essere concatenato dopo i metodi ar e kr. Ad esempio:

1 {SinOsc.ar(Line.ar(50, 10000, 10).poll).poll}.play ;

produce 1 Synth("temp__1198652111" : 1001) 2 UGen(Line): 50.0226 3 UGen(SinOsc): 0.00712373 4 UGen(Line): 149.523 5 UGen(SinOsc): -0.142406 6 UGen(Line): 249.023 7 UGen(SinOsc): -0.570459 8 UGen(Line): 348.523 9 UGen(SinOsc): -0.982863 10 UGen(Line): 448.023 11 UGen(SinOsc): -0.616042 12 UGen(Line): 547.523 13 UGen(SinOsc): 0.676455

Per ogni UGen, poll stampa sullo schermo il valore del segnale in uscita. Si

noti come SinOsc oscilli in[−1, 1] mentre Line inizi la progressione lineare da

50 a 10000.

Tornando all’ultima synthDef, la sequenza poll.round.poll.midicps.poll stampa prima il valore in uscita da EnvGen, quindi il suo arrotondamento, in- fine la sua conversione in Hz. Il metodo poll (che deriva da una UGen, Poll) effettua una operazione interessante, che verrà discussa in seguito ma che vale la pena sottolineare: invia messaggi dal server al client, mentre finora la comuni- cazione è avvenuta unidirezionalmente nel verso opposto. Se si legge l’output

sullo schermo si nota come le note midi siano espresse con numeri con la vir- gola. In altri termini, la notazione midi è un modo in cui può essere gestita una altezza, ma non necessariamente quest’ultima deve essere temperata (una “no- ta”). Ponendosi come esercizio proprio quest’ultimo obiettivo, si può utilizzare un operatore di arrotondamento, round, come nell’esempio seguente.

1 {

2 var levels, times, env ;

3 levels = Array.fill(50, { arg x ; (x%7)+(x%3)}).normalize ;

4 times = Array.fill(49, 1).normalizeSum ;

5 env = Env.new(levels, times) ;

7 Pulse.ar(Latch.kr(EnvGen.kr(env, 1, 24, 60, 20, 2) 8 .poll.round.poll.midicps.poll,

9 Impulse.kr(6))) 10 }.play

In questo modo, un segnale continuo diventa doppiamente discreto: da un

lato, si producono6 note al secondo attraverso Latch, dall’altro il segnale in al-

tezza viene riportato ad una griglia di interi ([60, 84]). Di fatto, è una sorta di nuova digitalizzazione del segnale. L’operatore round permette ovviamente di

definire l’unità di arrotondamento: nell’esempio, se fosse= 0.5, si arrotonde-

rebbe al quarto di tono.

La Figura 6.2 mostra un frammento dello UGen graph della synthDef, poi-

ché la struttura di Env è molto grande (sono50 posti). Si noti la presenza della

UGen Poll (che nella synthDef “c’è ma non si vede“), il suo trigger interno (una

UGen Impulse con freq= 10) e gli operatori Round e MIDICPS.

Il grafo mette in luce un punto. Gli operatori matematici disponibili sulle UGen non sono altro che UGen speciali, per la precisione di tipo BinaryOpUGen e UnaryOpUGen. L’utente non se ne deve preoccupare, ma l’osservazione serve per rimarcare come il server conosce soltanto UGen all’interno della definizione di una synthDef. La situazione è descritta in maniera minimale dai due esempi sottostanti:

1 {SinOsc.ar(200).round(0.5)}.play ; 2 {SinOsc.ar(200).abs-0.5}.play ;

i_out:0 Out EnvGen 1 24 60 20 2 0 49 -99 -99 0.25 0.020408163265306 1 0 0.5 0.020408163265306 1 0 0.375 0.020408163265306 1 0 0.625 0.020408163265306 1 0 0.875 0.020408163265306 1 0 0.75 0.020408163265306 1 0 0.125 0.020408163265306 1 0 0.375 0.020408163265306 1 0 0.25 0.020408163265306 1 0 0.5 0.020408163265306 1 0 0.75 0.020408163265306 1 0 0.625 0.020408163265306 1 0 0.875 0.020408163265306 1 0 0.25 0.020408163265306 1 0 0.125 0.020408163265306 1 0 0.375 0.020408163265306 1 0 0.625 0.020408163265306 1 0 0.5 0.020408163265306 1 0 0.75 0.020408163265306 1 0 1 0.020408163265306 1 0 0 0.020408163265306 1 0 0.25 0.020408163265306 1 0 0.5 0.020408163265306 1 0 0.375 0.020408163265306 1 0 0.625 0.020408163265306 1 0 0.875 0.020408163265306 1 0 0.75 0.020408163265306 1 0 0.125 0.020408163265306 1 0 0.375 0.020408163265306 1 0 0.25 0.020408163265306 1 0 0.5 0.020408163265306 1 0 0.75 0.020408163265306 1 0 0.625 0.020408163265306 1 0 0.875 0.020408163265306 1 0 0.25 0.020408163265306 1 0 0.125 0.020408163265306 1 0 0.375 0.020408163265306 1 0 0.625 0.020408163265306 1 0 0.5 0.020408163265306 1 0 0.75 0.020408163265306 1 0 1 0.020408163265306 1 0 0 0.020408163265306 1 0 0.25 0.020408163265306 1 0 0.5 0.020408163265306 1 0 0.375 0.020408163265306 1 0 0.625 0.020408163265306 1 0 0.875 0.020408163265306 1 0 0.75 0.020408163265306 1 0 0.125 0.020408163265306 1 0 Round 1 Poll -1 12 85 71 101 110 40 69 110 118 71 101 110 41 MIDICPS Poll -1 18 85 71 101 110 40 66 105 110 97 114 121 79 112 85 71 101 110 41 Poll -1 17 85 71 101 110 40 85 110 97 114 121 79 112 85 71 101 110 41 Latch Impulse 10 0 Impulse 10 0 Impulse 10 0 Impulse 6 0 Pulse 0.5

Fig. 6.2 UGen graph della synthDef.

I cui grafi ed i segnali sono invece in Figura 6.3.

Nel documento Introduzione a SuperCollider (pagine 174-180)