microsoft / DirectXTK12

The DirectX Tool Kit (aka DirectXTK12) is a collection of helper classes for writing DirectX 12 code in C++
https://walbourn.github.io/directx-tool-kit-for-directx-12/
MIT License
1.45k stars 371 forks source link

Mono sound using 3D #92

Closed Flone-dnb closed 3 years ago

Flone-dnb commented 3 years ago

Hi. I've recently written a simple audio engine with 3D sound using XAudio2. The issue is that on this PC I only hear 3D sound coming from the left speaker (headphones). On my other PC with the same headphones, the result is correct. I've asked a buddy to try running my app and he said that the sound is pure mono no matter what the position of the listener/emitter. The issue only occurs when using 3D audio. 2D audio is ok, Windows sound is ok.

I might guess that the issue is not in the code, but how can I distribute such code that works this weirdly? So, any tips on why this may happen?

P.S. I've tried sample code from this repo's wiki - same result.

HRESULT hr = CoInitializeEx(nullptr, COINIT_MULTITHREADED);
if (FAILED(hr)) {...}

std::unique_ptr<DirectX::AudioEngine> audEngine;

DirectX::AUDIO_ENGINE_FLAGS eflags = DirectX::AudioEngine_Default;
#ifdef _DEBUG
    eflags |= DirectX::AudioEngine_Debug;
#endif

std::unique_ptr<DirectX::SoundEffect> soundEffect;
soundEffect = std::make_unique<DirectX::SoundEffect>(audEngine.get(), L"Sound.wav");
auto effect = soundEffect->CreateInstance(DirectX::SoundEffectInstance_Use3D);

effect->Play(false);

DirectX::AudioListener listener;
listener.SetPosition(DirectX::XMFLOAT3(0.0f, 0.0f, 0.0f)); // does not matter

DirectX::AudioEmitter emitter;
emitter.SetPosition(DirectX::XMFLOAT3(5.0f, 0.0f, 0.0f)); // does not matter

effect->Apply3D(listener, emitter, false);
walbourn commented 3 years ago

Where is the code that creates the audEngine?

You are calling audEngine->Update() in a loop somewhere, yes?

walbourn commented 3 years ago

Clearly there's something unusual about your setup. I suggest you run through the tutorial on positional audio with DirectX Tool Kit for Audio.

Also, have you tried other wav files? Is the one you are using multi-channel?

Flone-dnb commented 3 years ago

I'm sorry I didn't include engine creation by mistake, here is the full code (I've referred to this wiki):

        HRESULT hr = CoInitializeEx(nullptr, COINIT_MULTITHREADED);
    if (FAILED(hr)) {}

    std::unique_ptr<DirectX::AudioEngine> audEngine;

    DirectX::AUDIO_ENGINE_FLAGS eflags = DirectX::AudioEngine_Default;
#ifdef _DEBUG
    eflags |= DirectX::AudioEngine_Debug;
#endif

    auto enumList = DirectX::AudioEngine::GetRendererDetails();

    std::wstring deviceId = L"";

    if (enumList.empty())
    {
        std::cout << "No audio devices\n";
        system("pause");
        return 1;
    }
    else
    {
        for (auto it = enumList.cbegin(); it != enumList.cend(); ++it)
        {
            // it->deviceId.c_str() - this is the device/end-point identifier you can
            // pass as a parameter to AudioEngine
            // it->description.c_str() - this is a textual description
        }
    }

    if (deviceId == L"")
    {
        audEngine = std::make_unique<DirectX::AudioEngine>(eflags);
    }
    else
    {
        audEngine = std::make_unique<DirectX::AudioEngine>(eflags, nullptr, deviceId.c_str());
    }

    if (!audEngine->Update()) // Update returns false if no audio is actually playing or critical error
    {
        // No audio device is active
        if (audEngine->IsCriticalError())
        {
            std::cout << "No audio device is active. Attempting to use another device.\n";

            if (audEngine->Reset())
            {
                // OK.
            }
            else
            {
                system("pause");
                return 1;
            }
        }
    }

    std::unique_ptr<DirectX::SoundEffect> soundEffect;
    soundEffect = std::make_unique<DirectX::SoundEffect>(audEngine.get(), L"sound.wav");
    auto effect = soundEffect->CreateInstance(DirectX::SoundEffectInstance_Use3D);
    effect->SetVolume(0.5f);

    effect->Play(false);

    DirectX::AudioListener listener;
    listener.SetPosition(DirectX::XMFLOAT3(0.0f, 0.0f, 0.0f));

    DirectX::AudioEmitter emitter;
    emitter.SetPosition(DirectX::XMFLOAT3(0.0f, 0.0f, -3.0f));

    effect->Apply3D(listener, emitter, false);

    auto stats = audEngine->GetStatistics();
    printf("\nPlaying: %zu / %zu; Instances %zu; Voices %zu / %zu / %zu / %zu;"
        "%zu audio bytes, %zu streaming buffer bytes %\n",
        stats.playingOneShots, stats.playingInstances,
        stats.allocatedInstances, stats.allocatedVoices, stats.allocatedVoices3d,
        stats.allocatedVoicesOneShot, stats.allocatedVoicesIdle,
        stats.audioBytes, stats.streamingBytes);

The sound has 2 channels and the master voice also has 2 channels (I've checked). AFAIK Update() should not be necessary in this example, we only need the Apply3D (which calls the X3DAudioCalculate to set the output matrix). The weird part is. This code works on 1 machine but does not work as expected on another machine. On one machine it's resulting in only left speaker working, on other both are working. And as I've said my buddy checked the sound on his machine it's also not working (on his machine the sound is pure mono).

walbourn commented 3 years ago

XAudio2 is a multi-threaded API, so all the calls are "non-blocking". You need to enter a loop somewhere to wait for the sound to finish playing... where do you do that?

For example, after you call Apply3D you need to so something like this which waits until the sound finishes playing...

while ( effect->GetState() == PLAYING )
{
    if (!audEngine->Update()) // Update returns false if no audio is actually playing or critical error
    {
        // No audio device is active
        if (audEngine->IsCriticalError())
        {
            std::cout << "No audio device is active. Attempting to use another device.\n";

            if (audEngine->Reset())
            {
                // OK.
            }
            else
            {
                system("pause");
                return 1;
            }
        }
    }

        // Sleep for 1 second
        Sleep(1000);
}
Flone-dnb commented 3 years ago

I have system("pause"); after GetStatistics. I now added the above code after GetStatistics and printf and it didn't change anything. Still hearing the sound only in the left speaker.

walbourn commented 3 years ago

If this is a non-mono .wav file, you need to set emitter.ChannelCount = 2;.

walbourn commented 3 years ago

If the ChannelCount doesn't fix it, please try some other program using X3DAudio to see if that works on your system or not. Try the tutorial, XAudio2Sound3D, SimplePlay3DSoundUWP, or the Audio3DTest unit tester.

Flone-dnb commented 3 years ago

If this is a non-mono .wav file, you need to set emitter.ChannelCount = 2;.

With this, the sound is indeed using both speakers but the panning is not applied anymore. No matter how I position listener/emitter the only thing that is changing is the volume of the sound and it is applied equally to both speakers. So now it's pure mono.

Flone-dnb commented 3 years ago

I've converted my audio to mono sound (1 channel) and now it works as expected (emitter.ChannelCount = 1 was also changed back), the issue is resolved. But there is another question. Why does handling stereo 3D sounds is so weird?

shawnhar commented 3 years ago

It doesn't really make sense to apply 3D positioning to a sound source that is already stereo. There are other ways to adjust the mix, routing various amounts of each source audio channel to the available output channels, but "place the whole audio at this specific 3D position" is only a well defined operation for mono source data.

walbourn commented 3 years ago

Makes sense. Glad to know it was the ChannelCount after all. I've updated the wiki with more details on this issue...

DirectSound's 3D positioning only supported mono-channel sounds, and as @shawnhar notes those 'make the most sense'.

XAudio2/X3DAudio supports 3D positioning of multi-channel sounds for some specialized scenarios of groups of sounds, but you have to set the emitter.EmitterAzimuths array values for each channel to get anything meaningful for panning. You may find this presentation from GameFest 2010 useful if you want to know more.

walbourn commented 3 years ago

Thanks again for the issue report. It motivated me to address these two open issues for DirectX Tool Kit for Audio:

https://github.com/microsoft/DirectXTK/issues/82

https://github.com/microsoft/DirectXTK/issues/4