Modelli strumentali¶
Indice¶
import os
Modelli strumentali ¶
Abbiamo ora tutti gli elementi per prototipare due possibili modelli di strumento virtuale.
Questi modelli possono servire come schema sintattico per tutte le tecniche di sintesi ed elaborazione del suono.
- strumenti che generano un singolo evento sonoro.
- strumenti che generano o modificano un flusso sonoro.

SuperCollider ¶
Prima tipologia.
ServerOptions.devices;
s.options.device_("nome device"); // Imposta device
s.boot;
s.meter; // Monitor visivi
s.scope;
s.plotTree;
(
SynthDef(\notan,
{arg freq=900, amp=0, dur=1, pan=0, tpan=0, t_gate=0, done=2;
var sig, env;
sig = Saw.ar(freq);
env = Env.new([0,1,0.5,0.5,0], [0.1,0.1,0.4,1].normalizeSum*dur, -4);
env = EnvGen.kr(env, t_gate, doneAction:done);
sig = sig * env * amp;
sig = Pan2.ar(sig, pan.lag(tpan));
Out.ar(0, sig)
}).add;
SynthDef(\notas,
{arg freq=900, amp=0, pan=0, tpan=0, gate=0, done=2;
var sig, env;
sig = Saw.ar(freq);
env = Env.new([0,1,0.5,0], [0.1,0.1,1], -4, 2);
env = EnvGen.kr(env, gate, doneAction:done);
sig = sig * env * amp;
sig = Pan2.ar(sig, pan.lag(tpan));
Out.ar(0, sig)
}).add;
)
// ------------------- Esempio polifonico senza sostegno
(
~dur = rrand(0.1,4);
Synth(\notan, [\freq, rrand(80,92).midicps,
\amp, rand(127) / 127,
\dur, ~dur,
\pan, rand2(1.0),
\tpan, ~dur,
\done, 2,
\t_gate, 1])
)
// ------------------- Esempio monofonico con sostegno
a = Synth(\notas, [\done, 0]);
a.set(\freq,rrand(800,1235),\amp,rand(0.5),\pan,rand2(1.0),\tpan,3,\gate, 1)
a.set(\gate,0);
a.release(4); // Come gate:0 modificando il tempo di fadeout
a.free;
Seconda tipologia
(
SynthDef(\fades,
{arg freq=900, amp=0, atk=0.1, rel=1, pant=1, panmin= -1, panmax=1, gate=0, done=2;
var sig, env, range, offset, lfo;
sig = Saw.ar(freq);
env = Linen.kr(gate,atk,1,rel,doneAction:done);
range = (panmax - panmin) / 2;
offset = panmin + range;
lfo = SinOsc.kr(pant/2,-0.5pi,mul:range,add:offset);
sig = sig * env * amp;
sig = Pan2.ar(sig, lfo);
Out.ar(0, sig)
}).add;
)
a = Synth(\fades, [\freq,rrand(800,1235),\amp,rand(0.5),\atk,2,\rel,4,\done,2]);
a.set(\gate, 1);
a.set(\pant, 4);
a.set(\panmax, 0.1);
a.set(\panmin, -0.3);
a.set(\gate,0);
Pure Data ¶
In PD dobbiamo ulteriormente formalizzare la struttura dei patch attraverso le astrazioni.
Astrazioni ¶
Le astrazioni sono simili ai subpatches.
Principale differenza:
- subpatches $\rightarrow$ interni al patch principale e salvati con esso.
- astrazioni $\rightarrow$ veri e propri patches indipendenti con inlets e outlets che possiamo richiamare all'interno di altri patches.
- Per crearle scriviamo il nome in un object box.
- Possiamo specificare argomenti che sono passati agli oggetti interni attraverso un indice (#1, #2, #3, etc.).
- nei send e receive interni se specifichiamo come indirizzo #0 otteniamo variabili locali definite da un numero assegnato automaticamente.
Utilizzare un patch come astrazione:
- $\rightarrow$ salvarlo nella stessa cartella
- specificare dove si trova (path) in $\rightarrow$ preferenze $\rightarrow$ Preferenze patch.

A questo punto possiamo adottare la seguente strategia di programmazione:
- strumenti virtuali $\rightarrow$ salvati come astrazioni con la stessa struttura (come le ___SynthDef___ in SuperCollider).
- i processi comuni agli strumenti (come inviluppi e panning) $\rightarrow$ inclusi in subpatches interni alle astrazioni.
Nell'immagine seguente il patch salvato come myinstr che possiamo utilizzare come astrazione richiamandolo in altri patches.

os.system('open 8_modelli/patch/myinstr.pd')
0
Tre strumenti virtuali uguali (tre istanze) controllabili separatamente.

La seconda tipologia in Max la affronteremo nel paragrafo seguente.
os.system('open 8_modelli/patch/8.1.pd')
0
Polifonia e allocazione delle voci ¶
Musicalmente possiamo utilizzare i modelli strumentali appena esposti in sequenze monofoniche oppure polifoniche.
Per quanto riguarda la gestione della polifonia a livello informatico ci sono due possibilità.
- allocazione statica delle voci ovvero ogni istanza di strumento ha un proprio nome o indice e inviamo i parametri a quel nome o indice.
- allocazione dinamica delle voci ovvero l'istanza esiste solo per il tempo necessario dopodichè si autodistrugge (doneAction:2 in SuperCollider)
SuperCollider ¶
Gli inviluppi e nello specifico la loro caratteristica di poter distruggere automaticamente le istanze di Synth al termine del loro percorso attraverso l'argomento ___doneAction: n___ sono le UGens più adatte a controllare la polifonia ottimizzando le prestazioni del computer.
Principalmente abbiamo a disposizione tre possibilità:
- monofonia con allocazione diretta delle voci (direct voce allocation).
- polifonia con allocazione statica delle voci (static voice allocation).
- polifonica con allocazione dinamica delle voci (dynamic voce allocation).
Quest'ultima da affrontare con diverse strategie di programmazione a seconda del tipo di inviluppo.
Senza sostegno ¶
Per questa tipologia di inviluppi possiamo realizzare strumenti monofonici o polifonici con allocazione dinamica delle voci.
s.boot;
s.meter;
s.plotTree;
(
SynthDef(\mono, {arg freq=789, amp=0, dur=0.2, pan=0, t_gate=0, done=2;
var sig, env;
sig = SinOsc.ar(freq);
env = Env.perc(0.1*dur, 0.9*dur);
env = EnvGen.kr(env,t_gate,doneAction:done);
sig = sig * env * amp;
sig = Pan2.ar(sig, pan);
Out.ar(0, sig)
}).add
)
// ------------------- Monofonico
a = Synth(\mono); // Creo un'istanza
a.set(\freq,rrand(700,1200),\amp,0.5,\done,0,\t_gate,1); // done:0 invio i parametri
a.free; // La distruggo
// ------------------- Polifonico con allocazione dinamica
Synth(\mono, [\freq, rrand(700, 1200),\amp,0.1,\dur,2,\done,2,\t_gate,1]);
Funzioni¶
In SuperCollider tutto ciò che è racchiuso tra parentesi graffe è una funzione.
Costrutto sintattico che definisce una sequenza di istruzioni o operazioni.
Determinati input $\rightarrow$ output.
I valori in uscita (output) sono in funzione (dipendono) di quelli in entrata (input).
Servono principalmente per evitare di dover riscrivere molte volte blocchi di codice che si ripetono esattamente come le variabili.
Le variabili contengono tipi di data mentre le funzioni sequenze di operazioni.
Inputs $=$ argomenti.
Per eseguire una funzione dobbiamo invocare il metodo ___.value( )___ (valuta).
Passiamo gli argomenti alla funzione come argomenti di questo metodo.
(
a = {arg a, b; // Argomenti (input)
var somma, moltiplica; // Variabili locali
somma = a + b; // Corpo della funzione (operazioni sugli input)
moltiplica = a * b;
"somma:"+somma+ "moltiplica:"+moltiplica // Output (ultimo elemento)
}
)
a.value(12, 3);
Notiamo come gli algoritmi di sintesi e elaborazione del suono nelle ___SynthDef___ siano funzioni.
Parametri $\rightarrow$ inputs
Segnale $\rightarrow$ output.
Con sostegno ¶
Strumenti monofonici o polifonici $\rightarrow$ allocazione statica delle voci.
Indicizzare i messaggi di note on (gate:1) inviati per poter poi inviare quelli di note off (gate:0) al Synth corrispondente.
Strategia $\rightarrow$ mappare i valori delle altezze (midinote o frequenze) con gli indici di un'array per poi sostituirne dinamicamente gli elementi.
Se l'elemento legato all'indice che corrisponde all'altezza è un istanza di ___Synth___ triggeriamo il release e lo sostituiamo con ___'nil'___.
Se invece è ___'nil'___ generiamo una nuova istanza di ___Synth___ e la sostituiamo a ___'nil'___.
Definiamo una funzione che si occupi di gestire le operazioni necessarie.
(
SynthDef(\monos, {arg freq=789, amp=0, pan=0, gate=0, done=2;
var sig, env;
sig = SinOsc.ar(freq);
env = Env.adsr(0.01,0.3,0.5,1);
env = EnvGen.kr(env, gate,doneAction:done);
sig = sig * env * amp;
sig = Pan2.ar(sig, pan);
Out.ar(0, sig)
}).add
)
// ------------------- Monofonico
a = Synth(\monos); // Creo un'istanza
a.set(\freq,rrand(700,1200),\amp,0.5,\done,0,\gate,1); // done:0 invio i parametri
a.set(\gate,0);
a.free; // La distruggo
// ------------------- Polifonico con allocazione statica
(
~note = Array.newClear(128); // Array vuoto di 128 elementi (tutte le note midi)
// Tutti gli elementi sono 'nil'
~freqs = Array.newClear(15000); // Oppure con le frequenze in Hertz...
~alloca = {arg pitch; // Funzione
var el;
el = ~note.at(pitch); // Richiama l'elemento corrispondente alla midi note
// assegnandolo alla variabile locale
if(el.notNil, // Se il contenuto della variabile non è 'nil' (sta suonando)
{el.set(\gate,0); // 1. Triggera il release di quell'istanza di Synth
~note.put(pitch, nil)}, // 2. la sostituisce con 'nil' all'indice corrispondente
// Se invece l'elemento è 'nil'
{el = Synth(\monos, // 1. Assegna alla variabile una nuova istanza di Synth
[\freq,pitch.midicps,
\amp, 0.1,
\done, 2,
\gate, 1]);
~note.put(pitch, el)} // 2. sostituisce 'nil' con la nuova istanza di Synth
)
};
)
~alloca.value(60); // Note on
~alloca.value(64);
~alloca.value(67);
~alloca.value(72);
~alloca.value(64); // Note off
~alloca.value(67);
~alloca.value(72);
~alloca.value(60);
Quasta strategia risulta molto utile quando utilizziamo controller esterni come una tastiera midi.
Pure Data ¶
In PD la polifonia è gestita principalmente con l'oggetto clone~ ed è con un'allocazione statica delle voci.
clone ¶
Accetta al suo interno un numero finito di astrazioni.
Possiamo accendere o spegnere la computazione audio delle singole istanze risparmiando di fatto risorse della CPU inviando un messaggio all'oggetto switch~.
Astrazione da caricare in clone:

os.system('open 8_modelli/patch/myclone.pd')
0
Primo argomento $\rightarrow$ nome dell'astrazione.
Secondo argomento $\rightarrow$ numero di istanze.

os.system('open 8_modelli/patch/8.2.pd')
0
Nell'astrazione precedente la computazione audio è accesa o spenta con un apposito messaggio (pow).
In alcuni casi possiamo automatizzare questa operazione mappandola sull'ampiezza.
Ritardiamo anche il muting della durata del fadeout per evitare clicks.

os.system('open 8_modelli/patch/8.3.pd')
0
Se l'evento sonoro prevede un inviluppo d'ampiezza possiamo sfruttare l'output di destra di envgen~.

os.system('open 8_modelli/patch/mycloneEnv.pd')
0
Senza sostegno ¶
Definiamo una sintassi che ci permetta un controllo sia monofonico che polifonico delle voci.

os.system('open 8_modelli/patch/8.4.pd')
0
Con sostegno ¶
Utilizziamo poly~ per automatizzare le voci.
Il flag -s 1 in clone fa cominciare gli indici da 0 e non da 1 per compatibilità con poly.

os.system('open 8_modelli/patch/8.5.pd')
0