Singolo valore

In SuperCollider la prima possibilità consiste nell'inviare i singoli valori del parametro desiderato dall'Interprete al Server attraverso la sintassi Synth.set(\arg, val). Nell'esempio seguente cambiamo la frequenza di una sinusoide ma non dimentichiamo che queste tecniche possono essere applicate a qualsiasi valore numerico rappresentativo di qualsiasi parametro di sintesi:

s.boot;
s.scope(1);
s.meter(1,1);

// ------------------------------ SynthDef e Synth
(
SynthDef(\ksig,
              {arg freq=400;        // parametro(i) da controllare come argomento
               var sig;
                   sig = SinOsc.ar(freq);
               Out.ar(0,sig)
               }
        ).add;

{~synth = Synth(\ksig)}.defer(0.1)  // ritarda di 0.1 secondi la valutazione della funzione
)

// ------------------------------ Controllo esterno dei parametri

~synth.set(\freq, rrand(200,2000).postln); // eseguire più volte
~synth.free;

Nel codice precedente la creazione del Synth e la sua assegnazione ad una variabile sono incluse all'interno di una funzione sulla quale è invocato il metodo {}.defer(0.1). Questa sintassi ritarda del tempo specificato come argomento di .defer() la valutazione del codice incluso nella funzione e si rende necessaria in situazioni come questa in cui SuperCollider impiega alcuni millisecondi per istanziare la SynthDef sul Server e dobbiamo attendere obbligatoriamente questo tempo prima di generare un'istanza di Synth derivata da quella SynthDef.

Smoothing

Nel caso precedente le frequenze cambiano repentinamente ad ogni valutazione del codice.

Ipotizziamo ora di voler aggiungere un portamento (un veloce glissato) ad ogni cambio di frequenza. Per farlo dobbiamo prima trasformare i valori provenienti dall'Interprete da numeri (int o float) in segnali di controllo o segnali audio per poter poi utilizzare una tecnica chiamata smoothing che "arrotonda" il segnale attraverso un qualche tipo di interpolazione.

Questa tecnica deriva dall'audio analogico e consiste appunto nell'arrotondare le discontinuità di un segnale facendolo passare per un filtro a un polo (onepole). In SuperCollider esiste una UGens chiamata Lag.kr() che effettua questa operazione e per utilizzarla possiamo adottare due sintassi differenti:

(
{[LFPulse.kr(1),
  Lag.kr(LFPulse.kr(1),0.2), // Come UGen
  LFPulse.kr(1).lag(0.2)     // Come metodo
]
}.plot(1)
)

Personalmente preferisco la sintassi che utilizza i metodi perchè mi sembra più rappresentativa di una trasformazione del segnale in uscita dalla UGen ma a livello computazionale si equivalgono.

image not found

Notiamo dall'immagine come il secondo argomento di Lag.kr() serva per specificare il tempo di interpolazione in secondi (smoothing time) o meglio il tempo che il segnale impiega nel diminuire (o aumentare) di 60 dB seguendo una curva esponenziale. Il codice seguenti è identico a quello illustrato in precedenza con due importanti differenze:

  1. prima trasformiamo il valore inviato dall'interprete in un segnale di controllo con la UGen K2A.ar() che genera un segnale audio formato dai valori specificati come argomento.

  2. poi effettuiamo uno smoothing dinamico su questo segnale.
// ------------------------------ SynthDef e Synth
(
SynthDef(\ksig,
              {arg freq=400,smooth=0.02;
               var sig, port;
                   port = K2A.ar(freq).lag(smooth); // genera il portamento
                   sig  = SinOsc.ar(port);
               Out.ar(0,sig)
               }
          ).add;

{~synth = Synth(\ksig)}.defer(0.1)
)

// ------------------------------ Controllo esterno dei parametri

~synth.set(\freq,rrand(200,2000).postln,\smooth,rrand(0.02,10).postln);
~synth.free;

Le due operazioni appena esposte possono essere semplificate in un abbreviazione sintattica che automatizza la trasformazione del valore numerico in segnale.

// ------------------------------ SynthDef e Synth
(
SynthDef(\ksig,
              {arg freq=400,smooth=0.02;
               var sig, port;
                   port = freq.lag(smooth); // abbreviazione sintattica
                   sig  = SinOsc.ar(port);
               Out.ar(0,sig)
               }
          ).add;

{~synth = Synth(\ksig)}.defer(0.1)
)

// ------------------------------ Controllo esterno dei parametri

~synth.set(\freq,rrand(200,2000).postln,\smooth,rrand(0.02,10).postln);
~synth.free;

Nel caso delle frequenze la differenza tra cambio repentino del valore e portamento ha una valenza musicale, ma in altri (come ad esempio nel controllo dinamico dell'ampiezza di un segnale) lo smoothing assume esclusivamente una valenza tecnica (eliminare eventuali clicks generati dalle discontinuità del segnale di controllo) e in questi casi possiamo fissare un tempo di smoothing che normalmente corrispondente a 20 millisecondi (0.02 secondi).

Infine la UGen Lag.kr() e il metodo corrispondente hanno diverse varianti con le quali possiamo modificare la curva di smooth:

(
{[LFPulse.kr(1,0.999),
  LFPulse.kr(1,0.999).varlag(0.2),
  LFPulse.kr(1,0.999).lag(0.2),
  LFPulse.kr(1,0.999).lag2(0.2),
  LFPulse.kr(1,0.999).lag3(0.2),
  LFPulse.kr(1,0.999).lagud(0.2,0.4), // u = up, d = down
  LFPulse.kr(1,0.999).lag2ud(0.2,0.4),
  LFPulse.kr(1,0.999).lag3ud(0.2,0.4)]
}.plot(1)
)

image not found

Notiamo dall'immagine che con LagUD.kr() e i suoi simili possiamo specificare un tempo per quando il valore del segnale aumenta (up) e un'altro tempo per quando diminuisce (down). Le UGens "derivate" sono come 2 o 3 Lag.kr() in serie e il tempo di smoothing che andiamo a specificare non è sempre preciso proprio perchè la risposta non è lineare. Un'ultima particolarità: VarLag.kr() ritarda sempre di uno step.