/* Copyright (C) 2015 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 "graphics/Camera.h" #include "graphics/GameView.h" #include "lib/rand.h" #include "ps/Game.h" #include "ps/CLogger.h" #include "ps/CStr.h" #include "ps/Filesystem.h" #include "ps/Util.h" #include "ps/XML/Xeromyces.h" #include "soundmanager/items/ISoundItem.h" #include "soundmanager/SoundManager.h" #include extern CGame *g_Game; #define PI 3.14126f 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.7f); 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(); } #if CONFIG2_AUDIO static float RandFloat(float min, float max) { return float(rand(min*100.0f, max*100.0f) / 100.0f); } #endif // CONFIG2_AUDIO float CSoundGroup::RadiansOffCenter(const CVector3D& position, bool& onScreen, float& itemRollOff) { float x, y; float answer = 0.0; const size_t screenWidth = g_Game->GetView()->GetCamera()->GetViewPort().m_Width; const size_t screenHeight = g_Game->GetView()->GetCamera()->GetViewPort().m_Height; float bufferSize = screenWidth * 0.10; float yBufferSize = 15; const size_t audioWidth = screenWidth; float radianCap = PI / 3; g_Game->GetView()->GetCamera()->GetScreenCoordinates(position, x, y); onScreen = true; if (x < -bufferSize) { onScreen = false; answer = -radianCap; } else if (x > screenWidth + bufferSize) { onScreen = false; answer = radianCap; } else { if ((x < 0) || (x > screenWidth)) { itemRollOff = 0.5; } float pixPerRadian = audioWidth / (radianCap * 2); answer = (x - (screenWidth/2)) / pixPerRadian; } if (y < -yBufferSize) { onScreen = false; } else if (y > screenHeight + yBufferSize) { onScreen = false; } else if ((y < 0) || (y > screenHeight)) { itemRollOff = 0.5; } return answer; } void CSoundGroup::UploadPropertiesAndPlay(size_t theIndex, const CVector3D& position, entity_id_t source) { #if CONFIG2_AUDIO if ( !g_SoundManager ) return; bool isOnscreen = false; ALfloat initialRolllOff = 0.1f; ALfloat itemRollOff = initialRolllOff; float offSet = RadiansOffCenter(position, isOnscreen, itemRollOff); bool shouldBePlayed = isOnscreen || TestFlag(eDistanceless) || TestFlag(eOmnipresent); if ( !shouldBePlayed ) return; if (snd_group.size() == 0) Reload(); if ( snd_group.size() <= theIndex ) return; CSoundData* sndData = snd_group[theIndex]; if ( sndData == NULL ) return; ISoundItem* hSound = ((CSoundManager*)g_SoundManager)->ItemForEntity( source, sndData); if ( hSound == NULL ) return; if (!TestFlag(eOmnipresent)) { CVector3D origin = g_Game->GetView()->GetCamera()->GetOrientation().GetTranslation(); float sndDist = origin.Y; float itemDist = ( position - origin ).Length(); if ( (sndDist * 2) < itemDist ) sndDist = itemDist; if (TestFlag(eDistanceless)) itemRollOff = 0; if ( sndData->IsStereo() ) LOGWARNING("OpenAL: stereo sounds can't be positioned: %s", sndData->GetFileName().string8()); hSound->SetLocation(CVector3D((sndDist * sin(offSet)), 0, - sndDist * cos(offSet))); hSound->SetRollOff(itemRollOff); } if (TestFlag(eRandPitch)) hSound->SetPitch(RandFloat(m_PitchLower, m_PitchUpper)); else hSound->SetPitch(m_Pitch); ALfloat theGain = m_Gain; if (TestFlag(eRandGain)) theGain = RandFloat(m_GainLower, m_GainUpper); hSound->SetCone(m_ConeInnerAngle, m_ConeOuterAngle, m_ConeOuterGain); ((CSoundManager*)g_SoundManager)->PlayGroupItem(hSound, theGain); #else // !CONFIG2_AUDIO UNUSED2(theIndex); UNUSED2(position); UNUSED2(source); #endif // !CONFIG2_AUDIO } static void HandleError(const CStrW& message, const VfsPath& pathname, Status err) { if (err == ERR::AGAIN) return; // open failed because sound is disabled (don't log this) LOGERROR("%s: pathname=%s, error=%s", utf8_from_wstring(message), pathname.string8(), utf8_from_wstring(ErrorString(err))); } void CSoundGroup::PlayNext(const CVector3D& position, entity_id_t source) { // if no sounds, return if (filenames.size() == 0) return; m_index = rand(0, (size_t)filenames.size()); UploadPropertiesAndPlay(m_index, position, source); } void CSoundGroup::Reload() { m_index = 0; // reset our index #if CONFIG2_AUDIO ReleaseGroup(); if ( g_SoundManager ) { for (size_t i = 0; i < filenames.size(); i++) { VfsPath thePath = m_filepath/filenames[i]; CSoundData* itemData = CSoundData::SoundDataFromFile(thePath); if (itemData == NULL) HandleError(L"error loading sound", thePath, ERR::FAIL); else snd_group.push_back(itemData->IncrementCount()); } if (TestFlag(eRandOrder)) random_shuffle(snd_group.begin(), snd_group.end()); } #endif // CONFIG2_AUDIO } void CSoundGroup::ReleaseGroup() { #if CONFIG2_AUDIO for (size_t i = 0; i < snd_group.size(); i++) { CSoundData::ReleaseSoundData( snd_group[i] ); } snd_group.clear(); #endif // CONFIG2_AUDIO } void CSoundGroup::Update(float UNUSED(TimeSinceLastFrame)) { } bool CSoundGroup::LoadSoundGroup(const VfsPath& pathnameXML) { CXeromyces XeroFile; if (XeroFile.Load(g_VFS, pathnameXML, "sound_group") != PSRETURN_OK) { HandleError(L"error loading file", pathnameXML, ERR::FAIL); 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(heardby); EL(distanceless); 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); #undef AT #undef EL XMBElement root = XeroFile.GetRoot(); if (root.GetNodeName() != el_soundgroup) { LOGERROR("Invalid SoundGroup format (unrecognised root element '%s')", 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_heardby) { if(child.GetText().FindInsensitive( "owner" ) == 0 ) SetFlag(eOwnerOnly); } else if(child_name == el_distanceless) { if(child.GetText().ToInt() == 1) SetFlag(eDistanceless); } 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(); } } return true; }