/* Copyright (C) 2010 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * 0 A.D. 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with 0 A.D. If not, see . */ /** * ========================================================================= * File : SoundGroup.cpp * Project : 0 A.D. * Description : Loads up a group of sound files with shared properties, * and provides a simple interface for playing them. * ========================================================================= */ #include "precompiled.h" #include "SoundGroup.h" #include #include "lib/rand.h" #include "ps/XML/Xeromyces.h" #include "ps/CLogger.h" #include "ps/Filesystem.h" #include "ps/Util.h" static const bool DISABLE_INTENSITY = true; // disable for now since it's broken void CSoundGroup::SetGain(float gain) { gain = std::min(gain, 1.0f); m_Gain = gain; } void CSoundGroup::SetDefaultValues() { m_index = 0; m_Flags = 0; m_Intensity = 0; m_CurTime = 0.0f; // sane defaults; will probably be replaced by the values read during LoadSoundGroup. SetGain(0.5f); m_Pitch = 1.0f; m_Priority = 60; m_PitchUpper = 1.1f; m_PitchLower = 0.9f; m_GainUpper = 1.0f; m_GainLower = 0.8f; m_ConeOuterGain = 0.0f; m_ConeInnerAngle = 360.0f; m_ConeOuterAngle = 360.0f; m_Decay = 3.0f; m_IntensityThreshold = 3; // WARNING: m_TimeWindow is currently unused and uninitialized } CSoundGroup::CSoundGroup() { SetDefaultValues(); } CSoundGroup::CSoundGroup(const VfsPath& pathnameXML) { SetDefaultValues(); LoadSoundGroup(pathnameXML); } CSoundGroup::~CSoundGroup() { // clean up all the handles from this group. ReleaseGroup(); } static float RandFloat(float min, float max) { return float(rand(min*100.0f, max*100.0f) / 100.0f); } void CSoundGroup::UploadPropertiesAndPlay(Handle hSound, const CVector3D& position) { // interface/UI sounds should always be played at the listener's // position, which is achieved by setting position to 0 and // having that treated as relative to the listener. float x = 0.0f, y = 0.0f, z = 0.0f; bool relative = true; if(!TestFlag(eOmnipresent)) { x = position.X; y = position.Y; z = position.Z; relative = false; } snd_set_pos(hSound, x, y, z, relative); float gain = TestFlag(eRandGain)? RandFloat(m_GainLower, m_GainUpper) : m_Gain; gain = std::min(gain, 1.0f); // guard against roundoff error in RandFloat or too high m_GainUpper snd_set_gain(hSound, gain); const float pitch = TestFlag(eRandPitch)? RandFloat(m_PitchLower, m_PitchUpper) : m_Pitch; snd_set_pitch(hSound, pitch); snd_play(hSound, m_Priority); } static void HandleError(const std::wstring& message, const VfsPath& pathname, Status err) { if(err == ERR::AGAIN) return; // open failed because sound is disabled (don't log this) LOGERROR(L"%ls: pathname=%ls, error=%ls", message.c_str(), pathname.string().c_str(), ErrorString(err)); } void CSoundGroup::PlayNext(const CVector3D& position) { if(m_Intensity >= m_IntensityThreshold && !DISABLE_INTENSITY) { if(!snd_is_playing(m_hReplacement)) { // load up replacement file const VfsPath pathname(m_filepath / m_intensity_file); m_hReplacement = snd_open(g_VFS, pathname); if(m_hReplacement < 0) { HandleError(L"PlayNext: snd_open for replacement file failed", pathname, (Status)m_hReplacement); return; } UploadPropertiesAndPlay(m_hReplacement, position); } } else { // if no sounds, return if (filenames.size() == 0) return; // try loading on the fly only when we need the sound to see if that fixes release problems... if(TestFlag(eRandOrder)) m_index = (size_t)rand(0, (size_t)filenames.size()); // (note: previously snd_group[m_index] was used in place of hs) const VfsPath pathname(m_filepath / filenames[m_index]); Handle hs = snd_open(g_VFS, pathname); if(hs < 0) { HandleError(L"PlayNext: snd_open failed", pathname, (Status)hs); return; } UploadPropertiesAndPlay(hs, position); } playtimes.at(m_index) = 0.0f; m_index++; m_Intensity++; if(m_Intensity > m_IntensityThreshold) m_Intensity = m_IntensityThreshold; if(m_index >= filenames.size()) Reload(); } void CSoundGroup::Reload() { m_index = 0; // reset our index // get rid of the used handles snd_group.clear(); // clear out the old timers playtimes.clear(); //Reload the sounds /*for(size_t i = 0; i < filenames.size(); i++) { string szTemp = m_filepath + filenames[i]; Handle temp = snd_open(m_filepath + filenames[i]); snd_set_gain(temp, m_Gain); snd_set_pitch(temp, m_Pitch); snd_set_cone(temp, m_ConeInnerAngle, m_ConeOuterAngle, m_ConeOuterGain); snd_group.push_back(temp); }*/ while(playtimes.size() < filenames.size()) playtimes.push_back(-1.0f); //if(TestFlag(eRandOrder)) //random_shuffle(snd_group.begin(), snd_group.end()); } void CSoundGroup::ReleaseGroup() { for(size_t i = m_index; i= 0.0f) playtimes[i] += TimeSinceLastFrame; if(playtimes[i] >= m_Decay) { playtimes[i] = -1.0f; m_Intensity--; } } } bool CSoundGroup::LoadSoundGroup(const VfsPath& pathnameXML) { CXeromyces XeroFile; if (XeroFile.Load(g_VFS, pathnameXML) != PSRETURN_OK) return false; // Define elements used in XML file #define EL(x) int el_##x = XeroFile.GetElementID(#x) #define AT(x) int at_##x = XeroFile.GetAttributeID(#x) EL(soundgroup); EL(gain); EL(looping); EL(omnipresent); EL(pitch); EL(priority); EL(randorder); EL(randgain); EL(randpitch); EL(conegain); EL(coneinner); EL(coneouter); EL(sound); EL(gainupper); EL(gainlower); EL(pitchupper); EL(pitchlower); EL(path); EL(threshold); EL(decay); EL(replacement); #undef AT #undef EL XMBElement root = XeroFile.GetRoot(); if (root.GetNodeName() != el_soundgroup) { LOGERROR(L"Invalid SoundGroup format (unrecognised root element '%hs')", XeroFile.GetElementString(root.GetNodeName()).c_str()); return false; } XERO_ITER_EL(root, child) { int child_name = child.GetNodeName(); if(child_name == el_gain) { SetGain(child.GetText().ToFloat()); } else if(child_name == el_looping) { if(child.GetText().ToInt() == 1) SetFlag(eLoop); } else if(child_name == el_omnipresent) { if(child.GetText().ToInt() == 1) SetFlag(eOmnipresent); } else if(child_name == el_pitch) { this->m_Pitch = child.GetText().ToFloat(); } else if(child_name == el_priority) { this->m_Priority = child.GetText().ToFloat(); } else if(child_name == el_randorder) { if(child.GetText().ToInt() == 1) SetFlag(eRandOrder); } else if(child_name == el_randgain) { if(child.GetText().ToInt() == 1) SetFlag(eRandGain); } else if(child_name == el_gainupper) { this->m_GainUpper = child.GetText().ToFloat(); } else if(child_name == el_gainlower) { this->m_GainLower = child.GetText().ToFloat(); } else if(child_name == el_randpitch) { if(child.GetText().ToInt() == 1) SetFlag(eRandPitch); } else if(child_name == el_pitchupper) { this->m_PitchUpper = child.GetText().ToFloat(); } else if(child_name == el_pitchlower) { this->m_PitchLower = child.GetText().ToFloat(); } else if(child_name == el_conegain) { this->m_ConeOuterGain = child.GetText().ToFloat(); } else if(child_name == el_coneinner) { this->m_ConeInnerAngle = child.GetText().ToFloat(); } else if(child_name == el_coneouter) { this->m_ConeOuterAngle = child.GetText().ToFloat(); } else if(child_name == el_sound) { this->filenames.push_back(child.GetText().FromUTF8()); } else if(child_name == el_path) { m_filepath = child.GetText().FromUTF8(); } else if(child_name == el_threshold) { m_IntensityThreshold = child.GetText().ToFloat(); } else if(child_name == el_decay) { m_Decay = child.GetText().ToFloat(); } else if(child_name == el_replacement) { m_intensity_file = child.GetText().FromUTF8(); } } Reload(); return true; }