mirror of
https://github.com/KartKrewDev/RingRacers.git
synced 2025-10-30 08:01:28 +00:00
1098 lines
22 KiB
C++
1098 lines
22 KiB
C++
// DR. ROBOTNIK'S RING RACERS
|
|
//-----------------------------------------------------------------------------
|
|
// Copyright (C) 2025 by Ronald "Eidolon" Kinard
|
|
// Copyright (C) 2025 by Kart Krew
|
|
//
|
|
// This program is free software distributed under the
|
|
// terms of the GNU General Public License, version 2.
|
|
// See the 'LICENSE' file for more details.
|
|
//-----------------------------------------------------------------------------
|
|
|
|
#include <algorithm>
|
|
#include <cmath>
|
|
#include <memory>
|
|
|
|
#include <SDL.h>
|
|
#include <tracy/tracy/Tracy.hpp>
|
|
|
|
#include "../audio/chunk_load.hpp"
|
|
#include "../audio/gain.hpp"
|
|
#include "../audio/mixer.hpp"
|
|
#include "../audio/music_player.hpp"
|
|
#include "../audio/resample.hpp"
|
|
#include "../audio/sound_chunk.hpp"
|
|
#include "../audio/sound_effect_player.hpp"
|
|
#include "../cxxutil.hpp"
|
|
#include "../io/streams.hpp"
|
|
|
|
#ifdef SRB2_CONFIG_ENABLE_WEBM_MOVIES
|
|
#include "../m_avrecorder.hpp"
|
|
#endif
|
|
|
|
#include "../doomdef.h"
|
|
#include "../i_sound.h"
|
|
#include "../s_sound.h"
|
|
#include "../sounds.h"
|
|
#include "../w_wad.h"
|
|
#include "../z_zone.h"
|
|
|
|
using std::make_shared;
|
|
using std::make_unique;
|
|
using std::shared_ptr;
|
|
using std::unique_ptr;
|
|
using std::vector;
|
|
|
|
using srb2::audio::Gain;
|
|
using srb2::audio::Mixer;
|
|
using srb2::audio::MusicPlayer;
|
|
using srb2::audio::Resampler;
|
|
using srb2::audio::Sample;
|
|
using srb2::audio::SoundChunk;
|
|
using srb2::audio::SoundEffectPlayer;
|
|
using srb2::audio::Source;
|
|
using namespace srb2;
|
|
using namespace srb2::io;
|
|
|
|
namespace
|
|
{
|
|
class SdlAudioStream final
|
|
{
|
|
SDL_AudioStream* stream_;
|
|
public:
|
|
SdlAudioStream(const SDL_AudioFormat format, const Uint8 channels, const int src_rate, const SDL_AudioFormat dst_format, const Uint8 dst_channels, const int dst_rate) noexcept
|
|
{
|
|
stream_ = SDL_NewAudioStream(format, channels, src_rate, dst_format, dst_channels, dst_rate);
|
|
}
|
|
SdlAudioStream(const SdlAudioStream&) = delete;
|
|
SdlAudioStream(SdlAudioStream&&) = default;
|
|
SdlAudioStream& operator=(const SdlAudioStream&) = delete;
|
|
SdlAudioStream& operator=(SdlAudioStream&&) = default;
|
|
~SdlAudioStream()
|
|
{
|
|
SDL_FreeAudioStream(stream_);
|
|
}
|
|
|
|
void put(tcb::span<const std::byte> buf)
|
|
{
|
|
int result = SDL_AudioStreamPut(stream_, buf.data(), buf.size_bytes());
|
|
if (result < 0)
|
|
{
|
|
char errbuf[512];
|
|
SDL_GetErrorMsg(errbuf, sizeof(errbuf));
|
|
throw std::runtime_error(errbuf);
|
|
}
|
|
}
|
|
|
|
size_t available() const
|
|
{
|
|
int result = SDL_AudioStreamAvailable(stream_);
|
|
if (result < 0)
|
|
{
|
|
char errbuf[512];
|
|
SDL_GetErrorMsg(errbuf, sizeof(errbuf));
|
|
throw std::runtime_error(errbuf);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
size_t get(tcb::span<std::byte> out)
|
|
{
|
|
int result = SDL_AudioStreamGet(stream_, out.data(), out.size_bytes());
|
|
if (result < 0)
|
|
{
|
|
char errbuf[512];
|
|
SDL_GetErrorMsg(errbuf, sizeof(errbuf));
|
|
throw std::runtime_error(errbuf);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
void clear() noexcept
|
|
{
|
|
SDL_AudioStreamClear(stream_);
|
|
}
|
|
};
|
|
|
|
class SdlVoiceStreamPlayer : public Source<2>
|
|
{
|
|
SdlAudioStream stream_;
|
|
float volume_ = 1.0f;
|
|
float sep_ = 0.0f;
|
|
bool terminal_ = true;
|
|
|
|
public:
|
|
SdlVoiceStreamPlayer() : stream_(AUDIO_F32SYS, 1, 48000, AUDIO_F32SYS, 2, 44100) {}
|
|
virtual ~SdlVoiceStreamPlayer() = default;
|
|
|
|
virtual std::size_t generate(tcb::span<Sample<2>> buffer) override
|
|
{
|
|
size_t written = stream_.get(tcb::as_writable_bytes(buffer)) / sizeof(Sample<2>);
|
|
|
|
for (size_t i = written; i < buffer.size(); i++)
|
|
{
|
|
buffer[i] = {0.f, 0.f};
|
|
}
|
|
|
|
// Apply gain de-popping if the last generation was terminal
|
|
if (terminal_)
|
|
{
|
|
for (size_t i = 0; i < std::min<size_t>(16, written); i++)
|
|
{
|
|
buffer[i].amplitudes[0] *= (float)(i) / 16;
|
|
buffer[i].amplitudes[1] *= (float)(i) / 16;
|
|
}
|
|
terminal_ = false;
|
|
}
|
|
|
|
if (written < buffer.size())
|
|
{
|
|
terminal_ = true;
|
|
}
|
|
|
|
for (size_t i = 0; i < written; i++)
|
|
{
|
|
float sep_pan = ((sep_ + 1.f) / 2.f) * (3.14159 / 2.f);
|
|
|
|
float left_scale = std::cos(sep_pan);
|
|
float right_scale = std::sin(sep_pan);
|
|
buffer[i] = {std::clamp(buffer[i].amplitudes[0] * volume_ * left_scale, -1.f, 1.f), std::clamp(buffer[i].amplitudes[1] * volume_ * right_scale, -1.f, 1.f)};
|
|
}
|
|
|
|
return buffer.size();
|
|
};
|
|
|
|
SdlAudioStream& stream() noexcept { return stream_; }
|
|
|
|
void set_properties(float volume, float sep) noexcept
|
|
{
|
|
volume_ = volume;
|
|
sep_ = sep;
|
|
}
|
|
};
|
|
|
|
} // namespace
|
|
|
|
// extern in i_sound.h
|
|
UINT8 sound_started = false;
|
|
|
|
static unique_ptr<Gain<2>> master_gain;
|
|
static shared_ptr<Mixer<2>> master;
|
|
static shared_ptr<Mixer<2>> mixer_sound_effects;
|
|
static shared_ptr<Mixer<2>> mixer_music;
|
|
static shared_ptr<Mixer<2>> mixer_voice;
|
|
static shared_ptr<MusicPlayer> music_player;
|
|
static shared_ptr<Resampler<2>> resample_music_player;
|
|
static shared_ptr<Gain<2>> gain_sound_effects;
|
|
static shared_ptr<Gain<2>> gain_music_player;
|
|
static shared_ptr<Gain<2>> gain_music_channel;
|
|
static shared_ptr<Gain<2>> gain_voice_channel;
|
|
|
|
static vector<shared_ptr<SoundEffectPlayer>> sound_effect_channels;
|
|
static vector<shared_ptr<SdlVoiceStreamPlayer>> player_voice_channels;
|
|
|
|
#ifdef SRB2_CONFIG_ENABLE_WEBM_MOVIES
|
|
static shared_ptr<srb2::media::AVRecorder> av_recorder;
|
|
#endif
|
|
|
|
static void (*music_fade_callback)();
|
|
|
|
static SDL_AudioDeviceID g_device_id;
|
|
static SDL_AudioDeviceID g_input_device_id;
|
|
|
|
void* I_GetSfx(sfxinfo_t* sfx)
|
|
{
|
|
if (sfx->lumpnum == LUMPERROR)
|
|
sfx->lumpnum = S_GetSfxLumpNum(sfx);
|
|
sfx->length = W_LumpLength(sfx->lumpnum);
|
|
|
|
std::byte* lump = static_cast<std::byte*>(W_CacheLumpNum(sfx->lumpnum, PU_SOUND));
|
|
auto _ = srb2::finally([lump]() { Z_Free(lump); });
|
|
|
|
tcb::span<std::byte> data_span(lump, sfx->length);
|
|
std::optional<SoundChunk> chunk = srb2::audio::try_load_chunk(data_span);
|
|
|
|
if (!chunk)
|
|
return nullptr;
|
|
|
|
SoundChunk* heap_chunk = new SoundChunk {std::move(*chunk)};
|
|
|
|
return heap_chunk;
|
|
}
|
|
|
|
void I_FreeSfx(sfxinfo_t* sfx)
|
|
{
|
|
if (sfx->data)
|
|
{
|
|
SoundChunk* chunk = static_cast<SoundChunk*>(sfx->data);
|
|
auto _ = srb2::finally([chunk]() { delete chunk; });
|
|
|
|
// Stop any channels playing this chunk
|
|
for (auto& player : sound_effect_channels)
|
|
{
|
|
if (player->is_playing_chunk(chunk))
|
|
{
|
|
player->reset();
|
|
}
|
|
}
|
|
}
|
|
sfx->data = nullptr;
|
|
sfx->lumpnum = LUMPERROR;
|
|
}
|
|
|
|
namespace
|
|
{
|
|
|
|
class SdlAudioLockHandle
|
|
{
|
|
public:
|
|
SdlAudioLockHandle() { SDL_LockAudioDevice(g_device_id); }
|
|
~SdlAudioLockHandle() { SDL_UnlockAudioDevice(g_device_id); }
|
|
};
|
|
|
|
#ifdef TRACY_ENABLE
|
|
static const char* kAudio = "Audio";
|
|
#endif
|
|
|
|
void audio_callback(void* userdata, Uint8* buffer, int len)
|
|
{
|
|
tracy::SetThreadName("SDL Audio Thread");
|
|
FrameMarkStart(kAudio);
|
|
ZoneScoped;
|
|
|
|
// The SDL Audio lock is implied to be held during callback.
|
|
|
|
try
|
|
{
|
|
Sample<2>* float_buffer = reinterpret_cast<Sample<2>*>(buffer);
|
|
size_t float_len = len / 8;
|
|
|
|
for (size_t i = 0; i < float_len; i++)
|
|
{
|
|
float_buffer[i] = Sample<2> {0.f, 0.f};
|
|
}
|
|
|
|
if (!master_gain)
|
|
return;
|
|
|
|
master_gain->generate(tcb::span {float_buffer, float_len});
|
|
|
|
for (size_t i = 0; i < float_len; i++)
|
|
{
|
|
float_buffer[i] = {
|
|
std::clamp(float_buffer[i].amplitudes[0], -1.f, 1.f),
|
|
std::clamp(float_buffer[i].amplitudes[1], -1.f, 1.f),
|
|
};
|
|
}
|
|
#ifdef SRB2_CONFIG_ENABLE_WEBM_MOVIES
|
|
if (av_recorder)
|
|
av_recorder->push_audio_samples(tcb::span {float_buffer, float_len});
|
|
#endif
|
|
}
|
|
catch (...)
|
|
{
|
|
}
|
|
|
|
FrameMarkEnd(kAudio);
|
|
|
|
return;
|
|
}
|
|
|
|
void initialize_sound()
|
|
{
|
|
if (SDL_InitSubSystem(SDL_INIT_AUDIO) < 0)
|
|
{
|
|
CONS_Alert(CONS_ERROR, "Error initializing SDL Audio: %s\n", SDL_GetError());
|
|
return;
|
|
}
|
|
|
|
SDL_AudioSpec desired {};
|
|
desired.format = AUDIO_F32SYS;
|
|
desired.channels = 2;
|
|
desired.samples = cv_soundmixingbuffersize.value;
|
|
desired.freq = 44100;
|
|
desired.callback = audio_callback;
|
|
|
|
if ((g_device_id = SDL_OpenAudioDevice(NULL, SDL_FALSE, &desired, NULL, 0)) == 0)
|
|
{
|
|
CONS_Alert(CONS_ERROR, "Failed to open SDL Audio device: %s\n", SDL_GetError());
|
|
SDL_QuitSubSystem(SDL_INIT_AUDIO);
|
|
return;
|
|
}
|
|
|
|
SDL_PauseAudioDevice(g_device_id, SDL_FALSE);
|
|
|
|
{
|
|
SdlAudioLockHandle _;
|
|
|
|
master_gain = make_unique<Gain<2>>();
|
|
master = make_shared<Mixer<2>>();
|
|
master_gain->bind(master);
|
|
mixer_sound_effects = make_shared<Mixer<2>>();
|
|
mixer_music = make_shared<Mixer<2>>();
|
|
mixer_voice = make_shared<Mixer<2>>();
|
|
music_player = make_shared<MusicPlayer>();
|
|
resample_music_player = make_shared<Resampler<2>>(music_player, 1.f);
|
|
gain_sound_effects = make_shared<Gain<2>>();
|
|
gain_music_player = make_shared<Gain<2>>();
|
|
gain_music_channel = make_shared<Gain<2>>();
|
|
gain_voice_channel = make_shared<Gain<2>>();
|
|
gain_sound_effects->bind(mixer_sound_effects);
|
|
gain_music_player->bind(resample_music_player);
|
|
gain_music_channel->bind(mixer_music);
|
|
gain_voice_channel->bind(mixer_voice);
|
|
master->add_source(gain_sound_effects);
|
|
master->add_source(gain_music_channel);
|
|
master->add_source(gain_voice_channel);
|
|
mixer_music->add_source(gain_music_player);
|
|
sound_effect_channels.clear();
|
|
for (size_t i = 0; i < static_cast<size_t>(cv_numChannels.value); i++)
|
|
{
|
|
shared_ptr<SoundEffectPlayer> player = make_shared<SoundEffectPlayer>();
|
|
sound_effect_channels.push_back(player);
|
|
mixer_sound_effects->add_source(player);
|
|
}
|
|
player_voice_channels.clear();
|
|
for (size_t i = 0; i < MAXPLAYERS; i++)
|
|
{
|
|
shared_ptr<SdlVoiceStreamPlayer> player = make_shared<SdlVoiceStreamPlayer>();
|
|
player_voice_channels.push_back(player);
|
|
mixer_voice->add_source(player);
|
|
}
|
|
}
|
|
|
|
sound_started = true;
|
|
}
|
|
|
|
} // namespace
|
|
|
|
void I_StartupSound(void)
|
|
{
|
|
if (!sound_started)
|
|
initialize_sound();
|
|
}
|
|
|
|
void I_ShutdownSound(void)
|
|
{
|
|
if (g_device_id)
|
|
{
|
|
SDL_CloseAudioDevice(g_device_id);
|
|
g_device_id = 0;
|
|
}
|
|
if (g_input_device_id)
|
|
{
|
|
SDL_CloseAudioDevice(g_input_device_id);
|
|
g_input_device_id = 0;
|
|
}
|
|
|
|
master_gain = nullptr;
|
|
master = nullptr;
|
|
mixer_sound_effects = nullptr;
|
|
mixer_music = nullptr;
|
|
mixer_voice = nullptr;
|
|
music_player = nullptr;
|
|
resample_music_player = nullptr;
|
|
gain_sound_effects = nullptr;
|
|
gain_music_player = nullptr;
|
|
gain_music_channel = nullptr;
|
|
gain_voice_channel = nullptr;
|
|
sound_effect_channels.clear();
|
|
player_voice_channels.clear();
|
|
|
|
SDL_QuitSubSystem(SDL_INIT_AUDIO);
|
|
|
|
sound_started = false;
|
|
}
|
|
|
|
void I_UpdateSound(void)
|
|
{
|
|
// The SDL audio lock is re-entrant, so it is safe to lock twice
|
|
// for the "fade to stop music" callback later.
|
|
SdlAudioLockHandle _;
|
|
|
|
if (music_fade_callback && !music_player->fading())
|
|
{
|
|
auto old_callback = music_fade_callback;
|
|
music_fade_callback = nullptr;
|
|
(old_callback());
|
|
}
|
|
return;
|
|
}
|
|
|
|
//
|
|
// SFX I/O
|
|
//
|
|
|
|
INT32 I_StartSound(sfxenum_t id, UINT8 vol, UINT8 sep, UINT8 pitch, UINT8 priority, INT32 channel)
|
|
{
|
|
(void) pitch;
|
|
(void) priority;
|
|
|
|
SdlAudioLockHandle _;
|
|
|
|
if (channel >= 0 && static_cast<size_t>(channel) >= sound_effect_channels.size())
|
|
return -1;
|
|
|
|
shared_ptr<SoundEffectPlayer> player_channel;
|
|
if (channel < 0)
|
|
{
|
|
// find a free sfx channel
|
|
for (size_t i = 0; i < sound_effect_channels.size(); i++)
|
|
{
|
|
if (sound_effect_channels[i]->finished())
|
|
{
|
|
player_channel = sound_effect_channels[i];
|
|
channel = i;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
player_channel = sound_effect_channels[channel];
|
|
}
|
|
|
|
if (!player_channel)
|
|
return -1;
|
|
|
|
SoundChunk* chunk = static_cast<SoundChunk*>(S_sfx[id].data);
|
|
if (chunk == nullptr)
|
|
return -1;
|
|
|
|
float vol_float = static_cast<float>(vol) / 255.f;
|
|
float sep_float = static_cast<float>(sep) / 127.f - 1.f;
|
|
|
|
player_channel->start(chunk, vol_float, sep_float);
|
|
|
|
return channel;
|
|
}
|
|
|
|
void I_StopSound(INT32 handle)
|
|
{
|
|
SdlAudioLockHandle _;
|
|
|
|
if (sound_effect_channels.empty())
|
|
return;
|
|
|
|
if (handle < 0)
|
|
return;
|
|
|
|
size_t index = handle;
|
|
|
|
if (index >= sound_effect_channels.size())
|
|
return;
|
|
|
|
sound_effect_channels[index]->reset();
|
|
}
|
|
|
|
boolean I_SoundIsPlaying(INT32 handle)
|
|
{
|
|
SdlAudioLockHandle _;
|
|
|
|
// Handle is channel index
|
|
if (sound_effect_channels.empty())
|
|
return 0;
|
|
|
|
if (handle < 0)
|
|
return 0;
|
|
|
|
size_t index = handle;
|
|
|
|
if (index >= sound_effect_channels.size())
|
|
return 0;
|
|
|
|
return sound_effect_channels[index]->finished() ? 0 : 1;
|
|
}
|
|
|
|
void I_UpdateSoundParams(INT32 handle, UINT8 vol, UINT8 sep, UINT8 pitch)
|
|
{
|
|
(void) pitch;
|
|
|
|
SdlAudioLockHandle _;
|
|
|
|
if (sound_effect_channels.empty())
|
|
return;
|
|
|
|
if (handle < 0)
|
|
return;
|
|
|
|
size_t index = handle;
|
|
|
|
if (index >= sound_effect_channels.size())
|
|
return;
|
|
|
|
shared_ptr<SoundEffectPlayer>& channel = sound_effect_channels[index];
|
|
if (!channel->finished())
|
|
{
|
|
float vol_float = static_cast<float>(vol) / 255.f;
|
|
float sep_float = static_cast<float>(sep) / 127.f - 1.f;
|
|
channel->update(vol_float, sep_float);
|
|
}
|
|
}
|
|
|
|
void I_SetSfxVolume(int volume)
|
|
{
|
|
SdlAudioLockHandle _;
|
|
float vol = static_cast<float>(volume) / 100.f;
|
|
|
|
if (gain_sound_effects)
|
|
{
|
|
gain_sound_effects->gain(std::clamp(vol * vol * vol, 0.f, 1.f));
|
|
}
|
|
}
|
|
|
|
void I_SetVoiceVolume(int volume)
|
|
{
|
|
SdlAudioLockHandle _;
|
|
float vol = static_cast<float>(volume) / 100.f;
|
|
|
|
if (gain_voice_channel)
|
|
{
|
|
gain_voice_channel->gain(std::clamp(vol * vol * vol, 0.f, 1.f));
|
|
}
|
|
}
|
|
|
|
void I_SetMasterVolume(int volume)
|
|
{
|
|
SdlAudioLockHandle _;
|
|
float vol = static_cast<float>(volume) / 100.f;
|
|
|
|
if (master_gain)
|
|
{
|
|
master_gain->gain(std::clamp(vol * vol * vol, 0.f, 1.f));
|
|
}
|
|
}
|
|
|
|
/// ------------------------
|
|
// MUSIC SYSTEM
|
|
/// ------------------------
|
|
|
|
void I_InitMusic(void)
|
|
{
|
|
if (!sound_started)
|
|
initialize_sound();
|
|
|
|
SdlAudioLockHandle _;
|
|
|
|
if (music_player != nullptr)
|
|
*music_player = audio::MusicPlayer();
|
|
}
|
|
|
|
void I_ShutdownMusic(void)
|
|
{
|
|
SdlAudioLockHandle _;
|
|
|
|
if (music_player)
|
|
*music_player = audio::MusicPlayer();
|
|
}
|
|
|
|
/// ------------------------
|
|
// MUSIC PROPERTIES
|
|
/// ------------------------
|
|
|
|
const char* I_SongType(void)
|
|
{
|
|
if (!music_player)
|
|
return nullptr;
|
|
|
|
SdlAudioLockHandle _;
|
|
|
|
std::optional<audio::MusicType> music_type = music_player->music_type();
|
|
|
|
if (music_type == std::nullopt)
|
|
{
|
|
return nullptr;
|
|
}
|
|
|
|
switch (*music_type)
|
|
{
|
|
case audio::MusicType::kOgg:
|
|
return "OGG";
|
|
case audio::MusicType::kMod:
|
|
return "Mod";
|
|
default:
|
|
return nullptr;
|
|
}
|
|
}
|
|
|
|
boolean I_SongPlaying(void)
|
|
{
|
|
if (!music_player)
|
|
return false;
|
|
|
|
SdlAudioLockHandle _;
|
|
|
|
return music_player->music_type().has_value();
|
|
}
|
|
|
|
boolean I_SongPaused(void)
|
|
{
|
|
if (!music_player)
|
|
return false;
|
|
|
|
SdlAudioLockHandle _;
|
|
|
|
return !music_player->playing();
|
|
}
|
|
|
|
/// ------------------------
|
|
// MUSIC EFFECTS
|
|
/// ------------------------
|
|
|
|
boolean I_SetSongSpeed(float speed)
|
|
{
|
|
if (resample_music_player)
|
|
{
|
|
resample_music_player->ratio(speed);
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/// ------------------------
|
|
// MUSIC SEEKING
|
|
/// ------------------------
|
|
|
|
UINT32 I_GetSongLength(void)
|
|
{
|
|
if (!music_player)
|
|
return 0;
|
|
|
|
SdlAudioLockHandle _;
|
|
|
|
std::optional<float> duration = music_player->duration_seconds();
|
|
|
|
if (!duration)
|
|
return 0;
|
|
|
|
return static_cast<UINT32>(std::round(*duration * 1000.f));
|
|
}
|
|
|
|
boolean I_SetSongLoopPoint(UINT32 looppoint)
|
|
{
|
|
if (!music_player)
|
|
return 0;
|
|
|
|
SdlAudioLockHandle _;
|
|
|
|
if (music_player->music_type() == audio::MusicType::kOgg)
|
|
{
|
|
music_player->loop_point_seconds(looppoint / 1000.f);
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
UINT32 I_GetSongLoopPoint(void)
|
|
{
|
|
if (!music_player)
|
|
return 0;
|
|
|
|
SdlAudioLockHandle _;
|
|
|
|
std::optional<float> loop_point_seconds = music_player->loop_point_seconds();
|
|
|
|
if (!loop_point_seconds)
|
|
return 0;
|
|
|
|
return static_cast<UINT32>(std::round(*loop_point_seconds * 1000.f));
|
|
}
|
|
|
|
boolean I_SetSongPosition(UINT32 position)
|
|
{
|
|
if (!music_player)
|
|
return false;
|
|
|
|
SdlAudioLockHandle _;
|
|
|
|
music_player->seek(position / 1000.f);
|
|
return true;
|
|
}
|
|
|
|
UINT32 I_GetSongPosition(void)
|
|
{
|
|
if (!music_player)
|
|
return 0;
|
|
|
|
SdlAudioLockHandle _;
|
|
|
|
std::optional<float> position_seconds = music_player->position_seconds();
|
|
|
|
if (!position_seconds)
|
|
return 0;
|
|
|
|
return static_cast<UINT32>(std::round(*position_seconds * 1000.f));
|
|
}
|
|
|
|
void I_UpdateSongLagThreshold(void)
|
|
{
|
|
}
|
|
|
|
void I_UpdateSongLagConditions(void)
|
|
{
|
|
}
|
|
|
|
/// ------------------------
|
|
// MUSIC PLAYBACK
|
|
/// ------------------------
|
|
|
|
namespace
|
|
{
|
|
void print_walk_ex_stack(const std::exception& ex)
|
|
{
|
|
CONS_Alert(CONS_WARNING, " Caused by: %s\n", ex.what());
|
|
try
|
|
{
|
|
std::rethrow_if_nested(ex);
|
|
}
|
|
catch (const std::exception& ex)
|
|
{
|
|
print_walk_ex_stack(ex);
|
|
}
|
|
}
|
|
|
|
void print_ex(const std::exception& ex)
|
|
{
|
|
CONS_Alert(CONS_WARNING, "Exception loading music: %s\n", ex.what());
|
|
try
|
|
{
|
|
std::rethrow_if_nested(ex);
|
|
}
|
|
catch (const std::exception& ex)
|
|
{
|
|
print_walk_ex_stack(ex);
|
|
}
|
|
}
|
|
} // namespace
|
|
|
|
boolean I_LoadSong(char* data, size_t len)
|
|
{
|
|
if (!music_player)
|
|
return false;
|
|
|
|
tcb::span<std::byte> data_span(reinterpret_cast<std::byte*>(data), len);
|
|
audio::MusicPlayer new_player;
|
|
try
|
|
{
|
|
new_player = audio::MusicPlayer {data_span};
|
|
}
|
|
catch (const std::exception& ex)
|
|
{
|
|
print_ex(ex);
|
|
return false;
|
|
}
|
|
|
|
if (music_fade_callback && music_player->fading())
|
|
{
|
|
auto old_callback = music_fade_callback;
|
|
music_fade_callback = nullptr;
|
|
(old_callback)();
|
|
}
|
|
|
|
SdlAudioLockHandle _;
|
|
|
|
try
|
|
{
|
|
*music_player = std::move(new_player);
|
|
}
|
|
catch (const std::exception& ex)
|
|
{
|
|
print_ex(ex);
|
|
return false;
|
|
}
|
|
|
|
if (gain_music_player)
|
|
{
|
|
// Reset song volume to 1.0 for newly loaded songs.
|
|
gain_music_player->gain(1.0);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void I_UnloadSong(void)
|
|
{
|
|
if (!music_player)
|
|
return;
|
|
|
|
if (music_fade_callback && music_player->fading())
|
|
{
|
|
auto old_callback = music_fade_callback;
|
|
music_fade_callback = nullptr;
|
|
(old_callback)();
|
|
}
|
|
|
|
SdlAudioLockHandle _;
|
|
|
|
*music_player = audio::MusicPlayer();
|
|
}
|
|
|
|
boolean I_PlaySong(boolean looping)
|
|
{
|
|
if (!music_player)
|
|
return false;
|
|
|
|
SdlAudioLockHandle _;
|
|
|
|
music_player->play(looping);
|
|
|
|
return true;
|
|
}
|
|
|
|
void I_StopSong(void)
|
|
{
|
|
if (!music_player)
|
|
return;
|
|
|
|
SdlAudioLockHandle _;
|
|
|
|
music_player->stop();
|
|
}
|
|
|
|
void I_PauseSong(void)
|
|
{
|
|
if (!music_player)
|
|
return;
|
|
|
|
SdlAudioLockHandle _;
|
|
|
|
music_player->pause();
|
|
}
|
|
|
|
void I_ResumeSong(void)
|
|
{
|
|
if (!music_player)
|
|
return;
|
|
|
|
SdlAudioLockHandle _;
|
|
|
|
music_player->unpause();
|
|
}
|
|
|
|
void I_SetMusicVolume(int volume)
|
|
{
|
|
float vol = static_cast<float>(volume) / 100.f;
|
|
|
|
if (gain_music_channel)
|
|
{
|
|
// Music channel volume is interpreted as logarithmic rather than linear.
|
|
// We approximate by cubing the gain level so vol 50 roughly sounds half as loud.
|
|
gain_music_channel->gain(std::clamp(vol * vol * vol, 0.f, 1.f));
|
|
}
|
|
}
|
|
|
|
void I_SetCurrentSongVolume(int volume)
|
|
{
|
|
float vol = static_cast<float>(volume) / 100.f;
|
|
|
|
if (gain_music_player)
|
|
{
|
|
// However, different from music channel volume, musicdef volumes are explicitly linear.
|
|
gain_music_player->gain(std::max(vol, 0.f));
|
|
}
|
|
}
|
|
|
|
boolean I_SetSongTrack(int track)
|
|
{
|
|
(void) track;
|
|
return false;
|
|
}
|
|
|
|
/// ------------------------
|
|
// MUSIC FADING
|
|
/// ------------------------
|
|
|
|
void I_SetInternalMusicVolume(UINT8 volume)
|
|
{
|
|
if (!music_player)
|
|
return;
|
|
|
|
SdlAudioLockHandle _;
|
|
|
|
float gain = volume / 100.f;
|
|
music_player->internal_gain(gain);
|
|
}
|
|
|
|
void I_StopFadingSong(void)
|
|
{
|
|
if (!music_player)
|
|
return;
|
|
|
|
SdlAudioLockHandle _;
|
|
|
|
music_player->stop_fade();
|
|
}
|
|
|
|
boolean I_FadeSongFromVolume(UINT8 target_volume, UINT8 source_volume, UINT32 ms, void (*callback)(void))
|
|
{
|
|
if (!music_player)
|
|
return false;
|
|
|
|
SdlAudioLockHandle _;
|
|
|
|
float source_gain = source_volume / 100.f;
|
|
float target_gain = target_volume / 100.f;
|
|
float seconds = ms / 1000.f;
|
|
|
|
music_player->fade_from_to(source_gain, target_gain, seconds);
|
|
|
|
if (music_fade_callback)
|
|
music_fade_callback();
|
|
music_fade_callback = callback;
|
|
|
|
return true;
|
|
}
|
|
|
|
boolean I_FadeSong(UINT8 target_volume, UINT32 ms, void (*callback)(void))
|
|
{
|
|
if (!music_player)
|
|
return false;
|
|
|
|
SdlAudioLockHandle _;
|
|
|
|
float target_gain = target_volume / 100.f;
|
|
float seconds = ms / 1000.f;
|
|
|
|
music_player->fade_to(target_gain, seconds);
|
|
|
|
if (music_fade_callback)
|
|
music_fade_callback();
|
|
music_fade_callback = callback;
|
|
|
|
return true;
|
|
}
|
|
|
|
static void stop_song_cb(void)
|
|
{
|
|
if (!music_player)
|
|
return;
|
|
|
|
SdlAudioLockHandle _;
|
|
|
|
music_player->stop();
|
|
}
|
|
|
|
boolean I_FadeOutStopSong(UINT32 ms)
|
|
{
|
|
return I_FadeSong(0.f, ms, stop_song_cb);
|
|
}
|
|
|
|
boolean I_FadeInPlaySong(UINT32 ms, boolean looping)
|
|
{
|
|
if (I_PlaySong(looping))
|
|
return I_FadeSongFromVolume(100, 0, ms, nullptr);
|
|
else
|
|
return false;
|
|
}
|
|
|
|
void I_UpdateAudioRecorder(void)
|
|
{
|
|
#ifdef SRB2_CONFIG_ENABLE_WEBM_MOVIES
|
|
// must be locked since av_recorder is used by audio_callback
|
|
SdlAudioLockHandle _;
|
|
|
|
av_recorder = g_av_recorder;
|
|
#endif
|
|
}
|
|
|
|
boolean I_SoundInputIsEnabled(void)
|
|
{
|
|
return g_input_device_id != 0;
|
|
}
|
|
|
|
boolean I_SoundInputSetEnabled(boolean enabled)
|
|
{
|
|
if (g_input_device_id == 0 && enabled)
|
|
{
|
|
if (!sound_started || SDL_GetNumAudioDevices(true) == 0)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
SDL_AudioSpec input_desired {};
|
|
input_desired.format = AUDIO_F32SYS;
|
|
input_desired.channels = 1;
|
|
input_desired.samples = 1024;
|
|
input_desired.freq = 48000;
|
|
SDL_AudioSpec input_obtained {};
|
|
g_input_device_id = SDL_OpenAudioDevice(nullptr, SDL_TRUE, &input_desired, &input_obtained, 0);
|
|
if (!g_input_device_id)
|
|
{
|
|
CONS_Alert(CONS_WARNING, "Failed to open input audio device: %s\n", SDL_GetError());
|
|
return false;
|
|
}
|
|
if (input_obtained.freq != 48000 || input_obtained.format != AUDIO_F32SYS || input_obtained.channels != 1)
|
|
{
|
|
CONS_Alert(CONS_WARNING, "Input audio device has unexpected unusable format: %s\n", SDL_GetError());
|
|
return false;
|
|
}
|
|
SDL_PauseAudioDevice(g_input_device_id, SDL_FALSE);
|
|
}
|
|
else if (g_input_device_id != 0 && !enabled)
|
|
{
|
|
SDL_PauseAudioDevice(g_input_device_id, SDL_TRUE);
|
|
SDL_ClearQueuedAudio(g_input_device_id);
|
|
SDL_CloseAudioDevice(g_input_device_id);
|
|
g_input_device_id = 0;
|
|
}
|
|
|
|
return enabled;
|
|
}
|
|
|
|
UINT32 I_SoundInputDequeueSamples(void *data, UINT32 len)
|
|
{
|
|
if (!g_input_device_id)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
UINT32 ret = SDL_DequeueAudio(g_input_device_id, data, len);
|
|
return ret;
|
|
}
|
|
|
|
UINT32 I_SoundInputRemainingSamples(void)
|
|
{
|
|
if (!g_input_device_id)
|
|
{
|
|
return 0;
|
|
}
|
|
UINT32 avail = SDL_GetQueuedAudioSize(g_input_device_id);
|
|
return avail / sizeof(float);
|
|
}
|
|
|
|
void I_QueueVoiceFrameFromPlayer(INT32 playernum, void *data, UINT32 len, boolean terminal)
|
|
{
|
|
if (!sound_started)
|
|
{
|
|
return;
|
|
}
|
|
|
|
SdlAudioLockHandle _;
|
|
SdlVoiceStreamPlayer* player = player_voice_channels.at(playernum).get();
|
|
player->stream().put(tcb::span((std::byte*)data, len));
|
|
}
|
|
|
|
void I_SetPlayerVoiceProperties(INT32 playernum, float volume, float sep)
|
|
{
|
|
if (!sound_started)
|
|
{
|
|
return;
|
|
}
|
|
|
|
SdlAudioLockHandle _;
|
|
SdlVoiceStreamPlayer* player = player_voice_channels.at(playernum).get();
|
|
player->set_properties(volume * volume * volume, sep);
|
|
}
|
|
|
|
void I_ResetVoiceQueue(INT32 playernum)
|
|
{
|
|
if (!sound_started)
|
|
{
|
|
return;
|
|
}
|
|
|
|
SdlAudioLockHandle _;
|
|
SdlVoiceStreamPlayer* player = player_voice_channels.at(playernum).get();
|
|
player->stream().clear();
|
|
}
|