void AudioEngine::Impl::AllocateVoice()

in Audio/AudioEngine.cpp [823:1028]


void AudioEngine::Impl::AllocateVoice(
    const WAVEFORMATEX* wfx,
    SOUND_EFFECT_INSTANCE_FLAGS flags,
    bool oneshot,
    IXAudio2SourceVoice** voice)
{
    if (!wfx)
        throw std::invalid_argument("Wave format is required\n");

    // No need to call IsValid on wfx because CreateSourceVoice will do that

    if (!voice)
        throw std::invalid_argument("Voice pointer must be non-null");

    *voice = nullptr;

    if (!xaudio2 || mCriticalError)
        return;

#ifndef NDEBUG
    const float maxFrequencyRatio = XAudio2SemitonesToFrequencyRatio(12);
    assert(maxFrequencyRatio <= XAUDIO2_DEFAULT_FREQ_RATIO);
#endif

    unsigned int voiceKey = 0;
    if (oneshot)
    {
        if (flags & (SoundEffectInstance_Use3D | SoundEffectInstance_ReverbUseFilters | SoundEffectInstance_NoSetPitch))
        {
            DebugTrace((flags & SoundEffectInstance_NoSetPitch)
                       ? "ERROR: One-shot voices must support pitch-shifting for voice reuse\n"
                       : "ERROR: One-use voices cannot use 3D positional audio\n");
            throw std::invalid_argument("Invalid flags for one-shot voice");
        }

    #ifdef VERBOSE_TRACE
        if (wfx->wFormatTag == WAVE_FORMAT_EXTENSIBLE)
        {
            DebugTrace("INFO: Requesting one-shot: Format Tag EXTENSIBLE %u, %u channels, %u-bit, %u blkalign, %u Hz\n",
                GetFormatTag(wfx), wfx->nChannels, wfx->wBitsPerSample, wfx->nBlockAlign, wfx->nSamplesPerSec);
        }
        else
        {
            DebugTrace("INFO: Requesting one-shot: Format Tag %u, %u channels, %u-bit, %u blkalign, %u Hz\n",
                wfx->wFormatTag, wfx->nChannels, wfx->wBitsPerSample, wfx->nBlockAlign, wfx->nSamplesPerSec);
        }
    #endif

        if (!(mEngineFlags & AudioEngine_DisableVoiceReuse))
        {
            voiceKey = makeVoiceKey(wfx);
            if (voiceKey != 0)
            {
                auto it = mVoicePool.find(voiceKey);
                if (it != mVoicePool.end())
                {
                    // Found a matching (stopped) voice to reuse
                    assert(it->second != nullptr);
                    *voice = it->second;
                    mVoicePool.erase(it);

                    // Reset any volume/pitch-shifting
                    HRESULT hr = (*voice)->SetVolume(1.f);
                    ThrowIfFailed(hr);

                    hr = (*voice)->SetFrequencyRatio(1.f);
                    ThrowIfFailed(hr);

                    if (wfx->nChannels == 1 || wfx->nChannels == 2)
                    {
                        // Reset any panning
                        float matrix[16] = {};
                        ComputePan(0.f, wfx->nChannels, matrix);

                        hr = (*voice)->SetOutputMatrix(nullptr, wfx->nChannels, masterChannels, matrix);
                        ThrowIfFailed(hr);
                    }
                }
                else if ((mVoicePool.size() + mOneShots.size() + 1) >= maxVoiceOneshots)
                {
                    DebugTrace("WARNING: Too many one-shot voices in use (%zu + %zu >= %zu); one-shot not played\n",
                               mVoicePool.size(), mOneShots.size() + 1, maxVoiceOneshots);
                    return;
                }
                else
                {
                    // makeVoiceKey already constrained the supported wfx formats to those supported for reuse

                    char buff[64] = {};
                    auto wfmt = reinterpret_cast<WAVEFORMATEX*>(buff);

                    uint32_t tag = GetFormatTag(wfx);
                    switch (tag)
                    {
                        case WAVE_FORMAT_PCM:
                            CreateIntegerPCM(wfmt, defaultRate, wfx->nChannels, wfx->wBitsPerSample);
                            break;

                        case WAVE_FORMAT_IEEE_FLOAT:
                            CreateFloatPCM(wfmt, defaultRate, wfx->nChannels);
                            break;

                        case WAVE_FORMAT_ADPCM:
                        {
                            auto wfadpcm = reinterpret_cast<const ADPCMWAVEFORMAT*>(wfx);
                            CreateADPCM(wfmt, sizeof(buff), defaultRate, wfx->nChannels, wfadpcm->wSamplesPerBlock);
                        }
                        break;

                        #ifdef DIRECTX_ENABLE_XMA2
                        case WAVE_FORMAT_XMA2:
                            CreateXMA2(wfmt, sizeof(buff), defaultRate, wfx->nChannels, 65536, 2, 0);
                            break;
                        #endif
                    }

                #ifdef VERBOSE_TRACE
                    DebugTrace("INFO: Allocate reuse voice: Format Tag %u, %u channels, %u-bit, %u blkalign, %u Hz\n",
                        wfmt->wFormatTag, wfmt->nChannels, wfmt->wBitsPerSample, wfmt->nBlockAlign, wfmt->nSamplesPerSec);
                #endif

                    assert(voiceKey == makeVoiceKey(wfmt));

                    HRESULT hr = xaudio2->CreateSourceVoice(voice, wfmt, 0, XAUDIO2_DEFAULT_FREQ_RATIO, &mVoiceCallback, nullptr, nullptr);
                    if (FAILED(hr))
                    {
                        DebugTrace("ERROR: CreateSourceVoice (reuse) failed with error %08X\n", static_cast<unsigned int>(hr));
                        throw std::runtime_error("CreateSourceVoice");
                    }
                }

                assert(*voice != nullptr);
                HRESULT hr = (*voice)->SetSourceSampleRate(wfx->nSamplesPerSec);
                if (FAILED(hr))
                {
                    DebugTrace("ERROR: SetSourceSampleRate failed with error %08X\n", static_cast<unsigned int>(hr));
                    throw std::runtime_error("SetSourceSampleRate");
                }
            }
        }
    }

    if (!*voice)
    {
        if (oneshot)
        {
            if ((mVoicePool.size() + mOneShots.size() + 1) >= maxVoiceOneshots)
            {
                DebugTrace("WARNING: Too many one-shot voices in use (%zu + %zu >= %zu); one-shot not played; see TrimVoicePool\n",
                           mVoicePool.size(), mOneShots.size() + 1, maxVoiceOneshots);
                return;
            }
        }
        else if ((mVoiceInstances + 1) >= maxVoiceInstances)
        {
            DebugTrace("ERROR: Too many instance voices (%zu >= %zu); see TrimVoicePool\n",
                mVoiceInstances + 1, maxVoiceInstances);
            throw std::runtime_error("Too many instance voices");
        }

        UINT32 vflags = (flags & SoundEffectInstance_NoSetPitch) ? XAUDIO2_VOICE_NOPITCH : 0u;

        HRESULT hr;
        if (flags & SoundEffectInstance_Use3D)
        {
            XAUDIO2_SEND_DESCRIPTOR sendDescriptors[2] = {};
            sendDescriptors[0].Flags = sendDescriptors[1].Flags = (flags & SoundEffectInstance_ReverbUseFilters)
                ? XAUDIO2_SEND_USEFILTER : 0u;
            sendDescriptors[0].pOutputVoice = mMasterVoice;
            sendDescriptors[1].pOutputVoice = mReverbVoice;
            const XAUDIO2_VOICE_SENDS sendList = { mReverbVoice ? 2U : 1U, sendDescriptors };

        #ifdef VERBOSE_TRACE
            DebugTrace("INFO: Allocate voice 3D: Format Tag %u, %u channels, %u-bit, %u blkalign, %u Hz\n",
                wfx->wFormatTag, wfx->nChannels, wfx->wBitsPerSample, wfx->nBlockAlign, wfx->nSamplesPerSec);
        #endif

            hr = xaudio2->CreateSourceVoice(voice, wfx, vflags, XAUDIO2_DEFAULT_FREQ_RATIO, &mVoiceCallback, &sendList, nullptr);
        }
        else
        {
        #ifdef VERBOSE_TRACE
            DebugTrace("INFO: Allocate voice: Format Tag %u, %u channels, %u-bit, %u blkalign, %u Hz\n",
                wfx->wFormatTag, wfx->nChannels, wfx->wBitsPerSample, wfx->nBlockAlign, wfx->nSamplesPerSec);
        #endif

            hr = xaudio2->CreateSourceVoice(voice, wfx, vflags, XAUDIO2_DEFAULT_FREQ_RATIO, &mVoiceCallback, nullptr, nullptr);
        }

        if (FAILED(hr))
        {
            DebugTrace("ERROR: CreateSourceVoice failed with error %08X\n", static_cast<unsigned int>(hr));
            throw std::runtime_error("CreateSourceVoice");
        }
        else if (!oneshot)
        {
            ++mVoiceInstances;
        }
    }

    if (oneshot)
    {
        assert(*voice != nullptr);
        mOneShots.emplace_back(std::make_pair(voiceKey, *voice));
    }
}