From f169ce1c8cc6cd8f018b9fd242a07b64ad6f40b8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20M=C3=BCller?= Date: Thu, 21 Sep 2023 22:59:46 +0200 Subject: [PATCH 1/7] Use `nullptr` instead of `0`, `0x0` and `NULL` --- src/engine/client/sound.cpp | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/src/engine/client/sound.cpp b/src/engine/client/sound.cpp index b1ce3c0e1..a3f074141 100644 --- a/src/engine/client/sound.cpp +++ b/src/engine/client/sound.cpp @@ -308,7 +308,7 @@ int CSound::Init() Format.userdata = NULL; // Open the audio device and start playing sound! - m_Device = SDL_OpenAudioDevice(NULL, 0, &Format, &FormatOut, 0); + m_Device = SDL_OpenAudioDevice(nullptr, 0, &Format, &FormatOut, 0); if(m_Device == 0) { @@ -357,7 +357,7 @@ void CSound::Shutdown() SDL_CloseAudioDevice(m_Device); SDL_QuitSubSystem(SDL_INIT_AUDIO); free(m_pMixBuffer); - m_pMixBuffer = 0; + m_pMixBuffer = nullptr; } int CSound::AllocID() @@ -365,7 +365,7 @@ int CSound::AllocID() // TODO: linear search, get rid of it for(unsigned SampleID = 0; SampleID < NUM_SAMPLES; SampleID++) { - if(m_aSamples[SampleID].m_pData == 0x0) + if(m_aSamples[SampleID].m_pData == nullptr) return SampleID; } @@ -726,8 +726,7 @@ void CSound::UnloadSample(int SampleID) Stop(SampleID); free(m_aSamples[SampleID].m_pData); - - m_aSamples[SampleID].m_pData = 0x0; + m_aSamples[SampleID].m_pData = nullptr; } float CSound::GetSampleDuration(int SampleID) @@ -926,7 +925,7 @@ void CSound::Stop(int SampleID) Voice.m_pSample->m_PausedAt = Voice.m_Tick; else Voice.m_pSample->m_PausedAt = 0; - Voice.m_pSample = 0; + Voice.m_pSample = nullptr; } } } @@ -944,7 +943,7 @@ void CSound::StopAll() else Voice.m_pSample->m_PausedAt = 0; } - Voice.m_pSample = 0; + Voice.m_pSample = nullptr; } } @@ -959,7 +958,7 @@ void CSound::StopVoice(CVoiceHandle Voice) if(m_aVoices[VoiceID].m_Age != Voice.Age()) return; - m_aVoices[VoiceID].m_pSample = 0; + m_aVoices[VoiceID].m_pSample = nullptr; m_aVoices[VoiceID].m_Age++; } From b0356aea1360d637c160eb2a328725d88f67b0c7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20M=C3=BCller?= Date: Thu, 21 Sep 2023 23:01:29 +0200 Subject: [PATCH 2/7] Rename argument, reduce indentation of `SetVoiceTimeOffset` --- src/engine/client/sound.cpp | 40 ++++++++++++++++++------------------- src/engine/client/sound.h | 2 +- src/engine/sound.h | 2 +- 3 files changed, 21 insertions(+), 23 deletions(-) diff --git a/src/engine/client/sound.cpp b/src/engine/client/sound.cpp index a3f074141..6c128b52d 100644 --- a/src/engine/client/sound.cpp +++ b/src/engine/client/sound.cpp @@ -788,7 +788,7 @@ void CSound::SetVoiceLocation(CVoiceHandle Voice, float x, float y) m_aVoices[VoiceID].m_Y = y; } -void CSound::SetVoiceTimeOffset(CVoiceHandle Voice, float offset) +void CSound::SetVoiceTimeOffset(CVoiceHandle Voice, float TimeOffset) { if(!Voice.IsValid()) return; @@ -799,27 +799,25 @@ void CSound::SetVoiceTimeOffset(CVoiceHandle Voice, float offset) if(m_aVoices[VoiceID].m_Age != Voice.Age()) return; - { - if(m_aVoices[VoiceID].m_pSample) - { - int Tick = 0; - bool IsLooping = m_aVoices[VoiceID].m_Flags & ISound::FLAG_LOOP; - uint64_t TickOffset = m_aVoices[VoiceID].m_pSample->m_Rate * offset; - if(m_aVoices[VoiceID].m_pSample->m_NumFrames > 0 && IsLooping) - Tick = TickOffset % m_aVoices[VoiceID].m_pSample->m_NumFrames; - else - Tick = clamp(TickOffset, (uint64_t)0, (uint64_t)m_aVoices[VoiceID].m_pSample->m_NumFrames); + if(!m_aVoices[VoiceID].m_pSample) + return; - // at least 200msec off, else depend on buffer size - float Threshold = maximum(0.2f * m_aVoices[VoiceID].m_pSample->m_Rate, (float)m_MaxFrames); - if(absolute(m_aVoices[VoiceID].m_Tick - Tick) > Threshold) - { - // take care of looping (modulo!) - if(!(IsLooping && (minimum(m_aVoices[VoiceID].m_Tick, Tick) + m_aVoices[VoiceID].m_pSample->m_NumFrames - maximum(m_aVoices[VoiceID].m_Tick, Tick)) <= Threshold)) - { - m_aVoices[VoiceID].m_Tick = Tick; - } - } + int Tick = 0; + bool IsLooping = m_aVoices[VoiceID].m_Flags & ISound::FLAG_LOOP; + uint64_t TickOffset = m_aVoices[VoiceID].m_pSample->m_Rate * TimeOffset; + if(m_aVoices[VoiceID].m_pSample->m_NumFrames > 0 && IsLooping) + Tick = TickOffset % m_aVoices[VoiceID].m_pSample->m_NumFrames; + else + Tick = clamp(TickOffset, (uint64_t)0, (uint64_t)m_aVoices[VoiceID].m_pSample->m_NumFrames); + + // at least 200msec off, else depend on buffer size + float Threshold = maximum(0.2f * m_aVoices[VoiceID].m_pSample->m_Rate, (float)m_MaxFrames); + if(absolute(m_aVoices[VoiceID].m_Tick - Tick) > Threshold) + { + // take care of looping (modulo!) + if(!(IsLooping && (minimum(m_aVoices[VoiceID].m_Tick, Tick) + m_aVoices[VoiceID].m_pSample->m_NumFrames - maximum(m_aVoices[VoiceID].m_Tick, Tick)) <= Threshold)) + { + m_aVoices[VoiceID].m_Tick = Tick; } } } diff --git a/src/engine/client/sound.h b/src/engine/client/sound.h index 207d52df5..2d527ec7c 100644 --- a/src/engine/client/sound.h +++ b/src/engine/client/sound.h @@ -49,7 +49,7 @@ public: void SetVoiceVolume(CVoiceHandle Voice, float Volume) override; void SetVoiceFalloff(CVoiceHandle Voice, float Falloff) override; void SetVoiceLocation(CVoiceHandle Voice, float x, float y) override; - void SetVoiceTimeOffset(CVoiceHandle Voice, float offset) override; // in s + void SetVoiceTimeOffset(CVoiceHandle Voice, float TimeOffset) override; // in s void SetVoiceCircle(CVoiceHandle Voice, float Radius) override; void SetVoiceRectangle(CVoiceHandle Voice, float Width, float Height) override; diff --git a/src/engine/sound.h b/src/engine/sound.h index 658c6f64f..bf5c0e0d1 100644 --- a/src/engine/sound.h +++ b/src/engine/sound.h @@ -78,7 +78,7 @@ public: virtual void SetVoiceVolume(CVoiceHandle Voice, float Volume) = 0; virtual void SetVoiceFalloff(CVoiceHandle Voice, float Falloff) = 0; virtual void SetVoiceLocation(CVoiceHandle Voice, float x, float y) = 0; - virtual void SetVoiceTimeOffset(CVoiceHandle Voice, float offset) = 0; // in s + virtual void SetVoiceTimeOffset(CVoiceHandle Voice, float TimeOffset) = 0; // in s virtual void SetVoiceCircle(CVoiceHandle Voice, float Radius) = 0; virtual void SetVoiceRectangle(CVoiceHandle Voice, float Width, float Height) = 0; From beaf263f3de93b4ba741077f547ba4d02a86e870 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20M=C3=BCller?= Date: Thu, 21 Sep 2023 23:02:53 +0200 Subject: [PATCH 3/7] Reorder `SetChannel` and `SetListenerPos` functions --- src/engine/client/sound.cpp | 12 ++++++------ src/engine/client/sound.h | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/engine/client/sound.cpp b/src/engine/client/sound.cpp index 6c128b52d..112faf954 100644 --- a/src/engine/client/sound.cpp +++ b/src/engine/client/sound.cpp @@ -737,6 +737,12 @@ float CSound::GetSampleDuration(int SampleID) return (m_aSamples[SampleID].m_NumFrames / m_aSamples[SampleID].m_Rate); } +void CSound::SetChannel(int ChannelID, float Vol, float Pan) +{ + m_aChannels[ChannelID].m_Vol = (int)(Vol * 255.0f); + m_aChannels[ChannelID].m_Pan = (int)(Pan * 255.0f); // TODO: this is only on and off right now +} + void CSound::SetListenerPos(float x, float y) { m_CenterX.store((int)x, std::memory_order_relaxed); @@ -853,12 +859,6 @@ void CSound::SetVoiceRectangle(CVoiceHandle Voice, float Width, float Height) m_aVoices[VoiceID].m_Rectangle.m_Height = maximum(0.0f, Height); } -void CSound::SetChannel(int ChannelID, float Vol, float Pan) -{ - m_aChannels[ChannelID].m_Vol = (int)(Vol * 255.0f); - m_aChannels[ChannelID].m_Pan = (int)(Pan * 255.0f); // TODO: this is only on and off right now -} - ISound::CVoiceHandle CSound::Play(int ChannelID, int SampleID, int Flags, float x, float y) { m_SoundLock.lock(); diff --git a/src/engine/client/sound.h b/src/engine/client/sound.h index 2d527ec7c..f2a5bacef 100644 --- a/src/engine/client/sound.h +++ b/src/engine/client/sound.h @@ -43,8 +43,8 @@ public: float GetSampleDuration(int SampleID) override; // in s - void SetListenerPos(float x, float y) override; void SetChannel(int ChannelID, float Vol, float Pan) override; + void SetListenerPos(float x, float y) override; void SetVoiceVolume(CVoiceHandle Voice, float Volume) override; void SetVoiceFalloff(CVoiceHandle Voice, float Falloff) override; From a2de08a8dbce241e8df5dca9ddd9733405f0eee1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20M=C3=BCller?= Date: Thu, 21 Sep 2023 23:05:10 +0200 Subject: [PATCH 4/7] Add `UpdateVolume` function, remove unnecessary lock usage Using the lock is not necessary, as the volume is already an atomic variable. --- src/engine/client/sound.cpp | 18 ++++++++---------- src/engine/client/sound.h | 2 ++ 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/engine/client/sound.cpp b/src/engine/client/sound.cpp index 112faf954..f949de3ff 100644 --- a/src/engine/client/sound.cpp +++ b/src/engine/client/sound.cpp @@ -327,24 +327,22 @@ int CSound::Init() SDL_PauseAudioDevice(m_Device, 0); m_SoundEnabled = true; - Update(); // update the volume + Update(); return 0; } int CSound::Update() { - // update volume - int WantedVolume = g_Config.m_SndVolume; + UpdateVolume(); + return 0; +} +void CSound::UpdateVolume() +{ + int WantedVolume = g_Config.m_SndVolume; if(!m_pGraphics->WindowActive() && g_Config.m_SndNonactiveMute) WantedVolume = 0; - - if(WantedVolume != m_SoundVolume) - { - std::unique_lock Lock(m_SoundLock); - m_SoundVolume = WantedVolume; - } - return 0; + m_SoundVolume.store(WantedVolume, std::memory_order_relaxed); } void CSound::Shutdown() diff --git a/src/engine/client/sound.h b/src/engine/client/sound.h index f2a5bacef..8e2b523ee 100644 --- a/src/engine/client/sound.h +++ b/src/engine/client/sound.h @@ -27,6 +27,8 @@ class CSound : public IEngineSound static int DecodeWV(int SampleID, const void *pData, unsigned DataSize); static int DecodeOpus(int SampleID, const void *pData, unsigned DataSize); + void UpdateVolume(); + public: CSound(); int Init() override; From 07e18ebecb6168c3788a61768d3d277be2aa1d2b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20M=C3=BCller?= Date: Thu, 21 Sep 2023 20:21:36 +0200 Subject: [PATCH 5/7] Replace most global variables in engine sound with member variables Most variables used in the sound engine were static globals, as they are used in the static sound mixing function. The global variables are replaced by member variables, by passing the sound interface as user-data for the SDL mixing callback. The `Mix` function is made a public member function of `ISound` instead of being exposed using `ISoundMixFunc GetSoundMixFunc()`. This allows to remove the direct dependency of the engine sound on the engine video, by instead passing the sound mixing function as a lambda to the engine video in the engine client. The old WavPack reader function interface does support passing a user-data pointer to the callback function, so global variables are still used here. --- src/engine/client/client.cpp | 4 +- src/engine/client/sound.cpp | 128 +++++++---------------------------- src/engine/client/sound.h | 74 +++++++++++++++++--- src/engine/shared/video.h | 4 +- src/engine/sound.h | 6 +- 5 files changed, 96 insertions(+), 120 deletions(-) diff --git a/src/engine/client/client.cpp b/src/engine/client/client.cpp index 7ff3e9976..81b4cb761 100644 --- a/src/engine/client/client.cpp +++ b/src/engine/client/client.cpp @@ -2637,7 +2637,9 @@ void CClient::Update() if(m_DemoPlayer.IsPlaying() && IVideo::Current()) { IVideo::Current()->NextVideoFrame(); - IVideo::Current()->NextAudioFrameTimeline(Sound()->GetSoundMixFunc()); + IVideo::Current()->NextAudioFrameTimeline([this](short *pFinalOut, unsigned Frames) { + Sound()->Mix(pFinalOut, Frames); + }); } else if(m_ButtonRender) Disconnect(); diff --git a/src/engine/client/sound.cpp b/src/engine/client/sound.cpp index f949de3ff..470eff92d 100644 --- a/src/engine/client/sound.cpp +++ b/src/engine/client/sound.cpp @@ -1,102 +1,27 @@ /* (c) Magnus Auvinen. See licence.txt in the root of the distribution for more information. */ /* If you are missing that file, acquire a complete release at teeworlds.com. */ -#include +#include + #include #include #include -#include - #include -#include - -#include "SDL.h" +#include #include "sound.h" -extern "C" { #if defined(CONF_VIDEORECORDER) #include #endif +extern "C" { #include #include } + #include -enum -{ - NUM_SAMPLES = 512, - NUM_VOICES = 256, - NUM_CHANNELS = 16, -}; - -struct CSample -{ - short *m_pData; - int m_NumFrames; - int m_Rate; - int m_Channels; - int m_LoopStart; - int m_LoopEnd; - int m_PausedAt; -}; - -struct CChannel -{ - int m_Vol; - int m_Pan; -}; - -struct CVoice -{ - CSample *m_pSample; - CChannel *m_pChannel; - int m_Age; // increases when reused - int m_Tick; - int m_Vol; // 0 - 255 - int m_Flags; - int m_X, m_Y; - float m_Falloff; // [0.0, 1.0] - - int m_Shape; - union - { - ISound::CVoiceShapeCircle m_Circle; - ISound::CVoiceShapeRectangle m_Rectangle; - }; -}; - -static CSample m_aSamples[NUM_SAMPLES] = {{0}}; -static CVoice m_aVoices[NUM_VOICES] = {{0}}; -static CChannel m_aChannels[NUM_CHANNELS] = {{255, 0}}; - -static std::mutex m_SoundLock; - -static std::atomic m_CenterX{0}; -static std::atomic m_CenterY{0}; - -static int m_MixingRate = 48000; -static std::atomic m_SoundVolume{100}; - -static int m_NextVoice = 0; -static int *m_pMixBuffer = 0; // buffer only used by the thread callback function -static uint32_t m_MaxFrames = 0; - -static const void *s_pWVBuffer = 0x0; -static int s_WVBufferPosition = 0; -static int s_WVBufferSize = 0; - -const int DefaultDistance = 1500; -int m_LastBreak = 0; - -static int IntAbs(int i) -{ - if(i < 0) - return -i; - return i; -} - -static void Mix(short *pFinalOut, unsigned Frames) +void CSound::Mix(short *pFinalOut, unsigned Frames) { Frames = minimum(Frames, m_MaxFrames); mem_zero(m_pMixBuffer, Frames * 2 * sizeof(int)); @@ -258,65 +183,59 @@ static void Mix(short *pFinalOut, unsigned Frames) #endif } -static void SdlCallback(void *pUnused, Uint8 *pStream, int Len) +static void SdlCallback(void *pUser, Uint8 *pStream, int Len) { - (void)pUnused; + CSound *pSound = static_cast(pUser); + #if defined(CONF_VIDEORECORDER) if(!(IVideo::Current() && g_Config.m_ClVideoSndEnable)) { - Mix((short *)pStream, Len / sizeof(short) / 2); + pSound->Mix((short *)pStream, Len / sizeof(short) / 2); } else { mem_zero(pStream, Len); } #else - Mix((short *)pStream, Len / sizeof(short) / 2); + pSound->Mix((short *)pStream, Len / sizeof(short) / 2); #endif } -CSound::CSound() : - m_SoundEnabled(false), m_Device(0), m_pGraphics(nullptr), m_pStorage(nullptr) -{ -} - int CSound::Init() { m_SoundEnabled = false; m_pGraphics = Kernel()->RequestInterface(); m_pStorage = Kernel()->RequestInterface(); - SDL_AudioSpec Format, FormatOut; - if(!g_Config.m_SndEnable) return 0; if(SDL_InitSubSystem(SDL_INIT_AUDIO) < 0) { - dbg_msg("client/sound", "unable to init SDL audio: %s", SDL_GetError()); + dbg_msg("sound", "unable to init SDL audio: %s", SDL_GetError()); return -1; } m_MixingRate = g_Config.m_SndRate; - // Set 16-bit stereo audio at 22Khz - Format.freq = g_Config.m_SndRate; + SDL_AudioSpec Format, FormatOut; + Format.freq = m_MixingRate; Format.format = AUDIO_S16; Format.channels = 2; Format.samples = g_Config.m_SndBufferSize; Format.callback = SdlCallback; - Format.userdata = NULL; + Format.userdata = this; // Open the audio device and start playing sound! m_Device = SDL_OpenAudioDevice(nullptr, 0, &Format, &FormatOut, 0); if(m_Device == 0) { - dbg_msg("client/sound", "unable to open audio: %s", SDL_GetError()); + dbg_msg("sound", "unable to open audio: %s", SDL_GetError()); return -1; } else - dbg_msg("client/sound", "sound init successful using audio driver '%s'", SDL_GetCurrentAudioDriver()); + dbg_msg("sound", "sound init successful using audio driver '%s'", SDL_GetCurrentAudioDriver()); m_MaxFrames = FormatOut.samples * 2; #if defined(CONF_VIDEORECORDER) @@ -460,6 +379,11 @@ int CSound::DecodeOpus(int SampleID, const void *pData, unsigned DataSize) return SampleID; } +// TODO: Update WavPack to get rid of these global variables +static const void *s_pWVBuffer = nullptr; +static int s_WVBufferPosition = 0; +static int s_WVBufferSize = 0; + static int ReadDataOld(void *pBuffer, int Size) { int ChunkSize = minimum(Size, s_WVBufferSize - s_WVBufferPosition); @@ -508,6 +432,7 @@ int CSound::DecodeWV(int SampleID, const void *pData, unsigned DataSize) CSample *pSample = &m_aSamples[SampleID]; char aError[100]; + dbg_assert(s_pWVBuffer == nullptr, "DecodeWV already in use"); s_pWVBuffer = pData; s_WVBufferSize = DataSize; s_WVBufferPosition = 0; @@ -890,7 +815,7 @@ ISound::CVoiceHandle CSound::Play(int ChannelID, int SampleID, int Flags, float m_aVoices[VoiceID].m_Y = (int)y; m_aVoices[VoiceID].m_Falloff = 0.0f; m_aVoices[VoiceID].m_Shape = ISound::SHAPE_CIRCLE; - m_aVoices[VoiceID].m_Circle.m_Radius = DefaultDistance; + m_aVoices[VoiceID].m_Circle.m_Radius = 1500; Age = m_aVoices[VoiceID].m_Age; } @@ -965,11 +890,6 @@ bool CSound::IsPlaying(int 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() -{ - return Mix; -} - void CSound::PauseAudioDevice() { SDL_PauseAudioDevice(m_Device, 1); diff --git a/src/engine/client/sound.h b/src/engine/client/sound.h index 8e2b523ee..5f7977cef 100644 --- a/src/engine/client/sound.h +++ b/src/engine/client/sound.h @@ -3,25 +3,80 @@ #ifndef ENGINE_CLIENT_SOUND_H #define ENGINE_CLIENT_SOUND_H -#include #include #include -class IEngineGraphics; -class IStorage; +#include +#include + +struct CSample +{ + short *m_pData; + int m_NumFrames; + int m_Rate; + int m_Channels; + int m_LoopStart; + int m_LoopEnd; + int m_PausedAt; +}; + +struct CChannel +{ + int m_Vol; + int m_Pan; +}; + +struct CVoice +{ + CSample *m_pSample; + CChannel *m_pChannel; + int m_Age; // increases when reused + int m_Tick; + int m_Vol; // 0 - 255 + int m_Flags; + int m_X, m_Y; + float m_Falloff; // [0.0, 1.0] + + int m_Shape; + union + { + ISound::CVoiceShapeCircle m_Circle; + ISound::CVoiceShapeRectangle m_Rectangle; + }; +}; class CSound : public IEngineSound { - bool m_SoundEnabled; - SDL_AudioDeviceID m_Device; + enum + { + NUM_SAMPLES = 512, + NUM_VOICES = 256, + NUM_CHANNELS = 16, + }; - IEngineGraphics *m_pGraphics; - IStorage *m_pStorage; + bool m_SoundEnabled = false; + SDL_AudioDeviceID m_Device = 0; + std::mutex m_SoundLock; - int AllocID(); + CSample m_aSamples[NUM_SAMPLES] = {{0}}; + CVoice m_aVoices[NUM_VOICES] = {{0}}; + CChannel m_aChannels[NUM_CHANNELS] = {{255, 0}}; + int m_NextVoice = 0; + uint32_t m_MaxFrames = 0; + + std::atomic m_CenterX = 0; + std::atomic m_CenterY = 0; + std::atomic m_SoundVolume = 100; + int m_MixingRate = 48000; static void RateConvert(int SampleID); + class IEngineGraphics *m_pGraphics = nullptr; + IStorage *m_pStorage = nullptr; + + int *m_pMixBuffer = nullptr; + + int AllocID(); // TODO: Refactor: clean this mess up static int DecodeWV(int SampleID, const void *pData, unsigned DataSize); @@ -30,7 +85,6 @@ class CSound : public IEngineSound void UpdateVolume(); public: - CSound(); int Init() override; int Update() override; void Shutdown() override; @@ -64,7 +118,7 @@ public: void StopVoice(CVoiceHandle Voice) override; bool IsPlaying(int SampleID) override; - ISoundMixFunc GetSoundMixFunc() override; + void Mix(short *pFinalOut, unsigned Frames) override; void PauseAudioDevice() override; void UnpauseAudioDevice() override; }; diff --git a/src/engine/shared/video.h b/src/engine/shared/video.h index ecf802f2b..c93cdd80a 100644 --- a/src/engine/shared/video.h +++ b/src/engine/shared/video.h @@ -3,7 +3,9 @@ #include -typedef void (*ISoundMixFunc)(short *pFinalOut, unsigned Frames); +#include + +typedef std::function ISoundMixFunc; class IVideo { diff --git a/src/engine/sound.h b/src/engine/sound.h index bf5c0e0d1..e55ca026b 100644 --- a/src/engine/sound.h +++ b/src/engine/sound.h @@ -3,9 +3,7 @@ #ifndef ENGINE_SOUND_H #define ENGINE_SOUND_H -#include "kernel.h" - -#include +#include #include class ISound : public IInterface @@ -90,7 +88,7 @@ public: virtual void StopVoice(CVoiceHandle Voice) = 0; virtual bool IsPlaying(int SampleID) = 0; - virtual ISoundMixFunc GetSoundMixFunc() = 0; + virtual void Mix(short *pFinalOut, unsigned Frames) = 0; // useful for thread synchronization virtual void PauseAudioDevice() = 0; virtual void UnpauseAudioDevice() = 0; From 7fe854d8618e89e19121be47a191b622c9fb140d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20M=C3=BCller?= Date: Thu, 21 Sep 2023 23:52:40 +0200 Subject: [PATCH 6/7] Reduce indentation, rename variables in `Mix` function --- src/engine/client/sound.cpp | 239 ++++++++++++++++++------------------ 1 file changed, 118 insertions(+), 121 deletions(-) diff --git a/src/engine/client/sound.cpp b/src/engine/client/sound.cpp index 470eff92d..c07d21a29 100644 --- a/src/engine/client/sound.cpp +++ b/src/engine/client/sound.cpp @@ -29,149 +29,146 @@ void CSound::Mix(short *pFinalOut, unsigned Frames) // acquire lock while we are mixing m_SoundLock.lock(); - int MasterVol = m_SoundVolume; + const int MasterVol = m_SoundVolume.load(std::memory_order_relaxed); for(auto &Voice : m_aVoices) { - if(Voice.m_pSample) + if(!Voice.m_pSample) + continue; + + // mix voice + int *pOut = m_pMixBuffer; + + const int Step = Voice.m_pSample->m_Channels; // setup input sources + short *pInL = &Voice.m_pSample->m_pData[Voice.m_Tick * Step]; + short *pInR = &Voice.m_pSample->m_pData[Voice.m_Tick * Step + 1]; + + unsigned End = Voice.m_pSample->m_NumFrames - Voice.m_Tick; + + int VolumeR = round_truncate(Voice.m_pChannel->m_Vol * (Voice.m_Vol / 255.0f)); + int VolumeL = VolumeR; + + // make sure that we don't go outside the sound data + if(Frames < End) + End = Frames; + + // check if we have a mono sound + if(Voice.m_pSample->m_Channels == 1) + pInR = pInL; + + // volume calculation + if(Voice.m_Flags & ISound::FLAG_POS && Voice.m_pChannel->m_Pan) { - // mix voice - int *pOut = m_pMixBuffer; + // TODO: we should respect the channel panning value + const int dx = Voice.m_X - m_CenterX.load(std::memory_order_relaxed); + const int dy = Voice.m_Y - m_CenterY.load(std::memory_order_relaxed); + float FalloffX = 0.0f; + float FalloffY = 0.0f; - int Step = Voice.m_pSample->m_Channels; // setup input sources - short *pInL = &Voice.m_pSample->m_pData[Voice.m_Tick * Step]; - short *pInR = &Voice.m_pSample->m_pData[Voice.m_Tick * Step + 1]; + int RangeX = 0; // for panning + bool InVoiceField = false; - unsigned End = Voice.m_pSample->m_NumFrames - Voice.m_Tick; - - int Rvol = (int)(Voice.m_pChannel->m_Vol * (Voice.m_Vol / 255.0f)); - int Lvol = (int)(Voice.m_pChannel->m_Vol * (Voice.m_Vol / 255.0f)); - - // make sure that we don't go outside the sound data - if(Frames < End) - End = Frames; - - // check if we have a mono sound - if(Voice.m_pSample->m_Channels == 1) - pInR = pInL; - - // volume calculation - if(Voice.m_Flags & ISound::FLAG_POS && Voice.m_pChannel->m_Pan) + switch(Voice.m_Shape) { - // TODO: we should respect the channel panning value - int dx = Voice.m_X - m_CenterX.load(std::memory_order_relaxed); - int dy = Voice.m_Y - m_CenterY.load(std::memory_order_relaxed); - // - int p = IntAbs(dx); - float FalloffX = 0.0f; - float FalloffY = 0.0f; + case ISound::SHAPE_CIRCLE: + { + const float Radius = Voice.m_Circle.m_Radius; + RangeX = Radius; - int RangeX = 0; // for panning - bool InVoiceField = false; - - switch(Voice.m_Shape) + // dx and dy can be larger than 46341 and thus the calculation would go beyond the limits of a integer, + // therefore we cast them into float + const int Dist = (int)length(vec2(dx, dy)); + if(Dist < Radius) { - case ISound::SHAPE_CIRCLE: - { - float r = Voice.m_Circle.m_Radius; - RangeX = r; + InVoiceField = true; - // dx and dy can be larger than 46341 and thus the calculation would go beyond the limits of a integer, - // therefore we cast them into float - int Dist = (int)length(vec2(dx, dy)); - if(Dist < r) - { - InVoiceField = true; - - // falloff - int FalloffDistance = r * Voice.m_Falloff; - if(Dist > FalloffDistance) - FalloffX = FalloffY = (r - Dist) / (r - FalloffDistance); - else - FalloffX = FalloffY = 1.0f; - } + // falloff + int FalloffDistance = Radius * Voice.m_Falloff; + if(Dist > FalloffDistance) + FalloffX = FalloffY = (Radius - Dist) / (Radius - FalloffDistance); else - InVoiceField = false; - - break; - } - - case ISound::SHAPE_RECTANGLE: - { - RangeX = Voice.m_Rectangle.m_Width / 2.0f; - - int abs_dx = absolute(dx); - int abs_dy = absolute(dy); - - int w = Voice.m_Rectangle.m_Width / 2.0f; - int h = Voice.m_Rectangle.m_Height / 2.0f; - - if(abs_dx < w && abs_dy < h) - { - InVoiceField = true; - - // falloff - int fx = Voice.m_Falloff * w; - int fy = Voice.m_Falloff * h; - - FalloffX = abs_dx > fx ? (float)(w - abs_dx) / (w - fx) : 1.0f; - FalloffY = abs_dy > fy ? (float)(h - abs_dy) / (h - fy) : 1.0f; - } - else - InVoiceField = false; - - break; - } - }; - - if(InVoiceField) - { - // panning - if(!(Voice.m_Flags & ISound::FLAG_NO_PANNING)) - { - if(dx > 0) - Lvol = ((RangeX - p) * Lvol) / RangeX; - else - Rvol = ((RangeX - p) * Rvol) / RangeX; - } - - { - Lvol *= FalloffX * FalloffY; - Rvol *= FalloffX * FalloffY; - } + FalloffX = FalloffY = 1.0f; } else + InVoiceField = false; + + break; + } + + case ISound::SHAPE_RECTANGLE: + { + RangeX = Voice.m_Rectangle.m_Width / 2.0f; + + const int abs_dx = absolute(dx); + const int abs_dy = absolute(dy); + + const int w = Voice.m_Rectangle.m_Width / 2.0f; + const int h = Voice.m_Rectangle.m_Height / 2.0f; + + if(abs_dx < w && abs_dy < h) { - Lvol = 0; - Rvol = 0; + InVoiceField = true; + + // falloff + int fx = Voice.m_Falloff * w; + int fy = Voice.m_Falloff * h; + + FalloffX = abs_dx > fx ? (float)(w - abs_dx) / (w - fx) : 1.0f; + FalloffY = abs_dy > fy ? (float)(h - abs_dy) / (h - fy) : 1.0f; } - } - - // process all frames - for(unsigned s = 0; s < End; s++) - { - *pOut++ += (*pInL) * Lvol; - *pOut++ += (*pInR) * Rvol; - pInL += Step; - pInR += Step; - Voice.m_Tick++; - } - - // free voice if not used any more - if(Voice.m_Tick == Voice.m_pSample->m_NumFrames) - { - if(Voice.m_Flags & ISound::FLAG_LOOP) - Voice.m_Tick = 0; else + InVoiceField = false; + + break; + } + }; + + if(InVoiceField) + { + // panning + if(!(Voice.m_Flags & ISound::FLAG_NO_PANNING)) { - Voice.m_pSample = 0; - Voice.m_Age++; + if(dx > 0) + VolumeL = ((RangeX - absolute(dx)) * VolumeL) / RangeX; + else + VolumeR = ((RangeX - absolute(dx)) * VolumeR) / RangeX; } + + { + VolumeL *= FalloffX * FalloffY; + VolumeR *= FalloffX * FalloffY; + } + } + else + { + VolumeL = 0; + VolumeR = 0; + } + } + + // process all frames + for(unsigned s = 0; s < End; s++) + { + *pOut++ += (*pInL) * VolumeL; + *pOut++ += (*pInR) * VolumeR; + pInL += Step; + pInR += Step; + Voice.m_Tick++; + } + + // free voice if not used any more + if(Voice.m_Tick == Voice.m_pSample->m_NumFrames) + { + if(Voice.m_Flags & ISound::FLAG_LOOP) + Voice.m_Tick = 0; + else + { + Voice.m_pSample = nullptr; + Voice.m_Age++; } } } - // release the lock m_SoundLock.unlock(); // clamp accumulated values From b9951dd411803ea4bb8e6ece9d7b6db146c8d81a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20M=C3=BCller?= Date: Thu, 21 Sep 2023 23:18:17 +0200 Subject: [PATCH 7/7] Refactor and reorder `RateConvert`, `DecodeOpus`, `DecodeWV` Pass sample by reference instead of by index, as the functions are only used internally and a valid sample is assured. --- src/engine/client/sound.cpp | 157 +++++++++++++++++------------------- src/engine/client/sound.h | 11 ++- src/engine/sound.h | 4 +- 3 files changed, 82 insertions(+), 90 deletions(-) diff --git a/src/engine/client/sound.cpp b/src/engine/client/sound.cpp index c07d21a29..220768573 100644 --- a/src/engine/client/sound.cpp +++ b/src/engine/client/sound.cpp @@ -286,94 +286,87 @@ int CSound::AllocID() return -1; } -void CSound::RateConvert(int SampleID) +void CSound::RateConvert(CSample &Sample) { - CSample *pSample = &m_aSamples[SampleID]; - // make sure that we need to convert this sound - if(!pSample->m_pData || pSample->m_Rate == m_MixingRate) + if(!Sample.m_pData || Sample.m_Rate == m_MixingRate) return; // allocate new data - int NumFrames = (int)((pSample->m_NumFrames / (float)pSample->m_Rate) * m_MixingRate); - short *pNewData = (short *)calloc((size_t)NumFrames * pSample->m_Channels, sizeof(short)); + const int NumFrames = (int)((Sample.m_NumFrames / (float)Sample.m_Rate) * m_MixingRate); + short *pNewData = (short *)calloc((size_t)NumFrames * Sample.m_Channels, sizeof(short)); for(int i = 0; i < NumFrames; i++) { // resample TODO: this should be done better, like linear at least float a = i / (float)NumFrames; - int f = (int)(a * pSample->m_NumFrames); - if(f >= pSample->m_NumFrames) - f = pSample->m_NumFrames - 1; + int f = (int)(a * Sample.m_NumFrames); + if(f >= Sample.m_NumFrames) + f = Sample.m_NumFrames - 1; // set new data - if(pSample->m_Channels == 1) - pNewData[i] = pSample->m_pData[f]; - else if(pSample->m_Channels == 2) + if(Sample.m_Channels == 1) + pNewData[i] = Sample.m_pData[f]; + else if(Sample.m_Channels == 2) { - pNewData[i * 2] = pSample->m_pData[f * 2]; - pNewData[i * 2 + 1] = pSample->m_pData[f * 2 + 1]; + pNewData[i * 2] = Sample.m_pData[f * 2]; + pNewData[i * 2 + 1] = Sample.m_pData[f * 2 + 1]; } } // free old data and apply new - free(pSample->m_pData); - pSample->m_pData = pNewData; - pSample->m_NumFrames = NumFrames; - pSample->m_Rate = m_MixingRate; + free(Sample.m_pData); + Sample.m_pData = pNewData; + Sample.m_NumFrames = NumFrames; + Sample.m_Rate = m_MixingRate; } -int CSound::DecodeOpus(int SampleID, const void *pData, unsigned DataSize) +bool CSound::DecodeOpus(CSample &Sample, const void *pData, unsigned DataSize) { - if(SampleID == -1 || SampleID >= NUM_SAMPLES) - return -1; - - CSample *pSample = &m_aSamples[SampleID]; - - OggOpusFile *pOpusFile = op_open_memory((const unsigned char *)pData, DataSize, NULL); + OggOpusFile *pOpusFile = op_open_memory((const unsigned char *)pData, DataSize, nullptr); if(pOpusFile) { - int NumChannels = op_channel_count(pOpusFile, -1); - int NumSamples = op_pcm_total(pOpusFile, -1); // per channel! + const int NumChannels = op_channel_count(pOpusFile, -1); + const int NumSamples = op_pcm_total(pOpusFile, -1); // per channel! - pSample->m_Channels = NumChannels; + Sample.m_Channels = NumChannels; - if(pSample->m_Channels > 2) + if(Sample.m_Channels > 2) { dbg_msg("sound/opus", "file is not mono or stereo."); - return -1; + return false; } - pSample->m_pData = (short *)calloc((size_t)NumSamples * NumChannels, sizeof(short)); + Sample.m_pData = (short *)calloc((size_t)NumSamples * NumChannels, sizeof(short)); int Pos = 0; while(Pos < NumSamples) { - const int Read = op_read(pOpusFile, pSample->m_pData + Pos * NumChannels, NumSamples * NumChannels, NULL); + const int Read = op_read(pOpusFile, Sample.m_pData + Pos * NumChannels, NumSamples * NumChannels, nullptr); if(Read < 0) { - free(pSample->m_pData); + free(Sample.m_pData); dbg_msg("sound/opus", "op_read error %d at %d", Read, Pos); - return -1; + return false; } else if(Read == 0) // EOF break; Pos += Read; } - pSample->m_NumFrames = Pos; - pSample->m_Rate = 48000; - pSample->m_LoopStart = -1; - pSample->m_LoopEnd = -1; - pSample->m_PausedAt = 0; + Sample.m_NumFrames = Pos; + Sample.m_Rate = 48000; + Sample.m_LoopStart = -1; + Sample.m_LoopEnd = -1; + Sample.m_PausedAt = 0; } else { dbg_msg("sound/opus", "failed to decode sample"); - return -1; + return false; } - return SampleID; + return true; } // TODO: Update WavPack to get rid of these global variables @@ -421,12 +414,8 @@ static int PushBackByte(void *pId, int Char) } #endif -int CSound::DecodeWV(int SampleID, const void *pData, unsigned DataSize) +bool CSound::DecodeWV(CSample &Sample, const void *pData, unsigned DataSize) { - if(SampleID == -1 || SampleID >= NUM_SAMPLES) - return -1; - - CSample *pSample = &m_aSamples[SampleID]; char aError[100]; dbg_assert(s_pWVBuffer == nullptr, "DecodeWV already in use"); @@ -447,24 +436,26 @@ int CSound::DecodeWV(int SampleID, const void *pData, unsigned DataSize) #endif if(pContext) { - int NumSamples = WavpackGetNumSamples(pContext); - int BitsPerSample = WavpackGetBitsPerSample(pContext); - unsigned int SampleRate = WavpackGetSampleRate(pContext); - int NumChannels = WavpackGetNumChannels(pContext); + const int NumSamples = WavpackGetNumSamples(pContext); + const int BitsPerSample = WavpackGetBitsPerSample(pContext); + const unsigned int SampleRate = WavpackGetSampleRate(pContext); + const int NumChannels = WavpackGetNumChannels(pContext); - pSample->m_Channels = NumChannels; - pSample->m_Rate = SampleRate; + Sample.m_Channels = NumChannels; + Sample.m_Rate = SampleRate; - if(pSample->m_Channels > 2) + if(Sample.m_Channels > 2) { dbg_msg("sound/wv", "file is not mono or stereo."); - return -1; + s_pWVBuffer = nullptr; + return false; } if(BitsPerSample != 16) { dbg_msg("sound/wv", "bps is %d, not 16", BitsPerSample); - return -1; + s_pWVBuffer = nullptr; + return false; } int *pBuffer = (int *)calloc((size_t)NumSamples * NumChannels, sizeof(int)); @@ -472,13 +463,14 @@ int CSound::DecodeWV(int SampleID, const void *pData, unsigned DataSize) { free(pBuffer); dbg_msg("sound/wv", "WavpackUnpackSamples failed. NumSamples=%d, NumChannels=%d", NumSamples, NumChannels); - return -1; + s_pWVBuffer = nullptr; + return false; } + + Sample.m_pData = (short *)calloc((size_t)NumSamples * NumChannels, sizeof(short)); + int *pSrc = pBuffer; - - pSample->m_pData = (short *)calloc((size_t)NumSamples * NumChannels, sizeof(short)); - short *pDst = pSample->m_pData; - + short *pDst = Sample.m_pData; for(int i = 0; i < NumSamples * NumChannels; i++) *pDst++ = (short)*pSrc++; @@ -487,18 +479,21 @@ int CSound::DecodeWV(int SampleID, const void *pData, unsigned DataSize) WavpackCloseFile(pContext); #endif - pSample->m_NumFrames = NumSamples; - pSample->m_LoopStart = -1; - pSample->m_LoopEnd = -1; - pSample->m_PausedAt = 0; + Sample.m_NumFrames = NumSamples; + Sample.m_LoopStart = -1; + Sample.m_LoopEnd = -1; + Sample.m_PausedAt = 0; + + s_pWVBuffer = nullptr; } else { dbg_msg("sound/wv", "failed to decode sample (%s)", aError); - return -1; + s_pWVBuffer = nullptr; + return false; } - return SampleID; + return true; } int CSound::LoadOpus(const char *pFilename, int StorageType) @@ -516,7 +511,7 @@ int CSound::LoadOpus(const char *pFilename, int StorageType) if(!m_pStorage) return -1; - int SampleID = AllocID(); + const int SampleID = AllocID(); if(SampleID < 0) { dbg_msg("sound/opus", "failed to allocate sample ID. filename='%s'", pFilename); @@ -531,15 +526,15 @@ int CSound::LoadOpus(const char *pFilename, int StorageType) return -1; } - SampleID = DecodeOpus(SampleID, pData, DataSize); + const bool DecodeSuccess = DecodeOpus(m_aSamples[SampleID], pData, DataSize); free(pData); - if(SampleID < 0) + if(!DecodeSuccess) return -1; if(g_Config.m_Debug) dbg_msg("sound/opus", "loaded %s", pFilename); - RateConvert(SampleID); + RateConvert(m_aSamples[SampleID]); return SampleID; } @@ -558,7 +553,7 @@ int CSound::LoadWV(const char *pFilename, int StorageType) if(!m_pStorage) return -1; - int SampleID = AllocID(); + const int SampleID = AllocID(); if(SampleID < 0) { dbg_msg("sound/wv", "failed to allocate sample ID. filename='%s'", pFilename); @@ -573,15 +568,15 @@ int CSound::LoadWV(const char *pFilename, int StorageType) return -1; } - SampleID = DecodeWV(SampleID, pData, DataSize); + const bool DecodeSuccess = DecodeWV(m_aSamples[SampleID], pData, DataSize); free(pData); - if(SampleID < 0) + if(!DecodeSuccess) return -1; if(g_Config.m_Debug) dbg_msg("sound/wv", "loaded %s", pFilename); - RateConvert(SampleID); + RateConvert(m_aSamples[SampleID]); return SampleID; } @@ -600,15 +595,14 @@ int CSound::LoadOpusFromMem(const void *pData, unsigned DataSize, bool FromEdito if(!pData) return -1; - int SampleID = AllocID(); + const int SampleID = AllocID(); if(SampleID < 0) return -1; - SampleID = DecodeOpus(SampleID, pData, DataSize); - if(SampleID < 0) + if(!DecodeOpus(m_aSamples[SampleID], pData, DataSize)) return -1; - RateConvert(SampleID); + RateConvert(m_aSamples[SampleID]); return SampleID; } @@ -627,15 +621,14 @@ int CSound::LoadWVFromMem(const void *pData, unsigned DataSize, bool FromEditor if(!pData) return -1; - int SampleID = AllocID(); + const int SampleID = AllocID(); if(SampleID < 0) return -1; - SampleID = DecodeWV(SampleID, pData, DataSize); - if(SampleID < 0) + if(!DecodeWV(m_aSamples[SampleID], pData, DataSize)) return -1; - RateConvert(SampleID); + RateConvert(m_aSamples[SampleID]); return SampleID; } diff --git a/src/engine/client/sound.h b/src/engine/client/sound.h index 5f7977cef..03ce2c925 100644 --- a/src/engine/client/sound.h +++ b/src/engine/client/sound.h @@ -70,17 +70,16 @@ class CSound : public IEngineSound std::atomic m_SoundVolume = 100; int m_MixingRate = 48000; - static void RateConvert(int SampleID); class IEngineGraphics *m_pGraphics = nullptr; IStorage *m_pStorage = nullptr; int *m_pMixBuffer = nullptr; int AllocID(); + void RateConvert(CSample &Sample); - // TODO: Refactor: clean this mess up - static int DecodeWV(int SampleID, const void *pData, unsigned DataSize); - static int DecodeOpus(int SampleID, const void *pData, unsigned DataSize); + bool DecodeOpus(CSample &Sample, const void *pData, unsigned DataSize); + bool DecodeWV(CSample &Sample, const void *pData, unsigned DataSize); void UpdateVolume(); @@ -91,10 +90,10 @@ public: bool IsSoundEnabled() override { return m_SoundEnabled; } - int LoadWV(const char *pFilename, int StorageType = IStorage::TYPE_ALL) override; - int LoadWVFromMem(const void *pData, unsigned DataSize, bool FromEditor) override; int LoadOpus(const char *pFilename, int StorageType = IStorage::TYPE_ALL) override; + int LoadWV(const char *pFilename, int StorageType = IStorage::TYPE_ALL) override; int LoadOpusFromMem(const void *pData, unsigned DataSize, bool FromEditor) override; + int LoadWVFromMem(const void *pData, unsigned DataSize, bool FromEditor) override; void UnloadSample(int SampleID) override; float GetSampleDuration(int SampleID) override; // in s diff --git a/src/engine/sound.h b/src/engine/sound.h index e55ca026b..17afbdea0 100644 --- a/src/engine/sound.h +++ b/src/engine/sound.h @@ -62,10 +62,10 @@ public: virtual bool IsSoundEnabled() = 0; - virtual int LoadWV(const char *pFilename, int StorageType = IStorage::TYPE_ALL) = 0; virtual int LoadOpus(const char *pFilename, int StorageType = IStorage::TYPE_ALL) = 0; - virtual int LoadWVFromMem(const void *pData, unsigned DataSize, bool FromEditor = false) = 0; + virtual int LoadWV(const char *pFilename, int StorageType = IStorage::TYPE_ALL) = 0; virtual int LoadOpusFromMem(const void *pData, unsigned DataSize, bool FromEditor = false) = 0; + virtual int LoadWVFromMem(const void *pData, unsigned DataSize, bool FromEditor = false) = 0; virtual void UnloadSample(int SampleID) = 0; virtual float GetSampleDuration(int SampleID) = 0; // in s