Linguaggi di programmazione

40
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 1

Transcript of Linguaggi di programmazione

Linguaggi di programmazione

Elaborato di supporto agli esami diLinguaggi 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 algoritmi1. 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:00000000000000000000000001000000000000000001000000000000010001000000001000000001000000000000000000000001000000000000000000111100

Assembler:z : INT;x : INT 8;y : INT 38;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-execute2 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 programmazionel’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 programmazioneProgrammazione 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 programmazioneimplementare 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 programmazioneche è 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 programmazionelinguaggio 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 programmazionedi 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 programmazioneSemantica

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 memoriariservata 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:

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.

5 binding = legame

11

Linguaggi di programmazioneI 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 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.

12

Linguaggi di programmazioneUna 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 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.

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.

13

Linguaggi di programmazione

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 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à

14

Linguaggi di programmazione

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.

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).

15

Linguaggi di programmazione

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) audio2. un linguaggio di programmazione per l’audio3. l’interprete (→ il programma interprete) per il linguaggio4. l’interprete in quanto cliente del server audio5. 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.

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

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(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 audiokr=1000 ;frequenza di controlloksmps=20 ;rapporto sr/krnchnls=2 ;numero di canali di uscita

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

21

Linguaggi di programmazionestatements 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 .5i1 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 programmazionepunto 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