diff --git a/engine/audio/private/snd_dev_direct.cpp b/engine/audio/private/snd_dev_direct.cpp index d8758dc3c..c65154dbd 100644 --- a/engine/audio/private/snd_dev_direct.cpp +++ b/engine/audio/private/snd_dev_direct.cpp @@ -10,6 +10,8 @@ #include #include +#include + #include "com_ptr.h" #include "iprediction.h" #include "eax.h" @@ -38,21 +40,18 @@ typedef enum {SIS_SUCCESS, SIS_FAILURE, SIS_NOTAVAIL} sndinitstat; #define SECONDARY_BUFFER_SIZE 0x10000 // output buffer size in bytes #define SECONDARY_BUFFER_SIZE_SURROUND 0x04000 // output buffer size in bytes, one per channel - -using DirectSoundCreate8Fn = decltype(&DirectSoundCreate8); -DirectSoundCreate8Fn pDirectSoundCreate; - -extern void ReleaseSurround(void); +extern void ReleaseSurround(); extern bool MIX_ScaleChannelVolume( paintbuffer_t *ppaint, channel_t *pChannel, int volume[CCHANVOLUMES], int mixchans ); + void OnSndSurroundCvarChanged( IConVar *var, const char *pOldString, float flOldValue ); void OnSndSurroundLegacyChanged( IConVar *var, const char *pOldString, float flOldValue ); void OnSndVarChanged( IConVar *var, const char *pOldString, float flOldValue ); -static LPDIRECTSOUND8 pDS = NULL; -static LPDIRECTSOUNDBUFFER pDSBuf = NULL; +static LPDIRECTSOUND8 pDS = nullptr; +static LPDIRECTSOUNDBUFFER pDSBuf = nullptr; // For the primary buffer, you must use the IDirectSoundBuffer interface; IDirectSoundBuffer8 is not available. // See https://learn.microsoft.com/en-us/previous-versions/windows/desktop/ee418055(v=vs.85) -static LPDIRECTSOUNDBUFFER pDSPBuf = NULL; +static LPDIRECTSOUNDBUFFER pDSPBuf = nullptr; static GUID IID_IDirectSound3DBufferDef = {0x279AFA86, 0x4981, 0x11CE, {0xA5, 0x21, 0x00, 0x20, 0xAF, 0x0B, 0xE5, 0x60}}; static ConVar windows_speaker_config("windows_speaker_config", "-1", FCVAR_ARCHIVE); @@ -60,6 +59,11 @@ static DWORD g_ForcedSpeakerConfig = 0; extern ConVar snd_mute_losefocus; +[[nodiscard]] constexpr short AdjustSampleVolume(int sample, + int volume_factor) noexcept { + return CLIP((sample * volume_factor) >> 8); +} + //----------------------------------------------------------------------------- // Purpose: Implementation of direct sound //----------------------------------------------------------------------------- @@ -107,7 +111,7 @@ class CAudioDirectSound : public CAudioDeviceBase bool SNDDMA_InitInterleaved( LPDIRECTSOUND8 lpDS, WAVEFORMATEX* lpFormat, int channelCount ); bool SNDDMA_InitSurround(LPDIRECTSOUND8 lpDS, WAVEFORMATEX* lpFormat, DSBCAPS* lpdsbc, int cchan); void S_TransferSurround16( portable_samplepair_t *pfront, portable_samplepair_t *prear, portable_samplepair_t *pcenter, int lpaintedtime, int endtime, int cchan); - void S_TransferSurround16Interleaved( const portable_samplepair_t *pfront, const portable_samplepair_t *prear, const portable_samplepair_t *pcenter, int lpaintedtime, int endtime); + void S_TransferSurround16Interleaved( portable_samplepair_t *pfront, portable_samplepair_t *prear, portable_samplepair_t *pcenter, int lpaintedtime, int endtime); void S_TransferSurround16Interleaved_FullLock( const portable_samplepair_t *pfront, const portable_samplepair_t *prear, const portable_samplepair_t *pcenter, int lpaintedtime, int endtime); int m_deviceChannels; // channels per hardware output buffer (1 for quad/5.1, 2 for stereo) @@ -157,9 +161,9 @@ bool CAudioDirectSound::Init( void ) first = false; } - if ( SNDDMA_InitDirect() == SIS_SUCCESS) + if ( SNDDMA_InitDirect() == SIS_SUCCESS ) { - if ( g_pVideo != NULL ) + if ( g_pVideo ) { g_pVideo->SoundDeviceCommand( VideoSoundDeviceOperation::SET_DIRECT_SOUND_DEVICE, pDS ); } @@ -170,10 +174,10 @@ bool CAudioDirectSound::Init( void ) return false; } -void CAudioDirectSound::Shutdown( void ) +void CAudioDirectSound::Shutdown() { // dimhotepus: Notify video we shut down sound device. - if ( g_pVideo != NULL ) + if ( g_pVideo ) { g_pVideo->SoundDeviceCommand( VideoSoundDeviceOperation::SET_DIRECT_SOUND_DEVICE, nullptr ); } @@ -225,7 +229,6 @@ void CAudioDirectSound::Shutdown( void ) // start position. int CAudioDirectSound::GetOutputPosition( void ) { - int samp16; int start, current; DWORD dwCurrent; @@ -249,7 +252,8 @@ int CAudioDirectSound::GetOutputPosition( void ) start = (int) m_outputBufferStartOffset; current = (int) dwCurrent; } - + + int samp16; // get 16 bit samples played, relative to buffer starting offset if (current > start) { @@ -303,9 +307,7 @@ float CAudioDirectSound::MixDryVolume( void ) bool CAudioDirectSound::Should3DMix( void ) { - if ( m_bSurround ) - return true; - return false; + return m_bSurround; } @@ -668,10 +670,8 @@ bool CAudioDirectSound::SNDDMA_InitInterleaved( LPDIRECTSOUND8 lpDS, WAVEFORMATE dsbdesc.dwFlags = 0; break; } - if ( !snd_mute_losefocus.GetBool() ) - { - dsbdesc.dwFlags |= DSBCAPS_GLOBALFOCUS; - } + + dsbdesc.dwFlags |= DSBCAPS_GLOBALFOCUS; se::win::com::com_ptr buffer; if(!FAILED(lpDS->CreateSoundBuffer(&dsbdesc, &buffer, NULL)) && @@ -719,49 +719,48 @@ Direct-Sound support */ sndinitstat CAudioDirectSound::SNDDMA_InitDirect( void ) { - DSBUFFERDESC dsbuf; - DSBCAPS dsbcaps; - DWORD dwSize, dwWrite; - WAVEFORMATEX format; - WAVEFORMATEX pformat; - HRESULT hresult; - void *lpData = NULL; - bool primary_format_set = false; - int pri_channels = 2; - if (!m_hInstDS) { m_hInstDS = LoadLibraryExA( "dsound.dll", nullptr, LOAD_LIBRARY_SEARCH_SYSTEM32 ); - if (m_hInstDS == NULL) - { - Warning( "Couldn't load dsound.dll\n"); - return SIS_FAILURE; - } - - pDirectSoundCreate = (DirectSoundCreate8Fn)GetProcAddress(m_hInstDS, V_STRINGIFY(DirectSoundCreate8) ); - if (!pDirectSoundCreate) + if (!m_hInstDS) { - Warning( "Couldn't get DS proc addr\n"); + Warning( "dsound [init]: Unable to load 'dsound.dll': %s.\n", + std::system_category().message( GetLastError() ).c_str() ); return SIS_FAILURE; } } + + using DirectSoundCreate8Function = decltype(&DirectSoundCreate8); + const auto direct_sound_create8 = + reinterpret_cast( + GetProcAddress( m_hInstDS, V_STRINGIFY(DirectSoundCreate8) ) ); + if (!direct_sound_create8) + { + Warning( "dsound [init]: Unable to get '%s' from 'dsound.dll': %s.\n", + V_STRINGIFY(DirectSoundCreate8), + std::system_category().message( GetLastError() ).c_str() ); + return SIS_FAILURE; + } - if ((hresult = pDirectSoundCreate(NULL, &pDS, NULL)) != DS_OK) + HRESULT hr; + if (FAILED(hr = direct_sound_create8(nullptr, &pDS, nullptr))) { - if (hresult != DSERR_ALLOCATED) + // The request failed because resources, such as a priority level, + // were already in use by another caller. + if (hr != DSERR_ALLOCATED) { - DevMsg ("DirectSound create failed w/e 0x%8x\n", hresult); + DevMsg( "dsound [init]: DirectSoundCreate8 failed w/e 0x%8x.\n", hr ); return SIS_FAILURE; } return SIS_NOTAVAIL; } - DWORD isCertified = DS_UNCERTIFIED; - hresult = pDS->VerifyCertification( &isCertified ); - if ( SUCCEEDED(hresult) && isCertified == DS_CERTIFIED ) + DWORD is_certified{DS_UNCERTIFIED}; + hr = pDS->VerifyCertification( &is_certified ); + if ( SUCCEEDED(hr) && is_certified == DS_CERTIFIED ) { - Msg( "Direct Sound card driver is certified." ); + Msg( "dsound [init]: DirectSound8 card driver is certified.\n" ); } // get snd_surround value from window settings @@ -771,6 +770,8 @@ sndinitstat CAudioDirectSound::SNDDMA_InitDirect( void ) m_bSurroundCenter = false; m_bHeadphone = false; m_isInterleaved = false; + + unsigned short pri_channels = 2; switch ( snd_surround.GetInt() ) { @@ -788,7 +789,7 @@ sndinitstat CAudioDirectSound::SNDDMA_InitDirect( void ) break; case 5: case 7: - m_bSurround = true; // 5.1 surround + m_bSurround = true; // 5.1 / 7.1 surround m_bSurroundCenter = true; pri_channels = 1; // primary buffer mixes 3d mono input data break; @@ -797,72 +798,79 @@ sndinitstat CAudioDirectSound::SNDDMA_InitDirect( void ) m_deviceChannels = pri_channels; // secondary buffers should have same # channels as primary m_deviceSampleBits = 16; // hardware bits per sample m_deviceDmaSpeed = SOUND_DMA_SPEED; // hardware playback rate - - Q_memset( &format, 0, sizeof(format) ); - format.wFormatTag = WAVE_FORMAT_PCM; - format.nChannels = pri_channels; - format.wBitsPerSample = m_deviceSampleBits; - format.nSamplesPerSec = m_deviceDmaSpeed; - format.nBlockAlign = format.nChannels * format.wBitsPerSample / 8; - format.cbSize = 0; - format.nAvgBytesPerSec = format.nSamplesPerSec * format.nBlockAlign; - - DSCAPS dscaps; - Q_memset( &dscaps, 0, sizeof(dscaps) ); - dscaps.dwSize = sizeof(dscaps); - if (DS_OK != pDS->GetCaps(&dscaps)) - { - Warning( "Couldn't get DS caps\n"); + + WAVEFORMATEX primary_format = {}; + primary_format.wFormatTag = WAVE_FORMAT_PCM; + primary_format.nChannels = pri_channels; + primary_format.wBitsPerSample = m_deviceSampleBits; + primary_format.nSamplesPerSec = m_deviceDmaSpeed; + primary_format.nBlockAlign = primary_format.nChannels * primary_format.wBitsPerSample / 8; + primary_format.cbSize = 0; + primary_format.nAvgBytesPerSec = primary_format.nSamplesPerSec * primary_format.nBlockAlign; + + DSCAPS device_caps = {}; + device_caps.dwSize = sizeof(device_caps); + if (FAILED(hr = pDS->GetCaps(&device_caps))) + { + Warning( "dsound [init]: Unable to query DirectSound8 device capabilities w/e 0x%8x.\n", hr ); + Shutdown(); + return SIS_FAILURE; } - if (dscaps.dwFlags & DSCAPS_EMULDRIVER) + if (device_caps.dwFlags & DSCAPS_EMULDRIVER) { - Warning( "No DirectSound driver installed\n"); + Warning( "dsound [init]: DirectSound8 driver is not installed.\n"); Shutdown(); return SIS_FAILURE; } - if (DS_OK != pDS->SetCooperativeLevel(*pmainwindow, DSSCL_EXCLUSIVE)) + if (FAILED(hr = pDS->SetCooperativeLevel(*pmainwindow, DSSCL_PRIORITY))) { - Warning( "Set coop level failed\n"); + Warning( "dsound [init]: DirectSound8 device apply cooperative level DSSCL_PRIORITY failed w/e 0x%8x.\n", hr ); Shutdown(); return SIS_FAILURE; } // get access to the primary buffer, if possible, so we can set the // sound hardware format - Q_memset( &dsbuf, 0, sizeof(dsbuf) ); - dsbuf.dwSize = sizeof(DSBUFFERDESC); - dsbuf.dwFlags = DSBCAPS_PRIMARYBUFFER; + DSBUFFERDESC buffer_desc = {}; + buffer_desc.dwSize = sizeof(DSBUFFERDESC); + buffer_desc.dwFlags = DSBCAPS_PRIMARYBUFFER; if ( snd_legacy_surround.GetBool() || m_bSurround ) { - dsbuf.dwFlags |= DSBCAPS_CTRL3D; + buffer_desc.dwFlags |= DSBCAPS_CTRL3D; } - dsbuf.dwBufferBytes = 0; - dsbuf.lpwfxFormat = NULL; - - Q_memset( &dsbcaps, 0, sizeof(dsbcaps) ); - dsbcaps.dwSize = sizeof(dsbcaps); - + buffer_desc.dwBufferBytes = 0; + buffer_desc.lpwfxFormat = nullptr; + + DSBCAPS buffer_caps = {}; + buffer_caps.dwSize = sizeof(buffer_caps); + + bool primary_format_set = false; if ( !CommandLine()->CheckParm("-snoforceformat")) { - if (DS_OK == pDS->CreateSoundBuffer(&dsbuf, &pDSPBuf, NULL)) + if (SUCCEEDED(hr = pDS->CreateSoundBuffer(&buffer_desc, &pDSPBuf, nullptr))) { - pformat = format; - - if (DS_OK != pDSPBuf->SetFormat(&pformat)) + WAVEFORMATEX format = primary_format; + if (FAILED(pDSPBuf->SetFormat(&format))) { if (snd_firsttime) - DevMsg ("Set primary sound buffer format: no\n"); + DevMsg ("dsound: Set primary sound buffer format: no\n"); } else { if (snd_firsttime) - DevMsg ("Set primary sound buffer format: yes\n"); + DevMsg ("dsound: Set primary sound buffer format: yes\n"); primary_format_set = true; } } + else + { + Warning( "dsound [init]: DirectSound8 device unable to create sound buffer w/e 0x%8x.\n", hr ); + Shutdown(); + return SIS_FAILURE; + } } if ( m_bSurround ) @@ -874,12 +882,12 @@ sndinitstat CAudioDirectSound::SNDDMA_InitDirect( void ) if (snd_surround.GetInt() == 4) { // attempt to init 4 channel surround - m_bSurround = SNDDMA_InitSurround(pDS, &format, &dsbcaps, 4); + m_bSurround = SNDDMA_InitSurround(pDS, &primary_format, &buffer_caps, 4); } else if (snd_surround.GetInt() == 5 || snd_surround.GetInt() == 7) { // attempt to init 5 channel surround - m_bSurroundCenter = SNDDMA_InitSurround(pDS, &format, &dsbcaps, 5); + m_bSurroundCenter = SNDDMA_InitSurround(pDS, &primary_format, &buffer_caps, 5); m_bSurround = m_bSurroundCenter; } } @@ -891,117 +899,149 @@ sndinitstat CAudioDirectSound::SNDDMA_InitDirect( void ) pri_channels = 4; } - m_bSurround = SNDDMA_InitInterleaved( pDS, &format, pri_channels ); + m_bSurround = SNDDMA_InitInterleaved( pDS, &primary_format, pri_channels ); } } if ( !m_bSurround ) { - // snd_surround.SetValue( 0 ); if ( !primary_format_set || !CommandLine()->CheckParm ("-primarysound") ) { // create the secondary buffer we'll actually work with - Q_memset( &dsbuf, 0, sizeof(dsbuf) ); - dsbuf.dwSize = sizeof(DSBUFFERDESC); - dsbuf.dwFlags = DSBCAPS_LOCSOFTWARE; // NOTE: don't use CTRLFREQUENCY (slow) - dsbuf.dwBufferBytes = SECONDARY_BUFFER_SIZE; - dsbuf.lpwfxFormat = &format; - if ( !snd_mute_losefocus.GetBool() ) - { - dsbuf.dwFlags |= DSBCAPS_GLOBALFOCUS; - } + Q_memset( &buffer_desc, 0, sizeof(buffer_desc) ); + buffer_desc.dwSize = sizeof(DSBUFFERDESC); + buffer_desc.dwFlags = DSBCAPS_LOCSOFTWARE; // NOTE: don't use CTRLFREQUENCY (slow) + buffer_desc.dwBufferBytes = SECONDARY_BUFFER_SIZE; + buffer_desc.lpwfxFormat = &primary_format; + buffer_desc.dwFlags |= DSBCAPS_GLOBALFOCUS; se::win::com::com_ptr buffer; - if (DS_OK != pDS->CreateSoundBuffer(&dsbuf, &buffer, NULL) || - FAILED(buffer.QueryInterface(IID_IDirectSoundBuffer8, &pDSBuf))) + if (FAILED(hr = pDS->CreateSoundBuffer(&buffer_desc, &buffer, nullptr)) || + FAILED(hr = buffer.QueryInterface(IID_IDirectSoundBuffer8, &pDSBuf))) { - Warning( "DS:CreateSoundBuffer Failed"); + Warning( "dsound [init]: DirectSound8 device unable to create sound buffer 8 w/e 0x%8x.\n", hr ); Shutdown(); return SIS_FAILURE; } - m_deviceChannels = format.nChannels; - m_deviceSampleBits = format.wBitsPerSample; - m_deviceDmaSpeed = format.nSamplesPerSec; + m_deviceChannels = primary_format.nChannels; + m_deviceSampleBits = primary_format.wBitsPerSample; + m_deviceDmaSpeed = primary_format.nSamplesPerSec; - Q_memset(&dsbcaps, 0, sizeof(dsbcaps)); - dsbcaps.dwSize = sizeof(dsbcaps); + Q_memset(&buffer_caps, 0, sizeof(buffer_caps)); + buffer_caps.dwSize = sizeof(buffer_caps); - if (DS_OK != pDSBuf->GetCaps( &dsbcaps )) + if (DS_OK != pDSBuf->GetCaps( &buffer_caps )) { - Warning( "DS:GetCaps failed\n"); + Warning( "dsound [init]: Unable to query DirectSound8 buffer capabilities w/e 0x%8x.\n", hr ); Shutdown(); return SIS_FAILURE; } if ( snd_firsttime ) - DevMsg ("Using secondary sound buffer\n"); + DevMsg ("dsound [init]: Using secondary sound buffer.\n"); } else { - if (DS_OK != pDS->SetCooperativeLevel(*pmainwindow, DSSCL_WRITEPRIMARY)) + if (FAILED(pDS->SetCooperativeLevel(*pmainwindow, DSSCL_WRITEPRIMARY))) { - Warning( "Set coop level failed\n"); + Warning( "dsound [init]: DirectSound8 device apply cooperative level DSSCL_WRITEPRIMARY failed w/e 0x%8x.\n", hr ); Shutdown(); return SIS_FAILURE; } - Q_memset(&dsbcaps, 0, sizeof(dsbcaps)); - dsbcaps.dwSize = sizeof(dsbcaps); - if (DS_OK != pDSPBuf->GetCaps(&dsbcaps)) + Q_memset(&buffer_caps, 0, sizeof(buffer_caps)); + buffer_caps.dwSize = sizeof(buffer_caps); + if (FAILED(pDSPBuf->GetCaps(&buffer_caps))) { - Msg ("DS:GetCaps failed\n"); + Warning( "dsound [init]: Unable to query DirectSound8 buffer capabilities w/e 0x%8x.\n", hr ); + Shutdown(); return SIS_FAILURE; } pDSBuf = pDSPBuf; - DevMsg ("Using primary sound buffer\n"); + DevMsg ("dsound [init]: Using primary sound buffer\n"); } if ( snd_firsttime ) { - DevMsg(" %d channel(s) | %d bits/sample | %d samples/sec\n", + DevMsg(" %d channel(s) | %d bits/sample | %d Hz\n", DeviceChannels(), DeviceSampleBits(), DeviceDmaSpeed()); } // initialize the buffer - m_bufferSizeBytes = dsbcaps.dwBufferBytes; - int reps = 0; - while ((hresult = pDSBuf->Lock(0, m_bufferSizeBytes, (void**)&lpData, &dwSize, NULL, NULL, 0)) != DS_OK) + m_bufferSizeBytes = buffer_caps.dwBufferBytes; + + DWORD data_size; + void *buffer_data = nullptr; + unsigned retries_count = 0; + while (FAILED(hr = pDSBuf->Lock(0, m_bufferSizeBytes, &buffer_data, &data_size, nullptr, nullptr, 0))) { - if (hresult != DSERR_BUFFERLOST) + if (hr != DSERR_BUFFERLOST) { - Warning( "SNDDMA_InitDirect: DS::Lock Sound Buffer Failed\n"); + Warning( "dsound [init]: Unable to lock DirectSound8 buffer w/e 0x%8x.\n", hr ); Shutdown(); return SIS_FAILURE; } - if (++reps > 10000) + if (++retries_count > 10000u) { - Warning( "SNDDMA_InitDirect: DS: couldn't restore buffer\n"); + Warning( "dsound [init]: Unable to restore DirectSound8 buffer w/e 0x%8x.\n", hr ); Shutdown(); return SIS_FAILURE; } } - Q_memset( lpData, 0, dwSize ); - pDSBuf->Unlock(lpData, dwSize, NULL, 0); + Q_memset( buffer_data, 0, data_size ); + hr = pDSBuf->Unlock(buffer_data, data_size, NULL, 0); + if (FAILED(hr)) + { + Warning( "dsound [init]: Unable to lock DirectSound8 buffer w/e 0x%8x.\n", hr ); + Shutdown(); + return SIS_FAILURE; + } - // Make sure mixer is active (this was moved after the zeroing to avoid popping on startup -- at least when using the dx9.0b debug .dlls) - pDSBuf->Play(0, 0, DSBPLAY_LOOPING); + // Make sure mixer is active (this was moved after the zeroing to avoid + // popping on startup -- at least when using the dx9.0b debug .dlls) + hr = pDSBuf->Play(0, 0, DSBPLAY_LOOPING); + if (FAILED(hr)) + { + Warning( "dsound [init]: Unable to play DirectSound8 buffer w/e 0x%8x.\n", hr ); + Shutdown(); + return SIS_FAILURE; + } // we don't want anyone to access the buffer directly w/o locking it first. - lpData = NULL; - - pDSBuf->Stop(); + buffer_data = NULL; - pDSBuf->GetCurrentPosition(&m_outputBufferStartOffset, &dwWrite); + hr = pDSBuf->Stop(); + if (FAILED(hr)) + { + Warning( "dsound [init]: Unable to stop DirectSound8 buffer w/e 0x%8x.\n", hr ); + Shutdown(); + return SIS_FAILURE; + } + + DWORD write_buffer_pos; + hr = pDSBuf->GetCurrentPosition(&m_outputBufferStartOffset, &write_buffer_pos); + if (FAILED(hr)) + { + Warning( "dsound [init]: Unable to get current position for DirectSound8 buffer w/e 0x%8x.\n", hr ); + Shutdown(); + return SIS_FAILURE; + } - pDSBuf->Play(0, 0, DSBPLAY_LOOPING); + hr = pDSBuf->Play(0, 0, DSBPLAY_LOOPING); + if (FAILED(hr)) + { + Warning( "dsound [init]: Unable to play DSBPLAY_LOOPING DirectSound8 buffer w/e 0x%8x.\n", hr ); + Shutdown(); + return SIS_FAILURE; + } } // number of mono samples output buffer may hold - m_deviceSampleCount = m_bufferSizeBytes/(DeviceSampleBytes()); + m_deviceSampleCount = m_bufferSizeBytes/ DeviceSampleBytes(); return SIS_SUCCESS; @@ -1016,27 +1056,27 @@ static DWORD GetSpeakerConfigForSurroundMode( int surroundMode, const char **pCo { case 0: newSpeakerConfig = DSSPEAKER_HEADPHONE; - speakerConfigDesc = "headphone"; + speakerConfigDesc = "Headphone"; break; case 2: default: newSpeakerConfig = DSSPEAKER_STEREO; - speakerConfigDesc = "stereo speaker"; + speakerConfigDesc = "Stereo speaker"; break; case 4: newSpeakerConfig = DSSPEAKER_QUAD; - speakerConfigDesc = "quad speaker"; + speakerConfigDesc = "Quad speaker"; break; case 5: - newSpeakerConfig = DSSPEAKER_5POINT1; + newSpeakerConfig = DSSPEAKER_5POINT1_BACK; speakerConfigDesc = "5.1 speaker"; break; case 7: - newSpeakerConfig = DSSPEAKER_7POINT1; + newSpeakerConfig = DSSPEAKER_7POINT1_WIDE; speakerConfigDesc = "7.1 speaker"; break; } @@ -1059,9 +1099,9 @@ static DWORD GetWindowsSpeakerConfig() // split out settings speaker_config = DSSPEAKER_CONFIG(speaker_config); if ( speaker_config == DSSPEAKER_7POINT1_SURROUND ) - speaker_config = DSSPEAKER_7POINT1; + speaker_config = DSSPEAKER_7POINT1_WIDE; if ( speaker_config == DSSPEAKER_5POINT1_SURROUND) - speaker_config = DSSPEAKER_5POINT1; + speaker_config = DSSPEAKER_5POINT1_BACK; } windows_speaker_config.SetValue((int)speaker_config); } @@ -1110,25 +1150,25 @@ void CAudioDirectSound::DetectWindowsSpeakerSetup() // DEBUG if (speaker_config == DSSPEAKER_MONO) - DevMsg( "DS:mono configuration detected\n"); + DevMsg( "dsound: Mono configuration detected.\n"); if (speaker_config == DSSPEAKER_HEADPHONE) - DevMsg( "DS:headphone configuration detected\n"); + DevMsg( "dsound: Headphone configuration detected.\n"); if (speaker_config == DSSPEAKER_STEREO) - DevMsg( "DS:stereo speaker configuration detected\n"); + DevMsg( "dsound: Stereo speaker configuration detected.\n"); if (speaker_config == DSSPEAKER_QUAD) - DevMsg( "DS:quad speaker configuration detected\n"); + DevMsg( "dsound: Quad speaker configuration detected.\n"); if (speaker_config == DSSPEAKER_SURROUND) - DevMsg( "DS:surround speaker configuration detected\n"); + DevMsg( "dsound: Surround speaker configuration detected.\n"); if (speaker_config == DSSPEAKER_5POINT1) - DevMsg( "DS:5.1 speaker configuration detected\n"); + DevMsg( "dsound: 5.1 speaker configuration detected.\n"); if (speaker_config == DSSPEAKER_7POINT1) - DevMsg( "DS:7.1 speaker configuration detected\n"); + DevMsg( "dsound: 7.1 speaker configuration detected.\n"); } /* @@ -1159,7 +1199,7 @@ void OnSndSurroundCvarChanged( IConVar *pVar, const char *pOldString, float flOl // set new configuration windows_speaker_config.SetValue( (int)newSpeakerConfig ); - Msg("Speaker configuration has been changed to %s.\n", speakerConfigDesc); + Msg("dsound: Audio configuration has been changed to '%s'.\n", speakerConfigDesc); // restart sound system so it takes effect g_pSoundServices->RestartSoundSystem(); @@ -1173,7 +1213,7 @@ void OnSndSurroundLegacyChanged( IConVar *pVar, const char *pOldString, float fl // should either be interleaved or have legacy surround set, not both if ( CAudioDirectSound::m_pSingleton->IsInterleaved() == var.GetBool() ) { - Msg( "Legacy Surround %s.\n", var.GetBool() ? "enabled" : "disabled" ); + Msg( "dsound: Legacy Surround %s.\n", var.GetBool() ? "enabled" : "disabled" ); // restart sound system so it takes effect g_pSoundServices->RestartSoundSystem(); } @@ -1321,10 +1361,7 @@ bool CAudioDirectSound::SNDDMA_InitSurround(LPDIRECTSOUND8 lpDS, WAVEFORMATEX* l dsbuf.dwSize = sizeof(DSBUFFERDESC); // NOTE: LOCHARDWARE causes SB AWE64 to crash in it's DSOUND driver dsbuf.dwFlags = DSBCAPS_CTRL3D; // don't use CTRLFREQUENCY (slow) - if ( !snd_mute_losefocus.GetBool() ) - { - dsbuf.dwFlags |= DSBCAPS_GLOBALFOCUS; - } + dsbuf.dwFlags |= DSBCAPS_GLOBALFOCUS; // reserve space for each buffer @@ -1338,7 +1375,7 @@ bool CAudioDirectSound::SNDDMA_InitSurround(LPDIRECTSOUND8 lpDS, WAVEFORMATEX* l if (DS_OK != lpDS->CreateSoundBuffer(&dsbuf, &buffer, NULL) || FAILED(buffer.QueryInterface(IID_IDirectSoundBuffer8, &pDSBufFL))) { - Warning( "DS:CreateSoundBuffer for 3d front left failed"); + Warning( "dsound [surround]: CreateSoundBuffer for 3d front left failed"); ReleaseSurround(); return FALSE; } @@ -1346,7 +1383,7 @@ bool CAudioDirectSound::SNDDMA_InitSurround(LPDIRECTSOUND8 lpDS, WAVEFORMATEX* l if (DS_OK != lpDS->CreateSoundBuffer(&dsbuf, &buffer, NULL) || FAILED(buffer.QueryInterface(IID_IDirectSoundBuffer8, &pDSBufFR))) { - Warning( "DS:CreateSoundBuffer for 3d front right failed"); + Warning( "dsound [surround]: CreateSoundBuffer for 3d front right failed"); ReleaseSurround(); return FALSE; } @@ -1354,7 +1391,7 @@ bool CAudioDirectSound::SNDDMA_InitSurround(LPDIRECTSOUND8 lpDS, WAVEFORMATEX* l if (DS_OK != lpDS->CreateSoundBuffer(&dsbuf, &buffer, NULL) || FAILED(buffer.QueryInterface(IID_IDirectSoundBuffer8, &pDSBufRL))) { - Warning( "DS:CreateSoundBuffer for 3d rear left failed"); + Warning( "dsound [surround]: CreateSoundBuffer for 3d rear left failed"); ReleaseSurround(); return FALSE; } @@ -1362,7 +1399,7 @@ bool CAudioDirectSound::SNDDMA_InitSurround(LPDIRECTSOUND8 lpDS, WAVEFORMATEX* l if (DS_OK != lpDS->CreateSoundBuffer(&dsbuf, &buffer, NULL) || FAILED(buffer.QueryInterface(IID_IDirectSoundBuffer8, &pDSBufRR))) { - Warning( "DS:CreateSoundBuffer for 3d rear right failed"); + Warning( "dsound [surround]: CreateSoundBuffer for 3d rear right failed"); ReleaseSurround(); return FALSE; } @@ -1374,7 +1411,7 @@ bool CAudioDirectSound::SNDDMA_InitSurround(LPDIRECTSOUND8 lpDS, WAVEFORMATEX* l if (DS_OK != lpDS->CreateSoundBuffer(&dsbuf, &buffer, NULL) || FAILED(buffer.QueryInterface(IID_IDirectSoundBuffer8, &pDSBufFC))) { - Warning( "DS:CreateSoundBuffer for 3d front center failed"); + Warning( "dsound [surround]: CreateSoundBuffer for 3d front center failed"); ReleaseSurround(); return FALSE; } @@ -1384,28 +1421,28 @@ bool CAudioDirectSound::SNDDMA_InitSurround(LPDIRECTSOUND8 lpDS, WAVEFORMATEX* l if (DS_OK != pDSBufFL->QueryInterface(IID_IDirectSound3DBufferDef, (void**)&pDSBuf3DFL)) { - Warning( "DS:Query 3DBuffer for 3d front left failed"); + Warning( "dsound [surround]: Query 3DBuffer for 3d front left failed"); ReleaseSurround(); return FALSE; } if (DS_OK != pDSBufFR->QueryInterface(IID_IDirectSound3DBufferDef, (void**)&pDSBuf3DFR)) { - Warning( "DS:Query 3DBuffer for 3d front right failed"); + Warning( "dsound [surround]: Query 3DBuffer for 3d front right failed"); ReleaseSurround(); return FALSE; } if (DS_OK != pDSBufRL->QueryInterface(IID_IDirectSound3DBufferDef, (void**)&pDSBuf3DRL)) { - Warning( "DS:Query 3DBuffer for 3d rear left failed"); + Warning( "dsound [surround]: Query 3DBuffer for 3d rear left failed"); ReleaseSurround(); return FALSE; } if (DS_OK != pDSBufRR->QueryInterface(IID_IDirectSound3DBufferDef, (void**)&pDSBuf3DRR)) { - Warning( "DS:Query 3DBuffer for 3d rear right failed"); + Warning( "dsound [surround]: Query 3DBuffer for 3d rear right failed"); ReleaseSurround(); return FALSE; } @@ -1414,7 +1451,7 @@ bool CAudioDirectSound::SNDDMA_InitSurround(LPDIRECTSOUND8 lpDS, WAVEFORMATEX* l { if (DS_OK != pDSBufFC->QueryInterface(IID_IDirectSound3DBufferDef, (void**)&pDSBuf3DFC)) { - Warning( "DS:Query 3DBuffer for 3d front center failed"); + Warning( "dsound [surround]: Query 3DBuffer for 3d front center failed"); ReleaseSurround(); return FALSE; } @@ -1440,7 +1477,7 @@ bool CAudioDirectSound::SNDDMA_InitSurround(LPDIRECTSOUND8 lpDS, WAVEFORMATEX* l } else { - Warning( "DS: failed to get 3D listener interface."); + Warning( "dsound [surround]: Failed to get 3D listener interface."); ReleaseSurround(); return FALSE; } @@ -1490,7 +1527,7 @@ bool CAudioDirectSound::SNDDMA_InitSurround(LPDIRECTSOUND8 lpDS, WAVEFORMATEX* l if (DS_OK != pDSBufFL->GetCaps (lpdsbc)) { - Warning( "DS:GetCaps failed for 3d sound buffer\n"); + Warning( "dsound [surround]: GetCaps failed for 3d sound buffer\n"); ReleaseSurround(); return FALSE; } @@ -1504,7 +1541,7 @@ bool CAudioDirectSound::SNDDMA_InitSurround(LPDIRECTSOUND8 lpDS, WAVEFORMATEX* l pDSBufFC->Play(0, 0, DSBPLAY_LOOPING); if (snd_firsttime) - DevMsg(" %d channel(s) | %d bits/sample | %d samples/sec\n", + DevMsg(" %d channel(s) | %d bits/sample | %d Hz\n", cchan, DeviceSampleBits(), DeviceDmaSpeed()); m_bufferSizeBytes = lpdsbc->dwBufferBytes; @@ -1517,20 +1554,21 @@ bool CAudioDirectSound::SNDDMA_InitSurround(LPDIRECTSOUND8 lpDS, WAVEFORMATEX* l { if (hresult != DSERR_BUFFERLOST) { - Warning( "SNDDMA_InitDirect: DS::Lock Sound Buffer Failed for FC\n"); + Warning( "dsound [surround]: Lock Sound Buffer Failed for FC\n"); ReleaseSurround(); return FALSE; } if (++reps > 10000) { - Warning( "SNDDMA_InitDirect: DS: couldn't restore buffer for FC\n"); + Warning( "dsound [surround]: Couldn't restore buffer for FC\n"); ReleaseSurround(); return FALSE; } } + memset(lpData, 0, dwSize); -// DEBUG_DS_FillSquare( lpData, dwSize ); + pDSBufFC->Unlock(lpData, dwSize, NULL, 0); } @@ -1539,14 +1577,14 @@ bool CAudioDirectSound::SNDDMA_InitSurround(LPDIRECTSOUND8 lpDS, WAVEFORMATEX* l { if (hresult != DSERR_BUFFERLOST) { - Warning( "SNDDMA_InitSurround: DS::Lock Sound Buffer Failed for 3d FL\n"); + Warning( "dsound [surround]: Lock Sound Buffer Failed for 3d FL\n"); ReleaseSurround(); return FALSE; } if (++reps > 10000) { - Warning( "SNDDMA_InitSurround: DS: couldn't restore buffer for 3d FL\n"); + Warning( "dsound [surround]: Couldn't restore buffer for 3d FL\n"); ReleaseSurround(); return FALSE; } @@ -1560,14 +1598,14 @@ bool CAudioDirectSound::SNDDMA_InitSurround(LPDIRECTSOUND8 lpDS, WAVEFORMATEX* l { if (hresult != DSERR_BUFFERLOST) { - Warning( "SNDDMA_InitSurround: DS::Lock Sound Buffer Failed for 3d FR\n"); + Warning( "dsound [surround]: Lock Sound Buffer Failed for 3d FR\n"); ReleaseSurround(); return FALSE; } if (++reps > 10000) { - Warning( "SNDDMA_InitSurround: DS: couldn't restore buffer for FR\n"); + Warning( "dsound [surround]: Couldn't restore buffer for FR\n"); ReleaseSurround(); return FALSE; } @@ -1581,14 +1619,14 @@ bool CAudioDirectSound::SNDDMA_InitSurround(LPDIRECTSOUND8 lpDS, WAVEFORMATEX* l { if (hresult != DSERR_BUFFERLOST) { - Warning( "SNDDMA_InitDirect: DS::Lock Sound Buffer Failed for RL\n"); + Warning( "dsound [surround]: Lock Sound Buffer Failed for RL\n"); ReleaseSurround(); return FALSE; } if (++reps > 10000) { - Warning( "SNDDMA_InitDirect: DS: couldn't restore buffer for RL\n"); + Warning( "dsound [surround]: Couldn't restore buffer for RL\n"); ReleaseSurround(); return FALSE; } @@ -1602,14 +1640,14 @@ bool CAudioDirectSound::SNDDMA_InitSurround(LPDIRECTSOUND8 lpDS, WAVEFORMATEX* l { if (hresult != DSERR_BUFFERLOST) { - Warning( "SNDDMA_InitDirect: DS::Lock Sound Buffer Failed for RR\n"); + Warning( "dsound [surround]: Lock Sound Buffer Failed for RR\n"); ReleaseSurround(); return FALSE; } if (++reps > 10000) { - Warning( "SNDDMA_InitDirect: DS: couldn't restore buffer for RR\n"); + Warning( "dsound [surround]: couldn't restore buffer for RR\n"); ReleaseSurround(); return FALSE; } @@ -1645,7 +1683,7 @@ bool CAudioDirectSound::SNDDMA_InitSurround(LPDIRECTSOUND8 lpDS, WAVEFORMATEX* l pDSBufFC->Play(0, 0, DSBPLAY_LOOPING); if (snd_firsttime) - Warning( "3d surround sound initialization successful\n"); + Msg( "dsound [surround]: 3d surround sound initialization successful\n"); return TRUE; } @@ -1748,6 +1786,7 @@ bool CAudioDirectSound::LockDSBuffer( LPDIRECTSOUNDBUFFER pBuffer, DWORD **pdwWr { if ( !pBuffer ) return false; + HRESULT hr; int reps = 0; while ((hr = pBuffer->Lock(0, m_bufferSizeBytes, (void**)pdwWriteBuffer, pdwSizeBuffer, @@ -1755,13 +1794,13 @@ bool CAudioDirectSound::LockDSBuffer( LPDIRECTSOUNDBUFFER pBuffer, DWORD **pdwWr { if (hr != DSERR_BUFFERLOST) { - Msg ("DS::Lock Sound Buffer Failed %s\n", pBufferName); + Msg ("dsound: Lock Sound Buffer Failed %s\n", pBufferName); return false; } if (++reps > 10000) { - Msg ("DS:: couldn't restore buffer %s\n", pBufferName); + Msg ("dsound: Couldn't restore buffer %s\n", pBufferName); return false; } } @@ -1775,14 +1814,15 @@ void CAudioDirectSound::S_TransferSurround16( portable_samplepair_t *pfront, por int lpos; DWORD *pdwWriteFL=NULL, *pdwWriteFR=NULL, *pdwWriteRL=NULL, *pdwWriteRR=NULL, *pdwWriteFC=NULL; DWORD dwSizeFL=0, dwSizeFR=0, dwSizeRL=0, dwSizeRR=0, dwSizeFC=0; - int i, j, *snd_p, *snd_rp, *snd_cp, volumeFactor; + + int *snd_p, *snd_rp, *snd_cp; short *snd_out_fleft, *snd_out_fright, *snd_out_rleft, *snd_out_rright, *snd_out_fcenter; pdwWriteFC = NULL; // compiler warning dwSizeFC = 0; snd_out_fcenter = NULL; - volumeFactor = S_GetMasterVolume() * 256; + int volumeFactor = S_GetMasterVolume() * 256; // lock all 4 or 5 mono directsound buffers FL, FR, RL, RR, FC if ( !LockDSBuffer( pDSBufFL, &pdwWriteFL, &dwSizeFL, "FL" ) || @@ -1817,15 +1857,15 @@ void CAudioDirectSound::S_TransferSurround16( portable_samplepair_t *pfront, por // endtime - number of full samples to play to - endtime is g_soundtime + mixahead samples while (lpaintedtime < endtime) - { + { lpos = lpaintedtime & sampleMask; // lpos is next output position in output buffer - linearCount = sampleMonoCount - lpos; + linearCount = sampleMonoCount - lpos; // limit output count to requested number of samples - if (linearCount > endtime - lpaintedtime) - linearCount = endtime - lpaintedtime; + if (linearCount > endtime - lpaintedtime) + linearCount = endtime - lpaintedtime; snd_out_fleft = (short *)pdwWriteFL + lpos; snd_out_fright = (short *)pdwWriteFR + lpos; @@ -1838,22 +1878,21 @@ void CAudioDirectSound::S_TransferSurround16( portable_samplepair_t *pfront, por // for 16 bit sample in the front and rear stereo paintbuffers, copy // into the 4 or 5 FR, FL, RL, RR, FC directsound paintbuffers - for (i=0, j= 0 ; i>8; - snd_out_fright[i] = (snd_p[j + 1]*volumeFactor)>>8; - snd_out_rleft[i] = (snd_rp[j]*volumeFactor)>>8; - snd_out_rright[i] = (snd_rp[j + 1]*volumeFactor)>>8; + snd_out_fleft[i] = AdjustSampleVolume(snd_p[j], volumeFactor); + snd_out_fright[i] = AdjustSampleVolume(snd_p[j + 1], volumeFactor); + snd_out_rleft[i] = AdjustSampleVolume(snd_rp[j], volumeFactor); + snd_out_rright[i] = AdjustSampleVolume(snd_rp[j + 1], volumeFactor); } // copy front center buffer (mono) data to center chan directsound paintbuffer if (cchan == 5) { - for (i=0, j=0 ; i>8; - + snd_out_fcenter[i] = AdjustSampleVolume(snd_cp[j], volumeFactor); } } @@ -1887,57 +1926,47 @@ struct surround_transfer_t static void TransferSamplesToSurroundBuffer( int outputCount, surround_transfer_t &transfer ) { - int i, j; int volumeFactor = S_GetMasterVolume() * 256; if ( transfer.channelCount == 2 ) { - for (i=0, j=0; i>8; // FL - transfer.pOutput[1] = (transfer.snd_p[j + 1]*volumeFactor)>>8; // FR + transfer.pOutput[0] = AdjustSampleVolume(transfer.snd_p[j], volumeFactor); // FL + transfer.pOutput[1] = AdjustSampleVolume(transfer.snd_p[j + 1], volumeFactor); // FR + transfer.pOutput += 2; } } // no center channel, 4 channel surround else if ( transfer.channelCount == 4 ) { - for (i=0, j=0; i>8; // FL - transfer.pOutput[1] = (transfer.snd_p[j + 1]*volumeFactor)>>8; // FR - transfer.pOutput[2] = (transfer.snd_rp[j]*volumeFactor)>>8; // RL - transfer.pOutput[3] = (transfer.snd_rp[j + 1]*volumeFactor)>>8; // RR + transfer.pOutput[0] = AdjustSampleVolume(transfer.snd_p[j], volumeFactor); // FL + transfer.pOutput[1] = AdjustSampleVolume(transfer.snd_p[j + 1], volumeFactor); // FR + transfer.pOutput[2] = AdjustSampleVolume(transfer.snd_rp[j], volumeFactor); // RL + transfer.pOutput[3] = AdjustSampleVolume(transfer.snd_rp[j + 1], volumeFactor); // RR + transfer.pOutput += 4; - //Assert( baseOffset <= (DeviceSampleCount()) ); } } else { Assert(transfer.snd_cp); // 6 channel / 5.1 - for (i=0, j=0 ; i>8; // FL - transfer.pOutput[1] = (transfer.snd_p[j + 1]*volumeFactor)>>8; // FR - - transfer.pOutput[2] = (transfer.snd_cp[j]*volumeFactor)>>8; // Center + transfer.pOutput[0] = AdjustSampleVolume(transfer.snd_p[j], volumeFactor); // FL + transfer.pOutput[1] = AdjustSampleVolume(transfer.snd_p[j + 1], volumeFactor); // FR - transfer.pOutput[3] = 0; + transfer.pOutput[2] = AdjustSampleVolume(transfer.snd_cp[j], volumeFactor); // Center + transfer.pOutput[3] = 0; - transfer.pOutput[4] = (transfer.snd_rp[j]*volumeFactor)>>8; // RL - transfer.pOutput[5] = (transfer.snd_rp[j + 1]*volumeFactor)>>8; // RR + transfer.pOutput[4] = AdjustSampleVolume(transfer.snd_rp[j], volumeFactor); // RL + transfer.pOutput[5] = AdjustSampleVolume(transfer.snd_rp[j + 1], volumeFactor); // RR -#if 0 - // average channels into the subwoofer, let the sub filter the output - // NOTE: avg l/r rear to do 2 shifts instead of divide by 5 - int sumFront = (int)transfer.pOutput[0] + (int)transfer.pOutput[1] + (int)transfer.pOutput[2]; - int sumRear = (int)transfer.pOutput[4] + (int)transfer.pOutput[5]; - transfer.pOutput[3] = (sumFront + (sumRear>>1)) >> 2; -#endif - transfer.pOutput += 6; - //Assert( baseOffset <= (DeviceSampleCount()) ); } } @@ -1953,25 +1982,21 @@ static void TransferSamplesToSurroundBuffer( int outputCount, surround_transfer_ transfer.paintedtime += outputCount; transfer.linearCount -= outputCount; - } void CAudioDirectSound::S_TransferSurround16Interleaved_FullLock( const portable_samplepair_t *pfront, const portable_samplepair_t *prear, const portable_samplepair_t *pcenter, int lpaintedtime, int endtime ) { - int lpos; - DWORD *pdwWrite = NULL; - DWORD dwSize = 0; - int i, j, *snd_p, *snd_rp, *snd_cp, volumeFactor; - - volumeFactor = S_GetMasterVolume() * 256; + int volumeFactor = S_GetMasterVolume() * 256; int channelCount = m_bSurroundCenter ? 5 : 4; if ( DeviceChannels() == 2 ) { channelCount = 2; } - + + DWORD *write_buffer = nullptr; + DWORD buffer_size = 0; // lock single interleaved buffer - if ( !LockDSBuffer( pDSBuf, &pdwWrite, &dwSize, "DS_INTERLEAVED" ) ) + if ( !LockDSBuffer( pDSBuf, &write_buffer, &buffer_size, "DS_INTERLEAVED" ) ) { S_Shutdown (); S_Startup (); @@ -1981,9 +2006,9 @@ void CAudioDirectSound::S_TransferSurround16Interleaved_FullLock( const portable // take stereo front and rear paintbuffers, and center paintbuffer if provided, // and copy samples into the 4 or 5 mono directsound buffers - snd_rp = (int *)prear; - snd_cp = (int *)pcenter; - snd_p = (int *)pfront; + int *snd_rp = (int *)prear; + int *snd_cp = (int *)pcenter; + int *snd_p = (int *)pfront; int linearCount; // space in output buffer for linearCount mono samples int sampleMonoCount = m_bufferSizeBytes/(DeviceSampleBytes()*DeviceChannels()); // number of mono samples per output buffer (was;(DeviceSampleCount()>>1)) @@ -1991,28 +2016,29 @@ void CAudioDirectSound::S_TransferSurround16Interleaved_FullLock( const portable // paintedtime - number of full samples that have played since start // endtime - number of full samples to play to - endtime is g_soundtime + mixahead samples - - short *pOutput = (short *)pdwWrite; + int lpos; + + short *pOutput = (short *)write_buffer; while (lpaintedtime < endtime) - { + { lpos = lpaintedtime & sampleMask; // lpos is next output position in output buffer - linearCount = sampleMonoCount - lpos; + linearCount = sampleMonoCount - lpos; // limit output count to requested number of samples - if (linearCount > endtime - lpaintedtime) - linearCount = endtime - lpaintedtime; + if (linearCount > endtime - lpaintedtime) + linearCount = endtime - lpaintedtime; if ( channelCount == 4 ) { int baseOffset = lpos * channelCount; - for (i=0, j= 0 ; i>8; // FL - pOutput[baseOffset+1] = (snd_p[j + 1]*volumeFactor)>>8; // FR - pOutput[baseOffset+2] = (snd_rp[j]*volumeFactor)>>8; // RL - pOutput[baseOffset+3] = (snd_rp[j + 1]*volumeFactor)>>8; // RR + pOutput[baseOffset+0] = AdjustSampleVolume(snd_p[j], volumeFactor); // FL + pOutput[baseOffset+1] = AdjustSampleVolume(snd_p[j + 1], volumeFactor); // FR + pOutput[baseOffset+2] = AdjustSampleVolume(snd_rp[j], volumeFactor); // RL + pOutput[baseOffset+3] = AdjustSampleVolume(snd_rp[j + 1], volumeFactor); // RR baseOffset += 4; } } @@ -2020,19 +2046,18 @@ void CAudioDirectSound::S_TransferSurround16Interleaved_FullLock( const portable { Assert(channelCount==5); // 6 channel / 5.1 int baseOffset = lpos * 6; - for (i=0, j= 0 ; i>8; // FL - pOutput[baseOffset+1] = (snd_p[j + 1]*volumeFactor)>>8; // FR + pOutput[baseOffset+0] = AdjustSampleVolume(snd_p[j], volumeFactor); // FL + pOutput[baseOffset+1] = AdjustSampleVolume(snd_p[j + 1], volumeFactor); // FR - pOutput[baseOffset+2] = (snd_cp[j]*volumeFactor)>>8; // Center + pOutput[baseOffset+2] = AdjustSampleVolume(snd_cp[j], volumeFactor); // Center // NOTE: Let the hardware mix the sub from the main channels since // we don't have any sub-specific sounds, or direct sub-addressing pOutput[baseOffset+3] = 0; - pOutput[baseOffset+4] = (snd_rp[j]*volumeFactor)>>8; // RL - pOutput[baseOffset+5] = (snd_rp[j + 1]*volumeFactor)>>8; // RR - + pOutput[baseOffset+4] = AdjustSampleVolume(snd_rp[j], volumeFactor); // RL + pOutput[baseOffset+5] = AdjustSampleVolume(snd_rp[j + 1], volumeFactor); // RR baseOffset += 6; } @@ -2045,13 +2070,18 @@ void CAudioDirectSound::S_TransferSurround16Interleaved_FullLock( const portable lpaintedtime += linearCount; } - pDSBuf->Unlock(pdwWrite, dwSize, NULL, 0); + HRESULT hr = pDSBuf->Unlock(write_buffer, buffer_size, nullptr, 0); + if (FAILED(hr)) + { + Warning( "dsound: Unable to unlock DirectSound8 buffer w/e 0x%8x.\n", hr ); + } } -void CAudioDirectSound::S_TransferSurround16Interleaved( const portable_samplepair_t *pfront, const portable_samplepair_t *prear, const portable_samplepair_t *pcenter, int lpaintedtime, int endtime ) +void CAudioDirectSound::S_TransferSurround16Interleaved( portable_samplepair_t *pfront, portable_samplepair_t *prear, portable_samplepair_t *pcenter, int lpaintedtime, int endtime ) { if ( !pDSBuf ) return; + if ( !snd_lockpartial.GetBool() ) { S_TransferSurround16Interleaved_FullLock( pfront, prear, pcenter, lpaintedtime, endtime ); @@ -2061,15 +2091,17 @@ void CAudioDirectSound::S_TransferSurround16Interleaved( const portable_samplepa // and copy samples into the 4 or 5 mono directsound buffers surround_transfer_t transfer; - transfer.snd_rp = (int *)prear; - transfer.snd_cp = (int *)pcenter; - transfer.snd_p = (int *)pfront; + transfer.snd_rp = &(prear->left); + transfer.snd_cp = &(pcenter->left); + transfer.snd_p = &(pfront->left); int sampleMonoCount = DeviceSampleCount()/DeviceChannels(); // number of full samples per output buffer Assert(IsPowerOfTwo(sampleMonoCount)); + transfer.sampleMask = sampleMonoCount - 1; transfer.paintedtime = lpaintedtime; transfer.linearCount = endtime - lpaintedtime; + // paintedtime - number of full samples that have played since start // endtime - number of full samples to play to - endtime is g_soundtime + mixahead samples int channelCount = m_bSurroundCenter ? 6 : 4; @@ -2077,37 +2109,46 @@ void CAudioDirectSound::S_TransferSurround16Interleaved( const portable_samplepa { channelCount = 2; } + transfer.channelCount = channelCount; - void *pBuffer0=NULL; - void *pBuffer1=NULL; - DWORD size0, size1; - int lpos = transfer.paintedtime & transfer.sampleMask; // lpos is next output position in output buffer - int offset = lpos*2*channelCount; - int lockSize = transfer.linearCount*2*channelCount; - int reps = 0; + // lpos is next output position in output buffer + const int next_pos = transfer.paintedtime & transfer.sampleMask; + const int offset = next_pos * 2 * channelCount; + const int lock_size = transfer.linearCount * 2 * channelCount; + + unsigned retries_count = 0; HRESULT hr; - while ( (hr = pDSBuf->Lock( offset, lockSize, &pBuffer0, &size0, &pBuffer1, &size1, 0 )) != DS_OK ) + + void *buffer_0_data = nullptr, *buffer_1_data = nullptr; + DWORD buffer_0_size, buffer_1_size; + while ( FAILED(hr = pDSBuf->Lock( offset, lock_size, &buffer_0_data, &buffer_0_size, &buffer_1_data, &buffer_1_size, 0 )) ) { if ( hr == DSERR_BUFFERLOST ) { - if ( ++reps < 10000 ) + if ( ++retries_count < 10000u ) continue; } - Msg ("DS::Lock Sound Buffer Failed\n"); + + Warning( "dsound: Unable to lock DirectSound8 buffer w/e 0x%8x.\n", hr ); return; } - if ( pBuffer0 ) + if ( buffer_0_data ) { - transfer.pOutput = (short *)pBuffer0; - TransferSamplesToSurroundBuffer( size0 / (channelCount*2), transfer ); + transfer.pOutput = static_cast(buffer_0_data); + TransferSamplesToSurroundBuffer( buffer_0_size / (channelCount * 2), transfer ); } - if ( pBuffer1 ) + + if ( buffer_1_data ) { - transfer.pOutput = (short *)pBuffer1; - TransferSamplesToSurroundBuffer( size1 / (channelCount*2), transfer ); + transfer.pOutput = static_cast(buffer_1_data); + TransferSamplesToSurroundBuffer( buffer_1_size / (channelCount * 2), transfer ); } - pDSBuf->Unlock(pBuffer0, size0, pBuffer1, size1); -} + hr = pDSBuf->Unlock(buffer_0_data, buffer_0_size, buffer_1_data, buffer_1_size); + if (FAILED(hr)) + { + Warning( "dsound: Unable to unlock DirectSound8 buffer w/e 0x%8x.\n", hr ); + } +} diff --git a/engine/audio/private/snd_dev_xaudio.cpp b/engine/audio/private/snd_dev_xaudio.cpp index 1601701a0..a5076168a 100644 --- a/engine/audio/private/snd_dev_xaudio.cpp +++ b/engine/audio/private/snd_dev_xaudio.cpp @@ -1,872 +1,1114 @@ -//========= Copyright Valve Corporation, All rights reserved. ============// +// Copyright Valve Corporation, All rights reserved. // -// Purpose: X360 XAudio Version -// -//=====================================================================================// - +// Purpose: PC XAudio2 Version. #include "audio_pch.h" -#include "snd_dev_xaudio.h" -#include "UtlLinkedList.h" -#include "session.h" -#include "server.h" -#include "client.h" -#include "matchmaking.h" - -// memdbgon must be the last include file in a .cpp file!!! -#include "tier0/memdbgon.h" - -// The outer code mixes in PAINTBUFFER_SIZE (# of samples) chunks (see MIX_PaintChannels), we will never need more than -// that many samples in a buffer. This ends up being about 20ms per buffer -#define XAUDIO2_BUFFER_SAMPLES 8192 -// buffer return has a latency, so need a decent pool -#define MAX_XAUDIO2_BUFFERS 32 +#include "snd_dev_xaudio.h" +#include "tier1/utllinkedlist.h" +#include "tier3/tier3.h" +#include "video/ivideoservices.h" -#define SURROUND_HEADPHONES 0 -#define SURROUND_STEREO 2 -#define SURROUND_DIGITAL5DOT1 5 - -// 5.1 means there are a max of 6 channels -#define MAX_DEVICE_CHANNELS 6 - -ConVar snd_xaudio_spew_packets( "snd_xaudio_spew_packets", "0", 0, "Spew XAudio packet delivery" ); - - - -//----------------------------------------------------------------------------- -// Implementation of XAudio -//----------------------------------------------------------------------------- -class CAudioXAudio : public CAudioDeviceBase -{ -public: - ~CAudioXAudio( void ); - - bool IsActive( void ) { return true; } - bool Init( void ); - void Shutdown( void ); +#include "winlite.h" +#include "com_ptr.h" - void Pause( void ); - void UnPause( void ); - int PaintBegin( float mixAheadTime, int soundtime, int paintedtime ); - int GetOutputPosition( void ); - void ClearBuffer( void ); - void TransferSamples( int end ); +#include +#include +#include +#include +#include +#include - const char *DeviceName( void ); - int DeviceChannels( void ) { return m_deviceChannels; } - int DeviceSampleBits( void ) { return m_deviceSampleBits; } - int DeviceSampleBytes( void ) { return m_deviceSampleBits/8; } - int DeviceDmaSpeed( void ) { return m_deviceDmaSpeed; } - int DeviceSampleCount( void ) { return m_deviceSampleCount; } +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" +/** + * @brief Should spew XAudio2 packets delivery stats? + */ +ConVar snd_xaudio_spew_packets("snd_xaudio_spew_packets", "0", 0, + "Spew XAudio2 packet delivery", true, 0, true, + 1); + +extern ConVar snd_mute_losefocus; + +namespace { + +/** + * @brief Add prefix for message group to message. + * @tparam out_size + * @param out Result message. + * @param group Group name to prefix. + * @param message Message. + * @return Result message. + */ +template +const char *PrefixMessageGroup(char (&out)[out_size], const char *group, + const char *message) { + const size_t length{strlen(message)}; + if (length > 1 && message[length - 1] == '\n') { + Q_snprintf(out, std::size(out), "[%s] %s", group, message); + } else { + Q_snprintf(out, std::size(out), "%s", message); + } + + return out; +} +/** + * @brief Thread-safe debug warn. Can't use tier0/dbg as it writes to console + * and not thread safe. + * @param format Format. + * @param Data. + */ +void DebugWarn(PRINTF_FORMAT_STRING const char *format, ...) { + char tmp[512]; + tmp[0] = '\0'; + + va_list argptr; + va_start(argptr, format); + V_vsnprintf(tmp, ssize(tmp), format, argptr); + va_end(argptr); + + char buffer[512]; + Plat_DebugString(PrefixMessageGroup(buffer, "xaudio2", tmp)); +} - void XAudioPacketCallback( int hCompletedBuffer ); +/** + * @brief The outer code mixes in PAINTBUFFER_SIZE (# of samples) chunks (see + * MIX_PaintChannels), we will never need more than that many samples in a + * buffer. This ends up being about 20ms per buffer. + */ +constexpr inline unsigned kXAudio2BufferSamplesCount{8192u}; + +/** + * @brief 7.1 means there are a max of 8 channels. + */ +constexpr inline unsigned kMaxXAudio2DeviceChannels{ + std::min(8, XAUDIO2_MAX_AUDIO_CHANNELS)}; + +/** + * @brief Buffer return has a latency, so need a decent pool. + */ +constexpr inline unsigned kMaxXAudio2DeviceBuffersCount{ + std::min(kMaxXAudio2DeviceChannels * 6u, + static_cast(XAUDIO2_MAX_QUEUED_BUFFERS))}; + +/** + * @brief Audio device form factor. + */ +enum class AudioDeviceFormFactor { + /** + * @brief Headphones or headset. + */ + HeadphonesOrHeadset = 0, + /** + * @brief Mono speaker. + */ + MonoSpeaker = 1, + /** + * @brief Stereo speakers. + */ + StereoSpeakers = 2, + /** + * @brief Quad speakers. + */ + QuadSpeakers = 4, + /** + * @brief 5.1 surround speakers. + */ + Digital5Dot1Surround = 5, + /** + * @brief 7.1 surround speakers. + */ + Digital7Dot1Surround = 7 +}; - static CAudioXAudio *m_pSingleton; +[[nodiscard]] constexpr short AdjustSampleVolume(int sample, + int volume_factor) noexcept { + return CLIP((sample * volume_factor) >> 8); +} - CXboxVoice *GetVoiceData( void ) { return &m_VoiceData; } - IXAudio2 *GetXAudio2( void ) { return m_pXAudio2; } +} // namespace -private: - int TransferStereo( const portable_samplepair_t *pFront, int paintedTime, int endTime, char *pOutptuBuffer ); - int TransferSurroundInterleaved( const portable_samplepair_t *pFront, const portable_samplepair_t *pRear, const portable_samplepair_t *pCenter, int paintedTime, int endTime, char *pOutputBuffer ); +namespace se::engine::audio::xaudio2 { - int m_deviceChannels; // channels per hardware output buffer (1 for quad/5.1, 2 for stereo) - int m_deviceSampleBits; // bits per sample (16) - int m_deviceSampleCount; // count of mono samples in output buffer - int m_deviceDmaSpeed; // samples per second per output buffer - int m_clockDivider; +/** + * @brief Callback for XAudio2 device events. + */ +struct AudioXAudio2Callback { + /** + * @brief Called when this voice has just finished processing a buffer. The + * buffer can now be reused or destroyed. + * @param buffer_idx Buffer index. + */ + virtual void OnBufferEnd(size_t buffer_idx) = 0; +}; - IXAudio2 *m_pXAudio2; - IXAudio2MasteringVoice *m_pMasteringVoice; - IXAudio2SourceVoice *m_pSourceVoice; +/** + * @brief Client notification interface for XAudio2 voice events. + */ +class XAudio2VoiceCallback : public IXAudio2VoiceCallback { + public: + explicit XAudio2VoiceCallback(AudioXAudio2Callback *callback) + : callback_{callback} { + Assert(!!callback); + } + ~XAudio2VoiceCallback() = default; + + STDMETHOD_(void, OnStreamEnd)() override {} + + STDMETHOD_(void, OnVoiceProcessingPassEnd)() override {} + + STDMETHOD_(void, OnVoiceProcessingPassStart) + (UINT32 samples_required) override {} + + /** + * @brief Called when this voice has just finished processing a buffer. The + * buffer can now be reused or destroyed. + * @param ctx Context. Buffer index in our case. + */ + STDMETHOD_(void, OnBufferEnd)(void *ctx) override { + callback_->OnBufferEnd(reinterpret_cast(ctx)); + } + + STDMETHOD_(void, OnBufferStart)(void *) override {} + + STDMETHOD_(void, OnLoopEnd)(void *) override {} + + /** + * @brief Called in the event of a critical error during voice processing, + * such as a failing xAPO or an error from the hardware XMA decoder. The + * voice may have to be destroyed and re-created to recover from the error. + * The callback arguments report which buffer was being processed when the + * error occurred, and its HRESULT code. + * @param ctx Context. Buffer index in our case. + * @param hr. HRESULT with error code. + */ + STDMETHOD_(void, OnVoiceError)(void *ctx, HRESULT hr) override { + DebugWarn("Voice processing error for buffer %zu: 0x%8x", + reinterpret_cast(ctx), hr); + } + + private: + AudioXAudio2Callback *callback_; +}; - XAUDIO2_BUFFER m_Buffers[MAX_XAUDIO2_BUFFERS]; - BYTE *m_pOutputBuffer; - int m_bufferSizeBytes; // size of a single hardware output buffer, in bytes - CInterlockedUInt m_BufferTail; - CInterlockedUInt m_BufferHead; - - CXboxVoice m_VoiceData; +// Implementation of XAudio2 device. +class CAudioXAudio2 : public CAudioDeviceBase, AudioXAudio2Callback { + public: + friend IAudioDevice * ::Audio_CreateXAudioDevice(); + + CAudioXAudio2() : xaudio2_voice_callback_{this} {} + ~CAudioXAudio2() = default; + + bool IsActive() override { return true; } + bool Init() override; + void Shutdown() override; + + void Pause() override; + void UnPause() override; + int PaintBegin(float mix_ahead_time, int sound_time, + int painted_time) override; + int GetOutputPosition() override; + void ClearBuffer() override; + void TransferSamples(int end) override; + + const char *DeviceName() override; + int DeviceChannels() override { return device_channels_count_; } + int DeviceSampleBits() override { return device_bits_per_sample_; } + int DeviceSampleBytes() override { return device_bits_per_sample_ / 8; } + int DeviceDmaSpeed() override { return device_sample_rate_; } + int DeviceSampleCount() override { return device_samples_count_; } + + void OnBufferEnd(size_t buffer_idx) override; + + private: + unsigned TransferStereo(const portable_samplepair_t *front, int painted_time, + int end_time, unsigned char *out_buffer); + + unsigned TransferMultichannelSurroundInterleaved( + const portable_samplepair_t *front, const portable_samplepair_t *rear, + const portable_samplepair_t *center, int painted_time, int end_time, + unsigned char *out_buffer); + + // Channels per hardware output buffer (6 for 5.1, 2 for stereo) + unsigned short device_channels_count_; + // Bits per sample (16). + unsigned short device_bits_per_sample_; + // Count of mono samples in output buffer. + unsigned device_samples_count_; + // Samples per second per output buffer. + unsigned device_sample_rate_; + + unsigned device_clock_divider_; + + se::win::com::com_ptr mm_device_enumerator_; + se::win::com::com_ptr mm_notification_client_; + + se::win::com::com_ptr xaudio2_engine_; + IXAudio2MasteringVoice *xaudio2_mastering_voice_; + IXAudio2SourceVoice *xaudio2_source_voice_; + + XAUDIO2_BUFFER audio_buffers_[kMaxXAudio2DeviceBuffersCount]; + BYTE *all_audio_buffers_; + // Size of a single hardware output buffer, in bytes. + unsigned audio_buffer_size_; + + CInterlockedUInt audio_buffer_tail_pos_; + CInterlockedUInt audio_buffer_head_pos_; + + XAudio2VoiceCallback xaudio2_voice_callback_; }; -CAudioXAudio *CAudioXAudio::m_pSingleton = NULL; +Singleton g_xaudio2_device; -//----------------------------------------------------------------------------- -// XAudio packet completion callback -//----------------------------------------------------------------------------- -class XAudio2VoiceCallback : public IXAudio2VoiceCallback -{ -public: - XAudio2VoiceCallback() {} - ~XAudio2VoiceCallback() {} +} // namespace se::engine::audio::xaudio2 - void OnStreamEnd() {} +// Create XAudio Device +IAudioDevice *Audio_CreateXAudioDevice() { + MEM_ALLOC_CREDIT(); - void OnVoiceProcessingPassEnd() {} + using se::engine::audio::xaudio2::g_xaudio2_device; - void OnVoiceProcessingPassStart( UINT32 SamplesRequired ) {} + auto *device = g_xaudio2_device.GetInstance(); - void OnBufferEnd( void *pBufferContext ) - { - CAudioXAudio::m_pSingleton->XAudioPacketCallback( (int)pBufferContext ); - } + if (!device->Init()) { + g_xaudio2_device.Delete(); - void OnBufferStart( void *pBufferContext ) {} + return nullptr; + } - void OnLoopEnd( void *pBufferContext ) {} + return device; +} - void OnVoiceError( void *pBufferContext, HRESULT Error ) {} +namespace se::engine::audio::xaudio2 { + +class ScopedPropVariant { + public: + ScopedPropVariant() noexcept { PropVariantInit(&prop_); } + ~ScopedPropVariant() noexcept { + [[maybe_unused]] HRESULT hr{PropVariantClear(&prop_)}; + Assert(SUCCEEDED(hr)); + } + + [[nodiscard]] VARTYPE type() const noexcept { return prop_.vt; } + + [[nodiscard]] PROPVARIANT *operator&() noexcept { return &prop_; } + + [[nodiscard]] bool as_wide_string(wchar_t *&str) const noexcept { + if (prop_.vt == VT_LPWSTR) { + str = prop_.pwszVal; + return true; + } + + str = nullptr; + return false; + } + + [[nodiscard]] bool as_uint(unsigned &value) const noexcept { + if (prop_.vt == VT_UI4) { + value = prop_.uintVal; + return true; + } + + value = UINT_MAX; + return false; + } + + ScopedPropVariant(ScopedPropVariant &) = delete; + ScopedPropVariant(ScopedPropVariant &&v) noexcept + : prop_{std::move(v.prop_)} { + memset(&v, 0, sizeof(v)); + } + ScopedPropVariant &operator=(ScopedPropVariant &) = delete; + ScopedPropVariant &operator=(ScopedPropVariant &&v) noexcept { + std::swap(v.prop_, prop_); + return *this; + } + + private: + PROPVARIANT prop_; }; -XAudio2VoiceCallback s_XAudio2VoiceCallback; - - -//----------------------------------------------------------------------------- -// Create XAudio Device -//----------------------------------------------------------------------------- -IAudioDevice *Audio_CreateXAudioDevice( void ) -{ - MEM_ALLOC_CREDIT(); - - if ( !CAudioXAudio::m_pSingleton ) - { - CAudioXAudio::m_pSingleton = new CAudioXAudio; - } - - if ( !CAudioXAudio::m_pSingleton->Init() ) - { - delete CAudioXAudio::m_pSingleton; - CAudioXAudio::m_pSingleton = NULL; - } - - return CAudioXAudio::m_pSingleton; +static bool GetDefaultAudioDeviceFormFactor( + se::win::com::com_ptr &mm_device_enumerator, + EDataFlow device_data_flow, ERole device_role, + AudioDeviceFormFactor &form_factor) { + // Default value. + form_factor = AudioDeviceFormFactor::StereoSpeakers; + + se::win::com::com_ptr default_render_device; + HRESULT hr{mm_device_enumerator->GetDefaultAudioEndpoint( + device_data_flow, device_role, &default_render_device)}; + if (FAILED(hr)) { + DebugWarn("Get default audio render endpoint failed w/e 0x%8x.\n", hr); + return false; + } + + se::win::com::com_ptr props; + hr = default_render_device->OpenPropertyStore(STGM_READ, &props); + if (FAILED(hr)) { + DebugWarn("Get default audio render endpoint props failed w/e 0x%8x.\n", + hr); + return false; + } + + ScopedPropVariant device_friendly_name; + hr = props->GetValue(PKEY_Device_FriendlyName, &device_friendly_name); + if (SUCCEEDED(hr)) { + wchar_t *audio_device_name{nullptr}; + if (device_friendly_name.as_wide_string(audio_device_name)) { + // Print endpoint friendly name and endpoint ID. + DebugWarn("Using system audio device \"%S\".\n", audio_device_name); + } else { + DebugWarn("Using system audio device \"%S\".\n", L"N/A"); + } + } else { + DebugWarn( + "Get default audio render endpoint friendly name failed w/e 0x%8x.\n", + hr); + // Continue. + } + + ScopedPropVariant device_physical_speakers; + hr = props->GetValue(PKEY_AudioEndpoint_PhysicalSpeakers, + &device_physical_speakers); + if (SUCCEEDED(hr)) { + unsigned physical_speakers_mask{UINT_MAX}; + // May fail. + if (device_physical_speakers.as_uint(physical_speakers_mask)) { + if ((physical_speakers_mask & KSAUDIO_SPEAKER_7POINT1_SURROUND) == + KSAUDIO_SPEAKER_7POINT1_SURROUND || + // Obsolete, but still. + (physical_speakers_mask & KSAUDIO_SPEAKER_7POINT1) & + KSAUDIO_SPEAKER_7POINT1) { + form_factor = AudioDeviceFormFactor::Digital7Dot1Surround; + return true; + } + + if ((physical_speakers_mask & KSAUDIO_SPEAKER_5POINT1_SURROUND) == + KSAUDIO_SPEAKER_5POINT1_SURROUND || + // Obsolete, but still. + (physical_speakers_mask & KSAUDIO_SPEAKER_5POINT1) & + KSAUDIO_SPEAKER_5POINT1) { + form_factor = AudioDeviceFormFactor::Digital5Dot1Surround; + return true; + } + + if ((physical_speakers_mask & KSAUDIO_SPEAKER_QUAD) == + KSAUDIO_SPEAKER_QUAD) { + form_factor = AudioDeviceFormFactor::QuadSpeakers; + return true; + } + + if ((physical_speakers_mask & KSAUDIO_SPEAKER_STEREO) == + KSAUDIO_SPEAKER_STEREO) { + form_factor = AudioDeviceFormFactor::StereoSpeakers; + return true; + } + + if ((physical_speakers_mask & KSAUDIO_SPEAKER_MONO) == + KSAUDIO_SPEAKER_MONO) { + form_factor = AudioDeviceFormFactor::MonoSpeaker; + return true; + } + } + + // Fallback to PKEY_AudioEndpoint_FormFactor. + } else { + DebugWarn( + "Get default audio render endpoint physical speakers mask failed w/e " + "0x%8x.\n", + hr); + + // Fallback to PKEY_AudioEndpoint_FormFactor. + } + + ScopedPropVariant device_form_factor; + hr = props->GetValue(PKEY_AudioEndpoint_FormFactor, &device_form_factor); + if (SUCCEEDED(hr)) { + unsigned untyped_factor{UINT_MAX}; + if (device_form_factor.as_uint(untyped_factor)) { + EndpointFormFactor typed_factor = + static_cast(untyped_factor); + + if (typed_factor == EndpointFormFactor::Headphones || + typed_factor == EndpointFormFactor::Headset) { + form_factor = AudioDeviceFormFactor::HeadphonesOrHeadset; + } + + if (typed_factor == EndpointFormFactor::Handset || + typed_factor == EndpointFormFactor::Speakers) { + form_factor = AudioDeviceFormFactor::StereoSpeakers; + } + } + } else { + DebugWarn( + "Get default audio render endpoint form factor failed w/e 0x%8x.\n", + hr); + return false; + } + + return true; } -CXboxVoice *Audio_GetXVoice( void ) -{ - if ( CAudioXAudio::m_pSingleton ) - { - return CAudioXAudio::m_pSingleton->GetVoiceData(); - } +class DefaultAudioDeviceChangedNotificationClient + : public IMMNotificationClient { + public: + // Copy enumerator to ensure it is alive till client destructed. + static HRESULT Create( + se::win::com::com_ptr mm_device_enumerator, + EDataFlow device_data_flow, ERole device_role, + IMMNotificationClient **client) noexcept { + if (!mm_device_enumerator || !client) return E_POINTER; + + auto *default_client = new DefaultAudioDeviceChangedNotificationClient( + std::move(mm_device_enumerator), device_data_flow, device_role); + const HRESULT hr{default_client->registration_hr()}; + if (SUCCEEDED(hr)) { + *client = default_client; + return S_OK; + } + + default_client->Release(); + return hr; + } + + HRESULT STDMETHODCALLTYPE QueryInterface( + REFIID riid, _COM_Outptr_ void __RPC_FAR *__RPC_FAR *ppvObject) override { + if (IID_IUnknown == riid) { + AddRef(); + *ppvObject = static_cast(this); + } else if (__uuidof(IMMNotificationClient) == riid) { + AddRef(); + *ppvObject = static_cast(this); + } else { + *ppvObject = nullptr; + return E_NOINTERFACE; + } + + return S_OK; + } + + ULONG STDMETHODCALLTYPE AddRef() override { return ++ref_counter_; } + + ULONG STDMETHODCALLTYPE Release() override { + ULONG current_ref_counter = --ref_counter_; + if (current_ref_counter == 0) { + delete this; + } + return current_ref_counter; + } + + HRESULT STDMETHODCALLTYPE OnDeviceStateChanged( + _In_ LPCWSTR pwstrDeviceId, _In_ DWORD dwNewState) override { + return S_OK; + } + + HRESULT STDMETHODCALLTYPE OnDeviceAdded(_In_ LPCWSTR pwstrDeviceId) override { + return S_OK; + } + + HRESULT STDMETHODCALLTYPE + OnDeviceRemoved(_In_ LPCWSTR pwstrDeviceId) override { + return S_OK; + } + + HRESULT STDMETHODCALLTYPE + OnDefaultDeviceChanged(_In_ EDataFlow flow, _In_ ERole role, + _In_opt_ LPCWSTR pwstrDefaultDeviceId) override { + // Note, this code executed from system thread. + // + // In implementing the IMMNotificationClient interface, the client should + // observe these rules to avoid deadlocks and undefined behavior: + // + // 1. The methods of the interface must be nonblocking. The client should + // never wait on a synchronization object during an event callback. + // 2. To avoid dead locks, the client should never call + // IMMDeviceEnumerator::RegisterEndpointNotificationCallback or + // IMMDeviceEnumerator::UnregisterEndpointNotificationCallback in its + // implementation of IMMNotificationClient methods. + // 3. The client should never release the final reference on an MMDevice API + // object during an event callback. + if (flow == device_data_flow_ && role == device_role_) { + DebugWarn("Default system audio '%s' device for '%s' has been %s.\n", + GetDeviceDataFlowDescription(device_data_flow_), + GetDeviceRoleDescription(device_role_), + pwstrDefaultDeviceId != nullptr ? "changed" : "removed"); + + if (pwstrDefaultDeviceId) { + AudioDeviceFormFactor form_factor{ + GetDefaultAudioDeviceFormFactor(mm_device_enumerator_, flow, role, + form_factor) + ? form_factor + : AudioDeviceFormFactor::StereoSpeakers}; + } else { + // TODO: reinit with null audio device as no audio in system. + } + } + + return S_OK; + } + + HRESULT STDMETHODCALLTYPE OnPropertyValueChanged( + _In_ LPCWSTR pwstrDeviceId, _In_ const PROPERTYKEY key) override { + return S_OK; + } + + private: + se::win::com::com_ptr mm_device_enumerator_; + + const EDataFlow device_data_flow_; + const ERole device_role_; + + std::atomic_ulong ref_counter_; + HRESULT registration_hr_; + + DefaultAudioDeviceChangedNotificationClient( + se::win::com::com_ptr &&mm_device_enumerator, + EDataFlow device_data_flow, ERole device_role) noexcept + : mm_device_enumerator_{std::move(mm_device_enumerator)}, + device_data_flow_{device_data_flow}, + device_role_{device_role} { + Assert(!!mm_device_enumerator_); + + AddRef(); + + // May fail with out of memory. + registration_hr_ = + mm_device_enumerator_->RegisterEndpointNotificationCallback(this); + Assert(SUCCEEDED(registration_hr_)); + } + + ~DefaultAudioDeviceChangedNotificationClient() noexcept { + if (SUCCEEDED(registration_hr_)) { + [[maybe_unused]] HRESULT hr{ + mm_device_enumerator_->UnregisterEndpointNotificationCallback(this)}; + Assert(SUCCEEDED(hr)); + } + } + + HRESULT registration_hr() const noexcept { return registration_hr_; } + + [[nodiscard]] static const char *GetDeviceDataFlowDescription( + EDataFlow device_data_flow) { + switch (device_data_flow) { + case EDataFlow::eRender: + return "Render"; + case EDataFlow::eCapture: + return "Capture"; + case EDataFlow::eAll: + return "Render & Capture"; + default: + return "N/A"; + } + } + + [[nodiscard]] static const char *GetDeviceRoleDescription(ERole device_role) { + switch (device_role) { + case ERole::eConsole: + return "Games, system notification sounds, and voice commands"; + case ERole::eMultimedia: + return "Music, movies, narration, and live music recording."; + case ERole::eCommunications: + return "Voice communications (talking to another person)."; + default: + return "N/A"; + } + } +}; - return NULL; +// Updates windows settings based on snd_surround cvar changing. This should +// only happen if the user has changed it via the console or the UI. Changes +// won't take effect until the engine has restarted. +void OnSndSurroundCvarChanged(IConVar *con_var, const char *old_string, + float old_value) { + // if the old value is -1, we're setting this from the detect routine for the + // first time no need to reset the device. + if (old_value == -1) return; + + // restart sound system so it takes effect. + g_pSoundServices->RestartSoundSystem(); } -IXAudio2 *Audio_GetXAudio2( void ) -{ - if ( CAudioXAudio::m_pSingleton ) - { - return CAudioXAudio::m_pSingleton->GetXAudio2(); - } - - return NULL; -} +void OnSndVarChanged(IConVar *con_var, const char *old_string, + float old_value) { + ConVarRef var(con_var); -//----------------------------------------------------------------------------- -// Destructor -//----------------------------------------------------------------------------- -CAudioXAudio::~CAudioXAudio( void ) -{ - m_pSingleton = NULL; + // restart sound system so the change takes effect + if (var.GetInt() != static_cast(old_value)) { + g_pSoundServices->RestartSoundSystem(); + } } -//----------------------------------------------------------------------------- -// Initialize XAudio -//----------------------------------------------------------------------------- -bool CAudioXAudio::Init( void ) -{ - XAUDIOSPEAKERCONFIG xAudioConfig = 0; - XAudioGetSpeakerConfig( &xAudioConfig ); - snd_surround.SetValue( ( xAudioConfig & XAUDIOSPEAKERCONFIG_DIGITAL_DOLBYDIGITAL ) ? SURROUND_DIGITAL5DOT1 : SURROUND_STEREO ); - - m_bHeadphone = false; - m_bSurround = false; - m_bSurroundCenter = false; - - switch ( snd_surround.GetInt() ) - { - case SURROUND_HEADPHONES: - m_bHeadphone = true; - m_deviceChannels = 2; - break; - - default: - case SURROUND_STEREO: - m_deviceChannels = 2; - break; - - case SURROUND_DIGITAL5DOT1: - m_bSurround = true; - m_bSurroundCenter = true; - m_deviceChannels = 6; - break; - } - - m_deviceSampleBits = 16; - m_deviceDmaSpeed = SOUND_DMA_SPEED; - - // initialize the XAudio Engine - // Both threads on core 2 - m_pXAudio2 = NULL; - HRESULT hr = XAudio2Create( &m_pXAudio2, 0, XboxThread5 ); - if ( FAILED( hr ) ) - return false; - - // create the mastering voice, this will upsample to the devices target hw output rate - m_pMasteringVoice = NULL; - hr = m_pXAudio2->CreateMasteringVoice( &m_pMasteringVoice ); - if ( FAILED( hr ) ) - return false; - - // 16 bit PCM - WAVEFORMATEX waveFormatEx = { 0 }; - waveFormatEx.wFormatTag = WAVE_FORMAT_PCM; - waveFormatEx.nChannels = m_deviceChannels; - waveFormatEx.nSamplesPerSec = m_deviceDmaSpeed; - waveFormatEx.wBitsPerSample = 16; - waveFormatEx.nBlockAlign = ( waveFormatEx.nChannels * waveFormatEx.wBitsPerSample ) / 8; - waveFormatEx.nAvgBytesPerSec = waveFormatEx.nSamplesPerSec * waveFormatEx.nBlockAlign; - waveFormatEx.cbSize = 0; - - m_pSourceVoice = NULL; - hr = m_pXAudio2->CreateSourceVoice( - &m_pSourceVoice, - &waveFormatEx, - 0, - XAUDIO2_DEFAULT_FREQ_RATIO, - &s_XAudio2VoiceCallback, - NULL, - NULL ); - if ( FAILED( hr ) ) - return false; - - float volumes[MAX_DEVICE_CHANNELS]; - for ( int i = 0; i < MAX_DEVICE_CHANNELS; i++ ) - { - if ( !m_bSurround && i >= 2 ) - { - volumes[i] = 0; - } - else - { - volumes[i] = 1.0; - } - } - m_pSourceVoice->SetChannelVolumes( m_deviceChannels, volumes ); - - m_bufferSizeBytes = XAUDIO2_BUFFER_SAMPLES * (m_deviceSampleBits/8) * m_deviceChannels; - m_pOutputBuffer = new BYTE[MAX_XAUDIO2_BUFFERS * m_bufferSizeBytes]; - ClearBuffer(); - - V_memset( m_Buffers, 0, MAX_XAUDIO2_BUFFERS * sizeof( XAUDIO2_BUFFER ) ); - for ( int i = 0; i < MAX_XAUDIO2_BUFFERS; i++ ) - { - m_Buffers[i].pAudioData = m_pOutputBuffer + i*m_bufferSizeBytes; - m_Buffers[i].pContext = (LPVOID)i; - } - m_BufferHead = 0; - m_BufferTail = 0; - - // number of mono samples output buffer may hold - m_deviceSampleCount = MAX_XAUDIO2_BUFFERS * (m_bufferSizeBytes/(DeviceSampleBytes())); - - // NOTE: This really shouldn't be tied to the # of bufferable samples. - // This just needs to be large enough so that it doesn't fake out the sampling in - // GetSoundTime(). Basically GetSoundTime() assumes a cyclical time stamp and finds wraparound cases - // but that means it needs to get called much more often than once per cycle. So this number should be - // much larger than the framerate in terms of output time - m_clockDivider = m_deviceSampleCount / DeviceChannels(); - - // not really part of XAudio2, but mixer xma lacks one-time init, so doing it here - XMAPlaybackInitialize(); - - hr = m_pSourceVoice->Start( 0 ); - if ( FAILED( hr ) ) - return false; - - DevMsg( "XAudio Device Initialized:\n" ); - DevMsg( " %s\n" - " %d channel(s)\n" - " %d bits/sample\n" - " %d samples/sec\n", DeviceName(), DeviceChannels(), DeviceSampleBits(), DeviceDmaSpeed() ); - - m_VoiceData.VoiceInit(); - - // success - return true; +// Initialize XAudio2 +bool CAudioXAudio2::Init() { + snd_surround.InstallChangeCallback(&OnSndSurroundCvarChanged); + snd_mute_losefocus.InstallChangeCallback(&OnSndVarChanged); + + // Audio rendering stream. Audio data flows from the application to the audio + // endpoint device, which renders the stream. + constexpr EDataFlow device_data_flow{EDataFlow::eRender}; + // Audio device for games, system notification sounds, and voice commands. + constexpr ERole device_role{ERole::eConsole}; + + HRESULT hr{mm_device_enumerator_.CreateInstance(__uuidof(MMDeviceEnumerator), + nullptr, CLSCTX_ALL)}; + if (FAILED(hr)) { + Warning("XAudio2: Create media devices enumerator failed w/e 0x%8x.\n", hr); + Warning( + "XAudio2: Unable to get default system audio device, assume stereo " + "speakers.\n"); + + snd_surround.SetValue(to_underlying(AudioDeviceFormFactor::StereoSpeakers)); + } else { + hr = DefaultAudioDeviceChangedNotificationClient::Create( + mm_device_enumerator_, device_data_flow, device_role, + &mm_notification_client_); + if (FAILED(hr)) { + Warning( + "XAudio2: Create default system audio device change watcher failed " + "w/e 0x%8x.\n", + hr); + Warning( + "XAudio2: Changing default system audio device will not change game " + "sound output.\n"); + } + + AudioDeviceFormFactor form_factor{ + GetDefaultAudioDeviceFormFactor(mm_device_enumerator_, device_data_flow, + device_role, form_factor) + ? form_factor + : AudioDeviceFormFactor::StereoSpeakers}; + snd_surround.SetValue(to_underlying(form_factor)); + } + + m_bHeadphone = false; + m_bSurround = false; + m_bSurroundCenter = false; + + switch (snd_surround.GetInt()) { + case to_underlying(AudioDeviceFormFactor::HeadphonesOrHeadset): + m_bHeadphone = true; + device_channels_count_ = 2; + break; + + default: + // TODO: Add mono speaker support. + case to_underlying(AudioDeviceFormFactor::MonoSpeaker): + case to_underlying(AudioDeviceFormFactor::StereoSpeakers): + device_channels_count_ = 2; + break; + + case to_underlying(AudioDeviceFormFactor::QuadSpeakers): + m_bSurround = true; + device_channels_count_ = 4; + break; + + case to_underlying(AudioDeviceFormFactor::Digital5Dot1Surround): + m_bSurround = true; + m_bSurroundCenter = true; + device_channels_count_ = 6; + break; + + case to_underlying(AudioDeviceFormFactor::Digital7Dot1Surround): + m_bSurround = true; + m_bSurroundCenter = true; + device_channels_count_ = 8; + break; + } + + // Initialize the XAudio2 Engine. + // + // If you specify XAUDIO2_ANY_PROCESSOR, the system will use all of the + // device's processors and, as noted above, create a worker thread for + // each processor. + // + // Implementations targeting Games and WIN10_19H1 and later, should use + // XAUDIO2_USE_DEFAULT_PROCESSOR instead to let XAudio2 select the appropriate + // default processor for the hardware platform. + hr = XAudio2Create(&xaudio2_engine_, 0, XAUDIO2_USE_DEFAULT_PROCESSOR); + if (FAILED(hr)) { + Warning("XAudio2: Creating engine failed w/e 0x%8x.\n", hr); + return false; + } + + // Create the mastering voice, this will upsample to the devices target + // hardware output rate. + hr = xaudio2_engine_->CreateMasteringVoice(&xaudio2_mastering_voice_); + if (FAILED(hr)) { + Warning("XAudio2: Creating mastering voice failed w/e 0x%8x.\n", hr); + return false; + } + + device_bits_per_sample_ = 16; + device_sample_rate_ = SOUND_DMA_SPEED; + + // 16 bit PCM + WAVEFORMATEX waveFormatEx = {}; + waveFormatEx.wFormatTag = WAVE_FORMAT_PCM; + waveFormatEx.nChannels = device_channels_count_; + // Sample rate. + waveFormatEx.nSamplesPerSec = device_sample_rate_; + waveFormatEx.wBitsPerSample = device_bits_per_sample_; + // Block size. + waveFormatEx.nBlockAlign = + (waveFormatEx.nChannels * waveFormatEx.wBitsPerSample) / 8; + waveFormatEx.nAvgBytesPerSec = + waveFormatEx.nSamplesPerSec * waveFormatEx.nBlockAlign; + waveFormatEx.cbSize = 0; + + hr = xaudio2_engine_->CreateSourceVoice( + &xaudio2_source_voice_, &waveFormatEx, 0, XAUDIO2_DEFAULT_FREQ_RATIO, + &xaudio2_voice_callback_, nullptr, nullptr); + if (FAILED(hr)) { + Warning("XAudio2: Creating source voice failed w/e 0x%8x.\n", hr); + return false; + } + + float channel_volumes[kMaxXAudio2DeviceChannels]; + for (size_t i = 0; i < kMaxXAudio2DeviceChannels; i++) { + channel_volumes[i] = !m_bSurround && i >= 2 ? 0 : 1.0f; + } + + hr = xaudio2_source_voice_->SetChannelVolumes(device_channels_count_, + channel_volumes); + if (FAILED(hr)) { + Warning("XAudio2: Setting %hu channel volumes failed w/e 0x%8x.\n", + device_channels_count_, hr); + return false; + } + + audio_buffer_size_ = kXAudio2BufferSamplesCount * + (device_bits_per_sample_ / 8) * device_channels_count_; + all_audio_buffers_ = + new BYTE[kMaxXAudio2DeviceBuffersCount * audio_buffer_size_]; + + ClearBuffer(); + + V_memset(audio_buffers_, 0, sizeof(audio_buffers_)); + for (size_t i = 0; i < kMaxXAudio2DeviceBuffersCount; i++) { + audio_buffers_[i].pAudioData = all_audio_buffers_ + i * audio_buffer_size_; + audio_buffers_[i].pContext = reinterpret_cast(i); + } + + audio_buffer_head_pos_ = 0; + audio_buffer_tail_pos_ = 0; + + // Number of mono samples output buffer may hold + device_samples_count_ = kMaxXAudio2DeviceBuffersCount * + (audio_buffer_size_ / DeviceSampleBytes()); + + // NOTE: This really shouldn't be tied to the # of bufferable samples. This + // just needs to be large enough so that it doesn't fake out the sampling in + // GetSoundTime(). Basically GetSoundTime() assumes a cyclical time stamp and + // finds wraparound cases but that means it needs to get called much more + // often than once per cycle. So this number should be much larger than the + // framerate in terms of output time. + device_clock_divider_ = device_samples_count_ / DeviceChannels(); + + hr = xaudio2_source_voice_->Start(0); + if (FAILED(hr)) { + Warning("XAudio2: Unable to start engine w/e 0x%8x.\n", hr); + return false; + } + + DevMsg("XAudio2 device initialized:\n"); + DevMsg("%s | %d channel(s) | %d bits/sample | %d Hz\n", DeviceName(), + DeviceChannels(), DeviceSampleBits(), DeviceDmaSpeed()); + + // Tell video subsytem to use our device as out one. + if (g_pVideo) { + g_pVideo->SoundDeviceCommand(VideoSoundDeviceOperation::HOOK_X_AUDIO, + xaudio2_engine_.GetInterfacePtr()); + } + + // success + return true; } -//----------------------------------------------------------------------------- -// Shutdown XAudio -//----------------------------------------------------------------------------- -void CAudioXAudio::Shutdown( void ) -{ - if ( m_pSourceVoice ) - { - m_pSourceVoice->Stop( 0 ); - m_pSourceVoice->DestroyVoice(); - m_pSourceVoice = NULL; - delete[] m_pOutputBuffer; - } - - if ( m_pMasteringVoice ) - { - m_pMasteringVoice->DestroyVoice(); - m_pMasteringVoice = NULL; - } - - // need to release ref to XAudio2 - m_VoiceData.VoiceShutdown(); - - if ( m_pXAudio2 ) - { - m_pXAudio2->Release(); - m_pXAudio2 = NULL; - } - - if ( this == CAudioXAudio::m_pSingleton ) - { - CAudioXAudio::m_pSingleton = NULL; - } +// Shutdown XAudio2 +void CAudioXAudio2::Shutdown() { + // Tell video subsytem to not use our device as out one. + if (g_pVideo) { + g_pVideo->SoundDeviceCommand(VideoSoundDeviceOperation::HOOK_X_AUDIO, + nullptr); + } + + if (xaudio2_source_voice_) { + xaudio2_source_voice_->Stop(0); + // Slow, blocking. + xaudio2_source_voice_->DestroyVoice(); + xaudio2_source_voice_ = nullptr; + + delete[] all_audio_buffers_; + } + + if (xaudio2_mastering_voice_) { + // Slow, blocking. + xaudio2_mastering_voice_->DestroyVoice(); + xaudio2_mastering_voice_ = nullptr; + } + + if (xaudio2_engine_) xaudio2_engine_.Release(); } -//----------------------------------------------------------------------------- -// XAudio has completed a packet. Assuming these are sequential -//----------------------------------------------------------------------------- -void CAudioXAudio::XAudioPacketCallback( int hCompletedBuffer ) -{ - // packet completion expected to be sequential - Assert( hCompletedBuffer == (int)( m_PacketTail % MAX_XAUDIO2_BUFFERS ) ); - - m_BufferTail++; - - if ( snd_xaudio_spew_packets.GetBool() ) - { - if ( m_BufferTail == m_BufferHead ) - { - Warning( "XAudio: Starved\n" ); - } - else - { - Msg( "XAudio: Packet Callback, Submit: %2d, Free: %2d\n", m_BufferHead - m_BufferTail, MAX_XAUDIO2_BUFFERS - ( m_BufferHead - m_BufferTail ) ); - } - } - - if ( m_BufferTail == m_BufferHead ) - { - // very bad, out of packets, xaudio is starving - // mix thread didn't keep up with audio clock and submit packets - // submit a silent buffer to keep stream playing and audio clock running - int head = m_BufferHead++; - XAUDIO2_BUFFER *pBuffer = &m_Buffers[head % MAX_XAUDIO2_BUFFERS]; - V_memset( pBuffer->pAudioData, 0, m_bufferSizeBytes ); - pBuffer->AudioBytes = m_bufferSizeBytes; - m_pSourceVoice->SubmitSourceBuffer( pBuffer ); - } +// XAudio2 has completed a packet. Assuming they are sequential. +void CAudioXAudio2::OnBufferEnd(size_t buffer_idx) { + // packet completion expected to be sequential + Assert(buffer_idx == static_cast(audio_buffer_tail_pos_ % + kMaxXAudio2DeviceBuffersCount)); + + ++audio_buffer_tail_pos_; + + if (snd_xaudio_spew_packets.GetBool()) { + if (audio_buffer_tail_pos_ == audio_buffer_head_pos_) { + Warning("XAudio2: Starved.\n"); + } else { + Msg("XAudio2: Packet Callback, Submit: %2u, Free: %2u.\n", + audio_buffer_head_pos_ - audio_buffer_tail_pos_, + kMaxXAudio2DeviceBuffersCount - + (audio_buffer_head_pos_ - audio_buffer_tail_pos_)); + } + } + + if (audio_buffer_tail_pos_ == audio_buffer_head_pos_) { + // Very bad, out of packets, XAudio2 is starving. Mix thread didn't keep up + // with audio clock and submit packets submit a silent buffer to keep stream + // playing and audio clock running. + unsigned head{audio_buffer_head_pos_++}; + XAUDIO2_BUFFER &buffer{ + audio_buffers_[head % kMaxXAudio2DeviceBuffersCount]}; + + V_memset(const_cast(buffer.pAudioData), 0, audio_buffer_size_); + + buffer.AudioBytes = audio_buffer_size_; + + const HRESULT hr{xaudio2_source_voice_->SubmitSourceBuffer(&buffer)}; + if (FAILED(hr)) + Warning("XAudio2: Starved. Submitting silent buffer failed w/e 0x%8x.\n", + hr); + } } -//----------------------------------------------------------------------------- -// Return the "write" cursor. Used to clock the audio mixing. -// The actual hw write cursor and the number of samples it fetches is unknown. -//----------------------------------------------------------------------------- -int CAudioXAudio::GetOutputPosition( void ) -{ - XAUDIO2_VOICE_STATE state; +// Return the "write" cursor. Used to clock the audio mixing. The actual hw +// write cursor and the number of samples it fetches is unknown. +int CAudioXAudio2::GetOutputPosition() { + XAUDIO2_VOICE_STATE state = {}; + state.SamplesPlayed = 0; - state.SamplesPlayed = 0; - m_pSourceVoice->GetState( &state ); + xaudio2_source_voice_->GetState(&state); - return ( state.SamplesPlayed % m_clockDivider ); + return state.SamplesPlayed % device_clock_divider_; } -//----------------------------------------------------------------------------- // Pause playback -//----------------------------------------------------------------------------- -void CAudioXAudio::Pause( void ) -{ - if ( m_pSourceVoice ) - { - m_pSourceVoice->Stop( 0 ); - } +void CAudioXAudio2::Pause() { + if (xaudio2_source_voice_) { + const HRESULT hr{xaudio2_source_voice_->Stop(0)}; + if (FAILED(hr)) { + Warning("XAudio2: Stopping source voice failed w/e 0x%8x.\n", hr); + } + } } -//----------------------------------------------------------------------------- // Resume playback -//----------------------------------------------------------------------------- -void CAudioXAudio::UnPause( void ) -{ - if ( m_pSourceVoice ) - { - m_pSourceVoice->Start( 0 ); - } +void CAudioXAudio2::UnPause() { + if (xaudio2_source_voice_) { + const HRESULT hr{xaudio2_source_voice_->Start(0)}; + if (FAILED(hr)) { + Warning("XAudio2: Starting source voice failed w/e 0x%8x.\n", hr); + } + } } -//----------------------------------------------------------------------------- -// Calc the paint position -//----------------------------------------------------------------------------- -int CAudioXAudio::PaintBegin( float mixAheadTime, int soundtime, int paintedtime ) -{ - // soundtime = total full samples that have been played out to hardware at dmaspeed - // paintedtime = total full samples that have been mixed at speed - - // endtime = target for full samples in mixahead buffer at speed - int mixaheadtime = mixAheadTime * DeviceDmaSpeed(); - int endtime = soundtime + mixaheadtime; - if ( endtime <= paintedtime ) - { - return endtime; - } - - int fullsamps = DeviceSampleCount() / DeviceChannels(); - - if ( ( endtime - soundtime ) > fullsamps ) - { - endtime = soundtime + fullsamps; - } - if ( ( endtime - paintedtime ) & 0x03 ) - { - // The difference between endtime and painted time should align on - // boundaries of 4 samples. This is important when upsampling from 11khz -> 44khz. - endtime -= ( endtime - paintedtime ) & 0x03; - } - - return endtime; -} +// Calculate the paint position. +int CAudioXAudio2::PaintBegin(float mix_ahead_time, int sound_time, + int painted_time) { + // soundtime = total full samples that have been played out to hardware at + // dmaspeed paintedtime = total full samples that have been mixed at speed -//----------------------------------------------------------------------------- -// Fill the output buffers with silence -//----------------------------------------------------------------------------- -void CAudioXAudio::ClearBuffer( void ) -{ - V_memset( m_pOutputBuffer, 0, MAX_XAUDIO2_BUFFERS * m_bufferSizeBytes ); -} + // endtime = target for full samples in mixahead buffer at speed + const int mixaheadtime{static_cast(mix_ahead_time * DeviceDmaSpeed())}; -//----------------------------------------------------------------------------- -// Fill the output buffer with L/R samples -//----------------------------------------------------------------------------- -int CAudioXAudio::TransferStereo( const portable_samplepair_t *pFrontBuffer, int paintedTime, int endTime, char *pOutputBuffer ) -{ - int linearCount; - int i; - int val; - - int volumeFactor = S_GetMasterVolume() * 256; - - int *pFront = (int *)pFrontBuffer; - short *pOutput = (short *)pOutputBuffer; - - // get size of output buffer in full samples (LR pairs) - // number of sequential sample pairs that can be wrriten - linearCount = g_AudioDevice->DeviceSampleCount() >> 1; - - // clamp output count to requested number of samples - if ( linearCount > endTime - paintedTime ) - { - linearCount = endTime - paintedTime; - } - - // linearCount is now number of mono 16 bit samples (L and R) to xfer. - linearCount <<= 1; - - // transfer mono 16bit samples multiplying each sample by volume. - for ( i=0; i> 8; - *pOutput++ = CLIP( val ); - - // R Channel - val = ( pFront[i+1] * volumeFactor ) >> 8; - *pOutput++ = CLIP( val ); - } - - return linearCount * DeviceSampleBytes(); -} + int end_time{sound_time + mixaheadtime}; + if (end_time <= painted_time) return end_time; -//----------------------------------------------------------------------------- -// Fill the output buffer with interleaved surround samples -//----------------------------------------------------------------------------- -int CAudioXAudio::TransferSurroundInterleaved( const portable_samplepair_t *pFrontBuffer, const portable_samplepair_t *pRearBuffer, const portable_samplepair_t *pCenterBuffer, int paintedTime, int endTime, char *pOutputBuffer ) -{ - int linearCount; - int i, j; - int val; - - int volumeFactor = S_GetMasterVolume() * 256; - - int *pFront = (int *)pFrontBuffer; - int *pRear = (int *)pRearBuffer; - int *pCenter = (int *)pCenterBuffer; - short *pOutput = (short *)pOutputBuffer; - - // number of mono samples per channel - // number of sequential samples that can be wrriten - linearCount = m_bufferSizeBytes/( DeviceSampleBytes() * DeviceChannels() ); - - // clamp output count to requested number of samples - if ( linearCount > endTime - paintedTime ) - { - linearCount = endTime - paintedTime; - } - - for ( i = 0, j = 0; i < linearCount; i++, j += 2 ) - { - // FL - val = ( pFront[j] * volumeFactor ) >> 8; - *pOutput++ = CLIP( val ); - - // FR - val = ( pFront[j+1] * volumeFactor ) >> 8; - *pOutput++ = CLIP( val ); - - // Center - val = ( pCenter[j] * volumeFactor) >> 8; - *pOutput++ = CLIP( val ); - - // Let the hardware mix the sub from the main channels since - // we don't have any sub-specific sounds, or direct sub-addressing - *pOutput++ = 0; - - // RL - val = ( pRear[j] * volumeFactor ) >> 8; - *pOutput++ = CLIP( val ); - - // RR - val = ( pRear[j+1] * volumeFactor ) >> 8; - *pOutput++ = CLIP( val ); - } - - return linearCount * DeviceSampleBytes() * DeviceChannels(); -} + const int samples_per_channel{DeviceSampleCount() / DeviceChannels()}; -//----------------------------------------------------------------------------- -// Transfer up to a full paintbuffer (PAINTBUFFER_SIZE) of samples out to the xaudio buffer(s). -//----------------------------------------------------------------------------- -void CAudioXAudio::TransferSamples( int endTime ) -{ - XAUDIO2_BUFFER *pBuffer; - - if ( m_BufferHead - m_BufferTail >= MAX_XAUDIO2_BUFFERS ) - { - DevWarning( "XAudio: No Free Buffers!\n" ); - return; - } - - int sampleCount = endTime - g_paintedtime; - if ( sampleCount > XAUDIO2_BUFFER_SAMPLES ) - { - DevWarning( "XAudio: Overflowed mix buffer!\n" ); - endTime = g_paintedtime + XAUDIO2_BUFFER_SAMPLES; - } - - unsigned int nBuffer = m_BufferHead++; - pBuffer = &m_Buffers[nBuffer % MAX_XAUDIO2_BUFFERS]; - - if ( !m_bSurround ) - { - pBuffer->AudioBytes = TransferStereo( PAINTBUFFER, g_paintedtime, endTime, (char *)pBuffer->pAudioData ); - } - else - { - pBuffer->AudioBytes = TransferSurroundInterleaved( PAINTBUFFER, REARPAINTBUFFER, CENTERPAINTBUFFER, g_paintedtime, endTime, (char *)pBuffer->pAudioData ); - } - - // submit buffer - m_pSourceVoice->SubmitSourceBuffer( pBuffer ); -} + if (end_time - sound_time > samples_per_channel) { + end_time = sound_time + samples_per_channel; + } -//----------------------------------------------------------------------------- -// Get our device name -//----------------------------------------------------------------------------- -const char *CAudioXAudio::DeviceName( void ) -{ - if ( m_bSurround ) - { - return "XAudio: 5.1 Channel Surround"; - } - - return "XAudio: Stereo"; -} + if ((end_time - painted_time) & 0x03) { + // The difference between endtime and painted time should align on + // boundaries of 4 samples. This is important when upsampling from 11khz -> + // 44khz. + end_time -= (end_time - painted_time) & 0x03; + } -CXboxVoice::CXboxVoice() -{ - m_pXHVEngine = NULL; + return end_time; } -//----------------------------------------------------------------------------- -// Initialize Voice -//----------------------------------------------------------------------------- -void CXboxVoice::VoiceInit( void ) -{ - if ( !m_pXHVEngine ) - { - // Set the processing modes - XHV_PROCESSING_MODE rgMode = XHV_VOICECHAT_MODE; - - // Set up parameters for the voice chat engine - XHV_INIT_PARAMS xhvParams = {0}; - xhvParams.dwMaxRemoteTalkers = MAX_PLAYERS; - xhvParams.dwMaxLocalTalkers = XUSER_MAX_COUNT; - xhvParams.localTalkerEnabledModes = &rgMode; - xhvParams.remoteTalkerEnabledModes = &rgMode; - xhvParams.dwNumLocalTalkerEnabledModes = 1; - xhvParams.dwNumRemoteTalkerEnabledModes = 1; - xhvParams.pXAudio2 = CAudioXAudio::m_pSingleton->GetXAudio2(); - - // Create the engine - HRESULT hr = XHV2CreateEngine( &xhvParams, NULL, &m_pXHVEngine ); - if ( hr != S_OK ) - { - Warning( "Couldn't load XHV engine!\n" ); - } - } - - VoiceResetLocalData( ); +// Fill the output buffers with silence. +void CAudioXAudio2::ClearBuffer() { + V_memset(all_audio_buffers_, 0, + kMaxXAudio2DeviceBuffersCount * audio_buffer_size_); } -void CXboxVoice::VoiceShutdown( void ) -{ - if ( !m_pXHVEngine ) - return; - - m_pXHVEngine->Release(); - m_pXHVEngine = NULL; -} +// Fill the output buffer with L/R samples. +unsigned CAudioXAudio2::TransferStereo( + const portable_samplepair_t *front_buffer, int painted_time, int end_time, + unsigned char *out_buffer) { + const int volume_factor{static_cast(S_GetMasterVolume() * 256)}; -void CXboxVoice::AddPlayerToVoiceList( CClientInfo *pClient, bool bLocal ) -{ - XHV_PROCESSING_MODE local_proc_mode = XHV_VOICECHAT_MODE; - - for ( int i = 0; i < pClient->m_cPlayers; ++i ) - { - if ( pClient->m_xuids[i] == 0 ) - continue; - - if ( bLocal == true ) - { - if ( m_pXHVEngine->RegisterLocalTalker( pClient->m_iControllers[i] ) == S_OK ) - { - m_pXHVEngine->StartLocalProcessingModes( pClient->m_iControllers[i], &local_proc_mode, 1 ); - } - } - else - { - if ( m_pXHVEngine->RegisterRemoteTalker( pClient->m_xuids[i], NULL, NULL, NULL ) == S_OK ) - { - m_pXHVEngine->StartRemoteProcessingModes( pClient->m_xuids[i], &local_proc_mode, 1 ); - } - } - } -} + // get size of output buffer in full samples (LR pairs) + // number of sequential sample pairs that can be written + unsigned linear_count{static_cast(DeviceSampleCount()) >> 1}; -void CXboxVoice::RemovePlayerFromVoiceList( CClientInfo *pClient, bool bLocal ) -{ - for ( int i = 0; i < pClient->m_cPlayers; ++i ) - { - if ( pClient->m_xuids[i] == 0 ) - continue; - - if ( bLocal == true ) - { - m_pXHVEngine->UnregisterLocalTalker( pClient->m_iControllers[i] ); - } - else - { - m_pXHVEngine->UnregisterRemoteTalker( pClient->m_xuids[i] ); - } - } -} + // clamp output count to requested number of samples + if (linear_count > static_cast(end_time - painted_time)) { + linear_count = end_time - painted_time; + } -void CXboxVoice::PlayIncomingVoiceData( XUID xuid, const byte *pbData, DWORD pdwSize ) -{ - XUID localXUID; + // linear_count is now number of mono 16 bit samples (L and R) to xfer. + linear_count <<= 1u; - XUserGetXUID( XBX_GetPrimaryUserId(), &localXUID ); + const portable_samplepair_t *front{front_buffer}; + short *out{reinterpret_cast(out_buffer)}; - //Hack: Don't play stuff that comes from ourselves. - if ( localXUID == xuid ) - return; + // transfer mono 16bit samples multiplying each sample by volume. + for (unsigned i = 0; i < linear_count; i += 2) { + // L Channel + *out++ = AdjustSampleVolume(front->left, volume_factor); - m_pXHVEngine->SubmitIncomingChatData( xuid, pbData, &pdwSize ); -} + // R Channel + *out++ = AdjustSampleVolume(front->right, volume_factor); + ++front; + } -void CXboxVoice::UpdateHUDVoiceStatus( void ) -{ - for ( int iClient = 0; iClient < cl.m_nMaxClients; iClient++ ) - { - bool bSelf = (cl.m_nPlayerSlot == iClient); - - int iIndex = iClient + 1; - XUID id = g_pMatchmaking->PlayerIdToXuid( iIndex ); - - if ( id != 0 ) - { - bool bTalking = false; - - if ( bSelf == true ) - { - //Make sure the player's own label is not on. - g_pSoundServices->OnChangeVoiceStatus( iIndex, false ); - - iIndex = -1; - - if ( IsPlayerTalking( XBX_GetPrimaryUserId(), true ) ) - { - bTalking = true; - } - } - else - { - if ( IsPlayerTalking( id, false ) ) - { - bTalking = true; - } - } - - g_pSoundServices->OnChangeVoiceStatus( iIndex, bTalking ); - } - else - { - g_pSoundServices->OnChangeVoiceStatus( iIndex, false ); - } - } + return linear_count * DeviceSampleBytes(); } -bool CXboxVoice::VoiceUpdateData( void ) -{ - DWORD dwNumPackets = 0; - DWORD dwBytes = 0; - WORD wVoiceBytes = 0; - bool bShouldSend = false; - DWORD dwVoiceFlags = m_pXHVEngine->GetDataReadyFlags(); - - //Update UI stuff. - UpdateHUDVoiceStatus(); - - for ( uint i = 0; i < XUSER_MAX_COUNT; ++i ) - { - // We currently only allow one player per console - if ( i != XBX_GetPrimaryUserId() ) - { - continue; - } - - if ( IsHeadsetPresent( i ) == false ) - continue; - - if ( !(dwVoiceFlags & ( 1 << i )) ) - continue; - - dwBytes = m_ChatBufferSize - m_wLocalDataSize; - - if( dwBytes < XHV_VOICECHAT_MODE_PACKET_SIZE ) - { - bShouldSend = true; - } - else - { - m_pXHVEngine->GetLocalChatData( i, m_ChatBuffer + m_wLocalDataSize, &dwBytes, &dwNumPackets ); - m_wLocalDataSize += ((WORD)dwBytes) & MAXWORD; - - if( m_wLocalDataSize > ( ( m_ChatBufferSize * 7 ) / 10 ) ) - { - bShouldSend = true; - } - } - - wVoiceBytes += m_wLocalDataSize & MAXWORD; - break; - } - - return bShouldSend || - ( wVoiceBytes && - ( GetTickCount() - m_dwLastVoiceSend ) > MAX_VOICE_BUFFER_TIME ); -} +// Fill the output buffer with interleaved surround samples. +// TODO: Add 7.1 support. +unsigned CAudioXAudio2::TransferMultichannelSurroundInterleaved( + const portable_samplepair_t *front_buffer, + const portable_samplepair_t *rear_buffer, + const portable_samplepair_t *center_buffer, int painted_time, int end_time, + unsigned char *out_buffer) { + const int volume_factor{static_cast(S_GetMasterVolume() * 256)}; -void CXboxVoice::SetPlaybackPriority( XUID remoteTalker, DWORD dwUserIndex, XHV_PLAYBACK_PRIORITY playbackPriority ) -{ - m_pXHVEngine->SetPlaybackPriority( remoteTalker, dwUserIndex, playbackPriority ); -} + // number of mono samples per channel + // number of sequential samples that can be wrriten + unsigned linear_count{audio_buffer_size_ / + (DeviceSampleBytes() * DeviceChannels())}; -void CXboxVoice::GetRemoteTalkers( int *pNumTalkers, XUID *pRemoteTalkers ) -{ - m_pXHVEngine->GetRemoteTalkers( (DWORD*)pNumTalkers, pRemoteTalkers ); -} + // clamp output count to requested number of samples + if (linear_count > static_cast(end_time - painted_time)) { + linear_count = end_time - painted_time; + } -void CXboxVoice::GetVoiceData( CLC_VoiceData *pMessage ) -{ - byte *puchVoiceData = NULL; - pMessage->m_nLength = m_wLocalDataSize; - XUserGetXUID( XBX_GetPrimaryUserId(), &pMessage->m_xuid ); + const portable_samplepair_t *front{front_buffer}; + const portable_samplepair_t *rear{rear_buffer}; + const portable_samplepair_t *center{center_buffer}; - puchVoiceData = m_ChatBuffer; + short *out{reinterpret_cast(out_buffer)}; - pMessage->m_DataOut.StartWriting( puchVoiceData, pMessage->m_nLength ); - pMessage->m_nLength *= 8; - pMessage->m_DataOut.SeekToBit( pMessage->m_nLength ); // set correct writing position -} + for (unsigned i = 0; i < linear_count; ++i) { + // FL + *out++ = AdjustSampleVolume(front->left, volume_factor); -void CXboxVoice::VoiceSendData( INetChannel *pChannel ) -{ - CLC_VoiceData voiceMsg; - GetVoiceData( &voiceMsg ); + // FR + *out++ = AdjustSampleVolume(front->right, volume_factor); - if ( pChannel ) - { - pChannel->SendNetMsg( voiceMsg, false, true ); - VoiceResetLocalData(); - } -} + ++front; + + // Quad speakers have no center. + if (device_channels_count_ > 4) { + // Center + *out++ = AdjustSampleVolume(center->left, volume_factor); -void CXboxVoice::VoiceResetLocalData( void ) -{ - m_wLocalDataSize = 0; - m_dwLastVoiceSend = GetTickCount(); - Q_memset( m_ChatBuffer, 0, m_ChatBufferSize ); + // Let the hardware mix the sub from the main channels since + // we don't have any sub-specific sounds, or direct sub-addressing + *out++ = 0; + + ++center; + } + + // RL + *out++ = AdjustSampleVolume(rear->left, volume_factor); + + // RR + *out++ = AdjustSampleVolume(rear->right, volume_factor); + + ++rear; + } + + return linear_count * DeviceSampleBytes() * DeviceChannels(); } -bool CXboxVoice::IsPlayerTalking( XUID uid, bool bLocal ) -{ - if ( bLocal == true ) - { - return m_pXHVEngine->IsLocalTalking( XBX_GetPrimaryUserId() ); - } - else - { - return !g_pMatchmaking->IsPlayerMuted( XBX_GetPrimaryUserId(), uid ) && m_pXHVEngine->IsRemoteTalking( uid ); - } - - return false; +// Transfer up to a full paintbuffer (PAINTBUFFER_SIZE) of samples out to the +// XAudio2 buffer(s). +void CAudioXAudio2::TransferSamples(int end_time) { + if (audio_buffer_head_pos_ - audio_buffer_tail_pos_ >= + kMaxXAudio2DeviceBuffersCount) { + DebugWarn("No free audio buffers! All %u buffers taken.\n", + kMaxXAudio2DeviceBuffersCount); + + return; + } + + const int samples_count{end_time - g_paintedtime}; + if (samples_count > kXAudio2BufferSamplesCount) { + DebugWarn("Overflowed mix buffer! All %u samples taken.\n", + kXAudio2BufferSamplesCount); + + end_time = g_paintedtime + kXAudio2BufferSamplesCount; + } + + const unsigned buffer_idx{audio_buffer_head_pos_++}; + XAUDIO2_BUFFER &buffer{ + audio_buffers_[buffer_idx % kMaxXAudio2DeviceBuffersCount]}; + + if (!m_bSurround) { + buffer.AudioBytes = + TransferStereo(PAINTBUFFER, g_paintedtime, end_time, + const_cast(buffer.pAudioData)); + } else { + buffer.AudioBytes = TransferMultichannelSurroundInterleaved( + PAINTBUFFER, REARPAINTBUFFER, CENTERPAINTBUFFER, g_paintedtime, + end_time, const_cast(buffer.pAudioData)); + } + + const HRESULT hr{xaudio2_source_voice_->SubmitSourceBuffer(&buffer)}; + if (FAILED(hr)) { + DebugWarn("Submitting samples buffer failed w/e 0x%8x.\n", hr); + } } -bool CXboxVoice::IsHeadsetPresent( int id ) -{ - return m_pXHVEngine->IsHeadsetPresent( id ); +// Get our device name +const char *CAudioXAudio2::DeviceName() { + if (m_bSurround) { + if (device_channels_count_ == 4) { + return "XAudio2 4 Channel Surround"; + } + + if (device_channels_count_ == 6) { + return "XAudio2 5.1 Channel Surround"; + } + + if (device_channels_count_ == 8) { + return "XAudio2 7.1 Channel Surround"; + } + } + + if (m_bHeadphone) { + return "XAudio2 Headphones"; + } + + return "XAudio2 Speakers"; } -void CXboxVoice::RemoveAllTalkers( CClientInfo *pLocal ) -{ - int numRemoteTalkers; - XUID remoteTalkers[MAX_PLAYERS]; - GetRemoteTalkers( &numRemoteTalkers, remoteTalkers ); - - for ( int iRemote = 0; iRemote < numRemoteTalkers; iRemote++ ) - { - m_pXHVEngine->UnregisterRemoteTalker( remoteTalkers[iRemote] ); - } - - if ( pLocal ) - { - for ( int i = 0; i < pLocal->m_cPlayers; ++i ) - { - if ( pLocal->m_xuids[i] == 0 ) - continue; - - m_pXHVEngine->UnregisterLocalTalker( pLocal->m_iControllers[i] ); - } - } -} \ No newline at end of file +} // namespace se::engine::audio::xaudio2 \ No newline at end of file diff --git a/engine/audio/private/snd_dev_xaudio.h b/engine/audio/private/snd_dev_xaudio.h index 83ec00bb0..7ad7b3470 100644 --- a/engine/audio/private/snd_dev_xaudio.h +++ b/engine/audio/private/snd_dev_xaudio.h @@ -55,7 +55,6 @@ class CXboxVoice }; CXboxVoice *Audio_GetXVoice( void ); -IXAudio2 *Audio_GetXAudio2( void ); #endif diff --git a/engine/audio/private/snd_dma.cpp b/engine/audio/private/snd_dma.cpp index d3a8b64a7..49ac7933f 100644 --- a/engine/audio/private/snd_dma.cpp +++ b/engine/audio/private/snd_dma.cpp @@ -21,7 +21,7 @@ #include "vaudio/ivaudio.h" #include "../../client.h" #include "../../cl_main.h" -#include "utldict.h" +#include "tier1/utldict.h" #include "mempool.h" #include "../../enginetrace.h" // for traceline #include "../../public/bspflags.h" // for traceline @@ -37,10 +37,6 @@ #include "../../pure_server.h" #include "filesystem/IQueuedLoader.h" #include "voice.h" -#if defined( _X360 ) -#include "xbox/xbox_console.h" -#include "xmp.h" -#endif #include "replay/iclientreplaycontext.h" #include "replay/ireplaymovierenderer.h" @@ -48,14 +44,6 @@ #include "video/ivideoservices.h" extern IVideoServices *g_pVideo; -/* -#include "gl_model_private.h" -#include "world.h" -#include "vphysics_interface.h" -#include "client_class.h" -#include "server_class.h" -*/ - // memdbgon must be the last include file in a .cpp file!!! #include "tier0/memdbgon.h" @@ -66,8 +54,23 @@ extern IVideoServices *g_pVideo; // //#define DEBUG_CHANNELS -#define SNDLVL_TO_DIST_MULT( sndlvl ) ( sndlvl ? ((powf( 10.0f, snd_refdb.GetFloat() / 20 ) / powf( 10.0f, (float)sndlvl / 20 )) / snd_refdist.GetFloat()) : 0 ) -#define DIST_MULT_TO_SNDLVL( dist_mult ) (soundlevel_t)(int)( (dist_mult) ? ( 20 * log10f( powf( 10.0f, snd_refdb.GetFloat() / 20 ) / ((dist_mult) * snd_refdist.GetFloat()) ) ) : 0 ) +extern ConVar snd_refdb; +extern ConVar snd_refdist; + +inline float SNDLVL_TO_DIST_MULT( soundlevel_t sndlvl ) +{ + return sndlvl != SNDLVL_NONE + ? powf( 10.0f, ( snd_refdb.GetFloat() - static_cast( sndlvl ) ) / 20 ) / snd_refdist.GetFloat() + : 0; +} + +inline soundlevel_t DIST_MULT_TO_SNDLVL( float dist_mult ) +{ + // dimhotepus: Rewrite and simplify to match SNDLVL_TO_DIST_MULT. + return (soundlevel_t)(int)( dist_mult + ? snd_refdb.GetFloat() - log10f( dist_mult * snd_refdist.GetFloat() ) * 20 + : 0 ); +} extern ConVar dsp_spatial; extern IPhysicsSurfaceProps *physprop; @@ -447,7 +450,7 @@ typedef struct static soundfade_t soundfade; // Client sound fading singleton object -// 0)headphones 2)stereo speakers 4)quad 5)5point1 +// 0)headphones 2)stereo speakers 4)quad 5)5point1 7)7point1 // autodetected from windows settings ConVar snd_surround( "snd_surround_speakers", "-1", FCVAR_INTERNAL_USE ); ConVar snd_legacy_surround( "snd_legacy_surround", "0", FCVAR_ARCHIVE ); @@ -497,7 +500,7 @@ class CResourcePreloadSound : public CResourcePreload { bool bSpew = ( g_pQueuedLoader->GetSpewDetail() & LOADER_DETAIL_PURGES ) != 0; - for ( int i = s_Sounds.FirstInorder(); i != s_Sounds.InvalidIndex(); i = s_Sounds.NextInorder( i ) ) + for ( auto i = s_Sounds.FirstInorder(); i != s_Sounds.InvalidIndex(); i = s_Sounds.NextInorder( i ) ) { // the master sound table grows forever // remove sound sources from the master sound table that were not in the preload list @@ -538,7 +541,7 @@ class CResourcePreloadSound : public CResourcePreload { bool bSpew = ( g_pQueuedLoader->GetSpewDetail() & LOADER_DETAIL_PURGES ) != 0; - for ( int i = s_Sounds.FirstInorder(); i != s_Sounds.InvalidIndex(); i = s_Sounds.NextInorder( i ) ) + for ( auto i = s_Sounds.FirstInorder(); i != s_Sounds.InvalidIndex(); i = s_Sounds.NextInorder( i ) ) { // the master sound table grows forever // remove sound sources from the master sound table that were not in the preload list @@ -1010,8 +1013,8 @@ void S_ReloadFilesInList( IFileList *pFilesToReload ) CUtlVector< CSfxTable * > processed; - int iLast = s_Sounds.LastInorder(); - for ( int i = s_Sounds.FirstInorder(); i != iLast; i = s_Sounds.NextInorder( i ) ) + auto iLast = s_Sounds.LastInorder(); + for ( auto i = s_Sounds.FirstInorder(); i != iLast; i = s_Sounds.NextInorder( i ) ) { FileNameHandle_t fnHandle = s_Sounds.Key( i ); char filename[MAX_PATH * 3]; @@ -1812,15 +1815,13 @@ void SND_GetDopplerPoints( channel_t *pChannel, QAngle &source_angles, Vector &v pitch = DOPPLER_PITCH_MAX - dist * (DOPPLER_PITCH_MAX - DOPPLER_PITCH_MIN); - pChannel->basePitch = (int)(pitch * 100.0); + pChannel->basePitch = (int)(pitch * 100.0f); } // console variables used to construct gain curve - don't change these! extern ConVar snd_foliage_db_loss; extern ConVar snd_gain; -extern ConVar snd_refdb; -extern ConVar snd_refdist; extern ConVar snd_gain_max; extern ConVar snd_gain_min; @@ -6411,7 +6412,7 @@ void S_Update_Thread() float frameTime = THREADED_MIX_TIME * 0.001f; double lastFrameTime = Plat_FloatTime(); - while ( !g_bMixThreadExit ) + while ( !g_bMixThreadExit.load(std::memory_order::memory_order_relaxed) ) { double t0 = Plat_FloatTime(); @@ -6441,7 +6442,7 @@ void S_ShutdownMixThread() { if ( g_hMixThread ) { - g_bMixThreadExit = true; + g_bMixThreadExit.store(true, std::memory_order::memory_order_relaxed); ThreadJoin( g_hMixThread ); ReleaseThreadHandle( g_hMixThread ); g_hMixThread = NULL; @@ -6460,7 +6461,7 @@ void S_Update_( float mixAheadTime ) { if ( !g_hMixThread ) { - g_bMixThreadExit = false; + g_bMixThreadExit.store(false, std::memory_order::memory_order_relaxed); g_hMixThread = ThreadExecuteSolo( "SoundMixer", S_Update_Thread ); } } diff --git a/engine/audio/private/snd_wave_data.cpp b/engine/audio/private/snd_wave_data.cpp index 5baa4d61d..dac883e29 100644 --- a/engine/audio/private/snd_wave_data.cpp +++ b/engine/audio/private/snd_wave_data.cpp @@ -42,10 +42,10 @@ extern double realtime; #define TF_XBOX_WAV_MEMORY_CACHE ( 24 * 1024 * 1024 ) // Team Fortress uses a larger cache // Dev builds will be missing soundcaches and hitch sometimes, we only care if its being properly launched from steam where sound caches should be complete. -ConVar snd_async_spew_blocking( "snd_async_spew_blocking", "1", 0, "Spew message to console any time async sound loading blocks on file i/o. ( 0=Off, 1=With -steam only, 2=Always" ); +ConVar snd_async_spew_blocking( "snd_async_spew_blocking", "1", 0, "Spew message to console any time async sound loading blocks on file I/O. (0=Off, 1=With -steam only, 2=Always)" ); ConVar snd_async_spew( "snd_async_spew", "0", 0, "Spew all async sound reads, including success" ); ConVar snd_async_fullyasync( "snd_async_fullyasync", "0", 0, "All playback is fully async (sound doesn't play until data arrives)." ); -ConVar snd_async_stream_spew( "snd_async_stream_spew", "0", 0, "Spew streaming info ( 0=Off, 1=streams, 2=buffers" ); +ConVar snd_async_stream_spew( "snd_async_stream_spew", "0", 0, "Spew streaming info (0=Off, 1=streams, 2=buffers)" ); static bool SndAsyncSpewBlocking() { diff --git a/engine/audio/private/snd_win.cpp b/engine/audio/private/snd_win.cpp index b45ec0f30..aca9cfbe9 100644 --- a/engine/audio/private/snd_win.cpp +++ b/engine/audio/private/snd_win.cpp @@ -1,8 +1,6 @@ //========= Copyright Valve Corporation, All rights reserved. ============// // -// Purpose: -// -//=====================================================================================// +// Purpose: Sound device dispatch and initialization. #include "audio_pch.h" @@ -17,23 +15,64 @@ ConVar snd_audioqueue( "snd_audioqueue", "1" ); #endif +#include "snd_dev_xaudio.h" + // memdbgon must be the last include file in a .cpp file!!! #include "tier0/memdbgon.h" +// dimhotepus: Allow to override sound device. +ConVar snd_device_override( "snd_device_override", "0", FCVAR_ARCHIVE, + "Allow to override used sound device.\nPossible values:\n" + "0\tAutodetection\n" +#ifdef PLATFORM_WINDOWS + "1\tXAudio 2 [default]\n" + "2\tDirect Sound 8\n", +#elif defined(OSX) + "1\tMac Audio Queue [default]\n" + "2\tOpen AL\n", +#elif defined(USE_SDL) + "1\tSDL [default]", +#endif + true, + 0, + true, +#if defined(PLATFORM_WINDOWS) + 2 +#elif defined(OSX) + 2 +#elif defined(USE_SDL) + 1 +#endif +); + +/** + * @brief Audio device kind. + */ +enum class AudioDeviceKind { + /** + * @brief Autodetect. + */ + kAutodetect = 0 +#ifdef PLATFORM_WINDOWS + , kXAudio2 = 1 + , kDirectSound8 = 2 +#elif defined(OSX) + , kMacAudioQueue = 1 + , kOpenAL = 2 +#elif defined(USE_SDL) + , kSDL = 2 +#endif +}; + bool snd_firsttime = true; /* * Global variables. Must be visible to window-procedure function * so it can unlock and free the data block after it has been played. */ -IAudioDevice *g_AudioDevice = NULL; - -/* -================== -S_BlockSound -================== -*/ -void S_BlockSound( void ) +IAudioDevice *g_AudioDevice = nullptr; + +void S_BlockSound() { if ( !g_AudioDevice ) return; @@ -41,12 +80,7 @@ void S_BlockSound( void ) g_AudioDevice->Pause(); } -/* -================== -S_UnblockSound -================== -*/ -void S_UnblockSound( void ) +void S_UnblockSound() { if ( !g_AudioDevice ) return; @@ -54,75 +88,75 @@ void S_UnblockSound( void ) g_AudioDevice->UnPause(); } -/* -================== -AutoDetectInit - -Try to find a sound device to mix for. -Returns a CAudioNULLDevice if nothing is found. -================== -*/ +/** + * @brief Autodetect and find sound device to mix on. + * @param Unused. + * @return Audio device to mix on. Null device when no sound. + */ IAudioDevice *IAudioDevice::AutoDetectInit() { - IAudioDevice *pDevice = NULL; + IAudioDevice *pDevice = nullptr; - if ( IsPC() ) - { #if defined( WIN32 ) && !defined( USE_SDL ) - if ( snd_firsttime ) - { - pDevice = Audio_CreateDirectSoundDevice(); + if ( snd_firsttime ) + { + const auto device_override = + static_cast(snd_device_override.GetInt()); + + switch (device_override) { + default: + Warning( "Unknown 'snd_device_override' con var value %d. Using autodetection.\n", + to_underlying(device_override) ); + [[fallthrough]]; + + case AudioDeviceKind::kAutodetect: + case AudioDeviceKind::kXAudio2: + { + pDevice = Audio_CreateXAudioDevice(); + if ( pDevice ) + { + // XAudio2 requires threaded mixing. + S_EnableThreadedMixing( true ); + } + } + break; + + case AudioDeviceKind::kDirectSound8: + pDevice = Audio_CreateDirectSoundDevice(); + break; } + } #elif defined(OSX) - if ( !CommandLine()->CheckParm( "-snd_openal" ) ) - { - DevMsg( "Using AudioQueue Interface\n" ); + const auto device_override = + static_cast(snd_device_override.GetInt()); + + switch (device_override) { + default: + Warning( "Unknown 'snd_device_override' con var value %d. Using autodetection.\n", + to_underlying(device_override) ); + [[fallthrough]]; + + case AudioDeviceKind::kAutodetect: + case AudioDeviceKind::kMacAudioQueue: pDevice = Audio_CreateMacAudioQueueDevice(); - } - if ( !pDevice ) - { - DevMsg( "Using OpenAL Interface\n" ); - pDevice = Audio_CreateOpenALDevice(); // fall back to openAL if the audio queue fails - } -#elif defined( USE_SDL ) - DevMsg( "Trying SDL Audio Interface\n" ); - pDevice = Audio_CreateSDLAudioDevice(); - -#ifdef NEVER - // Jul 2012. mikesart. E-mail exchange with Ryan Gordon after figuring out that - // Audio_CreatePulseAudioDevice() wasn't working on Ubuntu 12.04 (lots of stuttering). - // - // > I installed libpulse-dev, rebuilt SDL, and now SDL is using pulse - // > audio and everything is working great. However I'm wondering if we - // > need to fall back to PulseAudio in our codebase if SDL is doing that - // > for us. I mean, is it worth me going through and debugging our Pulse - // > Audio path or should I just remove it? - // - // Remove it...it never worked well, and only remained in case there were - // concerns about relying on SDL. The SDL codepath is way easier to read, - // simpler to maintain, and handles all sorts of strange audio backends, - // including Pulse. - if ( !pDevice ) - { - DevMsg( "Trying PulseAudio Interface\n" ); - pDevice = Audio_CreatePulseAudioDevice(); // fall back to PulseAudio if SDL fails - } -#endif // NEVER + break; -#else -#error "Please define your platform" -#endif + case AudioDeviceKind::kOpenAL: + pDevice = Audio_CreateOpenALDevice(); + break; } -#if defined( _X360 ) - else + + if ( !pDevice ) { - pDevice = Audio_CreateXAudioDevice( true ); - if ( pDevice ) - { - // xaudio requires threaded mixing - S_EnableThreadedMixing( true ); - } + // fall back to openAL if the audio queue fails + DevMsg( "Using OpenAL Interface\n" ); + pDevice = Audio_CreateOpenALDevice(); } +#elif defined( USE_SDL ) + DevMsg( "Trying SDL Audio Interface\n" ); + pDevice = Audio_CreateSDLAudioDevice(); +#else +#error "Please define your platform" #endif snd_firsttime = false; @@ -130,7 +164,7 @@ IAudioDevice *IAudioDevice::AutoDetectInit() if ( !pDevice ) { if ( snd_firsttime ) - DevMsg( "No sound device initialized\n" ); + DevMsg( "No sound device initialized.\n" ); return Audio_GetNullDevice(); } @@ -138,14 +172,7 @@ IAudioDevice *IAudioDevice::AutoDetectInit() return pDevice; } -/* -============== -SNDDMA_Shutdown - -Reset the sound device for exiting -=============== -*/ -void SNDDMA_Shutdown( void ) +void SNDDMA_Shutdown() { if ( g_AudioDevice != Audio_GetNullDevice() ) { @@ -159,4 +186,3 @@ void SNDDMA_Shutdown( void ) g_AudioDevice = Audio_GetNullDevice(); } } - diff --git a/engine/audio/private/sound_private.h b/engine/audio/private/sound_private.h index f054c7c89..b6eb25cf2 100644 --- a/engine/audio/private/sound_private.h +++ b/engine/audio/private/sound_private.h @@ -7,6 +7,7 @@ //=============================================================================// #include "basetypes.h" +#include "sound.h" #include "snd_fixedint.h" #ifndef SOUND_PRIVATE_H diff --git a/engine/audio/private/voice.cpp b/engine/audio/private/voice.cpp index 30d4adc4a..a2a82a0ac 100644 --- a/engine/audio/private/voice.cpp +++ b/engine/audio/private/voice.cpp @@ -368,7 +368,7 @@ class CVoiceWriter void Flush() { - for ( int i = m_VoiceWriter.FirstInorder(); i != m_VoiceWriter.InvalidIndex(); i = m_VoiceWriter.NextInorder( i ) ) + for ( auto i = m_VoiceWriter.FirstInorder(); i != m_VoiceWriter.InvalidIndex(); i = m_VoiceWriter.NextInorder( i ) ) { CVoiceWriterData *data = &m_VoiceWriter[ i ]; @@ -386,7 +386,7 @@ class CVoiceWriter return; } - for ( int i = m_VoiceWriter.FirstInorder(); i != m_VoiceWriter.InvalidIndex(); i = m_VoiceWriter.NextInorder( i ) ) + for ( auto i = m_VoiceWriter.FirstInorder(); i != m_VoiceWriter.InvalidIndex(); i = m_VoiceWriter.NextInorder( i ) ) { CVoiceWriterData *data = &m_VoiceWriter[ i ]; @@ -420,7 +420,7 @@ class CVoiceWriter CVoiceWriterData search; search.m_pChannel = ch; - int idx = m_VoiceWriter.Find( search ); + auto idx = m_VoiceWriter.Find( search ); if ( idx == m_VoiceWriter.InvalidIndex() ) { idx = m_VoiceWriter.Insert( search ); diff --git a/engine/engine.vpc b/engine/engine.vpc index 64df9b1ea..26cc50e2d 100644 --- a/engine/engine.vpc +++ b/engine/engine.vpc @@ -529,8 +529,7 @@ $Project "engine" } } - // X360 only audio files - $File "audio\private\snd_dev_xaudio.cpp" [$X360] \ + $File "audio\private\snd_dev_xaudio.cpp" \ "audio\private\snd_wave_mixer_xma.cpp" [$X360] { $Configuration diff --git a/engine/host.cpp b/engine/host.cpp index fc5ab07a2..d219848ef 100644 --- a/engine/host.cpp +++ b/engine/host.cpp @@ -123,10 +123,6 @@ #include "soundservice.h" #include "profile.h" #include "steam/isteamremotestorage.h" -#if defined( _X360 ) -#include "xbox/xbox_win32stubs.h" -#include "audio_pch.h" -#endif #if defined( LINUX ) #include #include "include/SDL3/SDL.h" @@ -135,7 +131,6 @@ #include "audio/private/voice_mixer_controls.h" #include "ixboxsystem.h" -extern IXboxSystem *g_pXboxSystem; extern ConVar cl_cloud_settings; extern ConVar cl_logofile; @@ -3562,6 +3557,11 @@ void Host_InitGpu() Cbuf_AddText("star_gpu"); } +void Host_InitAudio() +{ + Cbuf_AddText("star_audio_render"); +} + void Host_InitOperatingSystem() { Cbuf_AddText("star_os"); @@ -3972,6 +3972,8 @@ void Host_Init( bool bDedicated ) Host_InitRam(); // Print GPU info. Host_InitGpu(); + // Print Audio info. + Host_InitAudio(); // Print OS info. Host_InitOperatingSystem(); diff --git a/engine/sys_dll.cpp b/engine/sys_dll.cpp index ac55f72f8..928698501 100644 --- a/engine/sys_dll.cpp +++ b/engine/sys_dll.cpp @@ -61,6 +61,9 @@ #ifdef _WIN32 #include #endif + +#include "audio/public/snd_device.h" +#include "audio/private/sound_private.h" #include "toolframework/itoolframework.h" // memdbgon must be the last include file in a .cpp file!!! @@ -1646,7 +1649,7 @@ CON_COMMAND( star_memory, "Dump RAM stats" ) std::system_category().message( (int) ::GetLastError() ).c_str() ); } - ConDMsg( "hardware: Host RAM available: %s\n", + ConDMsg( "hardware: Host dedicated RAM available: %s\n", Q_pretifymem(host_parms.memsize, 2, true) ); #endif } @@ -1667,12 +1670,12 @@ CON_COMMAND( star_gpu, "Dump GPU stats" ) if (videomode) { - Q_snprintf( gpuInfo, sizeof( gpuInfo ), - "GPU %s, VRAM %s, DirectX level '%s', Viewport %d x %d", - info.m_pDriverName, - gpuRamSize, - dxlevel ? dxlevel : "N/A", - videomode->GetModeWidth(), videomode->GetModeHeight()); + Q_snprintf( gpuInfo, sizeof( gpuInfo ), + "GPU %s, VRAM %s, DirectX level '%s', Viewport %d x %d", + info.m_pDriverName, + gpuRamSize, + dxlevel ? dxlevel : "N/A", + videomode->GetModeWidth(), videomode->GetModeHeight()); } else { @@ -1687,6 +1690,20 @@ CON_COMMAND( star_gpu, "Dump GPU stats" ) ConDMsg( "hardware: %s\n", gpuInfo ); } +CON_COMMAND( star_audio_render, "Dump audio render device stats" ) +{ + if ( g_AudioDevice ) + { + ConDMsg( "hardware: Audio renderer %s, %d channel(s), %d bits/sample, %d Hz\n", + g_AudioDevice->DeviceName(), g_AudioDevice->DeviceChannels(), + g_AudioDevice->DeviceSampleBits(), g_AudioDevice->DeviceDmaSpeed() ); + } + else + { + ConDMsg( "hardware: Audio renderer N/A\n" ); + } +} + void DisplaySystemVersion(char *osversion, int maxlen); CON_COMMAND( star_os, "Dump operating system stats" ) diff --git a/engine/sys_mainwind.cpp b/engine/sys_mainwind.cpp index bd43c06b3..31e7c98b3 100644 --- a/engine/sys_mainwind.cpp +++ b/engine/sys_mainwind.cpp @@ -454,22 +454,27 @@ void VCR_HandlePlaybackMessages( { g_iVCRPlaybackSleepInterval -= 5; } - else if ( toupper( wParam ) == 'Q' ) - { - TerminateProcess( GetCurrentProcess(), 1 ); - } - else if ( toupper( wParam ) == 'P' ) - { - VCR_EnterPausedState(); - } - else if ( toupper( wParam ) == 'S' && !g_bVCRSingleStep ) - { - g_bWaitingForStepKeyUp = true; - VCR_EnterPausedState(); - } - else if ( toupper( wParam ) == 'D' ) + else { - g_bShowVCRPlaybackDisplay = !g_bShowVCRPlaybackDisplay; + const int upper = toupper(static_cast(wParam)); + + if ( upper == 'Q' ) + { + TerminateProcess( GetCurrentProcess(), 1 ); + } + else if ( upper == 'P' ) + { + VCR_EnterPausedState(); + } + else if ( upper == 'S' && !g_bVCRSingleStep ) + { + g_bWaitingForStepKeyUp = true; + VCR_EnterPausedState(); + } + else if ( upper == 'D' ) + { + g_bShowVCRPlaybackDisplay = !g_bShowVCRPlaybackDisplay; + } } g_iVCRPlaybackSleepInterval = clamp( g_iVCRPlaybackSleepInterval, 0, 500 ); @@ -642,17 +647,46 @@ LRESULT CGame::WindowProc( HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) ::SetForegroundWindow( hWnd ); break; - case WM_ACTIVATEAPP: + // Sent to both the window being activated and the window being deactivated. + // If the windows use the same input queue, the message is sent + // synchronously, first to the window procedure of the top-level window + // being deactivated, then to the window procedure of the top-level window + // being activated. If the windows use different input queues, the message + // is sent asynchronously, so the window is activated immediately. + case WM_ACTIVATE: { + const unsigned short window_activation_state{LOWORD( wParam )}; + const bool is_activated{window_activation_state != WA_INACTIVE}; + // The high-order word specifies the minimized state of the window + // being activated or deactivated. A nonzero value indicates the + // window is minimized. + // const bool is_window_minimized{HIWORD( wParam ) != 0}; + + // dimhotepus: Unify way to handle sound mute on focus lost. + ConVarRef snd_mute_losefocus("snd_mute_losefocus"); + if (snd_mute_losefocus.GetBool()) + { + if ( is_activated ) + { + S_UnblockSound(); + } + else + { + S_BlockSound(); + // dimhotepus: Need to be thread-safe. + // S_ClearBuffer(); + } + } + if ( CanPostActivateEvents() ) { - bool bActivated = ( wParam == 1 ); event.m_nType = IE_AppActivated; - event.m_nData = bActivated; + event.m_nData = is_activated; g_pInputSystem->PostUserEvent( event ); } + + break; } - break; case WM_POWERBROADCAST: switch (wParam) diff --git a/engine/voice_codecs/celt/voiceencoder_celt.cpp b/engine/voice_codecs/celt/voiceencoder_celt.cpp index 5bd74fd9c..d3279b592 100644 --- a/engine/voice_codecs/celt/voiceencoder_celt.cpp +++ b/engine/voice_codecs/celt/voiceencoder_celt.cpp @@ -75,7 +75,8 @@ VoiceEncoder_Celt::VoiceEncoder_Celt() { VoiceEncoder_Celt::~VoiceEncoder_Celt() { TermStates(); - Msg("Shut down CELT codec %u (bit stream version)\n", CELT_GET_BITSTREAM_VERSION); + Msg("Shut down CELT codec %u (bit stream version)\n", + CELT_GET_BITSTREAM_VERSION); } bool VoiceEncoder_Celt::Init(VoiceCodecQuality quality, int &raw_frame_size, @@ -90,7 +91,8 @@ bool VoiceEncoder_Celt::Init(VoiceCodecQuality quality, int &raw_frame_size, preset_ = kCeltPresets[to_underlying(quality)]; raw_frame_size = preset_.frame_size * BYTES_PER_SAMPLE; - Msg("Start up CELT codec %u (bit stream version)\n", CELT_GET_BITSTREAM_VERSION); + Msg("Start up CELT codec %u (bit stream version)\n", + CELT_GET_BITSTREAM_VERSION); int rc = 0; @@ -98,8 +100,7 @@ bool VoiceEncoder_Celt::Init(VoiceCodecQuality quality, int &raw_frame_size, if (!mode_) { Warning( "CELT codec (%d Hz, %d channel(s), %d samples/channel) startup " - "failure: " - "%s\n", + "failure: %s\n", preset_.sample_rate, kEncoderDecoderChannelsNo, preset_.frame_size, celt_strerror(rc)); return false; @@ -188,8 +189,7 @@ void VoiceEncoder_Celt::DecodeFrame(const char *in, char *out) { if (rc) { Warning( "CELT decoder (%d Hz, %d channel(s), %d samples/channel, %d in " - "bytes) " - "zero data failure: %s\n", + "bytes) zero data failure: %s\n", preset_.sample_rate, kEncoderDecoderChannelsNo, preset_.frame_size, preset_.packet_size, celt_strerror(rc)); } diff --git a/inputsystem/inputsystem.cpp b/inputsystem/inputsystem.cpp index 4dbe734ff..2b1605411 100644 --- a/inputsystem/inputsystem.cpp +++ b/inputsystem/inputsystem.cpp @@ -1250,13 +1250,27 @@ LRESULT CInputSystem::WindowProc( HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lP { #if !defined( USE_SDL ) - case WM_ACTIVATEAPP: - if ( hwnd == m_hAttachedHWnd ) + // Sent to both the window being activated and the window being deactivated. + // If the windows use the same input queue, the message is sent + // synchronously, first to the window procedure of the top-level window + // being deactivated, then to the window procedure of the top-level window + // being activated. If the windows use different input queues, the message + // is sent asynchronously, so the window is activated immediately. + case WM_ACTIVATE: { - bool bActivated = ( wParam == 1 ); - if ( !bActivated ) + const unsigned short window_activation_state{LOWORD( wParam )}; + const bool is_activated{window_activation_state != WA_INACTIVE}; + // The high-order word specifies the minimized state of the window + // being activated or deactivated. A nonzero value indicates the + // window is minimized. + // const bool is_window_minimized{HIWORD( wParam ) != 0}; + + if ( hwnd == m_hAttachedHWnd ) { - ResetInputState(); + if ( !is_activated ) + { + ResetInputState(); + } } } break;