サウンドの追加

Note

このトピックは、「DirectX を使った単純なユニバーサル Windows プラットフォーム (UWP) ゲームの作成」チュートリアル シリーズの一部です。 リンク先のトピックでは、このシリーズのコンテキストを説明しています。

このトピックでは、XAudio2 API を使用して、単純なサウンド エンジンを作成します。 XAudio2 について詳しくない場合は、「オーディオの概念」に簡単な概要があります。

Note

このサンプルの最新ゲーム コードをダウンロードしていない場合は、Direct3D サンプル ゲームのページに移動してください。 このサンプルは、UWP 機能サンプルの大規模なコレクションの一部です。 サンプルをダウンロードする方法については、「Windows 開発用のサンプル アプリケーション」をご覧ください。

目的

XAudio2 を使用してサンプル ゲームにサウンドを追加します。

オーディオ エンジンを定義する

このサンプル ゲームでは、オーディオのオブジェクトと動作が次の 3 つのファイルに定義されています。

  • Audio.h/.cpp: サウンド再生用の XAudio2 リソースが含まれている Audio オブジェクトを定義します。 また、ゲームが一時停止または非アクティブ化された場合にオーディオ再生を中断および再開する方法も定義します。
  • MediaReader.h/.cpp: オーディオ .wav ファイルをローカル ストレージから読み取るメソッドを定義します。
  • SoundEffect.h/.cpp: ゲーム内サウンド再生用のオブジェクトを定義します。

概要

ゲームへのオーディオ再生の設定には、主に 3 つの部分があります。

  1. オーディオ リソースを作成して初期化する
  2. オーディオ ファイルを読み込む
  3. サウンドをオブジェクトに関連付ける

これらはすべて Simple3DGame::Initialize メソッドに定義されています。 まず、このメソッドを調べて、各セクションでさらに詳しく説明します。

設定後に、サウンド効果をトリガーして再生する方法について説明します。 詳細については、「サウンドを再生する」を参照してください。

Simple3DGame::Initialize メソッド

m_controllerm_renderer の初期化も行われる Simple3DGame::Initialize では、オーディオ エンジンを設定してサウンドを再生できるようにします。

  • Audio クラスのインスタンスである m_audioController を作成します。
  • Audio::CreateDeviceIndependentResources メソッドを使用して、必要なオーディオ リソースを作成します。 ここでは、音楽エンジン オブジェクトとサウンド エンジン オブジェクトの 2 つの XAudio2 と、それぞれのマスタリング ボイスが作成されます。 音楽エンジン オブジェクトを使用すると、ゲームの BGM を再生できます。 サウンド エンジン オブジェクトを使用すると、ゲームのサウンド効果を再生できます。 詳細については、「オーディオ リソースを作成して初期化する」を参照してください。
  • MediaReader クラスのインスタンスである mediaReader を作成します。 SoundEffect クラスのヘルパー クラスである MediaReader は、ファイルの場所から小さいオーディオ ファイルを同期的に読み取り、音声データをバイト配列として返します。
  • MediaReader::LoadMedia を使用して、ファイルの場所からサウンド ファイルを読み込み、読み込まれた .wav サウンド データを保持する targetHitSound 変数を作成します。 詳細については、「オーディオ ファイルを読み込む」を参照してください。

サウンド効果は、ゲーム オブジェクトに関連付けられています。 そのため、そのゲーム オブジェクトで衝突が発生すると、再生するサウンド効果がトリガーされます。 このサンプル ゲームには、弾 (標的を撃つために使用) および標的用のサウンド効果があります。

  • GameObject クラスには、サウンド効果をオブジェクトに関連付けるために使用される HitSound プロパティがあります。
  • SoundEffect クラスの新しいインスタンスを作成して、初期化します。 初期化中に、サウンド効果のソース ボイスが作成されます。
  • このクラスは、Audio クラスによって提供されるマスタリング ボイスを使用してサウンドを再生します。 サウンド データは、MediaReader クラスを使用してファイルの場所から読み取られます。 詳細については、「サウンドをオブジェクトに関連付ける」を参照してください。

Note

サウンドを再生する実際のトリガーは、これらのゲーム オブジェクトの動きと衝突によって決まります。 そのため、これらのサウンドを実際に再生する呼び出しは、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) を使用して、音楽とサウンド効果のエンジンを定義する 2 つの新しい 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 オーディオファイルを読み取るには、MediaReader::LoadMedia を呼び出して、入力パラメーターとして .wav のファイル名を渡します。

MediaReader::LoadMedia メソッド

このメソッドは、 Media Foundation API を使用して、.wav オーディオ ファイルをパルス コード変調 (PCM) バッファーとして読み取ります。

ソース リーダーを設定する

  1. MFCreateSourceReaderFromURL を使用して、メディア ソース リーダー (IMFSourceReader) を作成します。
  2. MFCreateMediaType を使用して、メディアの種類 (IMFMediaType) のオブジェクト (mediaType) を作成します。 これは、メディア形式の説明を表します。
  3. mediaType のデコードされた出力が PCM オーディオであることを指定します。これは、XAudio2 が使うことができるオーディオの種類です。
  4. IMFSourceReader::SetCurrentMediaType を呼び出して、ソース リーダー用にデコードされる出力メディアの種類を設定します。

ソース リーダーを使用する理由の詳細については、「ソース リーダー」を参照してください。

オーディオ ストリームのデータ形式を記述する

  1. IMFSourceReader::GetCurrentMediaType を使用して、ストリームの現在のメディアの種類を取得します。
  2. IMFMediaType::MFCreateWaveFormatExFromMFMediaType を使用して、以前の操作の結果を入力として使って現在のオーディオ メディアの種類を WAVEFORMATEX バッファーに変換します。 この構造体は、オーディオが読み込まれた後に使用される Wave オーディオ ストリームのデータ形式を指定します。

WAVEFORMATEX 形式を使用して、PCM バッファーを記述できます。 WAVEFORMATEXTENSIBLE 構造体と比べると、オーディオ Wave 形式のサブセットの記述にのみ使用できます。 WAVEFORMATEXWAVEFORMATEXTENSIBLE の相違点の詳細については、「拡張可能な Wave 形式の記述子」を参照してください。

オーディオ ストリームを読み取る

  1. IMFSourceReader::GetPresentationAttribute を呼び出して、オーディオ ストリームの期間を秒数で取得した後、その期間をバイト数に変換します。
  2. IMFSourceReader::ReadSample を呼び出して、オーディオ ファイルをストリームとして読み取ります。 ReadSample は、メディア ソースから次のサンプルを読み取ります。
  3. IMFSample::ConvertToContiguousBuffer を使用して、オーディオ サンプル バッファー (sample) の内容を配列 (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 を使用して、サウンドを再生します。 これは、Audio クラスによって提供されるマスタリング ボイスを使用します。 MediaReader を使用して、ファイルの場所からサウンド データを読み取ることができます。

SoundEffect::Initialize を使用して SoundEffect インスタンスを初期化します。その際に使用される入力パラメーターは、サウンド エンジン オブジェクト (Audio::CreateDeviceIndependentResources メソッドで作成される IXAudio2 オブジェクト) へのポインター、MediaReader::GetOutputWaveFormatEx を使用した .wav ファイルの形式へのポインター、および MediaReader::LoadMedia メソッドを使用して読み込まれたサウンド データです。 初期化中に、サウンド効果のソース ボイスも作成されます。

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::P laySound 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 のバージョン」を参照してください。

XAudio2 は、信号処理とミキシングの基盤を提供する下位レベルの API です。 詳細については、「XAudio2 の主要な概念」を参照してください。

XAudio2 のボイス

XAudio2 のボイス オブジェクトには、ソース、サブミックス、マスタリングという 3 種類のボイスがあります。 ボイスは、オーディオ データを処理、操作、再生するために XAudio2 で使用されるオブジェクトです。

  • ソース ボイスは、クライアントから提供されたオーディオ データに適用されます。
  • ソース ボイスとサブミックス ボイスは、1 つ以上のサブミックス ボイスまたはマスタリング ボイスに向けて出力を送信します。
  • サブミックス ボイスとマスタリング ボイスは、それぞれに送られるすべてのボイスからオーディオをミキシングし、その結果に対して作用します。
  • マスタリング ボイスは、ソース ボイスとサブミックス ボイスからのデータを受信し、そのデータをオーディオ ハードウェアに送信します。

詳細については、「XAudio2 のボイス」を参照してください。

オーディオ グラフ

オーディオ グラフは XAudio2 のボイスのコレクションです。 オーディオは、ソース ボイスでオーディオ グラフの片側から開始され、必要に応じて 1 つ以上のサブミックス ボイスを通過し、マスタリング ボイスで終了します。 オーディオ グラフには、現在再生中の各サウンドのソース ボイス、0 個以上のサブミックス ボイス、1 つのマスタリング ボイスが含まれます。 最も単純なオーディオ グラフは、マスタリング ボイスに直接出力する 1 つのソース ボイスです。これは、XAudio2 でノイズを再生するための最小限の要件です。 詳細については、「オーディオ グラフ」を参照してください。

追加の参考資料

主要なオーディオ .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:
    ...
};