• Non ci sono risultati.

Segnali di controllo

Nel documento Introduzione a SuperCollider (pagine 116-126)

Introduzione a SuperCollider

3. conversione digitale-analogica: per essere ascoltabile, la sequenza numeri ca che compone il segnale viene riconvertita in segnale analogico attraverso

4.7 Segnali di controllo

Un segnale sinusoidale, come tutti i segnali perfettamente periodici, manca totalmente delle caratteristiche di dinamicità usualmente ascritte ad un suono “naturale” o, meglio, “acusticamente interessante”: è un suono, come direbbe Pierre Schaeffer, senza “forma” temporale, “omogeneo”. Suoni di questo tipo, peraltro, arredano il paesaggio sonoro della modernità meccanica ed elettrica sotto forma di humming, buzzing -e così via- prodotti dai ventilatori, dall’im- pedenza elettrica, dai motori. A parte questi casi, tipicamente la “forma” tem- porale di un suono prende la forma di un profilo dinamico, di una variazione della dinamica del suono che è descritta acusticamente sotto forma di una curva di inviluppo, le cui fasi prendono usualmente il nome di attacco/decadimen- to/sostegno/estinzione: da cui l’acronimo ADSR (attack, decay, sustain, release). Il modo più semplice di rappresentare un simile inviluppo consiste nell’utiliz- zare una spezzata (Figura 4.15).

A guardare l’inviluppo si osserva agevolmente come si tratti di altra cur- va, e cioè propriamente di un altro segnale, che si distingue dai segnali finora considerati per due caratteristiche importanti:

1. non è periodico (si compone di un unico ciclo). Dunque, il periodo del se- gnale d’inviluppo è pari alla durata del segnale audio: se il segnale dura 2 secondi (ovvero il periodo dell’inviluppo) allora l’inviluppo ha una frequen-

Time (s) 0 4.23184 –0.8233 0.8881 0 Time (s) 0 0.588944 –0.3398 0.3456 0

Fig. 4.15 Descrizione del profilo dinamico attraverso una spezzata (invilup- po).

2. è unipolare: assumendo che il segnale audio sia nell’escursione normalizza-

ta[−1, 1] (bipolarità), l’inviluppo è compreso in [0, 1] (unipolarità)8.

Passando dall’analisi del segnale alla sua sintesi, si tratterà perciò di ripro- durre le proprietà dell’inviluppo (la sua “forma”) e di applicare questa forma al segnale audio. L’inviluppo è un tipico segnale di controllo: un segnale che mo- difica -controlla- un segnale audio.

Essendo un segnale, per rappresentare un inviluppo si può utilizzare un ar- ray. Ad esempio, un tipico inviluppo ADSR potrebbe essere descritto dall’array di dieci punti di Figura 4.16.

0 1 2 3 4 5 6 7 8 9 10 0 0.2 0.4 0.6 0.8 1 Fig. 4.16 [0, 0.9, 0.4, 0.4, 0.4, 0.4, 0.3, 0.2, 0.1, 0].

8 Si ricordi il caso della funzione valor assoluto che rende unipolare un segnale

1 [0, 0.9, 0.4, 0.4, 0.4, 0.4, 0.3, 0.2, 0.1, 0].plot

Per incrementare o decrementare l’ampiezza di un segnale si può (lo si è vi- sto abbondantemente) moltiplicare il segnale per una costante: ogni campione

viene moltiplicato per la costante. Ad esempio, se la costante𝐴𝑚𝑝 = 0.5, l’am-

piezza del segnale viene ridotta della metà. Si potrebbe pensare ad una simile costante nei termini di un segnale: come un array, di dimensione pari a quella del segnale scalato, che contenga sempre lo stesso valore. Ogni valore dell’array

da scalare viene moltiplicato per il rispettivo valore dell’array𝐴𝑚𝑝 (sempre lo

stesso). L’idea smette di essere una complicazione inutile nel momento in cui

l’array𝐴𝑚𝑝 non contiene più sempre lo stesso valore, ma contiene invece va-

lori variabili che rappresentano appunto un inviluppo d’ampiezza. Dunque, ogni campione del segnale audio (ogni elemento dell’array) viene moltiplicato per un valore incrementale del segnale d’inviluppo ottenuto. La situazione è rappresentata in Figura 4.17. 0 1 2 3 4 5 6 7 8 9 10 0 0.2 0.4 0.6 0.8 1 0 100 200 300 400 500 600 700 800 900 1000 -1 -0.8 -0.6 -0.4 -0.2 0 0.2 0.4 0.6 0.8 1 0 100 200 300 400 500 600 700 800 900 1000 -1 -0.8 -0.6 -0.4 -0.2 0 0.2 0.4 0.6 0.8 1 0 100 200 300 400 500 600 700 800 900 1000 -1 -0.8 -0.6 -0.4 -0.2 0 0.2 0.4 0.6 0.8 1

Fig. 4.17 Inviluppo, segnale audio risultante, segnale audio, segnale audio in- viluppato.

Dovendo descrivere lo sviluppo del segnale audio in tutta la sua durata, la di- mensione dell’array d’inviluppo deve essere la stessa del segnale audio. Emer- ge qui un punto fondamentale, su cui si ritornerà: tipicamente è sufficiente un numero molto minore di punti per rappresentare un inviluppo rispetto ad un segnale audio, ovvero -assumendo che il segnale audio duri un solo secondo a

qualità CD- il rapporto tra i due è pari a10/44.100. Questo è in effetti il tratto

pertinente che identifica un segnale di controllo.

Passando all’implementazione in SC (qui di seguito), per intanto si può ge- nerare una sinusoide di durata pari ad un secondo attraverso il metodo wa- veFill. La frequenza prescelta è grave perché così sono meglio visibili i cicli del segnale. L’array di partenza che si vuole implementare è composto da dieci segmenti: poiché questi devono essere ripartiti sulla durata del segnale audio,

ognuno dei segmenti comprenderà44100/10 campioni. Questo valore è asse-

gnato alla variabile step. Si tratta ora di generare gli array che costituiscono i quattro segmenti ADSR (gli array att, dec, sus, rel, 8-11), per poi conca- tenarli in un array complessivo env. Poiché devono essere moltiplicati per un oggetto Signal allora devono anch’essi essere oggetti Signal. Il metodo se- ries(size, start, step) crea una progressione lineare di size valori a par-

tire da start con incremento step. L’attacco parte da0 e arriva a 0.9, occupa

un segmento, cioè 4410 campioni. Il valore 0.9 deve cioè essere raggiunto in

4410 punti: l’incremento di ognuno sarà perciò di0.9/4410. Dunque, 0.9/step.

All’ultimo punto si avrà un valore pari a0.9/𝑠𝑡𝑒𝑝 × 𝑠𝑡𝑒𝑝 = 0.9. Nel caso di dec

si tratta di scendere a0.5 nella durata di un altro segmento. In questo caso la

progressione parte da0.9 e l’incremento è negativo: bisogna scendere di 0.5 in

uno step, dunque l’incremento è -0.5/step. Nel terzo caso (sus) il valore è co-

stante a0.4 per una durata di 4 passi, dunque si può pensare ad un incremento

0. Il caso di rel è analogo a quello di dec. Il nuovo segnale di inviluppo env (13) è ottenuto concatenando i quattro segmenti ed è impiegato come moltipli- catore di sig per ottenere il segnale inviluppato envSig (14). La riga 16 disegna segnale, inviluppo e segnae inviluppato.

1 var sig, freq = 440, size = 44100, step ;

2 var env, att, dec, sus, rel, envSig ;

4 step = 44100/10 ;

5 sig = Signal.newClear(size) ;

6 sig.waveFill({ arg x, i; sin(x) }, 0, 2pi*50) ;

8 att = Signal.series(step, 0, 0.9/step) ; 9 dec = Signal.series(step, 0.9, -0.5/step) ; 10 sus = Signal.series(step*4, 0.4, 0) ;

11 rel = Signal.series(step*4, 0.4, -0.4/( step*4)) ; 13 env = att++dec++sus++rel ;

14 envSig = sig * env ;

16 [sig, env, envSig].flop.flat.plot(minval:-1, maxval: 1, numChannels: 3) ;

Il metodo precedente è molto laborioso, e del tutto teorico in SuperCollider. Quest’ultimo prevede infatti una classe Env specializzata nel costruire invilup- pi. Env assume che un inviluppo sia una spezzata che connette valori d’ampiez- za nel tempo e fornisce diverse modalità di interpolazione per i valori interme- di. Si considerino i due array seguenti v e d:

1 v = [0, 1, 0.3, 0.8, 0] ; 2 d = [ 2, 3, 1, 4 ] ;

Una coppia simile è tipicamente utilizzata per specificare un inviluppo: • v: indica i punti che compongono la spezzata (i picchi e le valli);

• d: indica la durata di ogni segmento che connette due punti.

Dunque, l’array delle durate contiene sempre un valore di durata in meno di quello delle ampiezze. Infatti, t[0] (= 2) indica che per andare da v[0] (= 0) a v[1] (= 1) sono necessari 2 unità di tempo (in SC: secondi, ma in realtà si trat- ta come si vedrà di unità astratte). Attraverso i due array v, d è così possibile descrivere un profilo (v) temporale (d). Nell’esempio, resta tuttavia da specifica- re cosa succede per ogni campione compreso nei due secondi che intercorrono

tra0 e 1. Il modo in cui i campioni sono calcolati dipende dalla modalità din interpolazione. Nell’esempio seguente e1, e2, e3 sono oggetti Env specificati dalla stessa coppia di array v, d, ma con differenti modalità di interpolazio- ne (lineare, discreta, esponenziale). L’ultimo esempio, e4, dimostra un’ulteriore possibilità: un array di modalità di interpolazione che vengono applicate per ogni segmento (l’ultima modalità è ’sine’).

1 var v, d, e1, e2, e3, e4 ;

3 v = [0, 1, 0.3, 0.8, 0] ; 4 d = [ 2, 3, 1, 4 ] ; 6 e1 = Env.new(v, d,’linear’) ;

7 e2 = Env.new(v, d,’step’) ;

9 v = [0.0001, 1, 0.3, 0.8, 0] ; 10 e3 = Env.new(v, d,’exponential’).asSignal ;

11 e4 = Env.new(v, d,[\linear , \step ,\exponential , \sine ]) ;

13 [e1, e2, e3, e4].collect{|i| i.asSignal}.flop.flat.plot(numChannels:4) ;

Si noti che nel caso di un inviluppo esponenziale il valore di partenza non

può essere pari a0: il primo valore di v viene quindi ridefinito con un valo-

re prossimo allo zero. Il significato dei parametri è illustrato nella Figura 4.18 che commenta quanto disegnato dal metodo plot. La classe Env eredita diret- tamente da Object e dunque non è un oggetto di tipo array. Tipicamente viene utilizzata come specificazione di inviluppo per il tempo reale (come si vedrà). Quando però un oggetto Env riceve il messaggio asSignal, esso restituisce un oggetto Signal che contiene un inviluppo campionato nel numero di punti che compongono il nuovo array. Il metodo è utilizzato (13) per ottenere array dagli inviluppi in modo da utilizzare la tecnica di plotting già discussa.

Attraverso asSignal la classe Env permette di utilizzare anche in tempo differito una specificazione per gli inviluppi decisamente più comoda. Inoltre, la classe prevede alcuni costruttori che restituiscono inviluppi particolarmente utili. Due esempi:

• triangle richiede due argomenti: il primo indica la durata, il secondo il va- lore di picco di un inviluppo triangolare (il picco cade cioè a metà della du- rata).

0 20 40 60 80 100 120 140 160 180 200 0 0.2 0.4 0.6 0.8 0 20 40 60 80 100 120 140 160 180 200 0 0.2 0.4 0.6 0.8 0 20 40 60 80 100 120 140 160 180 200 0 0.2 0.4 0.6 0.8 1 0 20 40 60 80 100 120 140 160 180 200 0 0.2 0.4 0.6 0.8 1

Fig. 4.18 Env: tipi di interpolazione.

• perc: permette di definire un inviluppo percussivo (attacco + rilascio). Gli argomenti sono tempo d’attacco, tempo di rilascio, valore di picco e valore di curvatura.

1 var sig, freq = 440, size = 1000 ; 2 var envT, envP ;

4 sig = Signal.newClear(size) ;

5 sig.waveFill({ arg x, i; sin(x) }, 0, 2pi*50) ;

7 envT = Env.triangle(1,1).asSignal(size);

8 envP = Env.perc(0.05, 1, 1, -4).asSignal(size) ; 10 [sig, envT, sig*envT, envP, sig*envP].flop.flat

11 .plot(minval:-1, maxval: 1, numChannels: 5) ;

Inviluppi di tipo triangle e perc, e le loro applicazioni a una sinusoide sono rappresentati in Figura 4.19.

0 10 20 30 40 50 60 70 80 90 100 0 0.2 0.4 0.6 0.8 1 0 100 200 300 400 500 600 700 800 900 1000 -1 -0.8 -0.6 -0.4 -0.2 0 0.2 0.4 0.6 0.8 1 0 10 20 30 40 50 60 70 80 90 100 0 0.2 0.4 0.6 0.8 1 0 100 200 300 400 500 600 700 800 900 1000 -1 -0.8 -0.6 -0.4 -0.2 0 0.2 0.4 0.6 0.8 1

Fig. 4.19 Inviluppi con Env, di tipo triangle (alto) e perc (basso).

L’applicazione di un segnale di inviluppo è ovviamente possibile anche per un segnale audio di provenienza concreta. Nell’esempio seguente il segnale sig è ottenuto importando il contento di sFile, e viene normalizzato in modo che il

suo picco sia pari a1 (6). A sig viene applicato un segnale di inviluppo env:

env è ottenuto attraverso due array riempiti di numeri pseudo-casuali, v e d. Il

primo oscilla nell’intervallo[0.0, 1.0] (è un segnale unipolare). Per evitare off-

set nell’ampiezza, il primo e l’ultimo valore dell’array vengono posti a 0, ed

aggiunti in testa e in coda (11). L’array d è composto di un numero di elementi

pari a quelli di v−1, secondo quanto richiesto dalla sintassi di Env. Gli intervalli

1 var env, v, d, breakPoints = 10 ;

2 var sig, sFile ;

4 sFile = SoundFile.new;

5 sFile.openRead(Platform.resourceDir+/+"sounds/a11wlk01-44_1.aiff");

6 sig = Signal.newClear(sFile.numFrames).normalize ;

7 sFile.readData(sig) ;

8 sFile.close;

10 v = Array.fill(breakPoints-2, { arg i ; 1.0.rand }) ; 11 v = v.add(0) ; v = [0].addAll(v) ;

12 d = Array.fill(breakPoints-1, { arg i; 4.0.rand }) ; 14 env = Env(v, d, ’lin’).asSignal(sig.size) ;

16 [sig, env, sig*env].flop.flat.plot(minval:-1, maxval: 1, numChannels: 3) ;

Due inviluppi pseudo-casuali generati dal codice sono disegnati in Figura

4.20: ad ogni valutazione del codice l’inviluppo assume infatti una forma di-

versa, a parte per i due estremi pari a0.

0 1 2 3 4 5 6 7 8 9 10 0 0.2 0.4 0.6 0.8 1 0 1 2 3 4 5 6 7 8 9 10 0 0.2 0.4 0.6 0.8 1

Fig. 4.20 Inviluppi pseudo-casuali.

4.8 Conclusioni

L’obiettivo di quanto visto finora era di introdurre il concetto di segnale di- gitale, attraverso alcune operazioni che si rendono tipicamente possibili grazie

alla sua natura numerica. Si è poi avuto modo di osservare come sia possibile modificare un segnale attraverso un altro segnale. In particolare, un segnale di controllo è un segnale che richiede una risoluzione temporale molto minore del segnale e la cui frequenza di situa al di sotto delle frequenze udibili (“sub-audio range”). Un segnale di controllo tipicamente modifica un segnale audio. Ma il capitolo è stato anche l’occasione per approfondire alcuni aspetti linguistici di SuperCollider. È a questo punto opportuno riprendere ed espandere gli aspetti affrontati attraverso il modus operandi più tipico di SC, il tempo reale.

Finora, di SuperCollider si è discusso di tutto, a parte di quello per cui è massimamente famoso, la sintesi e l’elaborazione del segnale audio. È perciò tempo di affrontare la questione, ma come al solito è necessaria una certa pa- zienza per mettere insieme tutti i pezzi necessari.

Nel documento Introduzione a SuperCollider (pagine 116-126)