1
0
Fork 1
mirror of https://github.com/TES3MP/openmw-tes3mp.git synced 2025-01-16 04:19:55 +00:00
openmw-tes3mp/apps/openmw/mwsound/soundmanager.cpp
Chris Robinson be337ef7cc Return SoundPtr objects from playSound[3D]
Note that each Sound object currently contains "precious" resources even after
the sound is stopped. The reference should be reliquished as soon as it's no
longer needed (the SoundManager will make sure the sound continues to play
until it's finished).
2012-03-28 06:08:25 -07:00

493 lines
14 KiB
C++

#include "soundmanager.hpp"
#include <iostream>
#include <algorithm>
#include <map>
#include <OgreRoot.h>
#include <components/esm_store/store.hpp>
#include "../mwworld/environment.hpp"
#include "../mwworld/world.hpp"
#include "../mwworld/player.hpp"
#include "sound_output.hpp"
#include "sound_decoder.hpp"
#include "sound.hpp"
#include "openal_output.hpp"
#define SOUND_OUT "OpenAL"
/* Set up the sound manager to use FFMPEG or MPG123+libsndfile for input. The
* OPENMW_USE_x macros are set in CMakeLists.txt.
*/
#ifdef OPENMW_USE_FFMPEG
#include "ffmpeg_decoder.hpp"
#ifndef SOUND_IN
#define SOUND_IN "FFmpeg"
#endif
#endif
#ifdef OPENMW_USE_MPG123
#include "mpgsnd_decoder.hpp"
#ifndef SOUND_IN
#define SOUND_IN "mpg123,sndfile"
#endif
#endif
namespace MWSound
{
SoundManager::SoundManager(bool useSound, MWWorld::Environment& environment)
: mResourceMgr(Ogre::ResourceGroupManager::getSingleton())
, mEnvironment(environment)
{
if(!useSound)
return;
std::cout << "Sound output: " << SOUND_OUT << std::endl;
std::cout << "Sound decoder: " << SOUND_IN << std::endl;
try
{
mOutput.reset(new DEFAULT_OUTPUT(*this));
std::vector<std::string> names = mOutput->enumerate();
std::cout <<"Enumerated output devices:"<< std::endl;
for(size_t i = 0;i < names.size();i++)
std::cout <<" "<<names[i]<< std::endl;
mOutput->init();
}
catch(std::exception &e)
{
std::cout <<"Sound init failed: "<<e.what()<< std::endl;
mOutput.reset();
return;
}
}
SoundManager::~SoundManager()
{
mActiveSounds.clear();
mMusic.reset();
mOutput.reset();
}
// Return a new decoder instance, used as needed by the output implementations
DecoderPtr SoundManager::getDecoder()
{
return DecoderPtr(new DEFAULT_DECODER);
}
// Convert a soundId to file name, and modify the volume
// according to the sounds local volume setting, minRange and
// maxRange.
std::string SoundManager::lookup(const std::string &soundId,
float &volume, float &min, float &max)
{
const ESM::Sound *snd = mEnvironment.mWorld->getStore().sounds.search(soundId);
if(snd == NULL)
throw std::runtime_error(std::string("Failed to lookup sound ")+soundId);
if(snd->data.volume == 0)
volume = 0.0f;
else
volume *= pow(10.0, (snd->data.volume/255.0f*3348.0 - 3348.0) / 2000.0);
if(snd->data.minRange == 0 && snd->data.maxRange == 0)
{
min = 100.0f;
max = 2000.0f;
}
else
{
min = snd->data.minRange * 20.0f;
max = snd->data.maxRange * 50.0f;
min = std::max(min, 1.0f);
max = std::max(min, max);
}
return std::string("Sound/")+snd->sound;
}
bool SoundManager::isPlaying(MWWorld::Ptr ptr, const std::string &id) const
{
SoundMap::const_iterator snditer = mActiveSounds.begin();
while(snditer != mActiveSounds.end())
{
if(snditer->second.first == ptr && snditer->second.second == id)
return snditer->first->isPlaying();
snditer++;
}
return false;
}
void SoundManager::stopMusic()
{
if(mMusic)
mMusic->stop();
mMusic.reset();
}
void SoundManager::streamMusicFull(const std::string& filename)
{
std::cout <<"Playing "<<filename<< std::endl;
try
{
if(mMusic)
mMusic->stop();
mMusic = mOutput->streamSound(filename, 0.4f, 1.0f);
mMusic->mBaseVolume = 0.4f;
}
catch(std::exception &e)
{
std::cout << "Music Error: " << e.what() << "\n";
}
}
void SoundManager::streamMusic(const std::string& filename)
{
streamMusicFull("Music/"+filename);
}
void SoundManager::startRandomTitle()
{
Ogre::StringVectorPtr filelist;
filelist = mResourceMgr.findResourceNames(Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME,
"Music/"+mCurrentPlaylist+"/*");
if(!filelist->size())
return;
int i = rand()%filelist->size();
streamMusicFull((*filelist)[i]);
}
bool SoundManager::isMusicPlaying()
{
return mMusic && mMusic->isPlaying();
}
void SoundManager::playPlaylist(const std::string &playlist)
{
mCurrentPlaylist = playlist;
startRandomTitle();
}
void SoundManager::say(MWWorld::Ptr ptr, const std::string& filename)
{
try
{
// The range values are not tested
float basevol = 1.0f; /* TODO: volume settings */
std::string filePath = std::string("Sound/")+filename;
const ESM::Position &pos = ptr.getCellRef().pos;
SoundPtr sound = mOutput->playSound3D(filePath, pos.pos, basevol, 1.0f,
20.0f, 12750.0f, false);
sound->mBaseVolume = basevol;
mActiveSounds[sound] = std::make_pair(ptr, std::string("_say_sound"));
}
catch(std::exception &e)
{
std::cout <<"Sound Error: "<<e.what()<< std::endl;
}
}
bool SoundManager::sayDone(MWWorld::Ptr ptr) const
{
return !isPlaying(ptr, "_say_sound");
}
SoundPtr SoundManager::playSound(const std::string& soundId, float volume, float pitch, bool loop)
{
SoundPtr sound;
try
{
float basevol = 1.0f; /* TODO: volume settings */
float min, max;
std::string file = lookup(soundId, basevol, min, max);
sound = mOutput->playSound(file, volume*basevol, pitch, loop);
sound->mVolume = volume;
sound->mBaseVolume = basevol;
sound->mMinDistance = min;
sound->mMaxDistance = max;
mActiveSounds[sound] = std::make_pair(MWWorld::Ptr(), soundId);
}
catch(std::exception &e)
{
std::cout <<"Sound Error: "<<e.what()<< std::endl;
}
return sound;
}
SoundPtr SoundManager::playSound3D(MWWorld::Ptr ptr, const std::string& soundId,
float volume, float pitch, bool loop,
bool untracked)
{
SoundPtr sound;
try
{
// Look up the sound in the ESM data
float basevol = 1.0f; /* TODO: volume settings */
float min, max;
std::string file = lookup(soundId, basevol, min, max);
const ESM::Position &pos = ptr.getCellRef().pos;
sound = mOutput->playSound3D(file, pos.pos, volume*basevol, pitch, min, max, loop);
sound->mVolume = volume;
sound->mBaseVolume = basevol;
sound->mMinDistance = min;
sound->mMaxDistance = max;
mActiveSounds[sound] = (!untracked ? std::make_pair(ptr, soundId) :
std::make_pair(MWWorld::Ptr(), soundId));
}
catch(std::exception &e)
{
std::cout <<"Sound Error: "<<e.what()<< std::endl;
}
return sound;
}
void SoundManager::stopSound3D(MWWorld::Ptr ptr, const std::string& soundId)
{
SoundMap::iterator snditer = mActiveSounds.begin();
while(snditer != mActiveSounds.end())
{
if(snditer->second.first == ptr && snditer->second.second == soundId)
{
snditer->first->stop();
mActiveSounds.erase(snditer++);
}
else
snditer++;
}
}
void SoundManager::stopSound3D(MWWorld::Ptr ptr)
{
SoundMap::iterator snditer = mActiveSounds.begin();
while(snditer != mActiveSounds.end())
{
if(snditer->second.first == ptr)
{
snditer->first->stop();
mActiveSounds.erase(snditer++);
}
else
snditer++;
}
}
void SoundManager::stopSound(const MWWorld::Ptr::CellStore *cell)
{
SoundMap::iterator snditer = mActiveSounds.begin();
while(snditer != mActiveSounds.end())
{
if(snditer->second.first != MWWorld::Ptr() &&
snditer->second.first.getCell() == cell)
{
snditer->first->stop();
mActiveSounds.erase(snditer++);
}
else
snditer++;
}
}
void SoundManager::stopSound(const std::string& soundId)
{
SoundMap::iterator snditer = mActiveSounds.begin();
while(snditer != mActiveSounds.end())
{
if(snditer->second.first == MWWorld::Ptr() &&
snditer->second.second == soundId)
{
snditer->first->stop();
mActiveSounds.erase(snditer++);
}
else
snditer++;
}
}
bool SoundManager::getSoundPlaying(MWWorld::Ptr ptr, const std::string& soundId) const
{
return isPlaying(ptr, soundId);
}
void SoundManager::updateObject(MWWorld::Ptr ptr)
{
const ESM::Position &pos = ptr.getCellRef().pos;
SoundMap::iterator snditer = mActiveSounds.begin();
while(snditer != mActiveSounds.end())
{
if(snditer->second.first == ptr)
snditer->first->update(pos.pos);
snditer++;
}
}
void SoundManager::updateRegionSound(float duration)
{
MWWorld::Ptr::CellStore *current = mEnvironment.mWorld->getPlayer().getPlayer().getCell();
static int total = 0;
static std::string regionName = "";
static float timePassed = 0.0;
//If the region has changed
timePassed += duration;
if((current->cell->data.flags & current->cell->Interior) || timePassed < 10)
return;
timePassed = 0;
if(regionName != current->cell->region)
{
regionName = current->cell->region;
total = 0;
}
const ESM::Region *regn = mEnvironment.mWorld->getStore().regions.find(regionName);
std::vector<ESM::Region::SoundRef>::const_iterator soundIter;
if(total == 0)
{
soundIter = regn->soundList.begin();
while(soundIter != regn->soundList.end())
{
total += (int)soundIter->chance;
soundIter++;
}
if(total == 0)
return;
}
int r = (int)(rand()/((double)RAND_MAX+1) * total);
int pos = 0;
soundIter = regn->soundList.begin();
while(soundIter != regn->soundList.end())
{
const std::string go = soundIter->sound.toString();
int chance = (int) soundIter->chance;
//std::cout << "Sound: " << go.name <<" Chance:" << chance << "\n";
soundIter++;
if(r - pos < chance)
{
//play sound
std::cout << "Sound: " << go <<" Chance:" << chance << "\n";
playSound(go, 1.0f, 1.0f);
break;
}
pos += chance;
}
}
void SoundManager::updateSounds(float duration)
{
static float timePassed = 0.0;
timePassed += duration;
if(timePassed < (1.0f/30.0f))
return;
timePassed = 0.0f;
// Make sure music is still playing
if(!isMusicPlaying())
startRandomTitle();
Ogre::Camera *cam = mEnvironment.mWorld->getPlayer().getRenderer()->getCamera();
Ogre::Vector3 nPos, nDir, nUp;
nPos = cam->getRealPosition();
nDir = cam->getRealDirection();
nUp = cam->getRealUp();
// The output handler is expecting vectors oriented like the game
// (that is, -Z goes down, +Y goes forward), but that's not what we
// get from Ogre's camera, so we have to convert.
float pos[3] = { nPos[0], -nPos[2], nPos[1] };
float at[3] = { nDir[0], -nDir[2], nDir[1] };
float up[3] = { nUp[0], -nUp[2], nUp[1] };
mOutput->updateListener(pos, at, up);
// Check if any sounds are finished playing, and trash them
SoundMap::iterator snditer = mActiveSounds.begin();
while(snditer != mActiveSounds.end())
{
if(!snditer->first->isPlaying())
mActiveSounds.erase(snditer++);
else
snditer++;
}
}
void SoundManager::update(float duration)
{
updateSounds(duration);
updateRegionSound(duration);
}
// Default readAll implementation, for decoders that can't do anything
// better
void Sound_Decoder::readAll(std::vector<char> &output)
{
size_t total = output.size();
size_t got;
output.resize(total+32768);
while((got=read(&output[total], output.size()-total)) > 0)
{
total += got;
output.resize(total*2);
}
output.resize(total);
}
const char *getSampleTypeName(SampleType type)
{
switch(type)
{
case SampleType_UInt8: return "U8";
case SampleType_Int16: return "S16";
}
return "(unknown sample type)";
}
const char *getChannelConfigName(ChannelConfig config)
{
switch(config)
{
case ChannelConfig_Mono: return "Mono";
case ChannelConfig_Stereo: return "Stereo";
}
return "(unknown channel config)";
}
size_t framesToBytes(size_t frames, ChannelConfig config, SampleType type)
{
switch(config)
{
case ChannelConfig_Mono: frames *= 1; break;
case ChannelConfig_Stereo: frames *= 2; break;
}
switch(type)
{
case SampleType_UInt8: frames *= 1; break;
case SampleType_Int16: frames *= 2; break;
}
return frames;
}
size_t bytesToFrames(size_t bytes, ChannelConfig config, SampleType type)
{
return bytes / framesToBytes(1, config, type);
}
}