[...] Così come la scrittura ha permesso di rappresentare e di tramandare stabilmente la storia e la cultura umana attraverso i secoli, anche nel campo musicale il sistema notazionale ha permesso alla musica stessa di fissare e tramandare le sue tappe evolutive contribuendo in modo fortemente sinergico al suo continuo processo di crescita. Nonostante l’incessante affinamento delle tecniche notazionali, la rappresentazione della musica non è mai stata in grado di trasferire in modo preciso tutta l’eventuale complessità sottesa nell’idea iniziale: non a caso l’interpretazione diventa quel valore aggiunto che può rendere più o meno compiuta, differenziata e differenziabile un’opera musicale.[...]
E.Giordani
Il succedersi di suoni nel tempo può essere rappresentato in diversi modi, a seconda delle ragioni o necessità che stanno alla base del tipo di rappresentazione stessa.
Per sopperire inizialmente ad una necessità mnemotecnica, la musica nella tradizione occidentale è stata tramandata utilizzando una notazione simbolica, la partitura musicale.
Questo tipo di notazione possiede una duplice valenza:
from IPython.display import HTML
HTML('<center><img src="media/partitura_2.png" width="55%"></center>')
from IPython.display import HTML
HTML('<center><img src="media/intavolatura.png" width="55%"></center>')
Anche se a prima vista può sembrare non esserci molta differenza pratica tra le due in quanto una partitura che rappresenta i parametri astratti del suono può anche essere letta da un cantante o uno strumentista che, codificando i simboli effettua i gesti necessari alla riproduzione dei suoni notati, in realtà sono idealmente differenti.
from IPython.display import HTML
HTML('<center><img src="media/tropo.png" width="60%"></center>')
I simboli utilizzati nel corso dei secoli sono mutati parallelamente al modificarsi dei linguaggi musicali, al sopraggiungere di nuove esigenze espressive e, non ultimo all'invenzione di nuovi strumenti musicali.
Lo studio della notazione musicale si chiama semiografia musicale.
Fase premensurale
from IPython.display import HTML
HTML('<center><img src="media/neumi.png" width="55%"></center>')
Fase mensurale
from IPython.display import HTML
HTML('<center><img src="media/mensurale.jpg" width="55%"></center>')
from IPython.display import HTML
HTML('<center><img src="media/vivaldi.png" width="55%"></center>')
from IPython.display import HTML
HTML('<center><img src="media/mozart.png" width="55%"></center>')
from IPython.display import HTML
HTML('<center><img src="media/mahler.png" width="55%"></center>')
from IPython.display import HTML
HTML('<center><img src="media/pierrot.png" width="65%"></center>')
from IPython.display import HTML
HTML('<center><img src="media/boulez.png" width="70%"></center>')
from IPython.display import HTML
HTML('<center><img src="media/cardew.png" width="60%"></center>')
from IPython.display import HTML
HTML('<center><img src="media/ligeti.png" width="60%"></center>')
Possiamo anche rappresentare i parametri fisico/acutici del suono slegando questa informazione dalle regole appartenenti a un qualsiasi linguaggio musicale e dall'azione attraverso la quale è stato generato.
Per quanto riguarda la musica elettroacustica e l'informatica musicale queste rappresentazioni sono fondamentali per monitorare la correttezza tecnica del lavoro quotidiano e possono diventare anche fonti di ispirazione creativa nella ricerca di nuovi suoni.
Un oscillogramma è la rappresentazione di una forma d'onda nel dominio del tempo.
from IPython.display import HTML
HTML('<center><img src="media/oscillo_1.png" width="30%"></center>')
Deriva dall'utilizzo di uno strumento elettroacustico (l'oscilloscopio) utilizzato per riprodurre e misurare le forme d’onda di segnali di tensione su di uno schermo illuminato sul quale è rappresentato un piano cartesiano.
from IPython.display import HTML
HTML('<center><img src="media/oscillo_2.png" width="40%"></center>')
Questo tipo di rappresentazione ci fornisce informazioni riguardo ampiezza e frequenza di un suono ma poco o nulla riguardo il timbro.
In realtà è una rappresentazione completa in quanto come abbiamo detto in precedenza è la forma d'onda che caratterizza il timbro, ma in un oscillogramma questo parametro è di difficile interpretazione salvo che in particolari casi come le forma d'onda classiche della musica elettroacustica.
from IPython.display import HTML
HTML('<center><img src="media/forme_osc.png" width="50%"></center>')
Per rappresentare le componenti spettrali e/o l'inviluppo spettrale (dominio delle frequenze) di un suono abbiamo tre possibilità:
attraverso un tipo di spettrogramma che analizza e visualizza il contenuto spettrale di un suono nel dominio della frequenza dove l'asse delle ascisse (x) corrisponde alle frequenze mentre l'asse delle ordinate (y) corrisponde alle ampiezze.
Può essere istantaneo ovvero la fotografia di un suono in un determinato momento oppure dinamico dove il grafico varia con il tempo e segue l'inviluppo spettrale.
Le frequenze sono espresse in Hertz mentre le ampiezze possono essere espresse in ampiezza lineare (tra 0 e 1), in dB (tra - inf e 0) oppure in dBspl (tra 0 e +inf).
from IPython.display import HTML
HTML('<center><img src="media/spettrogr.png" width="100%"></center>')
from IPython.display import HTML
HTML('<center><img src="media/sonografo.png" width="40%"></center>')
Questa è la forma di rappresentazione del suono più completa dove sull'asse delle ascisse (x) è indicato il tempo, sull'asse delle ordinate (y) la frequenza mentre i colori (o la gamma di grigi) rappresentano le ampiezze dei parziali.
from IPython.display import HTML
HTML('<center><img src="media/sonogramma.png" width="100%"></center>')
Come possiamo facilmente intuire dalle immagini, se la rappresentazione si basa sui colori i toni freddi come il blu scuro rappresentano suoni di bassa intensità mentre quelli gialli le alte intensità.
Nella scala di grigi il discorso è simile, il grigio chiaro corrisonde alle basse intensità e quelli scuri per le alte.
Osserviamo infine come le frequenze e le intensità possono essere riportate sia in scala lineare che logaritmica.
from IPython.display import HTML
HTML('<center><img src="media/waterfall.png" width="100%"></center>')
A questo link possiamo trovare ultriori approfondimenti riguardo le relazioni che intercorrono tra le diverse notazioni.
Con l'ingresso dell'elettronica e dell'informatica in ambito musicale si sono rese necessarie nuove tipologie di rappresentazioni.
Oltre a eventuali partiture musicali come quelle illustrate potremmo dover fornire agli esecutori o realizzatori ulteriori informazioni riguardo alle procedure da seguire per la corretta messa in opera.
Possiamo rappresentare graficamente tutte queste informazioni attraverso schemi a blocchi (block diagrams).
Quando in ambito musicale è previsto l’utilizzo di uno strumento elettroacustico o informatico, fornire questi diagrammi diventa fondamentale in quanto permettono la riproducibilità del brano nel tempo fornendo le istruzioni per realizzare l’idea musicale originale con qualsiasi tipo di software o dispositivo passato, presente o futuro alla stessa stregua delle partiture musicali.
Fra dieci, venti o cento anni infatti ci saranno nuove tecnologie e saranno inventati nuovi software e hardware, e se dovessimo legare la possibilità di eseguire un brano a una specifica tecnologia o software potrebbe non essere più possibile riprodurlo.
A questo link un software dedicato alla realizzazione di diagrammi di flusso.
In informatica storicamente si utilizzano diagrammi di flusso (flowchart) che possiamo considerare schemi a blocchi più specifici che servono a rappresentare graficamente il flusso di controllo di algoritmi, procedure e istruzioni operative.
from IPython.display import HTML
HTML('<center><img src="media/flow_2.png" width="30%"></center>')
Musicalmente possiamo impiegarli nell'ambito della composizione algoritmica sia nella generazione di partiture strumentali (anche senza parti elettroacustiche) sia nel controllo dei parametri di un qualche tipo di sintesi e/o elaborazione del suono.
from IPython.display import HTML
HTML('<center><img src="media/flow_1.png" width="100%"></center>')
Il linguaggio dei diagrammi di flusso è un possibile formalismo per la descrizione di algoritmi che descrive il flusso delle operazioni da eseguire per realizzare la trasformazione, definita nell’algoritmo, dai dati iniziali (input) ai risultati (output).
Ogni istruzione dell’algoritmo è rappresentata da un particolare simbolo detto blocco elementare la cui forma grafica è determinata dal tipo di istruzione.
I cinque tipi di blocchi più comuni sono.
from IPython.display import HTML
HTML('<center><img src="media/flow_4.png" width="60%"></center>')
Blocco iniziale. Ha forma ovale e viene posto all'inizio di ogni diagramma di flusso. Presenta sempre una sola linea di flusso in uscita.
Blocco finale. Ha forma ovale e viene posto alla fine di ogni diagramma di flusso. Presenta sempre una sola linea di flusso in entrata.
Blocco di lettura/scrittura. Ha forma trapezoidale e presenta i dati in ingresso oppure i risultati. Nel blocco entra una sola linea di flusso e ne esce un'altra.
Blocco di istruzioni. Ha forma rettangolare e contiene le istruzioni, i comandi e le operazioni che devono essere eseguite sui dati in ingresso. Nel blocco entra una sola linea di flusso e ne esce un'altra.
Blocco decisionale. Ha forma romboidale e permette di diramare il flusso a seconda del risultato di una operazione condizionale (vero/falso/nullo). Nel blocco entra una sola linea di flusso e ne escono due.
I blocchi sono collegati tra loro da linee di flusso, munite di frecce, che indicano il susseguirsi di azioni elementari.
Un diagramma di flusso minimo è composto dai seguenti elementi:
Un diagramma di flusso deve soddisfare le seguenti condizioni.
Il diagramma seguente ad esempio descrive una procedura per filtrare altezze appartenenti a una scala pentatonica da una scala cromatica.
from IPython.display import HTML
HTML('<center><img src="media/flow_3.2.png" width="20%"></center>')
Nell'immagine seguente possiamo trovare un’elenco dei blocchi elementari meno comuni.
from IPython.display import HTML
HTML('<center><img src="media/flow_8.png" width="35%"></center>')
Ogni procedura descrive una sequenza di operazioni che devono essere effettuate su dei dati.
Queste operazioni vengono effettuate attraverso diverse tipologie di operatori.
In informatica un operatore è un simbolo che specifica quale legge applicare a uno o più operandi per ottenere un risultato.
Gli operatori possono essere classificati in base al numero di operandi che accettano, ovvero in base al numero di dati su cui lavorano.
Gli esempi seguenti sono in SuperCollider e Max in quanto rappresentativi dei due principali paradigmi di programmazione.
Operatori unari.
Possiamo suddividerli in tre tipologie.
#[5,-4,7,-2,3].neg; // inversione di segno
-12.5.abs; // valore assoluto
3.14.frac; // restituisce i valori float
3.74.trunc; // restituisce i valori int
3.sqrt; // radice quadrata
3.log; // logaritmo naturale
2.log10; // logaritmo in base 10
0.7.sin; // seno
2.cos; // coseno
60.midicps; // da midi a cps (Hz)
440.cpsmidi; // da cps a midi
-12.dbamp; // da Decibel ad ampiezza lineare
0.9.ampdb; // da ampiezza lineare a Decibel
In Max sono gli oggetti con un solo inlet.
from IPython.display import HTML
HTML('<center><img src="media/max_unari.png" width="40%"></center>')
Operatori binari.
Questo tipo di operatori agisce su due elementi e possiamo suddividerli in quattro tipologie.
2 + 3;
2 * 3;
2 - 3;
2 / 3;
5 % 3; // restituisce il resto della divisione (modulo)
2**3; // esponente (n elevato alla n)
pow(2, 3) // diversa scrittura
2 pow: 3; // diversa scrittura
2.pow(3) // diversa scrittura
// ------------------------ Varibili globali
a = 23; // Lettere
~accidenti = 231.3 // Parole
// ------------------------ Variabili locali
var a = 23, ciao = 23.4; // Lettere e parole
// ------------------------ Argomenti di una funzione
5.do({arg i; i.postln});
5.do({|i| i.postln}); // Altra scrittura
2 > 3;
3 > 3;
4 > 3;
2 >= 3;
3 >= 3;
4 >= 3;
2 < 3;
3 < 3;
4 < 3;
2 <= 3;
3 <= 3;
4 <= 3;
2 == 3;
3 == 3;
(2 != 3).asInteger; // trasforma in int (1/0)
(3 != 3).asInteger;
1.asBoolean; // trasforma in Boolean
4.asBoolean;
0.asBoolean;
-3.asBoolean;
// ---------- Dichiariamo tre variabili globali
a = 5 > 2; // true
b = 7 < 9; // true
c = 7 < 5; // false
a && b; // AND
a && c;
a || b; // OR
a || c;
c || c;
a.not; // NOT
c.not;
from IPython.display import HTML
HTML('<center><img src="media/max_binari.png" width="45%"></center>')
Operatori ternari.
Questo tipo di operatori agisce su tre elementi. Il più utilizzato è if.
// if (test, {esegui_se_true}, {esegui_se_false});
(
var a, z;
a = 10.rand;
z = if (a < 5, {75},{1000});
a.postln;
z.postln;
)
from IPython.display import HTML
HTML('<center><img src="media/max_ternario.png" width="20%"></center>')
La programmazione strutturata è un paradigma che favorisce la descrizione di algoritmi facilmente documentabili e comprensibili.
Un diagramma a blocchi strutturato ha tre possibili schemi di flusso.
from IPython.display import HTML
HTML('<center><img src="media/flow_6.png" width="50%"></center>')
~in = [0.25, 2]; // INPUT
(
~val1 = rrand(~in[0],~in[1]); // Scegli 4 valori nel range
~val2 = rrand(~in[0],~in[1]);
~val3 = rrand(~in[0],~in[1]);
~val4 = rrand(~in[0],~in[1]);
~seq = [~val1, ~val2, ~val3, ~val4];
~seq = [~val1, ~val2, ~val3, ~val4].sort; // Ordinali dal più corto al più lungo
~seq = ~seq.reverse; // Leggi dalla fina all'inizio
~seq = ~seq * 2; // Raddoppia i valori
)
~out = ~seq; // OUTPUT
from IPython.display import HTML
HTML('<center><img src="media/max_seq.png" width="15%"></center>')
from IPython.display import HTML
HTML('<center><img src="media/cond_1.png" width="50%"></center>')
(
~seq = [0.25,0.75,1]; // INPUT
~val = ~seq[0];
if(~val == 0.25, {~seq.put(0, ~val*4)}); // Vero
~val = ~seq[1];
if(~val == 0.25, {~seq.put(1, ~val*4)}); // Falso
~val = ~seq[2];
if(~val == 0.25, {~seq.put(2, ~val*4)}); // Falso
~seq; // OUTPUT
)
(
~seq = [0.25,0.75,1]; // INPUT
~val = ~seq[0];
if(~val == 0.25, {~seq.put(0, ~val*4)}, {~seq.put(0, ~val-0.125)}); // else..
~val = ~seq[1];
if(~val == 0.25, {~seq.put(1, ~val*4)}, {~seq.put(1, ~val-0.125)});
~val = ~seq[2];
if(~val == 0.25, {~seq.put(2, ~val*4)}, {~seq.put(2, ~val-0.125)});
~seq; // OUTPUT
)
from IPython.display import HTML
HTML('<center><img src="media/max_cond_2.png" width="45%"></center>')
Nei diversi linguaggi di programmazione ci sono in genere tre situazioni.
(
a = 10.rand;
z = if (a < 5, {75},{1000});
a.postln;
z.postln;
)
from IPython.display import HTML
HTML('<center><img src="media/if_max.png" width="75%"></center>')
(
a = 5.rand;
z = switch (a,
2, {200},
1, {500},
0, {600},
3, {400},
4, {800}
);
a.postln;
z.postln;
)
from IPython.display import HTML
HTML('<center><img src="media/gate_max.png" width="65%"></center>')
(
a = 13.rand;
z = case{a >= 0 && a <= 6} {200}
{a == 7} {50}
{a >= 8 && a <= 12} {400};
a.postln;
z.postln;
)
from IPython.display import HTML
HTML('<center><img src="media/route_max.png" width="60%"></center>')
Schema di iterazione. Permette la ripetizione di certi passi un numero arbitrario di volte (loop).
Viene spesso utilizzato per iterare gli elementi di una collezione.
from IPython.display import HTML
HTML('<center><img src="media/flow_7.png" width="50%"></center>')
Nella maggior parte dei linguaggi ci sono almeno due oggetti dedicati.
for(3, 7, {arg i; i.postln}); // Test esterno
7.do({arg i; i.postln}); // i = Indici
[3,4,5,6,7].do({arg i; i.postln}); // i = Elementi
[3,4,5,6,7].do({arg i, id; [id, i].postln}); // i = Elementi, id = Indici
from IPython.display import HTML
HTML('<center><img src="media/iter_max.png" width="38%"></center>')
(
i=0; // Init contatore
while ({i <= 8}, // Test interno
{i.postln;
i = i + 1}); // Aggiorna contatore
)
from IPython.display import HTML
HTML('<center><img src="media/while_max.png" width="23%"></center>')
Il teorema di Böhm-Jacopini afferma che qualsiasi algoritmo può essere implementato seguendo questi tre schemi.
Le tre strutture possono essere concatenate o nidificate, ma non possono essere intrecciate o accavallate.
// -------------------- Concatenati (uno DOPO l'altro)
(
~in = [0.25,2];
~seq = 4.collect({rrand(~in[0],~in[1])}).sort.reverse * 2; // Concatenati da sx a dx...
)
// -------------------- Nidificati (uno DENTRO un altro)
(
~seq = [0.25,0.75,1];
~seq.do({arg i,id; // Schema di iterazione
if(i == 0.25, {~seq.put(id, i*4)}); // Schema di selezione
});
)
Possiamo rappresentare graficamente anche il percorso che deve compiere un segnale audio per realizzare un algoritmo di sintesi o di elaborazione del suono rendendolo di fatto realizzabile attraverso qualsiasi tipo di software o hardware che lo consenta.
from IPython.display import HTML
HTML('<center><img src="media/flow_sig1.png" width="28%"></center>')
Se la realizzazione viene effettuata attraverso un software in questo diagramma va specificato esclusivamente il percorso che i segnali compiono all'interno di esso.
Gli eventuali inputs e gli outputs saranno i segnali in ingresso e in uscita dalla scheda audio mentre il percorso degli stessi che va oltre la scheda audio dovrà essere illustrato in un altro diagramma riguardante il routing e la disposizione dei microfoni (eventuali) e degli altoparlanti.
Per poter rappresentare e leggere correttamente questo percorso dobbiamo adottare un insieme di simboli standardizzato e codificato.
Purtroppo però ancora oggi non esiste una codifica universalmente adottata come per altri ambiti ma ci sono alcune convenzioni grafiche utilizzate in importanti pubblicazioni ormai storiche riguardanti l'informatica musicale che si prestano maggiormante di altre a rappresentare elementi comuni a quasi tutti gli algoritmi di sintesi.
Simboli che indicano le possibili comunicazioni con il mondo esterno al software.
from IPython.display import HTML
HTML('<center><img src="media/InOut.png" width="18%"></center>')
I seguenti simboli indicano la somma e la moltiplicazione tra due o più segnali e i punti di connessione o ponte tra i collegamenti.
from IPython.display import HTML
HTML('<center><img src="media/Conn.png" width="18%"></center>')
I seguenti simboli indicano interruttori e ripartitori del segnale.
from IPython.display import HTML
HTML('<center><img src="media/Switch.png" width="18%"></center>')
I seguenti simboli indicano potenziometri (fader o knob) e amplificatori.
from IPython.display import HTML
HTML('<center><img src="media/Ampli.png" width="20%"></center>')
Il simbolo comunemente adottato per gli oscillatori può essere impiegato per tutti i tipi di generatori di suono.
from IPython.display import HTML
HTML('<center><img src="media/Oscillatori.png" width="45%"></center>')
Nella prima e seconda riga sono rappresentati gli oscillatori a forma d'onda fissa più comuni, nella terza un generatore di rumore, un generatore di segnali di controllo (LFO) e un player di Buffer o sound files. Infine nell'ultima riga un oscillatore tabellare generico.
I seguenti simboli possono essere utilizzati per indicare supporti specifici per dati audio.
from IPython.display import HTML
HTML('<center><img src="media/Memorie.png" width="60%"></center>')
I seguenti simboli possono essere utilizzati per indicare qualsiasi tipo di inviluppo e Break Point Function.
from IPython.display import HTML
HTML('<center><img src="media/Env.png" width="40%"></center>')
I seguenti simboli possono essere utilizzati per indicare qualsiasi tipo di filtro ed equalizzatore, nonchè transfert function.
from IPython.display import HTML
HTML('<center><img src="media/Filtri.png" width="60%"></center>')
I seguenti simboli possono essere utilizzati per indicare qualsiasi tipo di modulo generico impiegato per l'elaborazione del segnale come delay, pan, etc.
from IPython.display import HTML
HTML('<center><img src="media/Moduli.png" width="25%"></center>')
Il percorso dei segnali può andare sia dall'alto verso il basso.
from IPython.display import HTML
HTML('<center><img src="media/synth_dig.png" width="40%"></center>')
Sia da sinistra a destra.
from IPython.display import HTML
HTML('<center><img src="media/synth_gen.png" width="75%"></center>')
in questo caso i simboli mantengono l'aspetto orizzontale ma cambia il lato di ingresso dei parametri di controllo.
Schemi a blocchi di questo tipo sono tipicamente utilizzati anche nella rappresentazione delle caratteristiche di una scheda audio.
from IPython.display import HTML
HTML('<center><img src="media/ucx.png" width="100%"></center>')
Oppure di catene elettroacustiche dedicate all'elaborazione di strumenti acustici in tempo reale.
from IPython.display import HTML
HTML('<center><img src="media/nopie.png" width="100%"></center>')
Questo tipo di diagrammi serve a indicare tutte le informazioni necessarie alla corretta messa in scena di un lavoro e prevede sia la collocazione del pubblico e di eventuali esecutori che di tutta la strumentazione elettroacustica necessaria all'acquisizione, trasmissione e diffusione dei segnali audio.
Le immagini seguenti illustrano tre schemi di questo tipo in ordine crescente di complessità e detttaglio nelle indicazioni.
from IPython.display import HTML
HTML('<center><img src="media/stage_1.png" width="60%"></center>')
from IPython.display import HTML
HTML('<center><img src="media/stage_2.png" width="80%"></center>')
from IPython.display import HTML
HTML('<center><img src="media/flow_outs3.png" width="100%"></center>')