mirror of
https://github.com/minetest/minetest.git
synced 2025-03-06 20:48:40 +01:00
530 lines
15 KiB
C++
530 lines
15 KiB
C++
/*
|
|
Minetest
|
|
Copyright (C) 2022 DS
|
|
Copyright (C) 2013 celeron55, Perttu Ahola <celeron55@gmail.com>
|
|
OpenAL support based on work by:
|
|
Copyright (C) 2011 Sebastian 'Bahamada' Rühl
|
|
Copyright (C) 2011 Cyriaque 'Cisoun' Skrapits <cysoun@gmail.com>
|
|
Copyright (C) 2011 Giuseppe Bilotta <giuseppe.bilotta@gmail.com>
|
|
|
|
This program is free software; you can redistribute it and/or modify
|
|
it under the terms of the GNU Lesser General Public License as published by
|
|
the Free Software Foundation; either version 2.1 of the License, or
|
|
(at your option) any later version.
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU Lesser General Public License for more details.
|
|
|
|
You should have received a copy of the GNU Lesser General Public License along
|
|
with this program; ifnot, write to the Free Software Foundation, Inc.,
|
|
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
|
*/
|
|
|
|
#include "sound_manager.h"
|
|
|
|
#include "sound_singleton.h"
|
|
#include "util/numeric.h" // myrand()
|
|
#include "filesys.h"
|
|
#include "porting.h"
|
|
|
|
namespace sound {
|
|
|
|
void OpenALSoundManager::stepStreams(f32 dtime)
|
|
{
|
|
// spread work across steps
|
|
const size_t num_issued_sounds = std::min(
|
|
m_sounds_streaming_current_bigstep.size(),
|
|
(size_t)std::ceil(m_sounds_streaming_current_bigstep.size()
|
|
* dtime / m_stream_timer)
|
|
);
|
|
|
|
for (size_t i = 0; i < num_issued_sounds; ++i) {
|
|
auto wptr = std::move(m_sounds_streaming_current_bigstep.back());
|
|
m_sounds_streaming_current_bigstep.pop_back();
|
|
|
|
std::shared_ptr<PlayingSound> snd = wptr.lock();
|
|
if (!snd)
|
|
continue;
|
|
|
|
if (!snd->stepStream())
|
|
continue;
|
|
|
|
// sound still lives and needs more stream-stepping => add to next bigstep
|
|
m_sounds_streaming_next_bigstep.push_back(std::move(wptr));
|
|
}
|
|
|
|
m_stream_timer -= dtime;
|
|
if (m_stream_timer <= 0.0f) {
|
|
m_stream_timer = STREAM_BIGSTEP_TIME;
|
|
using std::swap;
|
|
swap(m_sounds_streaming_current_bigstep, m_sounds_streaming_next_bigstep);
|
|
}
|
|
}
|
|
|
|
void OpenALSoundManager::doFades(f32 dtime)
|
|
{
|
|
for (size_t i = 0; i < m_sounds_fading.size();) {
|
|
std::shared_ptr<PlayingSound> snd = m_sounds_fading[i].lock();
|
|
if (snd) {
|
|
if (snd->doFade(dtime)) {
|
|
// needs more fading later, keep in m_sounds_fading
|
|
++i;
|
|
continue;
|
|
}
|
|
}
|
|
|
|
// sound no longer needs to be faded
|
|
m_sounds_fading[i] = std::move(m_sounds_fading.back());
|
|
m_sounds_fading.pop_back();
|
|
// continue with same i
|
|
}
|
|
}
|
|
|
|
std::shared_ptr<ISoundDataOpen> OpenALSoundManager::openSingleSound(const std::string &sound_name)
|
|
{
|
|
// if already open, nothing to do
|
|
auto it = m_sound_datas_open.find(sound_name);
|
|
if (it != m_sound_datas_open.end())
|
|
return it->second;
|
|
|
|
// find unopened data
|
|
auto it_unopen = m_sound_datas_unopen.find(sound_name);
|
|
if (it_unopen == m_sound_datas_unopen.end())
|
|
return nullptr;
|
|
std::unique_ptr<ISoundDataUnopen> unopn_snd = std::move(it_unopen->second);
|
|
m_sound_datas_unopen.erase(it_unopen);
|
|
|
|
// open
|
|
std::shared_ptr<ISoundDataOpen> opn_snd = std::move(*unopn_snd).open(sound_name);
|
|
if (!opn_snd)
|
|
return nullptr;
|
|
m_sound_datas_open.emplace(sound_name, opn_snd);
|
|
return opn_snd;
|
|
}
|
|
|
|
std::string OpenALSoundManager::getLoadedSoundNameFromGroup(const std::string &group_name)
|
|
{
|
|
std::string chosen_sound_name = "";
|
|
|
|
auto it_groups = m_sound_groups.find(group_name);
|
|
if (it_groups == m_sound_groups.end())
|
|
return "";
|
|
|
|
std::vector<std::string> &group_sounds = it_groups->second;
|
|
while (!group_sounds.empty()) {
|
|
// choose one by random
|
|
int j = myrand() % group_sounds.size();
|
|
chosen_sound_name = group_sounds[j];
|
|
|
|
// find chosen one
|
|
std::shared_ptr<ISoundDataOpen> snd = openSingleSound(chosen_sound_name);
|
|
if (snd)
|
|
return chosen_sound_name;
|
|
|
|
// it doesn't exist
|
|
// remove it from the group and try again
|
|
group_sounds[j] = std::move(group_sounds.back());
|
|
group_sounds.pop_back();
|
|
}
|
|
|
|
return "";
|
|
}
|
|
|
|
std::string OpenALSoundManager::getOrLoadLoadedSoundNameFromGroup(const std::string &group_name)
|
|
{
|
|
std::string sound_name = getLoadedSoundNameFromGroup(group_name);
|
|
if (!sound_name.empty())
|
|
return sound_name;
|
|
|
|
// load
|
|
std::vector<std::string> paths = m_fallback_path_provider
|
|
->getLocalFallbackPathsForSoundname(group_name);
|
|
for (const std::string &path : paths) {
|
|
if (loadSoundFile(path, path))
|
|
addSoundToGroup(path, group_name);
|
|
}
|
|
return getLoadedSoundNameFromGroup(group_name);
|
|
}
|
|
|
|
std::shared_ptr<PlayingSound> OpenALSoundManager::createPlayingSound(
|
|
const std::string &sound_name, bool loop, f32 volume, f32 pitch,
|
|
f32 start_time, const std::optional<std::pair<v3f, v3f>> &pos_vel_opt)
|
|
{
|
|
infostream << "OpenALSoundManager: Creating playing sound \"" << sound_name
|
|
<< "\"" << std::endl;
|
|
warn_if_al_error("before createPlayingSound");
|
|
|
|
std::shared_ptr<ISoundDataOpen> lsnd = openSingleSound(sound_name);
|
|
if (!lsnd) {
|
|
// does not happen because of the call to getLoadedSoundNameFromGroup
|
|
errorstream << "OpenALSoundManager::createPlayingSound: Sound \""
|
|
<< sound_name << "\" disappeared." << std::endl;
|
|
return nullptr;
|
|
}
|
|
|
|
if (lsnd->m_decode_info.is_stereo && pos_vel_opt.has_value()
|
|
&& m_warned_positional_stereo_sounds.find(sound_name)
|
|
== m_warned_positional_stereo_sounds.end()) {
|
|
warningstream << "OpenALSoundManager::createPlayingSound: "
|
|
<< "Creating positional stereo sound \"" << sound_name << "\"."
|
|
<< std::endl;
|
|
m_warned_positional_stereo_sounds.insert(sound_name);
|
|
}
|
|
|
|
ALuint source_id;
|
|
alGenSources(1, &source_id);
|
|
if (warn_if_al_error("createPlayingSound (alGenSources)") != AL_NO_ERROR) {
|
|
// happens ie. if there are too many sources (out of memory)
|
|
return nullptr;
|
|
}
|
|
|
|
auto sound = std::make_shared<PlayingSound>(source_id, std::move(lsnd), loop,
|
|
volume, pitch, start_time, pos_vel_opt);
|
|
|
|
sound->play();
|
|
if (m_is_paused)
|
|
sound->pause();
|
|
warn_if_al_error("createPlayingSound");
|
|
return sound;
|
|
}
|
|
|
|
void OpenALSoundManager::playSoundGeneric(sound_handle_t id, const std::string &group_name,
|
|
bool loop, f32 volume, f32 fade, f32 pitch, bool use_local_fallback,
|
|
f32 start_time, const std::optional<std::pair<v3f, v3f>> &pos_vel_opt)
|
|
{
|
|
assert(id != 0);
|
|
|
|
if (group_name.empty()) {
|
|
reportRemovedSound(id);
|
|
return;
|
|
}
|
|
|
|
// choose random sound name from group name
|
|
std::string sound_name = use_local_fallback ?
|
|
getOrLoadLoadedSoundNameFromGroup(group_name) :
|
|
getLoadedSoundNameFromGroup(group_name);
|
|
if (sound_name.empty()) {
|
|
infostream << "OpenALSoundManager: \"" << group_name << "\" not found."
|
|
<< std::endl;
|
|
reportRemovedSound(id);
|
|
return;
|
|
}
|
|
|
|
volume = std::max(0.0f, volume);
|
|
f32 target_fade_volume = volume;
|
|
if (fade > 0.0f)
|
|
volume = 0.0f;
|
|
|
|
if (!(pitch > 0.0f)) {
|
|
warningstream << "OpenALSoundManager::playSoundGeneric: Illegal pitch value: "
|
|
<< start_time << std::endl;
|
|
pitch = 1.0f;
|
|
}
|
|
|
|
if (!std::isfinite(start_time)) {
|
|
warningstream << "OpenALSoundManager::playSoundGeneric: Illegal start_time value: "
|
|
<< start_time << std::endl;
|
|
start_time = 0.0f;
|
|
}
|
|
|
|
// play it
|
|
std::shared_ptr<PlayingSound> sound = createPlayingSound(sound_name, loop,
|
|
volume, pitch, start_time, pos_vel_opt);
|
|
if (!sound) {
|
|
reportRemovedSound(id);
|
|
return;
|
|
}
|
|
|
|
// add to streaming sounds if streaming
|
|
if (sound->isStreaming())
|
|
m_sounds_streaming_next_bigstep.push_back(sound);
|
|
|
|
m_sounds_playing.emplace(id, std::move(sound));
|
|
|
|
if (fade > 0.0f)
|
|
fadeSound(id, fade, target_fade_volume);
|
|
}
|
|
|
|
int OpenALSoundManager::removeDeadSounds()
|
|
{
|
|
int num_deleted_sounds = 0;
|
|
|
|
for (auto it = m_sounds_playing.begin(); it != m_sounds_playing.end();) {
|
|
sound_handle_t id = it->first;
|
|
PlayingSound &sound = *it->second;
|
|
// If dead, remove it
|
|
if (sound.isDead()) {
|
|
it = m_sounds_playing.erase(it);
|
|
reportRemovedSound(id);
|
|
++num_deleted_sounds;
|
|
} else {
|
|
++it;
|
|
}
|
|
}
|
|
|
|
return num_deleted_sounds;
|
|
}
|
|
|
|
OpenALSoundManager::OpenALSoundManager(SoundManagerSingleton *smg,
|
|
std::unique_ptr<SoundFallbackPathProvider> fallback_path_provider) :
|
|
Thread("OpenALSoundManager"),
|
|
m_fallback_path_provider(std::move(fallback_path_provider)),
|
|
m_device(smg->m_device.get()),
|
|
m_context(smg->m_context.get())
|
|
{
|
|
SANITY_CHECK(!!m_fallback_path_provider);
|
|
|
|
infostream << "Audio: Initialized: OpenAL " << std::endl;
|
|
}
|
|
|
|
OpenALSoundManager::~OpenALSoundManager()
|
|
{
|
|
infostream << "Audio: Deinitializing..." << std::endl;
|
|
}
|
|
|
|
/* Interface */
|
|
|
|
void OpenALSoundManager::step(f32 dtime)
|
|
{
|
|
m_time_until_dead_removal -= dtime;
|
|
if (m_time_until_dead_removal <= 0.0f) {
|
|
if (!m_sounds_playing.empty()) {
|
|
verbosestream << "OpenALSoundManager::step(): "
|
|
<< m_sounds_playing.size() << " playing sounds, "
|
|
<< m_sound_datas_unopen.size() << " unopen sounds, "
|
|
<< m_sound_datas_open.size() << " open sounds and "
|
|
<< m_sound_groups.size() << " sound groups loaded."
|
|
<< std::endl;
|
|
}
|
|
|
|
int num_deleted_sounds = removeDeadSounds();
|
|
|
|
if (num_deleted_sounds != 0)
|
|
verbosestream << "OpenALSoundManager::step(): Deleted "
|
|
<< num_deleted_sounds << " dead playing sounds." << std::endl;
|
|
|
|
m_time_until_dead_removal = REMOVE_DEAD_SOUNDS_INTERVAL;
|
|
}
|
|
|
|
doFades(dtime);
|
|
stepStreams(dtime);
|
|
}
|
|
|
|
void OpenALSoundManager::pauseAll()
|
|
{
|
|
for (auto &snd_p : m_sounds_playing) {
|
|
PlayingSound &snd = *snd_p.second;
|
|
snd.pause();
|
|
}
|
|
m_is_paused = true;
|
|
}
|
|
|
|
void OpenALSoundManager::resumeAll()
|
|
{
|
|
for (auto &snd_p : m_sounds_playing) {
|
|
PlayingSound &snd = *snd_p.second;
|
|
snd.resume();
|
|
}
|
|
m_is_paused = false;
|
|
}
|
|
|
|
void OpenALSoundManager::updateListener(const v3f &pos_, const v3f &vel_,
|
|
const v3f &at_, const v3f &up_)
|
|
{
|
|
v3f pos = swap_handedness(pos_);
|
|
v3f vel = swap_handedness(vel_);
|
|
v3f at = swap_handedness(at_);
|
|
v3f up = swap_handedness(up_);
|
|
ALfloat orientation[6] = {at.X, at.Y, at.Z, up.X, up.Y, up.Z};
|
|
|
|
alListener3f(AL_POSITION, pos.X, pos.Y, pos.Z);
|
|
alListener3f(AL_VELOCITY, vel.X, vel.Y, vel.Z);
|
|
alListenerfv(AL_ORIENTATION, orientation);
|
|
warn_if_al_error("updateListener");
|
|
}
|
|
|
|
void OpenALSoundManager::setListenerGain(f32 gain)
|
|
{
|
|
alListenerf(AL_GAIN, gain);
|
|
}
|
|
|
|
bool OpenALSoundManager::loadSoundFile(const std::string &name, const std::string &filepath)
|
|
{
|
|
// do not add twice
|
|
if (m_sound_datas_open.count(name) != 0 || m_sound_datas_unopen.count(name) != 0)
|
|
return false;
|
|
|
|
// coarse check
|
|
if (!fs::IsFile(filepath))
|
|
return false;
|
|
|
|
loadSoundFileNoCheck(name, filepath);
|
|
return true;
|
|
}
|
|
|
|
bool OpenALSoundManager::loadSoundData(const std::string &name, std::string &&filedata)
|
|
{
|
|
// do not add twice
|
|
if (m_sound_datas_open.count(name) != 0 || m_sound_datas_unopen.count(name) != 0)
|
|
return false;
|
|
|
|
loadSoundDataNoCheck(name, std::move(filedata));
|
|
return true;
|
|
}
|
|
|
|
void OpenALSoundManager::loadSoundFileNoCheck(const std::string &name, const std::string &filepath)
|
|
{
|
|
// remember for lazy loading
|
|
m_sound_datas_unopen.emplace(name, std::make_unique<SoundDataUnopenFile>(filepath));
|
|
}
|
|
|
|
void OpenALSoundManager::loadSoundDataNoCheck(const std::string &name, std::string &&filedata)
|
|
{
|
|
// remember for lazy loading
|
|
m_sound_datas_unopen.emplace(name, std::make_unique<SoundDataUnopenBuffer>(std::move(filedata)));
|
|
}
|
|
|
|
void OpenALSoundManager::addSoundToGroup(const std::string &sound_name, const std::string &group_name)
|
|
{
|
|
auto it_groups = m_sound_groups.find(group_name);
|
|
if (it_groups != m_sound_groups.end())
|
|
it_groups->second.push_back(sound_name);
|
|
else
|
|
m_sound_groups.emplace(group_name, std::vector<std::string>{sound_name});
|
|
}
|
|
|
|
void OpenALSoundManager::playSound(sound_handle_t id, const SoundSpec &spec)
|
|
{
|
|
return playSoundGeneric(id, spec.name, spec.loop, spec.gain, spec.fade, spec.pitch,
|
|
spec.use_local_fallback, spec.start_time, std::nullopt);
|
|
}
|
|
|
|
void OpenALSoundManager::playSoundAt(sound_handle_t id, const SoundSpec &spec,
|
|
const v3f &pos_, const v3f &vel_)
|
|
{
|
|
std::optional<std::pair<v3f, v3f>> pos_vel_opt({
|
|
swap_handedness(pos_),
|
|
swap_handedness(vel_)
|
|
});
|
|
|
|
return playSoundGeneric(id, spec.name, spec.loop, spec.gain, spec.fade, spec.pitch,
|
|
spec.use_local_fallback, spec.start_time, pos_vel_opt);
|
|
}
|
|
|
|
void OpenALSoundManager::stopSound(sound_handle_t sound)
|
|
{
|
|
m_sounds_playing.erase(sound);
|
|
reportRemovedSound(sound);
|
|
}
|
|
|
|
void OpenALSoundManager::fadeSound(sound_handle_t soundid, f32 step, f32 target_gain)
|
|
{
|
|
// Ignore the command if step isn't valid.
|
|
if (step == 0.0f)
|
|
return;
|
|
auto sound_it = m_sounds_playing.find(soundid);
|
|
if (sound_it == m_sounds_playing.end())
|
|
return; // No sound to fade
|
|
PlayingSound &sound = *sound_it->second;
|
|
if (sound.fade(step, target_gain))
|
|
m_sounds_fading.emplace_back(sound_it->second);
|
|
}
|
|
|
|
void OpenALSoundManager::updateSoundPosVel(sound_handle_t id, const v3f &pos_,
|
|
const v3f &vel_)
|
|
{
|
|
v3f pos = swap_handedness(pos_);
|
|
v3f vel = swap_handedness(vel_);
|
|
|
|
auto i = m_sounds_playing.find(id);
|
|
if (i == m_sounds_playing.end())
|
|
return;
|
|
i->second->updatePosVel(pos, vel);
|
|
}
|
|
|
|
/* Thread stuff */
|
|
|
|
void *OpenALSoundManager::run()
|
|
{
|
|
using namespace sound_manager_messages_to_mgr;
|
|
|
|
struct MsgVisitor {
|
|
enum class Result { Ok, Empty, StopRequested };
|
|
|
|
OpenALSoundManager &mgr;
|
|
|
|
Result operator()(std::monostate &&) {
|
|
return Result::Empty; }
|
|
|
|
Result operator()(PauseAll &&) {
|
|
mgr.pauseAll(); return Result::Ok; }
|
|
Result operator()(ResumeAll &&) {
|
|
mgr.resumeAll(); return Result::Ok; }
|
|
|
|
Result operator()(UpdateListener &&msg) {
|
|
mgr.updateListener(msg.pos_, msg.vel_, msg.at_, msg.up_); return Result::Ok; }
|
|
Result operator()(SetListenerGain &&msg) {
|
|
mgr.setListenerGain(msg.gain); return Result::Ok; }
|
|
|
|
Result operator()(LoadSoundFile &&msg) {
|
|
mgr.loadSoundFileNoCheck(msg.name, msg.filepath); return Result::Ok; }
|
|
Result operator()(LoadSoundData &&msg) {
|
|
mgr.loadSoundDataNoCheck(msg.name, std::move(msg.filedata)); return Result::Ok; }
|
|
Result operator()(AddSoundToGroup &&msg) {
|
|
mgr.addSoundToGroup(msg.sound_name, msg.group_name); return Result::Ok; }
|
|
|
|
Result operator()(PlaySound &&msg) {
|
|
mgr.playSound(msg.id, msg.spec); return Result::Ok; }
|
|
Result operator()(PlaySoundAt &&msg) {
|
|
mgr.playSoundAt(msg.id, msg.spec, msg.pos_, msg.vel_); return Result::Ok; }
|
|
Result operator()(StopSound &&msg) {
|
|
mgr.stopSound(msg.sound); return Result::Ok; }
|
|
Result operator()(FadeSound &&msg) {
|
|
mgr.fadeSound(msg.soundid, msg.step, msg.target_gain); return Result::Ok; }
|
|
Result operator()(UpdateSoundPosVel &&msg) {
|
|
mgr.updateSoundPosVel(msg.sound, msg.pos_, msg.vel_); return Result::Ok; }
|
|
|
|
Result operator()(PleaseStop &&msg) {
|
|
return Result::StopRequested; }
|
|
};
|
|
|
|
u64 t_step_start = porting::getTimeMs();
|
|
while (true) {
|
|
auto get_time_since_last_step = [&] {
|
|
return (f32)(porting::getTimeMs() - t_step_start);
|
|
};
|
|
auto get_remaining_timeout = [&] {
|
|
return (s32)((1.0e3f * SOUNDTHREAD_DTIME) - get_time_since_last_step());
|
|
};
|
|
|
|
bool stop_requested = false;
|
|
|
|
while (true) {
|
|
SoundManagerMsgToMgr msg =
|
|
m_queue_to_mgr.pop_frontNoEx(std::max(get_remaining_timeout(), 0));
|
|
|
|
MsgVisitor::Result res = std::visit(MsgVisitor{*this}, std::move(msg));
|
|
|
|
if (res == MsgVisitor::Result::Empty && get_remaining_timeout() <= 0) {
|
|
break; // finished sleeping
|
|
} else if (res == MsgVisitor::Result::StopRequested) {
|
|
stop_requested = true;
|
|
break;
|
|
}
|
|
}
|
|
if (stop_requested)
|
|
break;
|
|
|
|
f32 dtime = get_time_since_last_step();
|
|
t_step_start = porting::getTimeMs();
|
|
step(dtime);
|
|
}
|
|
|
|
send(sound_manager_messages_to_proxy::Stopped{});
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
} // namespace sound
|