Categories: VC/C++

DirectSound per i Suoni dei Videogiochi

Nelle applicazioni gestionali o che comunque richiedono al massimo di utilizzare un paio di suoni, la semplice chiamata all’API PlaySound() è più che sufficiente, ma se dobbiamo creare un videogioco con suoni in sottofondo, spari e rombi di motore, allora dobbiamo passare all’utilizzo di uno strumento che offra prestazioni migliori e sia nato per questo scopo, la libreria DirectSound di Microsoft, facente parte della libreria DirectX nata apposta per lo sviluppo dei videogiochi. Si tratta di un insieme di oggetti COM che consentono velocità e controllo, vediamo i principi base del motore DirectSound:

  • Sorgente Audio: un qualunque file oppure il canale di una periferica audio.
  • Buffer Secondari: blocchi di memoria atti a memorizzare dei campioni di segnale audio.
  • Mixer: miscela e convoglia campioni provenienti da più buffer secondari e li invia al buffer primario.
  • Buffer Primario: riceve i campioni audio dal mixer e gestisce le operazioni di sincronizzazione con il dispositivo HW.
  • Dispositivo HW: un modulo HW presente sulla scheda audio che riceve i campioni dal buffer primario, li decodifica in analogico e li riproduce.
  • Speaker: gli altoparlanti del sistema.

Ogni segnale audio è caratterizzato da una serie di proprietà che sono descritte da una struttura denominata WAVEFORMATEX che serve per descrivere:

  • Il numero di canali ( 1 o 2 ).
  • La frequenza di campionamento.
  • Il numero di bit per canale.
  • Il tipo di codifica, normalmente PCM ( Pulse Code Modulation ).

Per utilizzare DirectSound nelle nostre applicazioni occorre creare ed istanziare l’oggetto DirectSound attraverso la funzione DirectSoundCreate():

...
LPDIRECTSOUND lpDS;

CoInitialize(NULL);

if (FAILED(DirectSoundCreate(NULL, &lpDS, NULL)))
{
   CoUninitialize();
   return -1;
}
...

Dopo aver avuto accesso alla libreria occorre impostare il livello di cooperazione, noto a tutti coloro che utilizzano DirectX, che consente sia di effettuare il binding tra DirectSound e l’applicazione, sia stabilire la modalità di condivisione dell’oggetto tra applicazioni in esecuzione. I livelli di cooperazione possibili sono:

  • DSSCL_NORMAL: le applicazioni non possono modificare il formato audio nel buffer primario.
  • DSSCL_PRIORITY: le applicazioni possono scrivere direttamente nel buffer primario ( questo valore è utilizzato di solito nei videogames ).
  • DSSCL_EXCLUSIVE: usato in versioni passate, ora obsoleto.
  • DSSCL_WRITEPRIMARY: massimo controllo sul buffer primario.
...
if (FAILED(lpDS->SetCooperativeLevel(m_hWnd, DSSCL_PRIORITY)))
{
   lpDS->Release();
   CoUninitialize();
   return -1;
}
...

Il parametro m_hWnd è l’handle della finestra della nostra applicazione.
Il buffer è l’oggetto più importante di tutta l’architettura ed è rappresentato dall’interfaccia IDirectSoundBuffer che fornisce metodi e proprietà per consentirne il pieno controllo. Sebbene si possa ricavare il puntatore al buffer primario, le operazioni come la modifica del formato del segnale o mix personalizzati avvengono sempre a livello di buffer secondari. Un buffer secondario può essere suddiviso in due categorie distinte: static e streaming. Un buffer statico serve per memorizzare suoni brevi da ripetere molte volte ( spari ), mentre il buffer streaming al contrario serve per suoni più lunghi, la filosofia è quella di non caricare tutti i dati, ma soltanto la parte che deve essere eseguita. Il metodo CreateSoundBuffer() crea per default un buffer streaming, mentre se si inserisce DSBCAPS_STATIC come parametro crea un buffer statico.
Le impostazioni del buffer primario possono essere modificate rispetto a quelle di default solo se la cooperazione è almeno di tipo DDSCL_PRIORITY ed il momento migliore per modificare tali parametri è nella fase di inizializzazione dell’applicazione. Occorre dire che DirectSound gestisce eventuali differenze tra buffer primario e secondari convertendo e mixando il secondario nel primario, quindi ove possibile occorrerebbe utilizzare buffer secondari dello stesso formato del primario. Il formato del buffer come abbiamo già detto è definito nella struttura WAVEFORMATEX, vediamo come è possibile inizializzarla e cambiare il formato del buffer:

...
LPDIRECTSOUNDBUFFER lpDSBuffer;
WAVEFORMATEX wf;
DSBUFFERDESC dsbdesc;

memset(&wf, 0, sizeof(WAVEFORMATEX));
wf.wFormatTag = WAVE_FORMAT_PCM;
wf.nChannels = 2;
wf.nSamplesPerSec = 44100;
wf.wBitsPerSample = 16;
wf.nBlockAlign = 4;
wf.nAvgBytesPerSec = wf.nSamplesPerSec * wf.nBlockAlign;

memset(&dsbdesc, 0, sizeof(DSBUFFERDESC));
dsbdesc.dwSize = sizeof(DSBUFFERDESC);
dsbdesc.dwFlags = DSBCAPS_CTRLPAN|DSBCAPS_CTRLVOLUME|
   DSBCAPS_CTRLFREQUENCY;
dsbdesc.dwBufferBytes = 3 * wf.nAvgBytesPerSec;
dsbdesc.lpwfxFormat = (LPWAVEFORMATEX)&wf;

if (FAILED(lpDS->CreateSoundBuffer(&dsbdesc, &lpDSBuffer, NULL)))
{
   lpDS->Release();
   CoUninitialize();
   return -1;
}

/*
if (FAILED(lpDSBuffer->SetFormat(&wf)))
{
   lpDSBuffer->Release
   lpDS->Release();
   CoUninitialize();
   return -1;
}
*/...

In questo listato ho creato un buffer secondario, ma tra commenti ho lasciato la funzione SetFormat per mostrare come si cambia il formato di un buffer ( molto semplice, basta passare il puntatore alla struttura WAVEFORMATEX ). Una volta creato il buffer secondario possiamo fare quello che vogliamo, per esempio copiare i dati di un file wave, per fare questo occorre bloccare il buffer per evitare che altri vi scrivano e poi quando finito di copiare occorre sbloccarlo.

...
LPVOID lpWrite;
DWORD dwLength;

if (SUCCEEDED(lpDSBuffer->Lock(0, 0, &lpWrite, &dwLength,
   NULL, NULL, DSBLOCK_ENTIREBUFFER)))
{
   memcpy(lpWrite, cbData, dwLength);
   lpDSBuffer->Unlock(lpWrite, dwLength, NULL, 0);
}
...

In questo esempio cbData è il puntatore ai dati del nostro file wave, che può essere recuperato con delle funzioni accessorie come WaveOpenFile() e WaveReadFile() ( a tal proposito vedere qui ).
Quando abbiamo finito di caricare i dati nel buffer secondario possiamo far emettere il suono:

...
lpDSBuffer->SetCurrentPosition(0);
lpDSBuffer->Play(0, 0, 0);

lpDSBuffer->Release();
lpDS->Release();
CoUninitialize();
...

La libreria DirectSound è ancora presente in DirectX, ma è stata sostituita da XAudio2 che casomai vedremo in futuro in un altro articolo di questo blog, quindi continuate a seguirmi!

Share
Giampaolo Rossi

Sviluppatore di software gestionale da oltre 28 anni.

Recent Posts

Un Abbonamento per Tutti i Software

Sono arrivato alla convinzione che un abbonamento per tutti i miei software gestionali sia il…

1 anno ago

Software di Magazzino Gratuito

MerciGest è un software per la gestione del magazzino completamente gratuito. Continua a leggere→

1 anno ago

Mettere il PC in Lock Screen

In ufficio può capitare di doversi allontanare dal proprio posto di lavoro, ecco che allora…

3 anni ago

Fare il reset togliendo la corrente

In questo articolo vedremo quando è più o meno utile togliere la corrente ad un…

3 anni ago

Prossimi Aggiornamenti Software

Dopo la pausa invernale dovuta al lavoro che devo fare per sostentarmi, eccomi di nuovo…

3 anni ago

Come Eliminare i Files in Windows

Vediamo come eliminare i files direttamente da Windows senza utilizzare il cestino. Continua a leggere→

4 anni ago