diff --git a/src/engine/client/client.cpp b/src/engine/client/client.cpp index 7ff3e9976b9..81b4cb76128 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 b1ce3c0e105..22076857337 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; -} +#include -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)); @@ -104,149 +29,146 @@ static void 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) - { - // mix voice - int *pOut = m_pMixBuffer; + if(!Voice.m_pSample) + continue; - 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]; + // mix voice + int *pOut = m_pMixBuffer; - unsigned End = Voice.m_pSample->m_NumFrames - Voice.m_Tick; + 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]; - 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)); + unsigned End = Voice.m_pSample->m_NumFrames - Voice.m_Tick; - // make sure that we don't go outside the sound data - if(Frames < End) - End = Frames; + int VolumeR = round_truncate(Voice.m_pChannel->m_Vol * (Voice.m_Vol / 255.0f)); + int VolumeL = VolumeR; - // check if we have a mono sound - if(Voice.m_pSample->m_Channels == 1) - pInR = pInL; + // make sure that we don't go outside the sound data + if(Frames < End) + End = Frames; - // volume calculation - if(Voice.m_Flags & ISound::FLAG_POS && Voice.m_pChannel->m_Pan) + // 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) + { + // 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 RangeX = 0; // for panning + bool InVoiceField = false; + + 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; - - int RangeX = 0; // for panning - bool InVoiceField = false; - - switch(Voice.m_Shape) - { - case ISound::SHAPE_CIRCLE: + case ISound::SHAPE_CIRCLE: + { + const float Radius = Voice.m_Circle.m_Radius; + RangeX = Radius; + + // 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) { - float r = Voice.m_Circle.m_Radius; - RangeX = r; - - // 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; - } - else - InVoiceField = false; + InVoiceField = true; - break; + // falloff + int FalloffDistance = Radius * Voice.m_Falloff; + if(Dist > FalloffDistance) + FalloffX = FalloffY = (Radius - Dist) / (Radius - FalloffDistance); + else + FalloffX = FalloffY = 1.0f; } + else + InVoiceField = false; - case ISound::SHAPE_RECTANGLE: - { - RangeX = Voice.m_Rectangle.m_Width / 2.0f; + break; + } - int abs_dx = absolute(dx); - int abs_dy = absolute(dy); + case ISound::SHAPE_RECTANGLE: + { + RangeX = Voice.m_Rectangle.m_Width / 2.0f; - int w = Voice.m_Rectangle.m_Width / 2.0f; - int h = Voice.m_Rectangle.m_Height / 2.0f; + const int abs_dx = absolute(dx); + const int abs_dy = absolute(dy); - if(abs_dx < w && abs_dy < h) - { - InVoiceField = true; + const int w = Voice.m_Rectangle.m_Width / 2.0f; + const int h = Voice.m_Rectangle.m_Height / 2.0f; - // falloff - int fx = Voice.m_Falloff * w; - int fy = Voice.m_Falloff * h; + if(abs_dx < w && abs_dy < h) + { + InVoiceField = true; - 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; + // falloff + int fx = Voice.m_Falloff * w; + int fy = Voice.m_Falloff * h; - break; + 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; - if(InVoiceField) + break; + } + }; + + if(InVoiceField) + { + // panning + if(!(Voice.m_Flags & ISound::FLAG_NO_PANNING)) { - // 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; - } + if(dx > 0) + VolumeL = ((RangeX - absolute(dx)) * VolumeL) / RangeX; + else + VolumeR = ((RangeX - absolute(dx)) * VolumeR) / RangeX; } - else + { - Lvol = 0; - Rvol = 0; + VolumeL *= FalloffX * FalloffY; + VolumeR *= FalloffX * FalloffY; } } - - // process all frames - for(unsigned s = 0; s < End; s++) + else { - *pOut++ += (*pInL) * Lvol; - *pOut++ += (*pInR) * Rvol; - pInL += Step; - pInR += Step; - Voice.m_Tick++; + VolumeL = 0; + VolumeR = 0; } + } - // free voice if not used any more - if(Voice.m_Tick == Voice.m_pSample->m_NumFrames) + // 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 { - if(Voice.m_Flags & ISound::FLAG_LOOP) - Voice.m_Tick = 0; - else - { - Voice.m_pSample = 0; - Voice.m_Age++; - } + Voice.m_pSample = nullptr; + Voice.m_Age++; } } } - // release the lock m_SoundLock.unlock(); // clamp accumulated values @@ -258,65 +180,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(NULL, 0, &Format, &FormatOut, 0); + 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) @@ -327,24 +243,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() @@ -357,7 +271,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,103 +279,101 @@ 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; } 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 +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); @@ -502,14 +414,11 @@ 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"); s_pWVBuffer = pData; s_WVBufferSize = DataSize; s_WVBufferPosition = 0; @@ -527,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)); @@ -552,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; } - int *pSrc = pBuffer; - pSample->m_pData = (short *)calloc((size_t)NumSamples * NumChannels, sizeof(short)); - short *pDst = pSample->m_pData; + Sample.m_pData = (short *)calloc((size_t)NumSamples * NumChannels, sizeof(short)); + int *pSrc = pBuffer; + short *pDst = Sample.m_pData; for(int i = 0; i < NumSamples * NumChannels; i++) *pDst++ = (short)*pSrc++; @@ -567,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) @@ -596,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); @@ -611,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; } @@ -638,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); @@ -653,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; } @@ -680,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; } @@ -707,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; } @@ -726,8 +639,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) @@ -738,6 +650,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); @@ -789,7 +707,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; @@ -800,27 +718,25 @@ void CSound::SetVoiceTimeOffset(CVoiceHandle Voice, float offset) if(m_aVoices[VoiceID].m_Age != Voice.Age()) return; + if(!m_aVoices[VoiceID].m_pSample) + return; + + 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) { - if(m_aVoices[VoiceID].m_pSample) + // 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)) { - 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); - - // 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; - } - } + m_aVoices[VoiceID].m_Tick = Tick; } } } @@ -856,12 +772,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(); @@ -895,7 +805,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; } @@ -926,7 +836,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 +854,7 @@ void CSound::StopAll() else Voice.m_pSample->m_PausedAt = 0; } - Voice.m_pSample = 0; + Voice.m_pSample = nullptr; } } @@ -959,7 +869,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++; } @@ -970,11 +880,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 207d52df54b..03ce2c92577 100644 --- a/src/engine/client/sound.h +++ b/src/engine/client/sound.h @@ -3,53 +3,108 @@ #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, + }; + + bool m_SoundEnabled = false; + SDL_AudioDeviceID m_Device = 0; + std::mutex m_SoundLock; - IEngineGraphics *m_pGraphics; - IStorage *m_pStorage; + 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; + + class IEngineGraphics *m_pGraphics = nullptr; + IStorage *m_pStorage = nullptr; + + int *m_pMixBuffer = nullptr; int AllocID(); + void RateConvert(CSample &Sample); - static void RateConvert(int SampleID); + bool DecodeOpus(CSample &Sample, const void *pData, unsigned DataSize); + bool DecodeWV(CSample &Sample, const void *pData, unsigned DataSize); - // 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); + void UpdateVolume(); public: - CSound(); int Init() override; int Update() override; void Shutdown() override; 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 - 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; 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; @@ -62,7 +117,7 @@ class CSound : public IEngineSound 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 ecf802f2b15..c93cdd80a97 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 658c6f64fad..17afbdea0ea 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 @@ -64,10 +62,10 @@ class ISound : public IInterface 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 @@ -78,7 +76,7 @@ class ISound : public IInterface 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; @@ -90,7 +88,7 @@ class ISound : public IInterface 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;