Hinzufügen von Sound

Hinweis

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

In diesem Thema erstellen wir eine einfache Sound-Engine mit XAudio2-APIs . Wenn Sie noch nicht mit XAudio2 vertraut sind, haben wir ein kurzes Intro unter Audiokonzepte eingefügt.

Hinweis

Wenn Sie den neuesten Spielcode für dieses Beispiel nicht heruntergeladen haben, wechseln 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 mithilfe von XAudio2 Sounds hinzu.

Definieren der Audio-Engine

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

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

Übersicht

Es gibt drei Standard Teile bei der Einrichtung für die Audiowiedergabe in Ihrem Spiel.

  1. Erstellen und Initialisieren der Audioressourcen
  2. Audiodatei laden
  3. Zuordnen von Sound zum Objekt

Sie werden alle in der Simple3DGame::Initialize-Methode definiert. Lassen Sie uns also zunächst diese Methode untersuchen und dann in den einzelnen Abschnitten weitere Details untersuchen.

Nach dem Einrichten erfahren Sie, wie Sie die Soundeffekte auslösen, die wiedergegeben werden sollen. Weitere Informationen finden Sie unter Sound wiedergeben.

Simple3DGame::Initialize-Methode

In Simple3DGame::Initialize, wo auch m_controller und m_renderer initialisiert werden, richten wir die Audio-Engine ein und bereiten sie für die Wiedergabe von Sounds vor.

  • Erstellen Sie m_audioController, bei dem es sich um eine instance der Audio-Klasse handelt.
  • Erstellen Sie die erforderlichen Audioressourcen mit der Audio::CreateDeviceIndependentResources-Methode . Hier wurden zwei XAudio2-Objekte – ein Musik-Engine-Objekt und ein Sound-Engine-Objekt sowie eine Masterstimme für jedes von ihnen erstellt. Das Musikmodulobjekt kann verwendet werden, um Hintergrundmusik für Ihr Spiel abzuspielen. Die Sound-Engine kann zum Wiedergeben von Soundeffekten in Ihrem Spiel verwendet werden. Weitere Informationen finden Sie unter Erstellen und Initialisieren der Audioressourcen.
  • Erstellen Sie mediaReader, bei dem es sich um eine instance der MediaReader-Klasse handelt. MediaReader, eine Hilfsklasse für die SoundEffect-Klasse , liest kleine Audiodateien synchron vom Dateispeicherort und gibt Sounddaten als Bytearray zurück.
  • Verwenden Sie MediaReader::LoadMedia , um Sounddateien aus dem Speicherort zu laden und eine targetHitSound-Variable zu erstellen, die die geladenen WAV-Sounddaten enthält. Weitere Informationen finden Sie unter Laden der Audiodatei.

Soundeffekte sind dem Spielobjekt zugeordnet. Wenn also eine Kollision mit diesem Spielobjekt auftritt, löst sie den abgespielten Soundeffekt aus. In diesem Beispielspiel haben wir Soundeffekte für die Munition (mit der wir Ziele schießen) 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 instance 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 ab, die von der Audio-Klasse bereitgestellt wird. Sounddaten werden mithilfe der MediaReader-Klasse aus dem Dateispeicherort gelesen. Weitere Informationen finden Sie unter Zuordnen von Sound zum Objekt.

Hinweis

Der tatsächliche Trigger zum Wiedergeben des Sounds wird durch die Bewegung und Kollision dieser Spielobjekte bestimmt. Daher wird der Aufruf zum tatsächlichen Wiedergeben dieser Sounds 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 Soundeffekt-Engines definieren. Diese Methode gibt einen Zeiger auf die IXAudio2-Schnittstelle des Objekts zurück, die alle Audio-Engine-Zustände, den Audioverarbeitungsthread, das Sprachdiagramm usw. verwaltet.
  • Nachdem die Engines instanziiert wurden, verwenden Sie IXAudio2::CreateMasteringVoice , um eine Masterstimme für jedes Soundmodulobjekt zu erstellen.

Weitere Informationen finden Sie unter Vorgehensweise: Initialisieren von 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;
}

Audiodatei laden

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-Datei als Eingabeparameter.

MediaReader::LoadMedia-Methode

Diese Methode verwendet die Media Foundation-APIs, um die WAV-Audiodatei als Pulse Code Modulation (PCM)-Puffer einzulesen.

Einrichten des Quellleselesers

  1. Verwenden Sie MFCreateSourceReaderFromURL , um einen Medienquellenleser (IMFSourceReader) zu erstellen.
  2. Verwenden Sie MFCreateMediaType , um ein Medientypobjekt (IMFMediaType) zu erstellen (mediaType). Sie stellt eine Beschreibung eines Medienformats dar.
  3. Geben Sie an, dass die decodierte Ausgabe von mediaType PCM-Audio ist, was ein Audiotyp ist, 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 Quellleseprogramm.

Beschreiben des Datenformats des Audiostreams

  1. Verwenden Sie IMFSourceReader::GetCurrentMediaType , um den aktuellen Medientyp für den Stream abzurufen.
  2. Verwenden Sie IMFMediaType::MFCreateWaveFormatExFromMFMediaType , um den aktuellen Audiomedientyp in einen WAVEFORMATEX-Puffer zu konvertieren, wobei die Ergebnisse des vorherigen 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 Erweiterbare Wave-Format deskriptoren.

Lesen des Audiostreams

  1. Rufen Sie die Dauer des Audiostreams in Sekunden ab, indem Sie IMFSourceReader::GetPresentationAttribute aufrufen und dann die Dauer in Bytes konvertieren.
  2. Lesen Sie die Audiodatei als Stream, indem Sie IMFSourceReader::ReadSample aufrufen. ReadSample liest das nächste Beispiel aus der Medienquelle.
  3. Verwenden Sie IMFSample::ConvertToContiguousBuffer , um Inhalte des Audiobeispielpuffers (Sample) 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 zum Objekt

Das Zuordnen von Sounds zum Objekt erfolgt bei der Initialisierung des Spiels in der Simple3DGame::Initialize-Methode .

Zusammenfassung:

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

SoundEffect::Initialize wird verwendet, um die SoundEffect-instance mit den folgenden Eingabeparametern zu initalisieren: Zeiger auf das Sound engine-Objekt (IXAudio2-Objekte, die in der Audio::CreateDeviceIndependentResources-Methode erstellt wurden), Zeiger auf das Format der WAV-Datei mit MediaReader::GetOutputWaveFormatEx und die mit der 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;
}

Sound wiedergeben

Trigger zum Wiedergeben von Soundeffekten werden in der Simple3DGame::UpdateDynamics-Methode definiert, da hier die Bewegung der Objekte aktualisiert und Kollisionen zwischen Objekten bestimmt werden.

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

Wenn eine Kollision auftritt, löst sie im Prinzip den Wiedergegebenen Soundeffekt aus, indem SoundEffect::P laySound aufgerufen wird. Diese Methode beendet alle derzeit wiedergegebenen Soundeffekte und stellt den In-Memory-Puffer mit den gewünschten Sounddaten in die Warteschlange. Es verwendet die 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 zu starten m_soundData
  • Erstellt eine XAUDIO2_BUFFER, auf die ein Verweis auf den Sounddatenpuffer bereitgestellt wird, und übermittelt ihn dann mit einem Aufruf von IXAudio2SourceVoice::SubmitSourceBuffer.
  • Wenn die Sounddaten in die Warteschlange eingereiht sind, beginnt SoundEffect::P laySound mit der Wiedergabe, 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 kümmert sich um die Interaktion und Kollision zwischen Spielobjekten. Wenn Objekte kollidieren (oder sich überschneiden), wird der zugehörige Soundeffekt zur Wiedergabe ausgelöst.

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 Tutorials, Erweitern des Beispielspiels, werden weitere Optionen erläutert, die bei der Entwicklung eines Spiels verwendet werden können.

Audiokonzepte

Verwenden Sie für Windows 10 Spieleentwicklung XAudio2 Version 2.9. Diese Version wird mit Windows 10 ausgeliefert. Weitere Informationen finden Sie unter XAudio2-Versionen.

AudioX2 ist eine LOW-Level-API, die signalverarbeitungs- und Mischbasis bietet. 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 verwendet.

  • Quellstimmen verarbeiten die vom Client bereitgestellten Audiodaten.
  • Quell- und Submixstimmen senden ihre Ausgabe an mindestens eine Submix- oder Masterstimme.
  • Submix- und Masterstimmen mischen die Audiodaten aller Stimmen, von denen sie Daten erhalten, und verarbeiten das Ergebnis.
  • Mastering-Stimmen empfangen Daten von Quell- und Submixstimmstimden und senden diese Daten an die Audiohardware.

Weitere Informationen finden Sie unter XAudio2-Stimmen.

Audiodiagramm

Audiograph ist eine Sammlung von XAudio2-Stimmen. Audio beginnt auf einer Seite eines Audiodiagramms in Quellstimme, durchläuft optional eine oder mehrere Submixstimme und endet bei einer Masterstimme. Ein Audiodiagramm enthält eine Quellstimme für jeden derzeit wiedergegebenen Sound, 0 oder mehr Submixstimme und eine Masterstimme. Das einfachste Audiodiagramm und das minimum, das zum Erstellen eines Rauschens in XAudio2 erforderlich ist, ist eine einzelne Quellstimme, die direkt an eine Masterstimme ausgegeben wird. Weitere Informationen finden Sie unter Audiographen.

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:
    ...
};