5514: Update menu music state when the config variables change via console, various refactoring r=heinrich5991 a=Robyt3

Also update the background music when `snd_enable` or `snd_enable_music` change via console or bind. Closes #2911.

For this purpose, add `IsPlaying` method to engine sound and client sound component to check whether a specific sound sample is already playing.

Various refactoring in engine sound.

## Checklist

- [X] Tested the change ingame
- [ ] Provided screenshots if it is a visual change
- [ ] Tested in combination with possibly related configuration options
- [ ] Written a unit test if it works standalone, system.c especially
- [ ] Considered possible null pointers and out of bounds array indexing
- [ ] Changed no physics that affect existing maps
- [ ] Tested the change with [ASan+UBSan or valgrind's memcheck](https://github.com/ddnet/ddnet/#using-addresssanitizer--undefinedbehavioursanitizer-or-valgrinds-memcheck) (optional)


Co-authored-by: Robert Müller <robytemueller@gmail.com>
This commit is contained in:
bors[bot] 2022-06-30 11:07:07 +00:00 committed by GitHub
commit 2bd1273657
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 103 additions and 92 deletions

View file

@ -89,16 +89,6 @@ static int s_WVBufferSize = 0;
const int DefaultDistance = 1500; const int DefaultDistance = 1500;
int m_LastBreak = 0; int m_LastBreak = 0;
// TODO: there should be a faster way todo this
static short Int2Short(int i)
{
if(i > 0x7fff)
return 0x7fff;
else if(i < -0x7fff)
return -0x7fff;
return i;
}
static int IntAbs(int i) static int IntAbs(int i)
{ {
if(i < 0) if(i < 0)
@ -108,14 +98,13 @@ static int IntAbs(int i)
static void Mix(short *pFinalOut, unsigned Frames) static void Mix(short *pFinalOut, unsigned Frames)
{ {
int MasterVol;
Frames = minimum(Frames, m_MaxFrames); Frames = minimum(Frames, m_MaxFrames);
mem_zero(m_pMixBuffer, Frames * 2 * sizeof(int)); mem_zero(m_pMixBuffer, Frames * 2 * sizeof(int));
// acquire lock while we are mixing // acquire lock while we are mixing
m_SoundLock.lock(); m_SoundLock.lock();
MasterVol = m_SoundVolume; int MasterVol = m_SoundVolume;
for(auto &Voice : m_aVoices) for(auto &Voice : m_aVoices)
{ {
@ -260,21 +249,9 @@ static void Mix(short *pFinalOut, unsigned Frames)
// release the lock // release the lock
m_SoundLock.unlock(); m_SoundLock.unlock();
{ // clamp accumulated values
// clamp accumulated values for(unsigned i = 0; i < Frames * 2; i++)
// TODO: this seams slow pFinalOut[i] = clamp<int>(((m_pMixBuffer[i] * MasterVol) / 101) >> 8, std::numeric_limits<short>::min(), std::numeric_limits<short>::max());
for(unsigned i = 0; i < Frames; i++)
{
int j = i << 1;
int vl = ((m_pMixBuffer[j] * MasterVol) / 101) >> 8;
int vr = ((m_pMixBuffer[j + 1] * MasterVol) / 101) >> 8;
pFinalOut[j] = Int2Short(vl);
pFinalOut[j + 1] = Int2Short(vr);
// dbg_msg("sound", "the real shit: %d %d", pFinalOut[j], pFinalOut[j+1]);
}
}
#if defined(CONF_ARCH_ENDIAN_BIG) #if defined(CONF_ARCH_ENDIAN_BIG)
swap_endian(pFinalOut, sizeof(short), Frames * 2); swap_endian(pFinalOut, sizeof(short), Frames * 2);
@ -287,20 +264,20 @@ static void SdlCallback(void *pUnused, Uint8 *pStream, int Len)
#if defined(CONF_VIDEORECORDER) #if defined(CONF_VIDEORECORDER)
if(!(IVideo::Current() && g_Config.m_ClVideoSndEnable)) if(!(IVideo::Current() && g_Config.m_ClVideoSndEnable))
{ {
Mix((short *)pStream, Len / sizeof(int16_t) / 2); Mix((short *)pStream, Len / sizeof(short) / 2);
} }
else else
{ {
mem_zero(pStream, Len); mem_zero(pStream, Len);
} }
#else #else
Mix((short *)pStream, Len / 2 / 2); Mix((short *)pStream, Len / sizeof(short) / 2);
#endif #endif
} }
int CSound::Init() int CSound::Init()
{ {
m_SoundEnabled = 0; m_SoundEnabled = false;
m_pGraphics = Kernel()->RequestInterface<IEngineGraphics>(); m_pGraphics = Kernel()->RequestInterface<IEngineGraphics>();
m_pStorage = Kernel()->RequestInterface<IStorage>(); m_pStorage = Kernel()->RequestInterface<IStorage>();
@ -311,7 +288,7 @@ int CSound::Init()
if(SDL_InitSubSystem(SDL_INIT_AUDIO) < 0) if(SDL_InitSubSystem(SDL_INIT_AUDIO) < 0)
{ {
dbg_msg("gfx", "unable to init SDL audio: %s", SDL_GetError()); dbg_msg("client/sound", "unable to init SDL audio: %s", SDL_GetError());
return -1; return -1;
} }
@ -344,7 +321,7 @@ int CSound::Init()
SDL_PauseAudioDevice(m_Device, 0); SDL_PauseAudioDevice(m_Device, 0);
m_SoundEnabled = 1; m_SoundEnabled = true;
Update(); // update the volume Update(); // update the volume
return 0; return 0;
} }
@ -394,16 +371,14 @@ int CSound::AllocID()
void CSound::RateConvert(int SampleID) void CSound::RateConvert(int SampleID)
{ {
CSample *pSample = &m_aSamples[SampleID]; CSample *pSample = &m_aSamples[SampleID];
int NumFrames = 0;
short *pNewData = 0;
// make sure that we need to convert this sound // make sure that we need to convert this sound
if(!pSample->m_pData || pSample->m_Rate == m_MixingRate) if(!pSample->m_pData || pSample->m_Rate == m_MixingRate)
return; return;
// allocate new data // allocate new data
NumFrames = (int)((pSample->m_NumFrames / (float)pSample->m_Rate) * m_MixingRate); int NumFrames = (int)((pSample->m_NumFrames / (float)pSample->m_Rate) * m_MixingRate);
pNewData = (short *)calloc((size_t)NumFrames * pSample->m_Channels, sizeof(short)); short *pNewData = (short *)calloc((size_t)NumFrames * pSample->m_Channels, sizeof(short));
for(int i = 0; i < NumFrames; i++) for(int i = 0; i < NumFrames; i++)
{ {
@ -437,11 +412,11 @@ int CSound::DecodeOpus(int SampleID, const void *pData, unsigned DataSize)
CSample *pSample = &m_aSamples[SampleID]; CSample *pSample = &m_aSamples[SampleID];
OggOpusFile *OpusFile = op_open_memory((const unsigned char *)pData, DataSize, NULL); OggOpusFile *pOpusFile = op_open_memory((const unsigned char *)pData, DataSize, NULL);
if(OpusFile) if(pOpusFile)
{ {
int NumChannels = op_channel_count(OpusFile, -1); int NumChannels = op_channel_count(pOpusFile, -1);
int NumSamples = op_pcm_total(OpusFile, -1); // per channel! int NumSamples = op_pcm_total(pOpusFile, -1); // per channel!
pSample->m_Channels = NumChannels; pSample->m_Channels = NumChannels;
@ -453,15 +428,22 @@ int CSound::DecodeOpus(int SampleID, const void *pData, unsigned DataSize)
pSample->m_pData = (short *)calloc((size_t)NumSamples * NumChannels, sizeof(short)); pSample->m_pData = (short *)calloc((size_t)NumSamples * NumChannels, sizeof(short));
int Read;
int Pos = 0; int Pos = 0;
while(Pos < NumSamples) while(Pos < NumSamples)
{ {
Read = op_read(OpusFile, pSample->m_pData + Pos * NumChannels, NumSamples * NumChannels, NULL); const int Read = op_read(pOpusFile, pSample->m_pData + Pos * NumChannels, NumSamples * NumChannels, NULL);
if(Read < 0)
{
free(pSample->m_pData);
dbg_msg("sound/opus", "op_read error %d at %d", Read, Pos);
return -1;
}
else if(Read == 0) // EOF
break;
Pos += Read; Pos += Read;
} }
pSample->m_NumFrames = NumSamples; // ? pSample->m_NumFrames = Pos;
pSample->m_Rate = 48000; pSample->m_Rate = 48000;
pSample->m_LoopStart = -1; pSample->m_LoopStart = -1;
pSample->m_LoopEnd = -1; pSample->m_LoopEnd = -1;
@ -523,7 +505,6 @@ int CSound::DecodeWV(int SampleID, const void *pData, unsigned DataSize)
CSample *pSample = &m_aSamples[SampleID]; CSample *pSample = &m_aSamples[SampleID];
char aError[100]; char aError[100];
WavpackContext *pContext;
s_pWVBuffer = pData; s_pWVBuffer = pData;
s_WVBufferSize = DataSize; s_WVBufferSize = DataSize;
@ -536,9 +517,9 @@ int CSound::DecodeWV(int SampleID, const void *pData, unsigned DataSize)
Callback.get_pos = GetPos; Callback.get_pos = GetPos;
Callback.push_back_byte = PushBackByte; Callback.push_back_byte = PushBackByte;
Callback.read_bytes = ReadData; Callback.read_bytes = ReadData;
pContext = WavpackOpenFileInputEx(&Callback, (void *)1, 0, aError, 0, 0); WavpackContext *pContext = WavpackOpenFileInputEx(&Callback, (void *)1, 0, aError, 0, 0);
#else #else
pContext = WavpackOpenFileInput(ReadDataOld, aError); WavpackContext *pContext = WavpackOpenFileInput(ReadDataOld, aError);
#endif #endif
if(pContext) if(pContext)
{ {
@ -546,9 +527,6 @@ int CSound::DecodeWV(int SampleID, const void *pData, unsigned DataSize)
int BitsPerSample = WavpackGetBitsPerSample(pContext); int BitsPerSample = WavpackGetBitsPerSample(pContext);
unsigned int SampleRate = WavpackGetSampleRate(pContext); unsigned int SampleRate = WavpackGetSampleRate(pContext);
int NumChannels = WavpackGetNumChannels(pContext); int NumChannels = WavpackGetNumChannels(pContext);
int *pSrc;
short *pDst;
int i;
pSample->m_Channels = NumChannels; pSample->m_Channels = NumChannels;
pSample->m_Rate = SampleRate; pSample->m_Rate = SampleRate;
@ -566,13 +544,18 @@ int CSound::DecodeWV(int SampleID, const void *pData, unsigned DataSize)
} }
int *pBuffer = (int *)calloc((size_t)NumSamples * NumChannels, sizeof(int)); int *pBuffer = (int *)calloc((size_t)NumSamples * NumChannels, sizeof(int));
WavpackUnpackSamples(pContext, pBuffer, NumSamples); // TODO: check return value if(!WavpackUnpackSamples(pContext, pBuffer, NumSamples))
pSrc = pBuffer; {
free(pBuffer);
dbg_msg("sound/wv", "WavpackUnpackSamples failed. NumSamples=%d, NumChannels=%d", NumSamples, NumChannels);
return -1;
}
int *pSrc = pBuffer;
pSample->m_pData = (short *)calloc((size_t)NumSamples * NumChannels, sizeof(short)); pSample->m_pData = (short *)calloc((size_t)NumSamples * NumChannels, sizeof(short));
pDst = pSample->m_pData; short *pDst = pSample->m_pData;
for(i = 0; i < NumSamples * NumChannels; i++) for(int i = 0; i < NumSamples * NumChannels; i++)
*pDst++ = (short)*pSrc++; *pDst++ = (short)*pSrc++;
free(pBuffer); free(pBuffer);
@ -869,25 +852,23 @@ void CSound::SetChannel(int ChannelID, float Vol, float Pan)
ISound::CVoiceHandle CSound::Play(int ChannelID, int SampleID, int Flags, float x, float y) ISound::CVoiceHandle CSound::Play(int ChannelID, int SampleID, int Flags, float x, float y)
{ {
int VoiceID = -1;
int Age = -1;
int i;
m_SoundLock.lock(); m_SoundLock.lock();
// search for voice // search for voice
for(i = 0; i < NUM_VOICES; i++) int VoiceID = -1;
for(int i = 0; i < NUM_VOICES; i++)
{ {
int id = (m_NextVoice + i) % NUM_VOICES; int NextID = (m_NextVoice + i) % NUM_VOICES;
if(!m_aVoices[id].m_pSample) if(!m_aVoices[NextID].m_pSample)
{ {
VoiceID = id; VoiceID = NextID;
m_NextVoice = id + 1; m_NextVoice = NextID + 1;
break; break;
} }
} }
// voice found, use it // voice found, use it
int Age = -1;
if(VoiceID != -1) if(VoiceID != -1)
{ {
m_aVoices[VoiceID].m_pSample = &m_aSamples[SampleID]; m_aVoices[VoiceID].m_pSample = &m_aSamples[SampleID];
@ -966,10 +947,15 @@ void CSound::StopVoice(CVoiceHandle Voice)
if(m_aVoices[VoiceID].m_Age != Voice.Age()) if(m_aVoices[VoiceID].m_Age != Voice.Age())
return; return;
{ m_aVoices[VoiceID].m_pSample = 0;
m_aVoices[VoiceID].m_pSample = 0; m_aVoices[VoiceID].m_Age++;
m_aVoices[VoiceID].m_Age++; }
}
bool CSound::IsPlaying(int SampleID)
{
std::unique_lock<std::mutex> Lock(m_SoundLock);
const CSample *pSample = &m_aSamples[SampleID];
return std::any_of(std::begin(m_aVoices), std::end(m_aVoices), [pSample](const auto &Voice) { return Voice.m_pSample == pSample; });
} }
ISoundMixFunc CSound::GetSoundMixFunc() ISoundMixFunc CSound::GetSoundMixFunc()

View file

@ -13,17 +13,12 @@ class IStorage;
class CSound : public IEngineSound class CSound : public IEngineSound
{ {
int m_SoundEnabled; bool m_SoundEnabled;
SDL_AudioDeviceID m_Device; SDL_AudioDeviceID m_Device;
public:
IEngineGraphics *m_pGraphics; IEngineGraphics *m_pGraphics;
IStorage *m_pStorage; IStorage *m_pStorage;
int Init() override;
int Update() override;
int Shutdown() override;
int AllocID(); int AllocID();
static void RateConvert(int SampleID); static void RateConvert(int SampleID);
@ -32,7 +27,12 @@ public:
static int DecodeWV(int SampleID, const void *pData, unsigned DataSize); static int DecodeWV(int SampleID, const void *pData, unsigned DataSize);
static int DecodeOpus(int SampleID, const void *pData, unsigned DataSize); static int DecodeOpus(int SampleID, const void *pData, unsigned DataSize);
bool IsSoundEnabled() override { return m_SoundEnabled != 0; } public:
int Init() override;
int Update() override;
int Shutdown() override;
bool IsSoundEnabled() override { return m_SoundEnabled; }
int LoadWV(const char *pFilename) override; int LoadWV(const char *pFilename) override;
int LoadWVFromMem(const void *pData, unsigned DataSize, bool FromEditor) override; int LoadWVFromMem(const void *pData, unsigned DataSize, bool FromEditor) override;
@ -59,6 +59,7 @@ public:
void Stop(int SampleID) override; void Stop(int SampleID) override;
void StopAll() override; void StopAll() override;
void StopVoice(CVoiceHandle Voice) override; void StopVoice(CVoiceHandle Voice) override;
bool IsPlaying(int SampleID) override;
ISoundMixFunc GetSoundMixFunc() override; ISoundMixFunc GetSoundMixFunc() override;
void PauseAudioDevice() override; void PauseAudioDevice() override;

View file

@ -87,6 +87,7 @@ public:
virtual void Stop(int SampleID) = 0; virtual void Stop(int SampleID) = 0;
virtual void StopAll() = 0; virtual void StopAll() = 0;
virtual void StopVoice(CVoiceHandle Voice) = 0; virtual void StopVoice(CVoiceHandle Voice) = 0;
virtual bool IsPlaying(int SampleID) = 0;
virtual ISoundMixFunc GetSoundMixFunc() = 0; virtual ISoundMixFunc GetSoundMixFunc() = 0;
// useful for thread synchronization // useful for thread synchronization

View file

@ -994,6 +994,9 @@ void CMenus::OnInit()
Console()->Chain("add_friend", ConchainFriendlistUpdate, this); Console()->Chain("add_friend", ConchainFriendlistUpdate, this);
Console()->Chain("remove_friend", ConchainFriendlistUpdate, this); Console()->Chain("remove_friend", ConchainFriendlistUpdate, this);
Console()->Chain("snd_enable", ConchainUpdateMusicState, this);
Console()->Chain("snd_enable_music", ConchainUpdateMusicState, this);
Console()->Chain("cl_assets_entities", ConchainAssetsEntities, this); Console()->Chain("cl_assets_entities", ConchainAssetsEntities, this);
Console()->Chain("cl_asset_game", ConchainAssetGame, this); Console()->Chain("cl_asset_game", ConchainAssetGame, this);
Console()->Chain("cl_asset_emoticons", ConchainAssetEmoticons, this); Console()->Chain("cl_asset_emoticons", ConchainAssetEmoticons, this);
@ -1017,6 +1020,23 @@ void CMenus::OnInit()
Storage()->ListDirectory(IStorage::TYPE_ALL, "menuimages", MenuImageScan, this); Storage()->ListDirectory(IStorage::TYPE_ALL, "menuimages", MenuImageScan, this);
} }
void CMenus::ConchainUpdateMusicState(IConsole::IResult *pResult, void *pUserData, IConsole::FCommandCallback pfnCallback, void *pCallbackUserData)
{
pfnCallback(pResult, pCallbackUserData);
auto *pSelf = (CMenus *)pUserData;
if(pResult->NumArguments())
pSelf->UpdateMusicState();
}
void CMenus::UpdateMusicState()
{
const bool ShouldPlay = Client()->State() == IClient::STATE_OFFLINE && g_Config.m_SndEnable && g_Config.m_SndMusic;
if(ShouldPlay && !m_pClient->m_Sounds.IsPlaying(SOUND_MENU))
m_pClient->m_Sounds.Enqueue(CSounds::CHN_MUSIC, SOUND_MENU);
else if(!ShouldPlay && m_pClient->m_Sounds.IsPlaying(SOUND_MENU))
m_pClient->m_Sounds.Stop(SOUND_MENU);
}
void CMenus::PopupMessage(const char *pTopic, const char *pBody, const char *pButton) void CMenus::PopupMessage(const char *pTopic, const char *pBody, const char *pButton)
{ {
// reset active item // reset active item
@ -1264,7 +1284,7 @@ int CMenus::Render()
} }
else if(s_Frame == 1) else if(s_Frame == 1)
{ {
m_pClient->m_Sounds.Enqueue(CSounds::CHN_MUSIC, SOUND_MENU); UpdateMusicState();
s_Frame++; s_Frame++;
m_DoubleClickIndex = -1; m_DoubleClickIndex = -1;
@ -2441,7 +2461,7 @@ void CMenus::OnStateChange(int NewState, int OldState)
if(NewState == IClient::STATE_OFFLINE) if(NewState == IClient::STATE_OFFLINE)
{ {
if(OldState >= IClient::STATE_ONLINE && NewState < IClient::STATE_QUITTING) if(OldState >= IClient::STATE_ONLINE && NewState < IClient::STATE_QUITTING)
m_pClient->m_Sounds.Play(CSounds::CHN_MUSIC, SOUND_MENU, 1.0f); UpdateMusicState();
m_Popup = POPUP_NONE; m_Popup = POPUP_NONE;
if(Client()->ErrorString() && Client()->ErrorString()[0] != 0) if(Client()->ErrorString() && Client()->ErrorString()[0] != 0)
{ {

View file

@ -492,6 +492,8 @@ protected:
//void render_loading(float percent); //void render_loading(float percent);
int RenderMenubar(CUIRect r); int RenderMenubar(CUIRect r);
void RenderNews(CUIRect MainView); void RenderNews(CUIRect MainView);
static void ConchainUpdateMusicState(IConsole::IResult *pResult, void *pUserData, IConsole::FCommandCallback pfnCallback, void *pCallbackUserData);
void UpdateMusicState();
// found in menus_demo.cpp // found in menus_demo.cpp
static bool DemoFilterChat(const void *pData, int Size, void *pUser); static bool DemoFilterChat(const void *pData, int Size, void *pUser);

View file

@ -1775,13 +1775,7 @@ void CMenus::RenderSettingsSound(CUIRect MainView)
if(DoButton_CheckBox(&g_Config.m_SndEnable, Localize("Use sounds"), g_Config.m_SndEnable, &Button)) if(DoButton_CheckBox(&g_Config.m_SndEnable, Localize("Use sounds"), g_Config.m_SndEnable, &Button))
{ {
g_Config.m_SndEnable ^= 1; g_Config.m_SndEnable ^= 1;
if(g_Config.m_SndEnable) UpdateMusicState();
{
if(g_Config.m_SndMusic && Client()->State() == IClient::STATE_OFFLINE)
m_pClient->m_Sounds.Play(CSounds::CHN_MUSIC, SOUND_MENU, 1.0f);
}
else
m_pClient->m_Sounds.Stop(SOUND_MENU);
m_NeedRestartSound = g_Config.m_SndEnable && (!s_SndEnable || s_SndRate != g_Config.m_SndRate); m_NeedRestartSound = g_Config.m_SndEnable && (!s_SndEnable || s_SndRate != g_Config.m_SndRate);
} }
@ -1792,13 +1786,7 @@ void CMenus::RenderSettingsSound(CUIRect MainView)
if(DoButton_CheckBox(&g_Config.m_SndMusic, Localize("Play background music"), g_Config.m_SndMusic, &Button)) if(DoButton_CheckBox(&g_Config.m_SndMusic, Localize("Play background music"), g_Config.m_SndMusic, &Button))
{ {
g_Config.m_SndMusic ^= 1; g_Config.m_SndMusic ^= 1;
if(Client()->State() == IClient::STATE_OFFLINE) UpdateMusicState();
{
if(g_Config.m_SndMusic)
m_pClient->m_Sounds.Play(CSounds::CHN_MUSIC, SOUND_MENU, 1.0f);
else
m_pClient->m_Sounds.Stop(SOUND_MENU);
}
} }
MainView.HSplitTop(20.0f, &Button, &MainView); MainView.HSplitTop(20.0f, &Button, &MainView);

View file

@ -228,10 +228,22 @@ void CSounds::Stop(int SetId)
if(m_WaitForSoundJob || SetId < 0 || SetId >= g_pData->m_NumSounds) if(m_WaitForSoundJob || SetId < 0 || SetId >= g_pData->m_NumSounds)
return; return;
CDataSoundset *pSet = &g_pData->m_aSounds[SetId]; const CDataSoundset *pSet = &g_pData->m_aSounds[SetId];
for(int i = 0; i < pSet->m_NumSounds; i++) for(int i = 0; i < pSet->m_NumSounds; i++)
Sound()->Stop(pSet->m_aSounds[i].m_Id); if(pSet->m_aSounds[i].m_Id != -1)
Sound()->Stop(pSet->m_aSounds[i].m_Id);
}
bool CSounds::IsPlaying(int SetId)
{
if(m_WaitForSoundJob || SetId < 0 || SetId >= g_pData->m_NumSounds)
return false;
const CDataSoundset *pSet = &g_pData->m_aSounds[SetId];
for(int i = 0; i < pSet->m_NumSounds; i++)
if(pSet->m_aSounds[i].m_Id != -1 && Sound()->IsPlaying(pSet->m_aSounds[i].m_Id))
return true;
return false;
} }
ISound::CVoiceHandle CSounds::PlaySample(int Channel, int SampleId, float Vol, int Flags) ISound::CVoiceHandle CSounds::PlaySample(int Channel, int SampleId, float Vol, int Flags)

View file

@ -64,6 +64,7 @@ public:
void PlayAt(int Channel, int SetId, float Vol, vec2 Pos); void PlayAt(int Channel, int SetId, float Vol, vec2 Pos);
void PlayAndRecord(int Channel, int SetId, float Vol, vec2 Pos); void PlayAndRecord(int Channel, int SetId, float Vol, vec2 Pos);
void Stop(int SetId); void Stop(int SetId);
bool IsPlaying(int SetId);
ISound::CVoiceHandle PlaySample(int Channel, int SampleId, float Vol, int Flags = 0); ISound::CVoiceHandle PlaySample(int Channel, int SampleId, float Vol, int Flags = 0);
ISound::CVoiceHandle PlaySampleAt(int Channel, int SampleId, float Vol, vec2 Pos, int Flags = 0); ISound::CVoiceHandle PlaySampleAt(int Channel, int SampleId, float Vol, vec2 Pos, int Flags = 0);