ddnet/src/engine/client/sound.cpp
H-M-H 7ae4b39574 made recording demos work
- videorecorder only works for recording demos now
- demoplayer was modified to allow controll over the time that passed
  in order to get perfect fps, IVideo controlls how much time passed,
  basically every rendercall sets the time to record the next frame
2019-09-30 20:48:47 +08:00

988 lines
21 KiB
C++

/* (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 <base/math.h>
#include <base/system.h>
#include <engine/graphics.h>
#include <engine/storage.h>
#include <engine/shared/config.h>
#include "SDL.h"
#include "sound.h"
extern "C"
{
#if defined(CONF_VIDEORECORDER)
#include <engine/shared/video.h>
#endif
#include <opusfile.h>
#include <wavpack.h>
}
#include <math.h>
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 LOCK m_SoundLock = 0;
static int m_CenterX = 0;
static int m_CenterY = 0;
static int m_MixingRate = 48000;
static volatile int m_SoundVolume = 100;
static int m_NextVoice = 0;
static int *m_pMixBuffer = 0; // buffer only used by the thread callback function
static unsigned m_MaxFrames = 0;
static const void *s_pWVBuffer = 0x0;
static int s_WVBufferPosition = 0;
static int s_WVBufferSize = 0;
const int DefaultDistance = 1500;
// TODO: there should be a faster way todo this
static short Int2Short(int i)
{
if(i > 0x7fff)
return 0x7fff;
else if(i < -0x7fff)
return -0x7fff;
return i;
}
static int IntAbs(int i)
{
if(i<0)
return -i;
return i;
}
static void Mix(short *pFinalOut, unsigned Frames)
{
int MasterVol;
mem_zero(m_pMixBuffer, m_MaxFrames*2*sizeof(int));
Frames = minimum(Frames, m_MaxFrames);
// acquire lock while we are mixing
lock_wait(m_SoundLock);
MasterVol = m_SoundVolume;
for(unsigned i = 0; i < NUM_VOICES; i++)
{
if(m_aVoices[i].m_pSample)
{
// mix voice
CVoice *v = &m_aVoices[i];
int *pOut = m_pMixBuffer;
int Step = v->m_pSample->m_Channels; // setup input sources
short *pInL = &v->m_pSample->m_pData[v->m_Tick*Step];
short *pInR = &v->m_pSample->m_pData[v->m_Tick*Step+1];
unsigned End = v->m_pSample->m_NumFrames-v->m_Tick;
int Rvol = (int)(v->m_pChannel->m_Vol*(v->m_Vol/255.0f));
int Lvol = (int)(v->m_pChannel->m_Vol*(v->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(v->m_pSample->m_Channels == 1)
pInR = pInL;
// volume calculation
if(v->m_Flags&ISound::FLAG_POS && v->m_pChannel->m_Pan)
{
// TODO: we should respect the channel panning value
int dx = v->m_X - m_CenterX;
int dy = v->m_Y - m_CenterY;
//
int p = IntAbs(dx);
float FalloffX = 0.0f;
float FalloffY = 0.0f;
int RangeX = 0; // for panning
bool InVoiceField = false;
switch(v->m_Shape)
{
case ISound::SHAPE_CIRCLE:
{
float r = v->m_Circle.m_Radius;
RangeX = r;
int Dist = (int)sqrtf((float)dx*dx+dy*dy); // nasty float
if(Dist < r)
{
InVoiceField = true;
// falloff
int FalloffDistance = r*v->m_Falloff;
if(Dist > FalloffDistance)
FalloffX = FalloffY = (r-Dist)/(r-FalloffDistance);
else
FalloffX = FalloffY = 1.0f;
}
else
InVoiceField = false;
break;
}
case ISound::SHAPE_RECTANGLE:
{
RangeX = v->m_Rectangle.m_Width/2.0f;
int abs_dx = abs(dx);
int abs_dy = abs(dy);
int w = v->m_Rectangle.m_Width/2.0f;
int h = v->m_Rectangle.m_Height/2.0f;
if(abs_dx < w && abs_dy < h)
{
InVoiceField = true;
// falloff
int fx = v->m_Falloff * w;
int fy = v->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(!(v->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;
}
}
else
{
Lvol = 0;
Rvol = 0;
}
}
// process all frames
for(unsigned s = 0; s < End; s++)
{
*pOut++ += (*pInL)*Lvol;
*pOut++ += (*pInR)*Rvol;
pInL += Step;
pInR += Step;
v->m_Tick++;
}
// free voice if not used any more
if(v->m_Tick == v->m_pSample->m_NumFrames)
{
if(v->m_Flags&ISound::FLAG_LOOP)
v->m_Tick = 0;
else
{
v->m_pSample = 0;
v->m_Age++;
}
}
}
}
// release the lock
lock_unlock(m_SoundLock);
{
// clamp accumulated values
// TODO: this seams slow
for(unsigned i = 0; i < Frames; i++)
{
int j = i<<1;
int vl = ((m_pMixBuffer[j]*MasterVol)/101)>>8;
int vr = ((m_pMixBuffer[j+1]*MasterVol)/101)>>8;
pFinalOut[j] = Int2Short(vl);
pFinalOut[j+1] = Int2Short(vr);
// dbg_msg("sound", "the real shit: %d %d", pFinalOut[j], pFinalOut[j+1]);
}
}
#if defined(CONF_ARCH_ENDIAN_BIG)
swap_endian(pFinalOut, sizeof(short), Frames * 2);
#endif
#if defined(CONF_VIDEORECORDER)
if (IVideo::Current())
IVideo::Current()->nextAudioFrame(pFinalOut);
#endif
}
static void SdlCallback(void *pUnused, Uint8 *pStream, int Len)
{
(void)pUnused;
Mix((short *)pStream, Len/2/2);
}
int CSound::Init()
{
m_SoundEnabled = 0;
m_pGraphics = Kernel()->RequestInterface<IEngineGraphics>();
m_pStorage = Kernel()->RequestInterface<IStorage>();
SDL_AudioSpec Format, FormatOut;
m_SoundLock = lock_create();
if(!g_Config.m_SndEnable)
return 0;
if(SDL_InitSubSystem(SDL_INIT_AUDIO) < 0)
{
dbg_msg("gfx", "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; // ignore_convention
Format.format = AUDIO_S16; // ignore_convention
Format.channels = 2; // ignore_convention
Format.samples = g_Config.m_SndBufferSize; // ignore_convention
Format.callback = SdlCallback; // ignore_convention
Format.userdata = NULL; // ignore_convention
// Open the audio device and start playing sound!
m_Device = SDL_OpenAudioDevice(NULL, 0, &Format, &FormatOut, 0);
if (m_Device == 0)
{
dbg_msg("client/sound", "unable to open audio: %s", SDL_GetError());
return -1;
}
else
dbg_msg("client/sound", "sound init successful using audio driver '%s'", SDL_GetCurrentAudioDriver());
m_MaxFrames = FormatOut.samples*2;
m_pMixBuffer = (int *)calloc(m_MaxFrames * 2, sizeof(int));
SDL_PauseAudioDevice(m_Device, 0);
m_SoundEnabled = 1;
Update(); // update the volume
return 0;
}
int CSound::Update()
{
// update volume
int WantedVolume = g_Config.m_SndVolume;
if(!m_pGraphics->WindowActive() && g_Config.m_SndNonactiveMute)
WantedVolume = 0;
if(WantedVolume != m_SoundVolume)
{
lock_wait(m_SoundLock);
m_SoundVolume = WantedVolume;
lock_unlock(m_SoundLock);
}
return 0;
}
int CSound::Shutdown()
{
for(unsigned SampleID = 0; SampleID < NUM_SAMPLES; SampleID++)
{
UnloadSample(SampleID);
}
SDL_CloseAudioDevice(m_Device);
SDL_QuitSubSystem(SDL_INIT_AUDIO);
lock_destroy(m_SoundLock);
free(m_pMixBuffer);
m_pMixBuffer = 0;
return 0;
}
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)
return SampleID;
}
return -1;
}
void CSound::RateConvert(int SampleID)
{
CSample *pSample = &m_aSamples[SampleID];
int NumFrames = 0;
short *pNewData = 0;
// make sure that we need to convert this sound
if(!pSample->m_pData || pSample->m_Rate == m_MixingRate)
return;
// allocate new data
NumFrames = (int)((pSample->m_NumFrames/(float)pSample->m_Rate)*m_MixingRate);
pNewData = (short *)calloc(NumFrames * pSample->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;
// set new data
if(pSample->m_Channels == 1)
pNewData[i] = pSample->m_pData[f];
else if(pSample->m_Channels == 2)
{
pNewData[i*2] = pSample->m_pData[f*2];
pNewData[i*2+1] = pSample->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;
}
int CSound::DecodeOpus(int SampleID, const void *pData, unsigned DataSize)
{
if(SampleID == -1 || SampleID >= NUM_SAMPLES)
return -1;
CSample *pSample = &m_aSamples[SampleID];
OggOpusFile *OpusFile = op_open_memory((const unsigned char *) pData, DataSize, NULL);
if (OpusFile)
{
int NumChannels = op_channel_count(OpusFile, -1);
int NumSamples = op_pcm_total(OpusFile, -1); // per channel!
pSample->m_Channels = NumChannels;
if(pSample->m_Channels > 2)
{
dbg_msg("sound/opus", "file is not mono or stereo.");
return -1;
}
pSample->m_pData = (short *)calloc(NumSamples * NumChannels, sizeof(short));
int Read;
int Pos = 0;
while (Pos < NumSamples)
{
Read = op_read(OpusFile, pSample->m_pData + Pos*NumChannels, NumSamples*NumChannels, NULL);
Pos += Read;
}
pSample->m_NumFrames = NumSamples; // ?
pSample->m_Rate = 48000;
pSample->m_LoopStart = -1;
pSample->m_LoopEnd = -1;
pSample->m_PausedAt = 0;
}
else
{
dbg_msg("sound/opus", "failed to decode sample");
return -1;
}
return SampleID;
}
static int ReadDataOld(void *pBuffer, int Size)
{
int ChunkSize = minimum(Size, s_WVBufferSize - s_WVBufferPosition);
mem_copy(pBuffer, (const char *)s_pWVBuffer + s_WVBufferPosition, ChunkSize);
s_WVBufferPosition += ChunkSize;
return ChunkSize;
}
#if defined(CONF_WAVPACK_OPEN_FILE_INPUT_EX)
static int ReadData(void *pId, void *pBuffer, int Size)
{
(void)pId;
return ReadDataOld(pBuffer, Size);
}
static int ReturnFalse(void *pId)
{
(void)pId;
return 0;
}
static unsigned int GetPos(void *pId)
{
(void)pId;
return s_WVBufferPosition;
}
static unsigned int GetLength(void *pId)
{
(void)pId;
return s_WVBufferSize;
}
static int PushBackByte(void *pId, int Char)
{
s_WVBufferPosition -= 1;
return 0;
}
#endif
int CSound::DecodeWV(int SampleID, const void *pData, unsigned DataSize)
{
if(SampleID == -1 || SampleID >= NUM_SAMPLES)
return -1;
CSample *pSample = &m_aSamples[SampleID];
char aError[100];
WavpackContext *pContext;
s_pWVBuffer = pData;
s_WVBufferSize = DataSize;
s_WVBufferPosition = 0;
#if defined(CONF_WAVPACK_OPEN_FILE_INPUT_EX)
WavpackStreamReader Callback = {0};
Callback.can_seek = ReturnFalse;
Callback.get_length = GetLength;
Callback.get_pos = GetPos;
Callback.push_back_byte = PushBackByte;
Callback.read_bytes = ReadData;
pContext = WavpackOpenFileInputEx(&Callback, (void *)1, 0, aError, 0, 0);
#else
pContext = WavpackOpenFileInput(ReadDataOld, aError);
#endif
if(pContext)
{
int NumSamples = WavpackGetNumSamples(pContext);
int BitsPerSample = WavpackGetBitsPerSample(pContext);
unsigned int SampleRate = WavpackGetSampleRate(pContext);
int NumChannels = WavpackGetNumChannels(pContext);
int *pSrc;
short *pDst;
int i;
pSample->m_Channels = NumChannels;
pSample->m_Rate = SampleRate;
if(pSample->m_Channels > 2)
{
dbg_msg("sound/wv", "file is not mono or stereo.");
return -1;
}
if(BitsPerSample != 16)
{
dbg_msg("sound/wv", "bps is %d, not 16", BitsPerSample);
return -1;
}
int *pBuffer = (int *)calloc(NumSamples * NumChannels, sizeof(int));
WavpackUnpackSamples(pContext, pBuffer, NumSamples); // TODO: check return value
pSrc = pBuffer;
pSample->m_pData = (short *)calloc(NumSamples * NumChannels, sizeof(short));
pDst = pSample->m_pData;
for (i = 0; i < NumSamples*NumChannels; i++)
*pDst++ = (short)*pSrc++;
free(pBuffer);
pSample->m_NumFrames = NumSamples;
pSample->m_LoopStart = -1;
pSample->m_LoopEnd = -1;
pSample->m_PausedAt = 0;
}
else
{
dbg_msg("sound/wv", "failed to decode sample (%s)", aError);
return -1;
}
return SampleID;
}
int CSound::LoadOpus(const char *pFilename)
{
// don't waste memory on sound when we are stress testing
#ifdef CONF_DEBUG
if(g_Config.m_DbgStress)
return -1;
#endif
// no need to load sound when we are running with no sound
if(!m_SoundEnabled)
return -1;
if(!m_pStorage)
return -1;
IOHANDLE File = m_pStorage->OpenFile(pFilename, IOFLAG_READ, IStorage::TYPE_ALL);
if(!File)
{
dbg_msg("sound/opus", "failed to open file. filename='%s'", pFilename);
return -1;
}
int SampleID = AllocID();
int DataSize = io_length(File);
if(SampleID < 0 || DataSize <= 0)
{
io_close(File);
File = NULL;
dbg_msg("sound/opus", "failed to open file. filename='%s'", pFilename);
return -1;
}
// read the whole file into memory
char *pData = new char[DataSize];
io_read(File, pData, DataSize);
SampleID = DecodeOpus(SampleID, pData, DataSize);
delete[] pData;
io_close(File);
File = NULL;
if(g_Config.m_Debug)
dbg_msg("sound/opus", "loaded %s", pFilename);
RateConvert(SampleID);
return SampleID;
}
int CSound::LoadWV(const char *pFilename)
{
// don't waste memory on sound when we are stress testing
#ifdef CONF_DEBUG
if(g_Config.m_DbgStress)
return -1;
#endif
// no need to load sound when we are running with no sound
if(!m_SoundEnabled)
return -1;
if(!m_pStorage)
return -1;
IOHANDLE File = m_pStorage->OpenFile(pFilename, IOFLAG_READ, IStorage::TYPE_ALL);
if(!File)
{
dbg_msg("sound/wv", "failed to open file. filename='%s'", pFilename);
return -1;
}
int SampleID = AllocID();
int DataSize = io_length(File);
if(SampleID < 0 || DataSize <= 0)
{
io_close(File);
File = NULL;
dbg_msg("sound/wv", "failed to open file. filename='%s'", pFilename);
return -1;
}
// read the whole file into memory
char *pData = new char[DataSize];
io_read(File, pData, DataSize);
SampleID = DecodeWV(SampleID, pData, DataSize);
delete[] pData;
io_close(File);
File = NULL;
if(g_Config.m_Debug)
dbg_msg("sound/wv", "loaded %s", pFilename);
RateConvert(SampleID);
return SampleID;
}
int CSound::LoadOpusFromMem(const void *pData, unsigned DataSize, bool FromEditor = false)
{
// don't waste memory on sound when we are stress testing
#ifdef CONF_DEBUG
if(g_Config.m_DbgStress)
return -1;
#endif
// no need to load sound when we are running with no sound
if(!m_SoundEnabled && !FromEditor)
return -1;
if(!pData)
return -1;
int SampleID = AllocID();
if(SampleID < 0)
return -1;
SampleID = DecodeOpus(SampleID, pData, DataSize);
RateConvert(SampleID);
return SampleID;
}
int CSound::LoadWVFromMem(const void *pData, unsigned DataSize, bool FromEditor = false)
{
// don't waste memory on sound when we are stress testing
#ifdef CONF_DEBUG
if(g_Config.m_DbgStress)
return -1;
#endif
// no need to load sound when we are running with no sound
if(!m_SoundEnabled && !FromEditor)
return -1;
if(!pData)
return -1;
int SampleID = AllocID();
if(SampleID < 0)
return -1;
SampleID = DecodeWV(SampleID, pData, DataSize);
RateConvert(SampleID);
return SampleID;
}
void CSound::UnloadSample(int SampleID)
{
if(SampleID == -1 || SampleID >= NUM_SAMPLES)
return;
Stop(SampleID);
free(m_aSamples[SampleID].m_pData);
m_aSamples[SampleID].m_pData = 0x0;
}
float CSound::GetSampleDuration(int SampleID)
{
if(SampleID == -1 || SampleID >= NUM_SAMPLES)
return 0.0f;
return (m_aSamples[SampleID].m_NumFrames/m_aSamples[SampleID].m_Rate);
}
void CSound::SetListenerPos(float x, float y)
{
m_CenterX = (int)x;
m_CenterY = (int)y;
}
void CSound::SetVoiceVolume(CVoiceHandle Voice, float Volume)
{
if(!Voice.IsValid())
return;
int VoiceID = Voice.Id();
if(m_aVoices[VoiceID].m_Age != Voice.Age())
return;
Volume = clamp(Volume, 0.0f, 1.0f);
m_aVoices[VoiceID].m_Vol = (int)(Volume*255.0f);
}
void CSound::SetVoiceFalloff(CVoiceHandle Voice, float Falloff)
{
if(!Voice.IsValid())
return;
int VoiceID = Voice.Id();
if(m_aVoices[VoiceID].m_Age != Voice.Age())
return;
Falloff = clamp(Falloff, 0.0f, 1.0f);
m_aVoices[VoiceID].m_Falloff = Falloff;
}
void CSound::SetVoiceLocation(CVoiceHandle Voice, float x, float y)
{
if(!Voice.IsValid())
return;
int VoiceID = Voice.Id();
if(m_aVoices[VoiceID].m_Age != Voice.Age())
return;
m_aVoices[VoiceID].m_X = x;
m_aVoices[VoiceID].m_Y = y;
}
void CSound::SetVoiceTimeOffset(CVoiceHandle Voice, float offset)
{
if(!Voice.IsValid())
return;
int VoiceID = Voice.Id();
if(m_aVoices[VoiceID].m_Age != Voice.Age())
return;
lock_wait(m_SoundLock);
{
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);
// 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(abs(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;
}
}
}
}
lock_unlock(m_SoundLock);
}
void CSound::SetVoiceCircle(CVoiceHandle Voice, float Radius)
{
if(!Voice.IsValid())
return;
int VoiceID = Voice.Id();
if(m_aVoices[VoiceID].m_Age != Voice.Age())
return;
m_aVoices[VoiceID].m_Shape = ISound::SHAPE_CIRCLE;
m_aVoices[VoiceID].m_Circle.m_Radius = maximum(0.0f, Radius);
}
void CSound::SetVoiceRectangle(CVoiceHandle Voice, float Width, float Height)
{
if(!Voice.IsValid())
return;
int VoiceID = Voice.Id();
if(m_aVoices[VoiceID].m_Age != Voice.Age())
return;
m_aVoices[VoiceID].m_Shape = ISound::SHAPE_RECTANGLE;
m_aVoices[VoiceID].m_Rectangle.m_Width = maximum(0.0f, Width);
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)
{
int VoiceID = -1;
int Age = -1;
int i;
lock_wait(m_SoundLock);
// search for voice
for(i = 0; i < NUM_VOICES; i++)
{
int id = (m_NextVoice + i) % NUM_VOICES;
if(!m_aVoices[id].m_pSample)
{
VoiceID = id;
m_NextVoice = id+1;
break;
}
}
// voice found, use it
if(VoiceID != -1)
{
m_aVoices[VoiceID].m_pSample = &m_aSamples[SampleID];
m_aVoices[VoiceID].m_pChannel = &m_aChannels[ChannelID];
if(Flags & FLAG_LOOP)
m_aVoices[VoiceID].m_Tick = m_aSamples[SampleID].m_PausedAt;
else
m_aVoices[VoiceID].m_Tick = 0;
m_aVoices[VoiceID].m_Vol = 255;
m_aVoices[VoiceID].m_Flags = Flags;
m_aVoices[VoiceID].m_X = (int)x;
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;
Age = m_aVoices[VoiceID].m_Age;
}
lock_unlock(m_SoundLock);
return CreateVoiceHandle(VoiceID, Age);
}
ISound::CVoiceHandle CSound::PlayAt(int ChannelID, int SampleID, int Flags, float x, float y)
{
return Play(ChannelID, SampleID, Flags|ISound::FLAG_POS, x, y);
}
ISound::CVoiceHandle CSound::Play(int ChannelID, int SampleID, int Flags)
{
return Play(ChannelID, SampleID, Flags, 0, 0);
}
void CSound::Stop(int SampleID)
{
// TODO: a nice fade out
lock_wait(m_SoundLock);
CSample *pSample = &m_aSamples[SampleID];
for(int i = 0; i < NUM_VOICES; i++)
{
if(m_aVoices[i].m_pSample == pSample)
{
if(m_aVoices[i].m_Flags & FLAG_LOOP)
m_aVoices[i].m_pSample->m_PausedAt = m_aVoices[i].m_Tick;
else
m_aVoices[i].m_pSample->m_PausedAt = 0;
m_aVoices[i].m_pSample = 0;
}
}
lock_unlock(m_SoundLock);
}
void CSound::StopAll()
{
// TODO: a nice fade out
lock_wait(m_SoundLock);
for(int i = 0; i < NUM_VOICES; i++)
{
if(m_aVoices[i].m_pSample)
{
if(m_aVoices[i].m_Flags & FLAG_LOOP)
m_aVoices[i].m_pSample->m_PausedAt = m_aVoices[i].m_Tick;
else
m_aVoices[i].m_pSample->m_PausedAt = 0;
}
m_aVoices[i].m_pSample = 0;
}
lock_unlock(m_SoundLock);
}
void CSound::StopVoice(CVoiceHandle Voice)
{
if(!Voice.IsValid())
return;
int VoiceID = Voice.Id();
if(m_aVoices[VoiceID].m_Age != Voice.Age())
return;
lock_wait(m_SoundLock);
{
m_aVoices[VoiceID].m_pSample = 0;
m_aVoices[VoiceID].m_Age++;
}
lock_unlock(m_SoundLock);
}
IEngineSound *CreateEngineSound() { return new CSound; }