소리 추가

참고 항목

이 항목은 DirectX를 사용하여 간단한 UWP(유니버설 Windows 플랫폼) 게임 만들기 자습서 시리즈의 일부입니다. 해당 링크의 항목은 시리즈의 컨텍스트를 설정합니다.

이 항목에서는 XAudio2 API를 사용하여 간단한 사운드 엔진을 만듭니다. XAudio2를 처음 접하는 사용자를 위해 오디오 개념에 대한 간단한 소개가 포함되어 있습니다.

참고 항목

이 샘플의 최신 게임 코드를 다운로드하지 않은 경우 Direct3D 샘플 게임으로 이동합니다. 이 샘플은 UWP 기능 샘플의 큰 컬렉션의 일부입니다. 샘플을 다운로드하는 방법에 대한 지침은 Windows 개발을 위한 샘플 애플리케이션을 참조하세요.

목표

XAudio2를 사용하여 샘플 게임에 사운드를 추가합니다.

오디오 엔진 정의

샘플 게임에서 오디오 개체와 동작은 다음 세 개의 파일에 정의됩니다.

  • Audio.h/.cpp: 이 코드 파일은 사운드 재생을 위한 XAudio2 리소스를 포함하는 오디오 개체를 정의합니다. 또한 게임이 일시 중지되거나 비활성화된 경우 오디오 재생을 일시 중지하고 재시작하기 위한 방법을 정의합니다.
  • MediaReader.h/.cpp: 이 코드는 로컬 저장소에서 오디오 .wav 파일을 읽는 메서드를 정의합니다.
  • SoundEffect.h/.cpp: 이 코드는 게임 내 사운드 재생을 위한 개체를 정의합니다.

개요

게임에 오디오 재생을 설정하는 데는 세 가지 주요 부분이 있습니다.

  1. 오디오 리소스 만들기 및 초기화
  2. 오디오 파일 로드
  3. 개체에 사운드 연결

이러한 메서드는 모두 Simple3DGame::Initialize 메서드에 정의되어 있습니다. 먼저 이 메서드를 검토한 다음 각 섹션에서 좀 더 자세히 살펴보겠습니다.

설정한 후에는 사운드 효과를 트리거하여 재생하는 방법에 대해 알아봅니다. 자세한 내용은 사운드 재생을 참조하세요.

Simple3DGame::Initialize 메서드

m_controllerm_renderer도 초기화되는 Simple3DGame::Initialize에서 오디오 엔진을 설정하고 사운드를 재생하도록 준비합니다.

  • 오디오 클래스의 인스턴스인 m_audioController를 만듭니다.
  • Audio::CreateDeviceIndependentResources 메서드를 사용하여 필요한 오디오 리소스를 만듭니다. 여기에는 두 개의 XAudio2 개체(음악 엔진 개체와 사운드 엔진 개체)와 각각에 대한 마스터링 음성이 생성 됩니다. 음악 엔진 개체를 사용하여 게임의 배경 음악을 재생할 수 있습니다. 사운드 엔진은 게임에서 사운드 효과를 재생하는 데 사용할 수 있습니다. 자세한 내용은 오디오 리소스 만들기 및 초기화를 참조하세요.
  • MediaReader 클래스의 인스턴스인 mediaReader를 만듭니다. SoundEffect 클래스에 대 한 도우미 클래스인 MediaReader는 파일 위치에서 동기적으로 작은 오디오 파일을 읽고 사운드 데이터를 바이트 배열로 반환합니다.
  • MediaReader::LoadMedia를 사용하여 해당 위치에서 사운드 파일을 로드하고 로드된 .wav 사운드 데이터를 저장할 targetHitSound 변수를 만듭니다. 자세한 내용은 오디오 파일 로드를 참조하세요.

사운드 효과는 게임 개체와 연결됩니다. 따라서 해당 게임 개체에서 충돌이 발생하면, 재생되는 사운드 효과를 트리거합니다. 이 샘플 게임에서는 탄약(함께 대상을 쏘는 데 사용하는 항목) 및 대상에 대한 사운드 효과가 있습니다.

  • GameObject 클래스에는 사운드 효과를 개체에 연결하는 데 사용되는 HitSound 속성이 있습니다.
  • SoundEffect 클래스의 새 인스턴스를 만들고 초기화합니다. 초기화하는 동안 사운드 효과에 대한 원본 음성이 생성됩니다.
  • 이 클래스는 오디오 클래스에서 제공된 마스터링 음성을 사용하여 사운드를 재생합니다. MediaReader 클래스를 사용하여 파일 위치에서 사운드 데이터를 읽습니다. 자세한 내용은 개체에 사운드 연결을 참조하세요.

참고 항목

사운드를 재생하는 실제 트리거는 이러한 게임 개체의 이동 및 충돌에 따라 결정됩니다. 따라서 실제로 이러한 사운드를 재생하는 호출은 Simple3DGame::UpdateDynamics 메서드에 정의되어 있습니다. 자세한 내용은 사운드 재생을 참조하세요.

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]);
    }
    ...
}

오디오 리소스 만들기 및 초기화

  • XAudio2Create, XAudio2 API를 사용하여 음악 및 사운드 효과 엔진을 정의하는 두 개의 새 XAudio2 개체를 만듭니다. 이 메서드는 모든 오디오 엔진 상태, 오디오 처리 스레드, 음성 그래프 등을 관리하는 개체의 IXAudio2 인터페이스에 대한 포인터를 반환합니다.
  • 엔진이 인스턴스화되면 IXAudio2::CreateMasteringVoice를 사용하여 각 사운드 엔진 개체에 대한 마스터링 음성을 만듭니다.

자세한 내용은 방법: XAudio2 초기화로 이동하세요.

Audio::CreateDeviceIndependentResources 메서드

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

오디오 파일 로드

샘플 게임에서 오디오 형식 파일을 읽는 코드는 MediaReader.h/cpp__에 정의되어 있습니다. 인코딩된 .wav 오디오 파일을 읽으려면 입력 매개 변수로 .wav의 파일 이름을 전달하여 MediaReader::LoadMedia를 호출합니다.

MediaReader::LoadMedia 메서드

이 메서드는 Media Foundation API를 사용하여 .wav 오디오 파일에서 PCM(펄스 코드 변조) 버퍼로 읽습니다.

원본 판독기 설정

  1. MFCreateSourceReaderFromURL을 사용하여 미디어 원본 판독기(IMFSourceReader)를 만듭니다.
  2. MFCreateMediaType를 사용하 여 미디어 유형(IMFMediaType) 개체(mediaType)를 만듭니다. 미디어 형식에 대한 설명을 나타냅니다.
  3. mediaType의 디코드된 출력이 XAudio2에서 사용할 수 있는 오디오 유형인 PCM 오디오가 되도록 지정합니다.
  4. IMFSourceReader::SetCurrentMediaType을 호출하여 원본 판독기의 디코드된 출력 미디어 유형을 설정합니다.

원본 판독기를 사용하는 이유에 대한 자세한 내용은 원본 판독기로 이동하세요.

오디오 스트림의 데이터 서식 설명

  1. IMFSourceReader::GetCurrentMediaType을 사용하여 스트림에 대한 현재 미디어 형식을 가져옵니다.
  2. IMFMediaType::MFCreateWaveFormatExFromMFMediaType을 통해 이전 작업의 결과를 입력으로 사용하여 현재 오디오 미디어 형식을 WAVEFORMATEX 버퍼로 변환합니다. 이 구조는 오디오가 로드된 후 사용되는 웨이브 오디오 스트림의 데이터 서식을 지정합니다.

WAVEFORMATEX 형식은 PCM 버퍼를 설명하는 데 사용할 수 있습니다. 이는 WAVEFORMATEXTENSIBLE 구조와 비교하여 오디오 웨이브 형식의 하위 집합을 설명하는 데만 사용할 수 있습니다. WAVEFORMATEXWAVEFORMATEXTENSIBLE 간의 차이점에 대한 자세한 내용은 확장 가능한 웨이브 형식 설명자를 참조하세요.

오디오 스트림 읽기

  1. IMFSourceReader::GetPresentationAttribute를 호출하여 오디오 스트림의 기간(초)을 가져온 다음 해당 기간을 바이트로 변환합니다.
  2. IMFSourceReader::ReadSample을 호출하여 오디오 파일을 스트림으로 읽습니다. ReadSample은 미디어 원본에서 다음 샘플을 읽습니다.
  3. IMFSample::ConvertToContiguousBuffer를 사용하여 오디오 샘플 버퍼(샘플)의 콘텐츠를 배열(mediaBuffer)에 복사합니다.
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;
}

개체에 사운드 연결

게임이 초기화될 때 Simple3DGame::Initialize 메서드에서 사운드를 개체에 연결합니다.

요약:

  • GameObject 클래스에는 사운드 효과를 개체에 연결하는 데 사용되는 HitSound 속성이 있습니다.
  • SoundEffect 클래스 개체의 새 인스턴스를 만들고 게임 개체와 연결합니다. 이 클래스는 XAudio2 API를 사용하여 사운드를 재생합니다. 이는 오디오 클래스에서 제공하는 마스터링 음성을 사용합니다. MediaReader 클래스를 사용하여 파일 위치에서 사운드 데이터를 읽을 수 있습니다.

SoundEffect::Initialize는 사운드 엔진 개체에 대한 포인터(Audio::CreateDeviceIndependentResources 메서드에서 만든 IXAudio2 개체), MediaReader::GetOutputWaveFormatEx를 사용한 .wav 파일의 형식에 대한 포인터, MediaReader::LoadMedia 메서드를 사용하여 로드된 사운드 데이터 입력 매개 변수를 사용하여 SoundEffect 인스턴스를 초기화 하는 데 사용됩니다. 초기화하는 동안 사운드 효과에 대한 원본 음성도 생성됩니다.

SoundEffect::Initialize 메서드

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

사운드 재생

사운드 효과를 재생하는 트리거는Simple3DGame::UpdateDynamics 메서드에 정의됩니다. 여기에는 개체의 이동이 업데이트되고 개체 간의 충돌이 결정되기 때문입니다.

개체 간의 상호 작용은 게임에 따라 크게 달라지므로 여기에서는 게임 개체의 역동성에 대해 설명하지 않습니다. 구현을 이해하려면 Simple3DGame::UpdateDynamics 메서드를 참조하세요.

원칙상 충돌이 발생하면 SoundEffect::PlaySound를 호출하여 사운드 효과를 트리거합니다. 이 메서드는 현재 재생 중인 사운드 효과를 중지하고 원하는 사운드 데이터를 사용하여 메모리 내 버퍼를 큐에 대기합니다. 원본 음성을 사용하여 볼륨을 설정하고, 사운드 데이터를 제출하며, 재생을 시작합니다.

SoundEffect::PlaySound 메서드

  • 원본 음성 개체 m_sourceVoice를 사용하여 사운드 데이터 버퍼 m_soundData의 재생을 시작합니다.
  • 사운드 데이터 버퍼에 대한 참조를 제공하는 XAUDIO2_BUFFER를 만든 다음 IXAudio2SourceVoice::SubmitSourceBuffer를 호출하여 전송합니다.
  • 사운드 데이터가 대기하고 있으면 SoundEffect::PlaySound에서 IXAudio2SourceVoice::Start를 호출하여 재생을 시작합니다.
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 메서드

Simple3DGame::UpdateDynamics 메서드는 게임 개체 간의 상호 작용 및 충돌을 처리합니다. 개체가 충돌(또는 교차)할 때 연결된 사운드 효과를 트리거하여 재생합니다.

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
}

다음 단계

Windows 10 게임의 UWP 프레임워크, 그래픽, 컨트롤, 사용자 인터페이스 및 오디오에 대해 설명했습니다. 이 자습서의 다음 부분인 샘플 게임 확장에서는 게임을 개발할 때 사용할 수 있는 다른 옵션에 대해 설명합니다.

오디오 개념

Windows 10 게임 개발의 경우 XAudio2 버전 2.9를 사용합니다. 이 버전은 Windows 10과 함께 제공되어 있습니다. 자세한 내용은 XAudio2 버전으로 이동하세요.

AudioX2는 신호 처리 및 믹싱에 대한 기초를 제공하는 하위 수준의 API입니다. 자세한 내용은 XAudio2 주요 개념을 참조하세요.

XAudio2 음성

원본, 서브믹스 및 마스터링 음성이라는 세 가지 유형의 XAudio2 음성 개체가 있습니다. 음성은 XAudio2에서 오디오 데이터를 처리, 조작 및 재생하는 데 사용하는 개체입니다.

  • 원본 음성은 클라이언트에서 제공하는 오디오 데이터에서 작동합니다.
  • 원본 및 서브믹스 음성은 하나 이상의 서브믹스 또는 마스터링 음성으로 출력을 보냅니다.
  • 서브믹스 및 마스터링 음성은 모든 음성의 오디오를 공급하고 결과에 따라 작동합니다.
  • 마스터 음성은 원본 및 서브믹스 음성의 데이터를 결합하고 해당 데이터를 오디오 하드웨어에 보냅니다.

자세한 내용은 XAudio2 음성으로 이동하세요.

오디오 그래프

오디오 그래프는 XAudio2 음성의 컬렉션입니다. 오디오는 원본 음성으로 오디오 그래프의 한 쪽에서 시작되고, 필요에 따라 하나 이상의 서브믹스 음성을 통과하며, 마스터링 음성으로 끝납니다. 오디오 그래프에는 현재 재생 중인 각 사운드에 대한 원본 음성, 0개 이상의 서브믹스 음성 및 하나의 마스터링 음성이 포함됩니다. 가장 간단한 오디오 그래프 및 XAudio2에서 노이즈를 만드는 데 필요한 최소값은 마스터 음성으로 직접 출력되는 단일 원본 음성입니다. 자세한 내용은 오디오 그래프로 이동하세요.

추가 자료

Key audio .h 파일

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