Segnali di Controllo

Nelle tecniche illustrate in altri Paragrafi abbiamo trasformato i valori numerici provenienti dall'Interprete in segnali audio o di controllo (che vivono nel Server) e li abbiamo eventualmente modificati attraverso una qualche tecnica di smoothing.

(
s.boot;
s.plotTree;
)
(
a = {arg val=0; [val,        // senza smoothing
	         val.lag(2)] // smoothing
     }.scope;
)
a.set(\val,rand(1.0));		

Risulta evidente che possiamo utilizzare direttamente un qualsiasi tipo di segnale per modificare dinamicamente qualsiasi parametro di un'altro segnale, a patto che il primo produca valori compresi in un range corretto per quel parametro specifico come tra 0.0 e 1.0 per l'ampiezza, 20 e 20.000 per la frequenza, 0 e 127 per il Midi, etc.

Possiamo classificare le diverse tipologie di segnale in base a tre proprietà:

Rate

In tutti i software per l'audio digitale ci sono due tipi di segnali:

Entrambi sono generati o elaborati da UGens e la maggior parte di queste può "lavorare" sia con uno che con l'altro tipo di segnale rispondendo semplicemente a uno dei due metodi dedicati (.ar o .kr). Nell'immagine sottostante lo stesso segnale ad audio e a control rate:

image not found

Possiamo convertire un segnale da una tipologia all'altra con due UGens dedicate: A2K.kr() e K2A.ar()

{[WhiteNoise.kr,  A2K.kr(WhiteNoise.ar)]}.scope;  // Audio --> controllo (downsampling)
{[WhiteNoise.ar,  K2A.ar(WhiteNoise.kr)]}.scope;  // Controllo --> audio (upsampling)	

Il primo (A2K.ar()) lo possiamo utilizzare quando abbiamo necessità di sottocampionare un segnale audio in ingresso per utilizzarlo come controllo di altri segnali o come vedremo più avanti come test per il trigger di eventi mentre il secondo (K2A.ar()) può tornare utile quando utilizziamo controller hardware esterni a bassa definizione (come un'interfaccia midi) per controllare qualsiasi parametro di una sintesi che necessita invece un'alta definizione.

Prestiamo attenzione che queste UGens non effettuano alcuna interpolazione, cambiano solamente il numero di campioni al secondo tra segnale in entrata e segnale in uscita.

Range

A priori tutti i segnali possono essere compresi in qualsiasi ambito numerico (range) tra +/- ∞ in quanto le UGen trattano numeri non suoni.

Questa affermazione però vale solamente per i segnali di controllo (anche quelli ad audio rate) e non per i segnali audio scritti sull'output di SuperCollider che devono essere obbligatoriamente compresi tra +/- 1.0. Molte UGens hanno questo ambito di default.

I segnali di questo tipo vengono definiti bipolari in quanto trattano numeri sia positivi che negativi non necessariamente compresi tra +/- 1:

image not found

Viveversa i segnali compresi in un ambito con un solo segno (sia esso positivo o negativo) sono chiamati unipolari.

image not found

I segnali unipolari dovrebbero essere utilizzati solo come segnali di controllo, in quanto se un segnale audio in uscita è unipolare significa che ha un DC offset:

Riscalaggio

Possiamo modificare il range del segnale in uscita da una UGen in diversi modi:

Tipologie

Attraverso la combinazione di segnali in algoritmi di sintesi ed elaborazione del suono, possiamo generare un'infinità di timbri con caratteristiche morfologiche estremamete differenti. I segnali che li descrivono però (siano essi audio o di controllo) possono assumerne solo poche e le principali sono quattro:

image not found

(
{[
Impulse.kr(100),
LFNoise0.kr(10),
BrownNoise.kr(1).lag,
WhiteNoise.kr(1)
]}.plot(0.5)
)

Richiamando l'Help file delle diverse UGens presenti nel codice precedente possiamo cominciare autonomamente una piccola esplorazione delle specifiche carattteristiche.

Ogni segnale compreso in ognuna di queste categorie a sua volta può inoltre essere:

image not found

(
{[
Impulse.kr(100),
Dust.kr(100),
LFSaw.kr(10),
LFNoise0.kr(10),
SinOsc.kr(10),
WhiteNoise.kr(1)
]}.plot(1)
)

Impulsivi

I segnali impulsivi sono quelli in cui il valore di un singolo campione si discosta da quello dei campioni limitrofi

...0 0 0 0 1 0 0 0 0 1...

In SuperCollider sono generati da Clocking UGens e possono essere sia regolari:

...1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1...

che irregolari:

...1 0 1 0 0 1 0 0 1 0 0 0 0 0 0 1 0 1...

Sono spesso utilizzati come trigger di eventi in molte tecniche di elaborazione del segnale.

Periodici

In Supercollider possiamo generare un treno di impulsi periodici attraverso la UGen Impulse.ar() che genera una sequenza di clicks isocroni e ha come argomento il numero di clicks per secondo (bps):

image not found

(
s.boot;
s.meter(2,2);
s.plotTree;

{Impulse.ar(MouseX.kr(1,1000))}.scope; // da ritmo a treno di impulsi
)

Essendo un segnale unipolare possiamo utilizzarlo come moltiplicatore d'ampiezza di un altro segnale bipolare:

(
{var trig1, trig2;
     trig1 = Impulse.kr(5);
     trig2 = Impulse.kr(3);
WhiteNoise.ar(trig1) + SinOsc.ar(800,0,trig2)}.scope
)

In questo caso siccome i valori generati da questa Ugen sono esclusivamente 0 e 1 se volessimo modificare l'ampiezza randomicamente, dovremmo moltiplicarla per un generatore di rumore bianco reso unipolare oppure in modo deterministico per una rampa o inviluppo:

(
{var trig,noise,ramp;
     trig  = Impulse.kr(10);
     noise = WhiteNoise.kr.unipolar;
     ramp  = Line.kr(0,1,8);
SinOsc.ar(1200,0,trig*noise) + WhiteNoise.ar(trig*ramp)}.scope
)

Con le dovute modifiche al range possiamo applicarlo a qualsiasi parametro di un algoritmo di sintesi:

(
{var trig = Impulse.kr(12),
     amp  = WhiteNoise.kr.unipolar,
     imp  = trig*amp,            // tra 0 e 1
     fmin = 800,                 // frequenza minima
     freq = fmin+(imp*fmin*3);   // valori casuali nell'ambito di 3 ottave
     freq.poll(10);              // visualizza
SinOsc.ar(freq,0,imp)}.scope
)	

Un semplice esempio di sound design che riassume quanto appena esposto:

(
{var trig1,trig2,trig3,trig4,noise,ramp,imp,fmin,freq;
     trig1 = Impulse.kr(5);
     trig2 = Impulse.kr(3);
     trig3 = Impulse.kr(10);
     trig4 = Impulse.kr(12);
     noise = WhiteNoise.kr.unipolar;
     ramp  = Line.kr(0,1,8);
     imp   = trig4*noise;
     fmin  = 800;
     freq  = fmin+(imp*fmin*3);

WhiteNoise.ar(trig1) + SinOsc.ar(800,0,trig2) + SinOsc.ar(1200,0,trig3*noise) + 
WhiteNoise.ar(trig3*ramp) + SinOsc.ar(freq,0,imp) + SinOsc.ar(100,0,Impulse.ar(220))}.scope
)

Aperiodici

In Supercollider possiamo generare un treno di impulsi aperiodici attraverso la UGen Dust2.ar() che, a differenza di Impulse.ar() genera una sequenza di clicks con tempi delta e ampiezze randomiche. Il primo argomento specifica la media di impulsi per secondo.

Dust.ar() genera un segnale unipolare mentre Dust2.ar() bipolare.

image not found

{Dust.ar(MouseX.kr(1,100))}.scope;
{Dust2.ar(MouseX.kr(1,100))}.scope;

(
{var trig1,trig2,trig3,trig4,noise,ramp,imp,fmin,freq;
     trig1 = Dust.kr(5);
     trig2 = Dust.kr(3);
     trig3 = Dust.kr(10);
     trig4 = Dust.kr(12);
     noise = WhiteNoise.kr.unipolar;
     ramp  = Line.kr(0,1,8);
     imp  = trig4*noise;
     fmin = 800;
     freq = fmin+(imp*fmin*3);

WhiteNoise.ar(trig1) + SinOsc.ar(800,0,trig2) + SinOsc.ar(1200,0,trig3*noise) + 
WhiteNoise.ar(trig3*ramp) + SinOsc.ar(freq,0,imp) + Dust2.kr(250,0.4)}.scope
)

Decay.ar()

Possiamo trasformare i segnali impulsivi in segnali continui utilizzando due diverse UGens: Decay.ar() e Decay2.ar().

image not found

(
{var trig = Impulse.ar(5),      // trigger
     env  = Decay.ar(trig,0.1); // inviluppo con decay
WhiteNoise.ar(env)              // segnale
}.scope
)

(
{var trig = Impulse.ar(3),      // trigger
     env  = Decay.ar(trig,0.5); // inviluppo con decay
SinOsc.ar(800,0,env)            // segnale
}.scope
)	

A seconda del parametro sul quale mappiamo il segnale impulsivo (ampiezza, frequenza o altro) e/o a seconda della morfologia del segnale sul quale agisce possiamo sceglierne uno o l'altro. Nell'esempio precedente Decay.ar() funziona come moltiplicatore d'ampiezza del rumore bianco ma genera clicks quando lo utilizziamo con una sinusoide. In questo caso se non vogliamo udire questo artefatto dobbiamo utilizzare Decay2.ar().

Un altra questione da sottolineare consiste nel fatto che Impulse.ar() e Dust.ar() accettano valori temporali in bps mentre Decay.ar() e Decay2.ar() li accettano in secondi. E' consigliabile unificare l'unità di misura temporale effettuando le dovute conversioni:

Per quanto riguarda Decay2.ar() dobbiamo calcolare il tempo del decadimento sottraendo alla durata il tempo dell'attacco. E' dunque consigliabile specificare tutto direttamente in secondi.

Facciamo attenzione che con questa UGen possiamo addolcire o indurire l'attacco con tempi assoluti al di sotto di 0.1 secondi ma non modificare l'inviluppo.

(
SynthDef(\dec2 , {arg sec=0.2,atk=0.01;
	          var bps,dec,trig,env;
	              bps  = sec.reciprocal;
	              dec  = 1-atk * sec;
	
	              trig = Impulse.ar(bps);      
	              env  = Decay2.ar(trig,atk,dec); 
	          Out.ar(0, SinOsc.ar*env)
}).add;

{a = Synth(\dec2)}.defer(0.2)
)

a.set(\atk,rrand(0.008,0.01).postln);
a.set(\sec,rrand(0.05,0.8));	

Il prossimo esempio illustra come si possa utilizzare lo stesso segnale per controllare simultaneamente due o più parametri differenti semplicemente riscalandolo in ambiti diversi:

{Dust2.kr(5)}.plot(1);                               // tra -1 e +1
{Decay2.ar(Dust2.ar(5),0.005,0.3)}.plot(1);          // applica un inviluppo
{Decay2.ar(Dust2.ar(5),0.005,0.3)*200+1000}.plot(1); // tra 800 e 1200

(
SynthDef(\sync, {var trig,env,sig;
                     trig  = Dust2.ar(10);
	             env   = Decay2.ar(trig,0.005,0.3); 
	             sig   = SinOsc.ar(env*500+4000,0,env);
		 Out.ar(0,sig)
}).add;

{a = Synth(\sync)}.defer(0.2);
)

Discreti

I segnali discreti sono quelli in cui il valore dell'ampiezza istantanea cambia ogni n campioni:

...[0.5 0.5 0.5 0.5] [-0.2 -0.2 -0.2 -0.2] [0.8 0.8 0.8 0.8] [-0.7 -0.7 -0.7 -0.7]...

La maggior parte dei segnali discreti può essere generata da UGens dedicate che hanno come prefisso LF che è l'acronimo di Low Frequency. Quasi tutti sono bipolari, compresi tra +/-1 e possono essere:

Segnali periodici

image not found

(
{[LFCub.kr(5),
  LFGauss.kr(1/5),
  LFPar.kr(5),
  LFPulse.kr(5),
  LFSaw.kr(5),
  LFTri.kr(5)]}.plot(1)
)

La caratteristica principale di questi segnali periodici è che non sono limitati in banda di frequenza e possono dunque generare aliasing o foldover. Nell'immagine seguente possiamo osservare le stesse forme d'onda nella versione LF (ottimizzata pre il controllo di altri segnali) e nella versione con filtro anti aliasing (ottimizzata per i segnali in uscita da SuperCollider):

image not found

{LFCub.ar(MouseY.kr(20000,200),0,0.1)}.scope;  // con aliasing
{SinOsc.ar(MouseY.kr(20000,200),0,0.1)}.scope; // senza aliasing	

Segnali aperiodici

image not found

(
{[LFNoise0.kr(50),
  LFNoise1.kr(50),
  LFNoise2.kr(50),
  LFClipNoise.kr(100),
]}.plot(1);
)	

I segnali di questo tipo sono caratterizzati dal fatto che il loro primo argomento è la frequenza espressa in Hz, ovvero il numero di valori pseudocasuali generato in un secondo (ricordiamo che i generatori di rumore tradizionali non hanno per loro natura il parametro frequenza, ma solo ampiezza). Prestiamo attenzione a non incappare in un'incomprensione: questi segnali non generano solo il numero di valori indicato come frequenza, ma quello indicato dal metodo invocato (.ar o .kr). Il parametro frequenza indica solo ogni quanto cambiano.

image not found

Smoothing

Possiamo ottenere segnali continui da segnali discreti in due modi differenti:

Ovviamente tutti questi segnali opportunamente riscalati possono essere mappati su qualsiasi parametro di un algoritmo di sintesi o di elaborazione del suono.

Changed.kr()

Per ottenere un segnale impulsivo da uno discreto possiamo utilizzare la UGen Changed.kr() che genera un 1 ogni volta che cambia il valore di un segnale in ingresso:

image not found

(
SynthDef(\chan, {arg rate=5;
	         var ksig,trig,env,sig;
	             ksig = LFNoise0.ar(rate).scope;
                     trig = Changed.ar(ksig);
	             env  = Decay2.ar(trig,0.01,rate.reciprocal-0.01);
	             sig  = SinOsc.ar(ksig.range(80,86).midicps);
	        Out.ar(0,sig*env*ksig.unipolar)	
}).add;

{a = Synth(\chan)}.defer(0.2)
)

a.set(\rate,rrand(2,10).postln);

Il segnale impulsivo ottenuto ha tutte le peculiarità trattate nel paragrafo dedicato.

Uniformi

Questa tipologia rappresenta la maggior parte dei segnali audio, siano essi suoni campionati (segnali provenienti da microfoni o memorizzati in audio files), siano essi suoni sintetizzati direttamente in SuperCollider, siano essi suoni derivati dall'elaborazione di altri segnali. Tipicamente sono bipolari con valori compresi tra +/-1 e lavorano ad audio rate.

Anch'essi debitamente riscalati e/o sottocampionati e/o mofrfologicamente convertiti possono essere utilizzati per controllare uno o più parametri di altri segnali. Vediamo le principali tecniche che possiamo utilizzare.

Audio

image not found

Possono essere segnali in tempo reale oppure memorizzati su supporti sotto forma di sound file o buffer.

b = Buffer.read(s, Platform.resourceDir +/+ "sounds/a11wlk01.wav");
b.plot(\audio,800@200);

Periodici

image not found

Possono essere generati da UGens dedicate. In seguito alcune forme d'onda classiche. Notiamo come a differenza dei loro stretti parenti a bassa frequenza (LFUgens) queste siano bandlimited (si capisce visivamente dalla "rugosità" della forma d'onda) per evitare aliasing.

(
{[SinOsc.kr(10),
  Pulse.kr(10),
  Saw.kr(10),
  Blip.kr(10),
]}.plot
)	

Aperiodici

image not found

Diverse tipologie di rumore generate da UGens dedicate.

(
{[WhiteNoise.ar(1),
  PinkNoise.ar(1),
  BrownNoise.ar(1),
  GrayNoise.ar(1),
  ClipNoise.ar(1)]}.plot
)	

Triggers

Possiamo utilizzare segnali di qualsiasi tipologia per generare triggers ovvero discontinuità tra due valori limitrofi di ampiezza istantanea che possono far partire e/o arresatare un evento sonoro. E' prassi che un valore maggiore di 0 faccia partire un evento mentre un valore minore o uguale a 0 lo arresti.

Esistono due principali tipi di trigger:

image not found

Entrambi sono unipolari e possono subire tutte le alterazioni morfologiche dei segnali impulsivi (Decay.ar()) e di quelli discreti (tecniche di smooothing). Nell'immagine seguente un riassunto delle principali morfologie che può assumere un trigger ottenuto da un segnale:

image not found

(
{var sigk  = SinOsc.ar(3),       // segnale di controllo 
     trig0 = Impulse.kr(3),      // segnale impulsvo
     env   = trig0.lag*10,       // segnale impulsvo 'smoothed'
     trig1 = Trig1.kr(sigk,0.2), // con fase sostegno
     env0  = trig1.lag;          // con fase sostegno 'smoothed'
[sigk,trig0,env,trig1,env0]
}.plot(2)
)	

In questi casi i valori sono compresi tra 0 e 1 ma possono essere riscalati in qualsiasi range invocando il metodo .range(min,max).

Modalità di generazione

Un trigger sotto forma di segnale può essere generato in quattro modi:

Principali utilizzi

In seguito le pricipali tecniche che utilizzano triggers.

Sottocampionamento

In alcuni casi potremmo voler sottocampionare i valori di un segnale per poterli mappare debitamente riscalati e/o "arrotondati" o meno su un qualche parametro di altri segnali.

image not found

(
s.boot;
s.meter(2,2);
s.plotTree;

SynthDef(\sotto, {var rate,ksig,trig,vals,sig;
	              rate = Rand(2,5);        // numero di triggers per secondo
	              ksig = WhiteNoise.kr;    // segnale da sottocampionare
	              trig = Impulse.kr(rate); // generatore di trigger
	              vals = ksig*trig;        // sottocampionamento
	              sig  = Saw.ar(VarLag.kr(vals,rate.reciprocal).range(900,2500)); // mappato su freqs       
	          Out.ar(0,sig*Decay.kr(vals.abs,rate.reciprocal))                    // mappato su amps     
}).add;

{Synth(\sotto)}.defer(0.2)
)		

Ramp.ar()

Possiamo ottenere rampe generate attraverso un'interpolazione lineare tra i valori campionati con la UGen Ramp.ar()

image not found

(
SynthDef(\rate, {var rate,ksig,glis,sig;
	             rate = Rand(2,5);        
	             ksig = WhiteNoise.kr;    
	             glis = Ramp.kr(ksig,rate);     
	             sig  = Pulse.ar(glis.range(900,2500)); // mappato su freqs
	         Out.ar(0,sig*glis.unipolar*0.3)            // mappato su amps
}).add;

{Synth(\rate)}.defer(0.2)
)	

Sample and hold

Possiamo trasformare un segnale appartenente ad una qualsiasi tipologia in un segnale discreto attraverso la tecnica del campiona e mantieni che deriva sia dalle tecniche proprie dell'audio analogico che dal procedimento del campionamento. Fondamentalmente si tratta di campionare e mantenere uno o più valori tra le ampiezze istantanee di un segnale. In SuperCollider la UGen da utilizzare è Latch.ar() che accetta due argomenti:

image not found

Le diverse modalità di generazione dei triggers sono illustrate in un paragrafo dedicato.

Prestiamo attenzione che se utilizziamo questa tecnica per sottocampionare segnali impulsivi i due segnali devono coincidere oppure devono essere perfettamente sincronizzati. Stesso segnale (usando Impulse avremmo una serie di 1...)

image not found

(
SynthDef(\sah1, {var ksig,amp,sig;
	             ksig = Dust.kr(10);
	             amp  = Latch.ar(ksig, ksig).scope;
	             sig  = SinOsc.ar(amp.range(600,1100));
	         Out.ar(0,sig*amp.lag(0.2))
          }).add;

{a = Synth(\sah1)}.defer(0.2);
)

Step sequencing

Un'altra tecnica per ottenere segnali discreti da qualsiasi tipologia di segnale è lo step sequencing e in SuperCollider possiamo realizzarla con la Ugen Stepper.ar(). Questo oggetto incrementa un contatore ad ogni trigger, che passa da un valore minimo ad un valore massimo con un passo dato. I suoi argomenti sono:

Stepper.kr(trig, (reset), min, max, step)

Utilizziamo come esempio Impulse.ar() come trigger per generare una scala da 1 a 10 con passo (step) di 1:

image not found

(
{var trig  = Impulse.ar(10,-0.001), // un segnale
     min   = 1,                     // valore minimo
     max   = 10,                    // valore massimo
     passo = 1;                     // passo
Stepper.ar(trig, 0,min,max,passo)}.plot(2,minval:0,maxval:10);
)	

Come possiamo notare dalla rappresentazione grafica una volta raggiunto il valore massimo ritorna al valore minimo (wrap around).

Come facilmente intuibile basterà mappare i valori al range proprio del parametro che vogliamo controllare con questa tecnica. Nell'esempio seguente variamo il pitch di un Synth per realizzare degli arpeggi di vario tipo con le frequenze dei parziali.

(
SynthDef(\step, {arg fond = 200, rate = 8, step = 1;
	         var trig,seq,sig,env;
	             trig = Impulse.kr(rate);
	             seq  = Stepper.kr(trig,0,1,10,step);
	             (seq*0.1).scope;
	             sig  = SinOsc.ar(seq * fond,0,0.1);
		     env  = Decay2.kr(trig,0.01,rate.reciprocal-0.01);     // ...inviluppo d'ampiezza Sync
	         Out.ar(0,sig*env)
}).add;

{a = Synth(\step)}.defer(0.2);
)

a.set(\step,rrand(2,20).postln);
a.set(\step,rrand(2,14),\rate,rrand(2,14));

Stepper.ar() può essere usato in combinazione con Select.ar() per leggere gli indici di un Array e generare qualsiasi tipo di sequenza numerica. In questo caso i valori generati da Stepper.ar() sono da considerarsi come indici di lettura degli items:

(
SynthDef(\step2, {arg seq = #[1523.0,1311.0,1392.0,1523.0,1196.0,1294.0,1311.0,1262.0 ];
	          var rate,trig,count,freq,sig,env;	     
	              rate  = MouseX.kr(1,20);                  // dinamica...
	              trig  = Impulse.kr(rate,0.1);             // trigger
	              count = Stepper.kr(trig,0,0,seq.size,1);  // counter (indici da 0 a size)
	              freq  = Select.kr(count,seq);             // lettore Array tramite indici
	              sig   = SinOsc.ar(freq);
		      env   = Decay2.kr(trig,0.01,rate.reciprocal-0.01);
	          Out.ar(0,sig*env)
}).add;

{a = Synth(\step2)}.defer(0.2);
)

a.set(\seq, 8.collect({rrand(92,100)}).postln.midicps)	

Playback (grani)

Possiamo utilizzare un trigger per far partire il playback di un sound file oppure di una porzione di esso.

(
b = Buffer.read(s, Platform.resourceDir +/+ "sounds/a11wlk01.wav");
SynthDef(\grani, {var trig,pos,sig,env;
                      trig = Impulse.kr(MouseY.kr(0.5, 50, 1));
	              pos  = MouseX.kr(0, BufFrames.kr(b));
	              sig  = PlayBuf.ar(1, b, BufRateScale.kr(b), trig, pos, 1); // Playback
	              env = EnvGen.kr(Env.perc(0.01,0.2),trig);                  // Inviluppo d'ampiezza 
                  Out.ar(0,sig*env)
}).add;

{a = Synth(\grani)}.defer(0.2);
)	

Dobbiamo utilizzare lo stesso trigger (sincronizzare) per far partire un qualsiasi tipo di inviluppo d'ampiezza della stessa durata del playback per evitare discontinuità nel segnale.

Inviluppi

Possiamo mappare i valori di un trigger sul parametro gate di un inviluppo. A seconda della sua tipologia dobbiamo però utilizzare tecniche differenti.

Inviluppi senza sostegno

Per quanto riguarda questo tipo di inviluppi abbiamo bisogno di un solo campione con valore maggiore di 0 (gate:n) che faccia partire l'inviluppo seguito da un altro campione con valore 0 (gate:0) che permetterà di farlo ripartire successivamente.

image not found

(
{var ksig,sig,env;
     ksig = Impulse.kr(1);
     sig  = SinOsc.ar;
     env  = EnvGen.kr(Env.triangle,ksig);
[sig,ksig,sig*Decay.kr(ksig),env,sig*env]
}.plot(3)
)

Nelle modalità di generazione del trigger che prevedono una fase di sostegno come l'utilizzo di devices esterni o delle UGens Trig.ar() e Trig1.ar() il valore 0 servirà solamente a resettare l'inviluppo mentre se utilizziamo generatori d'impulsi ci troviamo a dover afforntare la stessa problematica osservata nel paragrafo su Decay.ar(): Impulse.ar(), Dust.ar() e Changed.ar() accettano valori temporali in bps mentre i diversi tipi di Env li accettano in secondi. E' dunque consigliabile uniformare l'unità di misura temporale effettuando le dovute conversioni:

// Tutto in bps

(
SynthDef(\bps,
              {arg bps=4;
               var ksig,sig,bpf,env;
		   ksig = Impulse.kr(bps);
		   sig  = SinOsc.ar(1234);
                   bpf  = Env.new([0,1,0.3,0],[0.01,0.2,3],\cub);
		   env  = EnvGen.kr(bpf.duration_(bps.reciprocal), // durata env riscalata in secondi
			                ksig,doneAction:0);     
               Out.ar(0,sig*env)
               }
          ).add;

{a = Synth(\bps)}.defer(0.1);
)

a.set(\bps,rrand(0.5,10).postln);   

// Tutto in secondi

(
SynthDef(\sec,
              {arg dur=1;
               var ksig,sig,bpf,env;
		   ksig = Dust.kr(dur.reciprocal); // durata env riscalata in bps
		   sig  = SinOsc.ar(1234);
                   bpf  = Env.new([0,1,0.3,0],[0.01,0.2,3],\cub);
		   env  = EnvGen.kr(bpf.duration_(dur),ksig,doneAction:0);     
               Out.ar(0,sig*env)
               }
          ).add;

{a = Synth(\sec)}.defer(0.1);
)

a.set(\dur,rrand(0.05,1).postln);   	

Inviluppi con sostegno

Per quanto riguarda questo tipo di inviluppi alcune modalità di generazione del trigger come l'utilizzo di devices esterni che prevedono già una fase di sostegno non danno alcun problema e possiamo mappare direttamente i valori sul parametro gate.

Per tutte le altre dovremo obbligatoriamente utilizzare le UGens Trig.ar() e Trig1.ar() oppure le diverse tecniche relative al superamento di una o più soglie. Osserviamo come sia possibile utilizzare queste due UGens sia con inviluppi con fase di sostegno che senza.

(
SynthDef(\nosust, {arg dur=0.125;
	           var sigk = SinOsc.kr(5),                        // Sine 5 hz
	               trig = Trig1.kr(sigk,dur),                  // trig ogni transizione
	               env  = Env.perc(0.1,1,1,-4).duration_(dur), // inviluppo percussivo
	               envi = EnvGen.kr(env,trig).scope;           // L'intensità
	           Out.ar(0,SinOsc.ar(600)*envi)
}).add;

{a = Synth(\nosust)}.defer(0.2);
)

a.set(\dur, 1);
a.set(\dur, 0.5);
a.set(\dur, 0.25);
a.set(\dur, 0.125);
a.set(\dur, 0.075);

//===============================

(
SynthDef(\sust, {arg dur=0.125;
	         var sigk = SinOsc.kr(5),              // Sine 5 hz
	             trig = Trig1.kr(sigk,dur),        // trig ogni transizione
	             env  = Env.asr(0.01,1, 0.2, -4),  // inviluppo con release Node
	             envi = EnvGen.kr(env,trig).scope; // L'intensità
	         Out.ar(0,SinOsc.ar(600)*envi)
}).add;

{a = Synth(\sust)}.defer(0.2);
)

a.set(\dur, 1);
a.set(\dur, 0.5);
a.set(\dur, 0.25);
a.set(\dur, 0.01);

Trigger all'Interprete

Possiamo inviare i valori di un trigger dal Server all'Interprete per poi utilizzarli come vogliamo, tipicamente per la visualizzazione su di una GUI oppure per controllare partenza e arresto di processi di sequencing o un counter.

Per farlo è però necessario filtrare la ridondanza di dati in quanto i valori 1 e 0 del trigger sono inviati a una rata di (sotto)campionamento.

(
var ora, prec = 0;                           // inizializza la variabile

SynthDef(\trigInt,
                   {var x,trig;
		        x    = MouseX.kr(-1,1);       // Tra -1 e 1
		        trig = Trig1.ar(x,1);         // Durata 1 secondo
		        SendReply.kr(Impulse.kr(50),  // rata di campionamento
		                     '/trig',         // indirizzo o nome
		                      trig);          // segnale da campionare
	            }
         ).add;

{a = Synth(\trigInt)}.defer(0.1);

// ------------------------------ OSC
OSCFunc.new({arg msg;
	         ora = msg[3];
// ------------------------------ Filtro
case 
   {(ora == 1 ).and(ora != prec)}{"noteOn".postln;  prec = ora}  // se == 1 e diverso dal precedente
   {(ora == 0 ).and(ora != prec)}{"noteOff".postln; prec = ora}; // se == 0 e diverso dal precedente
// ------------------------------
             },
		 '/trig',         // indirizzo o nome
		 s.addr);         // eventuale NetAddr
)	

ReSync UGens

Alcuni oscillatori come SyncSaw.ar() possono essere re-triggerati (resync), ovvero la fase viene resettata forzatamente. Questo produce un effetto simile alla pulse width modulation. Gli argomenti di SyncSaw sono: SyncSaw.ar(sincFreq, sawFreq ) dove il primo indica la frequenza della fondamentale, il secondo la frequenza dell'onda a dente di sega che effetua il resync. La seconda deve essere sempre maggiore della prima.

s.scope(1);

(
{[SyncSaw.ar(800, 1200),
  Impulse.ar(400)]}.plot // solo per visualizzare la rata di sync
)

{SyncSaw.ar(100, Line.kr(100, 800, 12), 0.1) }.play;            // Armonici...
{SyncSaw.ar(MouseX.kr(100,400), MouseY.kr(400,800), 0.1)}.play;	

In generale gli oscillatori che usano un resync sono di due tipi:

Attraverso la tecnica del resync possiamo utilizzare un inviluppo compreso tra +/- 1.0 come forma d'onda (wavetable) e un segnale di trigger per controllarne la frequenza. Nell'esempio seguente se muoviamo il mouse:

(
{var samptosec = [0,128,256,128]/SampleRate.ir, // da sample a secondi
     twind     = MouseY.kr(0,1)*samptosec,      // interazione col mouse
     func      = Env([0,0,1,-1,0], twind),      // Inviluppo = 1 ciclo di onda a dente di sega
     trig  = Impulse.ar(MouseX.kr(10,300,'exponential')!2); // Sync trigger

EnvGen.ar(func,trig)
}.play
)