DirectX の空間サウンドSpatial sound in DirectX

XAudio2およびxapoオーディオライブラリを使用して、DirectX に基づく Windows Mixed Reality アプリに空間サウンドを追加します。Add spatial sound to your Windows Mixed Reality apps based on DirectX by using the XAudio2 and xAPO audio libraries.

このトピックでは、HolographicHRTFAudioSample のサンプルコードを使用します。This topic uses sample code from the HolographicHRTFAudioSample.

注意

この記事のコードスニペットでは、現在、 C++ C++ holographic プロジェクトテンプレートで使用されてC++いる C + c++ 17 準拠の/WinRT ではなく、/cx の使用方法を示しています。The code snippets in this article currently demonstrate use of C++/CX rather than C++17-compliant C++/WinRT as used in the C++ holographic project template. これらの概念は、プロジェクトC++の場合と同じですが、コードを変換する必要があります。The concepts are equivalent for a C++/WinRT project, though you will need to translate the code.

ヘッド相対空間サウンドの概要Overview of Head Relative Spatial Sound

空間サウンドは、通常のオーディオストリームをspatializeするためにhead 関連の転送関数 (HRTF) フィルターを使用するオーディオ処理オブジェクト (APO) として実装されます。Spatial sound is implemented as an audio processing object (APO) that uses a head related transfer function (HRTF) filter to spatialize an ordinary audio stream.

次のヘッダーファイルを pch に含めて、オーディオ Api にアクセスします。Include these header files in pch.h to access the audio APIs:

  • XAudio2XAudio2.h
  • xapo. hxapo.h
  • hrtfアポストロフィ api .hhrtfapoapi.h

空間サウンドを設定するには:To set up spatial sound:

  1. Createhrtfapoを呼び出して、HRTF audio の新しい APO を初期化します。Call CreateHrtfApo to initialize a new APO for HRTF audio.
  2. HRTF parametersHRTF 環境を割り当てて、空間サウンド APO の音響特性を定義します。Assign the HRTF parameters and HRTF environment to define the acoustic characteristics of the spatial sound APO.
  3. XAudio2 engine を HRTF 処理用に設定します。Set up the XAudio2 engine for HRTF processing.
  4. IXAudio2SourceVoiceオブジェクトを作成し、 Startを呼び出します。Create an IXAudio2SourceVoice object and call Start.

DirectX アプリに HRTF と空間サウンドを実装するImplementing HRTF and spatial sound in your DirectX app

異なるパラメーターと環境で HRTF APO を構成することによって、さまざまな効果を実現できます。You can achieve a variety of effects by configuring the HRTF APO with different parameters and environments. 次のコードを使用して、可能性を調査します。Use the following code to explore the possibilities. ここからユニバーサル Windows プラットフォームコードサンプルをダウンロードします。空間サウンドのサンプルDownload the Universal Windows Platform code sample from here: Spatial sound sample

ヘルパーの種類は、次のファイルで使用できます。Helper types are available in these files:

無指向性ソースの空間サウンドを追加するAdd spatial sound for an omnidirectional source

ユーザーの周囲の一部のホログラムは、すべての方向に同じようにサウンドを出力します。Some holograms in the user's surroundings emit sound equally in all directions. 次のコードは、APO を初期化して全方向音を出力する方法を示しています。The following code shows how to initialize an APO to emit omnidirectional sound. この例では、この概念を Windows Holographic アプリテンプレートからスピンするキューブに適用します。In this example, we apply this concept to the spinning cube from the Windows Holographic app template. 完全なコードリストについては、「 OmnidirectionalSound」を参照してください。For the complete code listing, see OmnidirectionalSound.cpp.

ウィンドウの空間サウンドエンジンは、再生用に 48 k samplerate のみをサポートしています。Unity などのほとんどのミドルウェアプログラムは、サウンドファイルを目的の形式に自動的に変換しますが、オーディオシステムの低レベルで考察を開始する場合や、独自の動作を行う場合は、次のようなクラッシュや望ましくない動作を防ぐために注意することが非常に重要です。HRTF システムエラー。Window's spatial sound engine only supports 48k samplerate for playback. Most middleware programs, like Unity, will automatically convert sound files into the desired format, but if you start tinkering at lower levels in the audio system or making your own, this is very important to remember to prevent crashes or undesired behaviour like HRTF system failure.

まず、APO を初期化する必要があります。First, we need to initialize the APO. この holographic サンプルアプリでは、 HolographicSpaceを用意した後にこれを行うことを選択します。In our holographic sample app, we choose to do this once we have the HolographicSpace.

From HolographicHrtfAudioSampleMain:: SetHolographicSpace () :From HolographicHrtfAudioSampleMain::SetHolographicSpace():

// Spatial sound
auto hr = m_omnidirectionalSound.Initialize(L"assets//MonoSound.wav");

OmnidirectionalSoundからの Initialize の実装:The implementation of Initialize, from OmnidirectionalSound.cpp:

// Initializes an APO that emits sound equally in all directions.
HRESULT OmnidirectionalSound::Initialize( LPCWSTR filename )
{
    // _audioFile is of type AudioFileReader, which is defined in AudioFileReader.cpp.
    auto hr = _audioFile.Initialize( filename );

    ComPtr<IXAPO> xapo;
    if ( SUCCEEDED( hr ) )
    {
        // Passing in nullptr as the first arg for HrtfApoInit initializes the APO with defaults of
        // omnidirectional sound with natural distance decay behavior.
        // CreateHrtfApo fails with E_NOTIMPL on unsupported platforms.
        hr = CreateHrtfApo( nullptr, &xapo );
    }

    if ( SUCCEEDED( hr ) )
    {
        // _hrtfParams is of type ComPtr<IXAPOHrtfParameters>.
        hr = xapo.As( &_hrtfParams );
    }

    // Set the default environment.
    if ( SUCCEEDED( hr ) )
    {
        hr = _hrtfParams->SetEnvironment( HrtfEnvironment::Outdoors );
    }

    // Initialize an XAudio2 graph that hosts the HRTF xAPO.
    // The source voice is used to submit audio data and control playback.
    if ( SUCCEEDED( hr ) )
    {
        hr = SetupXAudio2( _audioFile.GetFormat(), xapo.Get(), &_xaudio2, &_sourceVoice );
    }

    // Submit audio data to the source voice.
    if ( SUCCEEDED( hr ) )
    {
        XAUDIO2_BUFFER buffer{ };
        buffer.AudioBytes = static_cast<UINT32>( _audioFile.GetSize() );
        buffer.pAudioData = _audioFile.GetData();
        buffer.LoopCount = XAUDIO2_LOOP_INFINITE;

        // _sourceVoice is of type IXAudio2SourceVoice*.
        hr = _sourceVoice->SubmitSourceBuffer( &buffer );
    }

    return hr;
}

HRTF に対して APO が構成されたら、ソース音声でStartを呼び出してオーディオを再生します。After the APO is configured for HRTF, you call Start on the source voice to play the audio. このサンプルアプリでは、キューブからのサウンドを引き続き再生できるように、ループに配置します。In our sample app, we choose to put it on a loop so that you can continue to hear the sound coming from the cube.

From HolographicHrtfAudioSampleMain:: SetHolographicSpace () :From HolographicHrtfAudioSampleMain::SetHolographicSpace():

if (SUCCEEDED(hr))
{
    m_omnidirectionalSound.SetEnvironment(HrtfEnvironment::Small);
    m_omnidirectionalSound.OnUpdate(m_spinningCubeRenderer->GetPosition());
    m_omnidirectionalSound.Start();
}

OmnidirectionalSoundから:From OmnidirectionalSound.cpp:

HRESULT OmnidirectionalSound::Start()
{
    _lastTick = GetTickCount64();
    return _sourceVoice->Start();
}

ここで、フレームを更新するたびに、デバイス自体に対するホログラムの位置を更新する必要があります。Now, whenever we update the frame, we need to update the hologram's position relative to the device itself. これは、HRTF の位置は常にユーザーの先頭に対して相対的に表されます。これは、head 位置と向きを含みます。This is because HRTF positions are always expressed relative to the user's head, including the head position and orientation.

HolographicSpace でこれを行うには、SpatialStationaryFrameOfReference 座標系から、デバイス自体に固定されている座標系に変換行列を構築する必要があります。To do this in a HolographicSpace, we need to construct a transform matrix from our SpatialStationaryFrameOfReference coordinate system to a coordinate system that is fixed to the device itself.

HolographicHrtfAudioSampleMain:: Update () から:From HolographicHrtfAudioSampleMain::Update():

m_spinningCubeRenderer->Update(m_timer);

SpatialPointerPose^ currentPose = SpatialPointerPose::TryGetAtTimestamp(currentCoordinateSystem, prediction->Timestamp);
if (currentPose != nullptr)
{
    // Use a coordinate system built from a pointer pose.
    SpatialPointerPose^ pose = SpatialPointerPose::TryGetAtTimestamp(currentCoordinateSystem, prediction->Timestamp);
    if (pose != nullptr)
    {
        float3 headPosition = pose->Head->Position;
        float3 headUp = pose->Head->UpDirection;
        float3 headDirection = pose->Head->ForwardDirection;

        // To construct a rotation matrix, we need three vectors that are mutually orthogonal.
        // The first vector is the gaze vector.
        float3 negativeZAxis = normalize(headDirection);

        // The second vector should end up pointing away from the horizontal plane of the device.
        // We first guess by using the head "up" direction.
        float3 positiveYAxisGuess = normalize(headUp);

        // The third vector completes the set by being orthogonal to the other two.
        float3 positiveXAxis = normalize(cross(negativeZAxis, positiveYAxisGuess));

        // Now, we can correct our "up" vector guess by redetermining orthogonality.
        float3 positiveYAxis = normalize(cross(negativeZAxis, positiveXAxis));

        // The rotation matrix is formed as a standard basis rotation.
        float4x4 rotationTransform =
            {
            positiveXAxis.x, positiveYAxis.x, negativeZAxis.x, 0.f,
            positiveXAxis.y, positiveYAxis.y, negativeZAxis.y, 0.f,
            positiveXAxis.z, positiveYAxis.z, negativeZAxis.z, 0.f,
            0.f, 0.f, 0.f, 1.f,
            };

        // The translate transform can be constructed using the Windows::Foundation::Numerics API.
        float4x4 translationTransform = make_float4x4_translation(-headPosition);

        // Now, we have a basis transform from our spatial coordinate system to a device-relative
        // coordinate system.
        float4x4 coordinateSystemTransform = translationTransform * rotationTransform;

        // Reinterpret the cube position in the device's coordinate system.
        float3 cubeRelativeToHead = transform(m_spinningCubeRenderer->GetPosition(), coordinateSystemTransform);

        // Note that at (0, 0, 0) exactly, the HRTF audio will simply pass through audio. We can use a minimal offset
        // to simulate a zero distance when the hologram position vector is exactly at the device origin in order to
        // allow HRTF to continue functioning in this edge case.
        float distanceFromHologramToHead = length(cubeRelativeToHead);
        static const float distanceMin = 0.00001f;
        if (distanceFromHologramToHead < distanceMin)
        {
            cubeRelativeToHead = float3(0.f, distanceMin, 0.f);
        }

        // Position the spatial sound source on the hologram.
        m_omnidirectionalSound.OnUpdate(cubeRelativeToHead);

        // For debugging, it can be interesting to observe the distance in the debugger.
        /*
        std::wstring distanceString = L"Distance from hologram to head: ";
        distanceString += std::to_wstring(distanceFromHologramToHead);
        distanceString += L"\n";
        OutputDebugStringW(distanceString.c_str());
        */
    }
}

HRTF position は、OmnidirectionalSound helper クラスによって、そのサウンドが APO に直接適用されます。The HRTF position is applied directly to the sound APO by the OmnidirectionalSound helper class.

OmnidirectionalSound:: OnUpdateから:From OmnidirectionalSound::OnUpdate:

HRESULT OmnidirectionalSound::OnUpdate(_In_ Numerics::float3 position)
{
    auto hrtfPosition = HrtfPosition{ position.x, position.y, position.z };
    return _hrtfParams->SetSourcePosition(&hrtfPosition);
}

これで完了です。That's it! HRTF audio と Windows Holographic でできることの詳細については、「」を参照してください。Continue reading to learn more about what you can do with HRTF audio and Windows Holographic.

方向変換元の空間サウンドを初期化するInitialize spatial sound for a directional source

ユーザーの周囲の一部のホログラムは、主に一方向にサウンドを出力します。Some holograms in the user's surroundings emit sound mostly in one direction. このサウンドパターンは、漫画のように見えるため、 cardioidという名前になります。This sound pattern is named cardioid because it looks like a cartoon heart. 次のコードは、APO を初期化して指向性音を出力する方法を示しています。The following code shows how to initialize an APO to emit directional sound. 完全なコードリストについては、「 CardioidSound 」を参照してください。For the complete code listing, see CardioidSound.cpp .

HRTF に対して APO が構成されたら、ソース音声でStartを呼び出してオーディオを再生します。After the APO is configured for HRTF, call Start on the source voice to play the audio.

// Initializes an APO that emits directional sound.
HRESULT CardioidSound::Initialize( LPCWSTR filename )
{
    // _audioFile is of type AudioFileReader, which is defined in AudioFileReader.cpp.
    auto hr = _audioFile.Initialize( filename );
    if ( SUCCEEDED( hr ) )
    {
        // Initialize with "Scaling" fully directional and "Order" with broad radiation pattern.
        // As the order goes higher, the cardioid directivity region becomes narrower.
        // Any direct path signal outside of the directivity region will be attenuated based on the scaling factor.
        // For example, if scaling is set to 1 (fully directional) the direct path signal outside of the directivity
        // region will be fully attenuated and only the reflections from the environment will be audible.
        hr = ConfigureApo( 1.0f, 4.0f );
    }
    return hr;
}

HRESULT CardioidSound::ConfigureApo( float scaling, float order )
{
    // Cardioid directivity configuration:
    // Directivity is specified at xAPO instance initialization and can't be changed per frame.
    // To change directivity, stop audio processing and reinitialize another APO instance with the new directivity.
    HrtfDirectivityCardioid cardioid;
    cardioid.directivity.type = HrtfDirectivityType::Cardioid;
    cardioid.directivity.scaling = scaling;
    cardioid.order = order;

    // APO intialization
    HrtfApoInit apoInit;
    apoInit.directivity = &cardioid.directivity;
    apoInit.distanceDecay = nullptr; // nullptr specifies natural distance decay behavior (simulates real world)

    // CreateHrtfApo fails with E_NOTIMPL on unsupported platforms.
    ComPtr<IXAPO> xapo;
    auto hr = CreateHrtfApo( &apoInit, &xapo );

    if ( SUCCEEDED( hr ) )
    {
        hr = xapo.As( &_hrtfParams );
    }

    // Set the initial environment.
    // Environment settings configure the "distance cues" used to compute the early and late reverberations.
    if ( SUCCEEDED( hr ) )
    {
        hr = _hrtfParams->SetEnvironment( _HrtfEnvironment::Outdoors );
    }

    // Initialize an XAudio2 graph that hosts the HRTF xAPO.
    // The source voice is used to submit audio data and control playback.
    if ( SUCCEEDED( hr ) )
    {
        hr = SetupXAudio2( _audioFile.GetFormat(), xapo.Get(), &_xaudio2, &_sourceVoice );
    }

    // Submit audio data to the source voice
    if ( SUCCEEDED( hr ) )
    {
        XAUDIO2_BUFFER buffer{ };
        buffer.AudioBytes = static_cast<UINT32>( _audioFile.GetSize() );
        buffer.pAudioData = _audioFile.GetData();
        buffer.LoopCount = XAUDIO2_LOOP_INFINITE;
        hr = _sourceVoice->SubmitSourceBuffer( &buffer );
    }

    return hr;
}

カスタムの減衰を実装するImplement custom decay

空間サウンドがどの程度の距離になるか、または完全に外に出てくる距離を上書きすることができます。You can override the rate at which a spatial sound falls off with distance and/or at what distance it cuts off completely. 空間サウンドにカスタムの減衰動作を実装するには、 HrtfDistanceDecay 構造体を設定し、それをHrtfアポストロフィ init 構造体distanceDecayフィールドに代入してから、 createhrtfapo関数に渡します。To implement custom decay behavior on a spatial sound, populate an HrtfDistanceDecay struct and assign it to the distanceDecay field in an HrtfApoInit struct before passing it to the CreateHrtfApo function.

前に示したInitializeメソッドに次のコードを追加して、カスタムの減衰動作を指定します。Add the following code to the Initialize method shown previously to specify custom decay behavior. 完全なコードリストについては、「CustomDecay.cpp」を参照してくださいを参照してください。For the complete code listing, see CustomDecay.cpp.

HRESULT CustomDecaySound::Initialize( LPCWSTR filename )
{
    auto hr = _audioFile.Initialize( filename );

    ComPtr<IXAPO> xapo;
    if ( SUCCEEDED( hr ) )
    {
        HrtfDistanceDecay customDecay;
        customDecay.type = HrtfDistanceDecayType::CustomDecay;               // Custom decay behavior, we'll pass in the gain value on every frame.
        customDecay.maxGain = 0;                                             // 0dB max gain
        customDecay.minGain = -96.0f;                                        // -96dB min gain
        customDecay.unityGainDistance = HRTF_DEFAULT_UNITY_GAIN_DISTANCE;    // Default unity gain distance
        customDecay.cutoffDistance = HRTF_DEFAULT_CUTOFF_DISTANCE;           // Default cutoff distance

        // Setting the directivity to nullptr specifies omnidirectional sound.
        HrtfApoInit init;
        init.directivity = nullptr;
        init.distanceDecay = &customDecay;

        // CreateHrtfApo will fail with E_NOTIMPL on unsupported platforms.
        hr = CreateHrtfApo( &init, &xapo );
    }
...
}