Il suono digitale ed il formato WAV

- tags: audio, cpp, programming - 27 comments

Vista la scarsa documentazione sull’argomento, in particolar modo in lingua nostrana, oggi vorrei parlarvi di come i suoni vengono gestiti ed immagazzinati dal nostro calcolatore, partendo dal concetto di suono analogico fino a vedere nel dettaglio uno tra i più semplici formati utilizzati per l’immagazzinamento di informazioni audio: il WAV.

Ringrazio anticipatamente l’amico che mi ha spinto ad informarmi sull’argomento e che ha contribuito a colmare una volta per tutte alcune lacune che mi portavo dietro da tempo. E direi che se alla fine della lettura questo articolo vi sarà stato utile, dovreste ringraziarlo anche voi.. ;)

Conclusa questa breve ma non inutile introduzione, passiamo ai fatti e per farlo partiamo dal concetto di “suono”, dato spesso per scontato, ma non sempre conosciuto con adeguata precisione.

Quando parliamo comunemente di “suono” (come ci suggerisce anche Wikipedia) in realtà ci stiamo riferendo al fenomeno con cui si propagano le onde sonore: quest’ultime sono le vibrazioni che scaturiscono da un oggetto in movimento (chiamato “sorgente”) e che si spostano attraverso l’aria, fino a raggiungere il nostro orecchio, all’interno del quale, tramite un complesso meccanismo interno, percepiamo la particolare sensazione uditiva riconducibile a quella vibrazione.

Esempio di onda sonora

Esempio di onda sonora

Come tutte le onde, anche quelle sonore sono caratterizzate da una frequenza e da un’intensità: la frequenza indica il numero di oscillazioni che l’onda compie in un secondo e si misura in Hertz (Hz); la “tonalità” del suono, o almeno quella che noi percepiamo come tale, è riconducibile alla frequenza (guardate ad esempio le frequenze delle 7 note musicali); l’intensità, invece, corrisponde all’ampiezza dell’onda sonora ed è un fattore strettamente legato al volume del suono. Infine la forma dell’onda costituisce il cosiddetto timbro del suono, che è la caratteristica distintiva delle diverse tipologie di suoni.

In alto l'abbaiare di un cane, in basso il rumore della pioggia

In alto l’abbaiare di un cane, in basso il rumore della pioggia

Adesso che sappiamo cosa è un suono, cerchiamo di capire come esso viene rappresentato nel nostro calcolatore.

Finora si è parlato solamente di segnali analogici, ma essi non vanno bene per la nostra macchina che è in grado di lavorare solo con valori discreti. Per questo motivo se vogliamo digitalizzare un segnale audio dobbiamo innanzitutto renderlo discreto: questa operazione è chiamata campionamento e consiste nella lettura dello stato del segnale analogico (in questo caso dell’intensità dell’onda sonora) in intervalli di tempo costanti. In questo modo, attraverso il campionamento, si ottiene un insieme omogeneo e lineare di valori sufficientemente grande da rappresentare gli stati del segnale originale assunti in un certo lasso di tempo. La qualità del segnale rappresentato, adesso codificato in PCM, dipenderà sia dalla frequenza di campionamento, sia dall’intervallo di valori usati per descriverne lo stato (nelle comuni schede audio si va dagli 8 bit fino ai 24).

A differenza dei formati audio di livello più alto (come ad esempio l’MP3 o l’OGG), la rappresentazione PCM è estremamente semplice e di basso livello. Proprio per questo motivo adesso analizzeremo il formato WAV che, sebbene supporti diverse codifiche audio, nella pratica utilizza principalmente proprio la codifica PCM.

All’interno di un file WAV distinguiamo tre diversi “chunk” (o blocchi): il Riff Chunk Descriptor (o Riff Header), l’Fmt Chunk e il Data Chunk.

Struttura di un file WAV

Struttura di un file WAV

Il Riff Header, composto da soli 12 byte, è l’intestazione che identifica il tipo del file e che ci dice di quanti byte esso sia costituito. Nello specifico il blocco si suddivide come segue:

Offset Length Contents
0 4 bytes “RIFF”
4 4 bytes dimensione del file - 8
8 4 bytes “WAVE”

Essendo il WAV un’evoluzione del formato RIFF, l’intestazione del file rimane la stessa: i primi 4 byte contengono la stringa “RIFF”; i successivi 4, invece, contengono la dimensione del file a partire dal byte successivo, ovvero la dimensione totale del file meno questi 4 byte e quelli che abbiamo visto in precedenza (dimensione del file - 8). Per finire, gli ultimi 4 contengono la seconda stringa identificativa “WAVE”.
E’ importante ricordare che all’interno del file WAV tutte le grandezze sono memorizzate come interi senza segno (di dimensione variabile).
Inoltre l’ordine dei byte utilizzato è il Little-endian (tranne per gli identificatori), ma se si vuole utilizzare il formato Big-endian è sufficiente trasformare la prima intestazione in “RIFX”.

L’Fmt Chunk è il blocco che contiene le informazioni sulla codifica utilizzata (abbiamo detto infatti che il formato WAV supporta diversi tipi di codifica). Nello specifico la struttura di questo blocco appare come segue:

Offset Length Contents
12 4 bytes “fmt ” (attenzione allo spazio nell’ultimo byte)
16 4 bytes 0×00000010 (dimensione del blocco, in questo caso 16)
20 2 bytes 0×0001 (codifica dei dati del segnale, 1 = PCM)
24 4 bytes frequenza di campionamento (es. 44100, 8000, etc, in Hz)
28 4 bytes byte al secondo
( frequenza di campionamento * allineameamento del blocco )
32 2 bytes allineamento del blocco
( numero di canali * bit per campione / 8 )
34 2 bytes bit per campionamento (es 8 bit, 16 bit, etc)

Come in quello precedente, i primi 4 byte contengono l’identificativo del blocco stesso, in questo caso la stringa “fmt ” (con uno spazio bianco nell’ultimo byte), mentre nei successivi 4 troviamo il numero di bytes restanti contenuti nel blocco: ho indicato 16 byte perché è la dimensione standard dell’Fmt Chunk in un file WAV che utilizza la codifica PCM.
Successivamente troviamo 2 byte che indicano la codifica usata per il segnale (anche in questo caso PCM è lo standard di fatto); altri 4 byte indicano la frequenza di campionamento (ovvero il numero di campioni riprodotti al secondo); 4 byte per il numero di byte riprodotti al secondo, ossia il byte rate; 2 byte per l’allineamento del blocco ed altri 2 per il numero di bit usati per il campionamento. (Come potete notare, il terzultimo e il penultimo campo si ricavano matematicamente dagli altri 7).

Finalmente troviamo l’ultimo blocco, il “Data Chunk”, che contiene le informazioni codificate del segnale (ossia i campioni). La sua struttura è la seguente:

Offset Length Contents
36 4 bytes “data”
40 4 bytes dimensione del blocco
( numero di campioni * numero di canali * bit per campione / 8 )
44 * data (vettore contenente i campioni)

Come per gli altri due blocchi, troviamo 4 byte per l’identificativo del blocco, ovvero la stringa “data”; altri 4 per la dimensione del blocco, che questa volta dev’essere calcolata tenendo conto dei parametri usati per il campionamento. Infine, un vettore contenente i dati veri e propri del segnale, ossia la lista dei campioni. Quest’ultimi vanno memorizzati tenendo conto del numero di bit usato per ogni campione: un byte senza segno per gli 8 bit (da 0 a 255), un intero con segno a 16 bit (da -32768 a 32767), etc. Durante la riproduzione di un file WAV, il valore di un campione determinerà la forza di contrazione applicata agli altoparlanti (espressa in dB) in un determinato istante. La forza sarà relativa alla gamma dinamica della scheda audio e all’impianto utilizzato.

Un’altra cosa importante da sapere è che se i canali di uscita sono più di uno (ad esempio in una riproduzione stereo), i campioni all’interno del vettore si alterneranno come segue:

Elemento Mono Stereo
0 campione 0 campione 0, canale 0
1 campione 1 campione 0, canale 1
2 campione 2 campione 1, canale 0
3 campione 3 campione 1, canale 1
4 campione 4 campione 2, canale 0
5 campione 5 campione 2, canale 1

Però adesso vediamo qualcosa di pratico. Personalmente non sopporto le guide che trattano solo la teoria, magari tralasciando le parti salienti che sono poi indispensabili per la pratica..

Se volessimo implementare in linguaggio C++ la struttura di un file WAV, probabilmente scriveremmo qualcosa del genere:

#include <stdint.h> // per i tipi a dimensione esatta

/* Riff header */
struct T_RiffHeader {

    uint8_t id[4];      // Identificativo
    uint32_t size;      // Byte rimanenti alla fine del file
    uint8_t format[4];  // Secondo identificativo
};

/* Fmt Chunk */
struct T_FmtChunk {

    uint8_t id[4];              // Identificativo
    uint32_t size;              // Byte rimanenti alla fine del blocco
    uint16_t format;            // Formato ( 1 = PCM )
    uint16_t channels;          // Numero dei canali ( 1 = Mono, 2 = Stereo )
    uint32_t rate;              // Frequenza di campionamento ( 44100 Hz, 8000 Hz, etc )
    uint32_t bytes_per_second;  // Numero di byte riprodotti al secondo
    uint16_t block_align;       // Allineamento del blocco
    uint16_t bits_per_sample;   // Bit utilizzati per il campionamento ( 8 bit, 16 bit, etc )
};

/* Data Chunk */
struct T_DataChunk {

    uint8_t id[4];      // Identificativo
    uint32_t size;      // Byte rimanenti alla fine del blocco

    // [Segue il vettore contenente i campioni]
};

/* Struttura di un file WAV (senza il vettore dei campioni) */
struct T_Wav {

    T_RiffHeader riff_header;   // Riff Header
    T_FmtChunk fmt_chunk;       // Fmt Chunk
    T_DataChunk data_chunk;     // Data Chunk
};

Ovviamente il codice poteva essere reso più dinamico attraverso l’uso delle classi, ma non è questo che ci interessava. Una volta definite le sue strutture, la lettura e la scrittura di un file WAV diventano estremamente semplici. Ecco ad esempio come potremmo fare se volessimo costruire un file WAV contenente una traccia audio di 5 secondo che riproduce un “LA”:

/*
    Title: makewav.cpp
    Author: Giacomo Trudu aka "Wicker25" - wicker25[at]gmail[dot]com
    Site: http://hackyourmind.org/
    License: GNU Lesser General Public License v3 (LGPL3)
*/

#include <iostream>
#include <fstream>
#include <stdint.h> // per i tipi a dimensione esatta
#include <cmath>    // per `M_PI` e `sin`

/* Stringhe identificative */
#define RIFF_ID     { 'R','I','F','F' }
#define WAVE_ID     { 'W','A','V','E' }
#define FMT_ID      { 'f','m','t',' ' }
#define DATA_ID     { 'd','a','t','a' }

/* Riff header */
struct T_RiffHeader {

    uint8_t id[4];      // Identificativo
    uint32_t size;      // Byte rimanenti alla fine del file
    uint8_t format[4];  // Secondo identificativo
};

/* Fmt Chunk */
struct T_FmtChunk {

    uint8_t id[4];              // Identificativo
    uint32_t size;              // Byte rimanenti alla fine del blocco
    uint16_t format;            // Formato ( 1 = PCM )
    uint16_t channels;          // Numero dei canali ( 1 = Mono, 2 = Stereo )
    uint32_t rate;              // Frequenza di campionamento ( 44100 Hz, 8000 Hz, etc )
    uint32_t bytes_per_second;  // Numero di byte riprodotti al secondo
    uint16_t block_align;       // Allineamento del blocco
    uint16_t bits_per_sample;   // Bit utilizzati per il campionamento ( 8 bit, 16 bit, etc )
};

/* Data Chunk */
struct T_DataChunk {

    uint8_t id[4];      // Identificativo
    uint32_t size;      // Byte rimanenti alla fine del blocco

    // [Segue il vettore contenente i campioni]
};

/* Struttura di un file WAV (senza il vettore dei campioni) */
struct T_Wav {

    T_RiffHeader riff_header;   // Riff Header
    T_FmtChunk fmt_chunk;       // Fmt Chunk
    T_DataChunk data_chunk;     // Data Chunk
};


int
main() {

    // Scelgo il tipo usato per i campioni
    typedef uint8_t T_SAMPLE;

    // Numero di byte per campione
    size_t bytes_per_sample = sizeof( T_SAMPLE );

    size_t  time        = 5,        // Durata della traccia (secondi)
            n_channels  = 1,        // Numero dei canali
            sample_rate = 8000,     // Frequenza di campionamento (Hz)

            bits_per_sample     = bytes_per_sample * 8,                 // Numero di bit per campione
            block_align         = n_channels * bytes_per_sample,        // Allineamento del blocco
            bytes_per_seconds   = sample_rate * block_align,            // Frequenza di campionamento in Hz
            n_samples           = n_channels * sample_rate * time;      // Numero di campioni richiesti per la durata scelta

    // Dimensione in byte del vettore dei campioni
    size_t samples_size = n_samples * sizeof( T_SAMPLE );


    // Struttura del nuovo file WAV
    T_Wav my_wav = {

        // Riff Header
        { RIFF_ID, sizeof( T_Wav ) + samples_size - 8, WAVE_ID },

        // Fmt Chunk
        { FMT_ID, 16, 1, n_channels, sample_rate, bytes_per_seconds, block_align, bits_per_sample },

        // Data Chunk
        { DATA_ID, samples_size }
    };

    // Creo il vettore dei campioni
    T_SAMPLE *samples = new T_SAMPLE[ n_samples ];


    // Frequenza del suono da riprodurre (Hz)
    const int tone = 440;

    std::cout << "-> Costruisco il segnale sinusoidale a " << tone << " Hz su " << n_samples << " campioni\n";

    // Calcolo l'oscillazione dell'onda sinusoidale
    const float oscillation = ( 2.0 * M_PI / (float) sample_rate / n_channels ) * (float) tone;

    // Costruisco i campioni seguendo l'onda sinusoidale
    size_t i = 0;

    for ( ; i < n_samples; ++i ) {

        // Onda quadra
        //samples[i] = (uint8_t) ( i % ( sample_rate / tone ) > ( sample_rate / tone ) / 2 ) ? 255 : 0;

        // Onda sinusoidale
        samples[i] = (uint8_t) ( std::sin( (float) i * oscillation ) * 127.0 + 128.0 );
    }


    std::cout << "-> Scrivo i " << ( sizeof( my_wav ) + samples_size ) << " byte nel nuovo file WAV\n";

    // Scrivo le informazioni sul file
    std::ofstream file( "sample.wav", std::ofstream::binary );

    file.write( (char *) &my_wav, sizeof( my_wav ) );
    file.write( (char *) samples, samples_size );

    file.close();


    // Distruggo il vettore dei campioni
    delete[] samples;

    return 0;
}

[Scarica il sorgente] [Scarica la versione in C]

Nel programma ho utilizzato un onda sinusoidale per rendere il timbro della nota più soffice, ma si poteva realizzare in numerosi modi.. e questo bel suono è il risultato. :D

Direi che quanto detto finora è sufficiente a farsi un’idea dell’argomento.. ;)

Oltre ai link presenti nel post, vi rimando ad alcuni altri siti da cui ho preso le varie informazioni:

[Aggiornato il 5 luglio 2013]

Did you find this article helpful?

27 comments

  1. Salve! Il miglior commento che posso lasciare è quello di dirle (GRAZIE!) :) Finalmente una spiegazione esaustiva e completa su che cosa sia un suono. A dire il vero non ho afferrato proprio tutto :p , ma sono sicuro che con una seconda lettura e una maggiore attenzione comprenderò meglio tale argomento. La ringrazio essenzialmente per il tempo e l’impegno che ha impiegato nella realizzazione di quanto ha scritto; naturalmente ringrazio anche il suo amico che lo ha spronato a fare tale ricerca. Spero di vedere realizzate, in futuro, altre sue ricerche correlate.

    Marco on Mon, 8 Nov 2010 @ 9:24 pm user image
  2. Grazie davvero.. Sono lieto che il mio lavoro sia stato utile a qualcuno.. :D

    admin on Mon, 8 Nov 2010 @ 9:50 pm user image
  3. Grazie! veramente utile ,poche righe esaustive:D

    civaldo on Sat, 9 Jul 2011 @ 12:30 pm user image
  4. Ciao, grazie per le info. Volevo chiederle una cosa lei sul programma che genera un LA dichiara una enum,non ho capito come funziona.Poichè sono pratico del C posso
    fare ad esempio: new_wav.riff_header.id[0]=’R’; e così via

    celjio on Fri, 29 Jul 2011 @ 12:30 pm user image
  5. Ho utilizzato “enum” per dichiarare alcune costanti del file audio, come ad esempio il numero dei canali, la frequenza di campionamento, etc etc. Più che altro per una questione di ordine, visto che alcune informazioni dipendono strettamente le une dalle altre.
    Volendo può anche solo allocare la struttura e poi, successivamente, impostarne i valori, come ha evidenziato nel suo esempio. Nel sorgente ho preferito impostare tutte le informazioni al momento dell’inizializzazione, a parte il vettore “data” del blocco “data chunck” che viene riempito subito dopo l’allocazione della struttura.. ;)

    admin on Fri, 29 Jul 2011 @ 12:51 pm user image
  6. Ok, un ‘ultima domanda lei dopo aver dichiarato l’enum esegur i seguienti comandi

    // Creo la struttura del nuovo file WAV
    T_WAV new_wav = {
    
    // Definisco il Riff Header
    { { 'R', 'I', 'F', 'F' }, sizeof( T_WAV  ) - 8, { 'W', 'A', 'V', 'E' } },
     .....
    

    siccome sono pratico del C come posso sostituire tali righe di codice in linguaggio C?

    celjio on Mon, 29 Aug 2011 @ 4:06 pm user image
  7. Ho riadattato il programma in linguaggio C. Lo può trovare al seguente indirizzo: http://www.hackyourmind.org/public/sources/makewav.c ;)
    Ho spostato il vettore dei campioni all’esterno della struttura principale, in modo tale che essa possa essere scritta attraverso la funzione fwrite.
    Appena avrò del tempo libero provvederò ad aggiornare la guida, cercando di riscrivere gli esempi in modo più semplice.. ;)

    admin on Mon, 29 Aug 2011 @ 7:15 pm user image
  8. Tranquillo,l’esempio era abbastanza esaustivo però non avendo dimestichezza con i template non avevo capito una parte del codice.

    celjio on Tue, 30 Aug 2011 @ 10:31 am user image
  9. Salve,un’ultima cosa, se io volessi usare due canali destro e sinistro e volessi scrivere e volgio scrivere una volta i campioni del canale destro e una volta quelli del sinistro come posso fare? Devo usare due funzioni fwrite?

    celjio on Wed, 7 Sep 2011 @ 2:03 pm user image
  10. Potresti utilizzare la funzione fseek, prima per scrivere nei settori del canale destro e poi in quelli del canale sinistro. Con due sole chiamate alla funzione fwrite non è possibile perché i dati dei due canali si alternano.. ;)

    admin on Tue, 13 Sep 2011 @ 10:48 am user image
  11. Con un ciclo for?

    celjio on Sat, 17 Sep 2011 @ 8:36 pm user image
  12. Esatto.. ;)

    admin on Tue, 20 Sep 2011 @ 12:36 pm user image
  13. Salve , scusa se ti disturbo , in rete ho trovato un programma che genera sequenze pseudo casuali:

    unsigned char LFSR;     //registro LFSR
    
    void StatoFuturo(void);   //prototipo di funzione per lo stato futuro
    
    void main (void)
    {
        unsigned char i;
        LFSR=0x01;     //inizializzazione LFSR
    
             i=LFRS;    //la variabile i contiene i numeri pseudo casuali
             StatoFuturo();
        }
    }
    
    void StatoFuturo(void)
    {
       unsigned char x;
    
       x=0;
       if (LFSR&amp;0x01) x=0x8E;
       LFSR=LFSR&gt;&gt;1;
       LFSR=LFSR^x;
    }
    

    Volevo sapere se posso usarlo per generare sequenze di gold; inoltre secondo lei la x è il polinomio generatore ? oppure sbaglio

    celjio on Wed, 28 Sep 2011 @ 9:22 pm user image
  14. Il polinomio LFSR dovrebbe essere contenuto nel numero 0x8E. Se lo convertiamo in binario osserviamo che esso è uguale a 10001110, riconducibile al polinomio x + x^5 + x^6 + x^7.

    http://it.wikipedia.org/wiki/Registro_a_scorrimento_a_retroazione_lineare

    Chiudiamo qui l’offtopic.. ;) per i messaggi personali è disponibile la mia email nella sezione About me.. ;)

    admin on Thu, 29 Sep 2011 @ 4:58 pm user image
  15. Molto ma molto esaustivo, complimenti per il Suo lavoro. Finalmente sono riuscito a codificare un file wav.

    Mi sono permesso di fare alcune piccole modifiche al Suo codice, in particolare generazione dell’onda quadra che quella scritta da Lei (nascosta in un commento) era sbagliata.

    //Volume del suono (0:127)
    const float volume = 64.0f;
    //serve per l'onda quadra    
    float scartoVolume = 127.0f - volume;
    //uno "switcher" utile per generare l'onda quadra
    bool switcher = false;
    
    for ( ; i < SAMPLES; i++ ) {
    
            if (i < SAMPLES/2)
            // Onda sinusoidale
            new_wav.data_chunk.data[i] = (uint8_t) ( sin( (float) i * oscillation ) * volume + 128.0f );
            else {
            // Onda quadra
                if (( i % ( SAMPLE_RATE / (tone*2) ) == 0 )) 
                   switcher = !switcher;
                new_wav.data_chunk.data[i] = (uint8_t) ( switcher ) ? (255-scartoVolume) : (0 + scartoVolume);
            }
        }
    

    In questo modo, per una durata totale di 5 secondi, si avranno 2,5 sec di onda sinusoidale e 2,5 sec di onda quadra.
    Ovviamente l’onda quadra, a bassa frequenza di campionamento, avrà una frequenza uditiva (che sentiamo noi) sensibilmente più bassa rispetto all’onda sinusoidale e ciò è dovuto al fatto che a bassa frequenza di campionamento il passaggio tra valore massimo e valore minimo di un campione si allontana dalla condizione limite di “verticalità” (guardando il grafico dell’onda)…

    Comunque complimenti per il suo lavoro.

    C’è un modo per mandare direttamente alla scheda audio i campioni grezzi così per come sono e quindi ascoltarli in tempo reale?

    Alberto on Mon, 24 Oct 2011 @ 8:26 pm user image
  16. Giustissima precisazione.. ;) la parte di codice incriminata, in effetti, generava un’onda quadra asimmetrica.. tuttavia, nella sua correzione, è necessario tirare fuori dal blocco condizionale l’assegnazione degli elementi del vettore, in questo modo:

    if (( i % ( SAMPLE_RATE / (tone*2) ) == 0 ))
        switcher = !switcher;
    }
    
    new_wav.data_chunk.data[i] = (uint8_t) ( switcher ) ? (255-scartoVolume) : (0 + scartoVolume);
    

    Altrimenti incappiamo un’altra volta nel problema iniziale.. in ogni caso ho provveduto ad aggiornare i sorgenti.. ;)

    Per quanto riguarda la riproduzione audio, dipende strettamente dall’ambiente su cui si sta lavorando.. Su sistemi Gnu/Linux si possono utilizzare le librerie di ALSA o di PulseAudio o addirittura scrivere direttamente sul device /dev/dsp
    In ogni caso quando si lavora con le schede audio la codifica di riferimento è sempre la PCM.

    admin on Mon, 24 Oct 2011 @ 11:03 pm user image
  17. grazie, grazie mille per il link!

    Alberto on Tue, 25 Oct 2011 @ 4:18 pm user image
  18. Scusi per l’ennesimo messaggio, ma ho trovato questa ottima guida (in inglese) per inviare dati grezzi alla scheda audio (esattamente quello che volevo fare) in ambiente Windows utilizzando le API a basso livello di Windows:

    http://www.planet-source-code.com/vb/scripts/ShowCode.asp?txtCodeId=4422&lngWId=3

    Funziona ottimamente ed è quello che cercavo
    Alberto

    Alberto on Tue, 25 Oct 2011 @ 8:09 pm user image
  19. Figurati.. ;)

    admin on Tue, 25 Oct 2011 @ 9:20 pm user image
  20. Grazie mille per questo, mi sarà molto utile ^.^ e ringrazia anche quell’amico da parte mia xD

    Berga95 on Sat, 10 Dec 2011 @ 9:46 pm user image
  21. Dimenticavo, complimenti per la Creative Commons!!!

    Berga95 on Sat, 10 Dec 2011 @ 10:23 pm user image
  22. Grazie per la risposta!

    Ho visto il programma che hai link-ato.
    Io ho provato a leggere l’intero file .wav in modo binario e ci sono riuscita. Però il problema è che non ho capito come leggere il vettore che è presente nel “data chunks”…praticamente con la funzione fseek dovrei spostarmi fino al data chunks e da lì fino alla fine del file leggere tutti i dati(il vettore). Questo non so come si fa. Per favore, mi aiuti con due righe di codice?

    Grazie mille! Sei un genio. Hai descritto veramente bene la struttura del wav su questa pagina!

    Antonella on Sat, 14 Dec 2013 @ 8:22 pm user image
  23. Ciao Giacomo.

    Io ho un problema che non so risolvere. Ho iniziato a fare un accordatore di chitarra in C++ però da un punto in poi non so più andare. Devo convertire un file .wav in uno vettore/array. Come si fa? Questo vettore dovrei trasformarlo con il FFT in frequenze e le frequenze trovate dovrei compararle per indicare se la corda della chitarra deve essere alzata o scesa. Mi potresti fare un codice in C++ per trasformare il file .wav in un array? Non ho idea come fare! Per favore, aiutami!

    Antonella on Fri, 13 Dec 2013 @ 10:38 pm user image
  24. Ciao Antonella,

    hai compreso gli esempi presenti in questo articolo? Una volta che hai definito le varie strutture che compongono il file WAV, ti resta solo da leggere le informazioni.

    Ho scritto un piccolo programma in C++ che dovrebbe fare al caso tuo: http://hackyourmind.org/public/sources/readwav.cpp

    Fammi sapere se qualcosa non ti torna.. ;)

    admin on Sat, 14 Dec 2013 @ 7:39 pm user image
  25. C’è un motivo in particolare per cui vuoi utilizzare le funzioni del C (ad esempio fseek) anziché quelle native del linguaggio C++?
    Se vuoi realizzare un programma in linguaggio C++ sarebbe meglio che tu utilizzassi la classe std::ifstream.

    In ogni caso, proverò ad essere un po’ più chiaro.

    Il programma che ti ho linkato fa proprio ciò di cui hai bisogno: per prima cosa legge dal file WAV i tre blocchi Riff Header, Fmt Chunk e Data Chunk (escluso il vettore dei campioni); poi, utilizzando le informazioni contenute nei tre blocchi, legge il vettore dei campioni e lo memorizza nell’array chiamato raw_samples.

    La dimensione complessiva del vettore dei campioni si ricava dal campo data_chunk.size.

    Per leggere le informazioni dal file ho utilizzato il metodo read, che richiede come parametri un puntatore ad un array di char, in cui verranno memorizzate le informazioni lette dal file, ed il numero dei byte che si desidera leggere. Ad ogni lettura l’indicatore della posizione viene incrementato del numero dei byte che abbiamo appena letto e si procede man mano verso la fine del file.

    Poiché il metodo read si limita a copiare le informazioni byte a byte dal file al buffer, se al posto di un array gli passiamo le strutture vuote dei nostri chunck, lui ce le riempirà con le informazioni di cui abbiamo bisogno.

    A questo punto però non è detto che ogni elemento del vettore raw_samples corrisponda ad un campione del file WAV, in quanto la dimensione dei campioni ed il numero dei canali utilizzati può variare di file in file.

    Per questo motivo non ho letto direttamente il vettore dei campioni con il metodo seekg (analogo a fseek), ma ho prima dovuto recuperare le informazioni contenute nei tre blocchi.

    Se supponiamo che sia presente un unico canale audio (ossia che tutti i campioni facciano parte dello stesso canale), possiamo trovare la dimensione dei campioni in questo modo:

    size_t  sample_size = fmt_chunk.bits_per_sample / 8,
            n_samples   = data_chunk.size / sample_size;
    

    Quindi, in base alla dimensione calcolata, dovremo trattare il vettore raw_samples come se fosse un array di byte (uint8_t), di word (uint16_t) o di qualche altra dimensione.

    Come sembra adesso? Spero di non averti confuso ulteriormente.. :D

    p.s. Non so quale sia la tua familiarità con il linguaggio C++, per cui non prendertela a male se ho scritto delle cose che per te sembrano banali.

    admin on Sun, 15 Dec 2013 @ 1:47 am user image
  26. Ciao. Molto chiaro. Sei veramente bravo sia a fare che a spiegarlo. Grazie mille. Adesso sono riuscita a mettere in un file il vettore del datachunk che mi interessa. Però non so come applicarli la trasformata fourier per determinare la frequenza. Per favore, mi potresti dare una mano? So che non è qui che devo postare la domanda, però non so a chi rivolgermi. Grazie mille x l’aiuto. Aspetto una risposta.

    Antonella on Wed, 1 Jan 2014 @ 7:55 pm user image
  27. Ciao Antonella,

    purtroppo in questi giorni non ho il tempo materiale per prepararti un codice d’esempio.

    Il mio consiglio è di utilizzare una libreria specifica, come ad esempio FFTW o FFTW++.

    Qui puoi trovare alcune informazioni utili su come procedere: https://stackoverflow.com/questions/2881583/how-to-extract-semi-precise-frequencies-from-a-wav-file-using-fourier-transforms/

    In bocca al lupo.. ;)

    admin on Wed, 15 Jan 2014 @ 2:41 pm user image