Formalizzazioni e messaggi¶
Indice¶
import os
Formalizzazioni Indice
Abbiamo ora le conoscenze di base per cominciare a progettare e realizzare uno strumento virtuale.
La procedura da seguire.
formalizziamo il pensiero in un algoritmo di sintesi e/o elaborazione del suono definendo sia il percorso dei segnali che i parametri di controllo e le modalità attraverso le quali agiremo su di essi (tecnica strumentale).
definiamo attraverso un diagramma di flusso le istruzioni necessarie alla costruzione di più copie dello strumento in modo che si possano realizzare con qualsiasi software passato, presente e futuro.
programmiamo con il software di nostra scelta un modello (stampo) dello strumento secondo le istruzioni presenti nel diagramma di flusso.
ricaviamo dal modello definito nel punto precedente le copie dello strumento di cui abbiamo bisogno.
modifichiamo secondo le modalità definite nel primo punto (tecniche strumentali) i parametri di controllo delle singole copie.
Possiamo intuire che questa procedura non è poi così dissimile da quella adottata da un liutaio nella costruzione di uno strumento acustico reale.
Introduzione ai diagrammi di flusso ¶
Come primo strumento decidiamo di progettare un semplice sintetizzatore monofonico che produce un suono sinusoidale del quale possiamo controllare frequenza, ampiezza e fase in precisi istanti nel tempo (oneshot).
Abbiamo bisogno dei seguenti elementi:
- un oscillatore per la generazione dell'onda del quale si possano controllare frequenza e fase.
- un amplificatore con il quale controllare l'ampiezza.
- un bus audio al quale inviare il segnale numerico per la conversione analogico/digitale.
Definiti gli elementi necessari li dobbiamo collegare tra loro per realizzare una catena audio (audio chain).
Per farlo disegnamo un diagramma di flusso.
Possiamo disegnarlo a mano oppure utilizzare draw.io sia nella versione online che in quella desktop

Con le istruzioni rappresentate nel grafico possiamo costruire uno strumento analogico oppure programmare attraverso un un software a nostra scelta uno strumento virtuale.
Realizziamone uno in SuperCollider e uno in Pure Data.
SynthDef e Synth in SuperCollider¶
Memorizziamo nel Server un modello di strumento attraverso la classe SynthDef (Synth Definition).
Questa classe deve contenere le istruzioni necessarie alla costruzione di un Synth non sotto forma grafica come nei diagrammi di flusso ma in un linguaggio comprensibile dal software.
La struttura sintattica.
s = Server.local;
s.boot;
(
SynthDef.new( // Nuova istanza di SynthDef
\sine, // Nome del modello di strumento
{ // Algoritmo di sintesi
arg freq=600, amp=0 fase=0; // Argomenti
var sig; // Variabili locali
sig = SinOsc.ar(freq,fase); // Oscillatore
sig = sig * amp; // Amplificatore (variabile riassegnata)
Out.ar(0, sig); // Segnale in uscita
}
).add; // Invia le istruzioni al Server
)
- il metodo ___.new___ genera una nuova istanza (copia) di una classe e può essere invocato su (quasi) tutte le classi. Può essere sottinteso.
- diamo un nome allo strumento sotto forma di simbolo o stringa.
- definiamo l'algoritmo di sintesi all'interno di una funzione (tra parentasi graffe).
- gli argomenti rappresentano i valori dei parametri che vogliamo poter controllare dall'esterno esattamente come quelli delle UGens. Sono preceduti dalla keyword arg, separati da virgole e terminano con un punto e virgola. Possiamo assegnare dei valori di default.
- la variabili locali sono simili a quelle globali o d'ambiente con la differenza che il loro nome e il dato a loro assegnato valgono solamente all'interno delle parentesi entro le quali sono state dichiarate (scope della variabile). Possiamo definirle con lettere o parole e sono precedute dalla keyword arg, separate da virgole e terminano con un punto e virgola. Nel corso della computazione possono essere riassegnate come in questo caso sig.
- la UGen ___Out.ar___ accetta come primo argomento il numero di un bus audio (0 = canale sinistro) sul quale scrivere il segnale specificato come secondo argomento).
- il metodo ___.add___ aggiunge la SynthDef al Server in uso che deve essere acceso (booted).
Per visualizzare tutte le SynthDef presenti in un Server da un'interfaccia grafica.
SynthDescLib.global.read.browse;

Ora che il Server ha le istruzioni necessarie alla costruzione di questo modello di strumento (ricordiamoci di valutare il blocco di codice della SynthDef) ne ricaviamo tante copie quante ne vogliamo attraverso la classe ___Synth___ specificandone il nome.
Synth.new(\sine); // Copia
Ogni istanza (copia) di Synth si chiama Node.
Per monitorare quanti Nodi ci sono nel Server apriamo l'interfaccia Node Tree.
s.plotTree;
Se valutiamo più volte la linea di codice del Synth vedremo comparire tutte le copie generate una sopra l'altra.

Quando utilizziamo la sintassi {UGen.ar}.play per far suonare una o più UGens in realtà stiamo scrivendo un'abbreviazione sintattica e chiediamo a SuperCollider di generare per noi una SynthDef e un Synth temporanei.
Per quanto possa sembrare meno macchinoso utilizziamo questa sintassi solo per prototipare algoritmi oppure come monitor uditivo e definiamo sempre noi SynthDef e Synth.
Possiamo cancellare tutti i Nodi nel Server sia interrompendo la computazione con cmd + . che invocando il metodo
s.freeAll;
os.system('open 5_formalizzazioni/patch/5.1.pd')
0
Notiamo che gli object box hanno degli argomenti di default.
Realizziamo un modello di strumento attraverso un subpatch.
- creiamo un nuovo object box e scriviamo p con un nome (esattamente come per il nome delle SynthDef.
- compare un nuovo patch interno al patch principale.
- utilizziamo inlet e outlet per far comunicare il contenuto del subpatch con l'esterno.

os.system('open 5_formalizzazioni/patch/5.2.pd')
0
Con un semplice copia e incolla generiamo tante copie indipendenti del subpatch quante ne vogliamo.

os.system('open 5_formalizzazioni/patch/5.3.pd')
0
Controllo attraverso messaggi¶
Abbiamo la possibilità di controllare i parametri degli strumenti che abbiamo costruito in diversi modi.
Uno consiste nell'inviare nuovi valori attraverso messaggi agli strumenti sia in SuperCollider che in Max.
SuperCollider
Assegnamo il singolo Synth a una variabile per contrassegnarlo con un indirizzo al quale inviare il messaggio.
Inviamo il messaggio con il metodo ___.set(\nome, valore)___.
s = Server.local;
s.boot;
(
SynthDef.new(\sine, {arg freq=600, amp=0;
var sig;
sig = SinOsc.ar(freq);
sig = sig * amp;
Out.ar(0, sig);
}).add;
)
a = Synth.new(\sine); // Istanza e indirizzo (variabile)
a.set(\freq, 567, \amp, 0.3); // nome_argomento, valore
a.set( \amp, 0.8);
a.set(\freq, 1278 );
a.set(\freq, 893, \amp, 0.2);
a.run(false); // Interrompe la computazione senza distruggere il Node
a.run(true); // Riprende la computazione
a.free; // Distrugge il Node
Possiamo impostare gli argomenti di un Synth anche al momento della sua creazione con la seguente sintassi.
a = Synth.new(\sine, [\freq, 987, \amp, 0.5]);
Pure Data
In PD ci sono due modi per collegare tra loro i diversi elementi:
- con patch cords
- wireless con gli oggetti send e receive.

os.system('open 5_formalizzazioni/patch/5.4.pd')
L'oggetto send può essere sostituito da un message box nella forma illustrata in figura dove il punto e virgola sta per send e il simbolo successivo è il nome del receive seguito dal valore o dai valori che vogliamo inviare.
Sfruttiamo questa caratteristica modificando il subpatch con l'oggetto route che separa i parametri e ci permette di indirizzarli agli inlet corretti.
In base a questa strategia definiamo una sintassi che ci permetta di inviare i diversi parametri comunicandoli alla singola copia dello strumento.

os.system('open 5_formalizzazioni/patch/5.5.pd')
0
Conversioni frequenziali¶
Usualmente gli oscillatori che abbiamo a disposizione nei software audio accettano valori assoluti di frequenza in hertz ma ci sono situazioni in cui potremmo volerli definire sotto forma di midinote.
In questi casi è necessario convertirli.
Formule
Da Hertz a MIDI e viceversa
$m = 12*log_{2}(f/440) + 69$
$f = 2^{(m−69)/12} * 440$
In SuperCollider
f = 440;
m = 12 * log2(f/440) + 69; // Sviluppando l'espressione
f.cpsmidi; // Con la funzione dedicata
m = 60;
f = 2**((m-69)/12) * 440; // Sviluppando l'espressione
m.midicps; // Con la funzione dedicata
In Pure Data

os.system('open 5_formalizzazioni/patch/5.6.pd')
0