/* Minetest Copyright (C) 2022 DS Copyright (C) 2013 celeron55, Perttu Ahola OpenAL support based on work by: Copyright (C) 2011 Sebastian 'Bahamada' Rühl Copyright (C) 2011 Cyriaque 'Cisoun' Skrapits Copyright (C) 2011 Giuseppe Bilotta 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 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 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 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 unopn_snd = std::move(it_unopen->second); m_sound_datas_unopen.erase(it_unopen); // open std::shared_ptr 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 &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 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 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 OpenALSoundManager::createPlayingSound( const std::string &sound_name, bool loop, f32 volume, f32 pitch, f32 start_time, const std::optional> &pos_vel_opt) { infostream << "OpenALSoundManager: Creating playing sound \"" << sound_name << "\"" << std::endl; warn_if_al_error("before createPlayingSound"); std::shared_ptr 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(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> &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 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 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(filepath)); } void OpenALSoundManager::loadSoundDataNoCheck(const std::string &name, std::string &&filedata) { // remember for lazy loading m_sound_datas_unopen.emplace(name, std::make_unique(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{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> 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