Cet article a fait l'objet d'une traduction automatique.

Facteur DirectX

Génération audio Windows 8 avec XAudio2

Charles Petzold

Télécharger l'exemple de code

Charles PetzoldUn app Store de Windows pour Windows 8 peut jouer MP3 ou son WMA fichiers facilement à l'aide de MediaElement, vous simplement lui donner un URI ou un flux de données vers le fichier audio. Windows Store apps peuvent également accéder à la jouer à l'API pour le streaming vidéo ou audio à des appareils externes.

Mais que se passe-t-il si vous avez besoin plus sophistiqués de traitement audio ? Peut-être que vous souhaitez modifier le contenu d'un fichier audio sur son chemin vers le matériel, ou de générer dynamiquement des sons.

Un app Store Windows peut également exécuter ces travaux par l'intermédiaire de DirectX. Windows 8 prend en charge les deux composants de DirectX pour génération sonore et le traitement — Core Audio et XAudio2. Dans le grand schéma des choses, ces deux sont des interfaces plutôt faible, mais Core Audio est inférieur à XAudio2 et est plus destiné aux applications nécessitant un lien plus étroit avec le matériel audio.

XAudio2 est le successeur de DirectSound et la Bibliothèque Xbox XAudio, et c'est probablement votre meilleur pari pour des apps de magasin de Windows qu'il faut faire des choses intéressantes avec le son. Alors que XAudio2 est principalement destiné aux jeux, qui ne devrait pas nous empêcher de l'utiliser à des fins plus graves, comme la musique ou recevoir l'utilisateur professionnel avec sons drôles.

XAudio2 version 2.8 est un composant intégré de Windows 8. Comme le reste de DirectX, l'interface de programmation XAudio2 est basé sur COM. Bien qu'il soit théoriquement possible d'accès XAudio2 de n'importe quel langage de programmation pris en charge par Windows 8, le langage plus naturel et plus facile pour XAudio2 est C++. Travail avec son souvent nécessite haute performance, code C++ est un bon choix à cet égard aussi bien.

Un premier programme de XAudio2

Commençons à écrire un programme qui utilise XAudio2 pour jouer des sons en réponse à la simple pression d'un bouton simple 5 seconde. Parce que vous pourriez être nouveau sur Windows 8 et DirectX de programmation, je vais prendre un peu lent.

Je supposerai que vous avez une version de Visual Studio est installé qui est appropriée pour créer des applications Windows Store. Dans la boîte de dialogue nouveau projet, sélectionnez Visual C++ et Windows Store à gauche et App vide (XAML) dans la liste des modèles disponibles. J'ai donné mon projet le nom SimpleAudio, et vous pouvez trouver ce projet parmi le code téléchargeable pour cet article.

Dans la construction d'un fichier exécutable qui utilise XAudio2, vous devez lier le programme avec la bibliothèque d'importation xaudio2.lib. Pour faire apparaître la boîte de dialogue Propriétés en sélectionnant le dernier élément dans le menu projet, ou par un clic droit sur le nom du projet dans l'Explorateur de solutions et en sélectionnant Propriétés. Dans la colonne de gauche, sélectionnez Propriétés de Configuration et puis l'éditeur de liens et entrée. Cliquez sur Additional Dependencies (l'élément supérieur) et la petite flèche. Sélectionnez modifier et tapez xaudio2.lib dans la zone.

Vous souhaitez également une référence au fichier d'en-tête xaudio2.h, ajoutez la déclaration suivante à la liste d'en-têtes précompilés dans pch.h :

#include <xaudio2.h>

Dans le fichier MainPage.xaml, j'ai ajouté un TextBlock pour afficher toutes les erreurs que le programme aient du travail avec l'API XAudio2 et un bouton pour la lecture d'un son. Celles-ci sont illustrées dans la figure 1.

Figure 1 le fichier MainPage.xaml pour SimpleAudio

<Page x:Class="SimpleAudio.MainPage" ...
>
  <Grid Background="{StaticResource ApplicationPageBackgroundThemeBrush}">
    <TextBlock Name="errorText"
               FontSize="24"
               TextWrapping="Wrap"
               HorizontalAlignment="Center"
               VerticalAlignment="Center" />
    <Button Name="submitButton"
            Content="Submit Audio Button"
            Visibility="Collapsed"
            HorizontalAlignment="Center"
            VerticalAlignment="Center"
            Click="OnSubmitButtonClick" />
  </Grid>
</Page>

La majeure partie du fichier d'en-tête MainPage.xaml.h montre Figure 2. J'ai enlevé la déclaration de la méthode OnNavigatedTo parce que je ne l'utiliser. Le gestionnaire Click du bouton est déclaré, comme le sont les quatre domaines liés à l'utilisation du programme de XAudio2.

Figure 2 le fichier d'en-tête MainPage.xaml.h pour SimpleAudio

namespace SimpleAudio
{
  public ref class MainPage sealed
  {
  private:
    Microsoft::WRL::ComPtr<IXAudio2> pXAudio2;
    IXAudio2MasteringVoice * pMasteringVoice;
    IXAudio2SourceVoice * pSourceVoice;
    byte soundData[2 * 5 * 44100];
  public:
    MainPage();
  private:
    void OnSubmitButtonClick(Platform::Object^ sender,
      Windows::UI::Xaml::RoutedEventArgs^ args);
  };
}

N'importe quel programme qui veut se prévaloir de XAudio2 devez créer un objet qui implémente l'interface IXAudio2. (Vous verrez comment cela se fait peu de temps). IXAudio2 dérive de la classe célèbre de IUnknown dans COM, et il est intrinsèquement contenant des références, ce qui signifie qu'il supprime ses propres ressources lorsqu'il n'est plus référencé par un programme. La classe ComPtr (COM pointeur) dans l'espace de noms Microsoft::WRL transforme un pointeur vers un objet COM en un pointeur « intelligent » qui assure le suivi de ses propres références. Il s'agit de l'approche recommandée pour l'utilisation d'objets COM dans une application Windows Store.

Tout programme non trivial de XAudio2 doit également des pointeurs vers des objets qui implémentent les interfaces IXAudio2MasteringVoice et IXaudio2SourceVoice. Dans le jargon XAudio2, une « voix » est un objet qui génère ou modifie des données audio. La voix de mastering est conceptuellement un mixeur qui rassemble toutes les voix individuelles et les prépare pour le matériel sonore. Vous aurez seulement l'un d'eux, mais vous pouvez avoir un nombre de voix de source générant des sons distincts. (Il ya aussi des moyens d'appliquer des filtres ou des effets à la voix de la source.)

Les pointeurs de IXAudio2MasterVoice et IXAudio2SourceVoice ne sont pas contenant des références ; leurs vies sont régies par l'objet IXAudio2.

J'ai aussi inclus un grand tableau pour 5 secondes une valeur de données audio :

byte soundData[5 * 2 * 44100];

Dans un programme réel, vous aurez envie d'allouer un tableau de cette taille au moment de l'exécution et s'en débarrasser lorsque vous n'avez pas besoin, mais vous verrez bientôt pourquoi je l'ai fait comme ça.

Comment a-t-il calculé la taille de ce tableau ? XAudio2 prend en charge l'audio compressé, la plupart des programmes qui génèrent des sons seront en tiendra au format appelée pulse-code modulation PCM. Formes d'onde audio en PCM sont représentés par les valeurs d'une taille fixe à un taux d'échantillonnage fixe. Pour la musique sur disques compacts, la fréquence d'échantillonnage est 44 100 fois par seconde, avec 2 octets par échantillon en stéréo, pour un total de 176 400 octets de données pour 1 seconde de son. (Lors de l'incorporation de sons dans une application, compression est recommandée. XAudio2 prend en charge ADPCM ; MP3 et WMA sont également supportées dans le moteur de Media Foundation.)

Pour ce programme, j'ai également choisi d'utiliser une fréquence d'échantillonnage de 44 100 avec 2 octets par échantillon. En C++, chaque échantillon est donc un peu. Je vais rester avec son monaural pour l'instant, donc 88 200 octets sont nécessaires par seconde de son. Dans l'allocation de tableau, qui est multipliée par 5 pendant 5 secondes.

Création d'objets

Une grande partie du fichier MainPage.xaml.cpp apparaît dans Figure 3. Tous de l'initialisation de XAudio2 est effectuée dans le constructeur MainPage. Elle commence par un appel à XAudio2Create pour obtenir un pointeur vers un objet qui implémente l'interface IXAudio2. Il s'agit de la première étape en utilisant XAudio2. Contrairement à certaines interfaces COM, aucun appel à CoCreateInstance n'est requis.

Figure 3 MainPage.xaml.cpp

MainPage::MainPage()
{
  InitializeComponent();
  // Create an IXAudio2 object
  HRESULT hr = XAudio2Create(&pXAudio2);
  if (FAILED(hr))
  {
    errorText->Text = "XAudio2Create failure: " + hr.ToString();
    return;
  }
  // Create a mastering voice
  hr = pXAudio2->CreateMasteringVoice(&pMasteringVoice);
  if (FAILED(hr))
  {
    errorText->Text = "CreateMasteringVoice failure: " + hr.ToString();
    return;
  }
  // Create a source voice
  WAVEFORMATEX waveformat;
  waveformat.wFormatTag = WAVE_FORMAT_PCM;
  waveformat.
nChannels = 1;
  waveformat.
nSamplesPerSec = 44100;
  waveformat.
nAvgBytesPerSec = 44100 * 2;
  waveformat.
nBlockAlign = 2;
  waveformat.wBitsPerSample = 16;
  waveformat.cbSize = 0;
  hr = pXAudio2->CreateSourceVoice(&pSourceVoice, &waveformat);
  if (FAILED(hr))
  {
    errorText->Text = "CreateSourceVoice failure: " + hr.ToString();
    return;
  }
  // Start the source voice
  hr = pSourceVoice->Start();
  if (FAILED(hr))
  {
    errorText->Text = "Start failure: " + hr.ToString();
    return;
  }
  // Fill the array with sound data
  for (int index = 0, second = 0; second < 5; second++)
  {
    for (int cycle = 0; cycle < 441; cycle++)
    {
      for (int sample = 0; sample < 100; sample++)
      {
        short value = sample < 50 ?
32767 : -32768;
        soundData[index++] = value & 0xFF;
        soundData[index++] = (value >> 8) & 0xFF;
      }
    }
  }
  // Make the button visible
  submitButton->Visibility = Windows::UI::Xaml::Visibility::Visible;
}
void MainPage::OnSubmitButtonClick(Object^ sender, 
  RoutedEventArgs^ args)
{
  // Create a button to reference the byte array
  XAUDIO2_BUFFER buffer = { 0 };
  buffer.AudioBytes = 2 * 5 * 44100;
  buffer.pAudioData = soundData;
  buffer.Flags = XAUDIO2_END_OF_STREAM;
  buffer.PlayBegin = 0;
  buffer.PlayLength = 5 * 44100;
  // Submit the buffer
  HRESULT hr = pSourceVoice->SubmitSourceBuffer(&buffer);
  if (FAILED(hr))
  {
    errorText->Text = "SubmitSourceBuffer failure: " + hr.ToString();
    submitButton->Visibility = 
      Windows::UI::Xaml::Visibility::Collapsed;
    return;
  }
}

Une fois que l'objet IXAudio2 est créé, les méthodes CreateMasteringVoice et CreateSourceVoice obtiennent des pointeurs vers les deux autres interfaces définies sous forme de champs dans le fichier d'en-tête.

L'appel de CreateSourceVoice requiert une structure WAVEFORMATEX qui sera familier à toute personne qui a travaillé avec l'audio dans l'API Win32. Cette structure définit la nature des données audio que vous utiliserez pour cette voix particulière. (Voix de différentes sources permet différents formats). Pour PCM, seulement trois numéros sont vraiment pertinentes : le taux d'échantillonnage (44 100 dans cet exemple), la taille de chaque échantillon (2 octets ou 16 bits) et le nombre de canaux (1 ici). Les autres champs sont basés sur ceux-ci : Le champ de laquelle nBlockAlign est nChannels fois wBitsPerSample divisé par 8, et nAvgBytesPerSec champ est le produit de nSamplesPerSec et de laquelle nBlockAlign. Mais dans cet exemple, j'ai montré tous les champs avec des valeurs explicites.

Une fois l'objet IXAudio2SourceVoice est obtenue, la méthode Start peut être appelée sur elle. À ce stade, XAudio2 joue sur le plan conceptuel, mais nous n'avons pas réellement donné les données audio à jouer. Une méthode d'arrêt est également disponible, et un vrai programme utiliserait ces deux méthodes pour contrôler quelle sons devraient et ne devrait pas être jouer.

Tous les quatre de ces appels ne sont pas aussi simples, tels qu'ils figurent dans le présent code ! Ils ont tous des arguments supplémentaires, mais commodes paramètres par défaut sont définies et j'ai simplement choisi d'accepter les valeurs par défaut pour l'instant.

Pratiquement tous les appels de fonction et de méthode dans DirectX retournent des valeurs HRESULT pour indiquer la réussite ou l'échec. Il existe différentes stratégies pour faire face à ces erreurs. J'ai choisi simplement pour afficher le code d'erreur à l'aide de TextBlock défini dans le fichier XAML et arrêt d'un traitement ultérieur.

Données Audio PCM

Le constructeur conclut en remplissant le tableau soundData avec des données audio, mais le tableau n'est pas réellement fourchu au-dessus à la IXAudio2SourceVoice jusqu'à ce que le bouton est enfoncé.

Le son est vibration, et les humains sont sensibles aux vibrations dans l'air à peu près dans la gamme de 20 Hz (ou cycles par seconde) à 20 000 Hz. C moyen au piano est environ de 261,6 Hz.

Supposons que vous travaillez avec une fréquence d'échantillonnage de 44 100 Hz et des échantillonnages de 16 bits et que vous souhaitez générer des données audio pour un signal à une fréquence de 4 410 Hz, ce qui est juste après la clé plus élevée sur un piano. Chaque cycle d'une telle forme d'onde nécessite 10 échantillons de valeurs de 16 bits signées. Ces 10 valeurs seraient répétera 4 410 fois pour chaque seconde de son.

Une forme d'onde à une fréquence de 441 Hz — très proche de 440 Hz, correspondant au A ci-dessus C moyen utilisé en accordage standard — est restitué avec 100 échantillons. Ce cycle se répétera 441 fois pour chaque seconde de son.

Parce que PCM implique un taux d'échantillonnage constant, sons de basse fréquence semblent être échantillonnés et rendue plus une résolution beaucoup plus élevée que les sons de haute fréquence. N'est-ce un problème ? Une forme d'onde 4 410 Hz rendu avec seulement 10 échantillons n'est pas une quantité considérable de distorsion par rapport à la forme d'onde 441 Hz ?

Il s'avère que toute distorsion de quantification en PCM se produit à des fréquences supérieures à la moitié du taux d'échantillonnage. (Ceci est connu comme la fréquence de Nyquist après que Bell Labs ingénieur Harry Nyquist.) Une des raisons pour qu'une fréquence d'échantillonnage de 44 100 Hz a été choisie pour le CD audio sont la fréquence de Nyquist est de 22 050 Hz que l'ouïe humaine plafonne à environ 20 000 Hz. En d'autres termes, à une fréquence d'échantillonnage de 44 100 Hz, la distorsion de quantification est inaudible pour l'homme.

Le programme SimpleAudio génère une onde algorithmiquement simple — une onde carrée avec une fréquence de 441 Hz. Il y a 100 échantillons par cycle. Dans chaque cycle, les 50 premiers sont des valeurs positives maximales (32 767 lorsqu'ils traitent avec des entiers courts) et les 50 prochains sont des valeurs négatives maximales (-32,768). Notez que ces valeurs courtes doivent être stockés dans le tableau d'octets avec l'octet de poids faible d'abord :

soundData[index + 0] = value & 0xFF;
soundData[index + 1] = (value >> 8) & 0xFF;

Jusqu'à présent, rien n'a réellement joué. Cela se produit dans le gestionnaire Click du bouton. La structure XAUDIO2_BUFFER est utilisée pour référencer le tableau d'octets avec un décompte des octets et une durée spécifiée en tant que le nombre d'échantillons. Ce tampon est passé à la méthode SubmitSourceBuffer de l'objet IXAudio2SourceVoice. Si la méthode de démarrage a déjà été appelée (comme dans cet exemple) puis le son commence à jouer immédiatement.

Je soupçonne que je n'ai pas de mentionner que le son joue de façon asynchrone. L'appel de SubmitSourceBuffer retourne immédiatement alors qu'un thread séparé est consacré au processus réel de pelleter les données pour le matériel audio. Le XAUDIO2_BUFFER passé à SubmitSourceBuffer peut être mis au rebut après l'appel — comme c'est dans ce programme lorsque le gestionnaire de clic est libéré et que la variable locale est hors de portée, mais le tableau réel d'octets doit rester dans la mémoire accessible. En effet, votre programme peut manipuler ces octets car le son est joué. Cependant, il sont a beaucoup plus techniques (portant sur les méthodes de rappel) qui permettent à votre programme génère dynamiquement des données audio.

Sans utiliser un rappel pour déterminer quand le son est terminé, ce programme doit conserver le tableau soundData pour la durée du programme.

Vous pouvez appuyer sur le bouton plusieurs fois, et chaque appel en file d'attente efficacement une autre mémoire tampon à jouer lorsque le tampon précédente se termine. Si le programme se dirige vers le fond, le son est désactivé, mais il continue de jouer dans le silence. En d'autres termes, si vous cliquez sur le bouton et déplacez le programme à l'arrière-plan pendant au moins 5 secondes, rien ne jouera lorsque le programme revient au premier plan.

Les caractéristiques du son

Une grande partie du son que nous entendons dans la vie quotidienne vient simultanément une variété de sources différentes et est donc assez complexe. Toutefois, dans certains cas — et particulièrement lorsqu'il s'agit de sons musicaux — tons individuels peuvent être définis avec quelques caractéristiques :

  • Amplitude, qui est interprété par nos sens dans le volume.
  • Fréquence, qui est interprétée comme de la poix.
  • Espace, qui peut être reproduite en lecture audio avec plusieurs haut-parleurs.
  • Timbre, qui est liée à la combinaison d'harmoniques dans un son et représente la différence perçue entre la trompette et le piano, par exemple.

Le projet SoundCharacteristics illustre ces quatre caractéristiques dans l'isolement. Il maintient les taux d'échantillonnage et les échantillons 16-bit du projet SimpleAudio 44 100 mais émet le son en stéréo. Pour les deux canaux du son, les données doivent être entrelacées : un échantillon de 16 bits pour le canal gauche, suivi d'un échantillon de 16 bits pour le canal droit.

Le fichier d'en-tête MainPage.xaml.h SoundCharacteristics définit certaines constantes :

static const int sampleRate = 44100;
static const int seconds = 5;
static const int channels = 2;
static const int samples = seconds * sampleRate;

Il définit également les quatre tableaux de données audio, mais il s'agit du type court plutôt qu'octet :

short volumeSoundData[samples * channels];
short pitchSoundData[samples * channels];
short spaceSoundData[samples * channels];
short timbreSoundData[samples * channels];

L'utilisation de tableaux courts facilite l'initialisation parce que les valeurs 16 bits signaux n'ont pas besoin d'être cassé en deux. Une distribution simple autorise le tableau d'être référencé par les XAUDIO2_BUFFER lors de la soumission des données audio. Ces tableaux ont doubler le nombre d'octets dans le tableau dans SimpleAudio parce que je suis en utilisant stéréo dans ce programme.

Tous les quatre de ces tableaux sont initialisés dans le constructeur MainPage. Pour la démonstration de volume, une onde carrée de 441 Hz est toujours impliquée, mais il démarre à zéro volume, devient progressivement plus fort pendant les 2 premières secondes et puis diminue en volume sur les 2 dernières secondes. Figure 4 affiche le code d'initialisation de volumeSoundData.

Figure 4 données sonores qui change de Volume pour SoundCharacteristics

for (int index = 0, sample = 0; sample < samples; sample++)
{
  double t = 1;
  if (sample < 2 * samples / 5)
    t = sample / (2.0 * samples / 5);
  else if (sample > 3 * samples / 5)
    t = (samples - sample) / (2.0 * samples / 5);
  double amplitude = pow(2, 15 * t) - 1;
  short waveform = sample % 100 < 50 ?
1 : -1;
  short value = short(amplitude * waveform);
  volumeSoundData[index++] = value;
  volumeSoundData[index++] = value;
}

La perception humaine du volume est logarithmique : Chaque doublement de l'amplitude d'une onde équivaut à une augmentation de 6 dB en volume. (L'amplitude de 16-bit utilisé pour les CD audio a une plage dynamique de 96 décibels.) Le code de Figure 4 de modifier le volume d'abord calcule une valeur de t, ce qui augmente de façon linéaire de 0 à 1, et puis diminue de nouveau à 0. La variable amplitude est calculée à l'aide de la fonction pow et varie entre 0 et 32 767. Cette valeur est multipliée par une onde carrée qui a les valeurs 1 et -1. Le résultat est ajouté au tableau : tout d'abord pour le canal gauche, puis pour le canal droit.

La perception humaine des fréquences est logarithmique. Une grande partie de la musique du monde organise pas autour de l'intervalle d'une octave, ce qui est un doublement de fréquence. Les deux premières notes du refrain de « Somewhere over the Rainbow » sont un saut d'octave, si elle est chantée par une basse ou une voix de soprano. Figure 5 montre le code qui fait varier le pas dans la gamme de deux octaves : de 220 à 880 basée sur une valeur de t (comme dans l'exemple de volume) va de 0 à 1, puis de nouveau vers le bas à 0.

Figure 5 les données audio qui change de fréquence pour SoundCharacteristics

double angle = 0;
for (int index = 0, sample = 0; sample < samples; sample++)
{
  double t = 1;
  if (sample < 2 * samples / 5)
    t = sample / (2.0 * samples / 5);
  else if (sample > 3 * samples / 5)
    t = (samples - sample) / (2.0 * samples / 5);
  double frequency = 220 * pow(2, 2 * t);
  double angleIncrement = 360 * frequency / waveformat.
nSamplesPerSec;
  angle += angleIncrement;
  while (angle > 360)
    angle -= 360;
  short value = angle < 180 ?
32767 : -32767;
  pitchSoundData[index++] = value;
  pitchSoundData[index++] = value;
}

Dans les exemples précédents, j'ai choisi une fréquence de 441 Hz car il divise correctement le taux d'échantillonnage de 44 100. Dans le cas général, le taux d'échantillonnage n'est pas un multiple entier de la fréquence, et par conséquent il ne peut pas être un nombre entier d'échantillons par cycle. Au lieu de cela, ce programme tient à jour une variable angleIncrement point flottant qui est proportionnelle à la fréquence et utilisé pour augmenter une valeur d'angle comprise entre 0 et 360. Cette valeur d'angle est ensuite utilisée pour construire la forme d'onde.

La démonstration de l'espace déplace le son du Centre pour le canal gauche, puis vers la droite, puis vers le centre. Pour la démo de timbre, l'onde commence par une courbe sinusoïdale à 441 Hz. Une courbe sinusoïdale est la représentation mathématique du type plus fondamental de vibration — la solution de l'équation différentielle où la force est inversement proportionnelle au déplacement. Toutes les autres formes d'onde périodiques contiennent des harmoniques, qui sont aussi des ondes sinusoïdales, mais avec des fréquences multiples intégraux de la fréquence de base. La démo de timbre change la forme d'onde en douceur d'une onde sinusoïdale d'une onde d'une onde carrée, une onde en dents de scie, triangle tout en augmentant le contenu harmonique du son.

L'image plus grande

Bien que j'ai vient de nous démontrer comment vous pouvez contrôler le volume, pitch, espace et le timbre en générant des données audio pour un objet IXAudio2SourceVoice, l'objet lui-même inclut des méthodes permettant de modifier le volume et l'espace et même la fréquence. (Une installation spatiale « 3D » est également pris en charge.) Bien qu'il soit possible de générer des données audio composites qui combine un tas de différents tons avec une voix d'une source unique, vous pouvez créer plusieurs objets IXAudio2SourceVoice et lisez-les tous ensemble par le biais de la voix même de mastering.

En outre, XAudio2 définit un IXAudioSubmixVoice qui permet de définir des filtres et autres effets comme la réverbération ou écho. Les filtres ont la possibilité de changer le timbre des sons existants dynamiquement, qui peut contribuer grandement à la création de sons musicaux intéressantes et réalistes.

Peut-être la plus essentielle amélioration au-delà de ce que j'ai montré dans ces deux programmes requiert travail avec fonctions de rappel XAudio2. Au lieu d'affectation et l'initialisation de gros morceaux de données audio comme le font ces deux programmes, il est beaucoup plus logique d'un programme pour générer dynamiquement des données, car il est en cours de lecture.

Charles Petzold est un contributeur de longue date à MSDN Magazine et l'auteur de « La programmation Windows, 6e édition » (o ' Reilly Media, 2012), un livre sur l'écriture d'applications pour Windows 8. Son site Web est charlespetzold.com.

Merci à l'expert technique suivant d'avoir relu cet article : Scott Selfon