mirror of
https://github.com/ddnet/ddnet.git
synced 2024-11-10 10:08:18 +00:00
Merge pull request #7231 from Robyt3/Engine-Sound-Refactoring
Replace most global variables in engine sound with member variables, various other refactoring of engine sound
This commit is contained in:
commit
8009e8654d
|
@ -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();
|
||||
|
|
|
@ -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 <atomic>
|
||||
#include <SDL.h>
|
||||
|
||||
#include <base/math.h>
|
||||
#include <base/system.h>
|
||||
|
||||
#include <engine/graphics.h>
|
||||
#include <engine/storage.h>
|
||||
|
||||
#include <engine/shared/config.h>
|
||||
#include <mutex>
|
||||
|
||||
#include "SDL.h"
|
||||
#include <engine/storage.h>
|
||||
|
||||
#include "sound.h"
|
||||
|
||||
extern "C" {
|
||||
#if defined(CONF_VIDEORECORDER)
|
||||
#include <engine/shared/video.h>
|
||||
#endif
|
||||
extern "C" {
|
||||
#include <opusfile.h>
|
||||
#include <wavpack.h>
|
||||
}
|
||||
|
||||
#include <cmath>
|
||||
|
||||
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<int> m_CenterX{0};
|
||||
static std::atomic<int> m_CenterY{0};
|
||||
|
||||
static int m_MixingRate = 48000;
|
||||
static std::atomic<int> 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));
|
||||
|
@ -104,23 +29,24 @@ 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)
|
||||
{
|
||||
if(!Voice.m_pSample)
|
||||
continue;
|
||||
|
||||
// mix voice
|
||||
int *pOut = m_pMixBuffer;
|
||||
|
||||
int Step = Voice.m_pSample->m_Channels; // setup input sources
|
||||
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 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));
|
||||
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)
|
||||
|
@ -134,10 +60,8 @@ static void Mix(short *pFinalOut, unsigned Frames)
|
|||
if(Voice.m_Flags & ISound::FLAG_POS && Voice.m_pChannel->m_Pan)
|
||||
{
|
||||
// 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);
|
||||
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;
|
||||
|
||||
|
@ -148,20 +72,20 @@ static void Mix(short *pFinalOut, unsigned Frames)
|
|||
{
|
||||
case ISound::SHAPE_CIRCLE:
|
||||
{
|
||||
float r = Voice.m_Circle.m_Radius;
|
||||
RangeX = r;
|
||||
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
|
||||
int Dist = (int)length(vec2(dx, dy));
|
||||
if(Dist < r)
|
||||
const int Dist = (int)length(vec2(dx, dy));
|
||||
if(Dist < Radius)
|
||||
{
|
||||
InVoiceField = true;
|
||||
|
||||
// falloff
|
||||
int FalloffDistance = r * Voice.m_Falloff;
|
||||
int FalloffDistance = Radius * Voice.m_Falloff;
|
||||
if(Dist > FalloffDistance)
|
||||
FalloffX = FalloffY = (r - Dist) / (r - FalloffDistance);
|
||||
FalloffX = FalloffY = (Radius - Dist) / (Radius - FalloffDistance);
|
||||
else
|
||||
FalloffX = FalloffY = 1.0f;
|
||||
}
|
||||
|
@ -175,11 +99,11 @@ static void Mix(short *pFinalOut, unsigned Frames)
|
|||
{
|
||||
RangeX = Voice.m_Rectangle.m_Width / 2.0f;
|
||||
|
||||
int abs_dx = absolute(dx);
|
||||
int abs_dy = absolute(dy);
|
||||
const int abs_dx = absolute(dx);
|
||||
const int abs_dy = absolute(dy);
|
||||
|
||||
int w = Voice.m_Rectangle.m_Width / 2.0f;
|
||||
int h = Voice.m_Rectangle.m_Height / 2.0f;
|
||||
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)
|
||||
{
|
||||
|
@ -205,28 +129,28 @@ static void Mix(short *pFinalOut, unsigned Frames)
|
|||
if(!(Voice.m_Flags & ISound::FLAG_NO_PANNING))
|
||||
{
|
||||
if(dx > 0)
|
||||
Lvol = ((RangeX - p) * Lvol) / RangeX;
|
||||
VolumeL = ((RangeX - absolute(dx)) * VolumeL) / RangeX;
|
||||
else
|
||||
Rvol = ((RangeX - p) * Rvol) / RangeX;
|
||||
VolumeR = ((RangeX - absolute(dx)) * VolumeR) / RangeX;
|
||||
}
|
||||
|
||||
{
|
||||
Lvol *= FalloffX * FalloffY;
|
||||
Rvol *= FalloffX * FalloffY;
|
||||
VolumeL *= FalloffX * FalloffY;
|
||||
VolumeR *= FalloffX * FalloffY;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Lvol = 0;
|
||||
Rvol = 0;
|
||||
VolumeL = 0;
|
||||
VolumeR = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// process all frames
|
||||
for(unsigned s = 0; s < End; s++)
|
||||
{
|
||||
*pOut++ += (*pInL) * Lvol;
|
||||
*pOut++ += (*pInR) * Rvol;
|
||||
*pOut++ += (*pInL) * VolumeL;
|
||||
*pOut++ += (*pInR) * VolumeR;
|
||||
pInL += Step;
|
||||
pInR += Step;
|
||||
Voice.m_Tick++;
|
||||
|
@ -239,14 +163,12 @@ static void Mix(short *pFinalOut, unsigned Frames)
|
|||
Voice.m_Tick = 0;
|
||||
else
|
||||
{
|
||||
Voice.m_pSample = 0;
|
||||
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<CSound *>(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<IEngineGraphics>();
|
||||
m_pStorage = Kernel()->RequestInterface<IStorage>();
|
||||
|
||||
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<std::mutex> 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;
|
||||
}
|
||||
|
||||
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++;
|
||||
|
||||
|
@ -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,12 +718,12 @@ void CSound::SetVoiceTimeOffset(CVoiceHandle Voice, float offset)
|
|||
if(m_aVoices[VoiceID].m_Age != Voice.Age())
|
||||
return;
|
||||
|
||||
{
|
||||
if(m_aVoices[VoiceID].m_pSample)
|
||||
{
|
||||
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 * offset;
|
||||
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
|
||||
|
@ -822,8 +740,6 @@ void CSound::SetVoiceTimeOffset(CVoiceHandle Voice, float offset)
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CSound::SetVoiceCircle(CVoiceHandle Voice, float Radius)
|
||||
{
|
||||
|
@ -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);
|
||||
|
|
|
@ -3,53 +3,108 @@
|
|||
#ifndef ENGINE_CLIENT_SOUND_H
|
||||
#define ENGINE_CLIENT_SOUND_H
|
||||
|
||||
#include <engine/shared/video.h>
|
||||
#include <engine/sound.h>
|
||||
|
||||
#include <SDL_audio.h>
|
||||
|
||||
class IEngineGraphics;
|
||||
class IStorage;
|
||||
#include <atomic>
|
||||
#include <mutex>
|
||||
|
||||
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;
|
||||
|
||||
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<int> m_CenterX = 0;
|
||||
std::atomic<int> m_CenterY = 0;
|
||||
std::atomic<int> 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 @@ 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;
|
||||
};
|
||||
|
|
|
@ -3,7 +3,9 @@
|
|||
|
||||
#include <base/system.h>
|
||||
|
||||
typedef void (*ISoundMixFunc)(short *pFinalOut, unsigned Frames);
|
||||
#include <functional>
|
||||
|
||||
typedef std::function<void(short *pFinalOut, unsigned Frames)> ISoundMixFunc;
|
||||
|
||||
class IVideo
|
||||
{
|
||||
|
|
|
@ -3,9 +3,7 @@
|
|||
#ifndef ENGINE_SOUND_H
|
||||
#define ENGINE_SOUND_H
|
||||
|
||||
#include "kernel.h"
|
||||
|
||||
#include <engine/shared/video.h>
|
||||
#include <engine/kernel.h>
|
||||
#include <engine/storage.h>
|
||||
|
||||
class ISound : public IInterface
|
||||
|
@ -64,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
|
||||
|
@ -78,7 +76,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;
|
||||
|
@ -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;
|
||||
|
|
Loading…
Reference in a new issue