La wavetable di tale oscillatore è implementata dall’array Tabsen: i suoi valori sono calcolati dalla funzione CreateTabsen, che è invocata a tempo di inizializzazione della libreria (per mezzo della macro InitpCM). Lo stato dell’oscillatore è mantenuto dalla struttura oscil, e in tal caso si limita alla sola fase di oscillazione. La parte operativa dell’element è costituita dalle seguenti due funzioni:
5 È importante distinguere tra heap e memoria libera, poichè lo standard lascia volutamente non specificato in che modo queste due aree di memoria siano correlate. Infatti, è specificato che malloc/free non devono essere implementate in termini di new/delete ([C++98]: sezione 20.4.6, paragrafi 3 e 4). In effetti, heap e memoria libera si comportano in modo differente e vi si accede in modo differente.
• newsinOsc: crea un nuovo sinosc, effettuandone l’allocazione e l’inizializzazione (è l’analogo del costruttore).
• SinOsc: svolge il calcolo del successivo campione emesso dall’oscillatore in ingresso, calcola- to rispetto ad una certa frequenza, in modo analogo a quanto esposto nel paragrafo 1.2.1. Oltre a oscil, il toolkit DSP definisce molti altri element, come filtri, inviluppi, delay, riverberi, ge- neratori di rumore, pluck, sampling, saw-wave, square-wave, flanger, e altro ancora. Ovviamente, il nostro obiettivo è quello di comprendere la struttura generale con cui un generico element è conce- pito, e non di certo presentare uno ad uno il funzionamento particolare di ogni singolo element (sebbene il lettore sia esortato a dare un’occhiata al codice di un qualche element a scelta).
Per un generico element di tipo structxxx, convenzionalmente il “costruttore” è implementato dal- la funzione newXxx, che restituisce un puntatore ad un blocco di memoria nell’heap, allocato (ed ini- zializzato) per mantenere un oggetto di tipo xxx.
Si noti che nel listato 2.6, per l’allocazione, newsinOsc utilizza la funzione NewPtrClear (dichiarata nell’header MacMemory.h), parte del Memory Manager di Mac OS X v10.0, ormai deprecata e sosti- tuita dalla funzione standard malloc.
Nel caso più semplice, la distruzione di un element avviene liberando la memoria allocata per esso sullo heap, con una chiamata a free. La dove siano necessarie operazioni ulteriori, è comunque pos- sibile definire una funzione a parte, convenzionalmente disposeXxx. Per esempio, questo è il caso dell’element envelope (vedi envelope.cpp).
Mentre in C++ la chiamata al metodo di un oggetto utilizza l’operatore operator ->, nel nostro caso si adotta la convenzione che il primo argomento di qualunque funzione concettualmente legata alla definizione di un element sia sempre l’istanza dell’element a cui la chiamata si riferisce.
Dietro la scelta di rinunciare al supporto alle classi del C++, molto probabilmente, c’è il timore che il prezzo da pagare per costrutti troppo sofisticati, come appunto quello delle classi e oggetti, possa
avere un impatto negativo sulle prestazioni. Per l’implementazione di pCM+ ci svincoleremo da questa linea di pensiero, sfruttando a 360 gradi le potenzialità espressive del C++.
2.4.4.2. Gestione degli eventi
Il framework pCM è stato implementato avendo in mente sia l’approccio algoritmico alla composi- zione [Hiller81], sia il controllo gestuale interattivo in performance dal vivo definito dal paradigma della musica elettroacustica [Tarabella03a].
Non esiste nessuno schema rigido sul modo con cui una composizione pCM-based generi gli eventi audio da destinare al rendering da parte dell’orchestra. Questo può avvenire in modo statico, cioè gli eventi generati sono noti nel momento in cui è definita la composizione, seguendo un approccio simile allo score tradizionale di Csound, oppure può avvenire in modo dinamico, applicando una qualche logica di mapping sui dati ricevuti dalle interfacce MIDI esterne.
Soluzioni intermedie tra i due approcci sono inoltre applicabili, dando al compositore piena libertà sul modo di concepire una composizione. Ad esempio, i messaggi di controllo ricevuti dall’utente possono essere usati per pilotare il modo con cui l’algoritmo di sintesi genera gli eventi audio. In tal caso, ad un singolo messaggio MIDI, possono corrispondere uno o più eventi audio da inoltrare al motore di sintesi.
Come già detto, per inoltrare un certo evento audio al motore di sintesi, lo score imposta le caratte- ristiche proprie di quell’evento audio invocando il metodo trig sull’instrument a cui l’evento è de- stinato. I parametri a cui può riferirsi l’evento dipendono ovviamente da quali parametri in ingresso compaiono nella firma del metodo trig dell’instrument in uso.
Tale funzionamento avviene in tempo reale: se nel corso dell’esecuzione di una composizione (cioè di un programma), lo score invoca il metodo trig di un certo instrument X, passando, per esempio, una frequenza di 440Hz, appena la chiamata termina, l’orchestra emetterà immediatamente un se- gnale con quella frequenza.
In questo modo però resta difficile riuscire ad avere un controllo sull’istante in cui si vuole emettere un certo evento. Per far fronte a questo problema, pCM introduce alcune funzionalità rivolte proprio alla gestione e alla sincronizzazione degli eventi audio.
Ogni valutazione temporale viene svolta rispetto all’ultimo ResetTime (macro definita in STARTpCM.h, vedi listato 2.1), che aggiorna il valore della variabile globale microReset, in modo tale che essa mantenga il tempo trascorso in micro-secondi dall’ultimo startup del sistema. Ad ogni modo, il codice utente non ha alcuna necessità di accedere direttamente al valore di questa variabile globale. Essa è infatti utilizzata dietro le quinte dal framework, per implementare le funzionalità rivolte al supporto agli eventi.
All’interno di un movimento è possibile specificare un evento utilizzando la macro At(t), dove il parametro t è l’istante espresso in secondi in cui intraprendere una certa azione (singola istruzione, o blocco di istruzioni), che solitamente è una chiamata al metodo trig di un instrument. Come già accennato, l’istante t è valutato rispetto all’ultimo ResetTime.
Per esempio, il codice nel listato 2.7 genera una scala di semitoni da LA3 a LA4 (e rappresenta un modo alternativo di esprimere quanto fatto nello score del listato 2.5). Si noti che le note specificate sono macro definite nel file header STARTpCM.h, sostituite dalla frequenza in Hz corrispondente. Analogamente a Csound, gli eventi non devono necessariamente essere specificati in ordine cre- scente rispetto ai tempi di emissione. pCM provvederà dietro le quinte ad ordinarli prima di emet- terli nei giusti tempi.
Il numero massimo di eventi definibili all’interno di un movimento è limitato a 10.000. Inoltre, un altro “limite” è rappresentato dalla durata massima di un movimento (non dell’intera composizio- ne), che non può eccedere i 232 micro-secondi, cioè circa 400 secondi (poco più di un’ora).
#include "../Framework/STARTpCM.h" #include "../instruments/stringks.h" senzaIngressoAudio
// Shared Global Variables: StringKS *corda = new StringKS; ...
Composition ... Mov MyOrch
At( 0.0 ) corda->trig( La/2 ); At( 0.5 ) corda->trig( Lad/2 ); At( 1.0 ) corda->trig( Si/2 ); At( 1.5 ) corda->trig( Do ); At( 2.0 ) corda->trig( Dod ); At( 2.5 ) corda->trig( Re ); At( 3.0 ) corda->trig( Red ); At( 3.5 ) corda->trig( Mi ); At( 4.0 ) corda->trig( Fa ); At( 4.5 ) corda->trig( Fad ); At( 5.0 ) corda->trig( Sol ); At( 5.5 ) corda->trig( Sold ); At( 6.0 ) corda->trig( La ); EndMov
End