2010-11-20 10:37:14 +00:00
|
|
|
/* (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. */
|
2023-09-21 18:21:36 +00:00
|
|
|
#include <SDL.h>
|
|
|
|
|
2012-01-06 18:27:18 +00:00
|
|
|
#include <base/math.h>
|
2010-05-29 07:25:38 +00:00
|
|
|
#include <base/system.h>
|
2012-01-06 18:27:18 +00:00
|
|
|
|
2011-02-27 14:03:57 +00:00
|
|
|
#include <engine/graphics.h>
|
2010-05-29 07:25:38 +00:00
|
|
|
#include <engine/shared/config.h>
|
2023-09-21 18:21:36 +00:00
|
|
|
#include <engine/storage.h>
|
2010-05-29 07:25:38 +00:00
|
|
|
|
|
|
|
#include "sound.h"
|
|
|
|
|
2016-08-30 23:39:59 +00:00
|
|
|
#if defined(CONF_VIDEORECORDER)
|
2020-09-26 19:41:58 +00:00
|
|
|
#include <engine/shared/video.h>
|
2016-08-30 23:39:59 +00:00
|
|
|
#endif
|
2023-09-21 18:21:36 +00:00
|
|
|
extern "C" {
|
2020-09-26 19:41:58 +00:00
|
|
|
#include <opusfile.h>
|
|
|
|
#include <wavpack.h>
|
2010-05-29 07:25:38 +00:00
|
|
|
}
|
|
|
|
|
2023-09-21 18:21:36 +00:00
|
|
|
#include <cmath>
|
2010-05-29 07:25:38 +00:00
|
|
|
|
2023-12-29 11:45:27 +00:00
|
|
|
static constexpr int SAMPLE_INDEX_USED = -2;
|
|
|
|
static constexpr int SAMPLE_INDEX_FULL = -1;
|
|
|
|
|
2023-09-21 18:21:36 +00:00
|
|
|
void CSound::Mix(short *pFinalOut, unsigned Frames)
|
2010-05-29 07:25:38 +00:00
|
|
|
{
|
2019-04-26 19:36:49 +00:00
|
|
|
Frames = minimum(Frames, m_MaxFrames);
|
2022-03-02 08:32:51 +00:00
|
|
|
mem_zero(m_pMixBuffer, Frames * 2 * sizeof(int));
|
2010-05-29 07:25:38 +00:00
|
|
|
|
2018-02-04 15:00:47 +00:00
|
|
|
// acquire lock while we are mixing
|
2021-10-28 11:46:53 +00:00
|
|
|
m_SoundLock.lock();
|
2011-08-11 08:59:14 +00:00
|
|
|
|
2023-09-21 21:52:40 +00:00
|
|
|
const int MasterVol = m_SoundVolume.load(std::memory_order_relaxed);
|
2011-08-11 08:59:14 +00:00
|
|
|
|
2020-10-26 14:14:07 +00:00
|
|
|
for(auto &Voice : m_aVoices)
|
2010-05-29 07:25:38 +00:00
|
|
|
{
|
2023-09-21 21:52:40 +00:00
|
|
|
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;
|
2010-05-29 07:25:38 +00:00
|
|
|
|
2023-09-21 21:52:40 +00:00
|
|
|
int VolumeR = round_truncate(Voice.m_pChannel->m_Vol * (Voice.m_Vol / 255.0f));
|
|
|
|
int VolumeL = VolumeR;
|
2011-08-11 08:59:14 +00:00
|
|
|
|
2023-09-21 21:52:40 +00:00
|
|
|
// make sure that we don't go outside the sound data
|
|
|
|
if(Frames < End)
|
|
|
|
End = Frames;
|
2010-05-29 07:25:38 +00:00
|
|
|
|
2023-09-21 21:52:40 +00:00
|
|
|
// check if we have a mono sound
|
|
|
|
if(Voice.m_pSample->m_Channels == 1)
|
|
|
|
pInR = pInL;
|
2010-05-29 07:25:38 +00:00
|
|
|
|
2023-09-21 21:52:40 +00:00
|
|
|
// 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;
|
2011-08-11 08:59:14 +00:00
|
|
|
|
2023-09-21 21:52:40 +00:00
|
|
|
int RangeX = 0; // for panning
|
|
|
|
bool InVoiceField = false;
|
2010-05-29 07:25:38 +00:00
|
|
|
|
2023-09-21 21:52:40 +00:00
|
|
|
switch(Voice.m_Shape)
|
2010-05-29 07:25:38 +00:00
|
|
|
{
|
2023-09-21 21:52:40 +00:00
|
|
|
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)
|
2020-09-26 19:41:58 +00:00
|
|
|
{
|
2023-09-21 21:52:40 +00:00
|
|
|
InVoiceField = true;
|
2014-11-29 13:29:40 +00:00
|
|
|
|
2023-09-21 21:52:40 +00:00
|
|
|
// falloff
|
|
|
|
int FalloffDistance = Radius * Voice.m_Falloff;
|
|
|
|
if(Dist > FalloffDistance)
|
|
|
|
FalloffX = FalloffY = (Radius - Dist) / (Radius - FalloffDistance);
|
|
|
|
else
|
|
|
|
FalloffX = FalloffY = 1.0f;
|
2020-09-26 19:41:58 +00:00
|
|
|
}
|
2023-09-21 21:52:40 +00:00
|
|
|
else
|
|
|
|
InVoiceField = false;
|
2014-11-29 13:29:40 +00:00
|
|
|
|
2023-09-21 21:52:40 +00:00
|
|
|
break;
|
|
|
|
}
|
2014-11-29 13:29:40 +00:00
|
|
|
|
2023-09-21 21:52:40 +00:00
|
|
|
case ISound::SHAPE_RECTANGLE:
|
|
|
|
{
|
|
|
|
RangeX = Voice.m_Rectangle.m_Width / 2.0f;
|
2014-11-29 13:29:40 +00:00
|
|
|
|
2023-09-21 21:52:40 +00:00
|
|
|
const int abs_dx = absolute(dx);
|
|
|
|
const int abs_dy = absolute(dy);
|
2014-11-29 13:29:40 +00:00
|
|
|
|
2023-09-21 21:52:40 +00:00
|
|
|
const int w = Voice.m_Rectangle.m_Width / 2.0f;
|
|
|
|
const int h = Voice.m_Rectangle.m_Height / 2.0f;
|
2014-11-29 13:29:40 +00:00
|
|
|
|
2023-09-21 21:52:40 +00:00
|
|
|
if(abs_dx < w && abs_dy < h)
|
|
|
|
{
|
|
|
|
InVoiceField = true;
|
2014-11-29 13:29:40 +00:00
|
|
|
|
2023-09-21 21:52:40 +00:00
|
|
|
// falloff
|
|
|
|
int fx = Voice.m_Falloff * w;
|
|
|
|
int fy = Voice.m_Falloff * h;
|
2020-09-26 19:41:58 +00:00
|
|
|
|
2023-09-21 21:52:40 +00:00
|
|
|
FalloffX = abs_dx > fx ? (float)(w - abs_dx) / (w - fx) : 1.0f;
|
|
|
|
FalloffY = abs_dy > fy ? (float)(h - abs_dy) / (h - fy) : 1.0f;
|
2020-09-26 19:41:58 +00:00
|
|
|
}
|
2023-09-21 21:52:40 +00:00
|
|
|
else
|
|
|
|
InVoiceField = false;
|
|
|
|
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
};
|
2014-11-29 13:29:40 +00:00
|
|
|
|
2023-09-21 21:52:40 +00:00
|
|
|
if(InVoiceField)
|
|
|
|
{
|
|
|
|
// panning
|
|
|
|
if(!(Voice.m_Flags & ISound::FLAG_NO_PANNING))
|
2010-05-29 07:25:38 +00:00
|
|
|
{
|
2023-09-21 21:52:40 +00:00
|
|
|
if(dx > 0)
|
|
|
|
VolumeL = ((RangeX - absolute(dx)) * VolumeL) / RangeX;
|
|
|
|
else
|
|
|
|
VolumeR = ((RangeX - absolute(dx)) * VolumeR) / RangeX;
|
2010-05-29 07:25:38 +00:00
|
|
|
}
|
2023-09-21 21:52:40 +00:00
|
|
|
|
2010-05-29 07:25:38 +00:00
|
|
|
{
|
2023-09-21 21:52:40 +00:00
|
|
|
VolumeL *= FalloffX * FalloffY;
|
|
|
|
VolumeR *= FalloffX * FalloffY;
|
2010-05-29 07:25:38 +00:00
|
|
|
}
|
|
|
|
}
|
2023-09-21 21:52:40 +00:00
|
|
|
else
|
2010-05-29 07:25:38 +00:00
|
|
|
{
|
2023-09-21 21:52:40 +00:00
|
|
|
VolumeL = 0;
|
|
|
|
VolumeR = 0;
|
2010-05-29 07:25:38 +00:00
|
|
|
}
|
2023-09-21 21:52:40 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// process all frames
|
|
|
|
for(unsigned s = 0; s < End; s++)
|
|
|
|
{
|
|
|
|
*pOut++ += (*pInL) * VolumeL;
|
|
|
|
*pOut++ += (*pInR) * VolumeR;
|
|
|
|
pInL += Step;
|
|
|
|
pInR += Step;
|
|
|
|
Voice.m_Tick++;
|
|
|
|
}
|
2011-08-11 08:59:14 +00:00
|
|
|
|
2023-09-21 21:52:40 +00:00
|
|
|
// 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
|
2011-04-13 18:00:54 +00:00
|
|
|
{
|
2023-09-21 21:52:40 +00:00
|
|
|
Voice.m_pSample = nullptr;
|
|
|
|
Voice.m_Age++;
|
2011-04-13 18:00:54 +00:00
|
|
|
}
|
2010-05-29 07:25:38 +00:00
|
|
|
}
|
|
|
|
}
|
2011-08-11 08:59:14 +00:00
|
|
|
|
2021-10-28 11:46:53 +00:00
|
|
|
m_SoundLock.unlock();
|
2010-05-29 07:25:38 +00:00
|
|
|
|
2022-06-26 19:53:15 +00:00
|
|
|
// clamp accumulated values
|
|
|
|
for(unsigned i = 0; i < Frames * 2; i++)
|
|
|
|
pFinalOut[i] = clamp<int>(((m_pMixBuffer[i] * MasterVol) / 101) >> 8, std::numeric_limits<short>::min(), std::numeric_limits<short>::max());
|
2010-05-29 07:25:38 +00:00
|
|
|
|
|
|
|
#if defined(CONF_ARCH_ENDIAN_BIG)
|
|
|
|
swap_endian(pFinalOut, sizeof(short), Frames * 2);
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
2023-09-21 18:21:36 +00:00
|
|
|
static void SdlCallback(void *pUser, Uint8 *pStream, int Len)
|
2010-05-29 07:25:38 +00:00
|
|
|
{
|
2023-09-21 18:21:36 +00:00
|
|
|
CSound *pSound = static_cast<CSound *>(pUser);
|
|
|
|
|
2019-10-26 12:01:09 +00:00
|
|
|
#if defined(CONF_VIDEORECORDER)
|
2020-09-26 19:41:58 +00:00
|
|
|
if(!(IVideo::Current() && g_Config.m_ClVideoSndEnable))
|
2022-03-02 08:32:51 +00:00
|
|
|
{
|
2023-09-21 18:21:36 +00:00
|
|
|
pSound->Mix((short *)pStream, Len / sizeof(short) / 2);
|
2022-03-02 08:32:51 +00:00
|
|
|
}
|
2020-01-08 06:25:06 +00:00
|
|
|
else
|
2022-03-02 08:32:51 +00:00
|
|
|
{
|
|
|
|
mem_zero(pStream, Len);
|
|
|
|
}
|
2019-11-02 08:09:00 +00:00
|
|
|
#else
|
2023-09-21 18:21:36 +00:00
|
|
|
pSound->Mix((short *)pStream, Len / sizeof(short) / 2);
|
2019-10-26 12:01:09 +00:00
|
|
|
#endif
|
2010-05-29 07:25:38 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
int CSound::Init()
|
|
|
|
{
|
2022-06-26 20:00:18 +00:00
|
|
|
m_SoundEnabled = false;
|
2010-05-29 07:25:38 +00:00
|
|
|
m_pGraphics = Kernel()->RequestInterface<IEngineGraphics>();
|
|
|
|
m_pStorage = Kernel()->RequestInterface<IStorage>();
|
2011-08-11 08:59:14 +00:00
|
|
|
|
2010-05-29 07:25:38 +00:00
|
|
|
if(!g_Config.m_SndEnable)
|
|
|
|
return 0;
|
2011-08-11 08:59:14 +00:00
|
|
|
|
2012-01-03 20:39:10 +00:00
|
|
|
if(SDL_InitSubSystem(SDL_INIT_AUDIO) < 0)
|
|
|
|
{
|
2023-09-21 18:21:36 +00:00
|
|
|
dbg_msg("sound", "unable to init SDL audio: %s", SDL_GetError());
|
2012-01-03 20:39:10 +00:00
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
2010-05-29 07:25:38 +00:00
|
|
|
m_MixingRate = g_Config.m_SndRate;
|
|
|
|
|
2023-09-21 18:21:36 +00:00
|
|
|
SDL_AudioSpec Format, FormatOut;
|
|
|
|
Format.freq = m_MixingRate;
|
2022-02-16 19:54:11 +00:00
|
|
|
Format.format = AUDIO_S16;
|
|
|
|
Format.channels = 2;
|
|
|
|
Format.samples = g_Config.m_SndBufferSize;
|
|
|
|
Format.callback = SdlCallback;
|
2023-09-21 18:21:36 +00:00
|
|
|
Format.userdata = this;
|
2010-05-29 07:25:38 +00:00
|
|
|
|
|
|
|
// Open the audio device and start playing sound!
|
2023-09-21 20:59:46 +00:00
|
|
|
m_Device = SDL_OpenAudioDevice(nullptr, 0, &Format, &FormatOut, 0);
|
2017-07-30 10:33:51 +00:00
|
|
|
|
2020-09-26 19:41:58 +00:00
|
|
|
if(m_Device == 0)
|
2010-05-29 07:25:38 +00:00
|
|
|
{
|
2023-09-21 18:21:36 +00:00
|
|
|
dbg_msg("sound", "unable to open audio: %s", SDL_GetError());
|
2010-05-29 07:25:38 +00:00
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
else
|
2023-09-21 18:21:36 +00:00
|
|
|
dbg_msg("sound", "sound init successful using audio driver '%s'", SDL_GetCurrentAudioDriver());
|
2010-05-29 07:25:38 +00:00
|
|
|
|
2020-09-26 19:41:58 +00:00
|
|
|
m_MaxFrames = FormatOut.samples * 2;
|
2022-03-02 08:32:51 +00:00
|
|
|
#if defined(CONF_VIDEORECORDER)
|
|
|
|
m_MaxFrames = maximum<uint32_t>(m_MaxFrames, 1024 * 2); // make the buffer bigger just in case
|
|
|
|
#endif
|
2018-04-09 09:56:39 +00:00
|
|
|
m_pMixBuffer = (int *)calloc(m_MaxFrames * 2, sizeof(int));
|
2012-01-06 18:27:18 +00:00
|
|
|
|
2023-12-29 11:45:27 +00:00
|
|
|
m_FirstFreeSampleIndex = 0;
|
|
|
|
for(size_t i = 0; i < std::size(m_aSamples) - 1; ++i)
|
|
|
|
{
|
|
|
|
m_aSamples[i].m_Index = i;
|
|
|
|
m_aSamples[i].m_NextFreeSampleIndex = i + 1;
|
|
|
|
}
|
|
|
|
m_aSamples[std::size(m_aSamples) - 1].m_Index = std::size(m_aSamples) - 1;
|
|
|
|
m_aSamples[std::size(m_aSamples) - 1].m_NextFreeSampleIndex = SAMPLE_INDEX_FULL;
|
|
|
|
|
2017-07-30 10:33:51 +00:00
|
|
|
SDL_PauseAudioDevice(m_Device, 0);
|
2011-08-11 08:59:14 +00:00
|
|
|
|
2022-06-26 20:00:18 +00:00
|
|
|
m_SoundEnabled = true;
|
2023-09-21 21:05:10 +00:00
|
|
|
Update();
|
2010-05-29 07:25:38 +00:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
int CSound::Update()
|
|
|
|
{
|
2023-09-21 21:05:10 +00:00
|
|
|
UpdateVolume();
|
|
|
|
return 0;
|
|
|
|
}
|
2011-08-11 08:59:14 +00:00
|
|
|
|
2023-09-21 21:05:10 +00:00
|
|
|
void CSound::UpdateVolume()
|
|
|
|
{
|
|
|
|
int WantedVolume = g_Config.m_SndVolume;
|
2010-05-29 07:25:38 +00:00
|
|
|
if(!m_pGraphics->WindowActive() && g_Config.m_SndNonactiveMute)
|
|
|
|
WantedVolume = 0;
|
2023-09-21 21:05:10 +00:00
|
|
|
m_SoundVolume.store(WantedVolume, std::memory_order_relaxed);
|
2010-05-29 07:25:38 +00:00
|
|
|
}
|
|
|
|
|
2022-09-16 14:31:36 +00:00
|
|
|
void CSound::Shutdown()
|
2010-05-29 07:25:38 +00:00
|
|
|
{
|
2024-03-05 14:44:09 +00:00
|
|
|
for(unsigned SampleId = 0; SampleId < NUM_SAMPLES; SampleId++)
|
2017-07-21 14:37:23 +00:00
|
|
|
{
|
2024-03-05 14:44:09 +00:00
|
|
|
UnloadSample(SampleId);
|
2017-07-21 14:37:23 +00:00
|
|
|
}
|
|
|
|
|
2017-07-30 10:33:51 +00:00
|
|
|
SDL_CloseAudioDevice(m_Device);
|
2012-01-03 20:39:10 +00:00
|
|
|
SDL_QuitSubSystem(SDL_INIT_AUDIO);
|
2018-04-09 09:56:39 +00:00
|
|
|
free(m_pMixBuffer);
|
2023-09-21 20:59:46 +00:00
|
|
|
m_pMixBuffer = nullptr;
|
2010-05-29 07:25:38 +00:00
|
|
|
}
|
|
|
|
|
2023-12-29 11:45:27 +00:00
|
|
|
CSample *CSound::AllocSample()
|
2010-05-29 07:25:38 +00:00
|
|
|
{
|
2023-12-29 11:45:27 +00:00
|
|
|
if(m_FirstFreeSampleIndex == SAMPLE_INDEX_FULL)
|
|
|
|
return nullptr;
|
|
|
|
|
|
|
|
CSample *pSample = &m_aSamples[m_FirstFreeSampleIndex];
|
|
|
|
m_FirstFreeSampleIndex = pSample->m_NextFreeSampleIndex;
|
|
|
|
pSample->m_NextFreeSampleIndex = SAMPLE_INDEX_USED;
|
|
|
|
if(pSample->m_pData != nullptr)
|
2010-05-29 07:25:38 +00:00
|
|
|
{
|
2023-12-29 11:45:27 +00:00
|
|
|
char aError[64];
|
|
|
|
str_format(aError, sizeof(aError), "Sample was not unloaded (index=%d, duration=%f)", pSample->m_Index, pSample->TotalTime());
|
|
|
|
dbg_assert(false, aError);
|
2010-05-29 07:25:38 +00:00
|
|
|
}
|
2023-12-29 11:45:27 +00:00
|
|
|
return pSample;
|
2010-05-29 07:25:38 +00:00
|
|
|
}
|
|
|
|
|
2023-10-06 11:02:56 +00:00
|
|
|
void CSound::RateConvert(CSample &Sample) const
|
2010-05-29 07:25:38 +00:00
|
|
|
{
|
2023-12-29 11:45:27 +00:00
|
|
|
dbg_assert(Sample.m_pData != nullptr, "Sample is not loaded");
|
2010-05-29 07:25:38 +00:00
|
|
|
// make sure that we need to convert this sound
|
2023-12-29 11:45:27 +00:00
|
|
|
if(Sample.m_Rate == m_MixingRate)
|
2010-05-29 07:25:38 +00:00
|
|
|
return;
|
|
|
|
|
|
|
|
// allocate new data
|
2023-09-21 21:18:17 +00:00
|
|
|
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));
|
2011-08-11 08:59:14 +00:00
|
|
|
|
2010-05-29 07:25:38 +00:00
|
|
|
for(int i = 0; i < NumFrames; i++)
|
|
|
|
{
|
2018-07-10 09:29:02 +00:00
|
|
|
// resample TODO: this should be done better, like linear at least
|
2020-09-26 19:41:58 +00:00
|
|
|
float a = i / (float)NumFrames;
|
2023-09-21 21:18:17 +00:00
|
|
|
int f = (int)(a * Sample.m_NumFrames);
|
|
|
|
if(f >= Sample.m_NumFrames)
|
|
|
|
f = Sample.m_NumFrames - 1;
|
2011-08-11 08:59:14 +00:00
|
|
|
|
2010-05-29 07:25:38 +00:00
|
|
|
// set new data
|
2023-09-21 21:18:17 +00:00
|
|
|
if(Sample.m_Channels == 1)
|
|
|
|
pNewData[i] = Sample.m_pData[f];
|
|
|
|
else if(Sample.m_Channels == 2)
|
2010-05-29 07:25:38 +00:00
|
|
|
{
|
2023-09-21 21:18:17 +00:00
|
|
|
pNewData[i * 2] = Sample.m_pData[f * 2];
|
|
|
|
pNewData[i * 2 + 1] = Sample.m_pData[f * 2 + 1];
|
2010-05-29 07:25:38 +00:00
|
|
|
}
|
|
|
|
}
|
2011-08-11 08:59:14 +00:00
|
|
|
|
2010-05-29 07:25:38 +00:00
|
|
|
// free old data and apply new
|
2023-09-21 21:18:17 +00:00
|
|
|
free(Sample.m_pData);
|
|
|
|
Sample.m_pData = pNewData;
|
|
|
|
Sample.m_NumFrames = NumFrames;
|
|
|
|
Sample.m_Rate = m_MixingRate;
|
2010-05-29 07:25:38 +00:00
|
|
|
}
|
|
|
|
|
2023-10-06 11:02:56 +00:00
|
|
|
bool CSound::DecodeOpus(CSample &Sample, const void *pData, unsigned DataSize) const
|
2014-10-27 15:23:53 +00:00
|
|
|
{
|
2023-09-21 21:18:17 +00:00
|
|
|
OggOpusFile *pOpusFile = op_open_memory((const unsigned char *)pData, DataSize, nullptr);
|
2022-06-28 15:38:38 +00:00
|
|
|
if(pOpusFile)
|
2014-10-27 15:23:53 +00:00
|
|
|
{
|
2023-09-21 21:18:17 +00:00
|
|
|
const int NumChannels = op_channel_count(pOpusFile, -1);
|
|
|
|
const int NumSamples = op_pcm_total(pOpusFile, -1); // per channel!
|
2014-10-27 15:23:53 +00:00
|
|
|
|
2024-04-25 18:17:15 +00:00
|
|
|
if(NumChannels > 2)
|
2014-10-27 15:23:53 +00:00
|
|
|
{
|
|
|
|
dbg_msg("sound/opus", "file is not mono or stereo.");
|
2023-09-21 21:18:17 +00:00
|
|
|
return false;
|
2014-10-27 15:23:53 +00:00
|
|
|
}
|
|
|
|
|
2024-04-25 18:03:38 +00:00
|
|
|
short *pSampleData = (short *)calloc((size_t)NumSamples * NumChannels, sizeof(short));
|
2014-10-27 15:23:53 +00:00
|
|
|
|
2014-10-28 09:27:00 +00:00
|
|
|
int Pos = 0;
|
2020-09-26 19:41:58 +00:00
|
|
|
while(Pos < NumSamples)
|
2022-06-28 15:38:05 +00:00
|
|
|
{
|
2024-04-25 18:03:38 +00:00
|
|
|
const int Read = op_read(pOpusFile, pSampleData + Pos * NumChannels, NumSamples * NumChannels, nullptr);
|
2022-06-28 15:38:05 +00:00
|
|
|
if(Read < 0)
|
|
|
|
{
|
2024-04-25 18:03:38 +00:00
|
|
|
free(pSampleData);
|
2022-06-28 15:38:05 +00:00
|
|
|
dbg_msg("sound/opus", "op_read error %d at %d", Read, Pos);
|
2023-09-21 21:18:17 +00:00
|
|
|
return false;
|
2022-06-28 15:38:05 +00:00
|
|
|
}
|
|
|
|
else if(Read == 0) // EOF
|
|
|
|
break;
|
|
|
|
Pos += Read;
|
|
|
|
}
|
2014-10-27 15:23:53 +00:00
|
|
|
|
2024-04-25 18:03:38 +00:00
|
|
|
Sample.m_pData = pSampleData;
|
2023-09-21 21:18:17 +00:00
|
|
|
Sample.m_NumFrames = Pos;
|
|
|
|
Sample.m_Rate = 48000;
|
2024-04-25 18:17:15 +00:00
|
|
|
Sample.m_Channels = NumChannels;
|
2023-09-21 21:18:17 +00:00
|
|
|
Sample.m_LoopStart = -1;
|
|
|
|
Sample.m_LoopEnd = -1;
|
|
|
|
Sample.m_PausedAt = 0;
|
2014-10-27 15:23:53 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2014-10-28 21:42:31 +00:00
|
|
|
dbg_msg("sound/opus", "failed to decode sample");
|
2023-09-21 21:18:17 +00:00
|
|
|
return false;
|
2014-10-27 15:23:53 +00:00
|
|
|
}
|
|
|
|
|
2023-09-21 21:18:17 +00:00
|
|
|
return true;
|
2014-10-27 15:23:53 +00:00
|
|
|
}
|
|
|
|
|
2023-09-21 18:21:36 +00:00
|
|
|
// 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;
|
|
|
|
|
2018-02-12 22:14:14 +00:00
|
|
|
static int ReadDataOld(void *pBuffer, int Size)
|
|
|
|
{
|
2019-04-26 19:36:49 +00:00
|
|
|
int ChunkSize = minimum(Size, s_WVBufferSize - s_WVBufferPosition);
|
2018-02-12 22:14:14 +00:00
|
|
|
mem_copy(pBuffer, (const char *)s_pWVBuffer + s_WVBufferPosition, ChunkSize);
|
|
|
|
s_WVBufferPosition += ChunkSize;
|
|
|
|
return ChunkSize;
|
|
|
|
}
|
|
|
|
|
2018-02-17 00:35:07 +00:00
|
|
|
#if defined(CONF_WAVPACK_OPEN_FILE_INPUT_EX)
|
2018-02-12 22:14:14 +00:00
|
|
|
static int ReadData(void *pId, void *pBuffer, int Size)
|
|
|
|
{
|
|
|
|
(void)pId;
|
|
|
|
return ReadDataOld(pBuffer, Size);
|
|
|
|
}
|
|
|
|
|
|
|
|
static int ReturnFalse(void *pId)
|
|
|
|
{
|
|
|
|
(void)pId;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2018-02-17 00:35:07 +00:00
|
|
|
static unsigned int GetPos(void *pId)
|
2018-02-12 22:14:14 +00:00
|
|
|
{
|
|
|
|
(void)pId;
|
|
|
|
return s_WVBufferPosition;
|
|
|
|
}
|
|
|
|
|
2018-02-17 00:35:07 +00:00
|
|
|
static unsigned int GetLength(void *pId)
|
2018-02-12 22:14:14 +00:00
|
|
|
{
|
|
|
|
(void)pId;
|
|
|
|
return s_WVBufferSize;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int PushBackByte(void *pId, int Char)
|
|
|
|
{
|
|
|
|
s_WVBufferPosition -= 1;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
2023-10-06 11:02:56 +00:00
|
|
|
bool CSound::DecodeWV(CSample &Sample, const void *pData, unsigned DataSize) const
|
2010-05-29 07:25:38 +00:00
|
|
|
{
|
2014-10-11 12:50:16 +00:00
|
|
|
char aError[100];
|
2010-05-29 07:25:38 +00:00
|
|
|
|
2023-09-21 18:21:36 +00:00
|
|
|
dbg_assert(s_pWVBuffer == nullptr, "DecodeWV already in use");
|
2018-02-12 22:14:14 +00:00
|
|
|
s_pWVBuffer = pData;
|
|
|
|
s_WVBufferSize = DataSize;
|
|
|
|
s_WVBufferPosition = 0;
|
|
|
|
|
2018-02-17 00:35:07 +00:00
|
|
|
#if defined(CONF_WAVPACK_OPEN_FILE_INPUT_EX)
|
|
|
|
WavpackStreamReader Callback = {0};
|
2018-02-12 22:14:14 +00:00
|
|
|
Callback.can_seek = ReturnFalse;
|
|
|
|
Callback.get_length = GetLength;
|
|
|
|
Callback.get_pos = GetPos;
|
|
|
|
Callback.push_back_byte = PushBackByte;
|
2018-02-17 00:35:07 +00:00
|
|
|
Callback.read_bytes = ReadData;
|
2022-06-26 19:39:48 +00:00
|
|
|
WavpackContext *pContext = WavpackOpenFileInputEx(&Callback, (void *)1, 0, aError, 0, 0);
|
2018-02-17 00:31:40 +00:00
|
|
|
#else
|
2022-06-26 19:39:48 +00:00
|
|
|
WavpackContext *pContext = WavpackOpenFileInput(ReadDataOld, aError);
|
2018-02-12 22:14:14 +00:00
|
|
|
#endif
|
|
|
|
if(pContext)
|
2010-05-29 07:25:38 +00:00
|
|
|
{
|
2023-09-21 21:18:17 +00:00
|
|
|
const int NumSamples = WavpackGetNumSamples(pContext);
|
|
|
|
const int BitsPerSample = WavpackGetBitsPerSample(pContext);
|
|
|
|
const unsigned int SampleRate = WavpackGetSampleRate(pContext);
|
|
|
|
const int NumChannels = WavpackGetNumChannels(pContext);
|
2010-05-29 07:25:38 +00:00
|
|
|
|
2024-04-25 18:17:15 +00:00
|
|
|
if(NumChannels > 2)
|
2010-05-29 07:25:38 +00:00
|
|
|
{
|
2014-10-11 12:50:16 +00:00
|
|
|
dbg_msg("sound/wv", "file is not mono or stereo.");
|
2023-09-21 21:18:17 +00:00
|
|
|
s_pWVBuffer = nullptr;
|
|
|
|
return false;
|
2010-05-29 07:25:38 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if(BitsPerSample != 16)
|
|
|
|
{
|
2014-10-11 12:50:16 +00:00
|
|
|
dbg_msg("sound/wv", "bps is %d, not 16", BitsPerSample);
|
2023-09-21 21:18:17 +00:00
|
|
|
s_pWVBuffer = nullptr;
|
|
|
|
return false;
|
2010-05-29 07:25:38 +00:00
|
|
|
}
|
|
|
|
|
2020-10-05 17:03:14 +00:00
|
|
|
int *pBuffer = (int *)calloc((size_t)NumSamples * NumChannels, sizeof(int));
|
2022-06-26 19:58:04 +00:00
|
|
|
if(!WavpackUnpackSamples(pContext, pBuffer, NumSamples))
|
|
|
|
{
|
|
|
|
free(pBuffer);
|
|
|
|
dbg_msg("sound/wv", "WavpackUnpackSamples failed. NumSamples=%d, NumChannels=%d", NumSamples, NumChannels);
|
2023-09-21 21:18:17 +00:00
|
|
|
s_pWVBuffer = nullptr;
|
|
|
|
return false;
|
2022-06-26 19:58:04 +00:00
|
|
|
}
|
2011-08-11 08:59:14 +00:00
|
|
|
|
2023-09-21 21:18:17 +00:00
|
|
|
Sample.m_pData = (short *)calloc((size_t)NumSamples * NumChannels, sizeof(short));
|
2010-05-29 07:25:38 +00:00
|
|
|
|
2023-09-21 21:18:17 +00:00
|
|
|
int *pSrc = pBuffer;
|
|
|
|
short *pDst = Sample.m_pData;
|
2022-06-26 19:39:48 +00:00
|
|
|
for(int i = 0; i < NumSamples * NumChannels; i++)
|
2010-05-29 07:25:38 +00:00
|
|
|
*pDst++ = (short)*pSrc++;
|
|
|
|
|
2018-04-09 09:56:39 +00:00
|
|
|
free(pBuffer);
|
2020-09-26 19:41:58 +00:00
|
|
|
#ifdef CONF_WAVPACK_CLOSE_FILE
|
2020-04-09 10:08:38 +00:00
|
|
|
WavpackCloseFile(pContext);
|
2020-04-11 11:17:14 +00:00
|
|
|
#endif
|
2010-05-29 07:25:38 +00:00
|
|
|
|
2023-09-21 21:18:17 +00:00
|
|
|
Sample.m_NumFrames = NumSamples;
|
2024-04-25 18:17:15 +00:00
|
|
|
Sample.m_Rate = SampleRate;
|
|
|
|
Sample.m_Channels = NumChannels;
|
2023-09-21 21:18:17 +00:00
|
|
|
Sample.m_LoopStart = -1;
|
|
|
|
Sample.m_LoopEnd = -1;
|
|
|
|
Sample.m_PausedAt = 0;
|
|
|
|
|
|
|
|
s_pWVBuffer = nullptr;
|
2010-05-29 07:25:38 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2014-10-11 12:50:16 +00:00
|
|
|
dbg_msg("sound/wv", "failed to decode sample (%s)", aError);
|
2023-09-21 21:18:17 +00:00
|
|
|
s_pWVBuffer = nullptr;
|
|
|
|
return false;
|
2010-05-29 07:25:38 +00:00
|
|
|
}
|
|
|
|
|
2023-09-21 21:18:17 +00:00
|
|
|
return true;
|
2014-10-11 12:50:16 +00:00
|
|
|
}
|
|
|
|
|
2023-06-25 18:41:49 +00:00
|
|
|
int CSound::LoadOpus(const char *pFilename, int StorageType)
|
2014-10-27 15:23:53 +00:00
|
|
|
{
|
|
|
|
// no need to load sound when we are running with no sound
|
|
|
|
if(!m_SoundEnabled)
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
if(!m_pStorage)
|
|
|
|
return -1;
|
|
|
|
|
2023-12-29 11:45:27 +00:00
|
|
|
CSample *pSample = AllocSample();
|
|
|
|
if(!pSample)
|
2014-10-27 15:23:53 +00:00
|
|
|
{
|
2022-06-14 18:51:02 +00:00
|
|
|
dbg_msg("sound/opus", "failed to allocate sample ID. filename='%s'", pFilename);
|
2014-10-27 15:23:53 +00:00
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
2022-06-14 18:51:02 +00:00
|
|
|
void *pData;
|
|
|
|
unsigned DataSize;
|
2023-06-25 18:41:49 +00:00
|
|
|
if(!m_pStorage->ReadFile(pFilename, StorageType, &pData, &DataSize))
|
2014-10-27 15:23:53 +00:00
|
|
|
{
|
2023-12-29 11:45:27 +00:00
|
|
|
UnloadSample(pSample->m_Index);
|
2014-10-27 15:23:53 +00:00
|
|
|
dbg_msg("sound/opus", "failed to open file. filename='%s'", pFilename);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
2023-12-29 11:45:27 +00:00
|
|
|
const bool DecodeSuccess = DecodeOpus(*pSample, pData, DataSize);
|
2022-06-14 18:51:02 +00:00
|
|
|
free(pData);
|
2023-09-21 21:18:17 +00:00
|
|
|
if(!DecodeSuccess)
|
2023-12-29 11:45:27 +00:00
|
|
|
{
|
|
|
|
UnloadSample(pSample->m_Index);
|
2023-03-17 09:39:17 +00:00
|
|
|
return -1;
|
2023-12-29 11:45:27 +00:00
|
|
|
}
|
2014-10-27 15:23:53 +00:00
|
|
|
|
|
|
|
if(g_Config.m_Debug)
|
|
|
|
dbg_msg("sound/opus", "loaded %s", pFilename);
|
|
|
|
|
2023-12-29 11:45:27 +00:00
|
|
|
RateConvert(*pSample);
|
|
|
|
return pSample->m_Index;
|
2014-10-27 15:23:53 +00:00
|
|
|
}
|
|
|
|
|
2023-06-25 18:41:49 +00:00
|
|
|
int CSound::LoadWV(const char *pFilename, int StorageType)
|
2014-10-11 12:50:16 +00:00
|
|
|
{
|
|
|
|
// no need to load sound when we are running with no sound
|
|
|
|
if(!m_SoundEnabled)
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
if(!m_pStorage)
|
|
|
|
return -1;
|
|
|
|
|
2023-12-29 11:45:27 +00:00
|
|
|
CSample *pSample = AllocSample();
|
|
|
|
if(!pSample)
|
2014-10-11 12:50:16 +00:00
|
|
|
{
|
2022-06-14 18:51:02 +00:00
|
|
|
dbg_msg("sound/wv", "failed to allocate sample ID. filename='%s'", pFilename);
|
2014-10-11 12:50:16 +00:00
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
2022-06-14 18:51:02 +00:00
|
|
|
void *pData;
|
|
|
|
unsigned DataSize;
|
2023-06-25 18:41:49 +00:00
|
|
|
if(!m_pStorage->ReadFile(pFilename, StorageType, &pData, &DataSize))
|
2014-10-24 21:03:16 +00:00
|
|
|
{
|
2023-12-29 11:45:27 +00:00
|
|
|
UnloadSample(pSample->m_Index);
|
2014-10-24 21:03:16 +00:00
|
|
|
dbg_msg("sound/wv", "failed to open file. filename='%s'", pFilename);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
2023-12-29 11:45:27 +00:00
|
|
|
const bool DecodeSuccess = DecodeWV(*pSample, pData, DataSize);
|
2022-06-14 18:51:02 +00:00
|
|
|
free(pData);
|
2023-09-21 21:18:17 +00:00
|
|
|
if(!DecodeSuccess)
|
2023-12-29 11:45:27 +00:00
|
|
|
{
|
|
|
|
UnloadSample(pSample->m_Index);
|
2023-03-17 09:39:17 +00:00
|
|
|
return -1;
|
2023-12-29 11:45:27 +00:00
|
|
|
}
|
2010-05-29 07:25:38 +00:00
|
|
|
|
|
|
|
if(g_Config.m_Debug)
|
|
|
|
dbg_msg("sound/wv", "loaded %s", pFilename);
|
|
|
|
|
2023-12-29 11:45:27 +00:00
|
|
|
RateConvert(*pSample);
|
|
|
|
return pSample->m_Index;
|
2010-05-29 07:25:38 +00:00
|
|
|
}
|
|
|
|
|
2014-10-27 15:23:53 +00:00
|
|
|
int CSound::LoadOpusFromMem(const void *pData, unsigned DataSize, bool FromEditor = false)
|
|
|
|
{
|
|
|
|
// no need to load sound when we are running with no sound
|
|
|
|
if(!m_SoundEnabled && !FromEditor)
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
if(!pData)
|
|
|
|
return -1;
|
|
|
|
|
2023-12-29 11:45:27 +00:00
|
|
|
CSample *pSample = AllocSample();
|
|
|
|
if(!pSample)
|
2014-10-27 15:23:53 +00:00
|
|
|
return -1;
|
|
|
|
|
2023-12-29 11:45:27 +00:00
|
|
|
if(!DecodeOpus(*pSample, pData, DataSize))
|
|
|
|
{
|
|
|
|
UnloadSample(pSample->m_Index);
|
2023-03-17 07:18:30 +00:00
|
|
|
return -1;
|
2023-12-29 11:45:27 +00:00
|
|
|
}
|
2014-10-27 15:23:53 +00:00
|
|
|
|
2023-12-29 11:45:27 +00:00
|
|
|
RateConvert(*pSample);
|
|
|
|
return pSample->m_Index;
|
2014-10-27 15:23:53 +00:00
|
|
|
}
|
|
|
|
|
2014-10-24 23:23:39 +00:00
|
|
|
int CSound::LoadWVFromMem(const void *pData, unsigned DataSize, bool FromEditor = false)
|
2014-10-11 12:50:16 +00:00
|
|
|
{
|
|
|
|
// no need to load sound when we are running with no sound
|
2014-10-24 23:23:39 +00:00
|
|
|
if(!m_SoundEnabled && !FromEditor)
|
2014-10-11 12:50:16 +00:00
|
|
|
return -1;
|
|
|
|
|
|
|
|
if(!pData)
|
|
|
|
return -1;
|
|
|
|
|
2023-12-29 11:45:27 +00:00
|
|
|
CSample *pSample = AllocSample();
|
|
|
|
if(!pSample)
|
2014-10-11 12:50:16 +00:00
|
|
|
return -1;
|
|
|
|
|
2023-12-29 11:45:27 +00:00
|
|
|
if(!DecodeWV(*pSample, pData, DataSize))
|
|
|
|
{
|
|
|
|
UnloadSample(pSample->m_Index);
|
2023-03-17 09:39:17 +00:00
|
|
|
return -1;
|
2023-12-29 11:45:27 +00:00
|
|
|
}
|
2014-10-11 12:50:16 +00:00
|
|
|
|
2023-12-29 11:45:27 +00:00
|
|
|
RateConvert(*pSample);
|
|
|
|
return pSample->m_Index;
|
2014-10-11 12:50:16 +00:00
|
|
|
}
|
|
|
|
|
2024-03-05 14:44:09 +00:00
|
|
|
void CSound::UnloadSample(int SampleId)
|
2014-10-11 11:38:08 +00:00
|
|
|
{
|
2024-03-05 14:44:09 +00:00
|
|
|
if(SampleId == -1 || SampleId >= NUM_SAMPLES)
|
2014-10-11 11:38:08 +00:00
|
|
|
return;
|
|
|
|
|
2024-03-05 14:44:09 +00:00
|
|
|
Stop(SampleId);
|
2023-12-29 11:45:27 +00:00
|
|
|
|
|
|
|
// Free data
|
2024-03-05 14:44:09 +00:00
|
|
|
CSample &Sample = m_aSamples[SampleId];
|
2023-12-29 11:45:27 +00:00
|
|
|
free(Sample.m_pData);
|
|
|
|
Sample.m_pData = nullptr;
|
|
|
|
|
|
|
|
// Free slot
|
|
|
|
if(Sample.m_NextFreeSampleIndex == SAMPLE_INDEX_USED)
|
|
|
|
{
|
|
|
|
Sample.m_NextFreeSampleIndex = m_FirstFreeSampleIndex;
|
|
|
|
m_FirstFreeSampleIndex = Sample.m_Index;
|
|
|
|
}
|
2014-10-11 11:38:08 +00:00
|
|
|
}
|
|
|
|
|
2024-03-05 14:44:09 +00:00
|
|
|
float CSound::GetSampleTotalTime(int SampleId)
|
2014-10-11 11:55:13 +00:00
|
|
|
{
|
2024-03-05 14:44:09 +00:00
|
|
|
if(SampleId == -1 || SampleId >= NUM_SAMPLES)
|
2014-10-11 11:55:13 +00:00
|
|
|
return 0.0f;
|
|
|
|
|
2024-03-05 14:44:09 +00:00
|
|
|
return m_aSamples[SampleId].TotalTime();
|
2014-10-11 11:55:13 +00:00
|
|
|
}
|
|
|
|
|
2024-03-05 14:44:09 +00:00
|
|
|
float CSound::GetSampleCurrentTime(int SampleId)
|
2023-09-18 08:54:45 +00:00
|
|
|
{
|
2024-03-05 14:44:09 +00:00
|
|
|
if(SampleId == -1 || SampleId >= NUM_SAMPLES)
|
2023-09-18 08:54:45 +00:00
|
|
|
return 0.0f;
|
|
|
|
|
2023-12-29 11:35:45 +00:00
|
|
|
const CLockScope LockScope(m_SoundLock);
|
2024-03-05 14:44:09 +00:00
|
|
|
CSample *pSample = &m_aSamples[SampleId];
|
2023-12-29 11:35:45 +00:00
|
|
|
for(auto &Voice : m_aVoices)
|
2023-09-18 08:54:45 +00:00
|
|
|
{
|
2023-12-29 11:35:45 +00:00
|
|
|
if(Voice.m_pSample == pSample)
|
2023-09-18 08:54:45 +00:00
|
|
|
{
|
2023-12-29 11:35:45 +00:00
|
|
|
return Voice.m_Tick / (float)pSample->m_Rate;
|
2023-09-18 08:54:45 +00:00
|
|
|
}
|
|
|
|
}
|
2023-10-05 19:22:24 +00:00
|
|
|
|
2023-12-29 11:35:45 +00:00
|
|
|
return pSample->m_PausedAt / (float)pSample->m_Rate;
|
2023-09-18 08:54:45 +00:00
|
|
|
}
|
|
|
|
|
2024-03-05 14:44:09 +00:00
|
|
|
void CSound::SetSampleCurrentTime(int SampleId, float Time)
|
2023-09-18 08:54:45 +00:00
|
|
|
{
|
2024-03-05 14:44:09 +00:00
|
|
|
if(SampleId == -1 || SampleId >= NUM_SAMPLES)
|
2023-09-18 08:54:45 +00:00
|
|
|
return;
|
|
|
|
|
2023-12-29 11:35:45 +00:00
|
|
|
const CLockScope LockScope(m_SoundLock);
|
2024-03-05 14:44:09 +00:00
|
|
|
CSample *pSample = &m_aSamples[SampleId];
|
2023-12-29 11:35:45 +00:00
|
|
|
for(auto &Voice : m_aVoices)
|
2023-09-18 08:54:45 +00:00
|
|
|
{
|
2023-12-29 11:35:45 +00:00
|
|
|
if(Voice.m_pSample == pSample)
|
2023-09-18 08:54:45 +00:00
|
|
|
{
|
2023-12-29 11:35:45 +00:00
|
|
|
Voice.m_Tick = pSample->m_NumFrames * Time;
|
|
|
|
return;
|
2023-09-18 08:54:45 +00:00
|
|
|
}
|
|
|
|
}
|
2023-12-29 11:35:45 +00:00
|
|
|
|
|
|
|
pSample->m_PausedAt = pSample->m_NumFrames * Time;
|
2023-09-18 08:54:45 +00:00
|
|
|
}
|
|
|
|
|
2024-03-05 14:44:09 +00:00
|
|
|
void CSound::SetChannel(int ChannelId, float Vol, float Pan)
|
2023-09-21 21:02:53 +00:00
|
|
|
{
|
2024-03-05 14:44:09 +00:00
|
|
|
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
|
2023-09-21 21:02:53 +00:00
|
|
|
}
|
|
|
|
|
2010-05-29 07:25:38 +00:00
|
|
|
void CSound::SetListenerPos(float x, float y)
|
|
|
|
{
|
2021-10-28 11:46:53 +00:00
|
|
|
m_CenterX.store((int)x, std::memory_order_relaxed);
|
|
|
|
m_CenterY.store((int)y, std::memory_order_relaxed);
|
2010-05-29 07:25:38 +00:00
|
|
|
}
|
2011-04-13 18:00:54 +00:00
|
|
|
|
2014-10-12 14:12:13 +00:00
|
|
|
void CSound::SetVoiceVolume(CVoiceHandle Voice, float Volume)
|
2014-10-11 15:55:22 +00:00
|
|
|
{
|
2014-10-12 14:12:13 +00:00
|
|
|
if(!Voice.IsValid())
|
|
|
|
return;
|
|
|
|
|
2024-03-05 14:44:09 +00:00
|
|
|
int VoiceId = Voice.Id();
|
2014-10-12 14:12:13 +00:00
|
|
|
|
2023-11-10 20:49:42 +00:00
|
|
|
const CLockScope LockScope(m_SoundLock);
|
2024-03-05 14:44:09 +00:00
|
|
|
if(m_aVoices[VoiceId].m_Age != Voice.Age())
|
2014-10-11 15:55:22 +00:00
|
|
|
return;
|
|
|
|
|
|
|
|
Volume = clamp(Volume, 0.0f, 1.0f);
|
2024-03-05 14:44:09 +00:00
|
|
|
m_aVoices[VoiceId].m_Vol = (int)(Volume * 255.0f);
|
2014-10-11 15:55:22 +00:00
|
|
|
}
|
|
|
|
|
2014-11-29 13:29:40 +00:00
|
|
|
void CSound::SetVoiceFalloff(CVoiceHandle Voice, float Falloff)
|
|
|
|
{
|
|
|
|
if(!Voice.IsValid())
|
|
|
|
return;
|
|
|
|
|
2024-03-05 14:44:09 +00:00
|
|
|
int VoiceId = Voice.Id();
|
2014-11-29 13:29:40 +00:00
|
|
|
|
2023-11-10 20:49:42 +00:00
|
|
|
const CLockScope LockScope(m_SoundLock);
|
2024-03-05 14:44:09 +00:00
|
|
|
if(m_aVoices[VoiceId].m_Age != Voice.Age())
|
2014-11-29 13:29:40 +00:00
|
|
|
return;
|
|
|
|
|
|
|
|
Falloff = clamp(Falloff, 0.0f, 1.0f);
|
2024-03-05 14:44:09 +00:00
|
|
|
m_aVoices[VoiceId].m_Falloff = Falloff;
|
2014-11-29 13:29:40 +00:00
|
|
|
}
|
|
|
|
|
2014-10-12 14:12:13 +00:00
|
|
|
void CSound::SetVoiceLocation(CVoiceHandle Voice, float x, float y)
|
|
|
|
{
|
|
|
|
if(!Voice.IsValid())
|
|
|
|
return;
|
|
|
|
|
2024-03-05 14:44:09 +00:00
|
|
|
int VoiceId = Voice.Id();
|
2014-10-12 14:12:13 +00:00
|
|
|
|
2023-11-10 20:49:42 +00:00
|
|
|
const CLockScope LockScope(m_SoundLock);
|
2024-03-05 14:44:09 +00:00
|
|
|
if(m_aVoices[VoiceId].m_Age != Voice.Age())
|
2014-10-12 14:12:13 +00:00
|
|
|
return;
|
|
|
|
|
2024-03-05 14:44:09 +00:00
|
|
|
m_aVoices[VoiceId].m_X = x;
|
|
|
|
m_aVoices[VoiceId].m_Y = y;
|
2014-10-12 14:12:13 +00:00
|
|
|
}
|
|
|
|
|
2023-09-21 21:01:29 +00:00
|
|
|
void CSound::SetVoiceTimeOffset(CVoiceHandle Voice, float TimeOffset)
|
2014-10-18 13:21:13 +00:00
|
|
|
{
|
|
|
|
if(!Voice.IsValid())
|
|
|
|
return;
|
|
|
|
|
2024-03-05 14:44:09 +00:00
|
|
|
int VoiceId = Voice.Id();
|
2014-10-18 13:21:13 +00:00
|
|
|
|
2023-11-10 20:49:42 +00:00
|
|
|
const CLockScope LockScope(m_SoundLock);
|
2024-03-05 14:44:09 +00:00
|
|
|
if(m_aVoices[VoiceId].m_Age != Voice.Age())
|
2014-10-18 13:21:13 +00:00
|
|
|
return;
|
|
|
|
|
2024-03-05 14:44:09 +00:00
|
|
|
if(!m_aVoices[VoiceId].m_pSample)
|
2023-09-21 21:01:29 +00:00
|
|
|
return;
|
|
|
|
|
|
|
|
int Tick = 0;
|
2024-03-05 14:44:09 +00:00
|
|
|
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;
|
2023-09-21 21:01:29 +00:00
|
|
|
else
|
2024-03-05 14:44:09 +00:00
|
|
|
Tick = clamp(TickOffset, (uint64_t)0, (uint64_t)m_aVoices[VoiceId].m_pSample->m_NumFrames);
|
2023-09-21 21:01:29 +00:00
|
|
|
|
|
|
|
// at least 200msec off, else depend on buffer size
|
2024-03-05 14:44:09 +00:00
|
|
|
float Threshold = maximum(0.2f * m_aVoices[VoiceId].m_pSample->m_Rate, (float)m_MaxFrames);
|
|
|
|
if(absolute(m_aVoices[VoiceId].m_Tick - Tick) > Threshold)
|
2014-10-18 13:21:13 +00:00
|
|
|
{
|
2023-09-21 21:01:29 +00:00
|
|
|
// take care of looping (modulo!)
|
2024-03-05 14:44:09 +00:00
|
|
|
if(!(IsLooping && (minimum(m_aVoices[VoiceId].m_Tick, Tick) + m_aVoices[VoiceId].m_pSample->m_NumFrames - maximum(m_aVoices[VoiceId].m_Tick, Tick)) <= Threshold))
|
2014-10-18 13:21:13 +00:00
|
|
|
{
|
2024-03-05 14:44:09 +00:00
|
|
|
m_aVoices[VoiceId].m_Tick = Tick;
|
2014-10-18 13:21:13 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-11-29 13:29:40 +00:00
|
|
|
void CSound::SetVoiceCircle(CVoiceHandle Voice, float Radius)
|
|
|
|
{
|
|
|
|
if(!Voice.IsValid())
|
|
|
|
return;
|
|
|
|
|
2024-03-05 14:44:09 +00:00
|
|
|
int VoiceId = Voice.Id();
|
2014-11-29 13:29:40 +00:00
|
|
|
|
2023-11-10 20:49:42 +00:00
|
|
|
const CLockScope LockScope(m_SoundLock);
|
2024-03-05 14:44:09 +00:00
|
|
|
if(m_aVoices[VoiceId].m_Age != Voice.Age())
|
2014-11-29 13:29:40 +00:00
|
|
|
return;
|
|
|
|
|
2024-03-05 14:44:09 +00:00
|
|
|
m_aVoices[VoiceId].m_Shape = ISound::SHAPE_CIRCLE;
|
|
|
|
m_aVoices[VoiceId].m_Circle.m_Radius = maximum(0.0f, Radius);
|
2014-11-29 13:29:40 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void CSound::SetVoiceRectangle(CVoiceHandle Voice, float Width, float Height)
|
|
|
|
{
|
|
|
|
if(!Voice.IsValid())
|
|
|
|
return;
|
|
|
|
|
2024-03-05 14:44:09 +00:00
|
|
|
int VoiceId = Voice.Id();
|
2014-11-29 13:29:40 +00:00
|
|
|
|
2023-11-10 20:49:42 +00:00
|
|
|
const CLockScope LockScope(m_SoundLock);
|
2024-03-05 14:44:09 +00:00
|
|
|
if(m_aVoices[VoiceId].m_Age != Voice.Age())
|
2014-11-29 13:29:40 +00:00
|
|
|
return;
|
|
|
|
|
2024-03-05 14:44:09 +00:00
|
|
|
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);
|
2014-11-29 13:29:40 +00:00
|
|
|
}
|
|
|
|
|
2024-03-05 14:44:09 +00:00
|
|
|
ISound::CVoiceHandle CSound::Play(int ChannelId, int SampleId, int Flags, float x, float y)
|
2010-05-29 07:25:38 +00:00
|
|
|
{
|
2023-11-10 20:49:42 +00:00
|
|
|
const CLockScope LockScope(m_SoundLock);
|
2011-08-11 08:59:14 +00:00
|
|
|
|
2010-05-29 07:25:38 +00:00
|
|
|
// search for voice
|
2024-03-05 14:44:09 +00:00
|
|
|
int VoiceId = -1;
|
2022-06-26 19:39:48 +00:00
|
|
|
for(int i = 0; i < NUM_VOICES; i++)
|
2010-05-29 07:25:38 +00:00
|
|
|
{
|
2024-03-05 14:44:09 +00:00
|
|
|
int NextId = (m_NextVoice + i) % NUM_VOICES;
|
|
|
|
if(!m_aVoices[NextId].m_pSample)
|
2010-05-29 07:25:38 +00:00
|
|
|
{
|
2024-03-05 14:44:09 +00:00
|
|
|
VoiceId = NextId;
|
|
|
|
m_NextVoice = NextId + 1;
|
2010-05-29 07:25:38 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
2011-08-11 08:59:14 +00:00
|
|
|
|
2010-05-29 07:25:38 +00:00
|
|
|
// voice found, use it
|
2022-06-26 19:39:48 +00:00
|
|
|
int Age = -1;
|
2024-03-05 14:44:09 +00:00
|
|
|
if(VoiceId != -1)
|
2010-05-29 07:25:38 +00:00
|
|
|
{
|
2024-03-05 14:44:09 +00:00
|
|
|
m_aVoices[VoiceId].m_pSample = &m_aSamples[SampleId];
|
|
|
|
m_aVoices[VoiceId].m_pChannel = &m_aChannels[ChannelId];
|
2011-07-02 11:11:32 +00:00
|
|
|
if(Flags & FLAG_LOOP)
|
2023-09-18 08:54:45 +00:00
|
|
|
{
|
2024-03-05 14:44:09 +00:00
|
|
|
m_aVoices[VoiceId].m_Tick = m_aSamples[SampleId].m_PausedAt;
|
2023-09-18 08:54:45 +00:00
|
|
|
}
|
|
|
|
else if(Flags & FLAG_PREVIEW)
|
|
|
|
{
|
2024-03-05 14:44:09 +00:00
|
|
|
m_aVoices[VoiceId].m_Tick = m_aSamples[SampleId].m_PausedAt;
|
|
|
|
m_aSamples[SampleId].m_PausedAt = 0;
|
2023-09-18 08:54:45 +00:00
|
|
|
}
|
2011-07-02 11:11:32 +00:00
|
|
|
else
|
2023-09-18 08:54:45 +00:00
|
|
|
{
|
2024-03-05 14:44:09 +00:00
|
|
|
m_aVoices[VoiceId].m_Tick = 0;
|
2023-09-18 08:54:45 +00:00
|
|
|
}
|
2024-03-05 14:44:09 +00:00
|
|
|
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 = 1500;
|
|
|
|
Age = m_aVoices[VoiceId].m_Age;
|
2010-05-29 07:25:38 +00:00
|
|
|
}
|
2011-08-11 08:59:14 +00:00
|
|
|
|
2024-03-05 14:44:09 +00:00
|
|
|
return CreateVoiceHandle(VoiceId, Age);
|
2010-05-29 07:25:38 +00:00
|
|
|
}
|
|
|
|
|
2024-03-05 14:44:09 +00:00
|
|
|
ISound::CVoiceHandle CSound::PlayAt(int ChannelId, int SampleId, int Flags, float x, float y)
|
2010-05-29 07:25:38 +00:00
|
|
|
{
|
2024-03-05 14:44:09 +00:00
|
|
|
return Play(ChannelId, SampleId, Flags | ISound::FLAG_POS, x, y);
|
2010-05-29 07:25:38 +00:00
|
|
|
}
|
|
|
|
|
2024-03-05 14:44:09 +00:00
|
|
|
ISound::CVoiceHandle CSound::Play(int ChannelId, int SampleId, int Flags)
|
2010-05-29 07:25:38 +00:00
|
|
|
{
|
2024-03-05 14:44:09 +00:00
|
|
|
return Play(ChannelId, SampleId, Flags, 0, 0);
|
2010-05-29 07:25:38 +00:00
|
|
|
}
|
|
|
|
|
2024-03-05 14:44:09 +00:00
|
|
|
void CSound::Pause(int SampleId)
|
2023-09-18 08:54:45 +00:00
|
|
|
{
|
|
|
|
// TODO: a nice fade out
|
2023-11-10 20:49:42 +00:00
|
|
|
const CLockScope LockScope(m_SoundLock);
|
2024-03-05 14:44:09 +00:00
|
|
|
CSample *pSample = &m_aSamples[SampleId];
|
2023-09-18 08:54:45 +00:00
|
|
|
for(auto &Voice : m_aVoices)
|
|
|
|
{
|
|
|
|
if(Voice.m_pSample == pSample)
|
|
|
|
{
|
|
|
|
Voice.m_pSample->m_PausedAt = Voice.m_Tick;
|
2023-10-05 19:22:24 +00:00
|
|
|
Voice.m_pSample = nullptr;
|
2023-09-18 08:54:45 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-03-05 14:44:09 +00:00
|
|
|
void CSound::Stop(int SampleId)
|
2010-05-29 07:25:38 +00:00
|
|
|
{
|
|
|
|
// TODO: a nice fade out
|
2023-11-10 20:49:42 +00:00
|
|
|
const CLockScope LockScope(m_SoundLock);
|
2024-03-05 14:44:09 +00:00
|
|
|
CSample *pSample = &m_aSamples[SampleId];
|
2020-10-26 14:14:07 +00:00
|
|
|
for(auto &Voice : m_aVoices)
|
2011-04-13 18:00:54 +00:00
|
|
|
{
|
2020-10-26 14:14:07 +00:00
|
|
|
if(Voice.m_pSample == pSample)
|
2011-07-02 11:11:32 +00:00
|
|
|
{
|
2020-10-26 14:14:07 +00:00
|
|
|
if(Voice.m_Flags & FLAG_LOOP)
|
|
|
|
Voice.m_pSample->m_PausedAt = Voice.m_Tick;
|
2011-07-02 11:11:32 +00:00
|
|
|
else
|
2020-10-26 14:14:07 +00:00
|
|
|
Voice.m_pSample->m_PausedAt = 0;
|
2023-09-21 20:59:46 +00:00
|
|
|
Voice.m_pSample = nullptr;
|
2011-07-02 11:11:32 +00:00
|
|
|
}
|
2011-04-13 18:00:54 +00:00
|
|
|
}
|
2010-05-29 07:25:38 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void CSound::StopAll()
|
|
|
|
{
|
|
|
|
// TODO: a nice fade out
|
2023-11-10 20:49:42 +00:00
|
|
|
const CLockScope LockScope(m_SoundLock);
|
2020-10-26 14:14:07 +00:00
|
|
|
for(auto &Voice : m_aVoices)
|
2010-05-29 07:25:38 +00:00
|
|
|
{
|
2020-10-26 14:14:07 +00:00
|
|
|
if(Voice.m_pSample)
|
2011-07-02 11:11:32 +00:00
|
|
|
{
|
2020-10-26 14:14:07 +00:00
|
|
|
if(Voice.m_Flags & FLAG_LOOP)
|
|
|
|
Voice.m_pSample->m_PausedAt = Voice.m_Tick;
|
2011-07-02 11:11:32 +00:00
|
|
|
else
|
2020-10-26 14:14:07 +00:00
|
|
|
Voice.m_pSample->m_PausedAt = 0;
|
2011-07-02 11:11:32 +00:00
|
|
|
}
|
2023-09-21 20:59:46 +00:00
|
|
|
Voice.m_pSample = nullptr;
|
2010-05-29 07:25:38 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-10-18 13:21:13 +00:00
|
|
|
void CSound::StopVoice(CVoiceHandle Voice)
|
|
|
|
{
|
|
|
|
if(!Voice.IsValid())
|
|
|
|
return;
|
|
|
|
|
2024-03-05 14:44:09 +00:00
|
|
|
int VoiceId = Voice.Id();
|
2014-10-18 13:21:13 +00:00
|
|
|
|
2023-11-10 20:49:42 +00:00
|
|
|
const CLockScope LockScope(m_SoundLock);
|
2024-03-05 14:44:09 +00:00
|
|
|
if(m_aVoices[VoiceId].m_Age != Voice.Age())
|
2014-10-18 13:21:13 +00:00
|
|
|
return;
|
|
|
|
|
2024-03-05 14:44:09 +00:00
|
|
|
m_aVoices[VoiceId].m_pSample = nullptr;
|
|
|
|
m_aVoices[VoiceId].m_Age++;
|
2014-10-18 13:21:13 +00:00
|
|
|
}
|
|
|
|
|
2024-03-05 14:44:09 +00:00
|
|
|
bool CSound::IsPlaying(int SampleId)
|
2022-06-26 19:35:36 +00:00
|
|
|
{
|
2023-11-10 20:49:42 +00:00
|
|
|
const CLockScope LockScope(m_SoundLock);
|
2024-03-05 14:44:09 +00:00
|
|
|
const CSample *pSample = &m_aSamples[SampleId];
|
2022-06-26 19:35:36 +00:00
|
|
|
return std::any_of(std::begin(m_aVoices), std::end(m_aVoices), [pSample](const auto &Voice) { return Voice.m_pSample == pSample; });
|
|
|
|
}
|
|
|
|
|
2022-03-02 08:32:51 +00:00
|
|
|
void CSound::PauseAudioDevice()
|
|
|
|
{
|
|
|
|
SDL_PauseAudioDevice(m_Device, 1);
|
|
|
|
}
|
|
|
|
|
|
|
|
void CSound::UnpauseAudioDevice()
|
|
|
|
{
|
|
|
|
SDL_PauseAudioDevice(m_Device, 0);
|
|
|
|
}
|
|
|
|
|
2010-05-29 07:25:38 +00:00
|
|
|
IEngineSound *CreateEngineSound() { return new CSound; }
|