Formati e codec
Come risultato del processo di campionamento di un segnale audio analogico otteniamo una sequenza di numeri binari (numeric streams) che può essere scritta in particolari tipi di files (audio files o sound files) memorizzati su svariati tipi di supporti digitali (CD, DVD, HD o altro). Questi files possono avere tre diversi formati di codifica:
- Non compressi: tutti i dati derivati dal processo di campionamento sono scritti nel file memorizzato.
- Lossy: l’informazione contenuta nel file memorizzato è minore di quella contenuta nei dati di origine (perdita di informazioni).
- Lossless: l’informazione contenuta nel file memorizzato è identica a quella contenuta nei dati di origine ma i dati sono comunque compressi.
Queste diverse possibilità nascono infatti dall'esigenza che nel momento in cui vogliamo memorizzare su un supporto digitale tutte le informazioni riguardanti un segnale potremmo avere la necessità di ridurre lo spazio di memorizzazione occupato a vantaggio della portabilità o della trasmissività del flusso codificato e per farlo dobbiamo ricorrere alla compressione delle informazioni stesse in un modo tale che permetta anche l'operazione inversa.
Questa operazione è svolta dai codec che sono dei programmi (o dispositivi) che si occupano sia della digitalizzazione dei segnali (tipicamente audio o video) che della loro codifica e/o decodifica digitale. Esistono vari tipi di codec, differenti tra loro per il tipo di segnale su cui devono operare e per l’algoritmo di codifica/compressione in essi implementato. Ogni formato di codifica può essere ottenuto da più codec differenti. Questi infatti permettono di ascoltare formati proprietari aperti da qualunque lettore di file, mantenendo separati il livello fisico del formato da quello logico della sua rappresentazione.
I vantaggi della compressione sono:
- occupare minor spazio sul supporto di destinazione.
- impiegare minor tempo in fase di trasferimento dati (bitrate).
Il costo (svantaggi) è l’aumento dei tempi di lettura/scrittura legati ai tempi di decompressione/compressione e, nel caso di audio files anche in termini di qualità audio.
Bitrate
Prima di approfondire i diversi tipi di formati audio soffermiamoci su concetti legati alla velocità di trasmissione dei dati in quanto i file audio sono per loro natura legati al tempo che scorre: ad ogni secondo è associato un certo contenuto informativo e quindi una certa sottosequenza di cifre binarie. Il numero di cifre binarie che compongono queste sottosequenze è detto bitrate. Il bitrate è il numero di cifre binarie impiegate per immagazzinare un secondo di informazione. I cd musicali ad esempio hanno come standard una frequenza di campionamento pari a 44.100Hz che genera dunque 44.100 valori al secondo per ogni canale. Nel caso di un file stereofonico vanno poi moltiplicati per 2 e siccome il campionamento avviene a 16 bit (pari appunto a 2 byte) vanno ulteriormente moltiplicati per 2:
44.100 * 2 * 2 * 60 (secondi) = 10.584.000 bytes (~10 MB) ogni minuto
Il bitrate si esprime in kilobit per secondo (kbps) e può variare da 32 a 320kbps. Se volessimo ad esempio calcolare il bitrate del file precedente dovremmo calcolare:
44.100 * 2 * 2 * 8 (da bytes a bit) = 1.411.200 bit/secondo (1.411 kbs)
I calcoli appena effettuati fanno riferimento a un formato non compresso mentre nel caso dei formati compressi, al diminuire della lunghezza globale del file diminuisce anche la lunghezza media delle sottosequenze e di conseguenza il bitrate medio che corrisponderà al fattore di compressione. Infatti se un file con un bitrate di 1411 Kbps come quello dell'esempio precedente fosse compresso fino ad ottenere un bitrate medio di 320 Kbps, avremmo ridotto le dimensioni del file originale di un fattore pari a circa 4.5 (1411/320).
Attualmente nei codec più evoluti esistono tre tipologie di implementazione del bitrate:
CBR (Costant BitRate). La modalità più semplice, più usata e ormai meno efficace. Il bitrate rimane costante in ogni frame e questo significa che l’encoder utilizzerà sempre la stessa quantità di bit per codificare ogni passaggio musicale. In pratica i passaggi più complessi avranno una qualità inferiore di quelli con poca dinamica o silenzio visto che saranno codificati con un numero sempre uguale di bit, mentre ne servirebbero di più per iprimi e di meno per i secondi. Un grosso vantaggio di questa modalità è che la dimensione del file risultante è sempre proporzionale alla durata del pezzo e facilmente valutabile.
ABR (Average BitRate). Il bitrate medio è una modalità che ha una resa superiore al CBR e consiste in una sorta di bitrate variabile. L’encoder codifica i passaggi che lo necessitano con più bit e quelli più semplici con meno, cercando di mantenere nell’intero file il bitrate medio impostato. In questo modo la dimensione del file sarà sempre abbastanza prevedibile e proporzionale tuttavia si ha un vantaggio in qualità rispetto al CBR in quanto i bit risparmiati nei passaggi musicali semplici saranno utilizzati per aumentare la risoluzione di quelli più complessi.
VBR (Variable BitRate ). Il bitrate è realmente variabile. Impostando un indice di qualità e un bitrate massimo e minimo l’encoder codifica ogni frame utilizzando il bitrate più appropriato, quindi per i passaggi musicali via via più complessi verrà utilizzato un bitrate sempre più alto o massimo e per i passaggi musicali più facili verrà utilizzato più basso. Per contro si ha una maggiore incertezza sulle dmensioni del file ottenuto
Formati non compressi
Esistono formati audio che non hanno compressione e che in fatto di qualità sonora sono i migliori. Per contro occupano molto più spazio in memoria ed una minore velocità di trasmissione rispetto ai formati compressi. Con software professionali come Pro Tools, SuperCollider o Max generalmente si lavora con file di questo tipo. I due principali formati sono:
WAV (Wave). Formato audio sviluppato da Microsoft e IBM per PC IBM compatibili. In questo formato gli strumenti musicali si sentono allo stesso modo indipendentemente dal PC su cui il file viene ascoltato (a parità di qualità acustica dei componenti hardware, chiaramente).
AIFF (Audio Interchange File Format). Formato sviluppato dalla Apple viene anche chiamato Apple Interchange File Format.
Compressione lossy
Permette compressioni maggiori, ma a scapito della qualità sonora. I metodi di compressione lossy in generale tendono a scartare le informazioni ritenute inutili, mantenendo solo quelle essenziali e nascono dall’idea che non tutte le frequenze contenute in uno spettro sonoro vengono percepite dall’orecchio umano. Si vanno allora a tagliare le alte frequenze, che si ritiene siano quelle meno distinte dal nostro orecchio. Ovviamente più frequenze si tagliano più lo spazio occupato dalla traccia audio diminuisce e con questo anche la qualità del risultato in quanto il processo di riconversione non permette il completo ripristino delle frequenze tagliate. Vediamo quali sono i principali formati audio di questo tipo:
MP3 (MPEG-1/2 Audio Layer 3). Algoritmo di compressione audio in grado di ridurre drasticamente la quantità di dati richiesti per riprodurre un suono, riuscendo nel compromesso di ottenere una riproduzione quasi fedele del file originale non compresso. Il codec migliore per la compressione è il Lame. bitrate massimo raggiungibile 320 Kbps.
WMA (Windows Media Audio). Standard per file audio inventato dalla Microsoft. Formato di compressione audio molto simile a un MP3.
OGG (Vorbis). E' un algoritmo open source e a parità di qualità percepita, permette una maggiore compressione rispetto al formato MP3, ottenuta mediante avanzate ricerche di psicoacustica.
AAC (Advanced Audio Coding). E' un formato di compressione audio incluso ufficialmente nell’MPEG-4. Fornisce una qualità audio superiore al formato MP3 con una codifica più compatta. Attualmente viene utilizzato principalmente da Apple, che nella variante che gestisce i diritti d’autore ha una compressione a 128 Kbps (lo standard di iTunes Store) e corrisponde a quella di un MP3 a 192 Kbps a bitrate costante. I tempi di conversione sono un po’ più lenti rispetto agli altri formati.
AC3 (Dolby Digital). Il Dolby Digital è un sistema di codifica audio multicanale sviluppato da Dolby Laboratories Inc ed utilizzato al cinema, nella TV digitale, nei Laser Disc, DVD ed in altri supporti di riproduzione o trasmissione audio digitale. Lavora da un minimo di 96 kbps ad un massimo di 640 kbps. Al cinema il Dolby Digital viene utilizzato con appena 320 kbps di banda, poiché stampato nel poco spazio disponibile fra i fori di scorrimento delle pellicole. Generalmente su DVD viene utilizzato con un bitrate di 192 kbps per codificare segnali stereo (2.0, 2.1) o stereo surround, e con un bitrate compreso fra 384 e 448 kbps per i segnali 5.1.
Compressione Lossless
Questi metodi di compressione cercano di diminuire lo spazio occupato dalla traccia senza andare a toccare il suono. La percentuale di compressione è decisamente inferiore rispetto ai metodi lossy, ma non si verifica perdita di qualità e in fase di riconversione il suono è identico all'originale. Vediamo quali sono i principali formati audio di questo tipo:
FLAC (Free Lossless Audio Codec). Diffuso codec audio open source attualmente ha un buon supporto da parte di vari software audio. Diversamente dalla maggior parte degli algoritmi di compressione lossless (come ZIP e gzip, per esempio) che raggiungono soltanto un 10-20% di compressione raggiunge compressioni importanti, dell’ordine del 30-50%.
APE (Monkey's Audio). Formato no lossy che permette di ridurre di circa il 50% lo spazio occupato dalla nostra musica (in certi casi anche di più). Attualmente non più sviluppato.
ALAC (AApple Lossless Audio Codec). Formato sviluppato dalla Apple memorizza i dati in un contenitore MPEG-4 con estensione .m4a. Non prevede una gestione dei diritti digitali (DRM) e al giorno d'oggi è in disuso.
Sound files e buffers
Path
Nel momento in cui decidiamo di caricare un audio file in un Buffer dobbiamo dire a SuperCollider dove andare a cercarlo fornendogli l'indirizzo e il percorso (path) da compiere per trovarlo sotto forma di stringa. Per ottenere facilmente questa informazione possiamo selezionare e trascinare (click and drop) con il mouse il file desiderato nello spazio che delimita l'Interprete ottenendo automaticamente l'intero percorso che si chiama path assoluto:
"/Users/andreavigani/Desktop/21_giugno/samples/archi.wav"
Questa operazione è valida per tutti i tipi di file, non solo quelli audio e come facilmente intuibile separa le cartelle e sottocartelle con il simbolo '/' (slash) seguendo una direzione d'inclusione che va da sinistra a destra.
Specificando un percorso in queso modo (path assoluto) possiamo però incorrere in una problematica: se dovessimo spostare il file o la cartella all'interno del computer oppure eseguire il codice su di un altro computer dovremmo ogni volta sostituire il path precedente con quello nuovo. Fortunatamente esiste un metodo che ci permette di specificare un path relativo, ovvero fa aggiungere autmaticamente a SuperCollider il percorso prima della cartella o del file che specifichiamo fino al punto in cui si trova il file su quale lo richiediamo. In questo caso l'unica limitazione consiste nel fatto che il file deve essere stato salvato sull'Hard disk:
"samples".resolveRelative;
E' buona pratica posizionare gli audio files che vogliamo utilizzare in uno script di SuperCollider all'interno di una cartella che è essa stessa all'interno di un'altra cartella contenente anche il file con le istruzioni necessarie a caricarli in un buffer. In questo modo scrivendo:
"samples/archi.wav".resolveRelative; // "sample" è il nome della cartella contenente gli audio files
non dovremo ogni volta ridefinire il path assoluto anche nel caso di spostamento della cartella principale su di un altro computer.
Caricare un sound file in un Buffer
Possiamo caricare un solo audio files in un singolo buffer invocando il metodo .read sulla Classe Buffer. Il file può essere monofonico, stereofonico o multicanale in quanto il buffer si adatta automaticamente al suo numero di canali:
( b = Buffer.read(s, // Server "samples/archi.wav".resolveRelative, // Path del file da caricare action:{("File "++"samples/archi.wav".basename++" loaded in Buffer " ++b.bufnum).postln}); // Un azione che compie quando il file è stato caricato )
Eseguendo il codice precedente SuperCollider crea un nuovo buffer nel Server, gli assegna automaticamente un numero partendo da 0 (indicizzazione) e carica i dati dell'audio file al suo interno. Se ad esempio eseguiamo due o più volte il codice precedente, a ogni esecuzione sarà creato un nuovo buffer contenente sempre lo stesso audio file ma con numero d'indice crescente anche se assegnato sempre alla stessa variabile (che sarà di volta in volta sovrascritta). Per sapere quale numero è assegnato ad un buffer possiamo invocare sulla singola istanza il metodo .bufnum:
b.bufnum;
Nel blocco di codice di esempio abbiamo anche programmato un monitor visivo che stampa nella Post window questa informazione unita al nome dell'audio file caricato. Per farlo abbiamo recuperato il nome dal path invocando su di esso il metodo .basename che riporta solo questa informazione tagliando il resto del path e concatenato tra loro valori e stringhe attraverso la sintassi ++:
"File "++"samples/archi.wav".basename++" loaded in Buffer "++b.bufnum;
Possiamo anche caricare un sound file in un Buffer attraverso una finestra di dialogo invocando il metodo .loadDialog:
b = Buffer.loadDialog(s, action:{("File loaded in Buffer "++b.bufnum).postln})
Quando carichiamo un sound file in un Buffer potremmo aver bisogno di alcune informazioni sia sul sound file originale sia sul buffer sul quale lo caricheremo. Queste potranno tornarci utili quando andremo a rileggerne il contenuto.
SoundFile e Buffer info
Il modo più semplice per ottenere informazioni su di un sound file consiste nel generare una finestra grafica nella quale queste sono visualizzate automaticamente:
( f = SoundFile.new; f.openRead(Platform.resourceDir +/+ "sounds/a11wlk01.wav"); f.inspect; f.close; )
Eseguendo il codice precedente abbiamo:
- Creato una nuova istanza della Classe SoundFile,
- Aperto e letto i dati di un sound file invocando su di essa il metodo .openRead() specificando il path del file come argomento,
- Visualizzato i dati in una GUI invocando il metodo .inspect
- Chiuso il sound file invocando il metodo .close
In alternativa possiamo recuperare le singole informazioni invocando su un'istanza di SoundFlie diversi metodi ma prima dobbiamo riaprire il file:
f = SoundFile.new; f.openRead(Platform.resourceDir +/+ "sounds/a11wlk01.wav");
- playback del sound file (ma solo per monitorarne il contenuto):
f.play;
- path assoluto del sound file:
f.path;
- formato audio del sound file:
f.headerFormat;
Nell'immagine seguente una lista dei formati audio supportati da SuperCollider:
- formato di campionamento del sound file:
f.sampleFormat;
Nell'immagine seguente una lista dei formati supportati da SuperCollider:
- numero di canali del sound file:
f.numChannels;
- numero di frames del sound file:
I frames equivalgono al numero di campioni moltiplicato per il numero di canali del file:
- se il buffer è mono: frames = samples
- se il buffer è stereo: frames = samples*2, etc.
f.numFrames;
- rata di campionamento alla quale è stato registrato il sound file:
f.sampleRate;
- durata in secondi del sound file:
f.duration;
Ricordiamo che il numero di samples (o frames) di un sound file non ci fornisce alcuna indicazione riguardo la sua durata se non lo mettiamo in relazione con la rata di campionamento: un suono registrato e riletto con una rata di campionamento di 44100 Hz dura un secondo, ma se registrato a 44100 e riletto a 88200 Hz dura mezzo secondo (lo stesso numero di campioni ha due durate differenti).
Per ottenere informazioni su di un Buffer possiamo invocare gli stessi metodi appena incontrati con l'aggiunta di altri riassunti nel codice seguente:
b = Buffer.read(s,Platform.resourceDir +/+ "sounds/a11wlk01.wav"); // carica un sound file b.bufnum; // numero (indice) del buffer b.plot; // visualizza il contenuto in un Plotter b.play; // 'suona' il buffer (solo per monitorarne il contenuto) b.path; // riporta il path assoluto del file caricato b.numFrames; // riporta il numero di Frames b.numChannels; // riporta il numero di canali b.sampleRate; // riporta la rata di campionamento (che portebbe essere diversa da quella del file caricato) b.duration; // riporta la durata in secondi b.query; // riporta le informazioni precedenti attraverso un solo comando
Pulire e liberare
Una volta che abbiamo creato un Buffer (allocato la memoria) possiamo cancellarne il contenuto invocando su di esso il metodo .zero:
b.zero; b.plot;
In questo caso il Buffer assegnato alla variabile b continua a esistere nel Server e occupa una memoria di tanti frames quanti quelli del file che conteneva. Se invece vogliamo eliminare il Buffer dal Server e liberare la variabile alla quale lo avevamo associato possiamo invocare il metodo .free:
b.free; b.plot; // WARNING: Buffer not allocated, can't plot data
Se invece vogliamo eliminare tutti i Buffers allocati su un Server possiamo invocare il metodo .freeAll:
Buffer.freeAll;
Questo comando ritorna utile in quanto c'è un limite nel numero di buffers che possono essere creati in un Server:
s.options.numBuffers; // Per ottenerne il numero s.options.numBuffers = 2000; // Si può cambiare ma bisogna fare il reboot del server
Caricare un solo canale
Se vogliamo caricare solo un numero parziale di canali di un file multicanale lo possiamo fare invocando il metodo .readChannel. Se non specifichiamo argomenti carica tutti i canali:
b = Buffer.readChannel(s, "samples/celesta.wav".resolveRelative); // il file è stereo b.plot; b.play;
Se invece specifichiamo come argomento (channels:) quale canale o quali canali vogliamo caricare sotto forma di Array carica solo quelli:
b = Buffer.readChannel(s, "samples/celesta.wav".resolveRelative,channels:[0]); // solo canale sinistro b.plot; b.play;
- [0] = sinistro
- [1] = destro
- [0, 1] = entrambi
- etc.
Caricare una porzione di sound file
Se vogliamo caricare solamente una porzione di file possiamo specificare inizio e fine in frames come secondo e terzo argomento (in questo caso il numero di frames corrisponde ai sample anche nel caso di files multicanale):
Buffer.freeAll; ( b = Buffer.read(s, "samples/celesta.wav".resolveRelative, 3456, // primo frame (onset) 12345) // numero di frames da leggere (durata) ) b.numFrames; b.plot; b.play; // Se vogliamo specificare la durata in secondi: ( b = Buffer.read(s, "samples/celesta.wav".resolveRelative, 0.0, // inizio s.sampleRate * 0.1) // durata (sample_rate * secondi) ) b.numFrames; b.plot; b.play; // Se vogliamo specificare inizio e durata in secondi: ( b = Buffer.read(s, "samples/archi.wav".resolveRelative, s.sampleRate * 0.1, // 4410 s.sampleRate * 0.3) // 13230 ) b.numFrames; b.plot; b.play; // Se vogliamo specificare inizio e fine in secondi: ( var inizio = 0.3, fine = 0.6; b = Buffer.read(s, "samples/archi.wav".resolveRelative, s.sampleRate * inizio, s.sampleRate * abs(fine-inizio)) // calcolo la durata ) b.numFrames; b.plot; b.play;
Caricare una cartella di sound files
Abbiamo suggerito in precedenza di posizionare tutti gli audio files in una cartella. Vediamo allora come caricarli tutti automaticamente in diversi buffers (uno per ogni file).
Definiamo il path assoluto della cartella e selezioniamo tutti i files con estensione .wav ivi contenuti invocando il metodo .pathMatch. Se volessimo solo files con altre estensioni basterà cambiare il suffisso .wav con l'estensione desiderata:
~paths = ("samples".resolveRelative ++ "/*.wav").pathMatch;
Eseguendo il codice precedente, assegnamo alla variabile globale ~paths un'Array di path assoluti dei file con estensione .wav contenuti nella cartella specificata.
Generiamo un'Array di buffers con il metodo ..collect invocato sull'Array ~paths:
~bufs = ~paths.collect{arg i; Buffer.read(s, i)};
Richiamiamo i singoli buffer invocando sull'Array il metodo .at come di consueto:
~bufs.at(0).play; ~bufs.at(1).play; ~bufs.at(2).play; ~bufs.at(3).play; ~bufs.at(4).play; ~bufs.at(rand(~files.size-1)).play; Buffer.freeAll;
Menu popup
Possiamo creare un Pop-up menu con i nomi dei file contenuti nei buffers in modo da poter interagire con una GUI.
Generiamo un Array contenente solo i nomi dei sound files ricavandoli automaticamente dall'Array di path generato in precedenza:
~nomi = ~paths.collect{arg i; PathName(i).fileName};
Verifichiamo che i sound files corrispondono agli indici dei buffers:
[~nomi, ~bufs].flop.do{arg i; i.postln}; "" // "" Per stampare tutto nella post window
Il metodo .flop inverte righe e colonne di un Array bidimensionale. O meglio prende l'indice 0 di due Array diversi e ne restituisce uno nuovo con i due items collocati a quell'indice, poi passa all'indice 1, e così via.
- Generiamo un Popup menu popolando automaticamente i nomi con l'Array nomi:
( w = Window.new("popup", Rect(800, 200, 200, 30)) .alwaysOnTop_(true) .front; p = PopUpMenu.new(w, Rect(2, 2, 196, 20)); p.items_(~nomi); // Quando interagiamo con il pop-up, restituisce l'indice del menu, partendo da 0. p.action_({arg view; view.value.postln}); )
Per selezionare ed eseguire i diversi files interagendo con la GUI possiamo usare il metodo p.value per ottenere lo stato del PopUpMenu e mapparlo come indice di selezione dei buffers contenuti nell'Array ~bufs:
- p.value == 0 --> ~bufs.at(0)
- p.value == 1 --> ~bufs.at(1)
- p.value == n --> ~bufs.at(n)
Separando di fatto l'azione di selezione del file da quella di esecuzione:
~bufs.at(p.value).play;
Se vogliamo invece triggerare il playback del buffer contestualmente alla sua scelta sul PopUpMenu possiamo utilizzare il valore riportato da view.value:
( w = Window.new("popup", Rect(800, 200, 200, 40)).alwaysOnTop_(true).front; p = PopUpMenu.new(w, Rect(2, 2, 196, 20)) .items_(~nomi) .action_({arg view; ~bufs.at(view.value).play}); )
In entrambi i casi tutti i buffers saranno eseguiti fino alla fine.
Visualizzare sound files
Come per altri oggetti di SuperCollider (Array, Env, etc.) abbiamo a disposizione due modi per visualizzare un sound file:
- in un Plotter caricandoli prima in un Buffer,
- in un'istanza della Classe SoundFileView (il corrispondente di EnvelopeView per gli inviluppi) senza necessariamente caricarli in un Buffer.
Plot
Nel primo caso possiamo creare un Plotter per ogni Buffer che contiene un sound file invocando su di esso il metodo .plot:
( b = Buffer.read(s, "samples/celesta.wav".resolveRelative); {b.plot(name: "buffer "++b.bufnum++" - "++"samples/archi.wav".basename, bounds:540@200, minval: -1, maxval: 1)}.defer(0.2) )
Realizzando un Plotter possiamo solo visualizzare il contenuto di un Buffer ma non possiamo interagire in alcun modo con esso.
SoundFileView.new()
Possiamo trovare info su come visualizzare dinamicamente sound files a questo link