14 Making use of wave banks
Chuck Walbourn редактировал(а) эту страницу 2022-11-29 13:11:56 -08:00
Getting Started for DX11 Getting Started for DX12

This lesson covers the basics of playing sounds with DirectX Tool Kit for Audio using of XACT-style wave banks.

Setup

First create a new project using the instructions from the earlier lessons: Using DeviceResources, then Adding the DirectX Tool Kit, Adding the DirectX Tool Kit for Audio, and finally Adding audio to your project which we will use for this lesson.

If using DirectX Tool Kit for DX12, use Using DeviceResources, then Adding the DirectX Tool Kit, add #include <Audio.h> and #define _USE_MATH_DEFINES to pch.h, and finally Adding audio to your project

Background

In the previous tutorial we loaded the individual sound files as distinct SoundEffect objects, but games commonly have hundreds or thousands of distinct sounds, music, and voice recordings. A more efficient way to manage this data is to make of a 'wave bank' which is a single file that contains a collection of .wav files that can be loaded all at once.

DirectX Tool Kit for Audio supports "wave banks" which were created for the Cross-platform Audio Creation Tool (XACT) from the legacy DirectX SDK. It makes no use of "sound banks" or "cues".

Creating wave bank files

  1. Download the XWBTool.exe from the DirectX Tool Kit site and extract the EXE into your project's folder.
  2. Download these media files to your project's folder Explo1.wav, Explo2.wav, Explo3.wav, Explo4.wav, NightAmbienceSimple_02.wav.
  3. Open a Command Prompt and then change to your project's folder.

These .wav files are from the XNA Game Studio SoundLab which provides a small collection of sounds under the MS-PL license.

Run the following command-line:

XWBTool -f -o sounds.xwb Explo1.wav Explo2.wav Explo3.wav Explo4.wav NightAmbienceSimple_02.wav

Then from the top menu in Visual Studio select Project / Add Existing Item.... Select sounds.xwb and click "OK".

If you are using a Universal Windows Platform app or Xbox project rather than a Windows desktop app, you need to manually edit the Visual Studio project properties on the sounds.xwb file and make sure "Content" is set to "Yes" so the data file will be included in your packaged build.

The xactbld tool from the legacy DirectX SDK can also create the same xwb files.

Playing a sound

In the pch.h file, add after the other includes:

#include <random>

In the Game.h file, add the following variables to the bottom of the Game class's private declarations:

std::unique_ptr<DirectX::WaveBank> m_sounds;
std::unique_ptr<std::mt19937> m_random;
std::unique_ptr<DirectX::SoundEffectInstance> m_nightLoop;
float explodeDelay;

In Game.cpp, add to the end of Initialize:

std::random_device rd;
m_random = std::make_unique<std::mt19937>(rd());

explodeDelay = 2.f;

m_sounds = std::make_unique<WaveBank>( m_audEngine.get(), L"sounds.xwb" );

m_nightLoop = m_sounds->CreateInstance( "NightAmbienceSimple_02" );
if ( m_nightLoop )
    m_nightLoop->Play(true);
...

In Game.cpp, add to the TODO of Update:

explodeDelay -= elapsedTime;
if (explodeDelay < 0.f)
{
    std::uniform_int_distribution<unsigned int> dist2(0, 3);
    m_sounds->Play( dist2(*m_random) );

    std::uniform_real_distribution<float> dist(1.f, 10.f);
    explodeDelay = dist(*m_random);
}

In Game.cpp, modify the handling of m_retryAudio in Update as follows:

...
if (m_retryAudio)
{
    m_retryAudio = false;

    if (m_audEngine->Reset())
    {
        // TODO: restart any looped sounds here
        if ( m_nightLoop )
            m_nightLoop->Play(true);
    }
}
...

In Game.cpp, add to the TODO of OnResuming:

explodeDelay = 2.f;

In Game.cpp, modify the destructor:

Game::~Game()
{
    if (m_audEngine)
    {
        m_audEngine->Suspend();
    }

    m_nightLoop.reset();
}

Build and run. This is the same as the last tutorial, but this time the sounds are being played from the wave bank. The explosion also now selects from 4 different exploding sounds at random to provide some variation.

WaveBank must remain in memory as the audio data is played directly from that object. XAudio does not copy the source audio data into it's own buffers, but fetches from the application's memory periodically from a worker thread.

Click here for troubleshooting advice

If you get a runtime exception, then you may have the "sounds.xwb" file in the wrong folder, have modified the "Working Directory" in the "Debugging" configuration settings, or otherwise changed the expected paths at runtime of the application. You should set a break-point on std::make_unique<WaveBank> line and step into the code to find the exact problem.

Technical notes

We need to ensure that the looping SoundEffectInstance is destroyed before the object it was created from is released. We therefore added an explicit reset to the Game's destructor to avoid being dependent on the order of destruction in the Game class. We also do this after the audio engine has been explicitly suspended since XAudio2 is multi-threaded and references data directly from the application's objects.

To make use of wavebanks a bit more robust, calls to CreateInstance for 'missing sounds' can return nullptr rather than throwing an exception. This is why we guard the use of m_nightLoop elsewhere. This lets your audio developer mess around with the content of the wave banks without necessarily causing the game to crash.

Generating wave bank entry headers

Wavebank entries can be referenced by a 0-based index or by an optional friendly name if present in the wave bank--this is why we used -f in our command-line to xwbtool above to have it include friendly names. You can also have xwbtool generate a C header with an enumeration for the indices by using -h.

XWBTool -f -o sounds.xwb -y -h sounds.h Explo1.wav Explo2.wav Explo3.wav Explo4.wav NightAmbienceSimple_02.wav

which would generate a sounds.h file you could use instead of having 'magic' numbers in your code:

#pragma once

enum XACT_WAVEBANK_SOUNDS : unsigned int
{
    XACT_WAVEBANK_SOUNDS_EXPLO1 = 0,
    XACT_WAVEBANK_SOUNDS_EXPLO2 = 1,
    XACT_WAVEBANK_SOUNDS_EXPLO3 = 2,
    XACT_WAVEBANK_SOUNDS_EXPLO4 = 3,
    XACT_WAVEBANK_SOUNDS_NIGHTAMBIENCESIMPLE_02 = 4,
};

#define XACT_WAVEBANK_SOUNDS_ENTRY_COUNT 5

Streaming audio from wave banks

In the previous section we generated 'in-memory' wave banks where the data is all loaded into RAM. XACT wave banks also support 'streaming' wave banks which are arranged on disk to support non-buffered asynchronous I/O.

Download these media files to your project's folder Electro_1.wav. Run the following command-line:

xwbtool -o music.xwb -s Electro_1.wav

Then from the top menu in Visual Studio select Project / Add Existing Item.... Select music.xwb and click "OK".

If you are using a Universal Windows Platform app or Xbox One project rather than a Windows desktop app, you need to manually edit the Visual Studio project properties on the music.xwb file and make sure "Content" is set to "Yes" so the data file will be included in your packaged build.

In the Game.h file, add the following variables to the bottom of the Game class's private declarations:

std::unique_ptr<DirectX::WaveBank> m_music;
std::unique_ptr<DirectX::SoundStreamInstance> m_stream;

In Game.cpp, add to the end of Initialize:

m_music = std::make_unique<WaveBank>( m_audEngine.get(), L"music.xwb" );

m_stream = m_music->CreateStreamInstance(0u);
if (m_stream)
{
    m_stream->SetVolume(0.5f);
    m_stream->Play(true);
}

In Game.cpp, modify the destructor:

Game::~Game()
{
    if (m_audEngine)
    {
        m_audEngine->Suspend();
    }

    m_stream.reset();
    m_nightLoop.reset();
}

In Game.cpp, modify the handling of m_retryAudio in Update as follows:

...
if (m_retryAudio)
{
    m_retryAudio = false;

    if (m_audEngine->Reset())
    {
      // TODO: restart any looped sounds here
      if (m_nightLoop)
          m_nightLoop->Play(true);
      if (m_stream)
          m_stream->Play(true);
    }
}
...

Build and run to hear a looping music track added to the other sounds.

Technical notes

You can create both in-memory and streaming wave banks using ADPCM, xWMA, or XMA2. For example, download this xWMA compressed version of Electro_1_xwma.wav and create the music wave bank:

xwbtool -o music.xwb -y -s Electro_1_xwma.wav

For Xbox you should also use the Advanced Format (4Kn) switch (-af)

Note: This will fail to run if you are using XAudio 2.8 which only supports ADPCM, but not xWMA. It will run with XAudio 2.9, XAudio 2.7, or the XAudio2Redist.

Next lesson: Using positional audio

Further reading

DirectX Tool Kit docs SoundEffectInstance, SoundStreamInstance, WaveBank

XAudio2AsyncStream, SimplePlaySoundStream (UWP), SimplePlaySoundStream (GDK) samples