Freigeben über


Hinzufügen von Sound

Hinweis

Dieses Thema ist Teil der Erstellen eines einfachen UWP-Spiels (Universelle Windows-Plattform) mit DirectX-Tutorial-Reihe. Das Thema unter diesem Link legt den Kontext für die Reihe fest.

In diesem Thema erstellen wir ein einfaches Soundmodul mit XAudio2-APIs . Wenn Sie noch nicht mit XAudio2 vertraut sind, haben wir unter den Audiokonzepten eine kurze Einführung aufgenommen.

Hinweis

Wenn Sie den neuesten Spielcode für dieses Beispiel nicht heruntergeladen haben, gehen Sie zu Direct3D-Beispielspiel. Dieses Beispiel ist Teil einer großen Sammlung von UWP-Featurebeispielen. Anweisungen zum Herunterladen des Beispiels finden Sie unter Beispielanwendungen für die Windows-Entwicklung.

Ziel

Fügen Sie dem Beispielspiel Sounds mit XAudio2 hinzu.

Definieren des Audiomoduls

Im Beispielspiel werden die Audioobjekte und -verhaltensweisen in drei Dateien definiert:

  • Audio.h/.cpp: Definiert das Audioobjekt, das die XAudio2-Ressourcen für die Soundwiedergabe enthält. Außerdem wird die Methode zum Anhalten und Fortsetzen der Audiowiedergabe definiert, wenn das Spiel angehalten oder deaktiviert wird.
  • MediaReader.h/.cpp: Definiert die Methoden zum Lesen von Audio- .wav Dateien aus dem lokalen Speicher.
  • SoundEffect.h/.cpp: Definiert ein Objekt für die In-Game-Soundwiedergabe.

Übersicht

Es gibt drei Hauptkomponenten für die Einrichtung der Audiowiedergabe in Ihrem Spiel.

  1. Erstellen und Initialisieren der Audioressourcen
  2. Laden einer Audiodatei
  3. Zuordnen von Sound zu Objekt

Sie sind alle in der Simple3DGame::Initialize-Methode definiert. Lassen Sie uns also zunächst diese Methode untersuchen und dann in den einzelnen Abschnitten ausführlichere Details einsehen.

Nach dem Einrichten erfahren Wir, wie die Soundeffekte ausgelöst werden, um wiedergegeben zu werden. Weitere Informationen finden Sie unter " Sound wiedergeben".

Simple3DGame::Initialize-Methode

In Simple3DGame::Initialize, wo m_controller und m_renderer ebenfalls initialisiert werden, richten wir das Audiomodul ein und bereiten es bereit, Sounds wiederzugeben.

  • Erstellen Sie m_audioController, bei dem es sich um eine Instanz der Audioklasse handelt.
  • Erstellen Sie die erforderlichen Audioressourcen mithilfe der Methode "Audio::CreateDeviceIndependentResources ". Hier wurden zwei XAudio2-Objekte – ein Musikmodulobjekt und ein Soundmodulobjekt– und eine Masterstimme für jedes von ihnen erstellt. Das Musikmodulobjekt kann verwendet werden, um Hintergrundmusik für Ihr Spiel wiederzugeben. Die Sound-Engine kann verwendet werden, um Soundeffekte in Ihrem Spiel wiederzugeben. Weitere Informationen finden Sie unter Erstellen und Initialisieren der Audioressourcen.
  • Erstellen Sie "mediaReader", bei dem es sich um eine Instanz der MediaReader-Klasse handelt. MediaReader, eine Hilfsklasse für die SoundEffect-Klasse , liest kleine Audiodateien synchron vom Dateispeicherort aus und gibt Sounddaten als Bytearray zurück.
  • Verwenden Sie MediaReader::LoadMedia , um Sounddateien von seinem Speicherort zu laden und eine TargetHitSound-Variable zu erstellen, um die geladenen .wav Sounddaten zu speichern. Weitere Informationen finden Sie unter "Laden der Audiodatei".

Soundeffekte sind dem Spielobjekt zugeordnet. Wenn also eine Kollision mit diesem Spielobjekt auftritt, löst es den Soundeffekt aus, der wiedergegeben wird. In diesem Beispielspiel haben wir Soundeffekte für die Munition (was wir zum Schießen von Zielen verwenden) und für das Ziel.

  • In der GameObject-Klasse gibt es eine HitSound-Eigenschaft , die verwendet wird, um den Soundeffekt dem Objekt zuzuordnen.
  • Erstellen Sie eine neue Instanz der SoundEffect-Klasse , und initialisieren Sie sie. Während der Initialisierung wird eine Quellstimme für den Soundeffekt erstellt.
  • Diese Klasse gibt einen Sound mit einer Masterstimme aus der Audioklasse wieder. Sounddaten werden mithilfe der MediaReader-Klasse aus dem Dateispeicherort gelesen. Weitere Informationen finden Sie unter "Zuordnen von Sound zu Objekt".

Hinweis

Der tatsächliche Trigger für die Wiedergabe des Sounds wird durch die Bewegung und Kollision dieser Spielobjekte bestimmt. Daher wird der Aufruf, diese Sounds tatsächlich wiederzugeben, in der Simple3DGame::UpdateDynamics-Methode definiert. Weitere Informationen finden Sie unter " Sound wiedergeben".

void Simple3DGame::Initialize(
    _In_ std::shared_ptr<MoveLookController> const& controller,
    _In_ std::shared_ptr<GameRenderer> const& renderer
    )
{
    // The following member is defined in the header file:
    // Audio m_audioController;

    ...

    // Create the audio resources needed.
    // Two XAudio2 objects are created - one for music engine,
    // the other for sound engine. A mastering voice is also
    // created for each of the objects.
    m_audioController.CreateDeviceIndependentResources();

    m_ammo.resize(GameConstants::MaxAmmo);

    ...

    // Create a media reader which is used to read audio files from its file location.
    MediaReader mediaReader;
    auto targetHitSoundX = mediaReader.LoadMedia(L"Assets\\hit.wav");

    // Instantiate the targets for use in the game.
    // Each target has a different initial position, size, and orientation.
    // But share a common set of material properties.
    for (int a = 1; a < GameConstants::MaxTargets; a++)
    {
        ...
        // Create a new sound effect object and associate it
        // with the game object's (target) HitSound property.
        target->HitSound(std::make_shared<SoundEffect>());

        // Initialize the sound effect object with
        // the sound effect engine, format of the audio wave, and audio data
        // During initialization, source voice of this sound effect is also created.
        target->HitSound()->Initialize(
            m_audioController.SoundEffectEngine(),
            mediaReader.GetOutputWaveFormatEx(),
            targetHitSoundX
            );
        ...
    }

    // Instantiate a set of spheres to be used as ammunition for the game
    // and set the material properties of the spheres.
    auto ammoHitSound = mediaReader.LoadMedia(L"Assets\\bounce.wav");

    for (int a = 0; a < GameConstants::MaxAmmo; a++)
    {
        m_ammo[a] = std::make_shared<Sphere>();
        m_ammo[a]->Radius(GameConstants::AmmoRadius);
        m_ammo[a]->HitSound(std::make_shared<SoundEffect>());
        m_ammo[a]->HitSound()->Initialize(
            m_audioController.SoundEffectEngine(),
            mediaReader.GetOutputWaveFormatEx(),
            ammoHitSound
            );
        m_ammo[a]->Active(false);
        m_renderObjects.push_back(m_ammo[a]);
    }
    ...
}

Erstellen und Initialisieren der Audioressourcen

  • Verwenden Sie XAudio2Create, eine XAudio2-API, um zwei neue XAudio2-Objekte zu erstellen, die die Musik- und Soundeffektmodule definieren. Diese Methode gibt einen Zeiger auf die IXAudio2-Schnittstelle des Objekts zurück, die alle Audiomodulzustände, den Audioverarbeitungsthread, das VoIP-Diagramm und vieles mehr verwaltet.
  • Nachdem die Engines instanziiert wurden, verwenden Sie IXAudio2::CreateMasteringVoice , um eine Masterstimme für jedes der Soundmodulobjekte zu erstellen.

Weitere Informationen finden Sie unter "How to: Initialize XAudio2".

Audio::CreateDeviceIndependentResources-Methode

void Audio::CreateDeviceIndependentResources()
{
    UINT32 flags = 0;

    winrt::check_hresult(
        XAudio2Create(m_musicEngine.put(), flags)
        );

    HRESULT hr = m_musicEngine->CreateMasteringVoice(&m_musicMasteringVoice);
    if (FAILED(hr))
    {
        // Unable to create an audio device
        m_audioAvailable = false;
        return;
    }

    winrt::check_hresult(
        XAudio2Create(m_soundEffectEngine.put(), flags)
        );

    winrt::check_hresult(
        m_soundEffectEngine->CreateMasteringVoice(&m_soundEffectMasteringVoice)
        );

    m_audioAvailable = true;
}

Laden einer Audiodatei

Im Beispielspiel wird der Code zum Lesen von Audiodateien in MediaReader.h/cpp__ definiert. Um eine codierte .wav Audiodatei zu lesen, rufen Sie MediaReader::LoadMedia auf, und übergeben Sie den Dateinamen der .wav als Eingabeparameter.

MediaReader::LoadMedia-Methode

Diese Methode verwendet die Media Foundation-APIs , um in der .wav Audiodatei als Pulse Code Modulation (PCM)-Puffer zu lesen.

Einrichten des Quelllesers

  1. Verwenden Sie MFCreateSourceReaderFromURL , um einen Medienquellenleser (IMFSourceReader) zu erstellen.
  2. Verwenden Sie MFCreateMediaType, um ein Medientypobjekt (IMFMediaType) (mediaType) zu erstellen. Sie stellt eine Beschreibung eines Medienformats dar.
  3. Geben Sie an, dass die decodierte Ausgabe von mediaType PCM-Audio ist, bei dem es sich um einen Audiotyp handelt, den XAudio2 verwenden kann.
  4. Legt den decodierten Ausgabemedientyp für den Quellleser fest, indem IMFSourceReader::SetCurrentMediaType aufgerufen wird.

Weitere Informationen dazu, warum wir den Quellleser verwenden, finden Sie unter "Quellleser".

Beschreiben des Datenformats des Audiodatenstroms

  1. Verwenden Sie IMFSourceReader::GetCurrentMediaType , um den aktuellen Medientyp für den Datenstrom abzurufen.
  2. Verwenden Sie IMFMediaType::MFCreateWaveFormatExFromMFMediaType , um den aktuellen Audiomedientyp in einen WAVEFORMATEX-Puffer zu konvertieren, wobei die Ergebnisse des früheren Vorgangs als Eingabe verwendet werden. Diese Struktur gibt das Datenformat des Wellenaudiodatenstroms an, der nach dem Laden von Audio verwendet wird.

Das WAVEFORMATEX-Format kann verwendet werden, um den PCM-Puffer zu beschreiben. Im Vergleich zur WAVEFORMATEXTENSIBLE-Struktur kann sie nur verwendet werden, um eine Teilmenge von Audiowellenformaten zu beschreiben. Weitere Informationen zu den Unterschieden zwischen WAVEFORMATEX und WAVEFORMATEXTENSIBLE finden Sie unter Extensible Wave-Format Descriptors.

Lesen des Audiodatenstroms

  1. Rufen Sie die Dauer des Audiodatenstroms in Sekunden ab, indem Sie IMFSourceReader::GetPresentationAttribute aufrufen und dann die Dauer in Bytes konvertieren.
  2. Lesen Sie die Audiodatei als Datenstrom, indem Sie IMFSourceReader::ReadSample aufrufen. ReadSample liest das nächste Beispiel aus der Medienquelle.
  3. Verwenden Sie IMFSample::ConvertToContiguousBuffer , um Inhalte des Audiobeispielpuffers (Beispiel) in ein Array (mediaBuffer) zu kopieren.
std::vector<byte> MediaReader::LoadMedia(_In_ winrt::hstring const& filename)
{
    winrt::check_hresult(
        MFStartup(MF_VERSION)
        );

    // Creates a media source reader.
    winrt::com_ptr<IMFSourceReader> reader;
    winrt::check_hresult(
        MFCreateSourceReaderFromURL(
        (m_installedLocationPath + filename).c_str(),
            nullptr,
            reader.put()
            )
        );

    // Set the decoded output format as PCM.
    // XAudio2 on Windows can process PCM and ADPCM-encoded buffers.
    // When using MediaFoundation, this sample always decodes into PCM.
    winrt::com_ptr<IMFMediaType> mediaType;
    winrt::check_hresult(
        MFCreateMediaType(mediaType.put())
        );

    // Define the major category of the media as audio. For more info about major media types,
    // go to: https://msdn.microsoft.com/library/windows/desktop/aa367377.aspx
    winrt::check_hresult(
        mediaType->SetGUID(MF_MT_MAJOR_TYPE, MFMediaType_Audio)
        );

    // Define the sub-type of the media as uncompressed PCM audio. For more info about audio sub-types,
    // go to: https://msdn.microsoft.com/library/windows/desktop/aa372553.aspx
    winrt::check_hresult(
        mediaType->SetGUID(MF_MT_SUBTYPE, MFAudioFormat_PCM)
        );

    // Sets the media type for a stream. This media type defines that format that the Source Reader 
    // produces as output. It can differ from the native format provided by the media source.
    // For more info, go to https://msdn.microsoft.com/library/windows/desktop/dd374667.aspx
    winrt::check_hresult(
        reader->SetCurrentMediaType(static_cast<uint32_t>(MF_SOURCE_READER_FIRST_AUDIO_STREAM), 0, mediaType.get())
        );

    // Get the current media type for the stream.
    // For more info, go to:
    // https://msdn.microsoft.com/library/windows/desktop/dd374660.aspx
    winrt::com_ptr<IMFMediaType> outputMediaType;
    winrt::check_hresult(
        reader->GetCurrentMediaType(static_cast<uint32_t>(MF_SOURCE_READER_FIRST_AUDIO_STREAM), outputMediaType.put())
        );

    // Converts the current media type into the WaveFormatEx buffer structure.
    UINT32 size = 0;
    WAVEFORMATEX* waveFormat;
    winrt::check_hresult(
        MFCreateWaveFormatExFromMFMediaType(outputMediaType.get(), &waveFormat, &size)
        );

    // Copies the waveFormat's block of memory to the starting address of the m_waveFormat variable in MediaReader.
    // Then free the waveFormat memory block.
    // For more info, go to https://msdn.microsoft.com/library/windows/desktop/aa366535.aspx and
    // https://msdn.microsoft.com/library/windows/desktop/ms680722.aspx
    CopyMemory(&m_waveFormat, waveFormat, sizeof(m_waveFormat));
    CoTaskMemFree(waveFormat);

    PROPVARIANT propVariant;
    winrt::check_hresult(
        reader->GetPresentationAttribute(static_cast<uint32_t>(MF_SOURCE_READER_MEDIASOURCE), MF_PD_DURATION, &propVariant)
        );

    // 'duration' is in 100ns units; convert to seconds, and round up
    // to the nearest whole byte.
    LONGLONG duration = propVariant.uhVal.QuadPart;
    unsigned int maxStreamLengthInBytes =
        static_cast<unsigned int>(
            ((duration * static_cast<ULONGLONG>(m_waveFormat.nAvgBytesPerSec)) + 10000000) /
            10000000
            );

    std::vector<byte> fileData(maxStreamLengthInBytes);

    winrt::com_ptr<IMFSample> sample;
    winrt::com_ptr<IMFMediaBuffer> mediaBuffer;
    DWORD flags = 0;

    int positionInData = 0;
    bool done = false;
    while (!done)
    {
        // Read audio data.
        ...
    }

    return fileData;
}

Zuordnen von Sound zu Objekt

Das Zuordnen von Sounds zum Objekt erfolgt, wenn das Spiel initialisiert wird, in der Simple3DGame::Initialize-Methode .

Rekapitulation:

  • In der GameObject-Klasse gibt es eine HitSound-Eigenschaft , die verwendet wird, um den Soundeffekt dem Objekt zuzuordnen.
  • Erstellen Sie eine neue Instanz des SoundEffect-Klassenobjekts , und ordnen Sie es dem Spielobjekt zu. Diese Klasse gibt einen Sound mit XAudio2-APIs wieder. Es verwendet eine Masterstimme, die von der Audioklasse bereitgestellt wird. Die Sounddaten können mithilfe der MediaReader-Klasse vom Dateispeicherort gelesen werden.

SoundEffect::Initialize wird verwendet, um die SoundEffect-Instanz mit den folgenden Eingabeparametern zu initalisieren: Zeiger auf das Soundmodulobjekt (IXAudio2-Objekte, die in der Audio::CreateDeviceIndependentResources-Methode erstellt wurden), Zeiger zum Format der .wav-Datei mithilfe von MediaReader::GetOutputWaveFormatEx und die mit MediaReader::LoadMedia-Methode geladenen Sounddaten. Während der Initialisierung wird auch die Quellstimme für den Soundeffekt erstellt.

SoundEffect::Initialize-Methode

void SoundEffect::Initialize(
    _In_ IXAudio2* masteringEngine,
    _In_ WAVEFORMATEX* sourceFormat,
    _In_ std::vector<byte> const& soundData)
{
    m_soundData = soundData;

    if (masteringEngine == nullptr)
    {
        // Audio is not available so just return.
        m_audioAvailable = false;
        return;
    }

    // Create a source voice for this sound effect.
    winrt::check_hresult(
        masteringEngine->CreateSourceVoice(
            &m_sourceVoice,
            sourceFormat
            )
        );
    m_audioAvailable = true;
}

Wiedergeben des Sounds

Trigger für die Wiedergabe von Soundeffekten werden in der Simple3DGame::UpdateDynamics-Methode definiert, da dies der Ort ist, an dem die Bewegung der Objekte aktualisiert und die Kollision zwischen Objekten bestimmt wird.

Da sich die Interaktion zwischen Objekten je nach Spiel stark unterscheidet, werden wir hier nicht über die Dynamik der Spielobjekte diskutieren. Wenn Sie die Implementierung verstehen möchten, wechseln Sie zur Simple3DGame::UpdateDynamics-Methode .

Im Prinzip löst er beim Auftreten einer Kollision den Soundeffekt aus, indem SoundEffect::P laySound aufgerufen wird. Mit dieser Methode werden alle Soundeffekte beendet, die derzeit wiedergegeben werden, und der Speicherpuffer wird mit den gewünschten Sounddaten in die Warteschlange gestellt. Es verwendet Quellstimme, um die Lautstärke festzulegen, Sounddaten zu übermitteln und die Wiedergabe zu starten.

SoundEffect::P laySound-Methode

  • Verwendet das Quellstimmobjekt m_sourceVoice , um die Wiedergabe des Sounddatenpuffers m_soundData
  • Erstellt einen XAUDIO2_BUFFER, auf den er einen Verweis auf den Sounddatenpuffer bereitstellt, und sendet ihn dann mit einem Aufruf von IXAudio2SourceVoice::SubmitSourceBuffer.
  • Nachdem die Sounddaten in die Warteschlange eingereiht wurden, startet SoundEffect::P laySound wieder, indem IXAudio2SourceVoice::Start aufgerufen wird.
void SoundEffect::PlaySound(_In_ float volume)
{
    XAUDIO2_BUFFER buffer = { 0 };

    if (!m_audioAvailable)
    {
        // Audio is not available so just return.
        return;
    }

    // Interrupt sound effect if it is currently playing.
    winrt::check_hresult(
        m_sourceVoice->Stop()
        );
    winrt::check_hresult(
        m_sourceVoice->FlushSourceBuffers()
        );

    // Queue the memory buffer for playback and start the voice.
    buffer.AudioBytes = (UINT32)m_soundData.size();
    buffer.pAudioData = m_soundData.data();
    buffer.Flags = XAUDIO2_END_OF_STREAM;

    winrt::check_hresult(
        m_sourceVoice->SetVolume(volume)
        );
    winrt::check_hresult(
        m_sourceVoice->SubmitSourceBuffer(&buffer)
        );
    winrt::check_hresult(
        m_sourceVoice->Start()
        );
}

Simple3DGame::UpdateDynamics-Methode

Die Simple3DGame::UpdateDynamics-Methode übernimmt die Interaktion und Kollision zwischen Spielobjekten. Wenn Objekte kollidieren (oder schneiden), löst sie den zugeordneten Soundeffekt aus, um wiedergegeben zu werden.

void Simple3DGame::UpdateDynamics()
{
    ...
    // Check for collisions between ammo.
#pragma region inter-ammo collision detection
if (m_ammoCount > 1)
{
    ...
    // Check collision between instances One and Two.
    ...
    if (distanceSquared < (GameConstants::AmmoSize * GameConstants::AmmoSize))
    {
        // The two ammo are intersecting.
        ...
        // Start playing the sounds for the impact between the two balls.
        m_ammo[one]->PlaySound(impact, m_player->Position());
        m_ammo[two]->PlaySound(impact, m_player->Position());
    }
}
#pragma endregion

#pragma region Ammo-Object intersections
    // Check for intersections between the ammo and the other objects in the scene.
    // ...
    // Ball is in contact with Object.
    // ...

    // Make sure that the ball is actually headed towards the object. At grazing angles there
    // could appear to be an impact when the ball is actually already hit and moving away.

    if (impact > 0.0f)
    {
        ...
        // Play the sound associated with the Ammo hitting something.
        m_objects[i]->PlaySound(impact, m_player->Position());

        if (m_objects[i]->Target() && !m_objects[i]->Hit())
        {
            // The object is a target and isn't currently hit, so mark
            // it as hit and play the sound associated with the impact.
            m_objects[i]->Hit(true);
            m_objects[i]->HitTime(timeTotal);
            m_totalHits++;

            m_objects[i]->PlaySound(impact, m_player->Position());
        }
        ...
    }
#pragma endregion

#pragma region Apply Gravity and world intersection
            // Apply gravity and check for collision against enclosing volume.
            ...
                if (position.z < limit)
                {
                    // The ammo instance hit the a wall in the min Z direction.
                    // Align the ammo instance to the wall, invert the Z component of the velocity and
                    // play the impact sound.
                    position.z = limit;
                    m_ammo[i]->PlaySound(-velocity.z, m_player->Position());
                    velocity.z = -velocity.z * GameConstants::Physics::GroundRestitution;
                }
                ...
#pragma endregion
}

Nächste Schritte

Wir haben das UWP-Framework, Grafiken, Steuerelemente, Benutzeroberfläche und Audio eines Windows 10-Spiels behandelt. Im nächsten Teil dieses Lernprogramms , dem Erweitern des Beispielspiels, werden weitere Optionen erläutert, die beim Entwickeln eines Spiels verwendet werden können.

Audiokonzepte

Verwenden Sie für die Entwicklung von Windows 10-Spielen XAudio2, Version 2.9. Diese Version wird mit Windows 10 ausgeliefert. Weitere Informationen finden Sie unter XAudio2-Versionen.

AudioX2 ist eine API auf niedriger Ebene, die Signalverarbeitungs- und Mischgrundwerte bereitstellt. Weitere Informationen finden Sie unter XAudio2-Schlüsselkonzepte.

XAudio2-Stimmen

Es gibt drei Arten von XAudio2-Sprachobjekten: Quell-, Submix- und Masterstimme. Stimmen sind die Objekte, die XAudio2 zum Verarbeiten, Bearbeiten und Wiedergeben von Audiodaten verwenden.

  • Quellstimmen werden auf vom Client bereitgestellten Audiodaten ausgeführt.
  • Quell- und Submixstimden senden ihre Ausgabe an eine oder mehrere Submix- oder Masterstimden.
  • Submix- und Masterstimmen mischen die Audiodaten von allen Stimmen, die sie füttern, und arbeiten mit dem Ergebnis.
  • Masterstimden empfangen Daten von Quellstimmen und Submixstimden und sendet diese Daten an die Audiohardware.

Weitere Informationen finden Sie unter XAudio2-Stimmen.

Audiodiagramm

Audiodiagramm ist eine Sammlung von XAudio2-Stimmen. Audio beginnt auf einer Seite eines Audiodiagramms in Quellstimme, übergibt optional eine oder mehrere Submixstimme und endet mit einer Masterstimme. Ein Audiodiagramm enthält eine Quellstimme für jeden aktuell wiedergegebenen Sound, null oder mehr Submixstimme und eine Masterstimme. Das einfachste Audiodiagramm und das Mindestmaß, das zum Erstellen eines Rauschens in XAudio2 erforderlich ist, ist eine einzige Quellstimme, die direkt an eine Masterstimme ausgegeben wird. Weitere Informationen finden Sie unter "Audiodiagramme".

Zusätzliche Lektüre

Schlüsselaudio-H-Dateien

Audio.h

// Audio:
// This class uses XAudio2 to provide sound output. It creates two
// engines - one for music and the other for sound effects - each as
// a separate mastering voice.
// The SuspendAudio and ResumeAudio methods can be used to stop
// and start all audio playback.

class Audio
{
public:
    Audio();

    void Initialize();
    void CreateDeviceIndependentResources();
    IXAudio2* MusicEngine();
    IXAudio2* SoundEffectEngine();
    void SuspendAudio();
    void ResumeAudio();

private:
    ...
};

MediaReader.h

// MediaReader:
// This is a helper class for the SoundEffect class. It reads small audio files
// synchronously from the package installed folder and returns sound data as a
// vector of bytes.

class MediaReader
{
public:
    MediaReader();

    std::vector<byte> LoadMedia(_In_ winrt::hstring const& filename);
    WAVEFORMATEX* GetOutputWaveFormatEx();

private:
    winrt::Windows::Storage::StorageFolder  m_installedLocation{ nullptr };
    winrt::hstring                          m_installedLocationPath;
    WAVEFORMATEX                            m_waveFormat;
};

SoundEffect.h

// SoundEffect:
// This class plays a sound using XAudio2. It uses a mastering voice provided
// from the Audio class. The sound data can be read from disk using the MediaReader
// class.

class SoundEffect
{
public:
    SoundEffect();

    void Initialize(
        _In_ IXAudio2* masteringEngine,
        _In_ WAVEFORMATEX* sourceFormat,
        _In_ std::vector<byte> const& soundData
        );

    void PlaySound(_In_ float volume);

private:
    ...
};