LCOV - code coverage report
Current view: top level - source/soundmanager/scripting - SoundGroup.cpp (source / functions) Hit Total Coverage
Test: 0 A.D. test coverage report Lines: 1 220 0.5 %
Date: 2023-01-19 00:18:29 Functions: 2 20 10.0 %

          Line data    Source code
       1             : /* Copyright (C) 2021 Wildfire Games.
       2             :  * This file is part of 0 A.D.
       3             :  *
       4             :  * 0 A.D. is free software: you can redistribute it and/or modify
       5             :  * it under the terms of the GNU General Public License as published by
       6             :  * the Free Software Foundation, either version 2 of the License, or
       7             :  * (at your option) any later version.
       8             :  *
       9             :  * 0 A.D. is distributed in the hope that it will be useful,
      10             :  * but WITHOUT ANY WARRANTY; without even the implied warranty of
      11             :  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
      12             :  * GNU General Public License for more details.
      13             :  *
      14             :  * You should have received a copy of the GNU General Public License
      15             :  * along with 0 A.D.  If not, see <http://www.gnu.org/licenses/>.
      16             :  */
      17             : 
      18             : #include "precompiled.h"
      19             : 
      20             : #include "SoundGroup.h"
      21             : #include "graphics/Camera.h"
      22             : #include "graphics/GameView.h"
      23             : #include "lib/rand.h"
      24             : #include "ps/CLogger.h"
      25             : #include "ps/ConfigDB.h"
      26             : #include "ps/CStr.h"
      27             : #include "ps/Filesystem.h"
      28             : #include "ps/Game.h"
      29             : #include "ps/Util.h"
      30             : #include "ps/XML/Xeromyces.h"
      31             : #include "simulation2/components/ICmpVisual.h"
      32             : #include "simulation2/system/Component.h"
      33             : #include "soundmanager/items/ISoundItem.h"
      34             : #include "soundmanager/SoundManager.h"
      35             : 
      36             : #include <algorithm>
      37             : #include <random>
      38             : 
      39             : extern CGame *g_Game;
      40             : 
      41             : #if CONFIG2_AUDIO
      42             : 
      43             : constexpr ALfloat DEFAULT_ROLLOFF = 0.5f;
      44             : constexpr ALfloat MAX_ROLLOFF = 0.7f;
      45             : 
      46             : /**
      47             :  * Low randomness, quite-a-lot-faster-than-std::mt19937 random number generator.
      48             :  * It matches the interface of UniformRandomBitGenerator for use in std::shuffle.
      49             :  */
      50             : class CFastRand
      51             : {
      52             : public:
      53             :     using result_type = u32;
      54             : 
      55             :     constexpr static result_type min() { return 0; }
      56           0 :     constexpr static result_type max() { return 0xFFFF; }
      57             : 
      58           0 :     static result_type Rand(result_type& seed)
      59             :     {
      60             :         // This is a mixed linear congruential random number generator.
      61             :         // The magic numbers are chosen so that they generate pseudo random numbers over a big enough period (0xFFFF).
      62           0 :         seed = 214013 * seed + 2531011;
      63           0 :         return (seed >> 16) & max();
      64             :     }
      65             : 
      66           0 :     static float RandFloat(result_type& seed, float min, float max)
      67             :     {
      68           0 :         return (static_cast<float>(Rand(seed)) / (0xFFFF)) * (max - min) + min;
      69             :     }
      70             : 
      71             :     CFastRand() {};
      72           0 :     CFastRand(result_type init) : m_Seed(init) {};
      73             : 
      74           0 :     result_type operator()()
      75             :     {
      76           0 :         return Rand(m_Seed);
      77             :     }
      78             : 
      79             :     result_type m_Seed;
      80             : };
      81             : #endif
      82             : 
      83           0 : void CSoundGroup::SetGain(float gain)
      84             : {
      85           0 :     m_Gain = std::min(gain, 1.0f);
      86           0 : }
      87             : 
      88           0 : void CSoundGroup::SetDefaultValues()
      89             : {
      90           0 :     m_CurrentSoundIndex = 0;
      91           0 :     m_Flags = 0;
      92           0 :     m_CurTime = 0.f;
      93           0 :     m_Gain = 0.7f;
      94           0 :     m_Pitch = 1.f;
      95           0 :     m_Priority = 60.f;
      96           0 :     m_PitchUpper = 1.1f;
      97           0 :     m_PitchLower = 0.9f;
      98           0 :     m_GainUpper = 1.f;
      99           0 :     m_GainLower = 0.8f;
     100           0 :     m_ConeOuterGain = 0.f;
     101           0 :     m_ConeInnerAngle = 360.f;
     102           0 :     m_ConeOuterAngle = 360.f;
     103           0 :     m_Decay = 3.f;
     104           0 :     m_Seed = 0;
     105           0 :     m_IntensityThreshold = 3.f;
     106             : 
     107           0 :     m_MinDist = 1.f;
     108           0 :     m_MaxDist = 350.f;
     109             :     // This is more than the default camera FOV: for now, our soundscape is not realistic anyways.
     110           0 :     m_MaxStereoAngle = static_cast<float>(M_PI / 6);
     111           0 :     if (CConfigDB::IsInitialised())
     112             :     {
     113           0 :         CFG_GET_VAL("sound.mindistance", m_MinDist);
     114           0 :         CFG_GET_VAL("sound.maxdistance", m_MaxDist);
     115           0 :         CFG_GET_VAL("sound.maxstereoangle", m_MaxStereoAngle);
     116             :     }
     117           0 : }
     118             : 
     119           0 : CSoundGroup::CSoundGroup()
     120             : {
     121           0 :     SetDefaultValues();
     122           0 : }
     123             : 
     124           0 : CSoundGroup::CSoundGroup(const VfsPath& pathnameXML)
     125             : {
     126           0 :     SetDefaultValues();
     127           0 :     LoadSoundGroup(pathnameXML);
     128           0 : }
     129             : 
     130           0 : CSoundGroup::~CSoundGroup()
     131             : {
     132             :     // clean up all the handles from this group.
     133           0 :     ReleaseGroup();
     134           0 : }
     135             : 
     136           0 : float CSoundGroup::RadiansOffCenter(const CVector3D& position, bool& onScreen, float& itemRollOff)
     137             : {
     138             : #if !CONFIG2_AUDIO
     139             :         UNUSED2(position);
     140             :         UNUSED2(onScreen);
     141             :         UNUSED2(itemRollOff);
     142             :     return 0.f;
     143             : #else
     144           0 :     const int screenWidth = g_Game->GetView()->GetCamera()->GetViewPort().m_Width;
     145           0 :     const int screenHeight = g_Game->GetView()->GetCamera()->GetViewPort().m_Height;
     146           0 :     const float xBufferSize = screenWidth * 0.1f;
     147           0 :     const float yBufferSize = 15.f;
     148           0 :     const float radianCap = m_MaxStereoAngle;
     149             : 
     150             :     float x, y;
     151           0 :     g_Game->GetView()->GetCamera()->GetScreenCoordinates(position, x, y);
     152             : 
     153           0 :     onScreen = true;
     154           0 :     float answer = 0.f;
     155           0 :     if (x < -xBufferSize)
     156             :     {
     157           0 :         onScreen = false;
     158           0 :         answer = -radianCap;
     159             :     }
     160           0 :     else if (x > screenWidth + xBufferSize)
     161             :     {
     162           0 :         onScreen = false;
     163           0 :         answer = radianCap;
     164             :     }
     165             :     else
     166             :     {
     167           0 :         if (x < 0 || x > screenWidth)
     168           0 :             itemRollOff = MAX_ROLLOFF;
     169             : 
     170           0 :         answer = radianCap * (x * 2 / screenWidth - 1);
     171             :     }
     172             : 
     173           0 :     if (y < -yBufferSize)
     174             :     {
     175           0 :         onScreen = false;
     176             :     }
     177           0 :     else if (y > screenHeight + yBufferSize)
     178             :     {
     179           0 :         onScreen = false;
     180             :     }
     181           0 :     else if (y < 0 || y > screenHeight)
     182             :     {
     183           0 :         itemRollOff = MAX_ROLLOFF;
     184             :     }
     185             : 
     186           0 :     return answer;
     187             : #endif // !CONFIG2_AUDIO
     188             : }
     189             : 
     190           0 : void CSoundGroup::UploadPropertiesAndPlay(size_t index, const CVector3D& position, entity_id_t source)
     191             : {
     192             : #if !CONFIG2_AUDIO
     193             :     UNUSED2(index);
     194             :     UNUSED2(position);
     195             :     UNUSED2(source);
     196             : #else
     197           0 :     if (!g_SoundManager)
     198           0 :         return;
     199             : 
     200           0 :     bool isOnscreen = false;
     201           0 :     ALfloat itemRollOff = DEFAULT_ROLLOFF;
     202           0 :     float offset = RadiansOffCenter(position, isOnscreen, itemRollOff);
     203             : 
     204           0 :     if (!isOnscreen && !TestFlag(eDistanceless) && !TestFlag(eOmnipresent))
     205           0 :         return;
     206             : 
     207           0 :     if (m_SoundGroups.empty())
     208           0 :         Reload();
     209             : 
     210           0 :     if (m_SoundGroups.size() <= index)
     211           0 :         return;
     212             : 
     213           0 :     CSoundData* sndData = m_SoundGroups[index];
     214           0 :     if (!sndData)
     215           0 :         return;
     216             : 
     217           0 :     ISoundItem* hSound = static_cast<CSoundManager*>(g_SoundManager)->ItemForEntity(source, sndData);
     218           0 :     if (!hSound)
     219           0 :         return;
     220             : 
     221           0 :     if (!TestFlag(eOmnipresent))
     222             :     {
     223           0 :         CVector3D origin = g_Game->GetView()->GetCamera()->GetOrientation().GetTranslation();
     224           0 :         float itemDist = (position - origin).Length();
     225             : 
     226           0 :         if (TestFlag(eDistanceless))
     227           0 :             itemRollOff = 0;
     228             : 
     229           0 :         if (sndData->IsStereo())
     230           0 :             LOGWARNING("OpenAL: stereo sounds can't be positioned: %s", sndData->GetFileName().string8());
     231             : 
     232           0 :         hSound->SetLocation(CVector3D(itemDist * sin(offset), 0, -itemDist * cos(offset)));
     233           0 :         hSound->SetRollOff(itemRollOff, m_MinDist, m_MaxDist);
     234             :     }
     235             : 
     236           0 :     CmpPtr<ICmpVisual> cmpVisual(*g_Game->GetSimulation2(), source);
     237           0 :     if (cmpVisual)
     238           0 :         m_Seed = cmpVisual->GetActorSeed();
     239             : 
     240           0 :     hSound->SetPitch(TestFlag(eRandPitch) ? CFastRand::RandFloat(m_Seed, m_PitchLower, m_PitchUpper) : m_Pitch);
     241             : 
     242           0 :     if (TestFlag(eRandGain))
     243           0 :         m_Gain = CFastRand::RandFloat(m_Seed, m_GainLower, m_GainUpper);
     244             : 
     245           0 :     hSound->SetCone(m_ConeInnerAngle, m_ConeOuterAngle, m_ConeOuterGain);
     246           0 :     static_cast<CSoundManager*>(g_SoundManager)->PlayGroupItem(hSound, m_Gain);
     247             : #endif // !CONFIG2_AUDIO
     248             : }
     249             : 
     250           0 : static void HandleError(const std::wstring& message, const VfsPath& pathname, Status err)
     251             : {
     252             :     // Open failed because sound is disabled (don't log this)
     253           0 :     if (err == ERR::AGAIN)
     254           0 :         return;
     255             : 
     256           0 :     LOGERROR("%s: pathname=%s, error=%s", utf8_from_wstring(message), pathname.string8(), GetStatusAsString(err).c_str());
     257             : }
     258             : 
     259           0 : void CSoundGroup::PlayNext(const CVector3D& position, entity_id_t source)
     260             : {
     261           0 :     if (m_Filenames.empty())
     262           0 :         return;
     263             : 
     264           0 :     m_CurrentSoundIndex = rand(0, m_Filenames.size());
     265           0 :     UploadPropertiesAndPlay(m_CurrentSoundIndex, position, source);
     266             : }
     267             : 
     268           0 : void CSoundGroup::Reload()
     269             : {
     270           0 :     m_CurrentSoundIndex = 0;
     271             : #if CONFIG2_AUDIO
     272           0 :     ReleaseGroup();
     273             : 
     274           0 :     if (!g_SoundManager)
     275           0 :         return;
     276             : 
     277           0 :     for (const std::wstring& filename : m_Filenames)
     278             :     {
     279           0 :         VfsPath absolutePath = m_Filepath / filename;
     280           0 :         CSoundData* itemData = CSoundData::SoundDataFromFile(absolutePath);
     281           0 :         if (!itemData)
     282           0 :             HandleError(L"error loading sound", absolutePath, ERR::FAIL);
     283             :         else
     284           0 :             m_SoundGroups.push_back(itemData->IncrementCount());
     285             :     }
     286             : 
     287           0 :     if (TestFlag(eRandOrder))
     288           0 :         std::shuffle(m_SoundGroups.begin(), m_SoundGroups.end(), CFastRand(m_Seed));
     289             : #endif
     290             : }
     291             : 
     292           0 : void CSoundGroup::ReleaseGroup()
     293             : {
     294             : #if CONFIG2_AUDIO
     295           0 :     for (CSoundData* soundGroup : m_SoundGroups)
     296           0 :         CSoundData::ReleaseSoundData(soundGroup);
     297             : 
     298           0 :     m_SoundGroups.clear();
     299             : #endif
     300           0 : }
     301             : 
     302           0 : void CSoundGroup::Update(float UNUSED(TimeSinceLastFrame))
     303             : {
     304           0 : }
     305             : 
     306           0 : bool CSoundGroup::LoadSoundGroup(const VfsPath& pathnameXML)
     307             : {
     308           0 :     CXeromyces XeroFile;
     309           0 :     if (XeroFile.Load(g_VFS, pathnameXML, "sound_group") != PSRETURN_OK)
     310             :     {
     311           0 :         HandleError(L"error loading file", pathnameXML, ERR::FAIL);
     312           0 :         return false;
     313             :     }
     314             : 
     315             : #define EL(x) int el_##x = XeroFile.GetElementID(#x)
     316           0 :     EL(soundgroup);
     317           0 :     EL(gain);
     318           0 :     EL(looping);
     319           0 :     EL(omnipresent);
     320           0 :     EL(heardby);
     321           0 :     EL(distanceless);
     322           0 :     EL(pitch);
     323           0 :     EL(priority);
     324           0 :     EL(randorder);
     325           0 :     EL(randgain);
     326           0 :     EL(randpitch);
     327           0 :     EL(conegain);
     328           0 :     EL(coneinner);
     329           0 :     EL(coneouter);
     330           0 :     EL(sound);
     331           0 :     EL(gainupper);
     332           0 :     EL(gainlower);
     333           0 :     EL(pitchupper);
     334           0 :     EL(pitchlower);
     335           0 :     EL(path);
     336           0 :     EL(threshold);
     337           0 :     EL(decay);
     338             : #undef EL
     339             : 
     340           0 :     XMBElement root = XeroFile.GetRoot();
     341             : 
     342           0 :     if (root.GetNodeName() != el_soundgroup)
     343             :     {
     344           0 :         LOGERROR("Invalid SoundGroup format (unrecognised root element '%s')", XeroFile.GetElementString(root.GetNodeName()));
     345           0 :         return false;
     346             :     }
     347             : 
     348           0 :     XERO_ITER_EL(root, child)
     349             :     {
     350           0 :         int child_name = child.GetNodeName();
     351             : 
     352           0 :         if (child_name == el_gain)
     353             :         {
     354           0 :             SetGain(child.GetText().ToFloat());
     355             :         }
     356           0 :         else if (child_name == el_looping)
     357             :         {
     358           0 :             if (child.GetText().ToInt() == 1)
     359           0 :                 SetFlag(eLoop);
     360             :         }
     361           0 :         else if (child_name == el_omnipresent)
     362             :         {
     363           0 :             if (child.GetText().ToInt() == 1)
     364           0 :                 SetFlag(eOmnipresent);
     365             :         }
     366           0 :         else if (child_name == el_heardby)
     367             :         {
     368           0 :             if (child.GetText().FindInsensitive("owner") == 0)
     369           0 :                 SetFlag(eOwnerOnly);
     370             :         }
     371           0 :         else if (child_name == el_distanceless)
     372             :         {
     373           0 :             if (child.GetText().ToInt() == 1)
     374           0 :                 SetFlag(eDistanceless);
     375             :         }
     376           0 :         else if (child_name == el_pitch)
     377             :         {
     378           0 :             this->m_Pitch = child.GetText().ToFloat();
     379             :         }
     380           0 :         else if (child_name == el_priority)
     381             :         {
     382           0 :             this->m_Priority = child.GetText().ToFloat();
     383             :         }
     384           0 :         else if (child_name == el_randorder)
     385             :         {
     386           0 :             if (child.GetText().ToInt() == 1)
     387           0 :                 SetFlag(eRandOrder);
     388             :         }
     389           0 :         else if (child_name == el_randgain)
     390             :         {
     391           0 :             if (child.GetText().ToInt() == 1)
     392           0 :                 SetFlag(eRandGain);
     393             :         }
     394           0 :         else if (child_name == el_gainupper)
     395             :         {
     396           0 :             this->m_GainUpper = child.GetText().ToFloat();
     397             :         }
     398           0 :         else if (child_name == el_gainlower)
     399             :         {
     400           0 :             this->m_GainLower = child.GetText().ToFloat();
     401             :         }
     402           0 :         else if (child_name == el_randpitch)
     403             :         {
     404           0 :             if (child.GetText().ToInt() == 1)
     405           0 :                 SetFlag(eRandPitch);
     406             :         }
     407           0 :         else if (child_name == el_pitchupper)
     408             :         {
     409           0 :             this->m_PitchUpper = child.GetText().ToFloat();
     410             :         }
     411           0 :         else if (child_name == el_pitchlower)
     412             :         {
     413           0 :             this->m_PitchLower = child.GetText().ToFloat();
     414             :         }
     415           0 :         else if (child_name == el_conegain)
     416             :         {
     417           0 :             this->m_ConeOuterGain = child.GetText().ToFloat();
     418             :         }
     419           0 :         else if (child_name == el_coneinner)
     420             :         {
     421           0 :             this->m_ConeInnerAngle = child.GetText().ToFloat();
     422             :         }
     423           0 :         else if (child_name == el_coneouter)
     424             :         {
     425           0 :             this->m_ConeOuterAngle = child.GetText().ToFloat();
     426             :         }
     427           0 :         else if (child_name == el_sound)
     428             :         {
     429           0 :             this->m_Filenames.push_back(child.GetText().FromUTF8());
     430             :         }
     431           0 :         else if (child_name == el_path)
     432             :         {
     433           0 :             m_Filepath = child.GetText().FromUTF8();
     434             :         }
     435           0 :         else if (child_name == el_threshold)
     436             :         {
     437           0 :             m_IntensityThreshold = child.GetText().ToFloat();
     438             :         }
     439           0 :         else if (child_name == el_decay)
     440             :         {
     441           0 :             m_Decay = child.GetText().ToFloat();
     442             :         }
     443             :     }
     444           0 :     return true;
     445           3 : }

Generated by: LCOV version 1.13