Sommaire

- Introduction ý DirectSound

- CharactÈristiques du son numerique, de la carte son et des buffers son

- Utilisation de DirectSound

- Utilisation de 3D Sound

- DirectSound & DirectMusic 8.1


Introduction au DirectSound

Qu'est ce qu'on peut faire avec DirectSound ?

- Jouer des sons statiques

- Sortie audio temps-réel

- Spatialiser le son en 3D

- Entrée audio

Charactéristiques du son numerique, de la carte son et des buffers son



Charactéristiques

- Taux d'échantillonnage (samples per second, sample rate)

- Nombre de canaux : mono, stéréo, ... (channels)

- Format des échantillons : PCM (Pulse Code Modulation), muLaw, ... (format)

- Taille d'un bloc d'échantillons (bloc align, frame size)



Charactéristiques format PCM

Taille des échantillons (8 bits, 16 bits, ...)

Taille d'un bloc = nombre de canaux x taille d'un échantillon en octets

8 ou 16 bits per sample -> 1 ou 2 octets



Types primitives à utiliser pour DirectSound

- son 8 bits : unsigned char

- son 16 bits : signed short (little endian)



Charactéristiques de la carte son

- Taux d'échantillonnage

- Nombre de canaux

- Format des échantillons

- Mémoire RAM sur la carte

- Traitement de son sur la carte (mixage, 3D, ...)

- Nombre de buffers maximale

- Taille du buffer interne

- Pilote DirectSound (WDM) ou pilote WAVE (MME)



Charactéristiques des buffers son

- Statique ou dynamique

- Primaire ou sécondaire

- Software ou hardware


Buffers statiques

Un buffer statique contient un son entièrement.

Buffers dynamiques

L'application fournit les échantillons au furs et à mesure pendant l'éxecution. L'application et le pilote de la carte son accèdent au buffer de manière concurrente : l'application remplie le buffer, le pilote le vide. Les buffers dynamique sont aussi appelés des "streaming buffers". Ils sont réalisé comme des buffers circulaires.







Buffer primaire

Dans DirectSound, il n'existe qu'un seul buffer primaire, qui est partagé par toutes les applications. Le buffer primaire est un buffer dynamique.

Buffer sécondaire

Les applications créent des buffers sécondaires pour l'utilisation interne. Les buffers sécondaires peuvent être statiques ou dynamiques. Tous les buffers sécondaires sont mixés dans le buffer primaire avant la sortie. En général, il est consillé que les buffers sécondaires ont le même format que le buffer primaire. Cela évite des conversion de format pendant le mixage.







Buffers software

Le buffer est alloué dans la mémoire vive de l'ordinateur. Le buffer est gÈrÈ par le pilote en "software".

Buffers hardware

Le buffer est gÈrÈ par la carte.


Utilisation de DirectSound


Résumé

- Fichier d'entête à inclure : #include <dsound.h>

- Bibliothèque à utiliser : dsound.lib

- #define INITGUID en haut du fichier

- Récupérer la liste des devices

- Créer l'objet DirectSound : DirectSoundCreate()

- Création du buffer primaire

- Création des buffers sécondaires



Récupérer la liste des devices audio disponibles

Utiliser la function DirectSoundEnumerate(LPDSENUMCALLBACK callback, LPVOID NULL).



/* The dsdevice_t structure contains the GUID and the name of a DirectSound
 * device. The structures are chained together in a linked list. */

typedef struct _dsdevice_t dsdevice_t;

struct _dsdevice_t {
  dsdevice_t* next;
  LPGUID guid;
  char* description;
};

/* The global list of DirectSound devices */
static dsdevice_t* dsdevice_list = NULL;

/* Allocate and inialize a new dsdevice_t. The newly allocated object
will be * inserted in the global list of DirectSound devices. */

dsdevice_t* new_dsdevice(LPGUID guid, const char* description)
{
  dsdevice_t* dev = (dsdevice_t*) malloc(sizeof(dsdevice_t));

  dev->next = NULL;
  dev->guid = NULL;
  dev->description = (description ? strdup(description) 
		      : strdup("unknown device"));

  if (guid != NULL) {
    dev->guid = malloc(sizeof(GUID));
    memcpy(dev->guid, guid, sizeof(GUID));
  }

  return dev;
}

/* The callback function used in the enumaration of the DirectSound
 * devices.  */
static BOOL 
ds_enum_callback(LPGUID guid, LPCSTR description, LPCSTR module, LPVOID
context)
{
  dsdevice_t* dev = new_dsdevice(guid, description);

  dev->next = dsdevice_list;
  dsdevice_list = dev;

  return TRUE;
}


dsdevice_t*  ds_get_device_list()
{
  if (dsdevice_list == NULL) {
    /* Enumarate the list of devices. For every device, the function
     * ds_enum_callback * will be called. */
    DirectSoundEnumerate((LPDSENUMCALLBACK) ds_enum_callback, NULL);
  }    
  return dsdevice_list;
}

Créer l'object DirectSound


Résumé :

- DirectSoundCreate : crée l'objet DirectSound

- SetCooperativeLevel : spécifie les droits d'accès au buffer primaire

- GetCaps : renvoit les information sur les capacités du device

- SetSpeakerConfig : définie la configuration des haut-parleurs (stéréo, 5.1, ...)



Note importante : Quand on crée l'objet DirectSound avec GUID égale à NULL, on ouvre le device par défaut. Le device par défaut est celui qui est spécifié dans les panneaux de contrôle de Windows.

Pour la création de l'objet DirectSound, il faut passer la référence d'une fenêtre en argument. (Je ne comprend pas trop pourquoi.)


#define INITGUID
#include 

typedef struct {
  LPGUID guid;
  LPDIRECTSOUND direct_sound;
} audioport_t;


audioport_t* new_audioport(LPGUID guid, HWND wnd)
{
  audioport_t* audioport;
  DSCAPS caps;
  HRESULT hr;
  
  /* Allocate space for the audio port object */
  audioport = (audioport_t*) malloc(sizeof(audioport_t));
  if (audioport == NULL) {
    /* ... */
  }

  /* Initialize the audio port object */
  audioport->guid = guid;
  audioport->direct_sound = NULL;

  /* Create the DirectSound object with the given GUID */
  hr = DirectSoundCreate(audioport->guid, &audioport->direct_sound, NULL);
  if (hr != DS_OK) {
    /* Handle error */
  }


  /* Set the cooperative level */
  hr = IDirectSound_SetCooperativeLevel(audioport->direct_sound, wnd,
					DSSCL_PRIORITY);
  if (hr != DS_OK) {
    /* Handle error */
  }

  /* Get the capabilities of the device */
  caps.dwSize = sizeof(caps);
  hr = IDirectSound_GetCaps(audioport->direct_sound, &caps);
  if (hr == DS_OK) {
    /* Use the information about the device capabilities */
  }
  
  return audioport;
}

void delete_audioport(audioport_t* audioport)
{
  /* Release the DirectSound object */
  if (audioport->direct_sound != NULL) {
    IDirectSound_Release(audioport->direct_sound);
  }
  free(audioport);
}

Différences entre C et C++

En C, les objets DirectSound sont des tableaux de fonctions (interfaces). Pour appeler la function GetCaps() sur un objet de type IDirectSound, on utilise IDirectSound_GetCaps(direct_sound). Le premier argument est toujours la reference retourné à la création de l'objet.

En C++, les objets DirectSound utilisent la notation standard, à savoir : referenceObjet->nomMethode(arguments). Pour appeler la function GetCaps(), on utilise directSound->GetCaps().

L'exemple précédent, réécrit en C++, est donné ci-dessous :


#define INITGUID
#include 

class AudioPort 
{
public:
  AudioPort(LPGUID guid, HWND wnd);
  virtual ~AudioPort();

private:
  LPGUID _guid;
  LPDIRECTSOUND _directSound;
};


AudioPort::AudioPort(LPGUID guid, HWND wnd)
{
  DSCAPS caps;

  /* Initialize the audio port object */
  _guid = guid;
  _directSound = NULL;

  /* Create the DirectSound object with the given GUID */
  hr = DirectSoundCreate(_guid, &_directSound, NULL);
  if (hr != DS_OK) {
    /* Handle error */
  }

  /* Set the cooperative level */
  hr = _directSound->SetCooperativeLevel(wnd, DSSCL_PRIORITY);
  if (hr != DS_OK) {
    /* Handle error */
  }


  /* Get the capabilities of the device */
  caps.dwSize = sizeof(caps);
  hr = _directSound->GetCaps(&caps);
  if (hr == DS_OK) {
    /* Use the information about the device capabilities */
  }
}

AudioPort::~AudioPort()
{
  /* Release the DirectSound object */
  if (_directSound != NULL) {
    _directSound->Release();
  }
}


Les niveaux de coopération de DirectSound

- DSSCL_WRITEPRIMARY

- DSSCL_EXCLUSIVE, DSSCL_PRIORITY

- DSSCL_NORMAL


Analyser les capacités du device

- IDirectSound::GetCaps()

- Nombre de buffers maximales (2D et 3D)

- Taille mémoire RAM

- ...


Création du buffer primaire

Résumé :


- CreateSoundBuffer : crée un buffer. Il faut passer l'option DSBCAPS_PRIMARYBUFFER pour créer un buffer primaire.

- SetFormat : définie le format du buffer primaire. Pour changer le format, il faut avoir un accès priviligié au buffer primaire.

- Play : démarre le buffer primaire. Il faut passer l'option DSBPLAY_LOOPING en argument.



La création d'un buffer prend en argument la référence d'une structure de type DSBUFFERDESC. Pour créer un buffer primaire, le champ le plus important est :

- dwBufferBytes : la taille du buffer en octets.

- lpwfxFormat : référence vers une structure de type WAVEFORMATEX, qui décrit le format du son

- dwFlags : des options pour le buffers

Pour la création d'un buffer primaire, on ne spécifie pas dwBufferBytes (définie par la carte son), lpwfxFormat doit être égale à NULL, et dwFlags doit contenir DSBCAPS_PRIMARYBUFFER.



typedef struct {
  LPGUID guid;
  LPDIRECTSOUND direct_sound;
  LPDIRECTSOUNDBUFFER primary_buffer;
} audioport_t;

 

audioport_t* new_audioport(LPGUID guid, HWND wnd)
{
  audioport_t* audioport;
  DSCAPS caps;
  HRESULT hr;
  DSBUFFERDESC desc;
  WAVEFORMATEX format;
  
  /* ... */


  /* Create the primary buffer */

  /* Initialize the description of the primary buffer. */
  ZeroMemory(&desc, sizeof(DSBUFFERDESC));

  desc.dwSize = sizeof(DSBUFFERDESC);
  desc.dwFlags = DSBCAPS_PRIMARYBUFFER;

  /* If you want to allocate the primary buffer on the sound card, you
   * have to set the DSBCAPS_LOCHARDWARE flag */

  if (caps.dwFreeHwMixingStreamingBuffers > 0) {
    desc.dwFlags |= DSBCAPS_LOCHARDWARE;
  }
    
  hr = IDirectSound_CreateSoundBuffer(audioport->direct_sound, &desc,
                                      &audioport->primary_buffer, NULL);
  if (hr != DS_OK) {
    /* Handle error */
  }


  /* Initalize de format structure for CD quality audio: 2 channels,
   * 44100 Hz sample rate, 16 bits per sample. */

  ZeroMemory(&format, sizeof(WAVEFORMATEX));
  format.wFormatTag = WAVE_FORMAT_PCM;
  format.nChannels = 2;
  format.nSamplesPerSec = 44100;
  format.wBitsPerSample = 16;
  format.nBlockAlign = format.nChannels * format.wBitsPerSample / 8;
  format.nAvgBytesPerSec = format.nSamplesPerSec * format.nBlockAlign;
  format.cbSize = sizeof(WAVEFORMATEX);


  /* Set the audio format of the primary sound buffer. */
  hr = IDirectSoundBuffer_SetFormat(audioport->primary_buffer, &format);
  if (hr != DS_OK) {
    /* Handle error */
  }


  /* Start playing the primary buffer. */
  hr = IDirectSoundBuffer_Play(audioport->primary_buffer, 0, 0, 
                               DSBPLAY_LOOPING);
  if (hr != DS_OK) {
    /* Handle error */
  }


  

  /* ... */
}

Création d'un buffer sécondaire pour un son statique



Résumé :

- CreateSoundBuffer : crée un buffer.

- Lock : bloquer le buffer pour l'Ècriture.

- Ecrire les Èchantillons dans le buffer.

- Unlock : rel’cher le buffer.



La création d'un buffer sécondaire prend en argument la référence d'une structure de type DSBUFFERDESC. Les champs les plus importants sont :

- dwBufferBytes : la taille du buffer en octets.

- lpwfxFormat : référence vers une structure de type WAVEFORMATEX qui décrit le format du son.

- dwFlags : des options du buffer.



Les options incluent :

- le choix d'allouer le buffer sur la carte ou en mémoire vive (DSBCAPS_LOCHARDWARE, DSBCAPS_LOCSOFTWARE, DSBCAPS_LOCDEFER, DSBCAPS_STATIC)

- le choix de continuer à jouer le buffer quand l'application perd le premier plan (DSBCAPS_GLOBALFOCUS, DSBCAPS_STICKYFOCUS)

- le choix de pouvoir contrôler certain paramètres (DSBCAPS_CTRLPAN, DSBCAPS_VOLUME, DSBCAPS_FREQUENCY)

- les options 3D (voir plus bas)


LPDIRECTSOUND audioport_get_direct_sound(audioport_t* audioport)
{
  return audioport->direct_sound;
}


typedef struct {
  audioport_t* audioport;
  LPDIRECTSOUNDBUFFER buffer;
} static_sound_t;


static_sound_t* 
new_static_sound(audioport_t* audioport, int size, short* sample_data)
{
  LPDIRECTSOUND direct_sound;
  DSBUFFERDESC desc;
  WAVEFORMATEX format;
  void *buf;
  
  static_sound_t* snd;
  
  snd = (static_sound_t*) calloc(sizeof(static_sound_t));
  snd->audioport = audioport;

  direct_sound = audioport_get_direct_sound(audioport);
  
  /* Initalize de format structure for CD quality audio: 2 channels,
   * 44100 Hz sample rate, 16 bits per sample. */

  ZeroMemory(&format, sizeof(WAVEFORMATEX));
  format.wFormatTag = WAVE_FORMAT_PCM;
  format.nChannels = 2;
  format.nSamplesPerSec = 44100;
  format.wBitsPerSample = 16;
  format.nBlockAlign = format.nChannels * format.wBitsPerSample / 8;
  format.nAvgBytesPerSec = format.nSamplesPerSec * format.nBlockAlign;
  format.cbSize = sizeof(WAVEFORMATEX);


  /* Initialize the description of the secondary buffer. */
  ZeroMemory(&desc, sizeof(DSBUFFERDESC));
  desc.dwSize = sizeof(DSBUFFERDESC);

  desc.dwFlags = 0;

  
  /* DSBCAPS_GLOBALFOCUS specifies that the buffer continues playing,
   *  even when the application is in the background.*/
  desc.dwFlags |= DSBCAPS_GLOBALFOCUS;

  /* Try to allocate the buffer in hardware, if possible. Otherwise,
     allocate it in RAM memory. */
  desc.dwFlags |= DSBCAPS_STATIC;

  if (caps.dwFreeHwMixingStreamingBuffers > 0) {
    desc.dwFlags |= DSBCAPS_LOCHARDWARE;
  }

  desc.lpwfxFormat = &format;
  desc.dwBufferBytes = size * format.nBlockAlign;
  desc.dwReserved = 0;


  /* Create the secondary buffer. */
  hr = IDirectSound_CreateSoundBuffer(direct_sound, &desc,
				      &snd->buffer, NULL);
  if (hr != DS_OK) {
    /* Handle error */
  }

  
  /* Lock the buffer to have write access to it. */
  IDirectSoundBuffer_Lock(snd->buffer, 0, desc.dwBufferBytes, &buf, 
			  &bytes, NULL, NULL, 0);
  
  /* Copy the samples to the buffer. */
  memcpy(buf, sample_data, desc.dwBufferBytes);

  /* Unlock the buffer again. */
  IDirectSoundBuffer_Unlock(snd->buffer, 0, desc.dwBufferBytes, &buf, 
			    &bytes, NULL, NULL, 0);

  return snd;
}


void delete_static_sound(static_sound_t* snd)
{
  IDirectSoundBuffer_Stop(snd->buffer);
  IDirectSoundBuffer_Release(snd->buffer);
  free(snd);
}


void static_sound_play(static_sound_t* snd)
{
  IDirectSoundBuffer_SetCurrentPosition(snd->buffer, 0);
  IDirectSoundBuffer_Play(snd->buffer, 0, 0, 0);
}


void static_sound_stop(static_sound_t* snd)
{
  IDirectSoundBuffer_Stop(snd->buffer);
}


Techniques de synchronisation

- EvÈnements de notification : l'interface DirectSoundNotify

- Position d'Ècriture : fonction GetPosition



EvÈnements de notification

Fonctionne seulement sur des buffers software.

- IDirectSoundBuffer::QueryInterface: rÈcupÈrer l'interface DirectSoundNotify

- CrÈer DSBPOSITIONNOTIFY; crÈer ÈvÈnements CreateEvent

- IDirectSoundNotify8::SetNotificationPositions: placer des ÈvÈnement ý des endroits prÈcis



Position d'Ècriture

GetPosition() renvoie la position de la tÍte de lecture et la position ý laquelle un peut Ècrire les Èchantillons.



Buffers dynamiques

Résumé

- Création des buffers sécondaires

- Jouer le buffer en boucle (Play(DSBPLAY_LOOPING))

- Utiliser les ÈvÈnements de notification ou GetPosition() pour la synchronisation

- Lock et Unlock du buffer pour y accÈder et Ècrire les Èchantillons

Utilisation de 3D Sound



Concepts

- L'auditeur

- Les buffers 3D


cfr. OpenAL

- L'auditeur

- Les buffers

- Les sources



Résumé

- Création de l'auditeur

- Création des buffers sécondaires

- Création des interfaces spécialisées pour le 3D

Création de l'auditeur



Résumé

- Création du buffer primaire

- Récuperer l'interface de l'auditeur

- Spécifier les paramètres 3D'



Création du buffer primaire

Voir plus haut. Il faut créer le buffer avec l'option DSBCAPS_3D. Allouér le buffer sur la carte (hardware buffer).



Créer l'interface de l'auditeur

Récupérer l'interface IDirectSound3DListener du buffer primaire.



LPDIRECTSOUNDBUFFER 
audioport_get_primary_buffer(audioport_t* audioport)
{
  return audioport->primary_buffer;
}


typedef struct {
  LPDIRECTSOUNDLISTENER listener;
} listener_t;


listener_t* new_listener(audioport_t* audioport)
{
  HRESULT hr;
  LPDIRECTSOUNDBUFFER primary_buffer;
  listener_t* l;

  /* Allocate a new lister_t object. */
  l = (listener_t*) malloc(sizeof(listener_t));
  if (l == NULL) {
    /* Handle error */
  }

  /* Obtain the primary buffer. */
  primary_buffer = audioport_get_primary_buffer(audioport);

  /* Get listener interface. The listener interface is a special
   * interface of the primary buffer. */
  hr = IDirectSoundBuffer_QueryInterface(primary_buffer, 
					 IID_IDirectSound3DListener,
					 (LPVOID *)&lp3DListener);
  if (hr != DS_OK) {
    /* Handle error */
  }

  return l;
}



Spécifier les paramètres 3D de l'auditeur

- Position, orientation et vélocité

- Facteur de distance

- Facteur Doppler

- Facteur "rolloff"



Position

GetPosition, SetPosition

Vélocité

GetVelocity, SetVelocity

Orientation

GetOrientation, SetOrientation

Facteur de distance

Mètres par unité de vecteur, GetDistanceFactor, SetDistanceFactor

Facteur Doppler

GetDopplerFactor, SetDopplerFactor

Facteur "rolloff"

Attenuation due à la distance, GetRolloffFactor, SetRolloffFactor

Création des buffers 3D



Résumé

- Création du buffer secondaire

- Récuperer l'interface 3D du buffer

- Spécifier les paramètres 3D



Création du buffer sécondaire

Pour chaque source sonore, il faut créer un buffer sécondaire. Il faut spécifier l'option DSBCAPS_3D à la création. De préférence allouer le buffer sur la carte. Ne pas utiliser DSBCAPS_PAN.


On peut spécifier l'algorithme 3D à utiliser. Cela à surtout une importance quand le buffer est alloué en "software" (DS3DALG_NO_VIRTUALIZATION, DS3DALG_HRTF_FULL, DS3DALG_HRTF_LIGHT).



Récuperer l'interface 3D du buffer

- Créer un buffer sécondaire avec l'option DSBCAPS_3D.

- Récuperer l'interface 3D avec la function QueryInterface.






typedef struct {
  audioport_t* audioport;
  LPDIRECTSOUNDBUFFER buffer;
  LPDIRECTSOUND3DBUFFER buffer_3d;
} static_sound_t;


static_sound_t* 
new_static_sound(audioport_t* audioport, int size, short* sample_data)
{
  /* Create secondary buffer  */
  /* ... */

  hr = IDirectSoundBuffer_QueryInterface(snd->buffer, (LPVOID*) &snd->buffer_3d)

}



Spécifier les paramètres 3D

- Position et vélocité

- Distance minimale et maximale

- Cones

- Mode



Position et vélocité

SetPosition, GetPosition, SetVelocity, GetVelocity

Distance minimale et maximale

SetMaxDistance, SetMinDistance, GetMaxDistance, GetMinDistance

Quand le buffer a été créé avec l'option DSBCAPS_MUTE3DATMAXDISTANCE, le buffer est désactivé quand la distance maximale est atteinte (gain de CPU).


Cones

- Orientation

- Angles

- Volume externe


Mode

- DS3DMODE_DISABLE : pas de 3D

- DS3DMODE_HEADRELATIVE : position et velocité relatives à l'auditeur

- DS3DMODE_NORMAL : position et velocité absolues



Examples

- jMax

- NeL

- MusicSpace



DirectSound 8.1

- DirectMusic : DirectMusic est devenu beaucoup plus important et englobe DirectSound. Selon Microsoft, c'est l'API conseillé.

- Différences dans la nomenclature : Toutes les interface ont un suffix de "8". Les anciens noms continuent de fonctionner.

- Les effets : Reverbe, chorus, ...

DLS2 : Downloadable sounds

Autres

- Band

- Chordmaps

- Transition