giovedì 22 dicembre 2011

Windows: riprodurre un'onda sonora tramite l'interfaccia "waveOut"

Per continuare l'argomento intrapreso due settimane fa, ovvero Un semplice programma in C per leggere i file RIFF WAVE, passiamo adesso alla riproduzione di un'onda PCM. Intanto, a differenza del precedente post, dimentichiamoci della struttura del file WAV e di come aprirlo e suonarlo (rimanderò ancora più avanti l'argomento), ma preoccupiamoci di generare un'onda qualsiasi (per semplicità genereremo un'onda sinusoidale), e comunicare in qualche modo con la scheda audio per inviarle dei dati grezzi e quindi, sentire un suono dalle casse del proprio computer.

La piattaforma in cui faremo ciò è Windows, e useremo alcune sue API di basso livello per comunicare con la scheda audio (come al solito, rimanderò più avanti nel caso di Linux). Sebbene utilizzeremo le API di Win32, scordatevi comunque di vedere una finestra bella colorata con tanti tasti e caselle di input: quel che vedremo è una scarna finestra di console che si chiude non appena finisce di riprodurre il suono.

Il linguaggio è sempre il C.
Prima cosa essenziale: creiamoci una bella enumerazione che ci consente di manipolare le caratteristiche dell'onda senza andare a cercare fra il codice:

enum {
TIME = 5, // Durata della traccia (secondi)
CHANNELS = 1,         // Numero dei canali
SAMPLE_RATE = 44100, // Frequenza di campionamento (Hz)
BITS_PER_SAMPLE = 8, // Bits per campione
BLOCK_ALIGN = CHANNELS * BITS_PER_SAMPLE / 8, // Allineamento del blocco
BYTES_PER_SECOND  SAMPLE_RATE * BLOCK_ALIGN, // Frequenza di campionamento in Hz
SAMPLES = CHANNELS * SAMPLE_RATE * TIME, // Numero di campioni richiesti per la durata scelta
};
E' necessario che abbiate letto almeno il precedente post  per capire cosa sia per esempio la frequenza di campionamento o le altre cose (se non lo sapete già).

PASSO 1: APRIRE IL DEVICE

Cominciamo a scrivere la nostra main e a creare le variabili necessarie per aprire il device.



int main() {    
    HWAVEOUT hWaveOut; /* device handle */
    WAVEFORMATEX wfx; /* una struttura che riassume le caratteristiche dell'audio */
    MMRESULT result;/* per il valore di ritorno di waveOut */
    char* block;/* il puntatore ai nostri campioni */
    /*
     * inizializzo la struttura
     */
    wfx.nSamplesPerSec = SAMPLE_RATE;
    wfx.wBitsPerSample = BITS_PER_SAMPLE;
    wfx.nChannels = CHANNELS;
    wfx.nBlockAlign = BLOCK_ALIGN;
    wfx.nAvgBytesPerSec = BYTES_PER_SECOND;
    wfx.cbSize = 0; /* lunghezza delle informazioni extra */
    wfx.wFormatTag = WAVE_FORMAT_PCM  /* onda PCM; */

    if(waveOutOpen(&hWaveOut, WAVE_MAPPER, &wfx, 0,0,CALLBACK_NULL) != MMSYSERR_NOERROR) {
        fprintf(stderr, "Impossibile aprire il device.\n");
        return 1;
    }
    else printf("Il device è stato aperto correttamente.\n");
    waveOutClose(hWaveOut); 
}


Questo programma per ora non fa niente, si limita ad aprire il device e a richiuderlo, ma comunque introduce molte caratteristiche interessanti. La funzione-chiave è waveOutOpen che accetta sei parametri:

LPHWAVEOUT phwo : in questo parametro passato per indirizzo, verrà "depositato" l'handle del dispositivo ((hWaveOut)
UINT uDeviceID : il  valore viene impostato a "WAVE_MAPPER" in modo che la funzione scelga il dispositivo adatto a riprodurre il formato specificato
LPWAVEFORMATEX pwfx : il puntatore alla struttura, ovvero &wfx
DWORD dwCallback : indirizzo dell'eventuale funzione CALLBACK, che in questo contesto non ci serve (più avanti si). Lo impostiamo a 0
DWORD dwInstance : specifica l'istanza del processo. Lo impostiamo a 0
DWORD fdwOpen : specifica alcuni comportamenti della funzione (callback, flags) che per ora ignoreremo. Lo impostiamo a CALLBACK_NULL(nessuna chiamata di callback)

Benissimo, adesso che abbiamo aperto il device, dobbiamo trovare un modo per  comunicare con lui. Ci arriveremo al passo 3, per ora creiamo l'onda.

PASSO 2 : CREIAMO UN'ONDA SINUSOIDALE



    int tone = 440;
    float volume = 10.0f;
    float pulsazione = ( 2.0f * 3.14f / (float) SAMPLE_RATE / CHANNELS ) * (float) tone;
    int i;
    if ((block = (char*)malloc(SAMPLES))==NULL)
       return 1;
    for (i=0 ; i < SAMPLES; i++ ) 
        block[i] = (char) ( cos( (float) i * oscillation ) * volume + 128.0f );

Questo pezzetto di codice alloca la memoria necessaria per memorizzare i campioni e la inizializza con i valori dell'onda.
Sappiamo che questa è l'equazione di un'onda sinusoidale di ampiezza A, pulsazione w e fase f. Possiamo dire lecitamente che l'ampiezza non è altro che il volume(intensità) dell'onda, la fase è nulla (non è altro che una traslazione orizzontale dell'onda  che non ci interessa) e la pulsazione è una funzione della frequenza secondo questa equazione:
dove f questa volta è la frequenza dell'onda. Non ho studiato teoria dei segnali, ma secondo il teorema del campionamento di Nyquist c'è una condizione: la frequenza di campionamento deve essere almeno il doppio della massima frequenza dello spettro dell'onda. Lo spettro acustico che un essere umano percepisce va da 16 Hz a 20000 Hz più o meno, quindi come frequenza di campionamento per memorizzare i campioni viene scelta 44100 Hz, che è diciamo uno standard, utilizzata negli ormai comuni CD Audio. Sempre secondo il teorema del campionamento,  l'equazione esatta dell'onda è questa:
dove n, un numero intero, rappresenta l'indice del campione (da 0 a SAMPLES nel codice sopra), Tc è il tempo che intercorre fra un campione e un altro, ossia l'inverso della frequenza di campionamento, fc è la frequenza di campionamento e f0 è la frequenza del segnale analogico.
In base a tutto ciò si spiega facilmente il codice sopra:  float pulsazione = ( 2.0f * 3.14f / (float) SAMPLE_RATE / CHANNELS ) * (float) tone; definisce la pulsazione costante mentre il ciclo for memorizza campione per campione tramite l'espressione block[i] = (char) ( cos( (float) i * oscillation ) * volume + 128.0f ); .

Da notare che il codice che utilizzo utilizza il puntatore a char* per memorizzare i campioni. char è un byte sostanzialmente, quindi se modifico BITS_PER_SAMPLE nell'enumerazione e metto 16, per esempio come valore, si avrà uno stravolgimento totale. Meglio non modificarlo! In C++ potremmo creare una funzione che memorizza i dati passandogli un tipo tramite un template, in modo da ovviare alla cosa. Ma non preoccupiamoci, almeno per ora.

PASSO 3 : INVIAMO I DATI AL DEVICE



void writeAudioBlock(HWAVEOUT hWaveOut, LPSTR block, DWORD size)
{
    WAVEHDR header;
    /*
     * Inizializza l'intestazione con la lunghezza dei dati
     * e naturalmente il puntatore.
     */
    ZeroMemory(&header, sizeof(WAVEHDR));
    header.dwBufferLength = size;
    header.lpData = block;
    /*
     * crea l'intestazione
     */
    waveOutPrepareHeader(hWaveOut, &header, sizeof(WAVEHDR));
    /*
     * scrive i dati in maniera asincrona
     * (il controllo passerà subito al programma)
     */
    waveOutWrite(hWaveOut, &header, sizeof(WAVEHDR));
    /*
     * Aspetta un po'
     */
    Sleep(500);
    while(waveOutUnprepareHeader(hWaveOut, &header, sizeof(WAVEHDR)) == WAVERR_STILLPLAYING)
      Sleep(100);
}

Una bella funzione. Il codice è tutto commentato quindi non ci dovrebbero essere problemi. In soldoni: preparo l'header, cancello tutta la memoria occupata dall'header, inizializzo l'header, creo l'intestazione, scrivo i dati, faccio un ciclo fin quando la riproduzione finisce, e chiudo.
Quindi, in definitiva, ecco il codice completo:

#include <windows.h>
#include <mmsystem.h>
#include <stdio.h>
#include <math.h>
#include <stdint.h>
enum {

        TIME                = 5,         // Durata della traccia (secondi)
        FMT_SIZE            = 16,        // Dimensione dell'Fmt Chunk
        CHANNELS            = 1,         // Numero dei canali
        SAMPLE_RATE            = 44100,   // Frequenza di campionamento (Hz)
        BITS_PER_SAMPLE        = 8,      // Bits per campione

        BLOCK_ALIGN            = CHANNELS * BITS_PER_SAMPLE / 8,    // Allineamento del blocco
        BYTES_PER_SECOND    = SAMPLE_RATE * BLOCK_ALIGN,            // Frequenza di campionamento in Hz

        SAMPLES = CHANNELS * SAMPLE_RATE * TIME,  // Numero di campioni richiesti per la durata scelta
    };
    
    

void writeAudioBlock(HWAVEOUT hWaveOut, LPSTR block, DWORD size)
{
    WAVEHDR header;
    /*
     * Inizializza l'intestazione con la lunghezza dei dati
     * e naturalmente il puntatore.
     */
    ZeroMemory(&header, sizeof(WAVEHDR));
    header.dwBufferLength = size;
    header.lpData = block;
    /*
     * crea l'intestazione
     */
    waveOutPrepareHeader(hWaveOut, &header, sizeof(WAVEHDR));
    /*
     * scrive i dati in maniera asincrona
     * (il controllo passerà subito al programma)
     */
    waveOutWrite(hWaveOut, &header, sizeof(WAVEHDR));
    /*
     * Aspetta un po'
     */
    Sleep(500);
    while(waveOutUnprepareHeader(hWaveOut, &header, sizeof(WAVEHDR)) == WAVERR_STILLPLAYING)
      Sleep(100);
}





int main() {
    HWAVEOUT hWaveOut; /* device handle */
    WAVEFORMATEX wfx; /* una struttura che riassume le caratteristiche dell'audio */
    MMRESULT result;/* per il valore di ritorno di waveOut */
    char* block;/* il puntatore ai nostri campioni */
    /*
     * inizializzo la struttura
     */
    wfx.nSamplesPerSec = SAMPLE_RATE;
    wfx.wBitsPerSample = BITS_PER_SAMPLE;
    wfx.nChannels = CHANNELS;
    wfx.nBlockAlign = BLOCK_ALIGN;
    wfx.nAvgBytesPerSec = BYTES_PER_SECOND;
    wfx.cbSize = 0; /* lunghezza delle informazioni extra */
    wfx.wFormatTag = WAVE_FORMAT_PCM  /* onda PCM; */

    if(waveOutOpen(&hWaveOut, WAVE_MAPPER, &wfx, 0,0,CALLBACK_NULL) != MMSYSERR_NOERROR) {
        fprintf(stderr, "Impossibile aprire il device.\n");
        return 1;
    }
    else printf("Il device è stato aperto correttamente.\n");


    int tone = 440;  //"La" centrale
    float volume = 10.0f;
    float pulsazione = ( 2.0f * 3.14f / (float) SAMPLE_RATE / CHANNELS ) * (float) tone;
    int i;
    if ((block = (char*)malloc(SAMPLES))==NULL)
       return 1;
    for (i=0 ; i < SAMPLES; i++ ) 
        block[i] = (char) ( cos( (float) i * oscillation ) * volume + 128.0f );
    }
    writeAudioBlock(hWaveOut, block, SAMPLES); 
    waveOutClose(hWaveOut);
}


Non dimentichiamoci di linkare al compilatore la libreria winmm  altrimenti il linker ci da errore.

Per dubbi, problemi e chiarimenti commentate pure.
Alberto


Siti da cui ho preso informazioni:

giovedì 15 dicembre 2011

Calcolare il determinante di una matrice di qualsiasi ordine in maniera ricorsiva

Nota: in data 12/11/2013 ho creato un programma con interfaccia grafica per windows: http://newbufferedwriter.blogspot.com/2013/11/calcolare-il-determinante-di-una.html
e per GNU/linux:
http://newbufferedwriter.blogspot.com/2013/11/calcolare-il-determinante-di-una_18.html

Era da tempo che mi volevo cimentare in un programma del genere. Per il corso di elettrotecnica avevo sviluppato un programma in C# per calcolare il determinante di una matrice 3x3, che mi è servito per calcolare la soluzione ai sistemi di equazioni per i circuiti; ma comunque era limitato solamente a matrici 3x3.

Ho pensato pochi giorni fa di implementare un programma del genere con la ricorsione. Dalla matematica "pratica" sappiamo che calcolare il determinante per matrici di grosse dimensioni è complesso, specialmente se si usa la definizione fondamentale del determinante. Io in questo programma ho usato lo sviluppo di Laplace avete questa forma:


che come si può notare, la formula stessa è ricorsiva, ovvero il determinante compare nell'espressione stessa. det Mij    è il determinante della matrice estrapolata dalla matrice originale escludendo la riga i e la colonna j, quindi in definitiva l'ordine diminuisce di 1 ad ogni ricorsione.  Quando si "ferma" la ricorsione? Semplice: quando la matrice raggiunge l'ordine 2, che, essendo una matrice elementare, è possibile con un'espressione elementare (non più ricorsiva) calcolare il suo determinante.
Facciamo un esempio. Consideriamo la seguente matrice di ordine 3:

| 2   8   6 |
| 4   5   2 |
| 1   7   4 |
Il suo determinante si può calcolare in questo modo:
1)Scegliamo una riga (per esempio la riga 1: {2,8,6}). Di solito nella pratica si sceglie una riga con molti zeri, ma in questo caso non ci sono zeri; e comunque ci dobbiamo mettere nei panni del calcolatore, che prende sempre e comunque la riga 1 per questo programma che ho creato.
2)Moltiplichiamo ogni elemento della riga per il suo complemento algebrico (ovvero il determinante della matrice ottenuta eliminando la riga 1 e la colonna del singolo elemento)
3)Addizioniamo i 3 risultati trovati, avendo cura di preporre ad ogni singolo elemento il segno, negativo o positivo che sia (Delta di Kronecker, in questo caso poitivo se la colonna è dispari, negativo se è pari).

           | 2   8   6 |
 det     | 4   5   2 |    =  2 * det | 5  2|     -  8 * det | 4   2|    +  6 * det | 4  | 5 |  = 2 * (5*4-2*7) - 
           | 1   7   4 |                    |7   4|                     | 1   4|                    | 1  | 7 |

- 8*(4*4-2*1)  + 6 *(4*7-5*1) = 2*6 - 8*14 + 6*23 = 38

Il seguente programma scirtto in C++ fa la stessa cosa per matrici di qualsiasi dimensione. Ho definito la funzione det che accetta come parametri la matrice (ovvero un doppio puntatore a interi) e il suo ordine. Nel metodo main ho inserito per esempio la matrice che abbiamo analizzato, ma potete crearne voi una qualsiasi.

#include <iostream>

using namespace std;

int det(int**,int);

int main(){
    int** m;
    m = new int*[3];        //ordine 3, alloco il primo vettore
    for (int i=0;i<3;i++)   //alloco gli altri vettori
        m[i] = new int[3];
    m[0][0]=2;m[0][1]=8;m[0][2]=6;
    m[1][0]=4;m[1][1]=5;m[1][2]=2;
    m[2][0]=1;m[2][1]=7;m[2][2]=4;
    int deter = det(m,3);
    cout << "Il determinante e': " << deter;
}

int det(int** matrice,int ordine){
    if (ordine==2)    
       return (matrice[0][0]*matrice[1][1] - matrice[0][1]*matrice[1][0]);
    else {
       int provv=0;  //risultato "provvisorio", ovvero diventa definitivo quando la ricorsione termina
       bool col=false; //variabile utile per creare la sottomatrice
       bool segnoNegativo = false;  //variabile che tiene conto del simbolo di Kronecker
       int elem;       
       for (int i=0;i<ordine;i++){     //colonna
          int** matrix;
          /*ALLOCO MEMORIA PER LA NUOVA MATRICE*/
          matrix = new int*[ordine-1];
          for (int c=0;c<ordine-1;c++)
              matrix[c] = new int[ordine-1];
           /*---------------FINE----------------*/
          for (int r=1;r<ordine;r++){ //inizio a creare la nuova matrice
              for (int c=0;c<ordine;c++){
                  if (c==i) {        
                      col = true; 
                      c++;         //salta questa colonna e vai avanti
                      if (c==ordine)     //controllo per non fare eccedere i limiti
                         break;
                  }
                  if (col)
                      matrix[r-1][c-1] = matrice[r][c];
                  else
                      matrix[r-1][c] = matrice[r][c];
               }
               col=false;
          } //Fine r
          if (segnoNegativo)
             elem = -matrice[0][i];
          else
             elem = matrice[0][i];
          provv += elem * det(matrix,ordine-1);
          segnoNegativo = !(segnoNegativo);
       }  //fine i
    return provv;
    }   //fine else
}

In sintesi, cosa fa il programma, in particolare la funzione int det(int** matrice,int ordine);  ?
1)Per prima cosa controlla l'ordine. Se è due, restituisce immediatamente il risultato, altrimenti continua.
2)Crea una matrice di ordine ordine-1 e la inizializza con la sottomatrice (col  è una variabile boolean utile per sapere qual'è la colonna a cui appartiene l'elemento).
3)Procede per aggiungere alla variabile provv (inizializzata a zero) il prodotto del singolo elemento per il suo complemento algebrico (ovvero una nuova chiamata alla stessa funzione det(); qui sta la ricorsione), tenendo conto del segno tramite la variabile boolean segnoNegativo.
4)Finito il ciclo della variabile temporanea i, restituisce il risultato (provv).


Analizziamo cosa succede in memoria. Il caso di ordine 3 è abbastanza semplice perché necessita di 3 cicli ed una sola ricorsione. La memoria della matrice di ordine 3-1 = 2 viene liberata alla fine di ogni ciclo ma comunque ad ogni ricorsione si accumula, quindi in definitiva per questo caso la massima memoria utilizzata contemporaneamente è quella per memorizzare una matrice 3x3 e una 2x2, ovvero rispettivamente 48 e 24 byte, quindi 48+24 = 72byte. Per una matrice di ordine 4 la massima memoria utilizzata contemporaneamente è quella per memorizzare una matrice 4x4, una 3x3 e una 2x2, ovvero rispettivamente 80 + 48 + 24 = 152. L'espressione della memoria occupata da una matrice di ordine n è:
   4 * (n^2 + n)
in questo modo ho stilato una tabella che ci indica quanta memoria viene utilizzata dal programma al crescere dell'ordine n:

n | Max memoria(byte)
--|---------------------
2|                          24
3|                          72
4|                        152
5|                        278
6|                        440
7|                        664
8|                        952
9|                      1312
10|                         1752

Tutto ciò era solamente per uno scopo goliardico...comunque sia la memoria utilizzata non è poi tanta...no?
Alberto

sabato 10 dicembre 2011

Minicomposer

NOME: Minicomposer
LINGUAGGIO: Borland Pascal
PIATTAFORMA: DOS
LIBRERIE: Giobe.TPU
PERIODO DI SVILUPPO: Finito il 29/05/2003

Ai tempi questo programma-gioco fu pubblicato (con tanto di scritta rossa in prima pagina)  qui: http://pascal.stormdrop.com/ ,un sito dedicato agli sviluppatori di programmini in Pascal, ma ormai da tempo è "freezato", ovvero l'ultimo aggiornamento è l'aggiunta di questo mio programma.

Si tratta di uno spartano programmino che visualizza una tastiera (priva di diesis, ovvero non suonabili) con la quale possiamo suonare note col mouse e registrarle in un file, e poi quest'ultimo può essere riproducibile con un altro programmino incluso nel pacchetto: PlayMusic.

E' stato il mio primo programma "utile" in assoluto, da quando mi avvicinai alla programmazione col Pascal. Inoltre è l'antenato di quel programma che sviluppai nel 2009 per la piattaforma .NET (Prodigious)

Ringrazio il creatore della libreria Giobe.TPU Ing. Giorgio Ober , webmaster del sito www.giobe2000.it che ha creato una libreria direi essenziale per chi programma(va) in Pascal.

Lo potete scaricare da qui: http://www.pernasoft.net/hosted/pascal/progs/minicomp.zip mentre i sorgenti da qui:  http://www.pernasoft.net/hosted/pascal/sorgenti/minicomp.zip

Nota: questo programma per suonare le note utilizza il beeper interno del sistema, ovvero non le casse (eh...era arduo con Turbo Pascal ed ero alle prime armi). Per esempio nel mio portatile con l'emulatore DOS di windows non si sente nulla. Ho provato invece con DosBox  e funziona perfettamente.
Bye
Alberto

venerdì 9 dicembre 2011

GoTetris: la mia versione di Tetris scritta in Java


NOME: GoTetris
LINGUAGGIO: Java
PIATTAFORMA: Indipendente
LIBRERIE: N/D
PERIODO DI SVILUPPO: 19-20 Nov, 8-9 Dic 2011

Stavo iniziando già a lavorare per un altro programma, ma visto che proprio oggi ho completato Tetris dopo 4 giorni, ho deciso di illustrare qui come ho fatto a crearlo.
Il gioco è più che altro un esercizio personale per capire meglio i Task, gli oggetti Graphics e comunque per implementare un'approccio ludico; proprio per questo, una volta scritto, non l'ho affatto curato, l'interfaccia è scarna e si limita all'essenziale, ho risolto solo i bug più invasivi tralasciando quelli che si presentano più raramente.

Per accorciare: potete scaricare da qui il file .jar (ovviamente dovete avere installata la java virtual machine sul vostro sistema).I tasti per giocare sono A(sinistra), D(destra), S(ruota), Z(avanzamento veloce).

Il gioco, lo dico subito, contiene alcuni bug noti. Il più lampante è che ogni tanto scompaiono le cornici, o non si disegna lo sfondo, ma almeno è giocabile.  Per realizzarlo ho utilizzato le primitive grafiche di java, ovvero ho fatto un uso massiccio dell'oggetto Graphics della classe Canvas (da me riscritta).

La parte principale del gioco è stata realizzata creando un TimerTask, che grazie a un oggetto di tipo Timer ho invocato il metodo public void schedule(TimerTask task, long delay, long period) che mi permette di eseguire continuamente il TimerTask con un ritardo (periodo) di period millisecondi, variabili in base al livello del gioco (500 ms livello 1, 50 ms livello 10, 0 ms >10). Non ho messo un controllo su questa variabile...teoricamente se si supera il livello 10 dovrebbe diventare ingiocabile visto che non c'è alcun ritardo.

L'interfaccia viene curata continuamente dalla classe MyCanvas che eredita da java.awt.Canvas, mentre tutto il contenuto dell'area di gioco (quella coi quadratini) viene curato dalla classe Grid (griglia) che ho creato interamente io, servendomi di altre classi "satelliti" come BlockCollection o Block, per dirne due.
Ogni blocco non è altro che un array bidimensionale 4x4 di boolean e il suo colore è passato all'oggetto Graphics prima di disegnarlo.
Così per esempio, i famosi blocchi di tetris sono stati definiti così:

public static final boolean[][] I = {{false,true,false,false},{false,true,false,false},{false,true,false,false},{false,true,false,false}};
public static final boolean[][] O = {{false,false,false,false},{true,true,false,false},{true,true,false,false},{false,false,false,false}};
public static final boolean[][] J = {{true,true,true,false},{true,false,false,false},{false,false,false,false},{false,false,false,false}};
public static final boolean[][] L = {{true,true,true,false},{false,false,true,false},{false,false,false,false},{false,false,false,false}};
public static final boolean[][] S = {{true,true,false,false},{false,true,true,false},{false,false,false,false},{false,false,false,false}};
public static final boolean[][] T = {{true,true,true,false},{false,true,false,false},{false,false,false,false},{false,false,false,false}};
public static final boolean[][] Z = {{false,true,true,false},{true,true,false,false},{false,false,false,false},{false,false,false,false}};


Il sistema dei punteggi è un po' strambo...beh almeno è originale.
Il livello è dato da questa espressione: level = (punteggio/1000)+1; mentre il punteggio viene dato ogni volta che si scala una riga con l'espressione: punteggio+=level*100;




Ultima cosa, nel programma prima di istanziare l'oggetto grid è possibile modificare il numero delle righe e il numero nelle colonne a piacimento, nonché la spaziatura in pixel fra i quadratini e la grandezza in pixel di ogni singolo quadratino.

Il codice beh...sopra c'è il file jar quindi con un disassemblatore si potrebbe leggere tutto....bye!

mercoledì 7 dicembre 2011

Un semplice programma in C per leggere i file RIFF WAVE

Il file Wave è senza dubbio il formato più semplice per memorizzare un file audio; essendo non compresso, non prevede particolari algoritmi per la codifica e quindi molto veloce. Di contro c'è che occupa molta memoria e quindi è poco maneggevole.

Prima di mettere un po' di codice, vediamo intanto di capire il formato RIFF Wave, in particolare capire come è strutturato.

La struttura

Il file Wave si compone di un header a cui fanno seguito i dati veri e propri dell'audio (chunks). L'header, di grandezza variabile, si compone a sua volta di tre parti: RIFF, fmt e data. Vediamoli separatamente:

1)RIFF
E' grande 12 byte, di cui i primi 4 sono le lettere ASCII di "RIFF", i seguenti 4 byte (Chunk Size) rappresentano la grandezza in byte di tutto il file compresi gli header.
Gli ultimi 4 byte sono le lettere ASCII di "WAVE".
Sintetizzando:

|R|I|F|F|    |?|?|?|?|      |W|A|V|E|

Dove a ogni singola casella corrisponde un byte.

2)fmt

I primi 4 byte di questo blocco sono i caratteri ASCII di "fmt " (compreso lo spazio).
A seguire ci sono 4 byte per il SubChunk1Size, che sarebbe la grandezza del blocco fmt esclusi questi primi 8 byte. Per i file Microsoft Wave, il valore di questo campo è 16 byte che analizzeremo qui appresso.

2 byte per AudioFormat, ovvero il Formato Audio.
2 byte per NumChannels, ovvero il numero di canali.
4 byte per SampleRate, ovvero la frequenza di campionamento.
4 byte per ByteRate, ovvero la frequenza di byte (byte/s)
2 byte per BlockAlign, ovver l'allineamento del blocco, che ci permette di leggere la giusta quantità di byte nei dati raw
2 byte per BitsPerSample, ovvero il numero di bit che usiamo per rappresentare un singolo campione.

Sintetizzando:

|f|m|t| |   |?|?|?|?|   |?|?|   |?|?|   |?|?|?|?|   |?|?|?|?|   |?|?|   |?|?|

3)data
Queste sono le informazioni finali che chiudono l'header.
I primi 4 byte sono i caratteri ASCII "data", i seguenti 4 byte rappresentano la grandezza in byte di tutto il blocco data (quindi anche i campioni).
Finalmente, adesso ci saranno file interminabili di byte che rappresentano i campioni.


Come viene rappresentato un campione?
Un campione non è altro che un numero che se viene rappresentato in un diagramma tempo-valore(di questo numero) insieme agli altri campioni forma un'onda. L'onda è più approssimata quanto più questi campioni sono "vicini" tra di loro, quindi, quanto più è maggiore la frequenza di campionamento vista poco fa (SampleRate).
Un singolo campione può essere a 8 bit o a 16 bit, quindi i suoi valori possono "spaziare" tra 0 e 127 o tra 0 e 65535: l'informazione che ci dice quanti bit vengono utilizzati per un campione è il BitPerSample già visto. 

Un po' di codice
Bene, era ora...posto un programmino che non fa altro che caricare un file wav e leggere il suo header e stampare i risultati a schermo. Utile per capire meglio il formato wave. Nota: il programma non legge affatto i campioni, comunque più avanti posterò anche un programma che legge i campioni e li esporta in formato csv per poi magari rappresentarli con un foglio di calcolo e vedere l'onda, o magari inviarli alla scheda audio e praticamente: riprodurre un file wave!
Ecco qui il codice:


#include <stdio.h>
#include <stdint.h>


struct T_RIFF {
       uint8_t ChunkID[4];
       uint32_t ChunkSize;
       uint8_t Format[4];
};


struct T_fmt {
       uint8_t Subchunk1ID[4];
       uint32_t Subchunk1Size;
       uint16_t AudioFormat;
       uint16_t NumChannels;
       uint32_t SampleRate;
       uint32_t ByteRate;
       uint16_t BlockAlign;
       uint16_t BitsPerSample;
};
struct T_data_nosamples {
       uint8_t Subchunk2ID[4];
       uint32_t Subchunk2Size;
};
struct T_data {
       uint8_t Subchunk2ID[4];
       uint32_t Subchunk2Size;
};
struct T_WAVE {
       struct T_RIFF RIFFheader;
       struct T_fmt fmtheader;
       struct T_data dataheader;
};


main() {
       struct T_WAVE WAVE;
       printf("Inserisci il percorso del file: ");
       char percorso[50];
       scanf("%s",percorso);
       FILE* stream = fopen(percorso,"r");
       if (stream==NULL)
          printf ("Il file non esiste! \n");
       else {
         
           fread(&WAVE,1,44,stream);
           uint32_t Samplesize, Sample;
           printf ("Grandezza blocco: %d byte\n\n",WAVE.RIFFheader.ChunkSize);
           printf ("Grandezza blocco fmt: %d byte\n",WAVE.fmtheader.Subchunk1Size);
           printf ("Formato audio: %d\n",WAVE.fmtheader.AudioFormat);
           printf ("Numero di canali: %d\n",WAVE.fmtheader.NumChannels);
           printf ("Frequenza di campionamento: %d Hz\n",WAVE.fmtheader.SampleRate);
           printf ("Frequenza di byte: %d byte/s\n",WAVE.fmtheader.ByteRate);
           printf ("Allineamento blocco: %d\n",WAVE.fmtheader.BlockAlign);
           printf ("Bits per campione: %d\n\n",WAVE.fmtheader.BitsPerSample);
           printf ("Grandezza blocco data: %d byte\n\n",WAVE.dataheader.Subchunk2Size);
           float durata =  (float) WAVE.dataheader.Subchunk2Size/WAVE.fmtheader.ByteRate;
           int durataAppr = WAVE.dataheader.Subchunk2Size/WAVE.fmtheader.ByteRate;
           printf ("Durata in secondi: %d s\n",durataAppr);
           uint32_t SampleSize = WAVE.fmtheader.BitsPerSample / 8;
           uint32_t numcampioni = (uint32_t) durata * WAVE.fmtheader.SampleRate;
           //processa16bit(WAVE.fmtheader.NumChannels,SampleSize,numcampioni,stream);
           fclose(stream);
       }
}


Mi ripeto: commentate se ci sono errori, o anche per consigli. Bye!
Alberto

Altervista: crearsi un sistema di gestione di commenti

Conosciamo altervista, la famosa piattaforma italiana che ti permette di creare un sito con un dominio di secondo livello, spazio web e una serie di utili strumenti per creare un sito (tra i più importanti citiamo php5 e mysql lite), il tutto in maniera gratuita.

Tempo fa per un mio sito che elenca una serie di canzoni "storiche", decisi di creare la possibilità di inserire dei commenti sotto ogni canzone. Ho creato tutto con php4  e ultimamente, avendo implementato il SimpleXML, anche php5.

Ovviamente questo sistema va bene per siti "minori" e non quelli con alto traffico (e quindi molti commenti).

Ho deciso di organizzare tutti i commenti (di tutte i post) in un file di nome "commenti.xml". Ipotizzando che ci siano due post con ID "post1" e "post2", ognuno con i relativi commenti, la struttura di questo file sarà:


<ROOT>

<COMMENTO ID="post1">

<NOME>Alberto</NOME>

<SITO>www.miosito.it</SITO>

<DATAORA>29/7/2009 alle 22:59</DATAORA>

<TESTO>
Questo è un commento a post1
</TESTO>
</COMMENTO>

<COMMENTO ID="post2">


<NOME>Alberto</NOME>

<SITO>www.miosito.it</SITO>

<DATAORA>29/7/2010 alle 22:59</DATAORA>

<TESTO>
Questo è un commento a post2
</TESTO>

</COMMENTO>
</ROOT>

Adesso dovremmo creare tre file php:

  1. Una pagina che visualizza il form da dove inserire il commento
  2. Un file php che contiene puro codice per processare il commento
  3. Una pagina che  visualizza i commenti
I suddetti file saranno solamente tre per qualsivoglia numero di post, i commenti verranno memorizzati nello stesso file "commenti.xml" in base al loro ID passato alle pagine con il metodo GET (per chi non mastica molto l'HTML, è un metodo per passare delle informazioni da una pagina a un'altra tramite l'indirizzo).

Iniziamo dunque dal primo file php: il form. 

1)Il Form
Salviamo il seguente codice in un file php, per esempio new.php:


<?
$ref=$_GET['ref'];


echo "<html>";
echo "<body bgcolor='#3366FF'>";
echo "<form method='post' action='post.php?rif=".$ref."'>Nome:<br /><input name='nome' type='text' style='width: 98%' />&nbsp;&nbsp; <br />Indirizzo:<br /><input name='indirizzo' style='width: 98%' type='text' /><br>Commento:<br/><textarea name='commento' style='width: 98%; height: 143px'></textarea><p><input name='Submit1' type='submit' value='Invia commento' /></p></form>";
?>
</body>
</html>

La prima istruzione ottiene il riferimento (ID, qui chiamato ref) col metodo GET e poi tramite il linguaggio php crea un form con i rispettivi campi per inserire un commento. Il form, se viene fatto click sul tasto "Invia commento", apre la pagina "post.php" e passerà col metodo POST (che, a differenza del metodo GET, non lascia trasparire all'utente i dati inviati) i campi del commento.
Esempio di codice HTML per inserire un commento su post1, per esempio:

<a href="/new.php?ref=post1">Invia commento</a>
 Adesso vediamo di creare il file "post.php".

2)Processare il commento: post.php
Salviamo il seguente codice in un file php, per esempio post.php:
<?
$rif=$_GET["rif"];                        //ottiene il riferimento
$xml=fopen("commenti.xml","r+");          //apre "commenti.xml"
$posizione = filesize("commenti.xml")-7;  
fseek($xml,$posizione,SEEK_SET);          //posiziona il puntatore nel file per aggiungere i tag XML alla coda del file, prima di </ROOT>


$nome = $_POST['nome'];                   //ottiene il nome tramite POST
$indirizzo = $_POST['indirizzo'];         //ottiene indirizzo tramite POST  
$arraydata = getdate();                   //ottiene la data corrente
$data = $arraydata['mday'].'/'.$arraydata['mon'].'/'.$arraydata['year'].' alle '.$arraydata['hours'].':'.$arraydata['minutes'];   //formatta la data
$commento = $_POST['commento'];           //ottiene il testo del commento       
fwrite($xml, '<COMMENTO ID="'.$rif.'"><NOME>'.$nome.'</NOME><SITO>'.$indirizzo.'</SITO><DATAORA>'.$data.'</DATAORA><TESTO>'.$commento.'</TESTO></COMMENTO></ROOT>'); //scrive i tag XML sul file  
mail("miamail@mail.com","Notifica commento","Email automatica: hai ricevuto un commento sul tuo sito. Commento ricevuto da $nome:\n$commento");
fclose($xml);                             //chiude il file
?>
<html>
<body>
<div style="border-style: groove; background-color: #3366FF">Commento pubblicato.</div>
</body>
</html>

 Il codice è tutto commentato. In questo modo si avrà la possibilità di ricevere una mail ogni qualvolta qualcuno pubblica un commento sul nostro sito, basta cambiare miamail@mail.com . Da notare che nonostante stia scrivendo su un file XML, non utilizzo alcuna funzione di libreria per gli XML semplicemente perché lo tratto come testo. Nella prossima sezione vedremo come per visualizzare i commenti, devo necessariamente utilizzare qualche funzione che mi faciliti la lettura dei file XML.

3)Visualizzare i commenti: view.php
Salviamo il seguente codice in un file php, per esempio view.php:


<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//IT" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<title>Visualizza commenti</title>
<meta charset="utf-8" />
</head>
<body link="#FFFF00" bgcolor="#3366FF">
<?php
$commentata=false;         //Una variabile per controllare se ci sono commenti così visualizza "nessun commento"
$ref=$_GET["ref"];                          //ottengo il riferimento
$xml = simplexml_load_file('commenti.xml'); //carico il file con SimpleXML(php5)
foreach($xml->COMMENTO as $commento)        //scansiono ogni commento
if ($commento['ID']==$ref)                  //se il commento appartiene al post che mi interessa... 
{
    $indirizzoenome=$commento->NOME;        //ottengo i vari campi...
    if (($commento->SITO!='')&($commento->SITO!=' '))  $indirizzoenome = '<b>'.$commento->NOME.'</b> da <b>'.$commento->SITO.'</b>';
    if (strstr($commento->SITO,'@')) $indirizzoenome='<a href="mailto:'.$commento->SITO.'">'.$commento->NOME.'</a>';
    if (strstr($commento->SITO,'.')) $indirizzoenome='<a href="'.$commento->SITO.'">'.$commento->NOME.'</a>';
    
    echo '<div style="background-color: #00BFFF; border-style: ridge; padding: 5px; margin: 5px 0px 5px 0px; width: 400px; " >';   //..e li stampo a schermo
    echo $indirizzoenome.' ha scritto: <br>';
    echo '<i>'.$commento->TESTO.'</i><br>';
    echo '<font size="2">'.$commento->DATAORA.'</font><br>';
    echo '</div>';   
    $commentata=true;                      //c'è almeno un commento.
}
if ($commentata==false) echo '<div style="background-color: #00BFFF; border-style: ridge; padding: 5px; margin: 5px 0px 5px 0px; width: 400px; " >Nessun commento</div>';                          


?>
</body>
</html>

Anche questo codice è commentato e non aggiungo altro. Visualizzerà ogni commeno in un blocco "div" e se non c'è alcun commento visualizza "Nessun commento". Anche qui è necessario il riferimento. Se voglio leggere tutti i commenti di "post1" utilizzo questo link:

<a href="/view.php?ref=post1">Visualizza commenti</a>


Tutto ciò è abbastanza semplice. Se volete chiarimenti, delucidazioni o volete far notare errori o inconvenienze, commentate :D

Alberto

martedì 6 dicembre 2011

Inaugurazione

In questo blog condividerò le mie conoscenze nel campo dell'informatica, sopratutto della programmazione.

In particolare concetti, spezzoni di codice e varie implementazioni dell'arte più geek che ci sia, l'arte della programmazione.

Saranno graditi commenti ed osservazioni (come consigli) dei più esperti e non, soprattutto per quei post dove espongo un mio problema o qualche bug irrisolvibile.

Alberto