Sei sulla pagina 1di 26

Linguaggi di programmazione

Elaborato di supporto agli esami di


Linguaggi di Programmazione

Indice

Linguaggi di Programmazione
- Introduzione...................................................................... pag...2
- Il linguaggio macchina................................................................................"........2
- Assembly...................................................................................................."........3
- La traduzione.............................................................................................."........3
- Linguaggi di alto livello.............................................................................."........4
- Paradigmi di programmazione...................................................................."........5
- Proprietà dei linguaggi................................................................................"........7
- Cenni storici................................................................................................"........9
- Sintassi e semantica...................................................................................."........10
- Il Binding...................................................................................................."........11
- Le variabili.................................................................................................."........12
- Le routine...................................................................................................."........13
- Programmazione orientata agli oggetti........................................................"........14
- Linguaggi di Programmazione Visuale (V.L.P).........................................."........16
SuperCollider
- Cenni generali............................................................................................."........17
- Cenni storici e Disponibilità rispetto ai sistemi operativi............................"........18
- Interfacce grafiche e Ambiente di sviluppo................................................."........18
CSound
- Cenni generali............................................................................................."........20
- Cenni storici................................................................................................"........20
- Funzionamento............................................................................................"........20
- Costanti e variabili......................................................................................"........21
- La sintassi di Csound.................................................................................."........21
Max MSP
- Cenni generali............................................................................................."........23
- Categorie di oggetti....................................................................................."........23
- Concetti fondamentali: patch, connessioni, oggetti....................................."........24
- Bibliografia – sitografia.............................................................................."........26

1
Linguaggi di programmazione

Linguaggi di programmazione
Introduzione
La programmazione può essere definita come la disciplina che demanda al calcolatore
elettronico la soluzione di un determinato problema. Un linguaggio di programmazione è, di
conseguenza, lo strumento utilizzato per scrivere programmi che realizzano algoritmi 1. Da un certo
punto di vista, un linguaggio di programmazione può essere immaginato come il linguaggio che
consente la comunicazione tra l’uomo (il programmatore) e il calcolatore. Il programmatore, infatti, ha
davanti a sé un problema da risolvere e vuole che il calcolatore lo faccia per lui. Naturalmente, il
calcolatore non è in grado di farlo da solo, ma ha bisogno che il programmatore lo istruisca su come
risolvere il problema in questione. Il programma, quindi, non è altro che la traduzione di un algoritmo
di soluzione per il problema nel linguaggio comprensibile al calcolatore. Il programmatore deve
inoltre comunicare al calcolatore i dati del problema da risolvere, mentre quest’ultimo si preoccuperà
di calcolare la soluzione e restituirla come risultato finale.

Il linguaggio macchina
Ogni calcolatore è in grado di comprendere un
particolare linguaggio di programmazione di basso livello
detto linguaggio macchina, il cui testo è una sequenza di bit
che il processore interpreta, secondo un funzionamento
dettato dalla sua struttura fisica, eseguendo una sequenza di
azioni. Per comprendere meglio la struttura e le
caratteristiche del linguaggio macchina, richiamiamo nella
figura l’architettura della macchina di von Neumann che
costituisce lo schema fondamentale su cui si basano i
calcolatori elettronici.
Il linguaggio macchina deve essere facilmente
rappresentabile sotto forma di segnali elettrici ed è quindi
formato soltanto dalle cifre binarie 0 ed 1. Ogni modello di
CPU ha il suo particolare linguaggio macchina anche se vi
sono molte similitudini fra un idioma e un altro. Le “parole” di un linguaggio macchina sono chiamate
istruzioni (codice operativo); ognuna di esse ordina alla CPU di eseguire un’azione elementare, come
la lettura di una locazione di memoria oppure il calcolo della somma dei valori contenuti in due
registri. Un programma è semplicemente una lunga lista di istruzioni che vengono eseguite da una
CPU. L’esecuzione di un programma avviene processando un’istruzione per volta secondo uno
schema che viene chiamato ciclo di fetch-execute. Esso consiste nel decodificare l’istruzione da
eseguire (fetch) e quindi eseguirla (execute).
Abbiamo dunque da una parte l’uomo, capace di comunicare attraverso un linguaggio molto
evoluto, e dall’altra il calcolatore, capace di comprendere soltanto il proprio linguaggio macchina. E’
ovvio che i primi programmatori si sono trovati costretti ad imparare il linguaggio macchina
dell’elaboratore da loro utilizzato. Programmare in linguaggio macchina risulta molto difficile, noioso
e frustrante perché bisogna scrivere e leggere attraverso simboli binari e bisogna guidare il calcolatore
attraverso tutti i suoi passi elementari verso l’ottenimento del risultato finale della computazione.

1
Wikipedia: Un algoritmo si può definire come un procedimento che consente di ottenere un risultato atteso eseguendo, in
un determinato ordine, un insieme di passi semplici corrispondenti ad azioni scelte solitamente da un insieme finito. In
informatica e matematica, con il termine algoritmo si intende, in genere, un metodo per la risoluzione di problemi
utilizzando un numero finito di passi. Per definirsi tale, un algoritmo deve possedere quattro proprietà fondamentali:
• la sequenza di istruzioni deve essere finita (finitezza);
• essa deve portare ad un risultato (effettività);
• le istruzioni devono essere eseguibili materialmente (realizzabilità);
• le istruzioni devono essere espresse in modo non ambiguo (non ambiguità).

2
Linguaggi di programmazione
Assembly
Sulla spinta del loro disagio i primi programmatori operarono una semplificazione del
linguaggio macchina introducendo il linguaggio Assembly. In questo linguaggio il codice binario delle
varie istruzioni è sostituito da un codice mnemonico, vengono introdotte le variabili per rappresentare
aree di memoria e i primi tipi di dato fondamentali come gli interi e i reali. L’idea alla base è pertanto
quella di sollevare il programmatore dal dover lavorare direttamente con sequenze di bit dandogli la
possibilità di:
– identificare ciascun comando in linguaggio macchina (codice operativo) con una parola chiave
più facile da ricordare (codice mnemonico);
– indirizzare le locazioni di memoria ed i registri attraverso degli identificatori testuali, anziché
attraverso i loro indirizzi binari.
Un programma scritto in linguaggio assembly, di conseguenza, consiste in un file di testo contenente
una serie di comandi. Perché il programma possa essere eseguito, il file deve essere preventivamente
tradotto in linguaggio macchina. Di questa traduzione si occupa l’assemblatore (o assembler).
La differenza tra un programma scritto in linguaggio macchina e l’equivalente scritto in
Assembly può essere apprezzata nell’esempio riportato di seguito. Esempio: calcolare z = x + y, dove
x = 8 e y = 38.
Nel linguaggio macchina bisogna stabilire quali locazioni di memoria associare alle variabili
x, y e z e codificare in binario i valori numerici 8 e 38, mentre utilizzando l’Assembler questo non si
rende più necessario.
Linguaggio Macchina: Assembler:
00000000000000000000000001000000 z : INT;
00000000000100000000000001000100 x : INT 8;
00000010000000010000000000000000 y : INT 38;
00000001000000000000000000111100 LOAD R0,x;
LOAD R1,y;
ADD R0,R1;
STORE R0,z;

Il programma nel suo formato originale alfanumerico (sequenza di caratteri) è chiamato


programma sorgente, mentre il programma assemblato in linguaggio macchina è detto programma
oggetto. Il termine assembler deriva dal fatto che le istruzioni vengono convertite e montate una
accanto all'altra come se fossero in fila. Ci sono molti tipi di linguaggi assembly e di conseguenza
diversi assemblatori: esistono gli assembler per programmare i microchip, per creare programmi sul
Personal Computer, per
telefoni cellulari, ecc. Questo
perché un assemblatore
produce codice assembly per
una specifica famiglia di
processori (intel 8086, 80386,
Motorola 68000, ecc. ) e
quindi è privo di portabilità.

La traduzione
L’unico e solo linguaggio che il calcolatore è in grado di comprendere ed eseguire è il
linguaggio macchina: la CPU non è in grado di eseguire programmi scritti in linguaggio Assembler.
Com’è che dunque si è affermata la programmazione in Assembler? Ciò è stato possibile grazie alla
realizzazione di un apposito programma (scritto dunque in linguaggio macchina), detto Assemblatore,
che si preoccupa di tradurre un qualsiasi programma scritto in Assembler nel programma equivalente
codificato in linguaggio macchina. Indubbiamente, la parte più delicata di questo processo di
traduzione risulta essere la determinazione dell’effettiva area di memoria associata a ogni variabile
utilizzata all’interno del programma. L’Assemblatore non è in grado di determinare in maniera
assoluta gli indirizzi di memoria poiché non conosce quella che sarà la situazione della memoria
all’atto dell’esecuzione del programma. Il codice generato dall’Assemblatore risulta essere quindi un
codice intermedio, detto codice rilocabile, in cui i riferimenti agli indirizzi di memoria sono specificati
in maniera relativa e non assoluta. Sarà compito di un altro programma, il loader, quello di “risolvere
gli indirizzi” una volta che il programma viene caricato in memoria per essere eseguito.

3
Linguaggi di programmazione
Linguaggi di alto livello
Una volta innescato il meccanismo di traduzione, i linguaggi di programmazione si sono
evoluti fino ad arrivare ai cosiddetti linguaggi di alto livello la cui sintassi è molto più vicina al
linguaggio naturale. Ciò è stato possibile grazie allo sviluppo di traduttori sempre più potenti e
complessi capaci di ricondurre in maniera non ambigua qualsiasi programma all’equivalente
codificato in linguaggio macchina. Programmare in un dato linguaggio di programmazione significa
generalmente scrivere uno o più semplici file di testo, chiamati codice sorgente. Il codice sorgente,
contenente le istruzioni da eseguire e (spesso) alcuni dati noti e costanti, può essere poi eseguito
passandolo ad un traduttore.
Esistono due tipi diversi di traduttori: gli interpreti e i compilatori.

L’interpretazione
L’interpretazione può essere vista come la riproposizione ad alto livello del ciclo di fetch-
2
execute della macchina di von Neumann. Essa consiste nel tradurre una singola istruzione del
programma sorgente nell’istruzione in linguaggio macchina equivalente, la quale viene subito
eseguita, per poi passare al processamento dell’istruzione successiva.

La compilazione
La compilazione consiste nel tradurre l’intero programma sorgente prima di eseguirlo.
Attraverso la compilazione diviene possibile anche tradurre diversi programmi sorgenti in modo da
ottenere un unico codice oggetto. Ciò si ottiene compilando separatamente i vari programmi e poi
collegando tra loro le traduzioni ottenute per mezzo di un apposito programma detto linker.

Interpretazione o compilazione ?
Non esiste un sistema di traduzione migliore in assoluto. Ciascuno dei due
sistemi di traduzione presenta sia vantaggi, sia condizioni sfavorevoli
rispetto all’altro:

L’INTERPRETAZIONE
− comporta una minore efficienza a tempo di esecuzione (run-time) poiché, in esecuzione, richiede più
memoria ed è meno veloce a causa dell’overhead3 introdotto dall’interprete stesso. Durante
2
In informatica, l'espressione ciclo di fetch-execute si riferisce alla dinamica generale di funzionamento dei processori dei
computer. In termini generali, un processore esegue iterativamente due operazioni: preleva (fetch) una istruzione dalla
memoria, e la esegue (execute). (Wikipedia)
3
In informatica, la parola inglese overhead (letteralmente in alto, che sta di sopra) serve per definire le risorse accessorie,
richieste in sovrappiù rispetto a quelle strettamente necessarie, per ottenere un determinato scopo in seguito all'introduzione
di un metodo o di un processo più evoluto o più generale. (Wikipedia)

4
Linguaggi di programmazione
l’esecuzione, l’interprete deve infatti analizzare le istruzioni a partire dal livello sintattico,
identificare le azioni da eseguire (eventualmente trasformando i nomi simbolici delle variabili
coinvolte nei corrispondenti indirizzo di memoria), ed eseguirle.
− risparmia memoria a tempo di esecuzione dato che il programma compilato, ovvero il codice
oggetto, può essere molto più grande del codice sorgente.
− risulta più utile nella determinazione di errori a tempo di esecuzione dato che spesso con la
compilazione si perdono tutte le relazioni esistenti tra codice sorgente e codice oggetto.

LA COMPILAZIONE
− genera, in linguaggio macchina, le istruzioni del codice (codice oggetto), le quali vengono caricate e
istantaneamente eseguite dal processore.
− conoscendo l’intero codice sorgente, il compilatore è in grado di effettuare ottimizzazioni sul codice
oggetto ottenuto in modo da migliorarne le prestazioni.

Di solito un linguaggio di programmazione è di tipo interpretato o compilato, ma esistono le


eccezioni, come il linguaggio Java, che applica un ibrido fra le due soluzioni, utilizzando un
compilatore per produrre del codice intermedio che viene successivamente interpretato.

Paradigmi di programmazione
Esistono numerose famiglie di linguaggi di programmazione, riconducibili a diversi
paradigmi di programmazione. Ciascun paradigma fornisce al programmatore strumenti concettuali di
diversa natura per descrivere gli algoritmi da far eseguire al calcolatore. Si parla così di un paradigma
logico per quei linguaggi che consentono all’utente di esprimersi usando notazioni e concetti derivati
dalla logica matematica e dal calcolo dei predicati; di paradigma funzionale se il linguaggio ha una
struttura che ricorda da vicino il calcolo funzionale della matematica, e così via.
Il paradigma più diffuso è quello procedurale (o imperativo), ma al giorno d’oggi, il
paradigma dominante è indubbiamente quello orientato agli oggetti, che deriva storicamente dal
paradigma procedurale.

Linguaggi imperativi
La programmazione imperativa è un paradigma di programmazione secondo cui un program-
ma viene inteso come un insieme di istruzioni (dette anche direttive, comandi), ciascuna delle quali
può essere pensata come un “ordine” che viene impartito alla macchina virtuale definita dal linguag-
gio di programmazione utilizzato. Da un punto di vista sintattico, i costrutti di un linguaggio impera-
tivo sono spesso identificati da verbi all’imperativo, per esempio:
print (stampa)
read (leggi)
do (fà)

Programmazione funzionale
A livello puramente astratto un programma (o, equivalentemente, un algoritmo) è una
funzione che, dato un certo input, restituisce un certo output (dove per output si può anche intendere la
situazione in cui il programma non termini la propria esecuzione o restituisca un errore). La
programmazione funzionale consiste esattamente nell’applicazione di questo punto di vista. Ogni
operazione sui dati in input viene effettuata attraverso particolari funzioni elementari,
appropriatamente definite del programmatore, che opportunamente combinate, attraverso il concetto
matematico di composizione di funzioni, danno vita al programma.
La programmazione funzionale tratta espressioni. Tanto che un modo alternativo di definirla
potrebbe essere programmazione orientata alle espressioni. Una espressione è un insieme di operandi
e variabili il cui risultato è un singolo valore. Esempio:
x == 5 (espressione booleana)4
5+(7-Y) (espressione aritmetica)
string.search("Ciao gente") (espressione stringa)

4
Nei linguaggi di programmazione il segno di uguale doppio (= =) è usato per confrontare due valori.
(Wikipedia)

5
Linguaggi di programmazione
Programmazione logica.
Il Prolog è stato il primo rappresentante di questa classe. Nati a partire da un progetto per un
dimostratore automatico di teoremi, i linguaggi logici (o dichiarativi), rappresentano un modo
completamente nuovo di concepire l’elaborazione dei dati: invece di una lista di comandi, un
programma in un linguaggio dichiarativo è una lista di regole che descrivono le proprietà dei dati e i
modi in cui questi possono trasformarsi. Le variabili non vengono mai assegnate (l’assegnamento non
esiste), ma istanziate al momento dell’applicazione in una
determinata regola; l’esecuzione del programma consiste nell’applicazione ripetuta delle
regole disponibili fino a trovare una catena di regole e trasformazioni che consente di stabilire se il
risultato desiderato è vero o meno. Non esistono né cicli, né salti, né un ordine rigoroso di esecuzione;
affinché sia possibile usarli in un programma dichiarativo, tutti i normali algoritmi devono essere
riformulati in termini ricorsivi e di backtracking; questo rende la programmazione con questi linguaggi
un’esperienza del tutto nuova e richiede di assumere un modo di pensare radicalmente diverso, perché
più che calcolare un risultato si richiede di dimostrarne il valore esatto.
A fronte di queste richieste, i linguaggi dichiarativi consentono di raggiungere risultati
eccezionali quando si tratta di manipolare gruppi di enti in relazione fra loro. A livello puramente
esemplificativo, supponiamo di voler scrivere un programma che calcoli la radice quadrata di 64. In un
ipotetico linguaggio logico esso potrebbe essere il seguente:
radice_quadrata(x) = y AND y * y = 64;
x = 64;
y=?
6
Naturalmente, l’unico valore per y che rende veri i predicati costituenti il programma è 8.

Programmazione concorrente
I moderni supercomputer e ormai tutti i calcolatori di fascia alta e media sono equipaggiati con
più di una CPU. Come ovvia conseguenza, questo richiede la capacità di sfruttarle; per questo sono
stati sviluppati dapprima il multithreading, cioè la capacità di lanciare più parti dello stesso
programma contemporaneamente su CPU diverse, e in seguito alcuni linguaggi studiati in modo tale
da poter individuare da soli, in fase di compilazione, le parti di codice da lanciare in parallelo.

Linguaggi di scripting
I linguaggi di questo tipo nacquero come linguaggi batch: vale a dire liste di comandi di
programmi interattivi che invece di venire digitati uno ad uno su una linea di comando, potevano
essere salvati in un file, che diventava così una specie di comando composto che si poteva eseguire in
modalità batch per automatizzare compiti lunghi e ripetitivi. I primi linguaggi di scripting sono stati
quelli delle shell Unix; successivamente, vista l’utilità del concetto molti altri programmi interattivi
iniziarono a permettere il salvataggio e l’esecuzione di file contenenti liste di comandi, oppure il
salvataggio di registrazioni di comandi visuali (le cosiddette Macro dei programmi di videoscrittura,
per esempio). Il passo successivo fu quello di far accettare a questi programmi anche dei comandi di
salto condizionato e delle istruzioni di ciclo, regolati da simboli associati ad un certo valore: in pratica

6
Linguaggi di programmazione
implementare cioè l’uso di variabili. Ormai molti programmi nati per tutt’altro scopo offrono agli
utenti la possibilità di programmarli in modo autonomo tramite linguaggi di scripting più o meno
proprietari. Molti di questi linguaggi hanno finito per adottare una sintassi molto simile a quella del C:
altri invece, come il Perl e il Python, sono stati sviluppati ex novo allo scopo. Visto che nascono tutti
come feature di altri programmi, tutti i linguaggi di scripting hanno in comune il fatto di essere
linguaggi interpretati, cioè eseguiti da un altro programma (il programma madre o un suo modulo).

Programmazione orientata agli oggetti.


La programmazione orientata agli oggetti (OOP, Object Oriented Programming) è un
paradigma di programmazione, che prevede di raggruppare in un’unica entità (la classe) sia le strutture
dati che le procedure che operano su di esse, creando per l'appunto un “oggetto” software dotato di
proprietà (dati) e metodi (procedure) che operano sui dati dell’oggetto stesso. La modularizzazione di
un programma viene realizzata progettando e realizzando il codice sotto forma di classi che
interagiscono tra di loro. Un programma ideale, realizzato applicando i criteri dell'OOP, sarebbe
completamente costituito da oggetti software (istanze di classi) che interagiscono gli uni con gli altri.
La programmazione orientata agli oggetti è particolarmente adatta a realizzare interfacce grafiche.

Proprietà dei linguaggi


Non ha senso, in generale, parlare di linguaggi migliori o peggiori, o di linguaggi migliori in
assoluto: ogni linguaggio nasce per affrontare una classe di problemi più o meno ampia, in un certo
modo e in un certo ambito. Però, dovendo dire se un dato linguaggio sia adatto o no per un certo uso, è
necessario valutare le caratteristiche dei vari linguaggi.

Caratteristiche intrinseche
Sono le qualità del linguaggio in sé, determinate dalla sua sintassi e dalla sua architettura
interna. Influenzano direttamente il lavoro del programmatore, condizionandolo. Non dipendono né
dagli strumenti usati (compilatore/interprete, linker) né dal sistema operativo o dal tipo di macchina.

ESPRESSIVITÀ
La facilità e la semplicità con cui si può scrivere un dato algoritmo in un dato linguaggio; può
dipendere dal tipo di algoritmo, se il linguaggio in questione è nato per affrontare certe particolari
classi di problemi. In generale se un certo linguaggio consente di scrivere algoritmi con poche
istruzioni, in modo chiaro e leggibile, la sua espressività è buona.

DIDATTICA
La semplicità del linguaggio e la rapidità con cui lo si può imparare. Il Basic, per esempio, è
un linguaggio facile da imparare: poche regole, una sintassi molto chiara e limiti ben definiti fra quello

7
Linguaggi di programmazione
che è permesso e quello che non lo è. Il Pascal non solo ha i pregi del Basic ma educa anche il neo-
programmatore ad adottare uno stile corretto che evita molti errori e porta a scrivere codice migliore.
Al contrario, il C non è un linguaggio didattico perché pur avendo poche regole ha una semantica
molto complessa, a volte oscura, che lo rende molto efficiente ed espressivo ma richiede tempo per
essere padroneggiata.

LEGGIBILITÀ
La facilità con cui, leggendo un codice sorgente, si può capire cosa fa e come funziona. La
leggibilità dipende non solo dal linguaggio ma anche dallo stile di programmazione di chi ha creato il
programma: tuttavia la sintassi di un linguaggio può facilitare o meno il compito. Non è detto che un
linguaggio leggibile per un profano lo sia anche per un esperto: in generale le abbreviazioni e la
concisione consentono a chi già conosce un linguaggio di concentrarsi meglio sulla logica del codice
senza perdere tempo a leggere, mentre per un profano è più leggibile un linguaggio molto prolisso. A
volte, un programma molto complesso e poco leggibile in un dato linguaggio può diventare
assolutamente semplice e lineare se riscritto in un linguaggio di classe differente, più adatta.

ROBUSTEZZA
La capacità del linguaggio di prevenire, nei limiti del possibile, gli errori di programmazione.
Di solito un linguaggio robusto si ottiene adottando un controllo molto stretto sui tipi di dati e una
sintassi chiara e molto rigida; altri sistemi sono l’implementare un garbage collector, limitando (a
prezzo di una certa perdita di efficienza) la creazione autonoma di nuove entità di dati e quindi l’uso
dei puntatori, che possono introdurre bug molto difficili da scoprire. L’esempio più comune di
linguaggio robusto è il Pascal, che essendo nato a scopo didattico presuppone sempre che una
irregolarità nel codice sia frutto di un errore del programmatore; mentre l’ASSEMBLER è l’esempio
per antonomasia di linguaggio totalmente libero, in cui niente vincola il programmatore (e se scrive
codice pericoloso o errato, non c’è modo di essere avvertiti).

MODULARITÀ
Quando un linguaggio facilita la scrittura di parti di programma indipendenti (moduli) viene
definito modulare. I moduli semplificano la ricerca e la correzione degli errori, permettendo di isolare
rapidamente la parte di programma che mostra il comportamento errato e modificarla senza timore di
introdurre conseguenze in altre parti del programma stesso. Questo si ripercuote positivamente sulla
manutenibilità del codice; inoltre permette di riutilizzare il codice scritto in passato per nuovi
programmi, apportando poche modifiche. In genere la modularità si ottiene con l’uso di
sottoprogrammi (subroutine, procedure, funzioni) e con la programmazione ad oggetti.

FLESSIBILITÀ
La possibilità di adattare il linguaggio, estendendolo con la definizione di nuovi comandi e
nuovi operatori. I linguaggi classici come il BASIC, il Pascal e il Fortran non hanno questa capacità,
che invece è presente nei linguaggi dichiarativi, in quelli funzionali e nei linguaggi imperativi ad
oggetti più recenti come il C++ e Java.

GENERALITÀ
La facilità con cui il linguaggio si presta a codificare algoritmi e soluzioni di problemi in
campi diversi. Di solito un linguaggio molto generale, per esempio il C, risulta meno espressivo e
meno potente in una certa classe di problemi di quanto non sia un linguaggio specializzato in quella
particolare nicchia, che in genere è perciò una scelta migliore finché il problema da risolvere non esce
da quei confini.

EFFICIENZA
La velocità di esecuzione e l’uso oculato delle risorse del sistema su cui il programma finito
gira. In genere i programmi scritti in linguaggi molto astratti tendono ad essere lenti e voraci di risorse,
perché lavorano entro un modello che non riflette la reale struttura dell’hardware ma è una cornice
concettuale, che deve essere ricreata artificialmente; in compenso facilitano molto la vita del
programmatore poiché lo sollevano dalla gestione di numerosi dettagli, accelerando lo sviluppo di
nuovi programmi ed eliminando intere classi di errori di programmazione possibili. Viceversa un

8
Linguaggi di programmazione
linguaggio meno astratto ma più vicino alla reale struttura di un computer genererà programmi molto
piccoli e veloci ma a costo di uno sviluppo più lungo e difficoltoso.

COERENZA
L’applicazione dei principi base di un linguaggio in modo uniforme in tutte le sue parti. Un
linguaggio coerente è un linguaggio facile da prevedere e da imparare, perché una volta appresi i
principi base questi sono validi sempre e senza (o con poche) eccezioni.

Caratteristiche esterne
Viste le qualità dei linguaggi, vediamo quelle degli ambienti in cui operano. Un
programmatore lavora con strumenti software, la cui qualità e produttività dipende da un insieme di
fattori che vanno pesati anch’essi in funzione del tipo di programmi che si intende scrivere.

DIFFUSIONE
Il numero di programmatori nel mondo che usa il tale linguaggio. Ovviamente più è numerosa
la comunità dei programmatori tanto più è facile trovare materiale, aiuto, librerie di funzioni,
documentazione, consigli. Inoltre ci sono un maggior numero di software house che producono
strumenti di sviluppo per quel linguaggio, e di qualità migliore.

STANDARDIZZAZIONE
Un produttore di strumenti di sviluppo sente sempre la tentazione di introdurre delle variazioni
sintattiche o delle migliorie più o meno grandi ad un linguaggio, originando un dialetto del linguaggio
in questione e fidelizzando così i programmatori al suo prodotto: ma più dialetti esistono, più la
comunità di programmatori si frammenta in sottocomunità più piccole e quindi meno utili. Per questo
è importante l’esistenza di uno standard per un dato linguaggio che ne garantisca certe caratteristiche,
in modo da evitarne la dispersione. Quando si parla di Fortran 77, Fortran 90, C 99 ecc. si intende lo
standard sintattico e semantico del tale linguaggio approvato nel tale anno, in genere dall’ANSI o
dall’ISO.

INTEGRABILITÀ
Dovendo scrivere programmi di una certa dimensione, è molto facile trovarsi a dover integrare
parti di codice precedente scritte in altri linguaggi: se un dato linguaggio di programmazione consente
di farlo facilmente, magari attraverso delle procedure standard, questo è decisamente un punto a suo
favore. In genere tutti i linguaggi “storici” sono bene integrabili, con l’eccezione di alcuni, come lo
Smalltalk, creati più per studio teorico che per il lavoro reale di programmazione.

PORTABILITÀ
La possibilità che portando il codice scritto su una certa piattaforma (CPU + architettura +
sistema operativo) su un’altra, questo funzioni subito, senza doverlo 9 modificare. A questo scopo è
molto importante l’esistenza di uno standard del linguaggio, anche se a volte si può contare su degli
standard de facto come il Delphi.

Cenni storici
Il primo linguaggio di programmazione della storia è a rigor di termini il Plankalkül di Konrad
Zuse, sviluppato da lui nella svizzera neutrale durante la II guerra mondiale e pubblicato nel 1946; ma
non venne mai realmente usato per programmare. La programmazione dei primi elaboratori veniva
fatta invece in Shortcode, da cui poi si è evoluto l’assembly o ASSEMBLER, che costituisce una
rappresentazione simbolica del linguaggio macchina. La sola forma di controllo di flusso è l’istruzione
di salto condizionato, che porta a scrivere programmi molto difficili da seguire logicamente per via dei
continui salti da un punto all’altro del codice.
La maggior parte dei linguaggi di programmazione successivi cercarono di astrarsi da tale
livello basilare, dando la possibilità di rappresentare strutture dati e strutture di controllo più generali e
più vicine alla maniera (umana) di rappresentare i termini dei problemi per i quali ci si prefigge di
scrivere programmi.
Tra i primi linguaggi ad alto livello a raggiungere una certa popolarità ci fu il Fortran, creato
nel 1957 da John Backus, da cui derivò successivamente il BASIC (1964): oltre al salto condizionato,
reso con l’istruzione IF, questa nuova generazione di linguaggi introduce nuove strutture di controllo

9
Linguaggi di programmazione
di flusso come i cicli WHILE e FOR e le istruzioni CASE e SWITCH: in questo modo diminuisce
molto il ricorso alle istruzioni di salto (GOTO), cosa che rende il codice più chiaro ed elegante, e
quindi di più facile manutenzione. Dopo la comparsa del Fortran nacquero una serie di altri linguaggi
di programmazione storici, che implementarono una serie di idee e paradigmi innovativi: i più
importanti sono l'ALGOL (1960) e il Lisp (1959).
Tutti i linguaggi di programmazione oggi esistenti possono essere considerati discendenti da
uno o più di questi primi linguaggi, di cui mutuano molti concetti di base; l’ultimo grande progenitore
dei linguaggi moderni fu il Simula (1967), che introdusse per primo il concetto (allora appena
abbozzato) di oggetto software.
Nel 1970 Niklaus Wirth pubblica il Pascal, il primo linguaggio strutturato, a scopo didattico;
nel 1972 dal BCPL nascono prima il B (rapidamente dimenticato) e poi il C, che invece fu fin
dall’inizio un grande successo. Nello stesso anno compare anche il Prolog, finora il principale esempio
di linguaggio logico, che pur non essendo di norma utilizzato per lo sviluppo industriale del software
(a causa della sua inefficienza) rappresenta una possibilità teorica estremamente affascinante.
Con i primi mini e microcomputer e le ricerche a Palo Alto, nel 1983 vede la luce Smalltalk, il
primo linguaggio realmente e completamente ad oggetti, che si ispira al Simula e al Lisp: oltre a essere
in uso tutt’oggi in determinati settori, Smalltalk viene ricordato per l’influenza enorme che ha
esercitato sulla storia dei linguaggi di programmazione, introducendo il paradigma orientato agli
oggetti nella sua prima incarnazione matura. Esempi di linguaggi orientati agli oggetti odierni sono
Eiffel (1986), C++ (che esce nello stesso anno di Eiffel) e successivamente Java, classe 1995.

Sintassi e Semantica
Un linguaggio di programmazione è un linguaggio formale dotato di una sintassi e una
semantica ben definita. Per linguaggio formale si intende un insieme di stringhe di lunghezza finita
costruite sopra un alfabeto finito, cioè sopra un insieme finito di oggetti tendenzialmente semplici che
vengono chiamati caratteri, simboli o lettere. Il compito della sintassi è quello di stabilire quali, tra le
innumerevoli stringhe generabili sull’alfabeto, costituiscono le “parole” (istruzioni) valide che
formano il linguaggio. Il compito della semantica, invece, è quello di assegnare un significato alle
innumerevoli “frasi” (programmi) ottenibili combinando le diverse parole del linguaggio.

2.1 Sintassi
La sintassi è un insieme di regole che definiscono la “forma” di un linguaggio; esse
definiscono come ottenere delle frasi sequenzializzando una serie di componenti fondamentali, dette
parole. Applicando le regole della sintassi è possibile stabilire se una frase è corretta oppure no. Un
programma non è altro che un insieme di frasi esprimibili in un particolare linguaggio di
programmazione.
Ma come si fa a definire la sintassi di un linguaggio? Poiché ci sono teoricamente un numero
infinito di programmi (sintatticamente corretti o meno), chiaramente non
possiamo enumerarli tutti. Abbiamo bisogno di un modo per definire un
insieme infinito attraverso una descrizione finita. Il FORTRAN fu
definito semplicemente enunciando alcune regole in Inglese.
L’ALGOL60 fu definito attraverso una grammatica libera dal
contesto sviluppata da John Backus. Questo metodo è diventato noto
come Backus-Naur Form (BNF), dato che Peter Naur fu colui che scrisse
il report dell’ALGOL60. La BNF fornisce un modo chiaro, potente e
compatto per definire la sintassi di un linguaggio di programmazione. La
BNF è quindi una meta-sintassi, ovvero un formalismo attraverso il
quale è possibile descrivere la sintassi di linguaggi formali. Si tratta di
uno strumento molto usato per descrivere, in modo preciso e non
ambiguo, la sintassi dei linguaggi di programmazione, dei protocolli di
rete e così via.
In sintesi, la descrizione sintattica di un linguaggio di programmazione ha due motivi
fondamentali:
- aiuta il programmatore nellos scrivere i programmi sintatticamente correti
- è lo strumento formale che i traduttori (interpretie compilatori) usano per stabilire se un
programma è sintatticamente corretto.

10
Linguaggi di programmazione
Semantica
La semantica definisce il significato dei programmi sintatticamente corretti nel linguaggio di
programmazione in questione. Per esempio, la semantica del C ci dice che la dichiarazione
int vector[10];
fa sì che si riservi spazio per 10 interi in memoria associati ad una variabile di nome vector. Gli
elementi di vector possono essere referenziati attraverso un indice che va da 0 a 9. In realtà non tutti i
programmi sintatticamente corretti possono avere un significato. La semantica, dunque, ha anche il
compito di separare i programmi realmente corretti da quelli semplicemente corretti sintatticamente.
Ad esempio, il programma
{ if ( x + 3 ) { x = 2;} }
è sintatticamente corretto in base alla BNF presentata nel paragrafo precedente, ma non lo è dal punto
di vista semantico dato che la semantica dell’istruzione condizionale richiede che la sua valutazione
dia luogo ad un risultato booleano di tipo true o false. Un metalinguaggio per descrivere formalmente
la semantica di un linguaggio di programmazione deve basarsi su concetti matematici ben formulati, in
modo che la definizione risultante sia rigorosa e non ambigua.
Se la BNF è stata universalmente accettata come lo strumento standard per la definizione della
sintassi dei linguaggi di programmazione, lo stesso compromesso non si è raggiunto per quanto
riguarda la definizione della semantica, per la quale gli approcci fondamentali utilizzati sono tre
(semantica assiomatica, denotazionale e operazionale).

Il Binding
I programmi sono composti di entità come variabili, routines e istruzioni. Ogni entità possiede
certe proprietà chiamate attributi:
− una variabile ha un nome, un tipo, un’area di memoria
riservata per la memorizzazione del proprio valore;
− una routine ha un nome, dei parametri formalidi un certo tipo,
delle convenzioni sul passaggio dei parametri;
− un’istruzione ha delle azioni associate.
I valori degli attributi devono essere specificati prima che l’entità possa essere usata.
Specificare il valore di un attributo è un’azione detta binding5. Per ogni entità, le informazioni sugli
attributi sono contenute in un apposito record detto descrittore. Il binding è un concetto fondamentale
nella definizione della semantica di un linguaggio di programmazione. I linguaggi di programmazione
si differenziano nel numero di entità che essi possono manipolare, nel numero di attributi associati ad
ogni entità, nel momento in cui ha luogo il binding (binding time) e nella sua stabilità (cioè, se una
volta stabilito il binding possa essere modificato). Ci sono quattro momenti in cui il binding può avere
luogo, ovvero durante:
−   




− 





− 






− 

5
binding = legame

11
Linguaggi di programmazione





Quando il binding viene stabilito prima dell’esecuzione del programma ed è, per questo, di
solito non modificabile, si dice che è statico(è la soluzione maggiormente usata), mentre quando esso
avviene a tempo di esecuzione si dice dinamico.

Le variabili
I calcolatori tradizionali si basano sull’utilizzo di una memoria principale formata da celle ele-
mentari ognuna delle quali è identificata da un indirizzo. Il contenuto di una cella è la rappresentazione
codificata di un valore. Un valore è un’astrazione matematica; la sua rappresentazione codificata in
una cella di memoria può essere letta e modificata durante l’esecuzione di un programma. La modifica
consiste nel sostituire la codifica corrente con un’altra.
I linguaggi di programmazione basati sul paradigma imperativo possono essere visti come
un’astrazione, a vari livelli, del comportamento dei calcolatori tradizionali. In particolare, essi introdu-
cono la nozione di variabile come un’astrazione della nozione di cella di memoria, il nome di una va-
riabile come un’astrazione dell’indirizzo di memoria e l’istruzione di assegnamento come un’astra-
zione della modifica del valore contenuto in una cella di memoria. Formalmente, una variabile è una
quintupla < nome, scope, tipo, l-value, r-value >.

Nome
E’ una stringa di caratteri usata per rappresentare la variabile all’interno delle istruzioni. Il
nome di una variabile viene introdotto di solito attraverso un’istruzione speciale, detta dichiarazione.

Scope
Rappresenta la porzione (campo d’azione) di programma in cui la variabile può essere usata; si
estende normalmente dal punto in cui avviene la dichiarazione a un certo altro punto la cui specifica
dipende dal linguaggio. Si dice di solito che una variabile è visibile all’interno del suo scope mentre è
invisibile al suo esterno.

Tipo
E’ il tipo di dato associato alla variabile. Il tipo di una variabile è definito come l’insieme dei
valori che possono essere associati alla variabile e delle operazioni che possono essere usate per
creare, accedere e modificare tali valori. Una variabile di un dato tipo è detta istanza del tipo. Quando
un linguaggio viene definito, certi tipi sono direttamente associati a certe classi di valori con le relative
operazioni. Ad esempio il tipo integer e gli operatori relativi sono associati alla propria controparte
matematica. I valori e le operazioni sono associati ad una certa rappresentazione sulla macchina
quando il linguaggio è implementato. Quest’ultimo tipo di binding può restringere l’insieme dei valori
che possono essere rappresentati sulla base di quanto spazio è riservato in memoria per il tipo in
questione.

L-Value
E’ l’area di memoria riservata alla variabile, utilizzata per memorizzare l’r-value della
variabile. Con il termine oggetto si indica la coppia < l-value, r-value >. L’azione di riservare dell’area
di memoria per una variabile è detta allocazione di memoria. Il tempo di vita va dal momento in cui
viene allocata la memoria fino a quando tale memoria non viene liberata (deallocazione di memoria).
In alcuni linguaggi, per certe variabili, l’allocazione è eseguita prima dell’esecuzione mentre la
deallocazione avviene soltanto alla terminazione del programma (allocazione statica). In altri
linguaggi, l’allocazione viene eseguita durante l’esecuzione (allocazione dinamica) e lo stesso avviene
per la deallocazione, quando si sa che la variabile non verrà più utilizzata (deallocazione dinamica) .

R-Value
L’r-value di una variabile è il valore codificato memorizzato nell’area di memoria riservata ad
una variabile (cioè il suo l-value). La rappresentazione codificata è interpretata a seconda del tipo della

12
Linguaggi di programmazione
variabile. Per esempio, una certa sequenza di bit sarà interpretata come un intero se il tipo è intero,
mentre sarà interpretata come una stringa se il tipo è un array di caratteri. Gli l-value e r-value delle
variabili sono i concetti principali legati all’esecuzione di un programma. Le istruzioni accedono alle
variabili attraverso il loro l-value e modificano eventualmente il loro r-value.
Ad esempio x (l-value) = y (r-value). La variabile che appare a sinistra indica una locazione,
mentre quella che appare a destra indica il contenuto di una locazione, ovvero un valore.

Le routine
I linguaggi di programmazione consentono di raggruppare una sequenza di istruzioni in un
unico blocco, ossia di strutturare un programma come composizione di un certo numero di unità
ciascuna delle quali è chiamata routine. Quest’ultima viene eseguita a seguito di una chiamata (una
routine può essere richiamata, invocata, attivata). La routine può avere un’intestazione nella quale
vengono specificati il nome della routine, il tipo dei parametri su cui essa deve andare ad operare, il
tipo di valore eventualmente ritornato. Questa operazione è detta segnatura.
Una routine (o subroutine) dovrebbe eseguire una determinata operazione o risolvere un
determinato problema. Ad esempio, una routine progettata per disporre in ordine crescente un insieme
di numeri interi può essere richiamata in tutti i contesti in cui questa operazione sia utile o necessaria,
e supplisce alla mancanza di una vera e propria "istruzione" dedicata allo scopo, consentendo al
contempo di descrivere il corrispondente algoritmo di ordinamento in un unico punto del programma.
Le subroutine che implementano funzionalità di base spesso richiamate nel codice sorgente dal
programmatore sono raccolte all'interno delle cosiddette librerie.
Nei diversi linguaggi di programmazione, le routine vengono realizzate in modi e con
terminologie parzialmente differenti. Consideriamo alcuni esempi:
− il termine subroutine è stato usato fino dagli albori della programmazione per riferirsi a sezioni di
codice assembly o in linguaggio macchina (e viene usato per estensione in altri contesti ad esempio
nelle prime versioni del Basic);
− i termini procedura e funzione vengono generalmente usati nel contesto dei linguaggi di
programmazione ad alto livello con due accezioni diverse. Per funzione si intende un
sottoprogramma il cui scopo principale sia quello di produrre un valore in output a partire da
determinati dati in ingresso (cosa che stabilisce un'analogia con l'omonimo concetto di funzione
matematica), mentre una procedura è un sottoprogramma che non "produce" alcun particolare
valore.
− il termine sottoprogramma è anch'esso tipico dei linguaggi di programmazione ad alto livello, ed è
talvolta usato come termine generale per riferirsi sia a procedure che a funzioni nel senso descritto
sopra.
− nella programmazione orientata agli oggetti, il ruolo della funzione è assunto dal metodo
− alcuni linguaggi distinguono tra dichiarazione e definizione di una routine:
o dichiarazione: introduce l’intestazione senza specificarne il corpo;
o definizione: specifica l’intestazione e il corpo.
− a volte succede di dover scrivere delle routine molto simili come nel caso in cui si vuole ordinare sia
un array di interi che uno di stringhe. In questo caso sono necessarie due procedure, anche se
l’algoritmo utilizzato è lo stesso, poiché i tipi dei parametri sono diversi. Alcuni linguaggi però
offrono la possibilità di definire routine generiche in modo da poter ovviare a questo inconveniente.
Le routine generiche sono dette anche templates.

Funzionamento
Poiché la routine è una porzione di codice che può essere invocata da qualsiasi punto di un
programma, l'esecuzione di quella parte di programma si interrompe fino a quando l'esecuzione di una
routine è terminata, e prosegue dall'istruzione successiva a quella di invocazione. E’ anche possibile
che una funzione richiami direttamente o indirettamente sé stessa. In questo caso, si dice che in un
dato momento sono in esecuzione più istanze di una stessa
funzione. Questa possibilità è essenziale per la programmazione
ricorsiva.
Nella programmazione le routine sono uno strumento
talmente importante e diffuso da richiedere una gestione della loro
esecuzione estremamente efficiente allo scopo di mantenere bassi i

13
Linguaggi di programmazione
tempi di chiamata della routine e di ritorno del controllo al programma chiamante. Per questo motivo
la gestione dell'allocazione delle variabili locali e del passaggio dei parametri vengono normalmente
supportate direttamente dall'hardware. L'esistenza dello stack6 nelle architetture hardware è appunto
riconducibile alla necessità di supportare efficientemente le funzioni. Infatti, quando viene invocata
una funzione il punto del codice in cui è stata invocata viene salvato sullo stack (indirizzo di ritorno),
e anche i parametri e le variabili locali di una funzione vengono salvati sullo stack.

Programmazione orientata agli oggetti


La programmazione orientata agli oggetti (OOP, Object Oriented Programming) è un
paradigma di programmazione che prevede la definizione di oggetti software (classi) dotati di
proprietà (dati) e metodi (procedure) che operano sui dati dell’oggetto stesso. La modularizzazione di
un programma O.O.P. viene realizzata progettando e realizzando il codice in modo tale che gli oggetti
software (istanze di classi) interagiscano gli uni con gli altri.

Cenni storici
Il primo linguaggio di programmazione orientato agli oggetti fu il Simula (1967), seguito negli
anni settanta da Smalltalk e da varie estensioni del Lisp. Negli anni ottanta sono state create estensioni
orientate ad oggetti del linguaggio C (C++, Objective C, e altri), e di altri linguaggi (Object Pascal).
Negli anni novanta è diventato il paradigma dominante, per cui gran parte dei linguaggi di
programmazione erano o nativamente orientati agli oggetti o avevano una estensione in tal senso.
Linguaggi che supportano solo il paradigma di programmazione orientata agli oggetti sono Smalltalk
ed Eiffel. Più spesso si incontra invece una realizzazione non esclusiva del paradigma di
programmazione orientata agli oggetti, come in C++, Java, Delphi, Python, C#, Visual Basic .NET,
Perl, PHP (a partire dalla versione 5).

Classi
La classe può essere considerata l’erede del tipo di dato astratto, una tendenza che si è
sviluppata all’interno del paradigma della programmazione procedurale, secondo la quale un modulo
dovrebbe implementare un tipo di dato definito dall’utente, con cui si possa interagire solo attraverso
una interfaccia ben definita, che nasconda agli altri moduli i dettagli dell’implementazione, in modo
che sia possibile modificarli contenendo gli effetti della modifica sul resto del programma. La classe
può essere vista come il costrutto che permette di realizzare questa astrazione con un supporto
strutturato da parte del linguaggio.
Le classi definiscono dei tipi di dato cioè le caratteristiche di un insieme di dati e permettono
la creazione degli oggetti che riflettono tali caratteristiche. Grazie alle relazioni di ereditarietà nuove
classi di oggetti sono derivate da classi esistenti, ereditando le loro caratteristiche ed estendendole con
caratteristiche proprie. I membri di una classe sono dati chiamati attributi(i dati, esattamente come i
membri di un record) e metodi (o procedure) che operano su tali attributi. Una classe può dichiarare
riservati una parte dei suoi attributi e/o dei suoi metodi, e riservarne l’uso a se stesso e/o a particolari
tipi di oggetti a lui correlati.

Oggetti
Un oggetto è una istanza di una classe (a volte di più classi), un suo membro, ossia la
realizzazione di un dato secondo specifiche definite nella classe stessa. Un oggetto contiene tutti gli
attributi definiti nella classe, i quali hanno un valore che può mutare durante l’esecuzione del
programma come quello di qualsiasi variabile. Se il paradigma della OOP è applicato in modo rigido,
gli attributi di un oggetto vengono manipolati soo da metodi invocati su quello stesso oggetto.
Secondo il principio noto come information hiding, su cui si basa l’incapsulamento, l’accesso agli
attributi di un’istanza (oggetto) è permesso solo tramite metodi invocati su quello stesso oggetto,
permettendo così un completo controllo del suo stato. L'incapsulamento riduce i margini di errori in
fase di sviluppo di un programma. Questo risultato viene ottenuto strutturando l'intero progetto ed i
6
In informatica, il termine stack (pila) viene usato in diversi contesti per riferirsi a strutture dati le cui modalità
d'accesso seguono una politica LIFO (Last In First Out), ovvero tale per cui i dati vengono estratti (letti) in ordine
rigorosamente inverso rispetto a quello in cui sono stati inseriti (scritti). Anche se è possibile inserire o prelevare elementi
anche dalla coda, infatti più in generale la pila è un particolare tipo di lista in cui le operazioni di inserimento ed estrazione si
compiono dallo stesso estremo.

14
Linguaggi di programmazione
moduli che lo compongono, in modo che un'errata decisione presa nell'implementazione di un singolo
modulo non si ripercuota sull'intero progetto, e possa essere corretta modificando soltanto quel
modulo.

Ereditarietà

Il meccanismo dell'ereditarietà permette di derivare nuove classi a partire da altre classi già
definite. Una classe derivata attraverso l'ereditarietà (sottoclasse), mantiene i metodi e gli attributi
della/e classe/i da cui deriva (classe base, o superclasse); possono inoltre essere aggiunti nuovi
attributi e metodi, e può essere modificato il comportamento dei metodi ereditati (overriding).
Quando una classe eredita da una sola superclasse si parla di eredità singola; viceversa, di eredità
multipla. Alcuni linguaggi (Java, Smalltalk, …) forniscono il supporto esclusivo all'eredità
singola. Altri (C++, Python) prevedono l'eredità multipla. L'ereditarietà può essere usata come
meccanismo per gestire l'estensione e il riuso del codice, e risulta particolarmente vantaggiosa
sfruttando le relazioni di eredità esistenti nei concetti modellati attraverso il costrutto di classe.
Oltre all'evidente riuso del codice della superclasse l'ereditarietà permette la definizione di codice
generico attraverso il meccanismo del polimorfismo.

ESEMPIO

Se nel programma esiste già una classe "MezzoDiTrasporto" che ha come proprietà i dati di
posizione, velocità, destinazione e carico utile, e occorre una nuova classe "Aereo", è
possibile crearla direttamente dall'oggetto "MezzoDiTrasporto" dichiarando una classe di tipo
"Aereo" che eredita da "MezzoDiTrasporto" e aggiungendovi anche il dato "Quota di
crociera", con il vantaggio che la nuova classe sarà sia un "Aereo" che un
"MezzoDiTrasporto", permettendo di gestire in modo omogeneo tutti i mezzi con una
semplice lista di "MezziDiTrasporto".

POLIMORFISMO
La possibilità che una classe derivata ridefinisca i metodi e le proprietà dei suoi antenati rende
possibile che gli oggetti appartenenti ad una classe che ha delle sottoclassi rispondano diversamente
alle stesse istruzioni. I metodi che vengono ridefiniti in una sottoclasse sono detti polimorfi, in quanto
lo stesso metodo si comporta diversamente a seconda del tipo di oggetto su cui è invocato.

Binding dinamico - Il polimorfismo è particolarmente utile quando la versione del metodo da


eseguire viene scelta sulla base del tipo di oggetto effettivamente contenuto in una variabile a runtime
(invece che al momento della compilazione). Questa funzionalità è detta binding dinamico (o late-
binding). Se ho una variabile di tipo A, e il tipo A ha due sottotipi (sottoclassi) B e C, che
ridefiniscono entrambe il metodo m(), l’oggetto contenuto nella variabile potrà essere di tipo A, B o C,
e quando sulla variabile viene invocato il metodo m() viene eseguita la versione appropriata per il tipo
di oggetto contenuto nella variabile in quel momento.
Per ritornare all’esempio di poco fa, supponiamo che un “Aereo” debba affrontare procedure
per l’arrivo e la partenza molto più complesse di un normale camion, come in effetti è: allora le
procedure arrivo() e partenza() devono essere cambiate rispetto a quelle della classe base
“MezzoDiTrasporto”. Quindi provvediamo a ridefinirle nella classe “Aereo” in modo che facciano
quello che è necessario (polimorfismo): a questo punto, dalla nostra lista di mezzi possiamo prendere
qualsiasi mezzo e chiamare arrivo() o partenza() senza doverci più preoccupare di che cos’è l’oggetto
che stiamo maneggiando: che sia un mezzo normale o un aereo, si comporterà rispondendo alla stessa
chiamata sempre nel modo giusto.
Il binding dinamico è supportato dai più diffusi linguaggi di programmazione ad oggetti come
il Java o il C++. C’è però da sottolineare che in Java il binding dinamico è implicitamente usato come
comportamento di default nelle classi polimorfe, mentre il C++ per default non usa il binding
dinamico e se lo si vuole utilizzare bisogna inserire la parola chiave virtual nella segnatura del metodo
interessato.

15
Linguaggi di programmazione
Diversi approcci all’OOP
La OOP soffre di grossi problemi di efficienza in termini di tempo e di memoria (overhead) se
applicata a tutto indiscriminatamente, come avviene nel linguaggio Smalltalk che utilizza gli oggetti
anche per i tipi primitivi. Un approccio più pratico e realista è quello adottato da linguaggi come Java
e C++ che limitano la creazione di oggetti alle sole entità che il programmatore decide di dichiarare
come tali più eventualmente una serie di oggetti predefiniti, per lo più riguardanti il sistema. In questo
modo tali linguaggi restano efficienti, ma l’uso degli oggetti creati richiede più attenzione e più
disciplina. Inoltre il fatto di ridefinire i metodi ereditati dalle classi base può portare a introdurre errori
nel programma se per caso questi sono usati all’interno della classe base stessa (il noto problema della
classe base fragile).

Linguaggi di Programmazione Visuale (Visual Programming Language V.P.L.)


E’ un linguaggio che consente la programmazione tramite la manipolazione grafica degli
elementi e non tramite sintassi scritta. Un VPL consente di programmare con "espressioni visuali" ma
anche all'evenienza di inserire spezzoni di codice (solitamente questa funzione è riservata a formule
matematiche). La maggioranza dei VPL è basata sull'idea boxes and arrows ovvero le "box" (o i
rettangoli, le circonferenze ec...) sono concepiti come funzioni connesse tra di loro da "arrows", le
frecce.
I VPL possono essere ulteriormente classificati, a seconda di come rappresentano su schermo
le funzioni, in icon-based, form-based (linguaggio a diagrammi). L'ambiente per la programmazione
visuale provvede tutto il necessario per poter "disegnare" subito un programma; in rapporto ai
linguaggi scritti le regole sintattiche sono praticamente inesistenti.
I vantaggi della programmazione visuale sono incredibili, oltre ad una facilità di
apprendimento e alla capacità di poter "vedere il programma" durante le fasi debug, la
programmazione parallela (se gestita dal software) diviene quasi "istintiva" e soprattutto eseguita in
automatico.

16
Linguaggi di programmazione

SuperCollider
Cenni generali
SuperCollider (SC) è un software per la sintesi e il controllo dell’audio in tempo reale.
Attualmente rappresenta lo stato dell’arte nell’ambito della programmazione audio: non c’è altro
software disponibile che sia insieme così potente, efficiente, flessibile. L’unico punto a sfavore di SC è
che non è “intuitivo”: richiede competenze relative alla programmazione, alla sintesi del segnale
audio, alla composizione musicale (nel senso più lato, aperto e sperimentale del termine). La
definizione di SC che appare sulla homepage di Sourceforge è: “SuperCollider is an environment and
programming language for real time audio synthesis and algorithmic composition. It provides an
interpreted object-oriented language which functions as a network client to a state of the art, realtime
sound synthesis server”7. Questa definizione è stata analizzata da Andrea Valle:
1. an environment: SC è un’applicazione che prevede più componenti separate. Di qui l’utilizzo del
termine “ambiente”.
2. and: SC è anche un’altra cosa del tutto diversa.
3. a programming language: SC è infatti anche un linguaggio di programmazione. Come si dice in
seguito, appartiene alla famiglia dei linguaggi "orientato agli oggetti", ed è, tra l’altro,
tipologicamente vicino a Smalltalk. Il codice del linguaggio SC, per essere operativo (per fare
qualcosa), deve essere interpretato. SC è anche l’interprete del linguaggio SC.
4. for realtime sound synthesis: SC è ottimizzato per la sintesi del segnale audio in tempo reale.
Questo lo rende ideale per un utilizzo strumentale (performance live) così come per la
realizzazioni di installazioni/ eventi. È senz’altro possibile utilizzare SC non in tempo reale per
generare materiale audio, ma in un certo senso è meno immediato che non utilizzarlo in tempo
reale.
5. and algorithmic composition: uno dei punti di forza di SC sta nel fatto che permette due approcci
complementari e opposti alla sintesi audio. Da un lato, permette di svolgere operazioni di basso
livello sul segnale audio. Dall’altro, permette al compositore di esprimersi ad alto livello, cioè non
in termini di campioni audio, ma di strutture che rappresentino oggetti per la composizione
musicale (ad esempio: scale, pattern ritmici, etc.). In questo senso, si rivela ideale per la
composizione algoritmica, ovvero per un approccio alla composizione musicale basato
sull’utilizzo di procedure formalizzate. In SC questo tipo di operazioni può essere svolto
interattivamente ed in tempo reale.
6. [the] language [. . .] functions as a network client to a [. . .] server: l’applicazione che interpreta il
linguaggio SC è anche un cliente che comunica, attraverso una rete, con un server, un fornitore di
servizi.
7. a state of the art: attualmente SC rappresenta lo stato dell’arte nell’ambito della programmazione
audio: non c’è altro software disponibile che sia insieme così potente, efficiente, flessibile (e
ormai anche portabile).
8. sound synthesis server: SC è un fornitore di servizi, in particolare di servizi audio. La locuzione
può sembrare misteriosa. Si traduce così: SuperCollider genera audio in tempo reale su richiesta.
In questo senso, SC fornisce audio su richiesta: chi richiede audio a SC è un suo cliente (client).

Riassumendo: quando si parla di SC si possono indicare cinque cose diverse:


1. un server (→ un fornitore di servizi) audio
2. un linguaggio di programmazione per l’audio
3. l’interprete (→ il programma interprete) per il linguaggio
4. l’interprete in quanto cliente del server audio
5. il programma (→ l’applicazione complessiva) che comprende tutte le componenti 1-4

7
http://supercollider.sourceforge.net/

17
Linguaggi di programmazione

La situazione è schematizzata nella figura a lato:

L’applicazione SC prevede due parti:


- una è il server audio (denominato scsynth)
- l’altra è l’interprete per il linguaggio (sclang)
che, oltre a interpretare il linguaggio SuperCollider,
svolge il ruolo di client rispetto a scsynth.

SuperCollider può essere usato per svariate finalità molto diverse tra loro:
− costruire un proprio sistema per fare musica live, interfaccia grafica,
− compresa
− fare musica dance (nel senso più vago del termine)
− allestire un dj set
− fare composizione elettroacustica (nel senso più vago del termine)
− sonificare dati
− controllare un complesso sistema di altoparlanti (> 170) dal vivo
− ricostruire in audio binaurale il Poème électronique (ovvero: la
− diffusione di 3 tracce in movimento su 350 altoparlanti)
− integrare audio e video dal vivo
− praticare live coding
− ecc.

Cenni storici e Disponibilità rispetto ai sistemi operativi


SuperCollider è stato originariamente sviluppato da James McCartney su piattaforma
Macintosh. SuperCollider 3 (che è insieme molto simile e molto diverso da SC 2) è stato sviluppato
per il sistema operativo Mac OSX ed è ora un software open source, sviluppato da una consistente
comunità di programmatori a partire dal lavoro di James McCartney. La comunità di sviluppatori ha
così effettuato il porting anche per le piattaforme Windows8 e Linux9. Queste ultime due versioni di
SC differiscono principalmente per difetto rispetto alla versione per OSX, nel senso che alcune
funzionalità presenti in quest’ultima non sono state portate nelle altre due. Tuttavia, in vista dell’uscita
del SuperCollider Book, nell’arco della seconda metà del 2007 è stato compiuto un grande sforzo dalla
comunità si sviluppatori con l’allestimento della versione 3.2 (Febbraio 2008), che ha apportato
notevoli migliorie al software, oltre ad un notevole incremento della documentazione ed un aumentata
compatibilità tra le piattaforme. Si può dire che le differenze tra le piattaforme sia ormai limitate agli
ambienti di sviluppo (necessariamente dipendenti dal sistema operativo). Nel Luglio 2010 è stata
rilasciata la versione 3.4.

Interfacce grafiche e Ambiente di sviluppo


SC permette di programmare gli elementi della GUI facendo astrazione dal pacchetto grafico
prescelto. In sostanza in questo modo è possibile costruire pulsanti, cursori, finestre riferendosi
genericamente ad essi e selezionando un gestore grafico che si occuperà di costruirle.
SC funziona in tempo reale ed in forma interattiva attraverso un’interfaccia testuale: tutte le
comunicazioni tra utente e programma avvengono attraverso testo.
Eseguendo il programma si apre appunto un editor di testo. In particolare si apre la Post
Window, la finestra che visualizza i messaggi che SC indirizza all’utente. Vedi le figure sotto:

8
La versione per Windows è stata implementata da Christopher Frauenberger e prende il nome di PsyCollider. Il
nome deriva da Py + SCollider, poiché l’implementazione utilizza il linguaggio Python. A differenza di quanto
avveniva inizialmente, l’utente finale non è tenuto né conoscere né ad avere Python installato sulla propria
macchina per potre eseguire Psycollider.exe. Python è incluso nei file binari ed è utilizzato internamente da
Psycollider.
9
La versione per Linux è stata implementata da Stefan Kersten

18
Linguaggi di programmazione

In Windows, la finestra SC Log stampa gli stessi messaggi di un terminal aperto in parallelo
all’interno però dell’applicazione PsyCollider. Entrando in esecuzione, SC effettua alcuni passaggi di
inizializzazione il cui risultato viene scritto sulla Post Window. Nel caso di PsyCollider, viene
immediatamente avviato il server audio (si noteranno i messaggi relativi al rilevamento della scheda
audio: Device options:, Booting with:). In Windows viene altresì avviato il server grafico
SwingOSC (booting java -jar SwingOSC. . . etc).
In OSX invece l’avviamento del server audio avviene per mano dell’utente, attraverso due
interfacce grafiche gemelle: in particolare, sono disponibili due server distinti, local e internal.
È possibile utilizzare la Post Window per scrivere il proprio codice (almeno in fase di test) ma
è sempre meglio creare una nuova finestra. A questo punto, l’utente immette del codice e chiede
all’interprete di valutarlo.

ESEMPIO,
Il codice "Hello World".postln richiede di stampare sullo schermo la stringa "Hello
World" (si noterà la sintassi colorata che distingue tra la stringa e il metodo .postln). Se
l’interpretazione va a buon fine, SC risponderà con un certo comportamento che dipende dal codice
immesso: in questo caso, stampando sulla Post Window la stringa "Hello World". Altrimenti segnalerà
l’errore attraverso la Post Window.

19
Linguaggi di programmazione

CSound
Cenni generali
Csound è un potentissimo strumento software. Si tratta di un sintetizzatore virtuale ad
architettura completamente configurabile dall’utilizzatore che permette praticamente tutti i metodi di
sintesi del suono attualmente conosciuti. E’ scritto in linguaggio C da cui il nome, ed è disponibile per
le più disparate piattaforme hardware. CSound non ha alcuna limitazione quantitativa (come ad
esempio il numero di oscillatori, di filtri, di inviluppi o di segmenti disponibili) o qualitativa (il tipo di
moduli di sintesi e le loro interconnessioni possono essere creati da zero dall’utente). In effetti si tratta
di un vero e proprio linguaggio di programmazione che necessita di due file sorgenti contenenti codice
inserito dall’utente: il file con suffisso .orc (orchestra) ed il file con suffisso .sco (partitura). Questi file
vengono compilati e il risultato in uscita di questa elaborazione consiste in un file audio (.wav, .aiff o
altro formato). L’unica limitazione del Csound, fino a qualche anno fa, era che l’enorme mole di
calcoli richiesti per la sintesi andava oltre la capacità dei computer di allora e non era possibile
controllare e ascoltare in tempo reale i suoni prodotti, ma occorreva aspettare a volte anche parecchie
ore prima di poter sentire il prodotto della propria programmazione sonora. Ora, con i moderni
computer, è possibile mandare l’output dell’elaborazione al convertitore digitale/analogico
direttamente durante la fase di elaborazione, senza dover aspettare che sia stata prima completata la
creazione del file audio, cioè è possibile il tempo reale. Questo permette, tra l’altro, di "suonare"
Csound direttamente da una tastiera MIDI, come se si trattasse di uno strumento musicale. Inoltre e'
stato aggiunto il supporto della grafica in 3D in tempo reale, oltre all'audio.

Cenni storici
Csound (1986) è un softwere che nasce dall’evoluzione di una decina di programmi che, a
partire dalla fine degli anni ’50 si sono succeduti nel campo della sintesi musicale10. In particolare,
esso è l’ultimo dei tre programmi scritto da Barry Vercoe al MIT (Massachussets Institute of
Technology). Il primo fu Music 360 (1968), era scritto in linguaggio Assembler 360 e poteva girare
solo su calcolatori IBM 360 e sui successivi (370 e 83xx). Il secondo fu Music 11 (1973), era scritto in
assembler DEC PDP-11 e girava solo su PDP-11 e sul successivo VAX.
Csound (1986) conserva, migliorate, le caratteristiche dei suoi predecessori, è scritto in
linguaggio C e cio permette una facile portabilità e l’implementazione che facciano uso di processori o
schede particolari, purchè sia disponibile un sistema di sviluppo in linguaggio C. In particolare è
disponibile su minicomputers in ambiente Unix, su personal computers MS-DOS, su Macintosh.
L'unico hardware aggiuntivo necessario (ma solo per l'ascolto del suono) è un convertitore digitale-
analogico (scheda audio).

Funzionamento
Csound conserva il concetto di strumento e di nota. Un complesso di più strumenti si chiama
orchestra, ed è a tutti gli effetti un programma eseguibile che utilizza un file di dati chiamato partitura
(score), composto da una serie di note.
10
Csound e i suoi predecessori:
− Music 1 scritto da Max Mathews nel 1957 su un calcolatore IBM 704 ai Bell Labs.
− Music 2 del 1958 permetteva la generazione di qualsiasi forma d'onda.
− Music 3 del 1959 scritto per la nuova generazione dell‘ IBM 7094
− Music 4 del 1960 sempre Max Mathews alla Princeton e Stanford University, introdotto il generatore d’inviluppo (envlp)
− Music4B 1966-67 trascritto in BEFAP assembler, introdotti i filtri risonanti.
− Music4BF per la serie IBM 360, poiché quest'ultima rendeva incompatibile la codifica BEFAP,
− Godfrey lo trascrisse in fortran, più lenta ma ovunque portabile.
− Music 5 del 1966 sempre di Max Mathews.
− Music 360 del 1968 sviluppato da Barry Vercoe del MIT (Massachusetts Institute of Technology)
− Music 10 da John Chowning e James Moorer all'università di Stanford.
− Music 11 del 1973 di Barry Vercoe al M.I.T. (Massachusetts Institute of Technology)
− CSound è stato scritto nel 1986 da Barry Vercoe con l'aiuto di Kevin Peterson, Alan Delespinase,
− Bill Gardner, Dan Ellis e Paris Smaragdis, la codifica è stata portata dall'assembler del PDP-11 al C di Unix.
Fonte: Alessandro Petrolati, Interfacce grafiche in CsoundAV, L.E.M.S. 2005.

20
Linguaggi di programmazione
Csound utilizza due distinte frequenze di campionamento, una audio (sr) e una per i segnali di
controllo (kr), tipicamente pari a 1/10 di sr. Infatti, se per i segnali audio è necessaria una frequenza di
campionamento almeno doppia della massima frequenza audio che interessa, i segnali di controllo
(come per esempio gli inviluppi di ampiezza o la spazializzazione stereofonica) possono variare con
una frequenza di campionamento molto più bassa; ciò riduce la quantità di calcoli necessaria e
accelera il processo di sintesi.
Alla partenza, CSound compie una serie di operazioni preliminari, poi inizia a leggere la
score. Genera le eventuali tabelle di forma d'onda richieste dalla score, poi inizia a leggere le note.
Compie quindi alcune operazioni di inizializzazione, operazioni che vanno svolte una sola volta
all'inizio di ogni nota (conversione da pitch a Hertz, da deciBel ad ampiezze assolute etc.), poi calcola
il primo valore dei segnali di controllo. Passa quindi alla generazione dei segnali audio, di cui genera
un numero di campioni pari al rapporto sr/kr. Se, per esempio, sr=20000 e kr=2000, genera
20000/2000=10 campioni audio. Genera poi i successivi segnali di controllo, e nuovamente altri 10
campioni audio, e così via fino al termine della nota.

Costanti e variabili
Csound utilizza diverse costanti(valori che non cambino nel corso dell'esecuzione) e variabili,
cioè celle di memoria identificate con nomi che seguono certe convenzioni, e che contengono valori
che cambiano nel corso dell'esecuzione.
Le costanti sono:
− la frequenza di campionamento dei segnali audio (sr)
− e la frequenza di campionamento dei segnali di controllo (kr);
− il rapporto sr/kr (che deve obbligatoriamente essere un numero intero);
− il numero di canali di uscita, che può assumere i valori 1 (suono monofonico), 2 (suono
stereofonico) o 4 (suono quadrifonico).
Esistono tre tipi di variabili:
− ·di inizializzazione (il loro nome deve incominciare con la lettera i), e vengono calcolate una sola
volta per ogni nota, all'inizio della stessa (es.: i1, ifreq, istart, iend, i121 etc.)
− ·di controllo (il loro nome deve incominciare con la lettera k), e vengono calcolate alla frequenza kr
(es.: k1, kfreq, kstart, kend, k444 etc. )
− ·audio (il nome deve iniziare con la lettera a), e vengono calcolate alla frequenza sr (es.: a1, aout,
astart, aend, a99 etc.)
Tutte queste variabili sono visibili (cioè sono conosciute e utilizzabili) solo all'interno dello
strumento nel quale sono definite. Vi è però un modo per passare variabili fra uno strumento e un
altro. Se al nome della variabile si premette la lettera g, questa assume l'attributo globale, ed è perciò
visibile da tutta l'orchestra. Nomi validi di variabili globali sono: ga1 (variable globale di tipo audio),
gkloop (variabile globale di tipo controllo), gi1 (variabile globale di tipo inizializzazione).

La sintassi di Csound
CSound è un linguaggio di sintesi molto potente, ma richiede in cambio all'utente di seguire in
modo rigido certe convenzioni sintattiche. Comunque, nel caso di errori di sintassi, quando si lancia
CSound si ricevono messaggi di errore che contengono, oltre alla diagnostica, anche il numero di riga
del file orchestra al quale si riferiscono.

a) Orchestra

Una orchestra CSound è costituita da un file di testo, ed è composta da due parti essenziali:
l’intestazione (header) e la sezione strumenti .
Nell’intestazione vengono dichiarate sr, kr, il rapporto sr/kr, e il numero di canali di uscita.
Tutto ciò che in una riga segue un punto e virgola è considerato un commento, e non viene considerato
da CSound. Un esempio di header può essere:

sr=20000 ;frequenza di campionamento audio


kr=1000 ;frequenza di controllo
ksmps=20 ;rapporto sr/kr
nchnls=2 ;numero di canali di uscita

21
Linguaggi di programmazione
Segue poi l'orchestra vera e propria, costituita da uno o più strumenti, che devono essere
compresi fra la coppia di istruzioni instr(seguito dal numero dello strumento) e endin. Tra questi due
statements potremo scrivere tutti gli altri codici che costituiranno il nostro strumento: opcodes ovvero
codici operativi (comandi e funzioni), variabili, ecc.
Esempio:

instr 1
...
...
...
endin

instr 2
...
...
...
endin

...

La forma generale di una istruzione è la seguente:


[etichetta:] [risultato] opcode [arg1, arg2, arg3...]
Le parentesi quadre indicano che l'elemento contenuto fra esse può esserci o no; quindi l'etichetta può
mancare, così come il risultato e gli argomenti. L'opcode è il simbolo della particolare unità
generatrice utilizzata; ma alcuni esempi chiariranno questo punto.

a1 oscil iamp, ifreq, itab


viene invocata l'unità generatrice oscil (oscillatore) che deposita il risultato nella variabile audio a1, e
utilizza gli argomenti iamp (ampiezza), ifreq (frequenza) e itab (numero della tabella contenente la
forma d'onda).

out ax
viene invocata l'unità generatrice out (uscita o scrittura dei campioni di suono su disco) con
l'argomento ax.

ifreq = cpspch(8.09)
viene invocata l'uguaglianza della conversione da pitch a Hertz o cps (cpspch) del valore 8.09 (LA3), e
assegnato il risultato alla variabile di inizializzazione ifreq.

b) Score

Anche la partitura è costituita da un file di testo, ma la sua sintassi è diversa. Il codice


operativo è indicato dal primo carattere di ogni riga, che può assumere i valori i, f, a, t, s ed e. I più
importanti sono i (nota) e f (funzione o forma d'onda). Al codice operativo seguono i parametri
separati da almeno uno spazio, in numero variabile, e con un massimo che dipende dalla particolare
versione di CSound (in genere almeno 50).

Esempi di righe di partitura possono essere:


f1 0 4096 10 1 begin_of_the_skype_highlighting 0 4096 10
1 end_of_the_skype_highlighting .5
i1 9 1.5 80 6.078 1 3 6 .66.

22
Linguaggi di programmazione

Max MSP
Cenni generali
MaxMSP11 è un ambiente integrato di programmazione per la musica, l’audio e il multimedia
orientato agli oggetti grafici, sviluppato presso l’IRCAM di Parigi, ed è di fatto lo standard per la
creatività tecnologicamente evoluta in ambito musicale e visivo. Un programma in MaxMSP si
presenta come un grafico simile ad un diagramma di flusso. Ci sono infatti degli oggetti grafici (objet)
collegati tra loro da cavi (patch cord) e ciò permette lo scambio di informazioni: ogni oggetto esegue
una qualche operazione sui dati che riceve e passa il risultato dell’elaborazione agli oggetti a cui è
collegato. Un insieme di oggetti collegati che svolge una determinata funzione si chiama patch (che si
può tradurre come “collegamento provvisorio”). Si tratta di un sistema completamente aperto che
permette di risolvere la maggior parte dei problemi inerenti le esecuzioni live-electronics.
Con Max si possono eseguire dei calcoli, produrre od elaborare dei suoni, creare immagini,
controllare strumenti o apparecchiature esterne, e fare tutto ciò che un completo linguaggio di
programmazione permette di fare. Si possono quindi creare sintetizzatori, campionatori, riverberi,
effetti e molto altro, attraverso la metafora del synth modulare: ciascun modulo svolge una particolare
funzione e passa le informazioni ai moduli a cui è connesso.
Max deve il suo nome a Max Mathews e può essere considerato un discendente di Music,
benché la sua natura grafica possa trarre in inganno. Un gran numero di persone usa Max anche senza
esserne consapevoli: talvolta, infatti, alcuni file elaborati in Max (chiamati patchers) possono essere
inseriti in applicazioni standalone free o commerciali. Inoltre Max può essere usato per elaborare
plug-in audio per le case di produttori di sistemi hi-fi.

L’IRCAM di Parigi è un centro di ricerca nel


campo della musica elettronica fondato nel
1977 dal compositore Pierre Boulez e si trova
nel sottosuolo adiacente al centro Pompidou
con cui è affiliato. Al suo interno lavorano
ricercatori e compositori con nuove
tecnologie musicali. L’obiettivo dell’IRCAM
è quello di riuscire ad unire compositori,
videoartisti, scienziati ingegneri del suono per
rinnovare la musica contemporanea attraverso
il contributo della tecnologia. Uno degli
aspetti su cui si è da sempre concentrato il
lavoro degli specialisti dell’IRCAM è la
tecnologia per la manipolazione del suono in
tempo reale.
Il centro accoglie al suo interno una sala anecoica ed una da concerto ad acustica variabile: la
quantità di eco varia cambiando la geometria della sala attraverso pannelli mobili sulle pareti e un soffitto
semovente. Dalla ricerca compiuta in oltre trent’anni sono scaturite applicazioni softwere molto potenti
come Audiosculpt (musica spettrale) e Max/Msp.

Categorie di oggetti
Gli oggetti disponibili sono divisi in due, o meglio tre categorie:
1. quelli dedicati alla composizione assistita, alla parte di controllo e al MIDI che costituiscono la
parte di MAX del software;
2. quelli dedicati alla generazione ed elaborazione di audio digitale che costituiscono la parte MSP
(Music Signal Processing) dell’ambiente;
3. quella chiamata Jitter di più recente implementazione che integrano in Max/MSP l’elaborazione e
la generazione di segnali video.

11
MSP = Max Signal Processing

23
Linguaggi di programmazione

Concetti fondamentali: patch, connessioni, oggetti.


La schermata iniziale di Max MSP appare come un foglio bianco e una serie di piccoli oggetti
che il programmatore ha a disposizione. Questi oggetti non sono altro dei blocchi di codice C
prefabbricati, che appaiono sul monitor come piccoli rettangoli con un nome e delle variabili
all'interno. Ogni oggetto ha una funzione. Può essere un semplice operatore matematico, un algoritmo
per convertire coordinate polari in cartesiane, un filtro del 2° ordine, un operatore logico, un
generatore di funzioni sinusoidali o un generatore di rumore video. Nella versione standard di
Max/Msp/Jitter gli oggetti sono circa 400, ma ne esistono altrettanti scaricabili gratuitamente dalla rete
e con l'implementazione del java scripting, i cosiddetti maxers (utenti di Max/MSP) possono scriversi
da soli oggetti custom, senza dover conoscere il linguaggio C.

Un insieme di oggetti Max connessi tra


loro, che realizzano una determinata funzione più
o meno complessa e sono contenuti in una
Patcher Window si chiama patch. Inoltre, ogni
patch può contenere un numero infinito di sub
patch.

Come fanno i dati ad “entrare” e “uscire” dagli oggetti Max? Tramite dei “cavi” che possono
essere tracciati con il mouse e che collegano gli outlet e gli inlet dei diversi oggetti.

Gli oggetti di Max /MSP

OBJET BOX
Questa è una object box che contiene la funzione “+” e che ha due ingressi (inlet) e una uscita
(outlet). I dati entrano negli inlet, vengono elaborati ed escono dall’outlet. Gli
object box svolgono la maggior parte delle funzioni di Max, hanno un nome,
che ne indica la funzione e un numero variabile di inlet e outlet. Un object
box, a seconda delle funzioni che svolge può avere uno o più argomenti,
ovvero numeri o stringhe che si scrivono all’interno dell’oggetto subito dopo
il nome della funzione. In Max sono disponibili una grande quantità di object
box diversi: oggetti matematici, oggetti per la comunicazione via MIDI,
oggetti per la gestione degli eventi nel tempo, e così via.

MESSAGE BOX
Un message box è destinato a contenere messaggi da inviare ai vari oggetti, ossia i dati sui
quali operano gli oggetti di Max, il quale è un linguaggio tipato, ossia i
cui dati da elaborare sono suddivisi in tipi. Oggetti diversi possono
accettare alcuni tipi di dato soltanto. I tipi di dato in Max/MSP sono:
− Int: numeri interi
− Float: numeri con virgola decimale
− List: lista di più numeri separati da uno spazio
− Bang: messaggio che forza un oggetto a fare ciò per cui è preposto a fare
− Symbol: sequenza di caratteri non numerici che serve ad inviare comandi particolari ad oggetti
− Anymessage: alcuni oggetti possono ricevere messaggi di qualunque tipo
Esempio - Nell’esempio della figura abbiamo un object box con argomento “5”: l’oggetto +
serve a sommare i numeri che entrano nell’inlet di sinistra con l’argomento (che come abbiamo detto è
il 5 che appare dopo il simbolo +). Ad ogni numero che entra nell’inlet di sinistra viene addizionato 5
e il risultato esce dall’outlet. A cosa serve l’inlet di destra? A cambiare l’argomento: se ad un certo

24
Linguaggi di programmazione
punto volessimo addizionare 4 invece che 5, ci basterà inviare il numero 4 all’inlet di destra, e ogni
nuovo numero che entrerà nell’inlet di sinistra verrà aumentato di quattro unità .

COMMENT BOX
E’ un riquadro nel quale è possibile inserire testo per commentare la patch, che consente di
rendere quest’ultima comprensibile ad utenti diversi e facilmente interpretabile ed eventualmente
modificabile al programmatore stesso che altrimenti avrebbe difficoltà a ricostruire il ragionamento
fatto a distanza di tempo.

USER INTERFACE OBJECT


Un’ulteriore categoria di oggetti dedicati all’interfaccia utente è costituita dagli user interface
object, oggetti di vario tipo che servono a manipolare graficamente dati e grandezze in uso nella patch.
Uno di questi è la kslider, ossia la tastiera virtuale mediante la quale è possibile definire una
nota midi e la sua velocity (0-127). Tra gli esempi troviamo anche l’oggetto waveform che serve a
rappresentare la forma d’onda di un file audio, l’oggetto breakpoint function editor, utilizzato per la
creazione e l’editing di inviluppi. Ed ancora il filter graph che permette di editare i parametri e la
risposta di un filtro; oppure lo scope, un oscilloscopio che può diventare correlatore di fase e
analizzatore di spettro.

JITTER
E’ l’ insieme di tutti quei componenti che rendono possibile l’ elaborazione multimediale
video. JITTER permettere di estendere l’ elaborazione multimediale audio di MAX / MSP al video ed
alla grafica. I componenti JITTER sono in grado di strutturare i dati sotto forma di matrici multi
dimensionali e grazie a questo metodo sono in grado anche di elaborare e processare qualsiasi tipo o
serie di dati numerici (vedi quindi ad esempio anche quelli audio). JITTER nasce quindi per elaborare
semplicemente e velocemente grosse moli di dati numerici come possono essere file video o grafici.

Max-MSP - Esempio di manipolzione audio-video

Per quanto riguarda il video, JITTER fa utilizzo di moltissimi operatori matematici, sistemi di
analisi, convertitori e correttori di colori, elaboratori di canali alpha, creatori di spazio, effetti speciali
e tutti questi componenti sono disponibili per essere utilizzati nelle proprie creazioni. JITTER supporta
il Quicktime video di Apple, il DV delle camere digitali ed è in grado di convertire tra loro formati di
video diversi oltre che poter interagire con qualsiasi periferica hardware sia essa Firewire, Usb o
seriale. A livello grafico esistono componenti per il supporto sia del 2D che del 3D che di bus
accelerati grafici come OpenGL API. Il modo di impiego dei componenti è lo stesso di MAX : blocchi
grafici / moduli che possono essere linkati tra loro per generare un flusso procedurale di elaborazione.
Uno degli esempi più semplici è la possibilità di modificare un'immagine in tempo reale
tramite una sorgente sonora. Si possono creare cioè similitudini fra altezza del suono e luminostà
immagine: più il suono è basso, più l'immagine è buia (e viceversa). In questo caso l’oggetto di Jitter
da poter utilizzare si chiama jit.brcosa dove BRCOSA sta per BRightness, COntrast e SAturation.
permette in tempo reale di modificare appunto luminosità, contrasto e saturazione di un'immagine.

25
Linguaggi di programmazione

Bibliografia/Sitografia:

− Andrea Valle – tSCIRMA: the SuperCollider Italian Manual at CIRMA, (→ DRAFT: 9 aprile 2008)
Sito: http://www.cirma.unito.it/andrea/sc.html
− Alessandro Petrolati, Interfacce grafiche in CsoundAV, L.E.M.S.
2005.http://www.csounds.com/articles/Interfacce_grafiche_in_CsoundAV.pdf
− Riccardo Bianchini, Introduzione a Csound, www.csounds.com/maldonado/cs_rb.htm
− Alessandro Cipriani – Maurizio Giri, Musica elettronica e sound design vol.1 / Teoria e pratica con
MaxMSP, ed. ConTempoNet, 2009.

26